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 {BaseModel} app the target application
* @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
*
@ -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<any>, callback: (obj: Object, key: any, value: any, path: any[]) => void): Object;
}
}
/// <reference types="jquery" />
@ -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<any>}
* @memberof BaseApplication
*/
static setting_wdg: GenericObject<any>;
/**
* Reference to per application setting i.e. setting_wdg
*
* @type {GenericObject<any>}
* @memberof BaseApplication
@ -2352,16 +2376,16 @@ declare namespace OS {
* @returns {void}
* @memberof BaseApplication
*/
init(): void;
init(): Promise<any>;
/**
* Render the application UI by first loading its scheme
* and then mount this scheme to the DOM tree
*
* @protected
* @returns {void}
* @returns {Promise<any>}
* @memberof BaseApplication
*/
protected loadScheme(): void;
protected loadScheme(): Promise<any>;
/**
* 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<any>}
* @memberof BaseModel
*/
protected render(p: string): void;
protected render(p: string): Promise<any>;
/**
* Exit the model
*

View File

@ -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<any>}
* @memberof BaseApplication
*/
static setting_wdg: GenericObject<any>;
/**
* Reference to per application setting i.e. setting_wdg
*
* @type {GenericObject<any>}
* @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<any> {
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<any>}
* @memberof BaseApplication
*/
protected loadScheme(): void {
protected loadScheme(): Promise<any> {
//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
*

View File

@ -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

View File

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

View File

@ -1234,30 +1234,37 @@ namespace OS {
* @returns {Promise<any>} a promise on the result data
*/
export function post(p: string, d: any): Promise<any> {
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<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 {BaseModel} app the target application
* @param {(HTMLElement | string)} parent The parent HTML element where the application is rendered.
* @return {Promise<any>} a promise object
*/
export function loadScheme(
path: string,
app: BaseModel,
parent: HTMLElement | string
): void {
path.asFileHandle()
.read()
.then(function (x) {
if (!x) {
return;
}
): Promise<any> {
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<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(
app,
application[app],

View File

@ -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);
}

View File

@ -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: [],
},
};

View File

@ -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