LT-259 New proactive UI (three options) for unsaved changes dialog + New API for unsaved changes module (now handlers can be attached to unsaved changes namespace) + Save measurements integration.
This commit is contained in:
1 parent
4a3b01a4f6
commit
91bafb367c
10 files changed
+671
-173
No files matched your search
@@ -0,0 +1,35 @@
|
||||
<template name="unsavedChangesDialog">
|
||||
<div class="modal unsavedChangesDialog fade" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button class="close" aria-label="Close" data-choice="abort-action" data-dismiss="modal">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h4 class="modal-title">You have unsaved changes!</h4>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
||||
{{#let label='Stay'}}
|
||||
<button class="btn btn-default" aria-label="{{label}}" data-choice="abort-action" data-dismiss="modal">
|
||||
{{label}}
|
||||
</button>
|
||||
{{/let}}
|
||||
|
||||
{{#let label='Leave without saving...'}}
|
||||
<button class="btn btn-danger" aria-label="{{label}}" data-choice="abandon-changes" data-dismiss="modal">
|
||||
{{label}}
|
||||
</button>
|
||||
{{/let}}
|
||||
|
||||
{{#let label='Save and Exit!'}}
|
||||
<button class="btn btn-primary" aria-label="{{label}}" data-choice="save-changes" data-dismiss="modal">
|
||||
{{label}}
|
||||
</button>
|
||||
{{/let}}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,133 @@
|
||||
import { Blaze } from 'meteor/blaze';
|
||||
import { Template } from 'meteor/templating';
|
||||
import { OHIF } from 'meteor/ohif:core';
|
||||
import { _ } from 'meteor/underscore';
|
||||
import { $ } from 'meteor/jquery';
|
||||
|
||||
const MARGIN_RIGHT = 15;
|
||||
const MARGIN_BOTTOM = 15;
|
||||
|
||||
Template.unsavedChangesDialog.onRendered(function() {
|
||||
|
||||
const instance = Template.instance();
|
||||
const $modal = instance.$('.modal.unsavedChangesDialog');
|
||||
|
||||
// Routine which effectivelly displays the BS modal...
|
||||
instance.displayModal = () => {
|
||||
|
||||
// Make modal options extensible...
|
||||
const modalOptions = _.extend({
|
||||
backdrop: 'static',
|
||||
keyboard: false
|
||||
}, instance.data.modalOptions);
|
||||
|
||||
// Set handler for "hidden" event... Simply remove the view!
|
||||
$modal.on('hidden.bs.modal', () => {
|
||||
Blaze.remove(instance.view);
|
||||
});
|
||||
|
||||
// Create the bootstrap modal
|
||||
$modal.modal(modalOptions);
|
||||
|
||||
};
|
||||
|
||||
// Routine which repositions the modal before display...
|
||||
instance.displayModalWithPosition = (position) => {
|
||||
|
||||
// Preserve original CSS rules...
|
||||
const origCSS = {
|
||||
display: $modal.css('display'),
|
||||
visibility: $modal.css('visibility')
|
||||
};
|
||||
|
||||
// Make sure modal is propperly rendered before proceeding with math...
|
||||
if (origCSS.display === 'none') {
|
||||
$modal.css({
|
||||
visibility: 'hidden',
|
||||
display: 'block'
|
||||
});
|
||||
}
|
||||
|
||||
// Run presentation code on next tick...
|
||||
setTimeout(() => {
|
||||
|
||||
let dimension;
|
||||
const $dialog = $modal.find('.modal-dialog');
|
||||
|
||||
const dialogRect = {
|
||||
position: {
|
||||
x: parseInt(position.x) || 0,
|
||||
y: parseInt(position.y) || 0
|
||||
},
|
||||
size: {
|
||||
width: $dialog.outerWidth(),
|
||||
height: $dialog.outerHeight()
|
||||
}
|
||||
};
|
||||
|
||||
const modalSize = {
|
||||
width: $modal.width(),
|
||||
height: $modal.height()
|
||||
};
|
||||
|
||||
dimension = dialogRect.position.x + dialogRect.size.width + MARGIN_RIGHT;
|
||||
if (dimension > modalSize.width) {
|
||||
dialogRect.position.x -= dimension - modalSize.width;
|
||||
}
|
||||
|
||||
if (dialogRect.position.x < 0) {
|
||||
dialogRect.position.x = 0;
|
||||
}
|
||||
|
||||
dimension = dialogRect.position.y + dialogRect.size.height + MARGIN_BOTTOM;
|
||||
if (dimension > modalSize.height) {
|
||||
dialogRect.position.y -= dimension - modalSize.height;
|
||||
}
|
||||
|
||||
if (dialogRect.position.y < 0) {
|
||||
dialogRect.position.y = 0;
|
||||
}
|
||||
|
||||
// Restore original CSS...
|
||||
$modal.css(origCSS);
|
||||
|
||||
// Set new position...
|
||||
$dialog.css({
|
||||
position: 'fixed',
|
||||
margin: 0,
|
||||
left: dialogRect.position.x,
|
||||
top: dialogRect.position.y
|
||||
});
|
||||
|
||||
instance.displayModal();
|
||||
|
||||
}, 0);
|
||||
|
||||
};
|
||||
|
||||
// Check if modal will be presented with custom positioning...
|
||||
let position = instance.data.position;
|
||||
if (position && 'x' in position && 'y' in position) {
|
||||
instance.displayModalWithPosition(position);
|
||||
} else {
|
||||
instance.displayModal();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
Template.unsavedChangesDialog.events({
|
||||
|
||||
'click button[data-choice]'(event) {
|
||||
|
||||
const instance = Template.instance();
|
||||
const callback = instance.data.callback;
|
||||
const choice = $(event.currentTarget).attr('data-choice') || '';
|
||||
|
||||
// if callback is a function, call it passing user choice...
|
||||
if (typeof callback === 'function') {
|
||||
callback.call(instance, choice);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
.modal.unsavedChangesDialog
|
||||
|
||||
.modal-dialog
|
||||
width: 400px
|
||||
|
||||
.modal-footer
|
||||
border-top: 0 none
|
||||
@@ -3,6 +3,8 @@ import './dialog/form.html';
|
||||
import './dialog/form.js';
|
||||
import './dialog/login.html';
|
||||
import './dialog/login.js';
|
||||
import './dialog/unsavedChangesDialog.html';
|
||||
import './dialog/unsavedChangesDialog.js';
|
||||
|
||||
import './dropdown/backdrop.html';
|
||||
import './dropdown/form.html';
|
||||
|
||||
Reference in new issue
Block a user