From de5878c34989a36f0b03ed2addda7e63b920a44d Mon Sep 17 00:00:00 2001 From: lxsang Date: Sun, 14 Mar 2021 21:12:27 +0100 Subject: [PATCH] Add features: - Improvement application list in market place - Allow triplet keyboard shortcut in GUI - CodePad allows setting shortcut in CommandPalette commands - CodePad should have recent menu entry that remember top n file opened - Improve File application grid view - Label text should be selectable --- src/core/BaseApplication.ts | 34 +++++---- src/core/gui.ts | 101 +++++++++++---------------- src/core/tags/AppDockTag.ts | 14 +++- src/packages/CodePad/extensions.json | 12 ++-- src/packages/CodePad/main.ts | 88 ++++++++++++++++++----- src/packages/Files/main.css | 5 ++ src/packages/MarketPlace/main.ts | 3 + src/themes/system/afx-label.css | 1 + 8 files changed, 166 insertions(+), 92 deletions(-) diff --git a/src/core/BaseApplication.ts b/src/core/BaseApplication.ts index 79de271..f810594 100644 --- a/src/core/BaseApplication.ts +++ b/src/core/BaseApplication.ts @@ -82,12 +82,7 @@ namespace OS { setting.applications[this.name] = {}; } this.setting = setting.applications[this.name]; - this.keycomb = { - ALT: {}, - CTRL: {}, - SHIFT: {}, - META: {}, - }; + this.keycomb = {}; this.subscribe("appregistry", (m) => { if (m.name === this.name) { this.applySetting(m.data.m); @@ -199,14 +194,29 @@ namespace OS { k: string, f: (e: JQuery.KeyboardEventBase) => void ): void { - const arr = k.split("-"); - if (arr.length !== 2) { + 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 == "") { return; } - const fnk = arr[0].toUpperCase(); - const c = arr[1].toUpperCase(); + fnk = `fn_${fnk.hash()}`; + if (!this.keycomb[fnk]) { - return; + this.keycomb[fnk] = {}; } this.keycomb[fnk][c] = f; } @@ -246,7 +256,7 @@ namespace OS { * @returns {boolean} return whether the shortcut is executed * @memberof BaseApplication */ - shortcut(fnk: string, c: string, e: JQuery.KeyDownEvent): boolean { + shortcut(fnk: string, c: string, e: JQuery.KeyUpEvent): boolean { if (!this.keycomb[fnk]) { return true; } diff --git a/src/core/gui.ts b/src/core/gui.ts index d45ae9e..9c0e106 100644 --- a/src/core/gui.ts +++ b/src/core/gui.ts @@ -34,47 +34,16 @@ namespace OS { */ export interface ShortcutType { /** - * Placeholder for all shortcut callbacks attached to `ALT` key, eg. + * Placeholder for all shortcut callbacks, example: * ```typescript - * ALT.c = function() {..} + * fn_193462204.c = function() {..} * // this function will be called when the hotkey `ALT-C` is triggered + * // fn_${"ALT".hash()} is fn_193462204 * ``` * * @memberof ShortcutType */ - ALT: GenericObject<(e: JQuery.MouseDownEvent) => void>; - - /** - * Placeholder for all shortcut callbacks attached to `CTRL` key, eg. - * ```typescript - * CTRL.c = function() {..} - * // this function will be called when the hotkey `CTRL-C` is triggered - * ``` - * - * @memberof ShortcutType - */ - CTRL: GenericObject<(e: JQuery.MouseDownEvent) => void>; - - /** - * Placeholder for all shortcut callbacks attached to `SHIFT` key, eg. - * ```typescript - * SHIFT.c = function() {..} - * // this function will be called when the hotkey `SHIFT-C` is triggered - * ``` - * - * @memberof ShortcutType - */ - SHIFT: GenericObject<(e: JQuery.MouseDownEvent) => void>; - /** - * Placeholder for all shortcut callbacks attached to `META` key, eg. - * ```typescript - * META[" "] = function() {..} - * // this function will be called when the hotkey `META-[space]` is triggered - * ``` - * - * @memberof ShortcutType - */ - META: GenericObject<(e: JQuery.MouseDownEvent) => void>; + [propName: string]: GenericObject<(e: JQuery.KeyUpEvent) => void>; } /** @@ -129,12 +98,7 @@ namespace OS { /** * Placeholder for system shortcuts */ - var shortcut: ShortcutType = { - ALT: {}, - CTRL: {}, - SHIFT: {}, - META: {}, - }; + var shortcut: ShortcutType = {}; /** * Convert an application html scheme to @@ -764,17 +728,32 @@ namespace OS { */ export function bindKey( k: string, - f: (e: JQuery.MouseDownEvent) => void, + f: (e: JQuery.KeyUpEvent) => void, force: boolean = true ): void { - const arr = k.split("-"); - if (arr.length !== 2) { + 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 == "") { return; } - const fnk = arr[0].toUpperCase(); - const c = arr[1].toUpperCase(); + fnk = `fn_${fnk.hash()}`; + if (!shortcut[fnk]) { - return; + shortcut[fnk] = {}; } if (shortcut[fnk][c] && !force) return; shortcut[fnk][c] = f; @@ -907,7 +886,7 @@ namespace OS { $("#wrapper").append(scheme); announcer.observable.one("sysdockloaded", () => { - $(window).bind("keydown", function (event) { + $(window).on("keyup", function (event) { const dock = $("#sysdock")[0] as tag.AppDockTag; if (!dock) { return; @@ -915,20 +894,24 @@ namespace OS { const app = dock.selectedApp; //return true unless app const c = String.fromCharCode(event.which).toUpperCase(); - let fnk = undefined; - if (event.ctrlKey) { - fnk = "CTRL"; - } else if (event.metaKey) { - fnk = "META"; - } else if (event.shiftKey) { - fnk = "SHIFT"; - } else if (event.altKey) { - fnk = "ALT"; + let fnk = ""; + if (event.metaKey) { + fnk += "META"; } - - if (!fnk) { + if (event.ctrlKey) { + fnk += "CTRL"; + } + if (event.altKey) { + fnk += "ALT"; + } + if (event.shiftKey) { + fnk += "SHIFT"; + } + + if ( fnk == "") { return; } + fnk = `fn_${fnk.hash()}`; const r = app ? app.shortcut(fnk, c, event) : true; if (!r) { return event.preventDefault(); diff --git a/src/core/tags/AppDockTag.ts b/src/core/tags/AppDockTag.ts index b7de706..a71a365 100644 --- a/src/core/tags/AppDockTag.ts +++ b/src/core/tags/AppDockTag.ts @@ -224,8 +224,9 @@ namespace OS { const bt = ($(e.target).closest( "afx-button" )[0] as any) as ButtonTag; - const app = bt.data; + const app = bt.data as application.BaseApplication; m.items = [ + { text: "__(New window)", dataid: "new" }, { text: "__(Show)", dataid: "show" }, { text: "__(Hide)", dataid: "hide" }, { text: "__(Close)", dataid: "quit" }, @@ -235,6 +236,17 @@ namespace OS { if (app[item.dataid]) { return app[item.dataid](); } + else + { + switch (item.dataid) { + case "new": + GUI.launch(app.name, []); + break; + + default: + break; + } + } }; return m.show(e); }; diff --git a/src/packages/CodePad/extensions.json b/src/packages/CodePad/extensions.json index 31ef6b1..ad960b1 100644 --- a/src/packages/CodePad/extensions.json +++ b/src/packages/CodePad/extensions.json @@ -6,19 +6,23 @@ "actions" : [ { "text": "__(New Project)", - "name": "create" + "name": "create", + "shortcut": "CTRL-ALT-N" }, { "text": "__(New project from current folder)", - "name": "init" + "name": "init", + "shortcut": "CTRL-ALT-C" }, { "text": "__(Build and Run)", - "name": "buildnrun" + "name": "buildnrun", + "shortcut": "CTRL-ALT-B" }, { "text": "__(Build release)", - "name": "release" + "name": "release", + "shortcut": "CTRL-ALT-R" } ] }, diff --git a/src/packages/CodePad/main.ts b/src/packages/CodePad/main.ts index 785ed44..4127964 100644 --- a/src/packages/CodePad/main.ts +++ b/src/packages/CodePad/main.ts @@ -285,6 +285,7 @@ namespace OS { }); let file = "Untitled".asFileHandle() as CodePadFileHandle; if (this.args && this.args.length > 0) { + this.addRecent(this.args[0].path); if (this.args[0].type === "dir") { this.currdir = this.args[0].path.asFileHandle() as CodePadFileHandle; } else { @@ -311,6 +312,7 @@ namespace OS { if (e.data.type === "dir") { return; } + this.addRecent(e.data.path); return this.eum.active.openFile( e.data.path.asFileHandle() as CodePadFileHandle ); @@ -359,7 +361,7 @@ namespace OS { this.bindKey("ALT-N", () => this.menuAction("new")); this.bindKey("ALT-O", () => this.menuAction("open")); - this.bindKey("ALT-F", () => this.menuAction("opendir")); + this.bindKey("CTRL-ALT-F", () => this.menuAction("opendir")); this.bindKey("CTRL-S", () => this.menuAction("save")); this.bindKey("ALT-W", () => this.menuAction("saveas")); @@ -432,7 +434,7 @@ namespace OS { this.langstat.text = stat.langmode.text; this.filestat.text = stat.file let win = this.scheme as GUI.tag.WindowTag; - if(win.apptitle != stat.file) + if (win.apptitle != stat.file) win.apptitle = stat.file; } @@ -595,7 +597,7 @@ namespace OS { * @return {*} {Promise} * @memberof CodePad */ - private loadExtensionMetaFromFile(path: string| API.VFS.BaseFileHandle): Promise { + private loadExtensionMetaFromFile(path: string | API.VFS.BaseFileHandle): Promise { return new Promise((resolve, reject) => { path .asFileHandle() @@ -613,7 +615,13 @@ namespace OS { ); this.extensions[ext.name].name = ext.name; for (let v of Array.from(ext.actions)) { + const action = v as any; this.extensions[ext.name].addAction(v); + if (action.shortcut) { + this.bindKey(action.shortcut, (e) => { + return this.loadAndRunExtensionAction(action); + }) + } } this.spotlight.addAction( this.extensions[ext.name] @@ -649,9 +657,9 @@ namespace OS { .then(() => { // try to load local extension this.loadExtensionMetaFromFile("home://.codepad/extensions.json") - .catch((e)=>{ - // ignore any error - }); + .catch((e) => { + // ignore any error + }); }) .catch((e) => { return this.error( @@ -711,7 +719,7 @@ namespace OS { * * @type {{ name: any, ext: any, rootpath?:string }} */ - parent: { name: any, ext: any, rootpath?:string }; + parent: { name: any, ext: any, rootpath?: string }; /** * Action name @@ -726,7 +734,7 @@ namespace OS { if (!CodePad.extensions[name]) { //load the extension let path = `${this.meta().path}/${name}.js`; - if(data.parent.rootpath) + if (data.parent.rootpath) path = `${data.parent.rootpath}/${name}.js`; this._api .requires(path, true) @@ -750,15 +758,41 @@ namespace OS { * @memberof CodePad */ private fileMenu(): GUI.BasicItemType { + const recent = this.setting.recent.map((i: string) => { + return { text: i }; + }); return { text: __("File"), nodes: [ { text: __("New"), dataid: "new", shortcut: "A-N" }, + { + text: __("Open Recent"), + dataid: "recent", + nodes: recent, + onchildselect: ( + e: GUI.TagEventType, + r: CodePad + ) => { + const handle = e.data.item.data.text.asFileHandle(); + handle.onready().then((meta: any) => { + if (!meta) { + return; + } + if (meta.type == "dir") { + this.currdir = handle; + this.toggleSideBar(); + } + else { + this.eum.active.openFile(handle); + } + }); + } + }, { text: __("Open"), dataid: "open", shortcut: "A-O" }, { text: __("Open Folder"), dataid: "opendir", - shortcut: "A-F", + shortcut: "C-A-F", }, { text: __("Save"), dataid: "save", shortcut: "C-S" }, { @@ -906,8 +940,22 @@ namespace OS { } - - + /** + * Add a file to recent files setting + * + * @private + * @param {string} file + * @memberof CodePad + */ + private addRecent(file: string): void { + if (!this.setting.recent) + this.setting.recent = []; + if (this.setting.recent.includes(file)) { + return; + } + this.setting.recent.push(file); + this.setting.recent.slice(0, 10); + } /** * Menu action definition @@ -934,9 +982,10 @@ namespace OS { (v) => v !== "dir" ), }) - .then((f: API.FileInfoType) => - me.eum.active.openFile(f.file.path.asFileHandle()) - ); + .then((f: API.FileInfoType) => { + this.addRecent(f.file.path); + me.eum.active.openFile(f.file.path.asFileHandle()); + }); case "opendir": return me .openDialog("FileDialog", { @@ -944,6 +993,7 @@ namespace OS { mimes: ["dir"], }) .then(function (f: API.FileInfoType) { + me.addRecent(f.file.path); me.currdir = f.file.path.asFileHandle(); return me.toggleSideBar(); }); @@ -1057,7 +1107,7 @@ namespace OS { */ class CMDMenu { text: string | FormattedString; - private shortcut: string; + shortcut: string; nodes: GenericObject[]; parent: CMDMenu; rootpath: string; @@ -1075,6 +1125,9 @@ namespace OS { */ constructor(text: string | FormattedString, shortcut?: string) { this.text = text; + if (shortcut) { + this.text += `(${shortcut})`; + } this.shortcut = shortcut; this.nodes = []; this.parent = undefined; @@ -1091,6 +1144,9 @@ namespace OS { */ addAction(v: ActionType): CMDMenu { v.parent = this; + if (v.shortcut) { + v.text = `${v.text.__()} (${v.shortcut})`; + } this.nodes.push(v); return this; } @@ -1140,7 +1196,7 @@ namespace OS { } CMDMenu.fromMenu = function (mn): CMDMenu { - const m = new CMDMenu(mn.text, mn.shortcut); + const m = new CMDMenu(mn.text, undefined); m.onchildselect(mn.onchildselect); for (let it of Array.from(mn.nodes)) { let v = it as ActionType; diff --git a/src/packages/Files/main.css b/src/packages/Files/main.css index 3ecceca..52092d2 100644 --- a/src/packages/Files/main.css +++ b/src/packages/Files/main.css @@ -32,6 +32,11 @@ afx-app-window[data-id ='files-app-window'] afx-vbox[data-id = "nav-bar"]{ afx-app-window[data-id ='files-app-window'] afx-grid-view afx-grid-row.grid_row_header div{ border-top:1px solid #A6A6A6; } + +afx-app-window[data-id ='files-app-window'] afx-grid-view afx-grid-row afx-label span { + white-space: nowrap; +} + afx-app-window[data-id ='files-app-window'] button{ height: 23px; border-radius: 0; diff --git a/src/packages/MarketPlace/main.ts b/src/packages/MarketPlace/main.ts index 6a30b26..96e25d0 100644 --- a/src/packages/MarketPlace/main.ts +++ b/src/packages/MarketPlace/main.ts @@ -96,6 +96,9 @@ namespace OS { this.bindKey("CTRL-R", () => { return this.menuOptionsHandle("repos"); }); + this.bindKey("CTRL-I", () => { + return this.menuOptionsHandle("install"); + }); $(this.searchbox).keyup((e) => this.search(e)); diff --git a/src/themes/system/afx-label.css b/src/themes/system/afx-label.css index 5ed7fc9..92b9426 100644 --- a/src/themes/system/afx-label.css +++ b/src/themes/system/afx-label.css @@ -5,4 +5,5 @@ afx-label i.label-text{ font-weight: normal; font-style: normal; margin-left: 3px; + user-select:text; } \ No newline at end of file