From 94a0c097a876de4403f4d85d2f4249982f553f95 Mon Sep 17 00:00:00 2001 From: lxsang Date: Sun, 14 Mar 2021 15:05:21 +0100 Subject: [PATCH] Add minor features: - File dialog should remember last opened folder - Add dynamic key-value dialog that work on any object - Window list panel should show window title in tooltip when mouse hovering on application icon - Improvement application list in market place --- Makefile | 2 +- src/core/BaseDialog.ts | 226 +++++++++++++++++++++++--- src/core/settings.ts | 2 +- src/core/tags/AppDockTag.ts | 4 +- src/packages/CodePad/main.ts | 3 + src/packages/CodePad/package.json | 2 +- src/packages/Files/main.ts | 1 + src/packages/Files/package.json | 2 +- src/packages/MarketPlace/main.css | 4 +- src/packages/MarketPlace/main.ts | 107 ++++++------ src/packages/MarketPlace/package.json | 2 +- src/packages/MarketPlace/scheme.html | 2 +- 12 files changed, 267 insertions(+), 90 deletions(-) diff --git a/Makefile b/Makefile index 6e369df..e3fab3f 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ DOCDIR?=/opt/www/htdocs/doc/antos BLUE=\033[1;34m NC=\033[0m -VERSION=1.1.2 +VERSION=1.2.0 GSED=sed UNAME_S := $(shell uname -s) diff --git a/src/core/BaseDialog.ts b/src/core/BaseDialog.ts index 26b5c44..05ffcdc 100644 --- a/src/core/BaseDialog.ts +++ b/src/core/BaseDialog.ts @@ -173,11 +173,11 @@ namespace OS { * Function called when dialog exits * * @protected - * @param {BaseEvent} e + * @param {BaseEvent} _e * @returns {void} * @memberof BaseDialog */ - protected onexit(e: BaseEvent): void { + protected onexit(_e: BaseEvent): void { if (this.parent) { return (this.parent.dialog = undefined); } @@ -199,11 +199,11 @@ namespace OS { * be either the string definition of the scheme or * the VFS file handle of the scheme file * - * @private + * @protected * @type {(string | OS.API.VFS.BaseFileHandle)} * @memberof BasicDialog */ - private markup: string | OS.API.VFS.BaseFileHandle; + protected markup: string | OS.API.VFS.BaseFileHandle; /** * If the `markup` variable is not provided, then @@ -322,12 +322,11 @@ namespace OS { $input.val(this.data.value); } - if (this.data && this.data.type) - { + if (this.data && this.data.type) { ($input[0] as HTMLInputElement).type = this.data.type } - (this.find("btnOk") as tag.ButtonTag).onbtclick = (e) => { + (this.find("btnOk") as tag.ButtonTag).onbtclick = (_e) => { if (this.handle) { this.handle($input.val()); } @@ -335,7 +334,7 @@ namespace OS { }; (this.find("btnCancel") as tag.ButtonTag).onbtclick = ( - e + _e ) => { return this.quit(); }; @@ -409,11 +408,10 @@ namespace OS { if (this.data && this.data.value) { $input.val(this.data.value); } - if(this.data && this.data.disable) - { + if (this.data && this.data.disable) { $input.prop('disabled', true); } - (this.find("btnOk") as tag.ButtonTag).onbtclick = (e) => { + (this.find("btnOk") as tag.ButtonTag).onbtclick = (_e) => { const value = $input.val(); if (!value || value === "") { return; @@ -425,7 +423,7 @@ namespace OS { }; (this.find("btnCancel") as tag.ButtonTag).onbtclick = ( - e + _e ): void => { return this.quit(); }; @@ -493,7 +491,7 @@ namespace OS { main(): void { super.main(); (this.find("btnOk") as tag.ButtonTag).onbtclick = ( - e + _e ): void => { const date = (this.find("cal") as tag.CalendarTag) .selectedDate; @@ -507,7 +505,7 @@ namespace OS { }; (this.find("btnCancel") as tag.ButtonTag).onbtclick = ( - e + _e ): void => { return this.quit(); }; @@ -571,7 +569,7 @@ namespace OS { main(): void { super.main(); (this.find("btnOk") as tag.ButtonTag).onbtclick = ( - e + _e ): void => { const color = (this.find( "cpicker" @@ -586,7 +584,7 @@ namespace OS { }; (this.find("btnCancel") as tag.ButtonTag).onbtclick = ( - e + _e ): void => { return this.quit(); }; @@ -666,7 +664,7 @@ namespace OS { ]; grid.rows = rows; (this.find("btnCancel") as tag.ButtonTag).onbtclick = ( - e + _e ): void => { return this.quit(); }; @@ -736,7 +734,7 @@ namespace OS { (this.find("lbl") as tag.LabelTag).set(this.data); } (this.find("btnYes") as tag.ButtonTag).onbtclick = ( - e + _e ): void => { if (this.handle) { this.handle(true); @@ -744,7 +742,7 @@ namespace OS { return this.quit(); }; (this.find("btnNo") as tag.ButtonTag).onbtclick = ( - e + _e ): void => { if (this.handle) { this.handle(false); @@ -819,7 +817,7 @@ namespace OS { if (this.data && this.data.data) { listview.data = this.data.data; } - const fn = (e: TagEventType) => { + const fn = (_e: TagEventType) => { const data = listview.selectedItem; if (!data) { return this.notify(__("Please select an item")); @@ -833,7 +831,7 @@ namespace OS { (this.find("btnOk") as tag.ButtonTag).onbtclick = fn; (this.find("btnCancel") as tag.ButtonTag).onbtclick = ( - e + _e ): void => { return this.quit(); }; @@ -916,7 +914,7 @@ namespace OS { grid.header = [{ text: "", width: 100 }, { text: "" }]; grid.rows = rows; (this.find("btnCancel") as tag.ButtonTag).onbtclick = ( - e + _e ): void => { return this.quit(); }; @@ -987,12 +985,21 @@ namespace OS { super("FileDialog"); } + /** + * Store the last opened directory + * + * @static + * @type {string} + * @memberof FileDialog + */ + static last_opened: string; /** * * * @returns {void} * @memberof FileDialog */ + main(): void { super.main(); const fileview = this.find("fileview") as tag.FileViewTag; @@ -1003,13 +1010,20 @@ namespace OS { if (!path) { return resolve(undefined); } - return path - .asFileHandle() + let dir = path.asFileHandle(); + return dir .read() .then(function (d) { if (d.error) { return reject(d); } + FileDialog.last_opened = path; + if (!dir.isRoot()) { + const p = dir.parent(); + p.filename = "[..]"; + p.type = "dir"; + d.result.unshift(p); + } return resolve(d.result); }) .catch((e: Error): void => reject(__e(e))); @@ -1033,10 +1047,19 @@ namespace OS { }; location.data = this.systemsetting.VFS.mountpoints.filter( (i) => i.type !== "app" + ).map( + (i) => { + if (FileDialog.last_opened) + i.selected = false; + return i; + } ); - if (location.selectedItem === undefined) { + if (location.selectedItem === undefined && !FileDialog.last_opened) { location.selected = 0; } + else if (FileDialog.last_opened) { + setroot(FileDialog.last_opened); + } } else { $(location).hide(); this.trigger("resize"); @@ -1047,7 +1070,7 @@ namespace OS { return $(filename).val(e.data.filename); } }; - (this.find("bt-ok") as tag.ButtonTag).onbtclick = (e) => { + (this.find("bt-ok") as tag.ButtonTag).onbtclick = (_e) => { const f = fileview.selectedFile; if (!f) { return this.notify( @@ -1096,7 +1119,7 @@ namespace OS { }; (this.find("bt-cancel") as tag.ButtonTag).onbtclick = ( - e + _e ) => { return this.quit(); }; @@ -1112,6 +1135,8 @@ namespace OS { } } } + + FileDialog.last_opened = undefined; /** * Scheme definition */ @@ -1133,6 +1158,153 @@ namespace OS { \ `; + + /** + * Generic & dynamic key-value dialog. The content + * of the dialog consist of an array of label and input elements + * which are generated based on the input model + * + * The input data of the dialog should be: + * + * ```typescript + * { + * title: string, // window title + * model: { + * [propName:string]: string + * }, + * data: { + * [propName:string]: string + * }, + * allow_empty: boolean + * } + * ``` + * Where: + * - keys of `model` are data fields, each key correspond to an input element + * - values of `model` are description texts of fields, each value correspond to a label text + * - data is the input data object in the format of model (optional) + * + * ``` + * Example: + * { + * title: "Greeting", + * model: { + * name: "Your name", + * email: "Your email" + * }, + * allow_empty: false + * } + *``` + + * The data passing from the dialog to the callback function is + * the user input data corresponding to the input model + * + * Example of callback data for the above model: + * + * ``` + * { + * name: "John Doe", + * email: "jd@mail.com" + * } + * ``` + * + * @export + * @class MultiInputDialog + * @extends {BasicDialog} + */ + export class MultiInputDialog extends BasicDialog { + + /** + * References to all the input fields in the + * dialog + * + * @private + * @type {HTMLElement[]} + * @memberof MultiInputDialog + */ + private inputs: JQuery; + + /** + *Creates an instance of MultiInputDialog. + * @memberof MultiInputDialog + */ + constructor() { + super("MultiInputDialog"); + } + + /** + * Generate the scheme before rendering + * + * @memberof MultiInputDialog + */ + init(): void { + let height = 60; + let html = ""; + if (this.data && this.data.model) { + const model = this.data.model; + for (const key in model) { + html += `\ + + +
+ `.format(model[key], key); + height += 60; + } + } + this.markup = MultiInputDialog.scheme.format(height, html); + super.init(); + } + /** + * Main entry point + * + * @memberof MultiInputDialog + */ + main(): void { + super.main(); + this.inputs = $("input", this.scheme); + if (this.data && this.data.data) { + const that = this; + this.inputs.each(function (_i) { + const input = this as HTMLInputElement; + input.value = that.data.data[input.name]; + }); + } + (this.find("btnCancel") as tag.ButtonTag).onbtclick = (_e) => this.quit(); + + (this.find("btnOk") as tag.ButtonTag).onbtclick = (_e) => { + let cdata: GenericObject = {}; + for (const el of this.inputs) { + let input = el as HTMLInputElement; + if (!this.data.allow_empty && input.value.trim() == "") { + return this.notify(__("All fields should be filled")); + } + cdata[input.name] = input.value.trim(); + } + if (this.handle) + this.handle(cdata); + this.quit(); + } + } + } + /** + * Scheme definition + */ + MultiInputDialog.scheme = `\ + + +
+ +
+ {1} + +
+ + + +
+ +
+ +`; } } } diff --git a/src/core/settings.ts b/src/core/settings.ts index d87819a..6357825 100644 --- a/src/core/settings.ts +++ b/src/core/settings.ts @@ -423,7 +423,7 @@ namespace OS { setting.VFS = { mountpoints: [ - //TODO: multi app try to write to this object, it neet to be cloned + //TODO: multi app try to write to this object, it need to be cloned { text: "__(Applications)", path: "app://", diff --git a/src/core/tags/AppDockTag.ts b/src/core/tags/AppDockTag.ts index 912d725..b7de706 100644 --- a/src/core/tags/AppDockTag.ts +++ b/src/core/tags/AppDockTag.ts @@ -171,7 +171,9 @@ namespace OS { el[0].uify(this.observable); bt.set(item); bt.data = item.app; - el.attr("tooltip", `cr:${item.app.title()}`); + $(el).on("mouseover", (e) =>{ + el.attr("tooltip", `cr:${item.app.title()}`); + }); item.domel = bt; bt.onbtclick = (e) => { e.id = this.aid; diff --git a/src/packages/CodePad/main.ts b/src/packages/CodePad/main.ts index 7d1248f..785ed44 100644 --- a/src/packages/CodePad/main.ts +++ b/src/packages/CodePad/main.ts @@ -431,6 +431,9 @@ namespace OS { if (stat.langmode) this.langstat.text = stat.langmode.text; this.filestat.text = stat.file + let win = this.scheme as GUI.tag.WindowTag; + if(win.apptitle != stat.file) + win.apptitle = stat.file; } /** diff --git a/src/packages/CodePad/package.json b/src/packages/CodePad/package.json index a915f63..08dda0f 100644 --- a/src/packages/CodePad/package.json +++ b/src/packages/CodePad/package.json @@ -7,7 +7,7 @@ "email": "xsang.le@gmail.com", "licences": "GPLv3" }, - "version":"0.1.1-b", + "version":"0.1.2-b", "category":"Developments", "iconclass":"fa fa-pencil-square-o", "mimes":[ diff --git a/src/packages/Files/main.ts b/src/packages/Files/main.ts index 1e6d6af..66fcb8e 100644 --- a/src/packages/Files/main.ts +++ b/src/packages/Files/main.ts @@ -152,6 +152,7 @@ namespace OS { } this.currdir = dir; $(this.navinput).val(dir.path); + (this.scheme as GUI.tag.WindowTag).apptitle = dir.path; return resolve(d.result); }) .catch((e) => reject(__e(e))); diff --git a/src/packages/Files/package.json b/src/packages/Files/package.json index 453014b..47815e0 100644 --- a/src/packages/Files/package.json +++ b/src/packages/Files/package.json @@ -6,7 +6,7 @@ "author": "Xuan Sang LE", "email": "xsang.le@gmail.com" }, - "version":"0.0.3-a", + "version":"0.1.1-a", "category":"System", "iconclass":"fa fa-hdd-o", "mimes":["dir"], diff --git a/src/packages/MarketPlace/main.css b/src/packages/MarketPlace/main.css index 928df79..5ac6358 100644 --- a/src/packages/MarketPlace/main.css +++ b/src/packages/MarketPlace/main.css @@ -3,8 +3,9 @@ afx-app-window[data-id="marketplace-win"] afx-list-view[data-id='repo'] div.list z-index: 10; } -afx-app-window[data-id="marketplace-win"] afx-vbox[data-id='container'] { +afx-app-window[data-id="marketplace-win"] afx-vbox[data-id='container'] div[data-id="desc-container"]{ overflow-y: auto; + overflow-x: none; } afx-app-window[data-id="marketplace-win"] afx-vbox[data-id='container'] afx-hbox { padding-left: 10px; @@ -30,6 +31,7 @@ afx-app-window[data-id="marketplace-win"] p[data-id='app-desc'] { afx-app-window[data-id="marketplace-win"] p[data-id='app-desc'] img{ display: block; margin-bottom: 10px; + max-width: 100%; } afx-app-window[data-id="marketplace-win"] ul[data-id='app-detail'] { padding:0; diff --git a/src/packages/MarketPlace/main.ts b/src/packages/MarketPlace/main.ts index afc2f6c..6a30b26 100644 --- a/src/packages/MarketPlace/main.ts +++ b/src/packages/MarketPlace/main.ts @@ -56,7 +56,7 @@ namespace OS { this.btexec = this.find("bt-exec") as GUI.tag.ButtonTag; this.searchbox = this.find("searchbox") as HTMLInputElement; $(this.container).css("visibility", "hidden"); - this.btexec.onbtclick = (e) => { + this.btexec.onbtclick = (_e) => { const el = this.applist.selectedItem; if (!el) { return; @@ -99,7 +99,7 @@ namespace OS { $(this.searchbox).keyup((e) => this.search(e)); - this.fetchApps().then((d) => { + this.fetchApps().then((_d) => { //console.log(d); }); } @@ -191,7 +191,7 @@ namespace OS { * @memberof MarketPlace */ private loadRemoteRepositories(list: string[]): Promise[]> { - return new Promise((resolve, reject) => { + return new Promise((resolve, _reject) => { if (list.length == 0) { let app_list = []; for (let k in this.apps_meta) { @@ -233,7 +233,7 @@ namespace OS { } fetchApps(): Promise> { - return new Promise((resolve, reject) => { + return new Promise((resolve, _reject) => { let v: API.PackageMetaType; this.apps_meta = {}; const pkgcache = this.systemsetting.system.packages; @@ -260,7 +260,16 @@ namespace OS { } this.loadRemoteRepositories(list) .then((apps_list) => { - this.applist.data = apps_list; + this.applist.data = apps_list.sort( + function (a: GenericObject, b: GenericObject): number { + if (a.text > b.text) { + return 1; + } + if (b.text > a.text) { + return -1; + } + return 0; + }); resolve(this.apps_meta); }); }); @@ -275,13 +284,13 @@ namespace OS { d.description .asFileHandle() .read() - .then((text) => { + .then((text: string) => { const converter = new showdown.Converter(); return $(this.appdesc).html( converter.makeHtml(text) ); }) - .catch((e) => { + .catch((_e) => { this.notify( __("Unable to read package description") ); @@ -404,8 +413,7 @@ namespace OS { if (installed_pkgs[arr[0]]) { let name = `${arr[0]}@${installed_pkgs[arr[0]].version}`; dep_list.uninstall.add(name); - if(list[k] != pkgname) - { + if (list[k] != pkgname) { let subdep = this.checkDependencies(name, true); dep_list.uninstall = new Set([...dep_list.uninstall, ...subdep.uninstall]); } @@ -432,8 +440,7 @@ namespace OS { if (this.apps_meta[list[k]]) { // new package should be installed dep_list.install.add(list[k]); - if(list[k] != pkgname) - { + if (list[k] != pkgname) { let subdep = this.checkDependencies(list[k], false); dep_list.uninstall = new Set([...dep_list.uninstall, ...subdep.uninstall]); dep_list.notfound = new Set([...dep_list.notfound, ...subdep.notfound]); @@ -449,12 +456,10 @@ namespace OS { } return dep_list; } - private installPkg(pkgname:string): Promise - { - return new Promise(async (resolve, reject) =>{ + private installPkg(pkgname: string): Promise { + return new Promise(async (resolve, reject) => { const meta = this.apps_meta[pkgname]; - if(!meta || !meta.download) - { + if (!meta || !meta.download) { return reject(this._api.throwe(__("Unable to find package: {0}", pkgname))); } try { @@ -472,25 +477,23 @@ namespace OS { } }); } - private bulkInstall(list:string[]): Promise - { - return new Promise((resolve, reject)=>{ - if(list.length == 0) - { + private bulkInstall(list: string[]): Promise { + return new Promise((resolve, reject) => { + if (list.length == 0) { return resolve(true); } - const pkgname = list.splice(0,1)[0]; + const pkgname = list.splice(0, 1)[0]; this.installPkg(pkgname) - .then((meta) =>{ + .then((_meta) => { this.bulkInstall(list) - .then((b) =>{ + .then((b) => { resolve(b); }) - .catch((e) =>{ + .catch((e) => { reject(e); }) }) - .catch((err) =>{ + .catch((err) => { reject(err); }) }); @@ -518,7 +521,7 @@ namespace OS { [...dep.notfound].join("\n") ) }) - .then((v) => { + .then((_v) => { reject(__("Unresolved dependencies on: {0}", pkgname)) }); } @@ -532,18 +535,18 @@ namespace OS { dep.install.size.toString(), [...dep.install].join("\n") ) - }).then((v) => { + }).then((_v) => { this.bulkUninstall([...dep.uninstall]) - .then((b)=>{ + .then((_b) => { this.bulkInstall([...dep.install]) - .then((b1)=>{ + .then((_b1) => { resolve(pkgname); }) - .catch((e1) =>{ + .catch((e1) => { reject(e1); }) }) - .catch((e2) =>{ + .catch((e2) => { reject(e2); }) }) @@ -655,40 +658,35 @@ namespace OS { .catch((e: Error) => reject(__e(e))); }); } - private bulkUninstall(list: string[]): Promise - { + private bulkUninstall(list: string[]): Promise { return new Promise(async (resolve, reject) => { - if(list.length == 0) - { + if (list.length == 0) { return resolve(true); } - const pkgname = list.splice(0,1)[0]; + const pkgname = list.splice(0, 1)[0]; this.uninstallPkg(pkgname) - .then((meta) =>{ + .then((_meta) => { this.bulkUninstall(list) - .then((b)=>{ + .then((b) => { resolve(b); }) - .catch((e)=>{ + .catch((e) => { reject(e); }) }) - .catch((err) =>{ + .catch((err) => { reject(err); }) }); } - private uninstallPkg(pkgname: string): Promise - { + private uninstallPkg(pkgname: string): Promise { return new Promise(async (resolve, reject) => { const meta = this.apps_meta[pkgname]; - if(!meta) - { + if (!meta) { return reject(this._api.throwe(__("Unable to find application meta-data: {0}", pkgname))); } const app = this.systemsetting.system.packages[meta.pkgname]; - if(!app) - { + if (!app) { return reject(this._api.throwe(__("Application {0} is not installed", pkgname))); } // got the app meta @@ -712,7 +710,7 @@ namespace OS { this.appDetail(meta); } else { - if(meta.domel) + if (meta.domel) this.applist.delete(meta.domel); $(this.container).css("visibility", "hidden"); } @@ -724,7 +722,7 @@ namespace OS { }); } private uninstall(): Promise { - return new Promise(async (resolve, reject) => { + return new Promise(async (_resolve, reject) => { const el = this.applist.selectedItem; if (!el) { return; @@ -750,10 +748,10 @@ namespace OS { return; } this.bulkUninstall([...dep.uninstall]) - .then((b)=>{ + .then((_b) => { this.notify(__("Uninstall successfully")); }) - .catch((err)=>{ + .catch((err) => { this.error(__("Unable to uninstall package(s): {0}", err.toString()), err); }); } @@ -782,9 +780,8 @@ namespace OS { const meta = this.apps_meta[`${sel.pkgname}@${app.version}`]; await this.remoteInstall(); try { - if(meta) - { - if(meta.domel) + if (meta) { + if (meta.domel) this.applist.delete(meta.domel); } return resolve(true); @@ -792,7 +789,7 @@ namespace OS { catch (e) { return reject(__e(e)); } - + } catch (e_1) { return reject(__e(e_1)); diff --git a/src/packages/MarketPlace/package.json b/src/packages/MarketPlace/package.json index de8f4dd..ebfdd76 100644 --- a/src/packages/MarketPlace/package.json +++ b/src/packages/MarketPlace/package.json @@ -6,7 +6,7 @@ "author": "Xuan Sang LE", "email": "xsang.le@gmail.com" }, - "version":"0.2.1-a", + "version":"0.2.2-a", "category":"System", "iconclass":"fa fa-adn", "mimes":["none"], diff --git a/src/packages/MarketPlace/scheme.html b/src/packages/MarketPlace/scheme.html index 1653615..6315baa 100644 --- a/src/packages/MarketPlace/scheme.html +++ b/src/packages/MarketPlace/scheme.html @@ -18,7 +18,7 @@

-
+