2020-05-29 22:22:00 +02:00
|
|
|
// Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
|
|
|
|
|
|
|
// AnTOS Web desktop is is licensed under the GNU General Public
|
|
|
|
// License v3.0, see the LICENCE file for more information
|
|
|
|
|
|
|
|
// This program is free software: you can redistribute it and/or
|
|
|
|
// modify it under the terms of the GNU General Public License as
|
|
|
|
// published by the Free Software Foundation, either version 3 of
|
|
|
|
// the License, or (at your option) any later version.
|
|
|
|
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
// General Public License for more details.
|
|
|
|
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
//along with this program. If not, see https://www.gnu.org/licenses/.
|
|
|
|
|
|
|
|
namespace OS {
|
2020-06-12 20:24:39 +02:00
|
|
|
/**
|
|
|
|
* This namespace is dedicated to application and service definition.
|
|
|
|
* When an application is loaded, its prototype definition will be
|
|
|
|
* inserted to this namespace for reuse lately
|
|
|
|
*/
|
2020-05-29 22:22:00 +02:00
|
|
|
export namespace application {
|
|
|
|
/**
|
2020-06-15 18:10:13 +02:00
|
|
|
* Abstract prototype of all AntOS applications.
|
2020-06-12 20:24:39 +02:00
|
|
|
* Any new application definition should extend
|
|
|
|
* this prototype
|
2020-05-29 22:22:00 +02:00
|
|
|
*
|
|
|
|
* @export
|
|
|
|
* @abstract
|
|
|
|
* @class BaseApplication
|
|
|
|
* @extends {BaseModel}
|
|
|
|
*/
|
|
|
|
export abstract class BaseApplication extends BaseModel {
|
2020-06-12 20:24:39 +02:00
|
|
|
/**
|
|
|
|
* Placeholder of all settings specific to the application.
|
|
|
|
* The settings stored in this object will be saved to system
|
|
|
|
* setting when logout and can be reused in the next login session
|
|
|
|
*
|
|
|
|
* @type {GenericObject<any>}
|
|
|
|
* @memberof BaseApplication
|
|
|
|
*/
|
2020-05-29 22:22:00 +02:00
|
|
|
setting: GenericObject<any>;
|
2020-06-12 20:24:39 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Hotkeys (shortcuts) defined for this application
|
|
|
|
*
|
|
|
|
* @protected
|
|
|
|
* @type {GUI.ShortcutType}
|
|
|
|
* @memberof BaseApplication
|
|
|
|
*/
|
2020-06-04 17:49:48 +02:00
|
|
|
protected keycomb: GUI.ShortcutType;
|
2020-06-12 20:24:39 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Reference to the system dock
|
|
|
|
*
|
|
|
|
* @type {GUI.tag.AppDockTag}
|
|
|
|
* @memberof BaseApplication
|
|
|
|
*/
|
2020-05-29 22:22:00 +02:00
|
|
|
sysdock: GUI.tag.AppDockTag;
|
2020-06-12 20:24:39 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Reference to the system application menu located
|
|
|
|
* on the system panel
|
|
|
|
*
|
|
|
|
* @type {GUI.tag.MenuTag}
|
|
|
|
* @memberof BaseApplication
|
|
|
|
*/
|
2020-05-29 22:22:00 +02:00
|
|
|
appmenu: GUI.tag.MenuTag;
|
|
|
|
|
|
|
|
/**
|
|
|
|
*Creates an instance of BaseApplication.
|
2020-06-12 20:24:39 +02:00
|
|
|
* @param {string} name application name
|
|
|
|
* @param {AppArgumentsType[]} args application arguments
|
2020-05-29 22:22:00 +02:00
|
|
|
* @memberof BaseApplication
|
|
|
|
*/
|
|
|
|
constructor(name: string, args: AppArgumentsType[]) {
|
|
|
|
super(name, args);
|
|
|
|
if (!setting.applications[this.name]) {
|
|
|
|
setting.applications[this.name] = {};
|
|
|
|
}
|
|
|
|
this.setting = setting.applications[this.name];
|
2021-03-14 21:12:27 +01:00
|
|
|
this.keycomb = {};
|
2020-06-04 17:49:48 +02:00
|
|
|
this.subscribe("appregistry", (m) => {
|
|
|
|
if (m.name === this.name) {
|
|
|
|
this.applySetting(m.data.m);
|
|
|
|
}
|
|
|
|
});
|
2020-05-29 22:22:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-06-12 20:24:39 +02:00
|
|
|
* Init the application, this function is called when the
|
2020-06-15 18:10:13 +02:00
|
|
|
* application process is created and docked in the application
|
2020-06-12 20:24:39 +02:00
|
|
|
* dock.
|
2020-05-29 22:22:00 +02:00
|
|
|
*
|
2020-06-12 20:24:39 +02:00
|
|
|
* The application UI will be rendered after the execution
|
|
|
|
* of this function.
|
2020-05-29 22:22:00 +02:00
|
|
|
*
|
|
|
|
* @returns {void}
|
|
|
|
* @memberof BaseApplication
|
|
|
|
*/
|
|
|
|
init(): void {
|
|
|
|
this.off("*");
|
|
|
|
this.on("exit", () => this.quit(false));
|
|
|
|
// first register some base event to the app
|
|
|
|
this.on("focus", () => {
|
|
|
|
this.sysdock.selectedApp = this;
|
|
|
|
this.appmenu.pid = this.pid;
|
2020-06-12 20:24:39 +02:00
|
|
|
this.appmenu.items = this.baseMenu() || [];
|
|
|
|
this.appmenu.onmenuselect = (
|
|
|
|
d: GUI.tag.MenuEventData
|
|
|
|
): void => {
|
|
|
|
return this.trigger("menuselect", d);
|
|
|
|
};
|
2020-05-29 22:22:00 +02:00
|
|
|
if (this.dialog) {
|
|
|
|
return this.dialog.show();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
this.on("hide", () => {
|
|
|
|
this.sysdock.selectedApp = null;
|
|
|
|
this.appmenu.items = [];
|
|
|
|
this.appmenu.pid = -1;
|
|
|
|
if (this.dialog) {
|
|
|
|
return this.dialog.hide();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
this.on("menuselect", (d) => {
|
|
|
|
switch (d.data.item.data.dataid) {
|
|
|
|
case `${this.name}-about`:
|
|
|
|
return this.openDialog("AboutDialog");
|
|
|
|
case `${this.name}-exit`:
|
|
|
|
return this.trigger("exit", undefined);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
this.on("apptitlechange", () => this.sysdock.update(undefined));
|
|
|
|
this.updateLocale(this.systemsetting.system.locale);
|
|
|
|
return this.loadScheme();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-06-12 20:24:39 +02:00
|
|
|
* Render the application UI by first loading its scheme
|
|
|
|
* and then mount this scheme to the DOM tree
|
2020-05-29 22:22:00 +02:00
|
|
|
*
|
2020-06-04 17:49:48 +02:00
|
|
|
* @protected
|
2020-05-29 22:22:00 +02:00
|
|
|
* @returns {void}
|
|
|
|
* @memberof BaseApplication
|
|
|
|
*/
|
2020-06-04 17:49:48 +02:00
|
|
|
protected loadScheme(): void {
|
2020-05-29 22:22:00 +02:00
|
|
|
//now load the scheme
|
|
|
|
const path = `${this.meta().path}/scheme.html`;
|
|
|
|
return this.render(path);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-06-12 20:24:39 +02:00
|
|
|
* API function to perform an heavy task.
|
|
|
|
* This function will trigger the global `loading`
|
|
|
|
* event at the beginning of the task, and the `loaded`
|
|
|
|
* event after finishing the task
|
2020-05-29 22:22:00 +02:00
|
|
|
*
|
2020-06-04 17:49:48 +02:00
|
|
|
* @protected
|
2020-06-12 20:24:39 +02:00
|
|
|
* @param {Promise<any>} promise the promise on a task to be performed
|
2020-12-20 16:45:51 +01:00
|
|
|
* @returns {Promise<void>}
|
2020-05-29 22:22:00 +02:00
|
|
|
* @memberof BaseApplication
|
|
|
|
*/
|
2020-12-20 16:45:51 +01:00
|
|
|
protected load(promise: Promise<any>): Promise<void> {
|
2020-05-29 22:22:00 +02:00
|
|
|
const q = this._api.mid();
|
|
|
|
return new Promise(async (resolve, reject) => {
|
|
|
|
this._api.loading(q, this.name);
|
|
|
|
try {
|
|
|
|
await promise;
|
|
|
|
this._api.loaded(q, this.name, "OK");
|
|
|
|
return resolve();
|
|
|
|
} catch (e) {
|
|
|
|
this._api.loaded(q, this.name, "FAIL");
|
|
|
|
return reject(__e(e));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-06-12 20:24:39 +02:00
|
|
|
* Bind a hotkey to the application, this function
|
|
|
|
* is used to define application keyboard shortcut
|
2020-05-29 22:22:00 +02:00
|
|
|
*
|
2020-06-04 17:49:48 +02:00
|
|
|
* @protected
|
2020-06-12 20:24:39 +02:00
|
|
|
* @param {string} k the hotkey to bind, should be in the following
|
|
|
|
* format: `[ALT|SHIFT|CTRL|META]-KEY`, e.g. `CTRL-S`
|
|
|
|
* @param {(e: JQuery.KeyboardEventBase) => void} f the callback function
|
2020-05-29 22:22:00 +02:00
|
|
|
* @returns {void}
|
|
|
|
* @memberof BaseApplication
|
|
|
|
*/
|
2020-06-12 20:24:39 +02:00
|
|
|
protected bindKey(
|
|
|
|
k: string,
|
|
|
|
f: (e: JQuery.KeyboardEventBase) => void
|
|
|
|
): void {
|
2021-03-14 21:12:27 +01:00
|
|
|
const arr = k.toUpperCase().split("-");
|
|
|
|
const c = arr.pop();
|
|
|
|
let fnk = "";
|
|
|
|
if (arr.includes("META")) {
|
|
|
|
fnk += "META";
|
|
|
|
}
|
|
|
|
if (arr.includes("CTRL")) {
|
|
|
|
fnk += "CTRL";
|
|
|
|
}
|
|
|
|
if (arr.includes("ALT")) {
|
|
|
|
fnk += "ALT";
|
|
|
|
}
|
|
|
|
if (arr.includes("SHIFT")) {
|
|
|
|
fnk += "SHIFT";
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( fnk == "") {
|
2020-05-29 22:22:00 +02:00
|
|
|
return;
|
|
|
|
}
|
2021-03-14 21:12:27 +01:00
|
|
|
fnk = `fn_${fnk.hash()}`;
|
|
|
|
|
2020-05-29 22:22:00 +02:00
|
|
|
if (!this.keycomb[fnk]) {
|
2021-03-14 21:12:27 +01:00
|
|
|
this.keycomb[fnk] = {};
|
2020-05-29 22:22:00 +02:00
|
|
|
}
|
|
|
|
this.keycomb[fnk][c] = f;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-06-12 20:24:39 +02:00
|
|
|
* Update the application local from the system
|
|
|
|
* locale or application specific locale configuration
|
2020-05-29 22:22:00 +02:00
|
|
|
*
|
2020-06-04 17:49:48 +02:00
|
|
|
* @private
|
2020-06-12 20:24:39 +02:00
|
|
|
* @param {string} name locale name e.g. `en_GB`
|
2020-05-29 22:22:00 +02:00
|
|
|
* @returns {void}
|
|
|
|
* @memberof BaseApplication
|
|
|
|
*/
|
2020-06-04 17:49:48 +02:00
|
|
|
protected updateLocale(name: string): void {
|
2020-05-29 22:22:00 +02:00
|
|
|
const meta = this.meta();
|
|
|
|
if (!meta || !meta.locales) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!meta.locales[name]) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const result = [];
|
|
|
|
for (let k in meta.locales[name]) {
|
|
|
|
const v = meta.locales[name][k];
|
|
|
|
result.push((this._api.lang[k] = v));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-06-12 20:24:39 +02:00
|
|
|
* Execute the callback subscribed to a
|
|
|
|
* keyboard shortcut
|
2020-05-29 22:22:00 +02:00
|
|
|
*
|
2020-06-12 20:24:39 +02:00
|
|
|
* @param {string} fnk meta or modifier key e.g. `CTRL`, `ALT`, `SHIFT` or `META`
|
|
|
|
* @param {string} c a regular key
|
|
|
|
* @param {JQuery.KeyboardEventBase} e JQuery keyboard event
|
|
|
|
* @returns {boolean} return whether the shortcut is executed
|
2020-05-29 22:22:00 +02:00
|
|
|
* @memberof BaseApplication
|
|
|
|
*/
|
2021-03-14 21:12:27 +01:00
|
|
|
shortcut(fnk: string, c: string, e: JQuery.KeyUpEvent): boolean {
|
2020-05-29 22:22:00 +02:00
|
|
|
if (!this.keycomb[fnk]) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (!this.keycomb[fnk][c]) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
this.keycomb[fnk][c](e);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-06-12 20:24:39 +02:00
|
|
|
* Apply a setting to the application
|
2020-05-29 22:22:00 +02:00
|
|
|
*
|
2020-06-04 17:49:48 +02:00
|
|
|
* @protected
|
2020-06-12 20:24:39 +02:00
|
|
|
* @param {string} k the setting name
|
2020-05-29 22:22:00 +02:00
|
|
|
* @memberof BaseApplication
|
|
|
|
*/
|
2020-06-04 17:49:48 +02:00
|
|
|
protected applySetting(k: string): void {}
|
2020-05-29 22:22:00 +02:00
|
|
|
|
|
|
|
/**
|
2020-06-12 20:24:39 +02:00
|
|
|
* Apply all settings to the application
|
2020-05-29 22:22:00 +02:00
|
|
|
*
|
2020-06-04 17:49:48 +02:00
|
|
|
* @protected
|
2020-05-29 22:22:00 +02:00
|
|
|
* @memberof BaseApplication
|
|
|
|
*/
|
2020-06-04 17:49:48 +02:00
|
|
|
protected applyAllSetting(): void {
|
2020-05-29 22:22:00 +02:00
|
|
|
for (let k in this.setting) {
|
|
|
|
const v = this.setting[k];
|
|
|
|
this.applySetting(k);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-06-12 20:24:39 +02:00
|
|
|
* Set a setting value to the application setting
|
|
|
|
* registry
|
2020-05-29 22:22:00 +02:00
|
|
|
*
|
2020-06-04 17:49:48 +02:00
|
|
|
* @protected
|
2020-06-12 20:24:39 +02:00
|
|
|
* @param {string} k setting name
|
|
|
|
* @param {*} v setting value
|
2020-05-29 22:22:00 +02:00
|
|
|
* @returns {void}
|
|
|
|
* @memberof BaseApplication
|
|
|
|
*/
|
2020-06-04 17:49:48 +02:00
|
|
|
protected registry(k: string, v: any): void {
|
2020-05-29 22:22:00 +02:00
|
|
|
this.setting[k] = v;
|
|
|
|
return this.publish("appregistry", k);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-06-12 20:24:39 +02:00
|
|
|
* Show the appliation
|
2020-05-29 22:22:00 +02:00
|
|
|
*
|
|
|
|
* @returns {void}
|
|
|
|
* @memberof BaseApplication
|
|
|
|
*/
|
|
|
|
show(): void {
|
2020-12-18 19:51:19 +01:00
|
|
|
this.trigger("focus", undefined);
|
2020-05-29 22:22:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-06-12 20:24:39 +02:00
|
|
|
* Blur the application
|
2020-05-29 22:22:00 +02:00
|
|
|
*
|
|
|
|
* @returns {void}
|
|
|
|
* @memberof BaseApplication
|
|
|
|
*/
|
|
|
|
blur(): void {
|
|
|
|
if (this.appmenu && this.pid === this.appmenu.pid) {
|
|
|
|
this.appmenu.items = [];
|
|
|
|
}
|
|
|
|
return this.trigger("blur", undefined);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-06-12 20:24:39 +02:00
|
|
|
* Hide the application
|
2020-05-29 22:22:00 +02:00
|
|
|
*
|
|
|
|
* @returns {void}
|
|
|
|
* @memberof BaseApplication
|
|
|
|
*/
|
|
|
|
hide(): void {
|
|
|
|
return this.trigger("hide", undefined);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-06-12 20:24:39 +02:00
|
|
|
* Maximize or restore the application window size
|
|
|
|
* and its position
|
2020-05-29 22:22:00 +02:00
|
|
|
*
|
|
|
|
* @returns {void}
|
|
|
|
* @memberof BaseApplication
|
|
|
|
*/
|
|
|
|
toggle(): void {
|
|
|
|
return this.trigger("toggle", undefined);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-06-12 20:24:39 +02:00
|
|
|
* Get the application title
|
2020-05-29 22:22:00 +02:00
|
|
|
*
|
2020-06-10 11:15:01 +02:00
|
|
|
* @returns {(string| FormattedString)}
|
2020-05-29 22:22:00 +02:00
|
|
|
* @memberof BaseApplication
|
|
|
|
*/
|
2020-06-12 20:24:39 +02:00
|
|
|
title(): string | FormattedString {
|
2020-05-29 22:22:00 +02:00
|
|
|
return (this.scheme as GUI.tag.WindowTag).apptitle;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-06-12 20:24:39 +02:00
|
|
|
* Function called when the application exit.
|
|
|
|
* If the input exit event is prevented, the application
|
|
|
|
* process will not be killed
|
2020-05-29 22:22:00 +02:00
|
|
|
*
|
|
|
|
*
|
2020-06-04 17:49:48 +02:00
|
|
|
* @protected
|
2020-06-12 20:24:39 +02:00
|
|
|
* @param {BaseEvent} evt exit event
|
2020-05-29 22:22:00 +02:00
|
|
|
* @memberof BaseApplication
|
|
|
|
*/
|
2020-06-04 17:49:48 +02:00
|
|
|
protected onexit(evt: BaseEvent): void {
|
2020-05-29 22:22:00 +02:00
|
|
|
this.cleanup(evt);
|
|
|
|
if (!evt.prevent) {
|
|
|
|
if (this.pid === this.appmenu.pid) {
|
|
|
|
this.appmenu.items = [];
|
|
|
|
}
|
|
|
|
$(this.scheme).remove();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-06-12 20:24:39 +02:00
|
|
|
* Get the application meta-data
|
2020-05-29 22:22:00 +02:00
|
|
|
*
|
|
|
|
* @returns {API.PackageMetaType}
|
|
|
|
* @memberof BaseApplication
|
|
|
|
*/
|
|
|
|
meta(): API.PackageMetaType {
|
|
|
|
return application[this.name].meta;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-06-12 20:24:39 +02:00
|
|
|
* Base menu definition. This function
|
2020-06-15 18:10:13 +02:00
|
|
|
* returns the based menu definition of all applications.
|
2020-06-12 20:24:39 +02:00
|
|
|
* Other application specific menu entries
|
|
|
|
* should be defined in [[menu]] function
|
2020-05-29 22:22:00 +02:00
|
|
|
*
|
2020-06-04 17:49:48 +02:00
|
|
|
* @protected
|
|
|
|
* @returns {GUI.BasicItemType[]}
|
2020-05-29 22:22:00 +02:00
|
|
|
* @memberof BaseApplication
|
|
|
|
*/
|
2020-06-04 17:49:48 +02:00
|
|
|
protected baseMenu(): GUI.BasicItemType[] {
|
2020-05-29 22:22:00 +02:00
|
|
|
let mn: GUI.BasicItemType[] = [
|
|
|
|
{
|
|
|
|
text: application[this.name].meta.name,
|
|
|
|
nodes: [
|
|
|
|
{ text: "__(About)", dataid: `${this.name}-about` },
|
|
|
|
{ text: "__(Exit)", dataid: `${this.name}-exit` },
|
|
|
|
],
|
|
|
|
},
|
|
|
|
];
|
|
|
|
mn = mn.concat(this.menu() || []);
|
|
|
|
return mn;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-06-12 20:24:39 +02:00
|
|
|
* The main application entry that is called after
|
|
|
|
* the application UI is rendered. This application
|
|
|
|
* must be implemented by all subclasses
|
2020-05-29 22:22:00 +02:00
|
|
|
*
|
|
|
|
* @abstract
|
|
|
|
* @memberof BaseApplication
|
|
|
|
*/
|
|
|
|
abstract main(): void;
|
|
|
|
|
|
|
|
/**
|
2020-06-12 20:24:39 +02:00
|
|
|
* Application specific menu definition
|
2020-05-29 22:22:00 +02:00
|
|
|
*
|
2020-06-04 17:49:48 +02:00
|
|
|
* @protected
|
|
|
|
* @returns {GUI.BasicItemType[]}
|
2020-05-29 22:22:00 +02:00
|
|
|
* @memberof BaseApplication
|
|
|
|
*/
|
2020-06-04 17:49:48 +02:00
|
|
|
protected menu(): GUI.BasicItemType[] {
|
2020-05-29 22:22:00 +02:00
|
|
|
// implement by subclasses
|
|
|
|
// to add menu to application
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-06-12 20:24:39 +02:00
|
|
|
* The cleanup function that is called by [[onexit]] function.
|
|
|
|
* Application need to override this function to perform some
|
|
|
|
* specific task before exiting or to prevent the application
|
|
|
|
* to be exited
|
2020-05-29 22:22:00 +02:00
|
|
|
*
|
2020-06-04 17:49:48 +02:00
|
|
|
* @protected
|
2020-05-29 22:22:00 +02:00
|
|
|
* @param {BaseEvent} e
|
|
|
|
* @memberof BaseApplication
|
|
|
|
*/
|
2020-06-04 17:49:48 +02:00
|
|
|
protected cleanup(e: BaseEvent): void {}
|
2020-05-29 22:22:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
BaseApplication.type = ModelType.Application;
|
|
|
|
}
|
|
|
|
}
|