feat: use a separated setting file for each application instead of a single system setting files
All checks were successful
gitea-sync/antos/pipeline/head This commit looks good

This commit is contained in:
DanyLE 2023-06-20 17:16:13 +02:00
parent 9fa766963a
commit 46e7e6d94f
9 changed files with 236 additions and 121 deletions

53
d.ts/antos.d.ts vendored
View File

@ -1037,8 +1037,9 @@ declare namespace OS {
* @param {string} path VFS path to the scheme file * @param {string} path VFS path to the scheme file
* @param {BaseModel} app the target application * @param {BaseModel} app the target application
* @param {(HTMLElement | string)} parent The parent HTML element where the application is rendered. * @param {(HTMLElement | string)} parent The parent HTML element where the application is rendered.
* @return {Promise<any>} a promise object
*/ */
function loadScheme(path: string, app: BaseModel, parent: HTMLElement | string): void; function loadScheme(path: string, app: BaseModel, parent: HTMLElement | string): Promise<any>;
/** /**
* Clear the current system theme * Clear the current system theme
* *
@ -2289,6 +2290,19 @@ declare namespace OS {
* @returns {*} * @returns {*}
*/ */
function switcher(...args: string[]): any; 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<any>, callback: (obj: Object, key: any, value: any, path: any[]) => void): Object;
} }
} }
/// <reference types="jquery" /> /// <reference types="jquery" />
@ -2311,9 +2325,19 @@ declare namespace OS {
*/ */
abstract class BaseApplication extends BaseModel { abstract class BaseApplication extends BaseModel {
/** /**
* Placeholder of all settings specific to the application. * Watcher of all settings specific to the application.
* The settings stored in this object will be saved to system * The settings stored in this object will be saved to application folder
* setting when logout and can be reused in the next login session * 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<any>}
* @memberof BaseApplication
*/
static setting_wdg: GenericObject<any>;
/**
* Reference to per application setting i.e. setting_wdg
* *
* @type {GenericObject<any>} * @type {GenericObject<any>}
* @memberof BaseApplication * @memberof BaseApplication
@ -2352,16 +2376,16 @@ declare namespace OS {
* @returns {void} * @returns {void}
* @memberof BaseApplication * @memberof BaseApplication
*/ */
init(): void; init(): Promise<any>;
/** /**
* Render the application UI by first loading its scheme * Render the application UI by first loading its scheme
* and then mount this scheme to the DOM tree * and then mount this scheme to the DOM tree
* *
* @protected * @protected
* @returns {void} * @returns {Promise<any>}
* @memberof BaseApplication * @memberof BaseApplication
*/ */
protected loadScheme(): void; protected loadScheme(): Promise<any>;
/** /**
* API function to perform an heavy task. * API function to perform an heavy task.
* This function will create a Task that is tracked by any * This function will create a Task that is tracked by any
@ -2420,17 +2444,6 @@ declare namespace OS {
* @memberof BaseApplication * @memberof BaseApplication
*/ */
protected applyAllSetting(): void; 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 * Show the appliation
* *
@ -2803,10 +2816,10 @@ declare namespace OS {
* *
* @protected * @protected
* @param {string} p VFS path to the UI scheme definition * @param {string} p VFS path to the UI scheme definition
* @returns {void} * @returns {Promise<any>}
* @memberof BaseModel * @memberof BaseModel
*/ */
protected render(p: string): void; protected render(p: string): Promise<any>;
/** /**
* Exit the model * Exit the model
* *

View File

@ -35,9 +35,21 @@ namespace OS {
*/ */
export abstract class BaseApplication extends BaseModel { export abstract class BaseApplication extends BaseModel {
/** /**
* Placeholder of all settings specific to the application. * Watcher of all settings specific to the application.
* The settings stored in this object will be saved to system * The settings stored in this object will be saved to application folder
* setting when logout and can be reused in the next login session * 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<any>}
* @memberof BaseApplication
*/
static setting_wdg: GenericObject<any>;
/**
* Reference to per application setting i.e. setting_wdg
* *
* @type {GenericObject<any>} * @type {GenericObject<any>}
* @memberof BaseApplication * @memberof BaseApplication
@ -69,10 +81,7 @@ namespace OS {
*/ */
constructor(name: string, args: AppArgumentsType[]) { constructor(name: string, args: AppArgumentsType[]) {
super(name, args); super(name, args);
if (!setting.applications[this.name]) { this.setting = (this.constructor as any).setting_wdg;
setting.applications[this.name] = {};
}
this.setting = setting.applications[this.name];
this.keycomb = {}; this.keycomb = {};
} }
@ -87,43 +96,52 @@ namespace OS {
* @returns {void} * @returns {void}
* @memberof BaseApplication * @memberof BaseApplication
*/ */
init(): void { init(): Promise<any> {
this.off("*"); return new Promise(async (ok, nok) =>{
this.on("exit", () => this.quit(false)); try {
// first register some base event to the app this.off("*");
this.on("focus", () => { this.on("exit", () => this.quit(false));
//if(this.sysdock.selectedApp != this) // first register some base event to the app
this.sysdock.selectedApp = this; this.on("focus", () => {
(this.scheme as GUI.tag.WindowTag).onmenuopen = (el) => el.nodes = this.baseMenu() || []; //if(this.sysdock.selectedApp != this)
OS.PM.pidactive = this.pid; this.sysdock.selectedApp = this;
this.trigger("focused", undefined); (this.scheme as GUI.tag.WindowTag).onmenuopen = (el) => el.nodes = this.baseMenu() || [];
if (this.dialog) { OS.PM.pidactive = this.pid;
return this.dialog.show(); 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 * and then mount this scheme to the DOM tree
* *
* @protected * @protected
* @returns {void} * @returns {Promise<any>}
* @memberof BaseApplication * @memberof BaseApplication
*/ */
protected loadScheme(): void { protected loadScheme(): Promise<any> {
//now load the scheme //now load the scheme
const path = `${this.meta().path}/scheme.html`; const path = `${this.meta().path}/scheme.html`;
return this.render(path); 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 * Show the appliation
* *

View File

@ -288,7 +288,7 @@ namespace OS {
return GUI.htmlToScheme(this.markup, this, this.host); return GUI.htmlToScheme(this.markup, this, this.host);
} else { } else {
// a file handle // a file handle
return this.render(this.markup.path); this.render(this.markup.path);
} }
} else if ( } else if (
this.constructor.scheme this.constructor.scheme

View File

@ -322,10 +322,10 @@ namespace OS {
* *
* @protected * @protected
* @param {string} p VFS path to the UI scheme definition * @param {string} p VFS path to the UI scheme definition
* @returns {void} * @returns {Promise<any>}
* @memberof BaseModel * @memberof BaseModel
*/ */
protected render(p: string): void { protected render(p: string): Promise<any> {
return GUI.loadScheme(p, this, this.host); return GUI.loadScheme(p, this, this.host);
} }

View File

@ -1234,30 +1234,37 @@ namespace OS {
* @returns {Promise<any>} a promise on the result data * @returns {Promise<any>} a promise on the result data
*/ */
export function post(p: string, d: any): Promise<any> { export function post(p: string, d: any): Promise<any> {
return API.Task(function (resolve, reject) { return API.Task(async (resolve, reject) => {
return $.ajax({ try
type: "POST", {
url: p, $.ajax({
contentType: "application/json", type: "POST",
data: JSON.stringify( url: p,
d, contentType: "application/json",
function (k, v) { data: JSON.stringify(
if (k === "domel") { d,
return undefined; function (k, v) {
} if (k === "domel") {
return v; return undefined;
}, }
4 return v;
), },
dataType: "json", 4
success: null, ),
}) dataType: "json",
.done(function (data) { success: null,
return resolve(data);
}) })
.fail(function (j, s, e) { .done(function (data) {
return reject(API.throwe(s)); return resolve(data);
}); })
.fail(function (j, s, e) {
reject(e);
});
}
catch(e)
{
reject(__e(e));
}
}); });
} }
@ -1776,5 +1783,38 @@ namespace OS {
}); });
return o; 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<any>, 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([]));
}
} }
} }

View File

@ -180,23 +180,24 @@ namespace OS {
* @param {string} path VFS path to the scheme file * @param {string} path VFS path to the scheme file
* @param {BaseModel} app the target application * @param {BaseModel} app the target application
* @param {(HTMLElement | string)} parent The parent HTML element where the application is rendered. * @param {(HTMLElement | string)} parent The parent HTML element where the application is rendered.
* @return {Promise<any>} a promise object
*/ */
export function loadScheme( export function loadScheme(
path: string, path: string,
app: BaseModel, app: BaseModel,
parent: HTMLElement | string parent: HTMLElement | string
): void { ): Promise<any> {
path.asFileHandle() return new Promise(async (ok,nok) =>{
.read() try {
.then(function (x) { const x = await path.asFileHandle().read();
if (!x) {
return;
}
htmlToScheme(x, app, parent); htmlToScheme(x, app, parent);
}) ok(true);
.catch((e) => { }
announcer.oserror(__("Cannot load scheme: {0}", path), e); catch(e)
}); {
nok(__e(e));
}
});
} }
/** /**
@ -724,6 +725,45 @@ namespace OS {
e); e);
return reject(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<any> = {} as API.AnnouncementDataType<any>;
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( const p = await PM.createProcess(
app, app,
application[app], application[app],

View File

@ -136,6 +136,25 @@ namespace OS {
if (i >= 0) { if (i >= 0) {
if (application[app.name].type === ModelType.Application) { if (application[app.name].type === ModelType.Application) {
GUI.undock(app as application.BaseApplication); 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 { } else {
GUI.detachservice(app as application.BaseService); GUI.detachservice(app as application.BaseService);
} }

View File

@ -398,7 +398,7 @@ namespace OS {
*/ */
export function resetSetting(): void { export function resetSetting(): void {
setting.desktop = { setting.desktop = {
path: "home://.desktop", path: "home://.antos/desktop",
menu: [], menu: [],
showhidden: false, showhidden: false,
}; };
@ -476,13 +476,13 @@ namespace OS {
menu: [], menu: [],
packages: {}, packages: {},
pkgpaths: { pkgpaths: {
user: "home://.packages", user: "home://.antos/packages",
system: "os://packages", system: "os://packages",
}, },
repositories: [], repositories: [],
startup: { startup: {
apps: [], apps: [],
services: ["Syslog/PushNotification", "Syslog/Calendar"], services: ["SystemServices/PushNotification", "SystemServices/Calendar"],
pinned: [], pinned: [],
}, },
}; };

View File

@ -564,13 +564,13 @@ namespace OS {
switch (data.dataid) { switch (data.dataid) {
case `${this.name}-hidden`: case `${this.name}-hidden`:
//@.view.set "showhidden", e.item.data.checked //@.view.set "showhidden", e.item.data.checked
return this.registry("showhidden", data.checked); this.setting.showhidden = data.checked;
//@.setting.showhidden = e.item.data.checked //@.setting.showhidden = e.item.data.checked
case `${this.name}-refresh`: case `${this.name}-refresh`:
this.view.path = this.currdir.path; this.view.path = this.currdir.path;
return; return;
case `${this.name}-nav`: case `${this.name}-nav`:
return this.registry("nav", data.checked); this.setting.nav = data.checked;
} }
} }
//@setting.nav = e.item.data.checked //@setting.nav = e.item.data.checked