snappymail/dev/Knoin/Knoin.js

544 lines
13 KiB
JavaScript
Raw Normal View History

2016-07-08 07:22:58 +08:00
import ko from 'ko';
2020-08-24 03:51:26 +08:00
import { $htmlCL, VIEW_MODELS } from 'Common/Globals';
2016-07-08 07:22:58 +08:00
2020-08-22 07:09:50 +08:00
//import { bMobileDevice } from 'Common/Globals';
2019-07-05 03:19:24 +08:00
let currentScreen = null,
2016-07-08 07:22:58 +08:00
defaultScreenName = '';
const SCREENS = {}, $ = jQuery,
2020-08-24 03:51:26 +08:00
isNonEmptyArray = values => Array.isArray(values) && values.length,
popupVisibilityNames = ko.observableArray([]);
export const popupVisibility = ko.computed(() => 0 < popupVisibilityNames().length);
popupVisibility.subscribe((bValue) => {
$htmlCL.toggle('rl-modal', bValue);
});
2016-07-08 07:22:58 +08:00
export const ViewType = {
Popup: 'Popups',
Left: 'Left',
Right: 'Right',
Center: 'Center'
};
2016-07-08 07:22:58 +08:00
/**
* @returns {void}
*/
2019-07-05 03:19:24 +08:00
export function hideLoading() {
2016-07-08 07:22:58 +08:00
$('#rl-content').addClass('rl-content-show');
2019-07-05 03:19:24 +08:00
$('#rl-loading')
.hide()
.remove();
2016-07-08 07:22:58 +08:00
}
2016-09-10 06:38:16 +08:00
/**
* @param {Function} fExecute
* @param {(Function|boolean|null)=} fCanExecute = true
* @returns {Function}
*/
2019-07-05 03:19:24 +08:00
export function createCommand(fExecute, fCanExecute = true) {
let fResult = null;
const fNonEmpty = (...args) => {
if (fResult && fResult.canExecute && fResult.canExecute()) {
fExecute.apply(null, args);
}
return false;
};
fResult = fExecute ? fNonEmpty : ()=>{};
fResult.enabled = ko.observable(true);
fResult.isCommand = true;
if (typeof fCanExecute === 'function') {
fResult.canExecute = ko.computed(() => fResult && fResult.enabled() && fCanExecute.call(null));
} else {
fResult.canExecute = ko.computed(() => fResult && fResult.enabled() && !!fCanExecute);
}
return fResult;
2016-09-10 06:38:16 +08:00
}
2016-07-08 07:22:58 +08:00
/**
* @param {Function} SettingsViewModelClass
* @param {string} template
* @param {string} labelName
* @param {string} route
* @param {boolean=} isDefault = false
* @returns {void}
*/
2019-07-05 03:19:24 +08:00
export function addSettingsViewModel(SettingsViewModelClass, template, labelName, route, isDefault = false) {
2016-07-08 07:22:58 +08:00
SettingsViewModelClass.__rlSettingsData = {
Label: labelName,
Template: template,
Route: route,
IsDefault: !!isDefault
};
VIEW_MODELS.settings.push(SettingsViewModelClass);
}
/**
* @param {Function} SettingsViewModelClass
* @returns {void}
*/
2019-07-05 03:19:24 +08:00
export function removeSettingsViewModel(SettingsViewModelClass) {
2016-07-08 07:22:58 +08:00
VIEW_MODELS['settings-removed'].push(SettingsViewModelClass);
}
/**
* @param {Function} SettingsViewModelClass
* @returns {void}
*/
2019-07-05 03:19:24 +08:00
export function disableSettingsViewModel(SettingsViewModelClass) {
2016-07-08 07:22:58 +08:00
VIEW_MODELS['settings-disabled'].push(SettingsViewModelClass);
}
/**
* @returns {void}
*/
2019-07-05 03:19:24 +08:00
export function routeOff() {
2016-07-08 07:22:58 +08:00
hasher.changed.active = false;
}
/**
* @returns {void}
*/
2019-07-05 03:19:24 +08:00
export function routeOn() {
2016-07-08 07:22:58 +08:00
hasher.changed.active = true;
}
/**
* @param {string} screenName
* @returns {?Object}
*/
2019-07-05 03:19:24 +08:00
export function screen(screenName) {
return screenName && null != SCREENS[screenName] ? SCREENS[screenName] : null;
2016-07-08 07:22:58 +08:00
}
2016-09-10 06:38:16 +08:00
/**
* @param {Function} ViewModelClassToShow
* @returns {Function|null}
*/
2019-07-05 03:19:24 +08:00
export function getScreenPopup(PopuViewModelClass) {
2016-09-10 06:38:16 +08:00
let result = null;
2019-07-05 03:19:24 +08:00
if (PopuViewModelClass) {
2016-09-10 06:38:16 +08:00
result = PopuViewModelClass;
2019-07-05 03:19:24 +08:00
if (PopuViewModelClass.default) {
2016-09-10 06:38:16 +08:00
result = PopuViewModelClass.default;
}
}
return result;
}
2016-07-08 07:22:58 +08:00
/**
* @param {Function} ViewModelClassToHide
* @returns {void}
*/
2019-07-05 03:19:24 +08:00
export function hideScreenPopup(ViewModelClassToHide) {
2016-09-10 06:38:16 +08:00
const ModalView = getScreenPopup(ViewModelClassToHide);
2019-07-05 03:19:24 +08:00
if (ModalView && ModalView.__vm && ModalView.__dom) {
2016-09-10 06:38:16 +08:00
ModalView.__vm.modalVisibility(false);
2016-07-08 07:22:58 +08:00
}
}
/**
* @param {Function} ViewModelClass
* @param {Object=} vmScreen
* @returns {*}
*/
2019-07-05 03:19:24 +08:00
export function buildViewModel(ViewModelClass, vmScreen) {
if (ViewModelClass && !ViewModelClass.__builded) {
2016-07-08 07:22:58 +08:00
let vmDom = null;
2019-07-05 03:19:24 +08:00
const vm = new ViewModelClass(vmScreen),
position = ViewModelClass.__type || '',
vmPlace = position ? $('#rl-content #rl-' + position.toLowerCase()) : null;
2016-07-08 07:22:58 +08:00
ViewModelClass.__builded = true;
ViewModelClass.__vm = vm;
vm.viewModelName = ViewModelClass.__name;
vm.viewModelNames = ViewModelClass.__names;
vm.viewModelTemplateID = ViewModelClass.__templateID;
vm.viewModelPosition = ViewModelClass.__type;
2016-07-08 07:22:58 +08:00
2019-07-05 03:19:24 +08:00
if (vmPlace && 1 === vmPlace.length) {
vmDom = $('<div></div>')
.addClass('rl-view-model')
.addClass('RL-' + vm.viewModelTemplateID)
.hide();
2016-07-08 07:22:58 +08:00
vmDom.appendTo(vmPlace);
vm.viewModelDom = vmDom;
ViewModelClass.__dom = vmDom;
2019-07-05 03:19:24 +08:00
if (ViewType.Popup === position) {
vm.cancelCommand = vm.closeCommand = createCommand(() => {
2016-07-08 07:22:58 +08:00
hideScreenPopup(ViewModelClass);
});
vm.modalVisibility.subscribe((value) => {
2019-07-05 03:19:24 +08:00
if (value) {
2016-07-08 07:22:58 +08:00
vm.viewModelDom.show();
vm.storeAndSetKeyScope();
popupVisibilityNames.push(vm.viewModelName);
vm.viewModelDom.css('z-index', 3000 + popupVisibilityNames().length + 10);
vm.onShowWithDelay && setTimeout(()=>vm.onShowWithDelay, 500);
2019-07-05 03:19:24 +08:00
} else {
vm.onHide && vm.onHide();
vm.onHideWithDelay && setTimeout(()=>vm.onHideWithDelay, 500);
2016-07-08 07:22:58 +08:00
vm.restoreKeyScope();
popupVisibilityNames.remove(vm.viewModelName);
vm.viewModelDom.css('z-index', 2000);
setTimeout(() => vm.viewModelDom.hide(), 300);
2016-07-08 07:22:58 +08:00
}
});
}
2019-07-05 03:19:24 +08:00
ko.applyBindingAccessorsToNode(
vmDom[0],
{
i18nInit: true,
2019-07-05 03:19:24 +08:00
template: () => ({ name: vm.viewModelTemplateID })
},
vm
);
2016-07-08 07:22:58 +08:00
vm.onBuild && vm.onBuild(vmDom);
2019-07-05 03:19:24 +08:00
if (vm && ViewType.Popup === position) {
2016-07-08 07:22:58 +08:00
vm.registerPopupKeyDown();
}
2019-07-05 03:19:24 +08:00
} else {
console.log('Cannot find view model position: ' + position);
2016-07-08 07:22:58 +08:00
}
}
return ViewModelClass ? ViewModelClass.__vm : null;
}
/**
* @param {Function} ViewModelClassToShow
* @param {Array=} params
* @returns {void}
*/
2019-07-05 03:19:24 +08:00
export function showScreenPopup(ViewModelClassToShow, params = []) {
2016-09-10 06:38:16 +08:00
const ModalView = getScreenPopup(ViewModelClassToShow);
2019-07-05 03:19:24 +08:00
if (ModalView) {
2016-09-10 06:38:16 +08:00
buildViewModel(ModalView);
2016-07-08 07:22:58 +08:00
2019-07-05 03:19:24 +08:00
if (ModalView.__vm && ModalView.__dom) {
params = params || [];
ModalView.__vm.onBeforeShow && ModalView.__vm.onBeforeShow(...params);
2016-07-08 07:22:58 +08:00
2016-09-10 06:38:16 +08:00
ModalView.__vm.modalVisibility(true);
2016-07-08 07:22:58 +08:00
ModalView.__vm.onShow && ModalView.__vm.onShow(...params);
2016-07-08 07:22:58 +08:00
2020-08-22 07:09:50 +08:00
// if (!bMobileDevice) {
const af = ModalView.__dom[0].querySelector('[autofocus]');
af && af.focus();
2016-07-08 07:22:58 +08:00
}
}
}
/**
* @param {Function} ViewModelClassToShow
* @returns {void}
*/
2019-07-05 03:19:24 +08:00
export function warmUpScreenPopup(ViewModelClassToShow) {
2016-09-10 06:38:16 +08:00
const ModalView = getScreenPopup(ViewModelClassToShow);
2019-07-05 03:19:24 +08:00
if (ModalView) {
2016-09-10 06:38:16 +08:00
buildViewModel(ModalView);
2019-07-05 03:19:24 +08:00
if (ModalView.__vm && ModalView.__dom) {
ModalView.__vm.onWarmUp && ModalView.__vm.onWarmUp();
}
}
}
2016-07-08 07:22:58 +08:00
/**
* @param {Function} ViewModelClassToShow
* @returns {boolean}
*/
2019-07-05 03:19:24 +08:00
export function isPopupVisible(ViewModelClassToShow) {
2016-09-10 06:38:16 +08:00
const ModalView = getScreenPopup(ViewModelClassToShow);
return ModalView && ModalView.__vm ? ModalView.__vm.modalVisibility() : false;
2016-07-08 07:22:58 +08:00
}
/**
* @param {string} screenName
* @param {string} subPart
* @returns {void}
*/
2019-07-05 03:19:24 +08:00
export function screenOnRoute(screenName, subPart) {
let vmScreen = null,
2016-07-08 07:22:58 +08:00
isSameScreen = false,
cross = null;
if (null == screenName || '' == screenName) {
2016-07-08 07:22:58 +08:00
screenName = defaultScreenName;
}
if (screenName) {
2016-07-08 07:22:58 +08:00
vmScreen = screen(screenName);
2019-07-05 03:19:24 +08:00
if (!vmScreen) {
2016-07-08 07:22:58 +08:00
vmScreen = screen(defaultScreenName);
2019-07-05 03:19:24 +08:00
if (vmScreen) {
2016-07-08 07:22:58 +08:00
subPart = screenName + '/' + subPart;
screenName = defaultScreenName;
}
}
2019-07-05 03:19:24 +08:00
if (vmScreen && vmScreen.__started) {
2016-07-08 07:22:58 +08:00
isSameScreen = currentScreen && vmScreen === currentScreen;
2019-07-05 03:19:24 +08:00
if (!vmScreen.__builded) {
2016-07-08 07:22:58 +08:00
vmScreen.__builded = true;
2019-07-05 03:19:24 +08:00
if (isNonEmptyArray(vmScreen.viewModels())) {
vmScreen.viewModels().forEach(ViewModelClass => {
2016-07-08 07:22:58 +08:00
buildViewModel(ViewModelClass, vmScreen);
});
}
vmScreen.onBuild && vmScreen.onBuild();
2016-07-08 07:22:58 +08:00
}
setTimeout(() => {
2016-07-08 07:22:58 +08:00
// hide screen
2019-07-05 03:19:24 +08:00
if (currentScreen && !isSameScreen) {
currentScreen.onHide && currentScreen.onHide();
currentScreen.onHideWithDelay && setTimeout(()=>currentScreen.onHideWithDelay(), 500);
2016-07-08 07:22:58 +08:00
2019-07-05 03:19:24 +08:00
if (isNonEmptyArray(currentScreen.viewModels())) {
currentScreen.viewModels().forEach(ViewModelClass => {
2019-07-05 03:19:24 +08:00
if (
ViewModelClass.__vm &&
ViewModelClass.__dom &&
ViewType.Popup !== ViewModelClass.__vm.viewModelPosition
) {
2016-07-08 07:22:58 +08:00
ViewModelClass.__dom.hide();
ViewModelClass.__vm.viewModelVisibility(false);
ViewModelClass.__vm.onHide && ViewModelClass.__vm.onHide();
ViewModelClass.__vm.onHideWithDelay && setTimeout(()=>ViewModelClass.__vm.onHideWithDelay(), 500);
2016-07-08 07:22:58 +08:00
}
});
}
}
// --
currentScreen = vmScreen;
// show screen
2019-07-05 03:19:24 +08:00
if (currentScreen && !isSameScreen) {
currentScreen.onShow && currentScreen.onShow();
2016-07-08 07:22:58 +08:00
2019-07-05 03:19:24 +08:00
if (isNonEmptyArray(currentScreen.viewModels())) {
currentScreen.viewModels().forEach(ViewModelClass => {
2019-07-05 03:19:24 +08:00
if (
ViewModelClass.__vm &&
ViewModelClass.__dom &&
ViewType.Popup !== ViewModelClass.__vm.viewModelPosition
) {
ViewModelClass.__vm.onBeforeShow && ViewModelClass.__vm.onBeforeShow();
2016-07-08 07:22:58 +08:00
ViewModelClass.__dom.show();
ViewModelClass.__vm.viewModelVisibility(true);
ViewModelClass.__vm.onShow && ViewModelClass.__vm.onShow();
2016-07-08 07:22:58 +08:00
2020-08-22 07:09:50 +08:00
// if (!bMobileDevice) {
const af = ViewModelClass.__dom[0].querySelector('[autofocus]');
af && af.focus();
ViewModelClass.__vm.onShowWithDelay && setTimeout(()=>ViewModelClass.__vm.onShowWithDelay, 200);
2016-07-08 07:22:58 +08:00
}
});
}
}
// --
2016-09-10 06:38:16 +08:00
cross = vmScreen && vmScreen.__cross ? vmScreen.__cross() : null;
2019-07-05 03:19:24 +08:00
if (cross) {
2016-07-08 07:22:58 +08:00
cross.parse(subPart);
}
}, 1);
2016-07-08 07:22:58 +08:00
}
}
}
/**
* @param {Array} screensClasses
* @returns {void}
*/
2019-07-05 03:19:24 +08:00
export function startScreens(screensClasses) {
screensClasses.forEach(CScreen => {
2019-07-05 03:19:24 +08:00
if (CScreen) {
const vmScreen = new CScreen(),
2016-07-08 07:22:58 +08:00
screenName = vmScreen ? vmScreen.screenName() : '';
if (vmScreen && screenName) {
if (!defaultScreenName) {
2016-07-08 07:22:58 +08:00
defaultScreenName = screenName;
}
SCREENS[screenName] = vmScreen;
}
}
});
Object.values(SCREENS).forEach(vmScreen => {
2019-07-05 03:19:24 +08:00
if (vmScreen && !vmScreen.__started && vmScreen.__start) {
2016-07-08 07:22:58 +08:00
vmScreen.__started = true;
vmScreen.__start();
vmScreen.onStart && vmScreen.onStart();
2016-07-08 07:22:58 +08:00
}
});
const cross = new Crossroads();
2019-07-05 03:19:24 +08:00
cross.addRoute(/^([a-zA-Z0-9-]*)\/?(.*)$/, screenOnRoute);
2016-07-08 07:22:58 +08:00
hasher.initialized.add(cross.parse, cross);
hasher.changed.add(cross.parse, cross);
hasher.init();
setTimeout(() => {
$htmlCL.remove('rl-started-trigger');
$htmlCL.add('rl-started');
}, 100);
setTimeout(() => $htmlCL.add('rl-started-delay'), 200);
2016-07-08 07:22:58 +08:00
}
/**
* @param {string} sHash
* @param {boolean=} silence = false
* @param {boolean=} replace = false
* @returns {void}
*/
2019-07-05 03:19:24 +08:00
export function setHash(hash, silence = false, replace = false) {
2016-07-08 07:22:58 +08:00
hash = '#' === hash.substr(0, 1) ? hash.substr(1) : hash;
hash = '/' === hash.substr(0, 1) ? hash.substr(1) : hash;
const cmd = replace ? 'replaceHash' : 'setHash';
2019-07-05 03:19:24 +08:00
if (silence) {
2016-07-08 07:22:58 +08:00
hasher.changed.active = false;
hasher[cmd](hash);
hasher.changed.active = true;
2019-07-05 03:19:24 +08:00
} else {
2016-07-08 07:22:58 +08:00
hasher.changed.active = true;
hasher[cmd](hash);
hasher.setHash(hash);
}
}
/**
* @param {Object} params
* @returns {Function}
*/
2019-07-05 03:19:24 +08:00
function viewDecorator({ name, type, templateID }) {
return (target) => {
2019-07-05 03:19:24 +08:00
if (target) {
if (name) {
if (Array.isArray(name)) {
target.__names = name;
2019-07-05 03:19:24 +08:00
} else {
target.__names = [name];
}
target.__name = target.__names[0];
}
2019-07-05 03:19:24 +08:00
if (type) {
target.__type = type;
}
2019-07-05 03:19:24 +08:00
if (templateID) {
target.__templateID = templateID;
}
}
};
}
2016-09-10 06:38:16 +08:00
/**
* @param {Object} params
* @returns {Function}
*/
2019-07-05 03:19:24 +08:00
function popupDecorator({ name, templateID }) {
return viewDecorator({ name, type: ViewType.Popup, templateID });
2016-09-10 06:38:16 +08:00
}
/**
* @param {Function} canExecute
* @returns {Function}
*/
2019-07-05 03:19:24 +08:00
function commandDecorator(canExecute = true) {
return (target, key, descriptor) => {
2019-07-05 03:19:24 +08:00
if (!key || !key.match(/Command$/)) {
throw new Error(`name "${key}" should end with Command suffix`);
}
2019-07-05 03:19:24 +08:00
const value = descriptor.value || descriptor.initializer(),
normCanExecute = typeof canExecute === 'function' ? canExecute : () => !!canExecute;
descriptor.value = function(...args) {
2019-07-05 03:19:24 +08:00
if (normCanExecute.call(this, this)) {
value.apply(this, args);
}
return false;
};
descriptor.value.__realCanExecute = normCanExecute;
descriptor.value.isCommand = true;
return descriptor;
};
}
2017-07-11 20:40:31 +08:00
/**
* @param {miced} $items
* @returns {Function}
*/
2019-07-05 03:19:24 +08:00
function settingsMenuKeysHandler($items) {
return ((event, handler)=>{
const up = handler && 'up' === handler.shortcut;
if (event && $items.length) {
let index = $items.index($items.filter('.selected'));
if (up && 0 < index) {
index -= 1;
} else if (!up && index < $items.length - 1) {
index += 1;
}
2017-07-11 20:40:31 +08:00
const resultHash = $items.eq(index).attr('href');
if (resultHash) {
setHash(resultHash, false, true);
}
2017-07-11 20:40:31 +08:00
}
}).throttle(200);
2017-07-11 20:40:31 +08:00
}
2016-09-10 06:38:16 +08:00
export {
2019-07-05 03:19:24 +08:00
commandDecorator,
commandDecorator as command,
viewDecorator,
viewDecorator as view,
viewDecorator as viewModel,
popupDecorator,
popupDecorator as popup,
2019-03-30 06:45:12 +08:00
settingsMenuKeysHandler
2016-09-10 06:38:16 +08:00
};