diff --git a/front/core/components/confirm/confirm.html b/front/core/components/confirm/confirm.html index a9765c49d..2b659ba61 100644 --- a/front/core/components/confirm/confirm.html +++ b/front/core/components/confirm/confirm.html @@ -1,4 +1,4 @@ - +
{{::$ctrl.message}}
{{::$ctrl.question}} @@ -7,4 +7,4 @@ - \ No newline at end of file +
\ No newline at end of file diff --git a/front/core/components/confirm/confirm.js b/front/core/components/confirm/confirm.js index 1431cf28a..8b27d8295 100644 --- a/front/core/components/confirm/confirm.js +++ b/front/core/components/confirm/confirm.js @@ -1,24 +1,27 @@ import ngModule from '../../module'; -import Dialog from '../dialog/dialog'; +import Dialog from '../dialog'; import template from './confirm.html'; export default class Confirm extends Dialog { - constructor($element, $scope, $compile) { - super($element); - let cTemplate = $compile(template)($scope)[0]; - this.body = cTemplate.querySelector('tpl-body'); - this.buttons = cTemplate.querySelector('tpl-buttons'); + constructor($element, $, $transclude) { + super($element, $, $transclude); + + let $template = angular.element(template); + let slots = $transclude.$$boundTransclude.$$slots; + + let bodyLinkFn = this.$compile($template.find('tpl-body')); + slots.body = this.createBoundTranscludeFn(bodyLinkFn); + + let buttonsLinkFn = this.$compile($template.find('tpl-buttons')); + slots.buttons = this.createBoundTranscludeFn(buttonsLinkFn); } } -Confirm.$inject = ['$element', '$scope', '$compile']; -ngModule.component('vnConfirm', { - template: require('../dialog/dialog.html'), +ngModule.vnComponent('vnConfirm', { + controller: Confirm, + transclude: true, bindings: { - onOpen: '&?', - onResponse: '&', question: '@', message: '@?' - }, - controller: Confirm + } }); diff --git a/front/core/components/dialog/dialog.html b/front/core/components/dialog/dialog.html deleted file mode 100644 index 1ce4eea58..000000000 --- a/front/core/components/dialog/dialog.html +++ /dev/null @@ -1,17 +0,0 @@ -
- -
-
-
-
-
-
-
\ No newline at end of file diff --git a/front/core/components/dialog/dialog.js b/front/core/components/dialog/dialog.js deleted file mode 100644 index 4c4535b4c..000000000 --- a/front/core/components/dialog/dialog.js +++ /dev/null @@ -1,126 +0,0 @@ -import ngModule from '../../module'; -import Component from '../../lib/component'; -import './style.scss'; - -/** - * Dialog component. - * - * @property {HTMLElement} body The dialog HTML body - * @property {HTMLElement} buttons The dialog HTML buttons - */ -export default class Dialog extends Component { - constructor($element, $scope, $transclude) { - super($element, $scope); - this.shown = false; - this.$element.addClass('vn-dialog'); - this.element.addEventListener('mousedown', - e => this.onBackgroundMouseDown(e)); - - if ($transclude) { - $transclude($scope.$parent, tClone => { - this.body = tClone[0]; - }, null, 'body'); - $transclude($scope.$parent, tClone => { - this.buttons = tClone[0]; - }, null, 'buttons'); - } - } - - set body(value) { - this.element.querySelector('.body').appendChild(value); - } - - set buttons(value) { - this.element.querySelector('.buttons').appendChild(value); - } - - /** - * Displays the dialog to the user. - */ - show() { - if (this.shown) return; - this.shown = true; - this.keyDownHandler = e => this.onkeyDown(e); - this.document.addEventListener('keydown', this.keyDownHandler); - this.element.style.display = 'flex'; - this.transitionTimeout = setTimeout(() => this.$element.addClass('shown'), 30); - - this.emit('open'); - } - - /** - * Hides the dialog calling the response handler. - */ - hide() { - this.fireResponse(); - this.realHide(); - this.emit('close'); - } - - /** - * Calls the response handler. - * - * @param {String} response The response code - * @return {Boolean} %true if response was canceled, %false otherwise - */ - fireResponse(response) { - let cancel = false; - if (this.onResponse) - cancel = this.onResponse({response: response}); - return cancel; - } - - realHide() { - if (!this.shown) return; - this.element.style.display = 'none'; - this.document.removeEventListener('keydown', this.keyDownHandler); - this.lastEvent = null; - this.shown = false; - this.transitionTimeout = setTimeout(() => this.$element.removeClass('shown'), 30); - } - - onButtonClick(event) { - let buttons = this.element.querySelector('.buttons'); - let tplButtons = buttons.querySelector('tpl-buttons'); - let node = event.target; - while (node.parentNode != tplButtons) { - if (node == buttons) return; - node = node.parentNode; - } - - let response = node.getAttribute('response'); - let cancel = this.fireResponse(response); - if (cancel !== false) this.realHide(); - } - - onDialogMouseDown(event) { - this.lastEvent = event; - } - - onBackgroundMouseDown(event) { - if (event != this.lastEvent) - this.hide(); - } - - onkeyDown(event) { - if (event.keyCode == 27) // Esc - this.hide(); - } - - $onDestroy() { - clearTimeout(this.transitionTimeout); - } -} -Dialog.$inject = ['$element', '$scope', '$transclude']; - -ngModule.component('vnDialog', { - template: require('./dialog.html'), - transclude: { - body: 'tplBody', - buttons: '?tplButtons' - }, - bindings: { - onResponse: '&?' - }, - controller: Dialog -}); diff --git a/front/core/components/dialog/index.html b/front/core/components/dialog/index.html new file mode 100644 index 000000000..066449aaf --- /dev/null +++ b/front/core/components/dialog/index.html @@ -0,0 +1,17 @@ + + +
+
+
+
+
+
\ No newline at end of file diff --git a/front/core/components/dialog/index.js b/front/core/components/dialog/index.js new file mode 100644 index 000000000..8bc3dfc85 --- /dev/null +++ b/front/core/components/dialog/index.js @@ -0,0 +1,73 @@ +import ngModule from '../../module'; +import Popup from '../popup'; +import template from './index.html'; +import './style.scss'; + +/** + * Dialog component. + * + * @property {HTMLElement} body The dialog HTML body + * @property {HTMLElement} buttons The dialog HTML buttons + */ +export default class Dialog extends Popup { + constructor($element, $, $transclude) { + super($element, $, $transclude); + + let linkFn = this.$compile(template); + $transclude.$$boundTransclude = this.createBoundTranscludeFn(linkFn); + } + + /** + * Hides the dialog calling the response handler. + */ + hide() { + // this.fireResponse(); + super.hide(); + } + + /** + * Calls the response handler. + * + * @param {String} response The response code + * @return {Boolean} %true if response was canceled, %false otherwise + */ + fireResponse(response) { + let cancel; + if (this.onResponse) + cancel = this.onResponse({response: response}); + return cancel; + } + + onButtonClick(event) { + let buttons = this.popup.querySelector('.buttons'); + let tplButtons = buttons.querySelector('tpl-buttons'); + let node = event.target; + while (node.parentNode != tplButtons) { + if (node == buttons) return; + node = node.parentNode; + } + + let response = node.getAttribute('response'); + let cancel = this.fireResponse(response); + + let close = res => { + if (res !== false) super.hide(); + }; + + if (cancel instanceof Object && cancel.then) + cancel.then(close); + else + close(cancel); + } +} + +ngModule.vnComponent('vnDialog', { + controller: Dialog, + transclude: { + body: 'tplBody', + buttons: '?tplButtons' + }, + bindings: { + onResponse: '&?' + } +}); diff --git a/front/core/components/dialog/dialog.spec.js b/front/core/components/dialog/index.spec.js similarity index 100% rename from front/core/components/dialog/dialog.spec.js rename to front/core/components/dialog/index.spec.js diff --git a/front/core/components/dialog/style.scss b/front/core/components/dialog/style.scss index e6e4f407d..18def2243 100644 --- a/front/core/components/dialog/style.scss +++ b/front/core/components/dialog/style.scss @@ -1,55 +1,27 @@ @import "effects"; -.vn-dialog { - display: none; - justify-content: center; - align-items: center; - z-index: 20; - position: fixed; - left: 0; - top: 0; - height: 100%; - width: 100%; - background-color: rgba(0, 0, 0, .6); - opacity: 0; - transition: opacity 300ms ease-in-out; - padding: 3em; - box-sizing: border-box; +.vn-dialog > .window { + padding: $spacing-lg; - &.shown { - opacity: 1; + & > .close { + @extend %clickable; + text-transform: uppercase; + background-color: transparent; + border: none; + border-radius: .1em; + position: absolute; + top: 0; + right: 0; + padding: .3em; + color: #666; } - & > div { - position: relative; - box-shadow: 0 0 .4em $color-shadow; - background-color: $color-bg-panel; - border-radius: .2em; - overflow: auto; - padding: 2em; - box-sizing: border-box; - max-height: 100%; - - tpl-body { + & > form { + & > .body > tpl-body { display: block; min-width: 16em; } - & > button.close { - @extend %clickable; - text-transform: uppercase; - background-color: transparent; - border: none; - border-radius: .1em; - position: absolute; - top: 0; - right: 0; - padding: .3em; - - & > vn-icon { - display: block; - color: #666; - } - } - & > form > .buttons { + & > .buttons > tpl-buttons { + display: block; margin-top: 1.5em; text-align: right; diff --git a/front/core/components/index.js b/front/core/components/index.js index 700765767..d59da46ea 100644 --- a/front/core/components/index.js +++ b/front/core/components/index.js @@ -3,7 +3,6 @@ import './crud-model/crud-model'; import './watcher/watcher'; import './textfield/textfield'; import './icon/icon'; -import './dialog/dialog'; import './confirm/confirm'; import './title/title'; import './subtitle/subtitle'; @@ -28,6 +27,7 @@ import './chip'; import './data-viewer'; import './date-picker'; import './debug-info'; +import './dialog'; import './field'; import './float-button'; import './icon-menu'; @@ -39,6 +39,7 @@ import './input-time'; import './input-file'; import './label'; import './list'; +import './popup'; import './radio'; import './submit'; import './table'; diff --git a/front/core/components/popup/index.html b/front/core/components/popup/index.html new file mode 100644 index 000000000..3e542d51f --- /dev/null +++ b/front/core/components/popup/index.html @@ -0,0 +1,7 @@ +
+
+
+
\ No newline at end of file diff --git a/front/core/components/popup/index.js b/front/core/components/popup/index.js new file mode 100644 index 000000000..1d22adff1 --- /dev/null +++ b/front/core/components/popup/index.js @@ -0,0 +1,125 @@ +import ngModule from '../../module'; +import Component from '../../lib/component'; +import template from './index.html'; +import './style.scss'; + +/** + * Base class for windows displayed over application content. + */ +export default class Popup extends Component { + constructor($element, $scope, $transclude) { + super($element, $scope); + this.$transclude = $transclude; + this._shown = false; + this.displayMode = 'centered'; + } + + $onDestroy() { + this.hide(); + } + + /** + * @type {Boolean} Wether to show or hide the popup. + */ + get shown() { + return this._shown; + } + + set shown(value) { + if (value) + this.show(); + else + this.hide(); + } + + /** + * Displays the dialog to the user. + */ + show() { + if (this.shown) return; + this._shown = true; + + let linkFn = this.$compile(template); + this.$contentScope = this.$.$new(); + this.popup = linkFn(this.$contentScope, null, + {parentBoundTranscludeFn: this.$transclude} + )[0]; + + let classList = this.popup.classList; + classList.add(this.displayMode); + classList.add(...this.constructor.$classNames); + + if (!this.transitionTimeout) + this.document.body.appendChild(this.popup); + + this.keyDownHandler = e => this.onkeyDown(e); + this.document.addEventListener('keydown', this.keyDownHandler); + + this.deregisterCallback = this.$transitions.onStart({}, + () => this.hide()); + + this.$timeout.cancel(this.transitionTimeout); + this.transitionTimeout = this.$timeout(() => { + this.transitionTimeout = null; + classList.add('shown'); + }, 10); + + this.emit('open'); + } + + /** + * Hides the dialog calling the response handler. + */ + hide() { + if (!this.shown) return; + + this.document.removeEventListener('keydown', this.keyDownHandler); + this.keyDownHandler = null; + + if (this.deregisterCallback) { + this.deregisterCallback(); + this.deregisterCallback = null; + } + + this.popup.classList.remove('shown'); + + this.$timeout.cancel(this.transitionTimeout); + this.transitionTimeout = this.$timeout(() => { + this.transitionTimeout = null; + this.document.body.removeChild(this.popup); + + this.$contentScope.$destroy(); + this.popup.remove(); + this.popup = null; + + this.emit('close'); + }, 200); + + this.lastEvent = null; + this._shown = false; + this.emit('closeStart'); + } + + onWindowMouseDown(event) { + this.lastEvent = event; + } + + onBgMouseDown(event) { + if (!event.defaultPrevented && event != this.lastEvent) + this.hide(); + } + + onkeyDown(event) { + if (!event.defaultPrevented && event.key == 'Escape') + this.hide(); + } +} +Popup.$inject = ['$element', '$scope', '$transclude']; + +ngModule.vnComponent('vnPopup', { + controller: Popup, + transclude: true, + bindings: { + shown: '=?' + } +}); diff --git a/front/core/components/popup/style.scss b/front/core/components/popup/style.scss new file mode 100644 index 000000000..9611e13e7 --- /dev/null +++ b/front/core/components/popup/style.scss @@ -0,0 +1,40 @@ +@import "effects"; + +.vn-popup { + z-index: 20; + position: fixed; + left: 0; + top: 0; + height: 100%; + width: 100%; + opacity: 0; + transition: opacity 200ms ease-in-out; + + &.shown { + opacity: 1; + transform: scale3d(1, 1, 1); + } + &.centered { + display: flex; + justify-content: center; + align-items: center; + background-color: rgba(0, 0, 0, .6); + padding: 1em; + box-sizing: border-box; + + & > .window { + position: relative; + box-shadow: 0 0 .4em $color-shadow; + background-color: $color-bg-panel; + border-radius: .2em; + overflow: auto; + box-sizing: border-box; + max-height: 100%; + transform: scale3d(.5, .5, .5); + transition: transform 200ms ease-in-out; + } + &.shown > .window { + transform: scale3d(1, 1, 1); + } + } +} diff --git a/front/core/directives/dialog.js b/front/core/directives/dialog.js index 3aadcaad0..167bd04e7 100644 --- a/front/core/directives/dialog.js +++ b/front/core/directives/dialog.js @@ -1,5 +1,5 @@ import ngModule from '../module'; -import Dialog from '../components/dialog/dialog'; +import Dialog from '../components/dialog'; import {kebabToCamel} from '../lib/string'; /** diff --git a/front/core/lib/component.js b/front/core/lib/component.js index 6373ee7a1..173a46e9f 100644 --- a/front/core/lib/component.js +++ b/front/core/lib/component.js @@ -56,6 +56,31 @@ export default class Component extends EventEmitter { $t(string, params) { return this.$translate.instant(string, params); } + + createBoundTranscludeFn(transcludeFn) { + let scope = this.$; + let previousBoundTranscludeFn = this.$transclude.$$boundTransclude; + + function vnBoundTranscludeFn( + transcludedScope, + cloneFn, + controllers, + futureParentElement, + containingScope) { + if (!transcludedScope) { + transcludedScope = scope.$new(false, containingScope); + transcludedScope.$$transcluded = true; + } + return transcludeFn(transcludedScope, cloneFn, { + parentBoundTranscludeFn: previousBoundTranscludeFn, + transcludeControllers: controllers, + futureParentElement: futureParentElement + }); + } + vnBoundTranscludeFn.$$slots = previousBoundTranscludeFn.$$slots; + + return vnBoundTranscludeFn; + } } Component.$inject = ['$element', '$scope']; diff --git a/front/core/lib/string.js b/front/core/lib/string.js index 582485cb1..50bee9873 100644 --- a/front/core/lib/string.js +++ b/front/core/lib/string.js @@ -6,10 +6,19 @@ * @return {String} The camelized string */ export function kebabToCamel(str) { - let camelCased = str.replace(/-([a-z])/g, function(g) { - return g[1].toUpperCase(); - }); - return camelCased; + return str.replace(/-([a-z])/g, g => g[1].toUpperCase()); +} + +/** + * Transforms a camelCase to kebab-case. + * + * @param {String} str The camelized string + * @return {String} The hyphenized string + */ +export function camelToKebab(str) { + let kebabCased = str.substr(1) + .replace(/[A-Z]/g, g => `-${g[0]}`); + return `${str.charAt(0)}${kebabCased}`.toLowerCase(); } /** diff --git a/front/core/module.js b/front/core/module.js index 7a955777d..33eb68c24 100644 --- a/front/core/module.js +++ b/front/core/module.js @@ -1,4 +1,5 @@ import {ng, ngDeps} from './vendor'; +import {camelToKebab} from './lib/string'; const ngModule = ng.module('vnCore', ngDeps); export default ngModule; @@ -21,14 +22,24 @@ ngModule.vnComponent = function(name, options) { let parent = Object.getPrototypeOf(controller); let parentOptions = parent.$options || {}; + let parentTransclude = parentOptions.transclude; + let transclude = parentTransclude instanceof Object + ? Object.assign({}, parentTransclude) + : parentTransclude; + + if (options.transclude instanceof Object) { + if (transclude instanceof Object) + Object.assign(transclude, options.transclude); + else + transclude = options.transclude; + } else if (options.transclude !== undefined) + transclude = options.transclude; + let mergedOptions = Object.assign({}, parentOptions, options, { - transclude: Object.assign({}, - parentOptions.transclude, - options.transclude - ), + transclude, bindings: Object.assign({}, parentOptions.bindings, options.bindings @@ -41,6 +52,10 @@ ngModule.vnComponent = function(name, options) { ); controller.$options = mergedOptions; + let classNames = [camelToKebab(name)]; + if (parent.$classNames) classNames = classNames.concat(parent.$classNames); + controller.$classNames = classNames; + return this.component(name, mergedOptions); }; diff --git a/front/core/styles/effects.scss b/front/core/styles/effects.scss index 92ae84c03..205a23bd2 100644 --- a/front/core/styles/effects.scss +++ b/front/core/styles/effects.scss @@ -4,8 +4,10 @@ cursor: pointer; transition: background-color 250ms ease-out; - &:hover { + &:hover, + &:focus { background-color: $color-hover-cd; + outline: none; } } @@ -13,8 +15,10 @@ cursor: pointer; transition: opacity 250ms ease-out; - &:hover { + &:hover, + &:focus { opacity: $color-hover-dc; + outline: none; } } diff --git a/front/salix/components/summary/style.scss b/front/salix/components/summary/style.scss index fbab066f8..58a613088 100644 --- a/front/salix/components/summary/style.scss +++ b/front/salix/components/summary/style.scss @@ -56,24 +56,7 @@ } } -.vn-dialog.dialog-summary { - vn-card { - border: none; - box-shadow: none; - } - & > div > button.close { - display: none; - } - & > div { - padding: 0 - } - tpl-body { - width: auto; - } - .buttons { - display: none; - } - vn-check label span { - font-size: .9em - } +.vn-popup .summary { + border: none; + box-shadow: none; } diff --git a/modules/agency/front/index/index.html b/modules/agency/front/index/index.html index e68446032..1e6d22727 100644 --- a/modules/agency/front/index/index.html +++ b/modules/agency/front/index/index.html @@ -63,13 +63,9 @@ - - - - - + + + diff --git a/modules/claim/front/index/index.html b/modules/claim/front/index/index.html index ef7ea04b6..ca232b3dc 100644 --- a/modules/claim/front/index/index.html +++ b/modules/claim/front/index/index.html @@ -74,10 +74,7 @@ vn-id="workerDescriptor" worker-fk="$ctrl.selectedWorker"> - - - - - + + + \ No newline at end of file diff --git a/modules/client/front/index/index.html b/modules/client/front/index/index.html index 0213bc04f..9ce0c9876 100644 --- a/modules/client/front/index/index.html +++ b/modules/client/front/index/index.html @@ -69,12 +69,9 @@ fixed-bottom-right> - - - - - - + + + + \ No newline at end of file diff --git a/modules/invoiceOut/front/index/index.html b/modules/invoiceOut/front/index/index.html index 8aa383926..1a74b28b4 100644 --- a/modules/invoiceOut/front/index/index.html +++ b/modules/invoiceOut/front/index/index.html @@ -71,15 +71,11 @@ - - - - - - + + + + diff --git a/modules/item/front/index/index.html b/modules/item/front/index/index.html index b3df8d575..5f91ccf82 100644 --- a/modules/item/front/index/index.html +++ b/modules/item/front/index/index.html @@ -131,11 +131,9 @@ - - - - - + + + - - - - - + + + \ No newline at end of file diff --git a/modules/route/front/index/index.html b/modules/route/front/index/index.html index c8ad62c04..9852eb880 100644 --- a/modules/route/front/index/index.html +++ b/modules/route/front/index/index.html @@ -63,15 +63,11 @@ - - - - - - + + + + diff --git a/modules/ticket/front/descriptor/index.html b/modules/ticket/front/descriptor/index.html index 9b981f27a..a2bc8ca30 100644 --- a/modules/ticket/front/descriptor/index.html +++ b/modules/ticket/front/descriptor/index.html @@ -91,46 +91,43 @@ - - -
-
- In which day you want to add the ticket? -
- - - - - - - - - - - - - - - - -
-
-
+ +
+
+ In which day you want to add the ticket? +
+ + + + + + + + + + + + + + + + +
+
@@ -166,7 +162,8 @@ vn-id="newShipped" vn-one ng-model="$ctrl.newShipped" - label="Shipped hour"> + label="Shipped hour" + vn-focus>
diff --git a/modules/ticket/front/descriptor/index.js b/modules/ticket/front/descriptor/index.js index 1ea16fa43..e77f469fe 100644 --- a/modules/ticket/front/descriptor/index.js +++ b/modules/ticket/front/descriptor/index.js @@ -283,10 +283,6 @@ class Controller { ); } } - - focusInput() { - this.$scope.newShipped.focus(); - } } Controller.$inject = ['$state', '$scope', '$http', 'vnApp', '$translate', 'aclService']; diff --git a/modules/ticket/front/index/index.html b/modules/ticket/front/index/index.html index 427eacc2b..1680d94d5 100644 --- a/modules/ticket/front/index/index.html +++ b/modules/ticket/front/index/index.html @@ -148,13 +148,9 @@ - - - - - + + + diff --git a/modules/ticket/front/sale/index.html b/modules/ticket/front/sale/index.html index 0b924d32e..b158e3e9a 100644 --- a/modules/ticket/front/sale/index.html +++ b/modules/ticket/front/sale/index.html @@ -204,8 +204,7 @@ - @@ -239,8 +238,7 @@ - diff --git a/modules/travel/front/index/index.html b/modules/travel/front/index/index.html index 56e2ebd0f..dc24e2738 100644 --- a/modules/travel/front/index/index.html +++ b/modules/travel/front/index/index.html @@ -60,13 +60,9 @@ - - - - - - + + + + \ No newline at end of file diff --git a/modules/worker/front/index/index.html b/modules/worker/front/index/index.html index 629a8c275..4568d982a 100644 --- a/modules/worker/front/index/index.html +++ b/modules/worker/front/index/index.html @@ -29,45 +29,44 @@ model="model" class="vn-my-md"> - - - - - - - + + + + \ No newline at end of file diff --git a/modules/worker/front/index/index.js b/modules/worker/front/index/index.js index adaaf6d40..6a1c2f65c 100644 --- a/modules/worker/front/index/index.js +++ b/modules/worker/front/index/index.js @@ -31,7 +31,6 @@ export default class Controller { callback.call(this); } } - Controller.$inject = ['$scope', '$state']; ngModule.component('vnWorkerIndex', { diff --git a/modules/worker/front/time-control/index.js b/modules/worker/front/time-control/index.js index c316602a5..953079b87 100644 --- a/modules/worker/front/time-control/index.js +++ b/modules/worker/front/time-control/index.js @@ -147,10 +147,6 @@ class Controller { this.newTime = now; this.selectedWeekday = weekday; this.$.addTimeDialog.show(); - - const selector = '[vn-id=addTimeDialog] input[type=time]'; - const input = this.$element[0].querySelector(selector); - input.focus(); } addTime(response) {