diff --git a/dev/App/Abstract.jsx b/dev/App/Abstract.jsx index 14f21d3c1..a942f83b7 100644 --- a/dev/App/Abstract.jsx +++ b/dev/App/Abstract.jsx @@ -10,19 +10,24 @@ import { sUserAgent, bMobileDevice, bAnimationSupported } from 'Common/Globals'; +import { + noop, isNormal, pString, inArray, microtime, timestamp, + detectDropdownVisibility, windowResizeCallback +} from 'Common/Utils'; + import {KeyState} from 'Common/Enums'; -import {noop, isNormal, pString, inArray, microtime, timestamp, detectDropdownVisibility, windowResizeCallback} from 'Common/Utils'; import * as Links from 'Common/Links'; import * as Settings from 'Storage/Settings'; import * as Events from 'Common/Events'; import {initOnStartOrLangChange, initNotificationLanguage} from 'Common/Translator'; +import {toggle as toggleCmd} from 'Common/Cmd'; +import {routeOff, setHash} from 'Knoin/Knoin'; import {AbstractBoot} from 'Knoin/AbstractBoot'; class AbstractApp extends AbstractBoot { /** - * @constructor * @param {RemoteStorage|AdminRemoteStorage} Remote */ constructor(Remote) @@ -62,13 +67,12 @@ class AbstractApp extends AbstractBoot } }); - $win.on('resize', function() { + $win.on('resize', () => { Events.pub('window.resize'); }); - Events.sub('window.resize', _.throttle(function() { - - var + Events.sub('window.resize', _.throttle(() => { + const iH = $win.height(), iW = $win.height(); @@ -79,7 +83,6 @@ class AbstractApp extends AbstractBoot Events.pub('window.resize.real'); } - }, 50)); // DEBUG @@ -92,25 +95,29 @@ class AbstractApp extends AbstractBoot // } // }); - $doc.on('keydown', function(oEvent) { - if (oEvent && oEvent.ctrlKey) + $doc.on('keydown', (event) => { + if (event && event.ctrlKey) { $html.addClass('rl-ctrl-key-pressed'); } - }).on('keyup', function(oEvent) { - if (oEvent && !oEvent.ctrlKey) + }).on('keyup', (event) => { + if (event && !event.ctrlKey) { $html.removeClass('rl-ctrl-key-pressed'); } }); - $doc.on('mousemove keypress click', _.debounce(function() { + $doc.on('mousemove keypress click', _.debounce(() => { Events.pub('rl.auto-logout-refresh'); }, 5000)); - key('esc, enter', KeyState.All, _.bind(function() { + key('esc, enter', KeyState.All, () => { detectDropdownVisibility(); - }, this)); + }); + + key('ctrl+shift+`', KeyState.All, () => { + toggleCmd(); + }); } remote() { @@ -223,7 +230,6 @@ class AbstractApp extends AbstractBoot loginAndLogoutReload(admin = false, logout = false, close = false) { const - kn = require('Knoin/Knoin'), mobile = Settings.appSettingsGet('mobile'), inIframe = !!Settings.appSettingsGet('inIframe'); @@ -256,9 +262,9 @@ class AbstractApp extends AbstractBoot } else { - kn.routeOff(); - kn.setHash(Links.root(), true); - kn.routeOff(); + routeOff(); + setHash(Links.root(), true); + routeOff(); _.delay(() => { if (inIframe && window.parent) diff --git a/dev/App/Admin.jsx b/dev/App/Admin.jsx index 3191aa569..f83e29972 100644 --- a/dev/App/Admin.jsx +++ b/dev/App/Admin.jsx @@ -22,8 +22,7 @@ import Remote from 'Remote/Admin/Ajax'; import {SettingsAdminScreen} from 'Screen/Admin/Settings'; import {LoginAdminScreen} from 'Screen/Admin/Login'; -import kn from 'Knoin/Knoin'; - +import {hideLoading, routeOff, setHash, startScreens} from 'Knoin/Knoin'; import {AbstractApp} from 'App/Abstract'; class AdminApp extends AbstractApp @@ -222,13 +221,13 @@ class AdminApp extends AbstractApp require('Stores/Admin/App').populate(); require('Stores/Admin/Capa').populate(); - kn.hideLoading(); + hideLoading(); if (!Settings.appSettingsGet('allowAdminPanel')) { - kn.routeOff(); - kn.setHash(Links.root(), true); - kn.routeOff(); + routeOff(); + setHash(Links.root(), true); + routeOff(); _.defer(() => { window.location.href = '/'; @@ -238,13 +237,13 @@ class AdminApp extends AbstractApp { if (Settings.settingsGet('Auth')) { - kn.startScreens([ + startScreens([ SettingsAdminScreen ]); } else { - kn.startScreens([ + startScreens([ LoginAdminScreen ]); } diff --git a/dev/App/User.jsx b/dev/App/User.jsx index a8898e665..ce2467ae5 100644 --- a/dev/App/User.jsx +++ b/dev/App/User.jsx @@ -57,7 +57,10 @@ import {LoginUserScreen} from 'Screen/User/Login'; import {MailBoxUserScreen} from 'Screen/User/MailBox'; import {SettingsUserScreen} from 'Screen/User/Settings'; -import kn from 'Knoin/Knoin'; +import { + hideLoading, routeOff, routeOn, setHash, + startScreens, showScreenPopup +} from 'Knoin/Knoin'; import {AbstractApp} from 'App/Abstract'; @@ -166,7 +169,7 @@ class AppUser extends AbstractApp MessageStore.messageListPageBeforeThread(1); iOffset = 0; - kn.setHash(Links.mailBox( + setHash(Links.mailBox( FolderStore.currentFolderFullNameHash(), MessageStore.messageListPage(), MessageStore.messageListSearch(), @@ -364,12 +367,12 @@ class AppUser extends AbstractApp if (!oMoveFolder && bUseFolder) { - kn.showScreenPopup(require('View/Popup/FolderSystem'), [nSetSystemFoldersNotification]); + showScreenPopup(require('View/Popup/FolderSystem'), [nSetSystemFoldersNotification]); } else if (!bUseFolder || (FolderType.Trash === iDeleteType && (sFromFolderFullNameRaw === FolderStore.spamFolder() || sFromFolderFullNameRaw === FolderStore.trashFolder()))) { - kn.showScreenPopup(require('View/Popup/Ask'), [i18n('POPUPS_ASK/DESC_WANT_DELETE_MESSAGES'), () => { + showScreenPopup(require('View/Popup/Ask'), [i18n('POPUPS_ASK/DESC_WANT_DELETE_MESSAGES'), () => { this.messagesDeleteHelper(sFromFolderFullNameRaw, aUidForRemove); MessageStore.removeMessagesFromList(sFromFolderFullNameRaw, aUidForRemove); }]); @@ -1200,11 +1203,11 @@ class AppUser extends AbstractApp } bootstartTwoFactorScreen() { - kn.showScreenPopup(require('View/Popup/TwoFactorConfiguration'), [true]); + showScreenPopup(require('View/Popup/TwoFactorConfiguration'), [true]); } bootstartWelcomePopup(url) { - kn.showScreenPopup(require('View/Popup/WelcomePage'), [url]); + showScreenPopup(require('View/Popup/WelcomePage'), [url]); } bootstartLoginScreen() { @@ -1214,7 +1217,7 @@ class AppUser extends AbstractApp const customLoginLink = pString(Settings.appSettingsGet('customLoginLink')); if (!customLoginLink) { - kn.startScreens([ + startScreens([ LoginUserScreen ]); @@ -1223,9 +1226,9 @@ class AppUser extends AbstractApp } else { - kn.routeOff(); - kn.setHash(Links.root(), true); - kn.routeOff(); + routeOff(); + setHash(Links.root(), true); + routeOff(); _.defer(function() { window.location.href = customLoginLink; @@ -1236,14 +1239,9 @@ class AppUser extends AbstractApp bootend() { if (progressJs) { - kn.hideLoading(); - progressJs.set(100).end(); } - else - { - kn.hideLoading(); - } + hideLoading(); } bootstart() { @@ -1299,9 +1297,9 @@ class AppUser extends AbstractApp { if ('' !== sStartupUrl) { - kn.routeOff(); - kn.setHash(Links.root(sStartupUrl), true); - kn.routeOn(); + routeOff(); + setHash(Links.root(sStartupUrl), true); + routeOn(); } if (window.jassl && window.crypto && window.crypto.getRandomValues && Settings.capa(Capa.OpenPGP)) @@ -1349,7 +1347,7 @@ class AppUser extends AbstractApp PgpStore.capaOpenPGP(false); } - kn.startScreens([ + startScreens([ MailBoxUserScreen, Settings.capa(Capa.Settings) ? SettingsUserScreen : null // false ? AboutUserScreen : null @@ -1442,7 +1440,6 @@ class AppUser extends AbstractApp { this.logout(); } - }); // }); // require code splitting diff --git a/dev/Common/Cmd.jsx b/dev/Common/Cmd.jsx new file mode 100644 index 000000000..5eff7107d --- /dev/null +++ b/dev/Common/Cmd.jsx @@ -0,0 +1,35 @@ + +// import window from 'window'; +import $ from '$'; +import _ from '_'; +import {$body} from 'Common/Globals'; + +let + opened = false, + cmdDom = null; + +/** + * @returns {void} + */ +function init() +{ + if (null === cmdDom) + { + cmdDom = $('
'); + cmdDom.appendTo($body); + } +} + +/** + * @returns {void} + */ +export function toggle() +{ + init(); + + opened = !opened; + + _.delay(() => { + cmdDom.toggleClass('opened', opened); + }, 50); +} diff --git a/dev/Knoin/Knoin.js b/dev/Knoin/Knoin.js deleted file mode 100644 index 3d5b17fd0..000000000 --- a/dev/Knoin/Knoin.js +++ /dev/null @@ -1,496 +0,0 @@ - -var - _ = require('_'), - $ = require('$'), - ko = require('ko'), - hasher = require('hasher'), - crossroads = require('crossroads'), - - Globals = require('Common/Globals'), - Plugins = require('Common/Plugins'), - Utils = require('Common/Utils'); - -/** - * @constructor - */ -function Knoin() -{ - this.oScreens = {}; - this.sDefaultScreenName = ''; - this.oCurrentScreen = null; -} - -Knoin.prototype.oScreens = {}; -Knoin.prototype.sDefaultScreenName = ''; -Knoin.prototype.oCurrentScreen = null; - -Knoin.prototype.hideLoading = function() -{ - $('#rl-content').addClass('rl-content-show'); - $('#rl-loading').hide().remove(); -}; - -/** - * @param {Object} context - */ -Knoin.prototype.constructorEnd = function(context) -{ - if (Utils.isFunc(context.__constructor_end)) - { - context.__constructor_end(); - } -}; - -/** - * @param {string|Array} mName - * @param {Function} ViewModelClass - */ -Knoin.prototype.extendAsViewModel = function(mName, ViewModelClass) -{ - if (ViewModelClass) - { - if (Utils.isArray(mName)) - { - ViewModelClass.__names = mName; - } - else - { - ViewModelClass.__names = [mName]; - } - - ViewModelClass.__name = ViewModelClass.__names[0]; - } -}; - -/** - * @param {Function} SettingsViewModelClass - * @param {string} sLabelName - * @param {string} sTemplate - * @param {string} sRoute - * @param {boolean=} bDefault - */ -Knoin.prototype.addSettingsViewModel = function(SettingsViewModelClass, sTemplate, sLabelName, sRoute, bDefault) -{ - SettingsViewModelClass.__rlSettingsData = { - Label: sLabelName, - Template: sTemplate, - Route: sRoute, - IsDefault: !!bDefault - }; - - Globals.aViewModels.settings.push(SettingsViewModelClass); -}; - -/** - * @param {Function} SettingsViewModelClass - */ -Knoin.prototype.removeSettingsViewModel = function(SettingsViewModelClass) -{ - Globals.aViewModels['settings-removed'].push(SettingsViewModelClass); -}; - -/** - * @param {Function} SettingsViewModelClass - */ -Knoin.prototype.disableSettingsViewModel = function(SettingsViewModelClass) -{ - Globals.aViewModels['settings-disabled'].push(SettingsViewModelClass); -}; - -Knoin.prototype.routeOff = function() -{ - hasher.changed.active = false; -}; - -Knoin.prototype.routeOn = function() -{ - hasher.changed.active = true; -}; - -/** - * @param {string} sScreenName - * @returns {?Object} - */ -Knoin.prototype.screen = function(sScreenName) -{ - return ('' !== sScreenName && !Utils.isUnd(this.oScreens[sScreenName])) ? this.oScreens[sScreenName] : null; -}; - -/** - * @param {Function} ViewModelClass - * @param {Object=} oScreen - */ -Knoin.prototype.buildViewModel = function(ViewModelClass, oScreen) -{ - if (ViewModelClass && !ViewModelClass.__builded) - { - var - oViewModel = new ViewModelClass(oScreen), - sPosition = oViewModel.viewModelPosition(), - oViewModelPlace = $('#rl-content #rl-' + sPosition.toLowerCase()), - oViewModelDom = null; - - ViewModelClass.__builded = true; - ViewModelClass.__vm = oViewModel; - - oViewModel.onShowTrigger = ko.observable(false); - oViewModel.onHideTrigger = ko.observable(false); - - oViewModel.viewModelName = ViewModelClass.__name; - oViewModel.viewModelNames = ViewModelClass.__names; - - if (oViewModelPlace && 1 === oViewModelPlace.length) - { - oViewModelDom = $('').addClass('rl-view-model').addClass('RL-' + oViewModel.viewModelTemplate()).hide(); - oViewModelDom.appendTo(oViewModelPlace); - - oViewModel.viewModelDom = oViewModelDom; - ViewModelClass.__dom = oViewModelDom; - - if ('Popups' === sPosition) - { - oViewModel.cancelCommand = oViewModel.closeCommand = Utils.createCommand(oViewModel, _.bind(function() { - this.hideScreenPopup(ViewModelClass); - }, this)); - - oViewModel.modalVisibility.subscribe(function(bValue) { - - var self = this; - if (bValue) - { - this.viewModelDom.show(); - this.storeAndSetKeyScope(); - - Globals.popupVisibilityNames.push(this.viewModelName); - oViewModel.viewModelDom.css('z-index', 3000 + Globals.popupVisibilityNames().length + 10); - - if (this.onShowTrigger) - { - this.onShowTrigger(!this.onShowTrigger()); - } - - Utils.delegateRun(this, 'onShowWithDelay', [], 500); - } - else - { - Utils.delegateRun(this, 'onHide'); - Utils.delegateRun(this, 'onHideWithDelay', [], 500); - - if (this.onHideTrigger) - { - this.onHideTrigger(!this.onHideTrigger()); - } - - this.restoreKeyScope(); - - _.each(this.viewModelNames, function(sName) { - Plugins.runHook('view-model-on-hide', [sName, self]); - }); - - Globals.popupVisibilityNames.remove(this.viewModelName); - oViewModel.viewModelDom.css('z-index', 2000); - - _.delay(function() { - self.viewModelDom.hide(); - }, 300); - } - - }, oViewModel); - } - - _.each(ViewModelClass.__names, function(sName) { - Plugins.runHook('view-model-pre-build', [sName, oViewModel, oViewModelDom]); - }); - - ko.applyBindingAccessorsToNode(oViewModelDom[0], { - translatorInit: true, - template: function() { - return { - name: oViewModel.viewModelTemplate() - }; - } - }, oViewModel); - - Utils.delegateRun(oViewModel, 'onBuild', [oViewModelDom]); - if (oViewModel && 'Popups' === sPosition) - { - oViewModel.registerPopupKeyDown(); - } - - _.each(ViewModelClass.__names, function(sName) { - Plugins.runHook('view-model-post-build', [sName, oViewModel, oViewModelDom]); - }); - } - else - { - Utils.log('Cannot find view model position: ' + sPosition); - } - } - - return ViewModelClass ? ViewModelClass.__vm : null; -}; - -/** - * @param {Function} ViewModelClassToHide - */ -Knoin.prototype.hideScreenPopup = function(ViewModelClassToHide) -{ - if (ViewModelClassToHide && ViewModelClassToHide.__vm && ViewModelClassToHide.__dom) - { - ViewModelClassToHide.__vm.modalVisibility(false); - } -}; - -/** - * @param {Function} ViewModelClassToShow - * @param {Array=} aParameters - */ -Knoin.prototype.showScreenPopup = function(ViewModelClassToShow, aParameters) -{ - if (ViewModelClassToShow) - { - this.buildViewModel(ViewModelClassToShow); - - if (ViewModelClassToShow.__vm && ViewModelClassToShow.__dom) - { - Utils.delegateRun(ViewModelClassToShow.__vm, 'onBeforeShow', aParameters || []); - - ViewModelClassToShow.__vm.modalVisibility(true); - - Utils.delegateRun(ViewModelClassToShow.__vm, 'onShow', aParameters || []); - - _.each(ViewModelClassToShow.__names, function(sName) { - Plugins.runHook('view-model-on-show', [sName, ViewModelClassToShow.__vm, aParameters || []]); - }); - } - } -}; - -/** - * @param {Function} ViewModelClassToShow - * @returns {boolean} - */ -Knoin.prototype.isPopupVisible = function(ViewModelClassToShow) -{ - return ViewModelClassToShow && ViewModelClassToShow.__vm ? ViewModelClassToShow.__vm.modalVisibility() : false; -}; - -/** - * @param {string} sScreenName - * @param {string} sSubPart - */ -Knoin.prototype.screenOnRoute = function(sScreenName, sSubPart) -{ - var - self = this, - oScreen = null, - bSameScreen = false, - oCross = null; - - if ('' === Utils.pString(sScreenName)) - { - sScreenName = this.sDefaultScreenName; - } - - if ('' !== sScreenName) - { - oScreen = this.screen(sScreenName); - if (!oScreen) - { - oScreen = this.screen(this.sDefaultScreenName); - if (oScreen) - { - sSubPart = sScreenName + '/' + sSubPart; - sScreenName = this.sDefaultScreenName; - } - } - - if (oScreen && oScreen.__started) - { - bSameScreen = this.oCurrentScreen && oScreen === this.oCurrentScreen; - - if (!oScreen.__builded) - { - oScreen.__builded = true; - - if (Utils.isNonEmptyArray(oScreen.viewModels())) - { - _.each(oScreen.viewModels(), function(ViewModelClass) { - this.buildViewModel(ViewModelClass, oScreen); - }, this); - } - - Utils.delegateRun(oScreen, 'onBuild'); - } - - _.defer(function() { - - // hide screen - if (self.oCurrentScreen && !bSameScreen) - { - Utils.delegateRun(self.oCurrentScreen, 'onHide'); - Utils.delegateRun(self.oCurrentScreen, 'onHideWithDelay', [], 500); - - if (self.oCurrentScreen.onHideTrigger) - { - self.oCurrentScreen.onHideTrigger(!self.oCurrentScreen.onHideTrigger()); - } - - if (Utils.isNonEmptyArray(self.oCurrentScreen.viewModels())) - { - _.each(self.oCurrentScreen.viewModels(), function(ViewModelClass) { - - if (ViewModelClass.__vm && ViewModelClass.__dom && - 'Popups' !== ViewModelClass.__vm.viewModelPosition()) - { - ViewModelClass.__dom.hide(); - ViewModelClass.__vm.viewModelVisibility(false); - - Utils.delegateRun(ViewModelClass.__vm, 'onHide'); - Utils.delegateRun(ViewModelClass.__vm, 'onHideWithDelay', [], 500); - - if (ViewModelClass.__vm.onHideTrigger) - { - ViewModelClass.__vm.onHideTrigger(!ViewModelClass.__vm.onHideTrigger()); - } - } - - }); - } - } - // -- - - self.oCurrentScreen = oScreen; - - // show screen - if (self.oCurrentScreen && !bSameScreen) - { - Utils.delegateRun(self.oCurrentScreen, 'onShow'); - if (self.oCurrentScreen.onShowTrigger) - { - self.oCurrentScreen.onShowTrigger(!self.oCurrentScreen.onShowTrigger()); - } - - Plugins.runHook('screen-on-show', [self.oCurrentScreen.screenName(), self.oCurrentScreen]); - - if (Utils.isNonEmptyArray(self.oCurrentScreen.viewModels())) - { - _.each(self.oCurrentScreen.viewModels(), function(ViewModelClass) { - - if (ViewModelClass.__vm && ViewModelClass.__dom && - 'Popups' !== ViewModelClass.__vm.viewModelPosition()) - { - Utils.delegateRun(ViewModelClass.__vm, 'onBeforeShow'); - - ViewModelClass.__dom.show(); - ViewModelClass.__vm.viewModelVisibility(true); - - Utils.delegateRun(ViewModelClass.__vm, 'onShow'); - if (ViewModelClass.__vm.onShowTrigger) - { - ViewModelClass.__vm.onShowTrigger(!ViewModelClass.__vm.onShowTrigger()); - } - - Utils.delegateRun(ViewModelClass.__vm, 'onShowWithDelay', [], 200); - - _.each(ViewModelClass.__names, function(sName) { - Plugins.runHook('view-model-on-show', [sName, ViewModelClass.__vm]); - }); - } - - }, self); - } - } - // -- - - oCross = oScreen.__cross ? oScreen.__cross() : null; - if (oCross) - { - oCross.parse(sSubPart); - } - }); - } - } -}; - -/** - * @param {Array} aScreensClasses - */ -Knoin.prototype.startScreens = function(aScreensClasses) -{ - _.each(aScreensClasses, function(CScreen) { - - if (CScreen) - { - var - oScreen = new CScreen(), - sScreenName = oScreen ? oScreen.screenName() : ''; - - if (oScreen && '' !== sScreenName) - { - if ('' === this.sDefaultScreenName) - { - this.sDefaultScreenName = sScreenName; - } - - this.oScreens[sScreenName] = oScreen; - } - } - - }, this); - - _.each(this.oScreens, function(oScreen) { - if (oScreen && !oScreen.__started && oScreen.__start) - { - oScreen.__started = true; - oScreen.__start(); - - Plugins.runHook('screen-pre-start', [oScreen.screenName(), oScreen]); - Utils.delegateRun(oScreen, 'onStart'); - Plugins.runHook('screen-post-start', [oScreen.screenName(), oScreen]); - } - }, this); - - var oCross = crossroads.create(); - oCross.addRoute(/^([a-zA-Z0-9\-]*)\/?(.*)$/, _.bind(this.screenOnRoute, this)); - - hasher.initialized.add(oCross.parse, oCross); - hasher.changed.add(oCross.parse, oCross); - hasher.init(); - - _.delay(function() { - Globals.$html.removeClass('rl-started-trigger').addClass('rl-started'); - }, 100); - - _.delay(function() { - Globals.$html.addClass('rl-started-delay'); - }, 200); -}; - -/** - * @param {string} sHash - * @param {boolean=} bSilence = false - * @param {boolean=} bReplace = false - */ -Knoin.prototype.setHash = function(sHash, bSilence, bReplace) -{ - sHash = '#' === sHash.substr(0, 1) ? sHash.substr(1) : sHash; - sHash = '/' === sHash.substr(0, 1) ? sHash.substr(1) : sHash; - - bReplace = Utils.isUnd(bReplace) ? false : !!bReplace; - - if (Utils.isUnd(bSilence) ? false : !!bSilence) - { - hasher.changed.active = false; - hasher[bReplace ? 'replaceHash' : 'setHash'](sHash); - hasher.changed.active = true; - } - else - { - hasher.changed.active = true; - hasher[bReplace ? 'replaceHash' : 'setHash'](sHash); - hasher.setHash(sHash); - } -}; - -module.exports = new Knoin(); diff --git a/dev/Knoin/Knoin.jsx b/dev/Knoin/Knoin.jsx new file mode 100644 index 000000000..b6c902d40 --- /dev/null +++ b/dev/Knoin/Knoin.jsx @@ -0,0 +1,488 @@ + +import _ from '_'; +import $ from '$'; +import ko from 'ko'; +import hasher from 'hasher'; +import crossroads from 'crossroads'; + +import {runHook} from 'Common/Plugins'; +import {$html, aViewModels as VIEW_MODELS, popupVisibilityNames} from 'Common/Globals'; + +import { + isFunc, isArray, isUnd, pString, log, + createCommand, delegateRun, isNonEmptyArray +} from 'Common/Utils'; + +let + currentScreen = null, + defaultScreenName = ''; + +const SCREENS = {}; + +/** + * @returns {void} + */ +export function hideLoading() +{ + $('#rl-content').addClass('rl-content-show'); + $('#rl-loading').hide().remove(); +} + +/** + * @param {Object} context + * @returns {void} + */ +export function constructorEnd(context) +{ + if (isFunc(context.__constructor_end)) + { + context.__constructor_end(); + } +} + +/** + * @param {string|Array} name + * @param {Function} ViewModelClass + * @returns {void} + */ +export function extendAsViewModel(name, ViewModelClass) +{ + if (ViewModelClass) + { + if (isArray(name)) + { + ViewModelClass.__names = name; + } + else + { + ViewModelClass.__names = [name]; + } + + ViewModelClass.__name = ViewModelClass.__names[0]; + } +} + +/** + * @param {Function} SettingsViewModelClass + * @param {string} template + * @param {string} labelName + * @param {string} route + * @param {boolean=} isDefault = false + * @returns {void} + */ +export function addSettingsViewModel(SettingsViewModelClass, template, labelName, route, isDefault = false) +{ + SettingsViewModelClass.__rlSettingsData = { + Label: labelName, + Template: template, + Route: route, + IsDefault: !!isDefault + }; + + VIEW_MODELS.settings.push(SettingsViewModelClass); +} + +/** + * @param {Function} SettingsViewModelClass + * @returns {void} + */ +export function removeSettingsViewModel(SettingsViewModelClass) +{ + VIEW_MODELS['settings-removed'].push(SettingsViewModelClass); +} + +/** + * @param {Function} SettingsViewModelClass + * @returns {void} + */ +export function disableSettingsViewModel(SettingsViewModelClass) +{ + VIEW_MODELS['settings-disabled'].push(SettingsViewModelClass); +} + +/** + * @returns {void} + */ +export function routeOff() +{ + hasher.changed.active = false; +} + +/** + * @returns {void} + */ +export function routeOn() +{ + hasher.changed.active = true; +} + +/** + * @param {string} screenName + * @returns {?Object} + */ +export function screen(screenName) +{ + return ('' !== screenName && !isUnd(SCREENS[screenName])) ? SCREENS[screenName] : null; +} + +/** + * @param {Function} ViewModelClassToHide + * @returns {void} + */ +export function hideScreenPopup(ViewModelClassToHide) +{ + if (ViewModelClassToHide && ViewModelClassToHide.__vm && ViewModelClassToHide.__dom) + { + ViewModelClassToHide.__vm.modalVisibility(false); + } +} + +/** + * @param {string} hookName + * @param {Function} ViewModelClass + * @param {mixed=} params = null + */ +export function vmRunHook(hookName, ViewModelClass, params = null) +{ + _.each(ViewModelClass.__names, (name) => { + runHook(hookName, [name, ViewModelClass.__vm, params]); + }); +} + +/** + * @param {Function} ViewModelClass + * @param {Object=} vmScreen + * @returns {*} + */ +export function buildViewModel(ViewModelClass, vmScreen) +{ + if (ViewModelClass && !ViewModelClass.__builded) + { + let vmDom = null; + const + vm = new ViewModelClass(vmScreen), + position = vm.viewModelPosition(), + vmPlace = $('#rl-content #rl-' + position.toLowerCase()); + + ViewModelClass.__builded = true; + ViewModelClass.__vm = vm; + + vm.onShowTrigger = ko.observable(false); + vm.onHideTrigger = ko.observable(false); + + vm.viewModelName = ViewModelClass.__name; + vm.viewModelNames = ViewModelClass.__names; + + if (vmPlace && 1 === vmPlace.length) + { + vmDom = $('').addClass('rl-view-model').addClass('RL-' + vm.viewModelTemplate()).hide(); + vmDom.appendTo(vmPlace); + + vm.viewModelDom = vmDom; + ViewModelClass.__dom = vmDom; + + if ('Popups' === position) + { + vm.cancelCommand = vm.closeCommand = createCommand(vm, () => { + hideScreenPopup(ViewModelClass); + }); + + vm.modalVisibility.subscribe((value) => { + if (value) + { + vm.viewModelDom.show(); + vm.storeAndSetKeyScope(); + + popupVisibilityNames.push(vm.viewModelName); + vm.viewModelDom.css('z-index', 3000 + popupVisibilityNames().length + 10); + + if (vm.onShowTrigger) + { + vm.onShowTrigger(!vm.onShowTrigger()); + } + + delegateRun(vm, 'onShowWithDelay', [], 500); + } + else + { + delegateRun(vm, 'onHide'); + delegateRun(vm, 'onHideWithDelay', [], 500); + + if (vm.onHideTrigger) + { + vm.onHideTrigger(!vm.onHideTrigger()); + } + + vm.restoreKeyScope(); + + vmRunHook('view-model-on-hide', ViewModelClass); + + popupVisibilityNames.remove(vm.viewModelName); + vm.viewModelDom.css('z-index', 2000); + + _.delay(() => vm.viewModelDom.hide(), 300); + } + }); + } + + vmRunHook('view-model-pre-build', ViewModelClass, vmDom); + + ko.applyBindingAccessorsToNode(vmDom[0], { + translatorInit: true, + template: () => ({name: vm.viewModelTemplate()}) + }, vm); + + delegateRun(vm, 'onBuild', [vmDom]); + if (vm && 'Popups' === position) + { + vm.registerPopupKeyDown(); + } + + vmRunHook('view-model-post-build', ViewModelClass, vmDom); + } + else + { + log('Cannot find view model position: ' + position); + } + } + + return ViewModelClass ? ViewModelClass.__vm : null; +} + +/** + * @param {Function} ViewModelClassToShow + * @param {Array=} params + * @returns {void} + */ +export function showScreenPopup(ViewModelClassToShow, params = []) +{ + if (ViewModelClassToShow) + { + buildViewModel(ViewModelClassToShow); + + if (ViewModelClassToShow.__vm && ViewModelClassToShow.__dom) + { + delegateRun(ViewModelClassToShow.__vm, 'onBeforeShow', params || []); + + ViewModelClassToShow.__vm.modalVisibility(true); + + delegateRun(ViewModelClassToShow.__vm, 'onShow', params || []); + + vmRunHook('view-model-on-show', ViewModelClassToShow, params || []); + } + } +} + +/** + * @param {Function} ViewModelClassToShow + * @returns {boolean} + */ +export function isPopupVisible(ViewModelClassToShow) +{ + return ViewModelClassToShow && ViewModelClassToShow.__vm ? ViewModelClassToShow.__vm.modalVisibility() : false; +} + +/** + * @param {string} screenName + * @param {string} subPart + * @returns {void} + */ +export function screenOnRoute(screenName, subPart) +{ + let + vmScreen = null, + isSameScreen = false, + cross = null; + + if ('' === pString(screenName)) + { + screenName = defaultScreenName; + } + + if ('' !== screenName) + { + vmScreen = screen(screenName); + if (!vmScreen) + { + vmScreen = screen(defaultScreenName); + if (vmScreen) + { + subPart = screenName + '/' + subPart; + screenName = defaultScreenName; + } + } + + if (vmScreen && vmScreen.__started) + { + isSameScreen = currentScreen && vmScreen === currentScreen; + + if (!vmScreen.__builded) + { + vmScreen.__builded = true; + + if (isNonEmptyArray(vmScreen.viewModels())) + { + _.each(vmScreen.viewModels(), (ViewModelClass) => { + buildViewModel(ViewModelClass, vmScreen); + }); + } + + delegateRun(vmScreen, 'onBuild'); + } + + _.defer(() => { + // hide screen + if (currentScreen && !isSameScreen) + { + delegateRun(currentScreen, 'onHide'); + delegateRun(currentScreen, 'onHideWithDelay', [], 500); + + if (currentScreen.onHideTrigger) + { + currentScreen.onHideTrigger(!currentScreen.onHideTrigger()); + } + + if (isNonEmptyArray(currentScreen.viewModels())) + { + _.each(currentScreen.viewModels(), (ViewModelClass) => { + if (ViewModelClass.__vm && ViewModelClass.__dom && 'Popups' !== ViewModelClass.__vm.viewModelPosition()) + { + ViewModelClass.__dom.hide(); + ViewModelClass.__vm.viewModelVisibility(false); + + delegateRun(ViewModelClass.__vm, 'onHide'); + delegateRun(ViewModelClass.__vm, 'onHideWithDelay', [], 500); + + if (ViewModelClass.__vm.onHideTrigger) + { + ViewModelClass.__vm.onHideTrigger(!ViewModelClass.__vm.onHideTrigger()); + } + } + }); + } + } + // -- + + currentScreen = vmScreen; + + // show screen + if (currentScreen && !isSameScreen) + { + delegateRun(currentScreen, 'onShow'); + if (currentScreen.onShowTrigger) + { + currentScreen.onShowTrigger(!currentScreen.onShowTrigger()); + } + + runHook('screen-on-show', [currentScreen.screenName(), currentScreen]); + + if (isNonEmptyArray(currentScreen.viewModels())) + { + _.each(currentScreen.viewModels(), (ViewModelClass) => { + + if (ViewModelClass.__vm && ViewModelClass.__dom && 'Popups' !== ViewModelClass.__vm.viewModelPosition()) + { + delegateRun(ViewModelClass.__vm, 'onBeforeShow'); + + ViewModelClass.__dom.show(); + ViewModelClass.__vm.viewModelVisibility(true); + + delegateRun(ViewModelClass.__vm, 'onShow'); + if (ViewModelClass.__vm.onShowTrigger) + { + ViewModelClass.__vm.onShowTrigger(!ViewModelClass.__vm.onShowTrigger()); + } + + delegateRun(ViewModelClass.__vm, 'onShowWithDelay', [], 200); + vmRunHook('view-model-on-show', ViewModelClass); + } + + }); + } + } + // -- + + cross = vmScreen.__cross ? vmScreen.__cross() : null; + if (cross) + { + cross.parse(subPart); + } + }); + } + } +} + +/** + * @param {Array} screensClasses + * @returns {void} + */ +export function startScreens(screensClasses) +{ + _.each(screensClasses, (CScreen) => { + if (CScreen) + { + const + vmScreen = new CScreen(), + screenName = vmScreen ? vmScreen.screenName() : ''; + + if (vmScreen && '' !== screenName) + { + if ('' === defaultScreenName) + { + defaultScreenName = screenName; + } + + SCREENS[screenName] = vmScreen; + } + } + }); + + _.each(SCREENS, (vmScreen) => { + if (vmScreen && !vmScreen.__started && vmScreen.__start) + { + vmScreen.__started = true; + vmScreen.__start(); + + runHook('screen-pre-start', [vmScreen.screenName(), vmScreen]); + delegateRun(vmScreen, 'onStart'); + runHook('screen-post-start', [vmScreen.screenName(), vmScreen]); + } + }); + + const cross = crossroads.create(); + cross.addRoute(/^([a-zA-Z0-9\-]*)\/?(.*)$/, screenOnRoute); + + hasher.initialized.add(cross.parse, cross); + hasher.changed.add(cross.parse, cross); + hasher.init(); + + _.delay(() => $html.removeClass('rl-started-trigger').addClass('rl-started'), 100); + _.delay(() => $html.addClass('rl-started-delay'), 200); +} + +/** + * @param {string} sHash + * @param {boolean=} silence = false + * @param {boolean=} replace = false + * @returns {void} + */ +export function setHash(hash, silence = false, replace = false) +{ + hash = '#' === hash.substr(0, 1) ? hash.substr(1) : hash; + hash = '/' === hash.substr(0, 1) ? hash.substr(1) : hash; + + const cmd = replace ? 'replaceHash' : 'setHash'; + + if (silence) + { + hasher.changed.active = false; + hasher[cmd](hash); + hasher.changed.active = true; + } + else + { + hasher.changed.active = true; + hasher[cmd](hash); + hasher.setHash(hash); + } +} diff --git a/dev/Screen/AbstractSettings.jsx b/dev/Screen/AbstractSettings.jsx index cc77121d3..83deca522 100644 --- a/dev/Screen/AbstractSettings.jsx +++ b/dev/Screen/AbstractSettings.jsx @@ -7,7 +7,7 @@ import {aViewModels as VIEW_MODELS} from 'Common/Globals'; import {delegateRun, windowResize, log, isUnd, pString} from 'Common/Utils'; import {settings} from 'Common/Links'; -import kn from 'Knoin/Knoin'; +import {setHash} from 'Knoin/Knoin'; import {AbstractScreen} from 'Knoin/AbstractScreen'; class AbstractSettingsScreen extends AbstractScreen @@ -135,7 +135,7 @@ class AbstractSettingsScreen extends AbstractScreen } else { - kn.setHash(settings(), false, true); + setHash(settings(), false, true); } } diff --git a/dev/Screen/Admin/Settings.jsx b/dev/Screen/Admin/Settings.jsx index 8facd2b06..5e7c873c9 100644 --- a/dev/Screen/Admin/Settings.jsx +++ b/dev/Screen/Admin/Settings.jsx @@ -1,7 +1,7 @@ /* global RL_COMMUNITY */ -import kn from 'Knoin/Knoin'; +import {addSettingsViewModel} from 'Knoin/Knoin'; import {runSettingsViewModelHooks} from 'Common/Plugins'; import {AbstractSettingsScreen} from 'Screen/AbstractSettings'; @@ -21,48 +21,48 @@ class SettingsAdminScreen extends AbstractSettingsScreen * @param {Function=} fCallback = null */ setupSettings(fCallback = null) { - kn.addSettingsViewModel(require('Settings/Admin/General'), + addSettingsViewModel(require('Settings/Admin/General'), 'AdminSettingsGeneral', 'TABS_LABELS/LABEL_GENERAL_NAME', 'general', true); - kn.addSettingsViewModel(require('Settings/Admin/Login'), + addSettingsViewModel(require('Settings/Admin/Login'), 'AdminSettingsLogin', 'TABS_LABELS/LABEL_LOGIN_NAME', 'login'); if (RL_COMMUNITY) { - kn.addSettingsViewModel(require('Settings/Admin/Branding'), + addSettingsViewModel(require('Settings/Admin/Branding'), 'AdminSettingsBranding', 'TABS_LABELS/LABEL_BRANDING_NAME', 'branding'); } else { - kn.addSettingsViewModel(require('Settings/Admin/Prem/Branding'), + addSettingsViewModel(require('Settings/Admin/Prem/Branding'), 'AdminSettingsBranding', 'TABS_LABELS/LABEL_BRANDING_NAME', 'branding'); } - kn.addSettingsViewModel(require('Settings/Admin/Contacts'), + addSettingsViewModel(require('Settings/Admin/Contacts'), 'AdminSettingsContacts', 'TABS_LABELS/LABEL_CONTACTS_NAME', 'contacts'); - kn.addSettingsViewModel(require('Settings/Admin/Domains'), + addSettingsViewModel(require('Settings/Admin/Domains'), 'AdminSettingsDomains', 'TABS_LABELS/LABEL_DOMAINS_NAME', 'domains'); - kn.addSettingsViewModel(require('Settings/Admin/Security'), + addSettingsViewModel(require('Settings/Admin/Security'), 'AdminSettingsSecurity', 'TABS_LABELS/LABEL_SECURITY_NAME', 'security'); - kn.addSettingsViewModel(require('Settings/Admin/Social'), + addSettingsViewModel(require('Settings/Admin/Social'), 'AdminSettingsSocial', 'TABS_LABELS/LABEL_INTEGRATION_NAME', 'integrations'); - kn.addSettingsViewModel(require('Settings/Admin/Plugins'), + addSettingsViewModel(require('Settings/Admin/Plugins'), 'AdminSettingsPlugins', 'TABS_LABELS/LABEL_PLUGINS_NAME', 'plugins'); - kn.addSettingsViewModel(require('Settings/Admin/Packages'), + addSettingsViewModel(require('Settings/Admin/Packages'), 'AdminSettingsPackages', 'TABS_LABELS/LABEL_PACKAGES_NAME', 'packages'); if (!RL_COMMUNITY) { - kn.addSettingsViewModel(require('Settings/Admin/Prem/Licensing'), + addSettingsViewModel(require('Settings/Admin/Prem/Licensing'), 'AdminSettingsLicensing', 'TABS_LABELS/LABEL_LICENSING_NAME', 'licensing'); } - kn.addSettingsViewModel(require('Settings/Admin/About'), + addSettingsViewModel(require('Settings/Admin/About'), 'AdminSettingsAbout', 'TABS_LABELS/LABEL_ABOUT_NAME', 'about'); runSettingsViewModelHooks(true); diff --git a/dev/Screen/User/Settings.jsx b/dev/Screen/User/Settings.jsx index dc5d7a69c..738ac09c4 100644 --- a/dev/Screen/User/Settings.jsx +++ b/dev/Screen/User/Settings.jsx @@ -8,8 +8,7 @@ import AppStore from 'Stores/User/App'; import AccountStore from 'Stores/User/Account'; import * as Settings from 'Storage/Settings'; - -import kn from 'Knoin/Knoin'; +import {addSettingsViewModel} from 'Knoin/Knoin'; import {AbstractSettingsScreen} from 'Screen/AbstractSettings'; import App from 'App/User'; @@ -45,30 +44,30 @@ class SettingsUserScreen extends AbstractSettingsScreen return false; } - kn.addSettingsViewModel(require('Settings/User/General'), + addSettingsViewModel(require('Settings/User/General'), 'SettingsGeneral', 'SETTINGS_LABELS/LABEL_GENERAL_NAME', 'general', true); if (AppStore.contactsIsAllowed()) { - kn.addSettingsViewModel(require('Settings/User/Contacts'), + addSettingsViewModel(require('Settings/User/Contacts'), 'SettingsContacts', 'SETTINGS_LABELS/LABEL_CONTACTS_NAME', 'contacts'); } if (Settings.capa(Capa.AdditionalAccounts) || Settings.capa(Capa.Identities)) { - kn.addSettingsViewModel(require('Settings/User/Accounts'), 'SettingsAccounts', + addSettingsViewModel(require('Settings/User/Accounts'), 'SettingsAccounts', Settings.capa(Capa.AdditionalAccounts) ? 'SETTINGS_LABELS/LABEL_ACCOUNTS_NAME' : 'SETTINGS_LABELS/LABEL_IDENTITIES_NAME', 'accounts'); } if (Settings.capa(Capa.Sieve)) { - kn.addSettingsViewModel(require('Settings/User/Filters'), + addSettingsViewModel(require('Settings/User/Filters'), 'SettingsFilters', 'SETTINGS_LABELS/LABEL_FILTERS_NAME', 'filters'); } if (Settings.capa(Capa.AutoLogout) || Settings.capa(Capa.TwoFactor)) { - kn.addSettingsViewModel(require('Settings/User/Security'), + addSettingsViewModel(require('Settings/User/Security'), 'SettingsSecurity', 'SETTINGS_LABELS/LABEL_SECURITY_NAME', 'security'); } @@ -77,37 +76,37 @@ class SettingsUserScreen extends AbstractSettingsScreen Settings.settingsGet('AllowFacebookSocial') || Settings.settingsGet('AllowTwitterSocial'))) { - kn.addSettingsViewModel(require('Settings/User/Social'), + addSettingsViewModel(require('Settings/User/Social'), 'SettingsSocial', 'SETTINGS_LABELS/LABEL_SOCIAL_NAME', 'social'); } if (Settings.settingsGet('ChangePasswordIsAllowed')) { - kn.addSettingsViewModel(require('Settings/User/ChangePassword'), + addSettingsViewModel(require('Settings/User/ChangePassword'), 'SettingsChangePassword', 'SETTINGS_LABELS/LABEL_CHANGE_PASSWORD_NAME', 'change-password'); } if (Settings.capa(Capa.Templates)) { - kn.addSettingsViewModel(require('Settings/User/Templates'), + addSettingsViewModel(require('Settings/User/Templates'), 'SettingsTemplates', 'SETTINGS_LABELS/LABEL_TEMPLATES_NAME', 'templates'); } if (Settings.capa(Capa.Folders)) { - kn.addSettingsViewModel(require('Settings/User/Folders'), + addSettingsViewModel(require('Settings/User/Folders'), 'SettingsFolders', 'SETTINGS_LABELS/LABEL_FOLDERS_NAME', 'folders'); } if (Settings.capa(Capa.Themes)) { - kn.addSettingsViewModel(require('Settings/User/Themes'), + addSettingsViewModel(require('Settings/User/Themes'), 'SettingsThemes', 'SETTINGS_LABELS/LABEL_THEMES_NAME', 'themes'); } if (Settings.capa(Capa.OpenPGP)) { - kn.addSettingsViewModel(require('Settings/User/OpenPgp'), + addSettingsViewModel(require('Settings/User/OpenPgp'), 'SettingsOpenPGP', 'SETTINGS_LABELS/LABEL_OPEN_PGP_NAME', 'openpgp'); } diff --git a/dev/Styles/@Main.less b/dev/Styles/@Main.less index 32187b889..c2f77b22f 100644 --- a/dev/Styles/@Main.less +++ b/dev/Styles/@Main.less @@ -93,5 +93,7 @@ @import "SettingsThemes.less"; @import "SettingsFilters.less"; +@import "Cmd.less"; + @import "Animations.less"; @import "_End.less"; diff --git a/dev/Styles/Cmd.less b/dev/Styles/Cmd.less new file mode 100644 index 000000000..abbb76310 --- /dev/null +++ b/dev/Styles/Cmd.less @@ -0,0 +1,18 @@ + +.rl-cmd { + position: absolute; + bottom: 0; + left: 0; + right: 0; + top: auto; + height: 0; + + background: rgba(0, 0, 0, .8); + border-top: 1px solid #000; + + .transition(height 0.1s ease-out); + + &.opened { + height: 200px; + } +}