From 46e7e6d94f78a3bc084b2d15b79ffa38cd6b7635 Mon Sep 17 00:00:00 2001 From: DanyLE Date: Tue, 20 Jun 2023 17:16:13 +0200 Subject: [PATCH] feat: use a separated setting file for each application instead of a single system setting files --- d.ts/antos.d.ts | 53 ++++++++++------ src/core/BaseApplication.ts | 121 ++++++++++++++++++------------------ src/core/BaseDialog.ts | 2 +- src/core/BaseModel.ts | 4 +- src/core/core.ts | 86 ++++++++++++++++++------- src/core/gui.ts | 62 ++++++++++++++---- src/core/pm.ts | 19 ++++++ src/core/settings.ts | 6 +- src/packages/Files/main.ts | 4 +- 9 files changed, 236 insertions(+), 121 deletions(-) diff --git a/d.ts/antos.d.ts b/d.ts/antos.d.ts index 59f6f8f..10e27eb 100644 --- a/d.ts/antos.d.ts +++ b/d.ts/antos.d.ts @@ -1037,8 +1037,9 @@ declare namespace OS { * @param {string} path VFS path to the scheme file * @param {BaseModel} app the target application * @param {(HTMLElement | string)} parent The parent HTML element where the application is rendered. + * @return {Promise} a promise object */ - function loadScheme(path: string, app: BaseModel, parent: HTMLElement | string): void; + function loadScheme(path: string, app: BaseModel, parent: HTMLElement | string): Promise; /** * Clear the current system theme * @@ -2289,6 +2290,19 @@ declare namespace OS { * @returns {*} */ function switcher(...args: string[]): any; + /** + * A watcher is a Proxy wrapper to an object + * + * It is used to automatically detect changes in the + * target object and notify the change to a callback + * handler + * + * @export + * @param {Object} target object + * @param {(obj: Object, key: string, value: any, path: any[]) => void} callback function + * @returns {Proxy} the wrapper object + */ + function watcher(target: GenericObject, callback: (obj: Object, key: any, value: any, path: any[]) => void): Object; } } /// @@ -2311,9 +2325,19 @@ declare namespace OS { */ abstract class BaseApplication extends BaseModel { /** - * 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 + * Watcher of all settings specific to the application. + * The settings stored in this object will be saved to application folder + * in JSON format as .settings.json and will be loaded automatically + * when application is initialized. + * + * This object is globally acessible to all processes of the same application + * + * @type {GenericObject} + * @memberof BaseApplication + */ + static setting_wdg: GenericObject; + /** + * Reference to per application setting i.e. setting_wdg * * @type {GenericObject} * @memberof BaseApplication @@ -2352,16 +2376,16 @@ declare namespace OS { * @returns {void} * @memberof BaseApplication */ - init(): void; + init(): Promise; /** * Render the application UI by first loading its scheme * and then mount this scheme to the DOM tree * * @protected - * @returns {void} + * @returns {Promise} * @memberof BaseApplication */ - protected loadScheme(): void; + protected loadScheme(): Promise; /** * API function to perform an heavy task. * This function will create a Task that is tracked by any @@ -2420,17 +2444,6 @@ declare namespace OS { * @memberof BaseApplication */ protected applyAllSetting(): void; - /** - * Set a setting value to the application setting - * registry - * - * @protected - * @param {string} k setting name - * @param {*} v setting value - * @returns {void} - * @memberof BaseApplication - */ - protected registry(k: string, v: any): void; /** * Show the appliation * @@ -2803,10 +2816,10 @@ declare namespace OS { * * @protected * @param {string} p VFS path to the UI scheme definition - * @returns {void} + * @returns {Promise} * @memberof BaseModel */ - protected render(p: string): void; + protected render(p: string): Promise; /** * Exit the model * diff --git a/src/core/BaseApplication.ts b/src/core/BaseApplication.ts index 2be1ece..7af4bb7 100644 --- a/src/core/BaseApplication.ts +++ b/src/core/BaseApplication.ts @@ -35,9 +35,21 @@ namespace OS { */ export abstract class BaseApplication extends BaseModel { /** - * 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 + * Watcher of all settings specific to the application. + * The settings stored in this object will be saved to application folder + * in JSON format as .settings.json and will be loaded automatically + * when application is initialized. + * + * This object is globally acessible to all processes of the same application + * + * @type {GenericObject} + * @memberof BaseApplication + */ + static setting_wdg: GenericObject; + + + /** + * Reference to per application setting i.e. setting_wdg * * @type {GenericObject} * @memberof BaseApplication @@ -69,10 +81,7 @@ namespace OS { */ constructor(name: string, args: AppArgumentsType[]) { super(name, args); - if (!setting.applications[this.name]) { - setting.applications[this.name] = {}; - } - this.setting = setting.applications[this.name]; + this.setting = (this.constructor as any).setting_wdg; this.keycomb = {}; } @@ -87,43 +96,52 @@ namespace OS { * @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", () => { - //if(this.sysdock.selectedApp != this) - this.sysdock.selectedApp = this; - (this.scheme as GUI.tag.WindowTag).onmenuopen = (el) => el.nodes = this.baseMenu() || []; - OS.PM.pidactive = this.pid; - this.trigger("focused", undefined); - if (this.dialog) { - return this.dialog.show(); + init(): Promise { + return new Promise(async (ok, nok) =>{ + try { + this.off("*"); + this.on("exit", () => this.quit(false)); + // first register some base event to the app + this.on("focus", () => { + //if(this.sysdock.selectedApp != this) + this.sysdock.selectedApp = this; + (this.scheme as GUI.tag.WindowTag).onmenuopen = (el) => el.nodes = this.baseMenu() || []; + OS.PM.pidactive = this.pid; + this.trigger("focused", undefined); + if (this.dialog) { + return this.dialog.show(); + } + }); + this.on("hide", () => { + this.sysdock.selectedApp = null; + 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(this)); + this.subscribe("appregistry", (m) => { + if (m.name === this.name) { + this.applySetting(m.message as string); + } + }); + + this.updateLocale(this.systemsetting.system.locale); + await this.loadScheme(); + this.applyAllSetting(); + } + catch(e) + { + nok(__e(e)); } }); - this.on("hide", () => { - this.sysdock.selectedApp = null; - 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(this)); - this.subscribe("appregistry", (m) => { - if (m.name === this.name) { - this.applySetting(m.message as string); - } - }); - - this.updateLocale(this.systemsetting.system.locale); - return this.loadScheme(); } /** @@ -131,10 +149,10 @@ namespace OS { * and then mount this scheme to the DOM tree * * @protected - * @returns {void} + * @returns {Promise} * @memberof BaseApplication */ - protected loadScheme(): void { + protected loadScheme(): Promise { //now load the scheme const path = `${this.meta().path}/scheme.html`; return this.render(path); @@ -270,21 +288,6 @@ namespace OS { } } - /** - * Set a setting value to the application setting - * registry - * - * @protected - * @param {string} k setting name - * @param {*} v setting value - * @returns {void} - * @memberof BaseApplication - */ - protected registry(k: string, v: any): void { - this.setting[k] = v; - return this.publish("appregistry", k); - } - /** * Show the appliation * diff --git a/src/core/BaseDialog.ts b/src/core/BaseDialog.ts index 88c04b6..23e1bb1 100644 --- a/src/core/BaseDialog.ts +++ b/src/core/BaseDialog.ts @@ -288,7 +288,7 @@ namespace OS { return GUI.htmlToScheme(this.markup, this, this.host); } else { // a file handle - return this.render(this.markup.path); + this.render(this.markup.path); } } else if ( this.constructor.scheme diff --git a/src/core/BaseModel.ts b/src/core/BaseModel.ts index 22465c7..01c172f 100644 --- a/src/core/BaseModel.ts +++ b/src/core/BaseModel.ts @@ -322,10 +322,10 @@ namespace OS { * * @protected * @param {string} p VFS path to the UI scheme definition - * @returns {void} + * @returns {Promise} * @memberof BaseModel */ - protected render(p: string): void { + protected render(p: string): Promise { return GUI.loadScheme(p, this, this.host); } diff --git a/src/core/core.ts b/src/core/core.ts index ae60acf..c7b862d 100644 --- a/src/core/core.ts +++ b/src/core/core.ts @@ -1234,30 +1234,37 @@ namespace OS { * @returns {Promise} a promise on the result data */ export function post(p: string, d: any): Promise { - return API.Task(function (resolve, reject) { - return $.ajax({ - type: "POST", - url: p, - contentType: "application/json", - data: JSON.stringify( - d, - function (k, v) { - if (k === "domel") { - return undefined; - } - return v; - }, - 4 - ), - dataType: "json", - success: null, - }) - .done(function (data) { - return resolve(data); + return API.Task(async (resolve, reject) => { + try + { + $.ajax({ + type: "POST", + url: p, + contentType: "application/json", + data: JSON.stringify( + d, + function (k, v) { + if (k === "domel") { + return undefined; + } + return v; + }, + 4 + ), + dataType: "json", + success: null, }) - .fail(function (j, s, e) { - return reject(API.throwe(s)); - }); + .done(function (data) { + return resolve(data); + }) + .fail(function (j, s, e) { + reject(e); + }); + } + catch(e) + { + reject(__e(e)); + } }); } @@ -1776,5 +1783,38 @@ namespace OS { }); return o; } + + /** + * A watcher is a Proxy wrapper to an object + * + * It is used to automatically detect changes in the + * target object and notify the change to a callback + * handler + * + * @export + * @param {Object} target object + * @param {(obj: Object, key: string, value: any, path: any[]) => void} callback function + * @returns {Proxy} the wrapper object + */ + export function watcher(target: GenericObject, callback: (obj: Object, key: any, value: any, path:any[]) => void): Object + { + const create_handle_for = (path:any[]) => { + return { + get: (obj: Object, key: any) => { + if(typeof obj[key] === "object" && obj[key] !== null) { + return new Proxy(obj[key], create_handle_for(path.concat(key))); + } + return obj[key]; + }, + set: (obj: Object, prop:any, value: any) => { + obj[prop] = value; + callback(obj, prop, value, path); + return true; + } + } + }; + return new Proxy(target, create_handle_for([])); + } + } } diff --git a/src/core/gui.ts b/src/core/gui.ts index 0ae54c0..c7d6588 100644 --- a/src/core/gui.ts +++ b/src/core/gui.ts @@ -180,23 +180,24 @@ namespace OS { * @param {string} path VFS path to the scheme file * @param {BaseModel} app the target application * @param {(HTMLElement | string)} parent The parent HTML element where the application is rendered. + * @return {Promise} a promise object */ export function loadScheme( path: string, app: BaseModel, parent: HTMLElement | string - ): void { - path.asFileHandle() - .read() - .then(function (x) { - if (!x) { - return; - } + ): Promise { + return new Promise(async (ok,nok) =>{ + try { + const x = await path.asFileHandle().read(); htmlToScheme(x, app, parent); - }) - .catch((e) => { - announcer.oserror(__("Cannot load scheme: {0}", path), e); - }); + ok(true); + } + catch(e) + { + nok(__e(e)); + } + }); } /** @@ -724,6 +725,45 @@ namespace OS { e); return reject(e); } + const mt = application[app].meta; + // load application setting if any + let settings = {}; + try + { + console.log("load setting for", app); + if(mt.path.asFileHandle().protocol === "home") + { + settings = await `${mt.path}/.settings.json`.asFileHandle().read("json"); + } + else + { + // system package + settings = await `home:///.antos/settings/${app}.json`.asFileHandle().read("json"); + } + } + catch(_) + { + } + application[app].setting_wdg = API.watcher(settings, (o,k,v,p) => { + console.log("Changed detected", o, k,v, p); + let key = k; + if(p.length > 0) + { + key = p[0]; + } + const data: API.AnnouncementDataType = {} as API.AnnouncementDataType; + data.icon = undefined; + if (mt && mt.icon) { + data.icon = `${mt.path}/${mt.icon}`; + } + data.id = 0; + data.name = app; + data.message = key; + data.iconclass = mt?mt.iconclass:undefined; + data.u_data = undefined; + console.log(data); + return announcer.trigger("appregistry", data); + }); const p = await PM.createProcess( app, application[app], diff --git a/src/core/pm.ts b/src/core/pm.ts index ded7512..36e0ed4 100644 --- a/src/core/pm.ts +++ b/src/core/pm.ts @@ -136,6 +136,25 @@ namespace OS { if (i >= 0) { if (application[app.name].type === ModelType.Application) { GUI.undock(app as application.BaseApplication); + // save setting file if any + if(PM.processes[app.name].length == 1) + { + const app_class = application[app.name] as typeof OS.application.BaseApplication; + let file = `${app_class.meta.path}/.settings.json`.asFileHandle(); + if(file.protocol !== "home") + { + file = `home:///.antos/settings/${app.name}.json`.asFileHandle(); + } + file.cache = app_class.setting_wdg; + //file.cache = JSON.stringify(app_class.setting_wdg, undefined, 4); + console.log("save setting file"); + file + .write("object") + .catch((e) =>{ + return announcer.osinfo( + __("Unable to save settings for application {0}: {1}", app.name, e.toString())); + }); + } } else { GUI.detachservice(app as application.BaseService); } diff --git a/src/core/settings.ts b/src/core/settings.ts index e23620b..e2ba5d5 100644 --- a/src/core/settings.ts +++ b/src/core/settings.ts @@ -398,7 +398,7 @@ namespace OS { */ export function resetSetting(): void { setting.desktop = { - path: "home://.desktop", + path: "home://.antos/desktop", menu: [], showhidden: false, }; @@ -476,13 +476,13 @@ namespace OS { menu: [], packages: {}, pkgpaths: { - user: "home://.packages", + user: "home://.antos/packages", system: "os://packages", }, repositories: [], startup: { apps: [], - services: ["Syslog/PushNotification", "Syslog/Calendar"], + services: ["SystemServices/PushNotification", "SystemServices/Calendar"], pinned: [], }, }; diff --git a/src/packages/Files/main.ts b/src/packages/Files/main.ts index c549b3d..6c29ddd 100644 --- a/src/packages/Files/main.ts +++ b/src/packages/Files/main.ts @@ -564,13 +564,13 @@ namespace OS { switch (data.dataid) { case `${this.name}-hidden`: //@.view.set "showhidden", e.item.data.checked - return this.registry("showhidden", data.checked); + this.setting.showhidden = data.checked; //@.setting.showhidden = e.item.data.checked case `${this.name}-refresh`: this.view.path = this.currdir.path; return; case `${this.name}-nav`: - return this.registry("nav", data.checked); + this.setting.nav = data.checked; } } //@setting.nav = e.item.data.checked