2016-07-08 07:22:58 +08:00
|
|
|
import ko from 'ko';
|
|
|
|
|
2021-11-01 18:24:11 +08:00
|
|
|
import { doc, $htmlCL, elementById } from 'Common/Globals';
|
|
|
|
import { isFunction, forEachObjectValue } from 'Common/Utils';
|
2016-07-08 07:22:58 +08:00
|
|
|
|
2021-11-01 18:24:11 +08:00
|
|
|
let
|
|
|
|
SCREENS = {},
|
|
|
|
currentScreen = null,
|
2021-03-17 00:09:39 +08:00
|
|
|
defaultScreenName = '';
|
2016-07-08 07:22:58 +08:00
|
|
|
|
2021-11-01 18:24:11 +08:00
|
|
|
const
|
2020-09-02 23:06:35 +08:00
|
|
|
autofocus = dom => {
|
|
|
|
const af = dom.querySelector('[autofocus]');
|
|
|
|
af && af.focus();
|
2021-08-20 21:40:07 +08:00
|
|
|
},
|
|
|
|
|
2021-09-22 19:37:12 +08:00
|
|
|
visiblePopups = new Set,
|
|
|
|
|
2021-08-20 21:40:07 +08:00
|
|
|
/**
|
|
|
|
* @param {string} screenName
|
|
|
|
* @returns {?Object}
|
|
|
|
*/
|
|
|
|
screen = screenName => screenName && null != SCREENS[screenName] ? SCREENS[screenName] : null,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {Function} ViewModelClass
|
|
|
|
* @param {Object=} vmScreen
|
|
|
|
* @returns {*}
|
|
|
|
*/
|
|
|
|
buildViewModel = (ViewModelClass, vmScreen) => {
|
|
|
|
if (ViewModelClass && !ViewModelClass.__builded) {
|
|
|
|
let vmDom = null;
|
|
|
|
const vm = new ViewModelClass(vmScreen),
|
2021-09-23 02:17:44 +08:00
|
|
|
position = vm.viewType || '',
|
2021-08-20 21:40:07 +08:00
|
|
|
vmPlace = position ? doc.getElementById('rl-' + position.toLowerCase()) : null;
|
|
|
|
|
|
|
|
ViewModelClass.__builded = true;
|
|
|
|
ViewModelClass.__vm = vm;
|
|
|
|
|
|
|
|
if (vmPlace) {
|
2021-10-11 20:31:54 +08:00
|
|
|
vmDom = Element.fromHTML('<div class="rl-view-model RL-' + vm.viewModelTemplateID + '" hidden=""></div>');
|
2021-08-20 21:40:07 +08:00
|
|
|
vmPlace.append(vmDom);
|
|
|
|
|
|
|
|
vm.viewModelDom = vmDom;
|
|
|
|
ViewModelClass.__dom = vmDom;
|
|
|
|
|
|
|
|
if (ViewType.Popup === position) {
|
|
|
|
vm.cancelCommand = vm.closeCommand = createCommand(() => {
|
|
|
|
hideScreenPopup(ViewModelClass);
|
|
|
|
});
|
|
|
|
|
|
|
|
// show/hide popup/modal
|
|
|
|
const endShowHide = e => {
|
|
|
|
if (e.target === vmDom) {
|
|
|
|
if (vmDom.classList.contains('show')) {
|
|
|
|
autofocus(vmDom);
|
|
|
|
vm.onShowWithDelay && vm.onShowWithDelay();
|
|
|
|
} else {
|
|
|
|
vmDom.hidden = true;
|
|
|
|
vm.onHideWithDelay && vm.onHideWithDelay();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
vm.modalVisibility.subscribe(value => {
|
|
|
|
if (value) {
|
2021-09-10 16:25:58 +08:00
|
|
|
visiblePopups.add(vm);
|
2021-09-22 19:37:12 +08:00
|
|
|
vmDom.style.zIndex = 3000 + visiblePopups.size + 10;
|
2021-08-20 21:40:07 +08:00
|
|
|
vmDom.hidden = false;
|
2021-09-23 02:17:44 +08:00
|
|
|
vm.keyScope.set();
|
2021-09-22 19:37:12 +08:00
|
|
|
arePopupsVisible(true);
|
2021-08-20 21:40:07 +08:00
|
|
|
requestAnimationFrame(() => { // wait just before the next paint
|
|
|
|
vmDom.offsetHeight; // force a reflow
|
|
|
|
vmDom.classList.add('show'); // trigger the transitions
|
|
|
|
});
|
2020-09-02 23:06:35 +08:00
|
|
|
} else {
|
2021-09-10 16:25:58 +08:00
|
|
|
visiblePopups.delete(vm);
|
2021-08-20 21:40:07 +08:00
|
|
|
vm.onHide && vm.onHide();
|
|
|
|
vmDom.classList.remove('show');
|
2021-09-23 02:17:44 +08:00
|
|
|
vm.keyScope.unset();
|
2021-09-22 19:37:12 +08:00
|
|
|
arePopupsVisible(0 < visiblePopups.size);
|
2020-09-02 23:06:35 +08:00
|
|
|
}
|
2021-08-20 21:40:07 +08:00
|
|
|
vmDom.setAttribute('aria-hidden', !value);
|
|
|
|
});
|
|
|
|
if ('ontransitionend' in vmDom) {
|
|
|
|
vmDom.addEventListener('transitionend', endShowHide);
|
2019-07-05 03:19:24 +08:00
|
|
|
} else {
|
2021-08-20 21:40:07 +08:00
|
|
|
// For Edge < 79 and mobile browsers
|
|
|
|
vm.modalVisibility.subscribe(() => ()=>setTimeout(endShowHide({target:vmDom}), 500));
|
2016-07-08 07:22:58 +08:00
|
|
|
}
|
2020-09-02 23:06:35 +08:00
|
|
|
}
|
2016-07-08 07:22:58 +08:00
|
|
|
|
2021-08-20 21:40:07 +08:00
|
|
|
ko.applyBindingAccessorsToNode(
|
|
|
|
vmDom,
|
|
|
|
{
|
|
|
|
i18nInit: true,
|
2021-10-11 20:31:54 +08:00
|
|
|
template: () => ({ name: vm.viewModelTemplateID })
|
2021-08-20 21:40:07 +08:00
|
|
|
},
|
|
|
|
vm
|
|
|
|
);
|
2021-05-31 22:19:01 +08:00
|
|
|
|
2021-08-20 21:40:07 +08:00
|
|
|
vm.onBuild && vm.onBuild(vmDom);
|
|
|
|
if (vm && ViewType.Popup === position) {
|
|
|
|
vm.registerPopupKeyDown();
|
|
|
|
}
|
|
|
|
|
|
|
|
dispatchEvent(new CustomEvent('rl-view-model', {detail:vm}));
|
|
|
|
} else {
|
|
|
|
console.log('Cannot find view model position: ' + position);
|
2016-07-08 07:22:58 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-20 21:40:07 +08:00
|
|
|
return ViewModelClass && ViewModelClass.__vm;
|
|
|
|
},
|
2016-07-08 07:22:58 +08:00
|
|
|
|
2021-09-23 02:17:44 +08:00
|
|
|
forEachViewModel = (screen, fn) => {
|
|
|
|
screen.viewModels.forEach(ViewModelClass => {
|
|
|
|
if (
|
|
|
|
ViewModelClass.__vm &&
|
|
|
|
ViewModelClass.__dom &&
|
|
|
|
ViewType.Popup !== ViewModelClass.__vm.viewType
|
|
|
|
) {
|
|
|
|
fn(ViewModelClass.__vm, ViewModelClass.__dom);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2021-11-01 18:24:11 +08:00
|
|
|
hideScreen = (screenToHide, destroy) => {
|
|
|
|
screenToHide.onHide && screenToHide.onHide();
|
|
|
|
forEachViewModel(screenToHide, (vm, dom) => {
|
|
|
|
dom.hidden = true;
|
|
|
|
vm.onHide && vm.onHide();
|
|
|
|
destroy && vm.viewModelDom.remove();
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2021-08-20 21:40:07 +08:00
|
|
|
/**
|
|
|
|
* @param {string} screenName
|
|
|
|
* @param {string} subPart
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
screenOnRoute = (screenName, subPart) => {
|
|
|
|
let vmScreen = null,
|
|
|
|
isSameScreen = false;
|
2016-07-08 07:22:58 +08:00
|
|
|
|
2021-08-20 21:40:07 +08:00
|
|
|
if (null == screenName || '' == screenName) {
|
|
|
|
screenName = defaultScreenName;
|
|
|
|
}
|
2016-07-08 07:22:58 +08:00
|
|
|
|
2021-09-10 16:25:58 +08:00
|
|
|
// Close all popups
|
|
|
|
for (let vm of visiblePopups) {
|
|
|
|
vm.closeCommand();
|
|
|
|
}
|
|
|
|
|
2021-08-20 21:40:07 +08:00
|
|
|
if (screenName) {
|
|
|
|
vmScreen = screen(screenName);
|
|
|
|
if (!vmScreen) {
|
|
|
|
vmScreen = screen(defaultScreenName);
|
|
|
|
if (vmScreen) {
|
|
|
|
subPart = screenName + '/' + subPart;
|
|
|
|
screenName = defaultScreenName;
|
|
|
|
}
|
2016-07-08 07:22:58 +08:00
|
|
|
}
|
|
|
|
|
2021-08-20 21:40:07 +08:00
|
|
|
if (vmScreen && vmScreen.__started) {
|
|
|
|
isSameScreen = currentScreen && vmScreen === currentScreen;
|
|
|
|
|
|
|
|
if (!vmScreen.__builded) {
|
|
|
|
vmScreen.__builded = true;
|
|
|
|
|
|
|
|
vmScreen.viewModels.forEach(ViewModelClass =>
|
|
|
|
buildViewModel(ViewModelClass, vmScreen)
|
|
|
|
);
|
|
|
|
|
|
|
|
vmScreen.onBuild && vmScreen.onBuild();
|
2016-07-08 07:22:58 +08:00
|
|
|
}
|
|
|
|
|
2021-08-20 21:40:07 +08:00
|
|
|
setTimeout(() => {
|
|
|
|
// hide screen
|
|
|
|
if (currentScreen && !isSameScreen) {
|
2021-11-01 18:24:11 +08:00
|
|
|
hideScreen(currentScreen);
|
2021-08-20 21:40:07 +08:00
|
|
|
}
|
|
|
|
// --
|
|
|
|
|
|
|
|
currentScreen = vmScreen;
|
2016-07-08 07:22:58 +08:00
|
|
|
|
2021-08-20 21:40:07 +08:00
|
|
|
// show screen
|
2021-09-23 02:17:44 +08:00
|
|
|
if (!isSameScreen) {
|
|
|
|
vmScreen.onShow && vmScreen.onShow();
|
|
|
|
|
|
|
|
forEachViewModel(vmScreen, (vm, dom) => {
|
|
|
|
vm.onBeforeShow && vm.onBeforeShow();
|
|
|
|
dom.hidden = false;
|
|
|
|
vm.onShow && vm.onShow();
|
|
|
|
autofocus(dom);
|
|
|
|
});
|
2016-07-08 07:22:58 +08:00
|
|
|
}
|
2021-08-20 21:40:07 +08:00
|
|
|
// --
|
|
|
|
|
2021-09-23 02:17:44 +08:00
|
|
|
vmScreen.__cross && vmScreen.__cross.parse(subPart);
|
2021-08-20 21:40:07 +08:00
|
|
|
}, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
export const
|
|
|
|
ViewType = {
|
|
|
|
Popup: 'Popups',
|
|
|
|
Left: 'Left',
|
|
|
|
Right: 'Right',
|
|
|
|
Content: 'Content'
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {Function} fExecute
|
|
|
|
* @param {(Function|boolean|null)=} fCanExecute = true
|
|
|
|
* @returns {Function}
|
|
|
|
*/
|
|
|
|
createCommand = (fExecute, fCanExecute = true) => {
|
|
|
|
let fResult = fExecute
|
|
|
|
? (...args) => {
|
|
|
|
if (fResult && fResult.canExecute && fResult.canExecute()) {
|
|
|
|
fExecute.apply(null, args);
|
2016-07-08 07:22:58 +08:00
|
|
|
}
|
2021-08-20 21:40:07 +08:00
|
|
|
return false;
|
|
|
|
} : ()=>0;
|
|
|
|
fResult.enabled = ko.observable(true);
|
|
|
|
fResult.isCommand = true;
|
2016-07-08 07:22:58 +08:00
|
|
|
|
2021-08-20 21:40:07 +08:00
|
|
|
if (isFunction(fCanExecute)) {
|
|
|
|
fResult.canExecute = ko.computed(() => fResult && fResult.enabled() && fCanExecute.call(null));
|
|
|
|
} else {
|
|
|
|
fResult.canExecute = ko.computed(() => fResult && fResult.enabled() && !!fCanExecute);
|
2016-07-08 07:22:58 +08:00
|
|
|
}
|
2021-08-20 21:40:07 +08:00
|
|
|
|
|
|
|
return fResult;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {Function} ViewModelClassToHide
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
hideScreenPopup = ViewModelClassToHide => {
|
|
|
|
if (ViewModelClassToHide && ViewModelClassToHide.__vm && ViewModelClassToHide.__dom) {
|
|
|
|
ViewModelClassToHide.__vm.modalVisibility(false);
|
2016-07-08 07:22:58 +08:00
|
|
|
}
|
2021-08-20 21:40:07 +08:00
|
|
|
},
|
|
|
|
|
|
|
|
getScreenPopupViewModel = ViewModelClassToShow =>
|
|
|
|
(buildViewModel(ViewModelClassToShow) && ViewModelClassToShow.__dom) && ViewModelClassToShow.__vm,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {Function} ViewModelClassToShow
|
|
|
|
* @param {Array=} params
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
showScreenPopup = (ViewModelClassToShow, params = []) => {
|
|
|
|
const vm = getScreenPopupViewModel(ViewModelClassToShow);
|
|
|
|
if (vm) {
|
|
|
|
params = params || [];
|
|
|
|
|
|
|
|
vm.onBeforeShow && vm.onBeforeShow(...params);
|
|
|
|
|
|
|
|
vm.modalVisibility(true);
|
|
|
|
|
|
|
|
vm.onShow && vm.onShow(...params);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2021-09-22 19:37:12 +08:00
|
|
|
arePopupsVisible = ko.observable(false),
|
2021-08-31 22:17:20 +08:00
|
|
|
|
2021-08-20 21:40:07 +08:00
|
|
|
/**
|
|
|
|
* @param {Function} ViewModelClassToShow
|
|
|
|
* @returns {boolean}
|
|
|
|
*/
|
|
|
|
isPopupVisible = ViewModelClassToShow =>
|
|
|
|
ViewModelClassToShow && ViewModelClassToShow.__vm && ViewModelClassToShow.__vm.modalVisibility(),
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {Array} screensClasses
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
startScreens = screensClasses => {
|
2021-11-01 18:24:11 +08:00
|
|
|
hasher.clear();
|
|
|
|
forEachObjectValue(SCREENS, screen => hideScreen(screen, 1));
|
|
|
|
SCREENS = {};
|
|
|
|
currentScreen = null,
|
|
|
|
defaultScreenName = '';
|
|
|
|
|
2021-08-20 21:40:07 +08:00
|
|
|
screensClasses.forEach(CScreen => {
|
|
|
|
if (CScreen) {
|
|
|
|
const vmScreen = new CScreen(),
|
2021-09-23 02:17:44 +08:00
|
|
|
screenName = vmScreen.screenName;
|
|
|
|
defaultScreenName || (defaultScreenName = screenName);
|
|
|
|
SCREENS[screenName] = vmScreen;
|
2021-08-20 21:40:07 +08:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2021-11-01 18:24:11 +08:00
|
|
|
forEachObjectValue(SCREENS, vmScreen => vmScreen.onStart());
|
2016-07-08 07:22:58 +08:00
|
|
|
|
2021-08-20 21:40:07 +08:00
|
|
|
const cross = new Crossroads();
|
|
|
|
cross.addRoute(/^([a-zA-Z0-9-]*)\/?(.*)$/, screenOnRoute);
|
2016-07-08 07:22:58 +08:00
|
|
|
|
2021-08-20 21:40:07 +08:00
|
|
|
hasher.add(cross.parse.bind(cross));
|
|
|
|
hasher.init();
|
2016-07-08 07:22:58 +08:00
|
|
|
|
2021-08-20 21:40:07 +08:00
|
|
|
setTimeout(() => $htmlCL.remove('rl-started-trigger'), 100);
|
|
|
|
setTimeout(() => $htmlCL.add('rl-started-delay'), 200);
|
2021-11-01 18:24:11 +08:00
|
|
|
|
|
|
|
const c = elementById('rl-content'), l = elementById('rl-loading');
|
|
|
|
c && (c.hidden = false);
|
|
|
|
l && l.remove();
|
2021-08-20 21:40:07 +08:00
|
|
|
},
|
2016-07-08 07:22:58 +08:00
|
|
|
|
2021-08-20 21:40:07 +08:00
|
|
|
decorateKoCommands = (thisArg, commands) =>
|
|
|
|
Object.entries(commands).forEach(([key, canExecute]) => {
|
|
|
|
let command = thisArg[key],
|
|
|
|
fn = (...args) => fn.enabled() && fn.canExecute() && command.apply(thisArg, args);
|
2016-07-08 07:22:58 +08:00
|
|
|
|
2021-08-20 21:40:07 +08:00
|
|
|
// fn.__realCanExecute = canExecute;
|
|
|
|
// fn.isCommand = true;
|
2016-09-03 07:19:37 +08:00
|
|
|
|
2021-08-20 21:40:07 +08:00
|
|
|
fn.enabled = ko.observable(true);
|
2016-09-03 07:19:37 +08:00
|
|
|
|
2021-08-20 21:40:07 +08:00
|
|
|
fn.canExecute = (typeof canExecute === 'function')
|
|
|
|
? ko.computed(() => fn.enabled() && canExecute.call(thisArg, thisArg))
|
|
|
|
: ko.computed(() => fn.enabled());
|
2021-02-19 19:09:20 +08:00
|
|
|
|
2021-08-20 21:40:07 +08:00
|
|
|
thisArg[key] = fn;
|
|
|
|
});
|
2021-02-19 19:09:20 +08:00
|
|
|
|
2021-03-01 07:52:46 +08:00
|
|
|
ko.decorateCommands = decorateKoCommands;
|