diff --git a/Makefile b/Makefile index 7b6800f..2856a11 100644 --- a/Makefile +++ b/Makefile @@ -48,7 +48,7 @@ javascripts= dist/core/core.js \ dist/core/pm.js \ dist/bootstrap.js -packages = Syslog #Files Setting CodePad MarketPlace +packages = Syslog CodePad#Files Setting MarketPlace main: initd build_javascripts build_themes libs build_packages languages - cp src/index.html $(BUILDDIR)/ diff --git a/src/core/Announcerment.ts b/src/core/Announcerment.ts index 18fa8c5..20c52a0 100644 --- a/src/core/Announcerment.ts +++ b/src/core/Announcerment.ts @@ -157,7 +157,7 @@ namespace OS { trigger(evtName: string, data: any): void { const trig = (name: string, d: any) => { const names = [name, "*"]; - for (let evt of Array.from(names)) { + for (let evt of names) { if (!this.observable[evt]) { continue; } @@ -272,7 +272,7 @@ namespace OS { ) { return; } - for (let i of Array.from(announcer.listeners[app.pid])) { + for (let i of announcer.listeners[app.pid]) { announcer.observable.off(i.e, i.f); } delete announcer.listeners[app.pid]; diff --git a/src/core/BaseDialog.ts b/src/core/BaseDialog.ts index e074bd9..98c7fa0 100644 --- a/src/core/BaseDialog.ts +++ b/src/core/BaseDialog.ts @@ -211,6 +211,10 @@ namespace OS { this.host ); } + else + { + this.error(__("Unable to find dialog scheme")); + } } /** @@ -846,7 +850,7 @@ namespace OS { if (!e || !e.data.item) { return; } - setroot(e.data.item.get("data").path); + setroot(e.data.item.data.path); }; location.data = this.systemsetting.VFS.mountpoints.filter( (i) => i.type !== "app" @@ -884,7 +888,7 @@ namespace OS { //verify the mime let m = false; if (f.mime) { - for (let v of Array.from(this.data.mimes)) { + for (let v of this.data.mimes) { if ( f.mime.match( new RegExp(v as string, "g") diff --git a/src/core/core.ts b/src/core/core.ts index 7169431..edb3dfc 100644 --- a/src/core/core.ts +++ b/src/core/core.ts @@ -378,7 +378,8 @@ namespace OS { Object.defineProperty(Object.prototype, "__", { value() { - return this.toString(); + if(this) + return this.toString(); }, enumerable: false, writable: true, diff --git a/src/core/gui.ts b/src/core/gui.ts index b1b30a4..23f91a4 100644 --- a/src/core/gui.ts +++ b/src/core/gui.ts @@ -44,7 +44,7 @@ namespace OS { * @interface BasicItemType */ export interface BasicItemType { - text: string; + text: string | FormatedString; children?: BasicItemType[]; nodes?: BasicItemType[]; [propName: string]: any; @@ -190,7 +190,7 @@ namespace OS { } let m: API.PackageMetaType; const mimes: Array = []; - for (m of Array.from(metas)) { + for (m of metas) { if (m) { mimes.push(m.mimes); } @@ -275,7 +275,7 @@ namespace OS { if (apps.length === 1) { return launch(apps[0].app, [it]); } - const list = Array.from(apps).map((e) => ({ + const list = apps.map((e) => ({ text: e.app, icon: e.icon, iconclass: e.iconclass, @@ -1041,17 +1041,16 @@ namespace OS { pushServices( (() => { const result = []; - for (let v of Array.from( + for (let v of setting.system.startup.services - )) { + ) { result.push(v); } return result; })() ); - return Array.from( - setting.system.startup.apps - ).map((a) => launch(a, [])); + return + setting.system.startup.apps.map((a) => launch(a, [])); }); } }); diff --git a/src/core/pm.ts b/src/core/pm.ts index 2816ad0..a9359d2 100644 --- a/src/core/pm.ts +++ b/src/core/pm.ts @@ -57,7 +57,7 @@ namespace OS { return obj; }; if (metaclass.dependencies) { - const libs = Array.from(metaclass.dependencies); + const libs = metaclass.dependencies; return API.require(libs) .then(() => resolve(f())) .catch((e: Error) => reject(__e(e))); @@ -77,7 +77,7 @@ namespace OS { export function appByPid(pid: number): BaseModel { let app = undefined; const find = function (l: Array) { - for (let a of Array.from(l)) { + for (let a of l) { if (a.pid === pid) { return a; } @@ -130,7 +130,7 @@ namespace OS { if (!PM.processes[app]) { return; } - Array.from(PM.processes[app]).map((a) => a.quit(force)); + PM.processes[app].map((a) => a.quit(force)); } } } diff --git a/src/core/settings.ts b/src/core/settings.ts index 631813e..acec9af 100644 --- a/src/core/settings.ts +++ b/src/core/settings.ts @@ -311,7 +311,7 @@ namespace OS { v1.complex = true; ar.push(v1); } else if (v.mimes) { - for (let m of Array.from(v.mimes)) { + for (let m of v.mimes) { if (t.match(new RegExp(m, "g"))) { v1 = {}; for (k1 in v) { diff --git a/src/core/tags/FileViewTag.ts b/src/core/tags/FileViewTag.ts index 6b80299..ec01c93 100644 --- a/src/core/tags/FileViewTag.ts +++ b/src/core/tags/FileViewTag.ts @@ -356,12 +356,13 @@ namespace OS { private refreshTree(): void { //@treeview.root.set("selectedItem", null) const tdata: TreeViewDataType = { - name: this.path, + text: this.path, path: this.path, open: true, - nodes: this.getTreeData(this.data), + nodes: this.getTreeData(this.data) }; (this.refs.treeview as TreeViewTag).data = tdata; + // (this.refs.treeview as TreeViewTag).expandAll(); } /** @@ -381,7 +382,7 @@ namespace OS { if (v.filename[0] === "." && !this.showhidden) { return undefined; } - v.name = v.filename; + v.text = v.filename; if (v.type === "dir") { v.nodes = []; v.open = false; @@ -500,13 +501,13 @@ namespace OS { const tree = this.refs.treeview as TreeViewTag; tree.fetch = (v) => { return new Promise((resolve, reject) => { - if (!this.fetch) { + if (!this._fetch) { return resolve(undefined); } if (!v.data.path) { return resolve(undefined); } - return this.fetch(v.data.path) + return this._fetch(v.data.path) .then((d: API.FileInfoType[]) => resolve( this.getTreeData( @@ -524,25 +525,25 @@ namespace OS { list.dragndrop = true; // even handles list.onlistselect = (e) => { - this.fileselect(e.data.item.get("data")); + this.fileselect(e.data.item.data); }; grid.onrowselect = (e) => { this.fileselect( - $(e.data.item).children()[0].get("data") + $(e.data.item).children()[0].data ); }; tree.ontreeselect = (e) => { - this.fileselect(e.data.item.get("data")); + this.fileselect(e.data.item.data); }; // dblclick list.onlistdbclick = (e) => { - this.filedbclick(e.data.item.get("data")); + this.filedbclick(e.data.item.data); }; grid.oncelldbclick = (e) => { - this.filedbclick(e.data.item.get("data")); + this.filedbclick(e.data.item.data); }; tree.ontreedbclick = (e) => { - this.filedbclick(e.data.item.get("data")); + this.filedbclick(e.data.item.data); }; this.switchView(); } diff --git a/src/core/tags/GridViewTag.ts b/src/core/tags/GridViewTag.ts index 291c17e..9af055d 100644 --- a/src/core/tags/GridViewTag.ts +++ b/src/core/tags/GridViewTag.ts @@ -181,7 +181,7 @@ namespace OS { return; } $(this.refs.header).empty(); - for (let item of Array.from(v)) { + for (let item of v) { const el = $(`<${this.headeritem}>`).appendTo( this.refs.header ); @@ -263,7 +263,7 @@ namespace OS { el.data = row; row.domel = rowel[0]; - for (let cell of Array.from(row)) { + for (let cell of row) { let tag = this.cellitem; if (cell.tag) { ({ tag } = cell); diff --git a/src/core/tags/ListViewTag.ts b/src/core/tags/ListViewTag.ts index 1ae8a65..d54bdcb 100644 --- a/src/core/tags/ListViewTag.ts +++ b/src/core/tags/ListViewTag.ts @@ -497,7 +497,7 @@ namespace OS { return; } $(this.refs.btlist).empty(); - for (let item of Array.from(v)) { + for (let item of v) { $(this.refs.btlist).show(); const bt = $("").appendTo(this.refs.btlist); (bt[0] as ButtonTag).set(item); @@ -524,7 +524,7 @@ namespace OS { this._selectedItem = undefined; this._selectedItems = []; $(this.refs.mlist).empty(); - for (let item of Array.from(data)) { + for (let item of data) { this.push(item, false); } $(this.refs.container).off("mousedown", this._onmousedown); @@ -791,7 +791,7 @@ namespace OS { * @memberof ListViewTag */ private idbclick(e: TagEventType) { - const evt = { id: this.aid, data: e }; + const evt = { id: this.aid, data: { item: e.data } }; this._onlistdbclick(evt); return this.observable.trigger("listdbclick", evt); } @@ -943,7 +943,7 @@ namespace OS { if (!e.data) { return; } - const evt = { id: this.aid, data: e }; + const evt = { id: this.aid, data: {item: e.data} }; const r = this._onitemclose(evt); if (!r) { return; diff --git a/src/core/tags/TabBarTag.ts b/src/core/tags/TabBarTag.ts index 7c9f5a9..b4329b1 100644 --- a/src/core/tags/TabBarTag.ts +++ b/src/core/tags/TabBarTag.ts @@ -16,7 +16,6 @@ namespace OS { * @extends {AFXTag} */ export class TabBarTag extends AFXTag { - private _items: GenericObject[]; private _selected: number; private _ontabclose: (e: TagEventType) => boolean; private _ontabselect: TagEventCallback; @@ -29,7 +28,6 @@ namespace OS { super(); this._ontabclose = (e) => true; this._ontabselect = (e) => {}; - this._items = []; } /** @@ -108,7 +106,6 @@ namespace OS { * @memberof TabBarTag */ set items(v: GenericObject[]) { - this._items = v; for (let i of v) { i.closable = this.closable; } @@ -122,7 +119,7 @@ namespace OS { * @memberof TabBarTag */ get items(): GenericObject[] { - return this._items; + return (this.refs.list as ListViewTag).data; } /** diff --git a/src/core/tags/TileLayoutTags.ts b/src/core/tags/TileLayoutTags.ts index 8a1e6f3..e842d60 100644 --- a/src/core/tags/TileLayoutTags.ts +++ b/src/core/tags/TileLayoutTags.ts @@ -149,7 +149,7 @@ namespace OS { } } - class HBoxTag extends TileLayoutTag { + export class HBoxTag extends TileLayoutTag { constructor() { super(); } @@ -162,7 +162,7 @@ namespace OS { } } - class VBoxTag extends TileLayoutTag { + export class VBoxTag extends TileLayoutTag { constructor() { super(); diff --git a/src/core/tags/TreeViewTag.ts b/src/core/tags/TreeViewTag.ts index 7b523ee..e6dfcaf 100644 --- a/src/core/tags/TreeViewTag.ts +++ b/src/core/tags/TreeViewTag.ts @@ -89,6 +89,7 @@ namespace OS { this.treepath = v.path; } this.selected = v.selected; + v.domel = this; this.ondatachange(); } @@ -171,6 +172,15 @@ namespace OS { $(this.refs.toggle).addClass("afx-tree-view-folder-close"); } + /** + * + * + * @type {boolean} + * @memberof TreeViewItemPrototype + */ + get open(): boolean { + return this.hasattr("open"); + } /** * * @@ -264,7 +274,7 @@ namespace OS { id: this.aid, data: { item: this, dblclick: false }, }; - this.indent = 0; + this._indent = 0; } /** * @@ -602,7 +612,7 @@ namespace OS { if (e.data.item === this.selectedItem && !e.data.dblclick) { return; } - this.selectedItem = e.data.item; + this._selectedItem = e.data.item; const evt = { id: this.aid, data: e.data }; if (e.data.dblclick) { this._ontreedbclick(evt); @@ -666,7 +676,7 @@ namespace OS { } let tag = this.itemtag; if (v.tag) { - ({ tag } = v); + tag = v.tag; } const el = $(`<${tag}>`).appendTo(this); el[0].uify(this.observable); diff --git a/src/core/tags/tag.ts b/src/core/tags/tag.ts index a95573f..153c919 100644 --- a/src/core/tags/tag.ts +++ b/src/core/tags/tag.ts @@ -169,7 +169,7 @@ namespace OS { $(dom).attr("tooltip", tag.tooltip.__()); } if (tag.children) { - for (let v of Array.from(tag.children)) { + for (let v of tag.children) { $(this.mkui(v)).appendTo(dom); } } diff --git a/src/core/vfs.ts b/src/core/vfs.ts index d34e2e6..9543bef 100644 --- a/src/core/vfs.ts +++ b/src/core/vfs.ts @@ -134,7 +134,6 @@ namespace OS { basename: string; info: FileInfoType; ext: string; - /** *Creates an instance of BaseFileHandle. * @param {string} path @@ -708,7 +707,7 @@ namespace OS { * @class RemoteFileHandle * @extends {BaseFileHandle} */ - class RemoteFileHandle extends BaseFileHandle { + export class RemoteFileHandle extends BaseFileHandle { constructor(path: string) { super(path); } @@ -1021,7 +1020,7 @@ namespace OS { * @class ApplicationHandle * @extends {BaseFileHandle} */ - class ApplicationHandle extends BaseFileHandle { + export class ApplicationHandle extends BaseFileHandle { /** *Creates an instance of ApplicationHandle. @@ -1099,7 +1098,7 @@ namespace OS { * @class BufferFileHandle * @extends {BaseFileHandle} */ - class BufferFileHandle extends BaseFileHandle { + export class BufferFileHandle extends BaseFileHandle { constructor(path: string, mime: string, data: any) { super(path); if (data) { @@ -1188,7 +1187,7 @@ namespace OS { * @class URLFileHandle * @extends {BaseFileHandle} */ - class URLFileHandle extends BaseFileHandle { + export class URLFileHandle extends BaseFileHandle { constructor(path: string) { super(path); this.ready = true; @@ -1228,7 +1227,7 @@ namespace OS { * @class SharedFileHandle * @extends {API.VFS.BaseFileHandle} */ - class SharedFileHandle extends API.VFS.BaseFileHandle { + export class SharedFileHandle extends API.VFS.BaseFileHandle { constructor(path: string) { super(path); if (this.isRoot()) { diff --git a/src/packages/CodePad/AntOSDK.ts b/src/packages/CodePad/AntOSDK.ts new file mode 100644 index 0000000..efec049 --- /dev/null +++ b/src/packages/CodePad/AntOSDK.ts @@ -0,0 +1,383 @@ +namespace OS { + // import the CodePad application module + const App = OS.application.CodePad; + + declare var CoffeeScript: any; + declare var JSZip: any; + declare var Terser: any; + + /** + * + * + * @class AntOSDK + * @extends {App.BaseExtension} + */ + class AntOSDK extends App.BaseExtension { + /** + *Creates an instance of AntOSDK. + * @param {application.CodePad} app + * @memberof AntOSDK + */ + constructor(app: application.CodePad) { + super(app); + } + + // public functions + /** + * + * + * @returns + * @memberof AntOSDK + */ + create(): void { + this.app + .openDialog("FileDialog", { + title: "__(New Project at)", + file: { basename: __("ProjectName") }, + mimes: ["dir"], + }) + .then((d) => { + return this.mktpl(d.file.path, d.name, true); + }); + } + + /** + * + * + * @returns {void} + * @memberof AntOSDK + */ + init(): void { + const dir = this.app.currdir; + if (!dir || !dir.basename) { + return this.create(); + } + dir.read().then((d) => { + if (d.error) { + return this.notify(__("Cannot read folder: {0}", dir.path)); + } + if (d.result.length !== 0) { + return this.notify( + __("The folder is not empty: {0}", dir.path) + ); + } + this.mktpl(dir.parent().path, dir.basename); + }); + } + + /** + * + * + * @memberof AntOSDK + */ + buildnrun(): void { + this.metadata("project.json") + .then(async (meta) => { + try { + await this.build(meta, true); + try { + return this.run(meta); + } catch (e) { + return this.error(__("Unable to run project"), e); + } + } catch (e_1) { + return this.error(__("Unable to build project"), e_1); + } + }) + .catch((e) => this.error(__("Unable to read meta-data"), e)); + } + + /** + * + * + * @memberof AntOSDK + */ + release(): void { + this.metadata("project.json") + .then(async (meta) => { + try { + await this.build(meta, false); + try { + return this.mkar( + `${meta.root}/build/debug`, + `${meta.root}/build/release/${meta.name}.zip` + ); + } catch (e) { + return this.error( + __("Unable to create package archive"), + e + ); + } + } catch (e_1) { + return this.error(__("Unable to build project"), e_1); + } + }) + .catch((e) => this.error(__("Unable to read meta-data"), e)); + } + + // private functions + /** + * + * + * @private + * @param {string} path + * @param {string} name + * @param {boolean} [flag] + * @memberof AntOSDK + */ + private mktpl(path: string, name: string, flag?: boolean): void { + const rpath = `${path}/${name}`; + const dirs = [ + `${rpath}/javascripts`, + `${rpath}/css`, + `${rpath}/coffees`, + `${rpath}/assets`, + ]; + if (flag) { + dirs.unshift(rpath); + } + const files = [ + ["templates/sdk-main.tpl", `${rpath}/coffees/main.coffee`], + ["templates/sdk-package.tpl", `${rpath}/package.json`], + ["templates/sdk-project.tpl", `${rpath}/project.json`], + ["templates/sdk-README.tpl", `${rpath}/README.md`], + ["templates/sdk-scheme.tpl", `${rpath}/assets/scheme.html`], + ]; + this.mkdirAll(dirs) + .then(async () => { + try { + await this.mkfileAll(files, path, name); + this.app.currdir = rpath.asFileHandle(); + this.app.initSideBar(); + return this.app.openFile( + `${rpath}/README.md`.asFileHandle() as application.CodePadFileHandle + ); + } catch (e) { + return this.error( + __("Unable to create template files"), + e + ); + } + }) + .catch((e) => + this.error(__("Unable to create project directory"), e) + ); + } + + /** + * + * + * @private + * @param {string[]} list + * @returns {Promise} + * @memberof AntOSDK + */ + private verify(list: string[]): Promise { + return new Promise((resolve, reject) => { + if (list.length === 0) { + return resolve(); + } + const file = list.splice(0, 1)[0].asFileHandle(); + this.notify(__("Verifying: {0}", file.path)); + return file + .read() + .then((data) => { + try { + CoffeeScript.nodes(data); + return this.verify(list) + .then(() => resolve()) + .catch((e) => reject(__e(e))); + } catch (ex) { + return reject(__e(ex)); + } + }) + .catch((e) => reject(__e(e))); + }); + } + + /** + * + * + * @private + * @param {GenericObject} meta + * @returns {Promise} + * @memberof AntOSDK + */ + private compile(meta: GenericObject): Promise { + return new Promise(async (resolve, reject) => { + try { + await this.import([ + `${this.basedir()}/libs/coffeescript.js`, + `${this.basedir()}/libs/terser.min.js`, + ]); + const list = meta.coffees.map( + (v: string) => `${meta.root}/${v}` + ); + try { + await this.verify(list.map((x: string) =>x)); + try { + const code = await this.cat(list, ""); + const jsrc = CoffeeScript.compile(code); + this.notify(__("Compiled successful")); + return resolve(jsrc); + } catch (e) { + return reject(__e(e)); + } + } catch (e_1) { + return reject(__e(e_1)); + } + } catch (e_2) { + return reject(__e(e_2)); + } + }); + } + + /** + * + * + * @private + * @param {GenericObject} meta + * @param {boolean} debug + * @returns {Promise} + * @memberof AntOSDK + */ + private build(meta: GenericObject, debug: boolean): Promise { + const dirs = [ + `${meta.root}/build`, + `${meta.root}/build/debug`, + `${meta.root}/build/release`, + ]; + return new Promise(async (resolve, reject) => { + try { + await this.mkdirAll(dirs); + try { + const src = await this.compile(meta); + let v: string; + try { + let jsrc = await this.cat( + (() => { + const result = []; + for (v of meta.javascripts) { + result.push(`${meta.root}/${v}`); + } + return result; + })(), + src + ); + await new Promise(async function (r, e) { + let code = jsrc; + if (!debug) { + const options = { + toplevel: true, + compress: { + passes: 3, + }, + mangle: true, + output: { + //beautify: true, + }, + }; + const result_1 = Terser.minify( + jsrc, + options + ); + if (result_1.error) { + this.notify( + __( + "Unable to minify code: {0}", + result_1.error + ) + ); + } else { + ({ code } = result_1); + } + } + try { + const d = await `${meta.root}/build/debug/main.js` + .asFileHandle() + .setCache(code) + .write("text/plain"); + return r(); + } catch (ex) { + return e(__e(ex)); + } + }); + await new Promise(async (r, e) => { + const txt = await this.cat( + (() => { + const result1 = []; + for (v of meta.css) { + result1.push(`${meta.root}/${v}`); + } + return result1; + })(), + "" + ); + if (txt === "") { + return r(); + } + try { + const d_1 = await `${meta.root}/build/debug/main.css` + .asFileHandle() + .setCache(txt) + .write("text/plain"); + return r(); + } catch (ex_1) { + return e(__e(ex_1)); + } + }); + await this.copy( + (() => { + const result1_1 = []; + for (v of meta.copies) { + result1_1.push(`${meta.root}/${v}`); + } + return result1_1; + })(), + `${meta.root}/build/debug` + ); + return resolve(); + } catch (e) { + return reject(__e(e)); + } + } catch (e_1) { + return reject(__e(e_1)); + } + } catch (e_2) { + return reject(__e(e_2)); + } + }); + } + + /** + * + * + * @private + * @param {GenericObject} meta + * @memberof AntOSDK + */ + private run(meta: GenericObject): void { + `${meta.root}/build/debug/package.json` + .asFileHandle() + .read("json") + .then((v) => { + v.text = v.name; + v.path = `${meta.root}/build/debug`; + v.filename = meta.name; + v.type = "app"; + v.mime = "antos/app"; + if (v.icon) { + v.icon = `${v.path}/${v.icon}`; + } + if (!v.iconclass && !v.icon) { + v.iconclass = "fa fa-adn"; + } + this.notify(__("Installing...")); + this.app.systemsetting.system.packages[meta.name] = v; + this.notify(__("Running {0}...", meta.name)); + return this.app._gui.forceLaunch(meta.name, []); + }); + } + } + App.extensions.AntOSDK = AntOSDK; +} diff --git a/src/packages/CodePad/BaseExtension.js b/src/packages/CodePad/BaseExtension.js deleted file mode 100644 index a6f79cf..0000000 --- a/src/packages/CodePad/BaseExtension.js +++ /dev/null @@ -1,195 +0,0 @@ -/* - * decaffeinate suggestions: - * DS101: Remove unnecessary use of Array.from - * DS102: Remove unnecessary code created because of implicit returns - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ -CodePad.BaseExtension = class BaseExtension { - - constructor(app) { - this.app = app; - } - - preload() { - return Ant.OS.API.require(this.dependencies()); - } - - import(libs) { - return Ant.OS.API.require(libs); - } - - basedir() { - return `${this.app.meta().path}/extensions`; - } - - notify(m) { - return this.app.notify(m); - } - - error(m, e) { - return this.app.error(m, e); - } - - dependencies() { - return []; - } - - cat(list, data) { - return new Promise((resolve, reject) => { - if (list.length === 0) { return resolve(data); } - const file = (list.splice(0, 1))[0].asFileHandle(); - return file - .read() - .then(text => { - data = data + "\n" + text; - return this.cat(list, data) - .then(d => resolve(d)) - .catch(e => reject(__e(e))); - }).catch(e => reject(__e(e))); - }); - } - - copy(files, to) { - return new Promise((resolve, reject) => { - if (files.length === 0) { return resolve(); } - const file = (files.splice(0, 1))[0].asFileHandle(); - const tof = `${to}/${file.basename}`.asFileHandle(); - return file.onready().then(meta => { - if (meta.type === "dir") { - // copy directory - const desdir = to.asFileHandle(); - return desdir.mk(file.basename).then(() => { - // read the dir content - return file.read().then(data => { - const list = (Array.from(data.result).map((v) => v.path)); - return this.copy(list, `${desdir.path}/${file.basename}`) - .then(() => { - return this.copy(files, to) - .then(() => resolve()) - .catch(e => reject(__e(e))); - }).catch(e => reject(__e(e))); - }).catch(e => reject(__e(e))); - }).catch(e => reject(__e(e))); - } else { - // copy file - return file.read("binary") - .then(data => { - return tof.setCache(new Blob([data], { type: file.info.mime })) - .write(file.info.mime) - .then(d => { - return this.copy(files, to) - .then(() => resolve()) - .catch(e => reject(__e(e))); - }); - }).catch(e => reject(__e(e))); - } - }).catch(e => reject(__e(e))); - }); - } - - aradd(list, zip, base) { - return new Promise((resolve, reject) => { - if (list.length === 0) { return resolve(zip); } - const path = (list.splice(0, 1))[0]; - const file = path.asFileHandle(); - return file.onready().then(meta => { - if (meta.type === "dir") { - return file.read().then(d => { - const l = (Array.from(d.result).map((v) => v.path)); - return this.aradd(l, zip, `${base}${file.basename}/`) - .then(() => { - return this.aradd(list, zip, base) - .then(() => resolve(zip)) - .catch(e => reject(__e(e))); - }).catch(e => reject(__e(e))); - }).catch(e => reject(__e(e))); - } else { - return file.read("binary").then(d => { - const zpath = `${base}${file.basename}`.replace(/^\/+|\/+$/g, ''); - zip.file(zpath, d, { binary: true }); - return this.aradd(list, zip, base) - .then(() => resolve(zip)) - .catch(e => reject(__e(e))); - }).catch(e => reject(__e(e))); - } - }).catch(e => reject(__e(e))); - }); - } - - mkar(src, dest) { - this.notify(__("Preparing for release")); - return new Promise((resolve, reject) => { - return new Promise((r, e) => { - return this.import(["os://scripts/jszip.min.js"]).then(() => src.asFileHandle() - .read().then(d => r(d.result)).catch(ex => e(__e(ex)))).catch(ex => e(__e(ex))); - }).then(files => { - return new Promise((r, e) => { - const zip = new JSZip(); - return this.aradd((Array.from(files).map((v) => v.path)), zip, "/") - .then(z => r(z)) - .catch(ex => e(__e(ex))); - }); - }).then(zip => { - return zip.generateAsync({ type: "base64" }).then(data => { - return dest.asFileHandle() - .setCache('data:application/zip;base64,' + data) - .write("base64").then(r => { - return this.notify(__("Archive is generated at: {0}", dest)); - }).catch(e => reject(__e(e))); - }); - }).catch(e => reject(__e(e))); - }); - } - - mkdirAll(list) { - return new Promise((resolve, reject) => { - if (list.length === 0) { return resolve(); } - const path = (list.splice(0, 1))[0].asFileHandle(); - return path.parent().mk(path.basename) - .then(d => { - this.app.trigger("filechange", { file: path.parent(), type: "dir" }); - return this.mkdirAll(list) - .then(() => resolve()) - .catch(e => reject(__e(e))); - }).catch(e => reject(__e(e))); - }); - } - - mkfileAll(list, path, name) { - return new Promise((resolve, reject) => { - if (list.length === 0) { return resolve(); } - const item = (list.splice(0, 1))[0]; - return `${this.basedir()}/${item[0]}` - .asFileHandle() - .read() - .then(data => { - const file = item[1].asFileHandle(); - return file - .setCache(data.format(name, `${path}/${name}`)) - .write("text/plain") - .then(() => { - this.app.trigger("filechange", { file, type: "file" }); - return this.mkfileAll(list, path, name) - .then(() => resolve()) - .catch(e => reject(__e(e))); - }).catch(e => reject(__e(e))); - }).catch(e => reject(__e(e))); - }); - } - - metadata(file) { - return new Promise((resolve, reject) => { - if (!this.app.currdir) { - return reject(this.app._api.throwe(__("Current folder is not found"))); - } - return `${this.app.currdir.path}/${file}` - .asFileHandle() - .read("json") - .then(data => resolve(data)).catch(e => { - return reject(this.app._api.throwe(__("Unable to read meta-data"))); - }); - }); - } -}; - -CodePad.extensions = {}; \ No newline at end of file diff --git a/src/packages/CodePad/BaseExtension.ts b/src/packages/CodePad/BaseExtension.ts new file mode 100644 index 0000000..f2d8ec9 --- /dev/null +++ b/src/packages/CodePad/BaseExtension.ts @@ -0,0 +1,435 @@ +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +namespace OS { + const CodePad = OS.application.CodePad; + declare var JSZip: any; + + export namespace application { + export type CodePadBaseExtension = typeof BaseExtension; + } + /** + * + * + * @class BaseExtension + */ + class BaseExtension { + protected app: OS.application.CodePad; + constructor(app: OS.application.CodePad) { + this.app = app; + } + + /** + * + * + * @returns {Promise} + * @memberof BaseExtension + */ + preload(): Promise { + return API.require(this.dependencies()); + } + + /** + * + * + * @param {string[]} libs + * @returns {Promise} + * @memberof BaseExtension + */ + import(libs: string[]): Promise { + return API.require(libs); + } + + /** + * + * + * @protected + * @returns {string} + * @memberof BaseExtension + */ + protected basedir(): string { + return `${this.app.meta().path}`; + } + + /** + * + * + * @protected + * @param {(string | FormatedString)} m + * @returns {void} + * @memberof BaseExtension + */ + protected notify(m: string | FormatedString): void { + return this.app.notify(m); + } + + /** + * + * + * @protected + * @param {(string | FormatedString)} m + * @param {Error} e + * @returns {void} + * @memberof BaseExtension + */ + protected error(m: string | FormatedString, e: Error): void { + return this.app.error(m, e); + } + + /** + * + * + * @protected + * @returns {string[]} + * @memberof BaseExtension + */ + protected dependencies(): string[] { + return []; + } + + /** + * + * + * @protected + * @param {string[]} list + * @param {string} data + * @returns {Promise} + * @memberof BaseExtension + */ + protected cat(list: string[], data: string): Promise { + return new Promise((resolve, reject) => { + if (list.length === 0) { + return resolve(data); + } + const file = list.splice(0, 1)[0].asFileHandle(); + return file + .read() + .then((text: string) => { + data = data + "\n" + text; + return this.cat(list, data) + .then((d) => resolve(d)) + .catch((e) => reject(__e(e))); + }) + .catch((e: Error) => reject(__e(e))); + }); + } + + /** + * + * + * @protected + * @param {string[]} files + * @param {string} to + * @returns {Promise} + * @memberof BaseExtension + */ + protected copy(files: string[], to: string): Promise { + return new Promise((resolve, reject) => { + if (files.length === 0) { + return resolve(); + } + const file = files.splice(0, 1)[0].asFileHandle(); + const tof = `${to}/${file.basename}`.asFileHandle(); + return file + .onready() + .then((meta: { type: string }) => { + if (meta.type === "dir") { + // copy directory + const desdir = to.asFileHandle(); + return desdir + .mk(file.basename) + .then(() => { + // read the dir content + return file + .read() + .then((data: API.RequestResult) => { + const list = (data.result as API.FileInfoType[]).map( + (v) => v.path + ); + return this.copy( + list, + `${desdir.path}/${file.basename}` + ) + .then(() => { + return this.copy(files, to) + .then(() => resolve()) + .catch((e) => + reject(__e(e)) + ); + }) + .catch((e) => reject(__e(e))); + }) + .catch((e: Error) => reject(__e(e))); + }) + .catch((e: Error) => reject(__e(e))); + } else { + // copy file + return file + .read("binary") + .then(async (data: BlobPart) => { + const d = await tof + .setCache( + new Blob([data], { + type: file.info.mime, + }) + ) + .write(file.info.mime); + try { + await this.copy(files, to); + return resolve(); + } catch (e) { + return reject(__e(e)); + } + }) + .catch((e: Error) => reject(__e(e))); + } + }) + .catch((e: Error) => reject(__e(e))); + }); + } + + /** + * + * + * @private + * @param {string[]} list + * @param {*} zip + * @param {string} base + * @returns {Promise} + * @memberof BaseExtension + */ + private aradd(list: string[], zip: any, base: string): Promise { + return new Promise((resolve, reject) => { + if (list.length === 0) { + return resolve(zip); + } + const path = list.splice(0, 1)[0]; + const file = path.asFileHandle(); + return file + .onready() + .then((meta: { type: string }) => { + if (meta.type === "dir") { + return file + .read() + .then( + (d: { + result: + | Iterable + | ArrayLike; + }) => { + const l = (d.result as API.FileInfoType[]).map( + (v) => v.path + ); + return this.aradd( + l, + zip, + `${base}${file.basename}/` + ) + .then(() => { + return this.aradd( + list, + zip, + base + ) + .then(() => resolve(zip)) + .catch((e) => + reject(__e(e)) + ); + }) + .catch((e) => reject(__e(e))); + } + ) + .catch((e: Error) => reject(__e(e))); + } else { + return file + .read("binary") + .then((d: any) => { + const zpath = `${base}${file.basename}`.replace( + /^\/+|\/+$/g, + "" + ); + zip.file(zpath, d, { binary: true }); + return this.aradd(list, zip, base) + .then(() => resolve(zip)) + .catch((e) => reject(__e(e))); + }) + .catch((e: Error) => reject(__e(e))); + } + }) + .catch((e: Error) => reject(__e(e))); + }); + } + + /** + * + * + * @protected + * @param {string} src + * @param {string} dest + * @returns {Promise} + * @memberof BaseExtension + */ + protected mkar(src: string, dest: string): Promise { + this.notify(__("Preparing for release")); + return new Promise((resolve, reject) => { + return new Promise(async (r, e) => { + try { + await this.import(["os://scripts/jszip.min.js"]); + try { + const d = await src.asFileHandle().read(); + return r(d.result); + } catch (ex) { + return e(__e(ex)); + } + } catch (ex_1) { + return e(__e(ex_1)); + } + }) + .then((files: API.FileInfoType[]) => { + return new Promise(async (r, e) => { + const zip = new JSZip(); + try { + const z = await this.aradd( + files.map((v: { path: any }) => v.path), + zip, + "/" + ); + return r(z); + } catch (ex) { + return e(__e(ex)); + } + }); + }) + .then((zip: any) => { + return zip + .generateAsync({ type: "base64" }) + .then((data: string) => { + return dest + .asFileHandle() + .setCache( + "data:application/zip;base64," + data + ) + .write("base64") + .then((r: any) => { + resolve(); + return this.notify( + __( + "Archive is generated at: {0}", + dest + ) + ); + }) + .catch((e: Error) => reject(__e(e))); + }); + }) + .catch((e) => reject(__e(e))); + }); + } + + /** + * + * + * @protected + * @param {string[]} list + * @returns {Promise} + * @memberof BaseExtension + */ + protected mkdirAll(list: string[]): Promise { + return new Promise((resolve, reject) => { + if (list.length === 0) { + return resolve(); + } + const path = list.splice(0, 1)[0].asFileHandle(); + return path + .parent() + .mk(path.basename) + .then((d: any) => { + this.app.trigger("filechange", { + file: path.parent(), + type: "dir", + }); + return this.mkdirAll(list) + .then(() => resolve()) + .catch((e) => reject(__e(e))); + }) + .catch((e: Error) => reject(__e(e))); + }); + } + + /** + * + * + * @protected + * @param {string[]} list + * @param {string} path + * @param {string} name + * @returns {Promise} + * @memberof BaseExtension + */ + protected mkfileAll( + list: Array, + path: string, + name: string + ): Promise { + return new Promise((resolve, reject) => { + if (list.length === 0) { + return resolve(); + } + const item = list.splice(0, 1)[0]; + return `${this.basedir()}/${item[0]}` + .asFileHandle() + .read() + .then((data) => { + const file = item[1].asFileHandle(); + return file + .setCache(data.format(name, `${path}/${name}`)) + .write("text/plain") + .then(() => { + this.app.trigger("filechange", { + file, + type: "file", + }); + return this.mkfileAll(list, path, name) + .then(() => resolve()) + .catch((e) => reject(__e(e))); + }) + .catch((e: Error) => reject(__e(e))); + }) + .catch((e) => reject(__e(e))); + }); + } + + /** + * + * + * @protected + * @param {string} file + * @returns {Promise>} + * @memberof BaseExtension + */ + protected metadata(file: string): Promise> { + return new Promise((resolve, reject) => { + if (!this.app.currdir) { + return reject( + this.app._api.throwe(__("Current folder is not found")) + ); + } + `${this.app.currdir.path}/${file}` + .asFileHandle() + .read("json") + .then((data) => resolve(data)) + .catch((e) => { + return reject( + this.app._api.throwe(__("Unable to read meta-data")) + ); + }); + }); + } + } + + CodePad.extensions = {}; + CodePad.BaseExtension = BaseExtension; +} diff --git a/src/packages/CodePad/CommandPalette.js b/src/packages/CodePad/CommandPalette.js deleted file mode 100644 index 2aff941..0000000 --- a/src/packages/CodePad/CommandPalette.js +++ /dev/null @@ -1,98 +0,0 @@ -/* - * decaffeinate suggestions: - * DS101: Remove unnecessary use of Array.from - * DS102: Remove unnecessary code created because of implicit returns - * DS205: Consider reworking code to avoid use of IIFEs - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ -class CommandPalette extends this.OS.GUI.BasicDialog { - constructor() { - super("CommandPalete", CommandPalette.scheme); - } - - main() { - super.main(); - const offset = $(".afx-window-content", this.parent.scheme).offset(); - const pw = this.parent.scheme.get("width") / 5; - this.scheme.set("width", 3 * pw); - $(this.scheme).offset({ top: offset.top - 2, left: offset.left + pw }); - var cb = e => { - if (($(e.target)).closest(this.scheme).length > 0) { - return $(this.find("searchbox")).focus(); - } else { - $(document).unbind("mousedown", cb); - return this.quit(); - } - }; - $(document).on("mousedown", cb); - $(this.find("searchbox")).focus(); - this.cmdlist = this.find("container"); - if (this.data) { this.cmdlist.set("data", (Array.from(this.data.child))); } - $(this.cmdlist).click(e => { - return this.selectCommand(); - }); - - this.searchbox = this.find("searchbox"); - return ($(this.searchbox)).keyup(e => { - return this.search(e); - }); - } - - search(e) { - let v; - switch (e.which) { - case 27: - // escape key - this.quit(); - if (this.data.parent && this.data.parent.run) { return this.data.parent.run(this.parent); } - break; - case 37: - return e.preventDefault(); - case 38: - this.cmdlist.selectPrev(); - return e.preventDefault(); - case 39: - return e.preventDefault(); - case 40: - this.cmdlist.selectNext(); - return e.preventDefault(); - case 13: - e.preventDefault(); - return this.selectCommand(); - default: - var text = this.searchbox.value; - if (text.length === 2) { this.cmdlist.set("data", ((() => { - const result1 = []; - for (v of Array.from(this.data.child)) { result1.push(v); - } - return result1; - })())); } - if (text.length < 3) { return; } - var result = []; - var term = new RegExp(text, 'i'); - for (v of Array.from(this.data.child)) { if (v.text.match(term)) { result.push(v); } } - return this.cmdlist.set("data", result); - } - } - - - selectCommand() { - const el = this.cmdlist.get("selectedItem"); - if (!el) { return; } - el.set("selected", false); - let result = false; - if (this.handle) { result = this.handle({ data: { item: el } }); } - if (!result) { return this.quit(); } - } -} - -CommandPalette.scheme = `\ - - - - - -\ -`; \ No newline at end of file diff --git a/src/packages/CodePad/ExtensionMaker.ts b/src/packages/CodePad/ExtensionMaker.ts new file mode 100644 index 0000000..7097eb6 --- /dev/null +++ b/src/packages/CodePad/ExtensionMaker.ts @@ -0,0 +1,492 @@ +namespace OS { + // import the CodePad application module + const App = OS.application.CodePad; + + declare var CoffeeScript: any; + declare var JSZip: any; + /** + * + * + * @class ExtensionMaker + * @extends {App.BaseExtension} + */ + class ExtensionMaker extends App.BaseExtension { + constructor(app: application.CodePad) { + super(app); + } + + // public functions + /** + * + * + * @memberof ExtensionMaker + */ + create(): void { + this.app + .openDialog("FileDialog", { + title: "__(New CodePad extension at)", + file: { basename: __("ExtensionName") }, + mimes: ["dir"], + }) + .then((d) => { + return this.mktpl(d.file.path, d.name); + }); + } + + /** + * + * + * @memberof ExtensionMaker + */ + buildnrun(): void { + this.metadata("extension.json") + .then(async (meta) => { + try { + await this.build(meta); + try { + return this.run(meta); + } catch (e) { + return this.error(__("Unable to run extension"), e); + } + } catch (e_1) { + return this.error(__("Unable to build extension"), e_1); + } + }) + .catch((e) => this.error(__("Unable to read meta-data"), e)); + } + + /** + * + * + * @memberof ExtensionMaker + */ + release(): void { + this.metadata("extension.json") + .then(async (meta) => { + try { + await this.build(meta); + try { + return this.mkar( + `${meta.root}/build/debug`, + `${meta.root}/build/release/${meta.meta.name}.zip` + ); + } catch (e) { + return this.error( + __("Unable to create archive"), + e + ); + } + } catch (e_1) { + return this.error(__("Unable to build extension"), e_1); + } + }) + .catch((e) => this.error(__("Unable to read meta-data"), e)); + } + + /** + * + * + * @memberof ExtensionMaker + */ + install(): void { + this.app + .openDialog("FileDialog", { + title: "__(Select extension archive)", + mimes: [".*/zip"], + }) + .then(async (d) => { + try { + await this.installZip(d.file.path); + this.notify(__("Extension installed")); + return this.app.loadExtensionMetaData(); + } catch (e) { + return this.error(__("Unable to install extension"), e); + } + }); + } + + /** + * + * + * @private + * @param {string} path + * @param {string} name + * @memberof ExtensionMaker + */ + private mktpl(path: string, name: string): void { + const rpath = `${path}/${name}`; + const dirs = [ + rpath, + `${rpath}/build`, + `${rpath}/build/release`, + `${rpath}/build/debug`, + ]; + const files = [ + ["templates/ext-main.tpl", `${rpath}/${name}.coffee`], + ["templates/ext-extension.tpl", `${rpath}/extension.json`], + ]; + this.mkdirAll(dirs) + .then(async () => { + try { + await this.mkfileAll(files, path, name); + this.app.currdir = rpath.asFileHandle(); + this.app.initSideBar(); + return this.app.openFile( + `${rpath}/${name}.coffee`.asFileHandle() as application.CodePadFileHandle + ); + } catch (e) { + return this.error( + __("Unable to create extension template"), + e + ); + } + }) + .catch((e) => + this.error(__("Unable to create extension directories"), e) + ); + } + + /** + * + * + * @private + * @param {string[]} list + * @returns {Promise} + * @memberof ExtensionMaker + */ + private verify(list: string[]): Promise { + return new Promise((resolve, reject) => { + if (list.length === 0) { + return resolve(); + } + const file = list.splice(0, 1)[0].asFileHandle(); + this.notify(__("Verifying: {0}", file.path)); + return file + .read() + .then((data) => { + try { + CoffeeScript.nodes(data); + return this.verify(list) + .then(() => resolve()) + .catch((e) => reject(__e(e))); + } catch (ex) { + return reject(__e(ex)); + } + }) + .catch((e) => reject(__e(e))); + }); + } + + /** + * + * + * @private + * @param {GenericObject} meta + * @returns {Promise} + * @memberof ExtensionMaker + */ + private compile(meta: GenericObject): Promise { + return new Promise(async (resolve, reject) => { + try { + await this.import([`${this.basedir()}/libs/coffeescript.js`]); + const list = meta.coffees.map( + (v) => `${meta.root}/${v}` + ); + try { + await this.verify(list.map((x: string) =>x)); + try { + const code = await this.cat(list, ""); + const jsrc = CoffeeScript.compile(code); + this.notify(__("Compiled successful")); + return resolve(jsrc); + } catch (e) { + return reject(__e(e)); + } + } catch (e_1) { + return reject(__e(e_1)); + } + } catch (e_2) { + return reject(__e(e_2)); + } + }); + } + + /** + * + * + * @private + * @param {GenericObject} meta + * @returns {Promise} + * @memberof ExtensionMaker + */ + private build(meta: GenericObject): Promise { + return new Promise(async (resolve, reject) => { + try { + const src = await this.compile(meta); + let v: string; + try { + const jsrc = await this.cat( + (() => { + const result = []; + for (v of meta.javascripts) { + result.push(`${meta.root}/${v}`); + } + return result; + })(), + src + ); + await new Promise((r, e) => + `${meta.root}/build/debug/${meta.meta.name}.js` + .asFileHandle() + .setCache(jsrc) + .write("text/plain") + .then((d) => r()) + .catch((ex) => e(__e(ex))) + ); + await new Promise((r, e) => + `${meta.root}/build/debug/extension.json` + .asFileHandle() + .setCache(meta.meta) + .write("object") + .then((data) => r(data)) + .catch((ex_1) => e(__e(ex_1))) + ); + await this.copy( + (() => { + const result1 = []; + for (v of meta.copies) { + result1.push(`${meta.root}/${v}`); + } + return result1; + })(), + `${meta.root}/build/debug` + ); + return resolve(); + } catch (e) { + return reject(__e(e)); + } + } catch (e_1) { + return reject(__e(e_1)); + } + }); + } + + /** + * + * + * @private + * @param {GenericObject} meta + * @returns {Promise} + * @memberof ExtensionMaker + */ + private run(meta: GenericObject): Promise { + return new Promise(async (resolve, reject) => { + const path = `${meta.root}/build/debug/${meta.meta.name}.js`; + if (this.app._api.shared[path]) { + delete this.app._api.shared[path]; + } + try { + await this.app._api.requires(path); + let v: GenericObject; + if (this.app.extensions[meta.meta.name]) { + this.app.extensions[meta.meta.name].child = []; + for (v of meta.meta.actions) { + this.app.extensions[meta.meta.name].addAction(v); + } + } else { + this.app.extensions[meta.meta.name] = new App.CMDMenu( + meta.meta.text + ); + this.app.extensions[meta.meta.name].name = + meta.meta.name; + for (v of meta.meta.actions) { + this.app.extensions[meta.meta.name].addAction(v); + } + this.app.spotlight.addAction( + this.app.extensions[meta.meta.name] + ); + this.app.extensions[meta.meta.name].onchildselect( + (e: GUI.TagEventType) => { + return this.app.loadAndRunExtensionAction( + e.data.item.data + ); + } + ); + } + this.app.spotlight.run(this.app); + return resolve(); + } catch (e) { + return reject(__e(e)); + } + }); + } + + /** + * + * + * @private + * @param {string[]} files + * @param {*} zip + * @returns {Promise} + * @memberof ExtensionMaker + */ + private installExtension(files: string[], zip: any): Promise { + return new Promise((resolve, reject) => { + const idx = files.indexOf("extension.json"); + if (idx < 0) { + reject(this.app._api.throwe(__("No meta-data found"))); + } + const metafile = files.splice(idx, 1)[0]; + // read the meta file + return zip + .file(metafile) + .async("uint8array") + .then((d: Uint8Array) => { + const meta = JSON.parse( + new TextDecoder("utf-8").decode(d) + ); + return this.installFiles(files, zip, meta) + .then(() => resolve()) + .catch((e) => reject(__e(e))); + }) + .catch((e: Error) => reject(__e(e))); + }); + } + + /** + * + * + * @private + * @param {string[]} files + * @param {*} zip + * @param {GenericObject} meta + * @returns {Promise} + * @memberof ExtensionMaker + */ + private installFiles( + files: string[], + zip: any, + meta: GenericObject + ): Promise { + if (files.length === 0) { + return this.installMeta(meta); + } + return new Promise((resolve, reject) => { + const file = files.splice(0, 1)[0]; + const path = `${this.basedir()}/${file}`; + return zip + .file(file) + .async("uint8array") + .then((d: Uint8Array) => { + return path + .asFileHandle() + .setCache(new Blob([d], { type: "octet/stream" })) + .write("text/plain") + .then((r) => { + if (r.error) { + return reject(r.error); + } + return this.installFiles(files, zip, meta) + .then(() => resolve()) + .catch((e) => reject(__e(e))); + }) + .catch((e) => reject(__e(e))); + }) + .catch((e: Error) => reject(__e(e))); + }); + } + + /** + * + * + * @private + * @param {GenericObject} meta + * @returns {Promise} + * @memberof ExtensionMaker + */ + private installMeta(meta: GenericObject): Promise { + return new Promise(async (resolve, reject) => { + const file = `${ + this.app.meta().path + }/extensions.json`.asFileHandle(); + try { + const data = await file.read("json"); + const names = []; + for (let v of data) { + names.push(v.name); + } + const idx = names.indexOf(meta.name); + if (idx >= 0) { + data.splice(idx, 1); + } + data.push(meta); + try { + await file.setCache(data).write("object"); + return resolve(); + } catch (e) { + return reject(__e(e)); + } + } catch (e_1) { + return reject(__e(e_1)); + } + }); + } + + /** + * + * + * @private + * @param {string} path + * @returns {Promise} + * @memberof ExtensionMaker + */ + private installZip(path: string): Promise { + return new Promise((resolve, reject) => { + this.import(["os://scripts/jszip.min.js"]) + .then(() => { + path.asFileHandle() + .read("binary") + .then((data) => { + JSZip.loadAsync(data) + .then((zip: any) => { + const pth = this.basedir(); + const dir = []; + const files = []; + for (let name in zip.files) { + const file = zip.files[name]; + if (file.dir) { + dir.push(pth + "/" + name); + } else { + files.push(name); + } + } + if (dir.length > 0) { + this.mkdirAll(dir) + .then(() => { + this.installExtension( + files, + zip + ) + .then(() => resolve()) + .catch((e) => + reject(__e(e)) + ); + }) + .catch((e) => reject(__e(e))); + } else { + this.installExtension(files, zip) + .then(() => resolve()) + .catch((e) => reject(__e(e))); + } + }) + .catch((e: Error) => reject(__e(e))); + }) + .catch((e) => reject(__e(e))); + }) + .catch((e) => reject(__e(e))); + }); + } + } + + App.extensions.ExtensionMaker = ExtensionMaker; +} diff --git a/src/packages/CodePad/Makefile b/src/packages/CodePad/Makefile index 261c66d..6c6ebad 100644 --- a/src/packages/CodePad/Makefile +++ b/src/packages/CodePad/Makefile @@ -1,10 +1,16 @@ -module_files = CommandPalette.js main.js BaseExtension.js +module_files = main.js BaseExtension.js libfiles = cssfiles = css/main.css -copyfiles = assets/scheme.html package.json extensions.json extensions +copyfiles = assets/scheme.html \ + package.json \ + extensions.json \ + libs \ + templates \ + $(DIST)/AntOSDK.js \ + $(DIST)/ExtensionMaker.js PKG_NAME=CodePad diff --git a/src/packages/CodePad/extensions/AntOSDK.js b/src/packages/CodePad/extensions/AntOSDK.js deleted file mode 100644 index e6f4a18..0000000 --- a/src/packages/CodePad/extensions/AntOSDK.js +++ /dev/null @@ -1,222 +0,0 @@ -(function() { -/* - * decaffeinate suggestions: - * DS101: Remove unnecessary use of Array.from - * DS102: Remove unnecessary code created because of implicit returns - * DS205: Consider reworking code to avoid use of IIFEs - * DS208: Avoid top-level this - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ -// import the CodePad application module -const App = this.OS.APP.CodePad; - -// define the extension -App.extensions.AntOSDK = class AntOSDK extends App.BaseExtension { - constructor(app) { - super(app); - } - - // public functions - create() { - return this.app.openDialog("FileDialog", { - title: "__(New Project at)", - file: { basename: __("ProjectName") }, - mimes: ["dir"] - }).then(d => { - return this.mktpl(d.file.path, d.name, true); - }); - } - - init() { - const dir = this.app.currdir; - if (!dir || !dir.basename) { return this.create(); } - return dir.read() - .then(d => { - if (d.error) { return this.notify(__("Cannot read folder: {0}", dir.path)); } - if (d.result.length !== 0) { return this.notify(__("The folder is not empty: {0}", dir.path)); } - return this.mktpl(dir.parent().path, dir.basename); - }); - } - - buildnrun() { - return this.metadata("project.json").then(meta => { - return this.build(meta, true).then(() => { - return this.run(meta).catch(e => this.error(__("Unable to run project"), e)); - }).catch(e => { - return this.error(__("Unable to build project"), e); - }); - }).catch(e => this.error(__("Unable to read meta-data"), e)); - } - - release() { - return this.metadata("project.json").then(meta => { - return this.build(meta, false).then(() => { - return this.mkar(`${meta.root}/build/debug`, `${meta.root}/build/release/${meta.name}.zip`) - .catch(e => this.error(__("Unable to create package archive"), e)); - }).catch(e => {}, - this.error(__("Unable to build project"), e) - ); - }).catch(e => this.error(__("Unable to read meta-data"), e)); - } - - - // private functions - mktpl(path, name, flag) { - const rpath = `${path}/${name}`; - const dirs = [ - `${rpath}/javascripts`, - `${rpath}/css`, - `${rpath}/coffees`, - `${rpath}/assets` - ]; - if (flag) { dirs.unshift(rpath); } - const files = [ - ["templates/sdk-main.tpl", `${rpath}/coffees/main.coffee`], - ["templates/sdk-package.tpl", `${rpath}/package.json`], - ["templates/sdk-project.tpl", `${rpath}/project.json`], - ["templates/sdk-README.tpl", `${rpath}/README.md`], - ["templates/sdk-scheme.tpl", `${rpath}/assets/scheme.html`] - ]; - return this.mkdirAll(dirs) - .then(() => { - return this.mkfileAll(files, path, name) - .then(() => { - this.app.currdir = rpath.asFileHandle(); - this.app.initSideBar(); - return this.app.openFile(`${rpath}/README.md`.asFileHandle()); - }).catch(e => this.error(__("Unable to create template files"), e)); - }).catch(e => this.error(__("Unable to create project directory"), e)); - } - - verify(list) { - return new Promise((resolve, reject) => { - if (list.length === 0) { return resolve(); } - const file = (list.splice(0, 1))[0].asFileHandle(); - this.notify(__("Verifying: {0}", file.path)); - return file.read().then(data => { - try { - CoffeeScript.nodes(data); - return this.verify(list) - .then(() => resolve()) - .catch(e => reject(__e(e))); - } catch (ex) { - return reject(__e(ex)); - } - }).catch(e => reject(__e(e))); - }); - } - - compile(meta) { - return new Promise((resolve, reject) => { - return this.import([ - `${this.basedir()}/coffeescript.js`, - `${this.basedir()}/terser.min.js` - ]).then(() => { - const list = (Array.from(meta.coffees).map((v) => `${meta.root}/${v}`)); - return this.verify((Array.from(list))).then(() => { - return this.cat(list).then(code => { - const jsrc = CoffeeScript.compile(code); - this.notify(__("Compiled successful")); - return resolve(jsrc); - }).catch(e => reject(__e(e))); - }).catch(e => reject(__e(e))); - }).catch(e => reject(__e(e))); - }); - } - - build(meta, debug) { - const dirs = [ - `${meta.root}/build`, - `${meta.root}/build/debug`, - `${meta.root}/build/release` - ]; - return new Promise((resolve, reject) => { - return this.mkdirAll(dirs).then(() => { - return this.compile(meta).then(src => { - let v; - return this.cat(((() => { - const result = []; - for (v of Array.from(meta.javascripts)) { result.push(`${meta.root}/${v}`); - } - return result; - })()), src) - .then(jsrc => new Promise(function(r, e) { - let code = jsrc; - if (!debug) { - const options = { - toplevel: true, - compress: { - passes: 3, - //pure_getters: true, - //unsafe: true, - }, - mangle: true, - output: { - //beautify: true, - }, - }; - const result = Terser.minify(jsrc, options); - if (result.error) { - this.notify(__("Unable to minify code: {0}", result.error)); - } else { - ({ - code - } = result); - } - } - return `${meta.root}/build/debug/main.js` - .asFileHandle() - .setCache(code) - .write("text/plain") - .then(d => r()).catch(ex => e(__e(ex))); - })).then(() => { - return new Promise((r, e) => { - return this.cat(((() => { - const result1 = []; - for (v of Array.from(meta.css)) { result1.push(`${meta.root}/${v}`); - } - return result1; - })()), "") - .then(function(txt) { - if (txt === "") { return r(); } - return `${meta.root}/build/debug/main.css` - .asFileHandle() - .setCache(txt) - .write("text/plain") - .then(d => r()).catch(ex => e(__e(ex))); - }); - }); - }).then(() => { - return this.copy(((() => { - const result1 = []; - for (v of Array.from(meta.copies)) { result1.push(`${meta.root}/${v}`); - } - return result1; - })()), `${meta.root}/build/debug`); - }).then(() => resolve()) - .catch(e => reject(__e(e))); - }).catch(e => reject(__e(e))); - }).catch(e => reject(__e(e))); - }); - } - - run(meta) { - return `${meta.root}/build/debug/package.json` - .asFileHandle() - .read("json") - .then(v => { - v.text = v.name; - v.path = `${meta.root}/build/debug`; - v.filename = meta.name; - v.type = "app"; - v.mime = "antos/app"; - if (v.icon) { v.icon = `${v.path}/${v.icon}`; } - if (!v.iconclass && !v.icon) { v.iconclass = "fa fa-adn"; } - this.notify(__("Installing...")); - this.app.systemsetting.system.packages[meta.name] = v; - this.notify(__("Running {0}...", meta.name)); - return this.app._gui.forceLaunch(meta.name); - }); - } -}; -}).call(this); \ No newline at end of file diff --git a/src/packages/CodePad/extensions/ExtensionMaker.js b/src/packages/CodePad/extensions/ExtensionMaker.js deleted file mode 100644 index d41ee1c..0000000 --- a/src/packages/CodePad/extensions/ExtensionMaker.js +++ /dev/null @@ -1,261 +0,0 @@ -(function() { -/* - * decaffeinate suggestions: - * DS101: Remove unnecessary use of Array.from - * DS102: Remove unnecessary code created because of implicit returns - * DS205: Consider reworking code to avoid use of IIFEs - * DS208: Avoid top-level this - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ -// import the CodePad application module -const App = this.OS.APP.CodePad; - -// define the extension -App.extensions.ExtensionMaker = class ExtensionMaker extends App.BaseExtension { - constructor(app) { - super(app); - } - - // public functions - create() { - return this.app.openDialog("FileDialog", { - title: "__(New CodePad extension at)", - file: { basename: __("ExtensionName") }, - mimes: ["dir"] - }).then(d => { - return this.mktpl(d.file.path, d.name); - }); - } - - buildnrun() { - return this.metadata("extension.json").then(meta => { - return this.build(meta).then(() => { - return this.run(meta).catch(e => this.error(__("Unable to run extension"), e)); - }).catch(e => { - return this.error(__("Unable to build extension"), e); - }); - }).catch(e => this.error(__("Unable to read meta-data"), e)); - } - - release() { - return this.metadata("extension.json").then(meta => { - return this.build(meta).then(() => { - return this.mkar(`${meta.root}/build/debug`, - `${meta.root}/build/release/${meta.meta.name}.zip`) - .catch(e => this.error(__("Unable to create archive"), e)); - }).catch(e => {}, - this.error(__("Unable to build extension"), e) - ); - }).catch(e => this.error(__("Unable to read meta-data"), e)); - } - - install() { - return this.app.openDialog("FileDialog", { - title: "__(Select extension archive)", - mimes: [".*/zip"] - }).then(d => { - return this.installZip(d.file.path) - .then(() => { - this.notify(__("Extension installed")); - return this.app.loadExtensionMetaData(); - }).catch(e => this.error(__("Unable to install extension"), e)); - }); - } - // private functions - mktpl(path, name) { - const rpath = `${path}/${name}`; - const dirs = [ - rpath, - `${rpath}/build`, - `${rpath}/build/release`, - `${rpath}/build/debug` - ]; - const files = [ - ["templates/ext-main.tpl", `${rpath}/${name}.coffee`], - ["templates/ext-extension.tpl", `${rpath}/extension.json`], - ]; - return this.mkdirAll(dirs) - .then(() => { - return this.mkfileAll(files, path, name) - .then(() => { - this.app.currdir = rpath.asFileHandle(); - this.app.initSideBar(); - return this.app.openFile(`${rpath}/${name}.coffee`.asFileHandle()); - }).catch(e => this.error(__("Unable to create extension template"), e)); - }).catch(e => this.error(__("Unable to create extension directories"), e)); - } - - - verify(list) { - return new Promise((resolve, reject) => { - if (list.length === 0) { return resolve(); } - const file = (list.splice(0, 1))[0].asFileHandle(); - this.notify(__("Verifying: {0}", file.path)); - return file.read().then(data => { - try { - CoffeeScript.nodes(data); - return this.verify(list) - .then(() => resolve()) - .catch(e => reject(__e(e))); - } catch (ex) { - return reject(__e(ex)); - } - }).catch(e => reject(__e(e))); - }); - } - - compile(meta) { - return new Promise((resolve, reject) => { - return this.import([`${this.basedir()}/coffeescript.js`]).then(() => { - const list = (Array.from(meta.coffees).map((v) => `${meta.root}/${v}`)); - return this.verify((Array.from(list))).then(() => { - return this.cat(list).then(code => { - const jsrc = CoffeeScript.compile(code); - this.notify(__("Compiled successful")); - return resolve(jsrc); - }).catch(e => reject(__e(e))); - }).catch(e => reject(__e(e))); - }).catch(e => reject(__e(e))); - }); - } - - build(meta) { - return new Promise((resolve, reject) => { - return this.compile(meta).then(src => { - let v; - return this.cat(((() => { - const result = []; - for (v of Array.from(meta.javascripts)) { result.push(`${meta.root}/${v}`); - } - return result; - })()), src) - .then(jsrc => new Promise((r, e) => `${meta.root}/build/debug/${meta.meta.name}.js` - .asFileHandle() - .setCache(jsrc) - .write("text/plain") - .then(d => r()).catch(ex => e(__e(ex))))).then(() => new Promise((r, e) => `${meta.root}/build/debug/extension.json` - .asFileHandle() - .setCache(meta.meta) - .write("object") - .then(data => r(data)).catch(ex => e(__e(ex))))).then(() => { - return this.copy(((() => { - const result1 = []; - for (v of Array.from(meta.copies)) { result1.push(`${meta.root}/${v}`); - } - return result1; - })()), `${meta.root}/build/debug`); - }).then(() => resolve()) - .catch(e => reject(__e(e))); - }).catch(e => reject(__e(e))); - }); - } - - run(meta) { - return new Promise((resolve, reject) => { - const path = `${meta.root}/build/debug/${meta.meta.name}.js`; - if (this.app._api.shared[path]) { delete this.app._api.shared[path]; } - return this.app._api.requires(path) - .then(() => { - let v; - if (this.app.extensions[meta.meta.name]) { - this.app.extensions[meta.meta.name].child = []; - for (v of Array.from(meta.meta.actions)) { this.app.extensions[meta.meta.name].addAction(v); } - } else { - this.app.extensions[meta.meta.name] = new App.CMDMenu(meta.meta.text); - this.app.extensions[meta.meta.name].name = meta.meta.name; - for (v of Array.from(meta.meta.actions)) { this.app.extensions[meta.meta.name].addAction(v); } - this.app.spotlight.addAction(this.app.extensions[meta.meta.name]); - this.app.extensions[meta.meta.name].onchildselect(e => { - return this.app.loadAndRunExtensionAction(e.data.item.get("data")); - }); - } - this.app.spotlight.run(this.app); - return resolve(); - }).catch(e => reject(__e(e))); - }); - } - - - installExtension(files, zip) { - return new Promise((resolve, reject) => { - const idx = files.indexOf("extension.json"); - if (idx < 0) { reject(this.app._api.throwe(__("No meta-data found"))); } - const metafile = (files.splice(idx, 1))[0]; - // read the meta file - return zip.file(metafile).async("uint8array").then(d => { - const meta = JSON.parse(new TextDecoder("utf-8").decode(d)); - return this.installFiles(files, zip, meta) - .then(() => resolve()) - .catch(e => reject(__e(e))); - }).catch(e => reject(__e(e))); - }); - } - - installFiles(files, zip, meta) { - if (files.length === 0) { return this.installMeta(meta); } - return new Promise((resolve, reject) => { - const file = (files.splice(0, 1))[0]; - const path = `${this.basedir()}/${file}`; - return zip.file(file).async("uint8array").then(d => { - return path.asFileHandle() - .setCache(new Blob([d], { type: "octet/stream" })) - .write("text/plain").then(r => { - if (r.error) { return reject(r.error); } - return this.installFiles(files, zip, meta) - .then(() => resolve()) - .catch(e => reject(__e(e))); - }).catch(e => reject(__e(e))); - }).catch(e => reject(__e(e))); - }); - } - - installMeta(meta) { - return new Promise((resolve, reject) => { - const file = `${this.app.meta().path}/extensions.json`.asFileHandle(); - return file.read("json").then(function(data) { - for (let v of Array.from(data)) { const names = (v.name); } - const idx = name.indexOf(meta.name); - if (idx >= 0) { data.splice(idx, 1); } - data.push(meta); - return file.setCache(data) - .write("object") - .then(() => resolve()) - .catch(e => reject(__e(e)));}).catch(e => reject(__e(e))); - }); - } - - installZip(path) { - return new Promise((resolve, reject) => { - return this.import(["os://scripts/jszip.min.js"]).then(() => { - return path.asFileHandle().read("binary").then(data => { - return JSZip.loadAsync(data).then(zip => { - const pth = this.basedir(); - const dir = []; - const files = []; - for (let name in zip.files) { - const file = zip.files[name]; - if (file.dir) { - dir.push(pth + "/" + name); - } else { - files.push(name); - } - } - if (dir.length > 0) { - return this.mkdirAll(dir) - .then(() => { - return this.installExtension(files, zip) - .then(() => resolve()) - .catch(e)(() => reject(__e(e))); - }).catch(e => reject(__e(e))); - } else { - return this.installExtension(files, zip) - .then(() => resolve()) - .catch(e => reject(__e(e))); - } - }).catch(e => reject(__e(e))); - }).catch(e => reject(__e(e))); - }).catch(e => reject(__e(e))); - }); - } -}; -}).call(this); \ No newline at end of file diff --git a/src/packages/CodePad/extensions/coffeescript.js b/src/packages/CodePad/libs/coffeescript.js similarity index 100% rename from src/packages/CodePad/extensions/coffeescript.js rename to src/packages/CodePad/libs/coffeescript.js diff --git a/src/packages/CodePad/extensions/terser.min.js b/src/packages/CodePad/libs/terser.min.js similarity index 100% rename from src/packages/CodePad/extensions/terser.min.js rename to src/packages/CodePad/libs/terser.min.js diff --git a/src/packages/CodePad/main.js b/src/packages/CodePad/main.js deleted file mode 100644 index 902fde7..0000000 --- a/src/packages/CodePad/main.js +++ /dev/null @@ -1,628 +0,0 @@ -/* - * decaffeinate suggestions: - * DS101: Remove unnecessary use of Array.from - * DS102: Remove unnecessary code created because of implicit returns - * DS205: Consider reworking code to avoid use of IIFEs - * DS208: Avoid top-level this - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ -const Ant = this; - -class CodePad extends this.OS.GUI.BaseApplication { - constructor(args) { - super("CodePad", args); - this.currfile = "Untitled".asFileHandle(); - this.currdir = undefined; - if (this.args && (this.args.length > 0)) { - if (this.args[0].type === "dir") { - this.currdir = this.args[0].path.asFileHandle(); - } else { - this.currfile = this.args[0].path.asFileHandle(); - this.currdir = this.currfile.parent(); - } - } - } - - main() { - this.extensions = {}; - this.fileview = this.find("fileview"); - this.sidebar = this.find("sidebar"); - this.tabbar = this.find("tabbar"); - this.langstat = this.find("langstat"); - this.editorstat = this.find("editorstat"); - - this.fileview.set("fetch", path => new Promise(function(resolve, reject) { - let dir = path; - if (typeof path === "string") { dir = path.asFileHandle(); } - return dir.read().then(function(d) { - if (d.error) { return reject(d.error); } - return resolve(d.result);}).catch(e => reject(__e(e))); - })); - return this.setup(); - } - - setup() { - ace.config.set('basePath', '/scripts/ace'); - ace.require("ace/ext/language_tools"); - this.editor = ace.edit(this.find("datarea")); - this.editor.setOptions({ - enableBasicAutocompletion: true, - enableSnippets: true, - enableLiveAutocompletion: true, - highlightActiveLine: true, - highlightSelectedWord: true, - behavioursEnabled: true, - wrap: true, - fontSize: "11pt", - showInvisibles: true - }); - //themes = ace.require "ace/ext/themelist" - this.editor.setTheme("ace/theme/monokai"); - this.modes = ace.require("ace/ext/modelist"); - this.editor.completers.push({ getCompletions( editor, session, pos, prefix, callback ) {} }); - this.editor.getSession().setUseWrapMode(true); - this.editormux = false; - this.editor.on("input", () => { - if (this.editormux) { - this.editormux = false; - return false; - } - if (!this.currfile.dirty) { - this.currfile.dirty = true; - this.currfile.text += "*"; - return this.tabbar.update(); - } - }); - this.editor.getSession().selection.on("changeCursor", e => { - return this.updateStatus(); - }); - - this.tabbar.set("ontabselect", e => { - return this.selecteTab($(e.data.item).index()); - }); - this.tabbar.set("ontabclose", e => { - const it = e.data.item; - if (!it) { return false; } - if (!it.get("data").dirty) { return this.closeTab(it); } - this.openDialog("YesNoDialog", { - title: __("Close tab"), - text: __("Close without saving ?") - }).then(d => { - if (d) { return this.closeTab(it); } - return this.editor.focus(); - }); - return false; - }); - this.fileview.set("onfileopen", e => { - if (!e.data || !e.data.path) { return; } - if (e.data.type === "dir") { return; } - return this.openFile(e.data.path.asFileHandle()); - }); - - this.fileview.set("onfileselect", e => { - if (!e.data || !e.data.path) { return; } - if (e.data.type === "dir") { return; } - const i = this.findTabByFile(e.data.path.asFileHandle()); - if (i !== -1) { return this.tabbar.set("selected", i); } - }); - - this.on("resize", () => this.editor.resize()); - this.on("focus", () => this.editor.focus()); - this.spotlight = new CMDMenu(__("Command palette")); - this.bindKey("ALT-P", () => this.spotlight.run(this)); - this.find("datarea").contextmenuHandle = (e, m) => { - m.set("items", [{ - text: __("Command palete"), - onmenuselect: e => { - return this.spotlight.run(this); - } - }]); - return m.show(e); - }; - - this.fileview.contextmenuHandle = (e, m) => { - m.set("items", [ - { text: "__(New file)", id: "new" }, - { text: "__(New folder)", id: "newdir" }, - { text: "__(Rename)", id: "rename" }, - { text: "__(Delete)", id: "delete" } - ]); - m.set("onmenuselect", e => { - return this.ctxFileMenuHandle(e); - }); - return m.show(e); - }; - - this.bindKey("ALT-N", () => this.menuAction("new")); - this.bindKey("ALT-O", () => this.menuAction("open")); - this.bindKey("ALT-F", () => this.menuAction("opendir")); - this.bindKey("CTRL-S", () => this.menuAction("save")); - this.bindKey("ALT-W", () => this.menuAction("saveas")); - - this.fileview.set("ondragndrop", e => { - const src = e.data.from.get("data").path.asFileHandle(); - const des = e.data.to.get("data").path; - return src.move(`${des}/${src.basename}`) - .then(function(d) { - e.data.to.update(des); - return e.data.from.get("parent").update(src.parent().path);}).catch(e => this.error(__("Unable to move file/folder"), e)); - }); - - this.on("filechange", data => { - let { - path - } = data.file; - if (data.type === "file") { ({ - path - } = data.file.parent()); } - return this.fileview.update(path); - }); - - - this.loadExtensionMetaData(); - this.initCommandPalete(); - this.initSideBar(); - return this.openFile(this.currfile); - } - - openFile(file) { - //find tab - const i = this.findTabByFile(file); - if (i !== -1) { return this.tabbar.set("selected", i); } - if (file.path.toString() === "Untitled") { return this.newTab(file); } - - return file.read() - .then(d => { - file.cache = d || ""; - return this.newTab(file); - }).catch(e => { - return this.error(__("Unable to open: {0}", file.path), e); - }); - } - - findTabByFile(file) { - const lst = this.tabbar.get("items"); - const its = ((() => { - const result = []; - for (let i = 0; i < lst.length; i++) { - const d = lst[i]; - if (d.hash() === file.hash()) { - result.push(i); - } - } - return result; - })()); - if (its.length === 0) { return -1; } - return its[0]; - } - - newTab(file) { - file.text = file.basename ? file.basename : file.path; - if (!file.cache) { file.cache = ""; } - file.um = new ace.UndoManager(); - this.currfile.selected = false; - file.selected = true; - //console.log cnt - return this.tabbar.push(file); - } - - closeTab(it) { - this.tabbar.remove(it); - const cnt = this.tabbar.get("items").length; - - if (cnt === 0) { - this.openFile("Untitled".asFileHandle()); - return false; - } - this.tabbar.set("selected", cnt - 1); - return false; - } - - selecteTab(i) { - //return if i is @tabbar.get "selidx" - const file = (this.tabbar.get("items"))[i]; - if (!file) { return; } - this.scheme.set("apptitle", file.text.toString()); - //return if file is @currfile - if (this.currfile !== file) { - this.currfile.cache = this.editor.getValue(); - this.currfile.cursor = this.editor.selection.getCursor(); - this.currfile.selected = false; - this.currfile = file; - } - - if (!file.langmode) { - if (file.path.toString() !== "Untitled") { - const m = this.modes.getModeForPath(file.path); - file.langmode = { caption: m.caption, mode: m.mode }; - } else { - file.langmode = { caption: "Text", mode: "ace/mode/text" }; - } - } - this.editormux = true; - this.editor.getSession().setUndoManager(new ace.UndoManager()); - this.editor.setValue(file.cache, -1); - this.editor.getSession().setMode(file.langmode.mode); - if (file.cursor) { - this.editor.renderer.scrollCursorIntoView({ - row: file.cursor.row, column: file.cursor.column - }, 0.5); - this.editor.selection.moveTo(file.cursor.row, file.cursor.column); - } - this.editor.getSession().setUndoManager(file.um); - this.updateStatus(); - return this.editor.focus(); - } - - updateStatus() { - const c = this.editor.session.selection.getCursor(); - const l = this.editor.session.getLength(); - this.editorstat.set("text", __("Row {0}, col {1}, lines: {2}", c.row + 1, c.column + 1, l)); - return this.langstat.set("text", this.currfile.langmode.caption); - } - - initSideBar() { - if (this.currdir) { - $(this.sidebar).show(); - this.fileview.set("path", this.currdir.path); - } else { - $(this.sidebar).hide(); - } - return this.trigger("resize"); - } - - addAction(action) { - this.spotlight.addAction(action); - return this; - } - - addActions(list) { - this.spotlight.addActions(list); - return this; - } - - initCommandPalete() { - let v; - const themes = ace.require("ace/ext/themelist"); - const cmdtheme = new CMDMenu(__("Change theme")); - for (let k in themes.themesByName) { v = themes.themesByName[k]; cmdtheme.addAction({ text: v.caption, theme: v.theme }); } - cmdtheme.onchildselect(function(d, r) { - const data = d.data.item.get("data"); - r.editor.setTheme(data.theme); - return r.editor.focus(); - }); - this.spotlight.addAction(cmdtheme); - const cmdmode = new CMDMenu(__("Change language mode")); - for (v of Array.from(this.modes.modes)) { cmdmode.addAction({ text: v.caption, mode: v.mode }); } - cmdmode.onchildselect(function(d, r) { - const data = d.data.item.get("data"); - r.editor.session.setMode(data.mode); - r.currfile.langmode = { caption: data.text, mode: data.mode }; - r.updateStatus(); - return r.editor.focus(); - }); - this.spotlight.addAction(cmdmode); - return this.addAction(CMDMenu.fromMenu(this.fileMenu())); - } - - loadExtensionMetaData() { - return `${this.meta().path}/extensions.json` - .asFileHandle() - .read("json") - .then(d => { - return (() => { - const result = []; - for (var ext of Array.from(d)) { - if (this.extensions[ext.name]) { - this.extensions[ext.name].child = []; - result.push((() => { - const result1 = []; - for (let v of Array.from(ext.actions)) { result1.push(this.extensions[ext.name].addAction(v)); - } - return result1; - })()); - } else { - this.extensions[ext.name] = new CMDMenu(ext.text); - this.extensions[ext.name].name = ext.name; - for (let v of Array.from(ext.actions)) { this.extensions[ext.name].addAction(v); } - this.spotlight.addAction(this.extensions[ext.name]); - result.push(this.extensions[ext.name].onchildselect(e => { - return this.loadAndRunExtensionAction(e.data.item.get("data")); - })); - } - } - return result; - })(); - }).catch(e => { - return this.error(__("Cannot load extension meta data"), e); - }); - } - - runExtensionAction(name, action) { - if (!CodePad.extensions[name]) { return this.error(__("Unable to find extension: {0}", name)); } - const ext = new (CodePad.extensions[name])(this); - if (!ext[action]) { return this.error(__("Unable to find action: {0}", action)); } - return ext.preload() - .then(() => ext[action]()).catch(e => { - return this.error(__("Unable to preload extension"), e); - }); - } - - loadAndRunExtensionAction(data) { - const { - name - } = data.parent; - const action = data.name; - //verify if the extension is load - if (!CodePad.extensions[name]) { - //load the extension - const path = `${this.meta().path}/extensions/${name}.js`; - return this._api.requires(path) - .then(() => this.runExtensionAction(name, action)) - .catch(e => { - return this.error(__("unable to load extension: {0}", name), e); - }); - } else { - return this.runExtensionAction(name, action); - } - } - - fileMenu() { - return { - text: __("File"), - child: [ - { text: __("New"), dataid: "new", shortcut: "A-N" }, - { text: __("Open"), dataid: "open", shortcut: "A-O" }, - { text: __("Open Folder"), dataid: "opendir", shortcut: "A-F" }, - { text: __("Save"), dataid: "save", shortcut: "C-S" }, - { text: __("Save as"), dataid: "saveas", shortcut: "A-W" } - ], - onchildselect: (e, r) => { - return this.menuAction(e.data.item.get("data").dataid, r); - } - }; - } - - ctxFileMenuHandle(e) { - const el = e.data.item; - if (!el) { return; } - const data = el.get("data"); - if (!data) { return; } - let file = this.fileview.get("selectedFile"); - let dir = this.currdir; - if (file && (file.type === "dir")) { dir = file.path.asFileHandle(); } - if (file && (file.type === "file")) { dir = file.path.asFileHandle().parent(); } - - switch (data.id) { - case "new": - if (!dir) { return; } - return this.openDialog("PromptDialog", { - title: "__(New file)", - label: "__(File name)" - }) - .then(d => { - const fp = `${dir.path}/${d}`.asFileHandle(); - return fp.write("text/plain") - .then(r => { - return this.fileview.update(dir.path); - }).catch(e => { - return this.error(__("Fail to create: {0}", e.stack), e); - }); - }); - - case "newdir": - if (!dir) { return; } - return this.openDialog("PromptDialog", { - title: "__(New folder)", - label: "__(Folder name)" - }) - .then(d => { - return dir.mk(d) - .then(r => { - return this.fileview.update(dir.path); - }).catch(e => { - return this.error(__("Fail to create: {0}", dir.path), e); - }); - }); - - case "rename": - if (!file) { return; } - return this.openDialog("PromptDialog", { - title: "__(Rename)", - label: "__(File name)", - value: file.filename - }) - .then(d => { - if (d === file.filename) { return; } - file = file.path.asFileHandle(); - dir = file.parent(); - return file.move(`${dir.path}/${d}`) - .then(r => { - return this.fileview.update(dir.path); - }).catch(e => { - return this.error(__("Fail to rename: {0}", file.path), e); - }); - }); - - case "delete": - if (!file) { return; } - return this.openDialog("YesNoDialog", { - title: "__(Delete)", - iconclass: "fa fa-question-circle", - text: __("Do you really want to delete: {0}?", file.filename) - }) - .then(d => { - if (!d) { return; } - file = file.path.asFileHandle(); - dir = file.parent(); - return file.remove() - .then(r => { - return this.fileview.update(dir.path); - }).catch(e => { - return this.error(__("Fail to delete: {0}", file.path), e); - }); - }); - - default: - } - } - - - save(file) { - return file.write("text/plain") - .then(d => { - file.dirty = false; - file.text = file.basename; - this.tabbar.update(); - return this.scheme.set("apptitle", `${this.currfile.basename}`); - }).catch(e => this.error(__("Unable to save file: {0}", file.path), e)); - } - - - saveAs() { - return this.openDialog("FileDialog", { - title: __("Save as"), - file: this.currfile - }) - .then(f => { - let d = f.file.path.asFileHandle(); - if (f.file.type === "file") { d = d.parent(); } - this.currfile.setPath(`${d.path}/${f.name}`); - return this.save(this.currfile); - }); - } - - menuAction(dataid, r) { - let me = this; - if (r) { me = r; } - switch (dataid) { - case "new": - return me.openFile("Untitled".asFileHandle()); - case "open": - return me.openDialog("FileDialog", { - title: __("Open file"), - mimes: (Array.from(me.meta().mimes).filter((v) => v !== "dir")) - }) - .then(f => me.openFile(f.file.path.asFileHandle())); - case "opendir": - return me.openDialog("FileDialog", { - title: __("Open folder"), - mimes: ["dir"] - }) - .then(function(f) { - me.currdir = f.file.path.asFileHandle(); - return me.initSideBar(); - }); - case "save": - me.currfile.cache = me.editor.getValue(); - if (me.currfile.basename) { return me.save(me.currfile); } - return me.saveAs(); - case "saveas": - me.currfile.cache = me.editor.getValue(); - return me.saveAs(); - default: - return console.log(dataid); - } - } - - cleanup(evt) { - let v; - const dirties = ((() => { - const result = []; - for (v of Array.from(this.tabbar.get("items"))) { if (v.dirty) { - result.push(v); - } - } - return result; - })()); - if (dirties.length === 0) { return; } - evt.preventDefault(); - return this.openDialog("YesNoDialog", { - title: "__(Quit)", - text: __("Ignore all unsaved files: {0} ?", ((() => { - const result1 = []; - for (v of Array.from(dirties)) { result1.push(v.filename()); - } - return result1; - })()).join(", ") ) - }).then(d => { - if (d) { - for (v of Array.from(dirties)) { v.dirty = false; } - return this.quit(); - } - }); - } - - menu() { - const menu = [ - this.fileMenu(), - { - text: "__(View)", - child: [ - { text: "__(Command Palette)", dataid: "cmdpalette", shortcut: "A-P" } - ], - onchildselect: (e, r) => { - return this.spotlight.run(this); - } - } - ]; - return menu; - } -} - -class CMDMenu { - constructor(text, shortcut) { - this.text = text; - this.shortcut = shortcut; - this.child = []; - this.parent = undefined; - this.select = function(e) {}; - } - - addAction(v) { - v.parent = this; - this.child.push(v); - return this; - } - - addActions(list) { - return Array.from(list).map((v) => this.addAction(v)); - } - - onchildselect(f) { - this.select = f; - return this; - } - - run(root) { - return root.openDialog(new CommandPalette(), this) - .then(d => { - const data = d.data.item.get("data"); - if (data.run) { return data.run(root); } - return this.select(d, root); - }); - } -} - -CMDMenu.fromMenu = function(mn) { - const m = new CMDMenu(mn.text, mn.shortcut); - m.onchildselect(mn.onchildselect); - for (let v of Array.from(mn.child)) { - if (v.child) { - m.addAction(CMDMenu.fromMenu(v)); - } else { - m.addAction(v); - } - } - return m; -}; - -CodePad.CMDMenu = CMDMenu; - -CodePad.dependencies = [ - "os://scripts/ace/ace.js", - "os://scripts/ace/ext-language_tools.js", - "os://scripts/ace/ext-modelist.js", - "os://scripts/ace/ext-themelist.js" -]; -this.OS.register("CodePad", CodePad); \ No newline at end of file diff --git a/src/packages/CodePad/main.ts b/src/packages/CodePad/main.ts new file mode 100644 index 0000000..9dae4a2 --- /dev/null +++ b/src/packages/CodePad/main.ts @@ -0,0 +1,1203 @@ +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS205: Consider reworking code to avoid use of IIFEs + * DS208: Avoid top-level this + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +var ace: any; +namespace OS { + export namespace application { + export type CodePadFileHandle = API.VFS.RemoteFileHandle & { + text: string; + um: GenericObject; + selected: boolean; + cursor: GenericObject; + langmode: GenericObject; + }; + type ActionType = CMDMenu | GenericObject; + + /** + * + * + * @export + * @class CodePad + * @extends {BaseApplication} + */ + export class CodePad extends BaseApplication { + private currfile: CodePadFileHandle; + currdir: API.VFS.BaseFileHandle; + extensions: GenericObject; + private fileview: GUI.tag.FileViewTag; + private sidebar: GUI.tag.VBoxTag; + private tabbar: GUI.tag.TabBarTag; + private langstat: GUI.tag.LabelTag; + private editorstat: GUI.tag.LabelTag; + private editor: GenericObject; + private modes: GenericObject; + private editormux: boolean; + spotlight: CMDMenu; + static extensions: GenericObject; + static CMDMenu: typeof CMDMenu; + static CommandPalette: typeof CommandPalette; + static BaseExtension: CodePadBaseExtension; + /** + *Creates an instance of CodePad. + * @param {AppArgumentsType[]} args + * @memberof CodePad + */ + constructor(args: AppArgumentsType[]) { + super("CodePad", args); + this.currfile = "Untitled".asFileHandle() as CodePadFileHandle; + this.currdir = undefined; + if (this.args && this.args.length > 0) { + if (this.args[0].type === "dir") { + this.currdir = this.args[0].path.asFileHandle() as CodePadFileHandle; + } else { + this.currfile = this.args[0].path.asFileHandle() as CodePadFileHandle; + this.currdir = this.currfile.parent(); + } + } + } + + /** + * + * + * @returns {void} + * @memberof CodePad + */ + main(): void { + this.extensions = {}; + this.fileview = this.find("fileview") as GUI.tag.FileViewTag; + this.sidebar = this.find("sidebar") as GUI.tag.VBoxTag; + this.tabbar = this.find("tabbar") as GUI.tag.TabBarTag; + this.langstat = this.find("langstat") as GUI.tag.LabelTag; + this.editorstat = this.find("editorstat") as GUI.tag.LabelTag; + + this.fileview.fetch = (path) => + new Promise(function (resolve, reject) { + let dir: API.VFS.BaseFileHandle; + if (typeof path === "string") { + dir = path.asFileHandle(); + } else { + dir = path; + } + return dir + .read() + .then(function (d) { + if (d.error) { + return reject(d.error); + } + return resolve(d.result); + }) + .catch((e) => reject(__e(e))); + }); + return this.setup(); + } + + /** + * + * + * @private + * @returns {void} + * @memberof CodePad + */ + private setup(): void { + ace.config.set("basePath", "/scripts/ace"); + ace.require("ace/ext/language_tools"); + this.editor = ace.edit(this.find("datarea")); + this.editor.setOptions({ + enableBasicAutocompletion: true, + enableSnippets: true, + enableLiveAutocompletion: true, + highlightActiveLine: true, + highlightSelectedWord: true, + behavioursEnabled: true, + wrap: true, + fontSize: "11pt", + showInvisibles: true, + }); + //themes = ace.require "ace/ext/themelist" + this.editor.setTheme("ace/theme/monokai"); + this.modes = ace.require("ace/ext/modelist"); + this.editor.completers.push({ + getCompletions( + editor: any, + session: any, + pos: any, + prefix: any, + callback: any + ) {}, + }); + this.editor.getSession().setUseWrapMode(true); + this.editormux = false; + this.editor.on("input", () => { + if (this.editormux) { + this.editormux = false; + return false; + } + if (!this.currfile.dirty) { + this.currfile.dirty = true; + this.currfile.text += "*"; + return this.tabbar.update(undefined); + } + }); + this.editor + .getSession() + .selection.on("changeCursor", (e: any) => { + return this.updateStatus(); + }); + + this.tabbar.ontabselect = (e) => { + return this.selecteTab($(e.data.item).index()); + }; + this.tabbar.ontabclose = (e) => { + const it = e.data.item; + if (!it) { + return false; + } + if (!it.data.dirty) { + return this.closeTab(it); + } + this.openDialog("YesNoDialog", { + title: __("Close tab"), + text: __("Close without saving ?"), + }).then((d) => { + if (d) { + return this.closeTab(it); + } + return this.editor.focus(); + }); + return false; + }; + this.fileview.onfileopen = (e) => { + if (!e.data || !e.data.path) { + return; + } + if (e.data.type === "dir") { + return; + } + return this.openFile(e.data.path.asFileHandle()); + }; + + this.fileview.onfileselect = (e) => { + if (!e.data || !e.data.path) { + return; + } + if (e.data.type === "dir") { + return; + } + const i = this.findTabByFile(e.data.path.asFileHandle()); + if (i !== -1) { + return (this.tabbar.selected = i); + } + }; + + this.on("resize", () => this.editor.resize()); + this.on("focus", () => this.editor.focus()); + this.spotlight = new CMDMenu(__("Command palette")); + this.bindKey("ALT-P", () => this.spotlight.run(this)); + this.find("datarea").contextmenuHandle = (e, m) => { + m.items = [ + { + text: __("Command palete"), + onmenuselect: (e: GUI.TagEventType) => { + return this.spotlight.run(this); + }, + }, + ]; + return m.show(e); + }; + + this.fileview.contextmenuHandle = (e, m) => { + m.items = [ + { text: "__(New file)", id: "new" }, + { text: "__(New folder)", id: "newdir" }, + { text: "__(Rename)", id: "rename" }, + { text: "__(Delete)", id: "delete" }, + ]; + m.onmenuselect = (e) => { + return this.ctxFileMenuHandle(e); + }; + return m.show(e); + }; + + this.bindKey("ALT-N", () => this.menuAction("new")); + this.bindKey("ALT-O", () => this.menuAction("open")); + this.bindKey("ALT-F", () => this.menuAction("opendir")); + this.bindKey("CTRL-S", () => this.menuAction("save")); + this.bindKey("ALT-W", () => this.menuAction("saveas")); + + this.fileview.ondragndrop = (e) => { + const src = e.data.from.data.path.asFileHandle(); + const des = e.data.to.data.path; + return src + .move(`${des}/${src.basename}`) + .then(function (d: any) { + const p1 = des; + const p2 = src.parent().path; + if(p1.length < p2.length) + { + e.data.to.update(p1); + e.data.from.parent.update(p2); + } + else + { + e.data.from.parent.update(p2); + e.data.to.update(p1); + } + + }) + .catch((e: Error) => + this.error(__("Unable to move file/folder"), e) + ); + }; + + this.on("filechange", (data) => { + let { path } = data.file; + if (data.type === "file") { + ({ path } = data.file.parent()); + } + return this.fileview.update(path); + }); + + this.loadExtensionMetaData(); + this.initCommandPalete(); + this.initSideBar(); + return this.openFile(this.currfile); + } + + + /** + * + * + * @param {CodePadFileHandle} file + * @returns {void} + * @memberof CodePad + */ + openFile(file: CodePadFileHandle): void { + //find tab + const i = this.findTabByFile(file); + if (i !== -1) { + this.tabbar.selected = i; + return; + } + if (file.path.toString() === "Untitled") { + this.newTab(file); + return; + } + + file.read() + .then((d) => { + file.cache = d || ""; + return this.newTab(file); + }) + .catch((e) => { + return this.error( + __("Unable to open: {0}", file.path), + e + ); + }); + } + + /** + * + * + * @private + * @param {CodePadFileHandle} file + * @returns {number} + * @memberof CodePad + */ + private findTabByFile(file: CodePadFileHandle): number { + const lst = this.tabbar.items; + const its = (() => { + const result = []; + for (let i = 0; i < lst.length; i++) { + const d = lst[i]; + if (d.hash() === file.hash()) { + result.push(i); + } + } + return result; + })(); + if (its.length === 0) { + return -1; + } + return its[0]; + } + + /** + * + * + * @private + * @param {CodePadFileHandle} file + * @memberof CodePad + */ + private newTab(file: CodePadFileHandle): void { + file.text = file.basename ? file.basename : file.path; + if (!file.cache) { + file.cache = ""; + } + file.um = new ace.UndoManager(); + this.currfile.selected = false; + file.selected = true; + //console.log cnt + this.tabbar.push(file); + } + + /** + * + * + * @private + * @param {GUI.tag.ListViewItemTag} it + * @returns {boolean} + * @memberof CodePad + */ + private closeTab(it: GUI.tag.ListViewItemTag): boolean { + this.tabbar.delete(it); + const cnt = this.tabbar.items.length; + + if (cnt === 0) { + this.openFile( + "Untitled".asFileHandle() as CodePadFileHandle + ); + return false; + } + this.tabbar.selected = cnt - 1; + return false; + } + + /** + * + * + * @private + * @param {number} i + * @returns {void} + * @memberof CodePad + */ + private selecteTab(i: number): void { + //return if i is @tabbar.get "selidx" + const file = this.tabbar.items[i] as CodePadFileHandle; + if (!file) { + return; + } + (this + .scheme as GUI.tag.WindowTag).apptitle = file.text.toString(); + //return if file is @currfile + if (this.currfile !== file) { + this.currfile.cache = this.editor.getValue(); + this.currfile.cursor = this.editor.selection.getCursor(); + this.currfile.selected = false; + this.currfile = file; + } + + if (!file.langmode) { + if (file.path.toString() !== "Untitled") { + const m = this.modes.getModeForPath(file.path); + file.langmode = { caption: m.caption, mode: m.mode }; + } else { + file.langmode = { + caption: "Text", + mode: "ace/mode/text", + }; + } + } + this.editormux = true; + this.editor.getSession().setUndoManager(new ace.UndoManager()); + this.editor.setValue(file.cache, -1); + this.editor.getSession().setMode(file.langmode.mode); + if (file.cursor) { + this.editor.renderer.scrollCursorIntoView( + { + row: file.cursor.row, + column: file.cursor.column, + }, + 0.5 + ); + this.editor.selection.moveTo( + file.cursor.row, + file.cursor.column + ); + } + this.editor.getSession().setUndoManager(file.um); + this.updateStatus(); + this.editor.focus(); + } + + /** + * + * + * @private + * @memberof CodePad + */ + private updateStatus(): void { + const c = this.editor.session.selection.getCursor(); + const l = this.editor.session.getLength(); + this.editorstat.text = __( + "Row {0}, col {1}, lines: {2}", + c.row + 1, + c.column + 1, + l + ); + this.langstat.text = this.currfile.langmode.caption; + } + + + /** + * + * + * @memberof CodePad + */ + initSideBar(): void { + if (this.currdir) { + $(this.sidebar).show(); + this.fileview.path = this.currdir.path; + } else { + $(this.sidebar).hide(); + } + this.trigger("resize"); + } + + /** + * + * + * @private + * @param {ActionType} action + * @returns {CodePad} + * @memberof CodePad + */ + private addAction(action: ActionType): CodePad { + this.spotlight.addAction(action); + return this; + } + + /** + * + * + * @private + * @param {ActionType[]} list + * @returns {CodePad} + * @memberof CodePad + */ + private addActions(list: ActionType[]): CodePad { + this.spotlight.addActions(list); + return this; + } + + /** + * + * + * @private + * @memberof CodePad + */ + private initCommandPalete(): void { + let v: any; + const themes = ace.require("ace/ext/themelist"); + const cmdtheme = new CMDMenu(__("Change theme")); + for (let k in themes.themesByName) { + v = themes.themesByName[k]; + cmdtheme.addAction({ text: v.caption, theme: v.theme }); + } + cmdtheme.onchildselect(function ( + d: GUI.TagEventType, + r: CodePad + ) { + const data = d.data.item.data; + r.editor.setTheme(data.theme); + return r.editor.focus(); + }); + this.spotlight.addAction(cmdtheme); + const cmdmode = new CMDMenu(__("Change language mode")); + for (v of Array.from(this.modes.modes)) { + cmdmode.addAction({ text: v.caption, mode: v.mode }); + } + cmdmode.onchildselect(function ( + d: GUI.TagEventType, + r: CodePad + ) { + const data = d.data.item.data; + r.editor.session.setMode(data.mode); + r.currfile.langmode = { + caption: data.text, + mode: data.mode, + }; + r.updateStatus(); + r.editor.focus(); + }); + this.spotlight.addAction(cmdmode); + this.addAction(CMDMenu.fromMenu(this.fileMenu())); + } + + + /** + * + * + * @memberof CodePad + */ + loadExtensionMetaData(): void { + `${this.meta().path}/extensions.json` + .asFileHandle() + .read("json") + .then((d: GenericObject[]) => { + for (var ext of Array.from(d)) { + if (this.extensions[ext.name]) { + this.extensions[ext.name].nodes = []; + for (let v of Array.from(ext.actions)) { + this.extensions[ext.name].addAction(v); + } + } else { + this.extensions[ext.name] = new CMDMenu( + ext.text + ); + this.extensions[ext.name].name = ext.name; + for (let v of Array.from(ext.actions)) { + this.extensions[ext.name].addAction(v); + } + this.spotlight.addAction( + this.extensions[ext.name] + ); + this.extensions[ext.name].onchildselect( + (e: GUI.TagEventType) => { + return this.loadAndRunExtensionAction( + e.data.item.data + ); + } + ); + } + } + }) + .catch((e) => { + return this.error( + __("Cannot load extension meta data"), + e + ); + }); + } + + /** + * + * + * @private + * @param {string} name + * @param {string} action + * @returns {void} + * @memberof CodePad + */ + private runExtensionAction(name: string, action: string): void { + if (!CodePad.extensions[name]) { + return this.error( + __("Unable to find extension: {0}", name) + ); + } + const ext = new CodePad.extensions[name](this); + if (!ext[action]) { + return this.error(__("Unable to find action: {0}", action)); + } + ext.preload() + .then(() => ext[action]()) + .catch((e: Error) => { + return this.error(__("Unable to preload extension"), e); + }); + } + + + /** + * + * + * @param {{ + * parent: { name: any }; + * name: any; + * }} data + * @memberof CodePad + */ + loadAndRunExtensionAction(data: { + parent: { name: any }; + name: any; + }): void { + const { name } = data.parent; + const action = data.name; + //verify if the extension is load + if (!CodePad.extensions[name]) { + //load the extension + const path = `${this.meta().path}/${name}.js`; + this._api + .requires(path) + .then(() => this.runExtensionAction(name, action)) + .catch((e) => { + return this.error( + __("unable to load extension: {0}", name), + e + ); + }); + } else { + this.runExtensionAction(name, action); + } + } + + /** + * + * + * @private + * @returns {GUI.BasicItemType} + * @memberof CodePad + */ + private fileMenu(): GUI.BasicItemType { + return { + text: __("File"), + nodes: [ + { text: __("New"), dataid: "new", shortcut: "A-N" }, + { text: __("Open"), dataid: "open", shortcut: "A-O" }, + { + text: __("Open Folder"), + dataid: "opendir", + shortcut: "A-F", + }, + { text: __("Save"), dataid: "save", shortcut: "C-S" }, + { + text: __("Save as"), + dataid: "saveas", + shortcut: "A-W", + }, + ], + onchildselect: (e: GUI.TagEventType, r: CodePad) => { + return this.menuAction(e.data.item.data.dataid, r); + }, + }; + } + + /** + * + * + * @private + * @param {GUI.TagEventType} e + * @returns {void} + * @memberof CodePad + */ + private ctxFileMenuHandle(e: GUI.TagEventType): void { + const el = e.data.item as GUI.tag.MenuEntryTag; + if (!el) { + return; + } + const data = el.data; + if (!data) { + return; + } + let file: API.VFS.BaseFileHandle | API.FileInfoType = this + .fileview.selectedFile; + let dir = this.currdir; + if (file && file.type === "dir") { + dir = file.path.asFileHandle(); + } + if (file && file.type === "file") { + dir = file.path.asFileHandle().parent(); + } + + switch (data.id) { + case "new": + if (!dir) { + return; + } + this.openDialog("PromptDialog", { + title: "__(New file)", + label: "__(File name)", + }).then(async (d) => { + const fp = `${dir.path}/${d}`.asFileHandle(); + try { + const r = await fp.write("text/plain"); + return this.fileview.update(dir.path); + } catch (e) { + return this.error( + __("Fail to create: {0}", e.stack), + e + ); + } + }); + break; + + case "newdir": + if (!dir) { + return; + } + this.openDialog("PromptDialog", { + title: "__(New folder)", + label: "__(Folder name)", + }).then(async (d) => { + try { + const r = await dir.mk(d); + return this.fileview.update(dir.path); + } catch (e) { + return this.error( + __("Fail to create: {0}", dir.path), + e + ); + } + }); + break; + + case "rename": + if (!file) { + return; + } + this.openDialog("PromptDialog", { + title: "__(Rename)", + label: "__(File name)", + value: file.filename, + }).then(async (d) => { + if (d === file.filename) { + return; + } + file = file.path.asFileHandle(); + dir = file.parent(); + try { + const r = await file.move(`${dir.path}/${d}`); + return this.fileview.update(dir.path); + } catch (e) { + return this.error( + __("Fail to rename: {0}", file.path), + e + ); + } + }); + break; + + case "delete": + if (!file) { + return; + } + this.openDialog("YesNoDialog", { + title: "__(Delete)", + iconclass: "fa fa-question-circle", + text: __( + "Do you really want to delete: {0}?", + file.filename + ), + }).then(async (d) => { + if (!d) { + return; + } + file = file.path.asFileHandle(); + dir = file.parent(); + try { + const r = await file.remove(); + return this.fileview.update(dir.path); + } catch (e) { + return this.error( + __("Fail to delete: {0}", file.path), + e + ); + } + }); + break; + default: + } + } + + /** + * + * + * @private + * @param {CodePadFileHandle} file + * @memberof CodePad + */ + private save(file: CodePadFileHandle): void { + file.write("text/plain") + .then((d) => { + file.dirty = false; + file.text = file.basename; + this.tabbar.update(undefined); + (this + .scheme as GUI.tag.WindowTag).apptitle = `${this.currfile.basename}`; + }) + .catch((e) => + this.error(__("Unable to save file: {0}", file.path), e) + ); + } + + /** + * + * + * @private + * @memberof CodePad + */ + private saveAs(): void { + this.openDialog("FileDialog", { + title: __("Save as"), + file: this.currfile, + }).then((f) => { + let d = f.file.path.asFileHandle(); + if (f.file.type === "file") { + d = d.parent(); + } + this.currfile.setPath(`${d.path}/${f.name}`); + this.save(this.currfile); + }); + } + + /** + * + * + * @private + * @param {string} dataid + * @param {CodePad} [r] + * @returns {void} + * @memberof CodePad + */ + private menuAction(dataid: string, r?: CodePad): void { + let me: any = this; + if (r) { + me = r; + } + switch (dataid) { + case "new": + return me.openFile("Untitled".asFileHandle()); + case "open": + return me + .openDialog("FileDialog", { + title: __("Open file"), + mimes: Array.from(me.meta().mimes).filter( + (v) => v !== "dir" + ), + }) + .then((f: API.FileInfoType) => + me.openFile(f.file.path.asFileHandle()) + ); + case "opendir": + return me + .openDialog("FileDialog", { + title: __("Open folder"), + mimes: ["dir"], + }) + .then(function (f: API.FileInfoType) { + me.currdir = f.file.path.asFileHandle(); + return me.initSideBar(); + }); + case "save": + me.currfile.cache = me.editor.getValue(); + if (me.currfile.basename) { + return me.save(me.currfile); + } + return me.saveAs(); + case "saveas": + me.currfile.cache = me.editor.getValue(); + return me.saveAs(); + default: + return console.log(dataid); + } + } + + /** + * + * + * @param {BaseEvent} evt + * @returns {void} + * @memberof CodePad + */ + cleanup(evt: BaseEvent): void { + let v: GenericObject; + const dirties = (() => { + const result = []; + for (v of Array.from(this.tabbar.items)) { + if (v.dirty) { + result.push(v); + } + } + return result; + })(); + if (dirties.length === 0) { + return; + } + evt.preventDefault(); + this.openDialog("YesNoDialog", { + title: "__(Quit)", + text: __( + "Ignore all unsaved files: {0} ?", + (() => { + const result1 = []; + for (v of Array.from(dirties)) { + result1.push(v.filename()); + } + return result1; + })().join(", ") + ), + }).then((d) => { + if (d) { + for (v of Array.from(dirties)) { + v.dirty = false; + } + return this.quit(false); + } + }); + } + + /** + * + * + * @returns {GUI.BasicItemType[]} + * @memberof CodePad + */ + menu(): GUI.BasicItemType[] { + return [ + this.fileMenu(), + { + text: "__(View)", + nodes: [ + { + text: "__(Command Palette)", + dataid: "cmdpalette", + shortcut: "A-P", + }, + ], + onchildselect: ( + e: GUI.TagEventType, + r: CodePadFileHandle + ) => { + return this.spotlight.run(this); + }, + }, + ]; + } + } + + /** + * + * + * @class CMDMenu + */ + class CMDMenu { + text: string | FormatedString; + private shortcut: string; + nodes: GenericObject[]; + parent: CMDMenu; + private select: (e: GUI.TagEventType, r: CodePad) => void; + static fromMenu: (mn: GUI.BasicItemType) => CMDMenu; + + /** + *Creates an instance of CMDMenu. + * @param {(string | FormatedString)} text + * @param {string} [shortcut] + * @memberof CMDMenu + */ + constructor(text: string | FormatedString, shortcut?: string) { + this.text = text; + this.shortcut = shortcut; + this.nodes = []; + this.parent = undefined; + this.select = function (e) {}; + } + + /** + * + * + * @param {ActionType} v + * @returns {CMDMenu} + * @memberof CMDMenu + */ + addAction(v: ActionType): CMDMenu { + v.parent = this; + this.nodes.push(v); + return this; + } + + /** + * + * + * @param {ActionType[]} list + * @memberof CMDMenu + */ + addActions(list: ActionType[]): void { + Array.from(list).map((v) => this.addAction(v)); + } + + /** + * + * + * @param {(e: GUI.TagEventType, r: CodePad) => void} f + * @returns {CMDMenu} + * @memberof CMDMenu + */ + onchildselect( + f: (e: GUI.TagEventType, r: CodePad) => void + ): CMDMenu { + this.select = f; + return this; + } + + /** + * + * + * @param {CodePad} root + * @memberof CMDMenu + */ + run(root: CodePad) { + root.openDialog(new CommandPalette(), this).then((d) => { + const data = d.data.item.data; + if (data.run) { + return data.run(root); + } + return this.select(d, root); + }); + } + } + + CMDMenu.fromMenu = function (mn): CMDMenu { + const m = new CMDMenu(mn.text, mn.shortcut); + m.onchildselect(mn.onchildselect); + for (let it of Array.from(mn.nodes)) { + let v = it as ActionType; + if (v.nodes) { + m.addAction(CMDMenu.fromMenu(v as GUI.BasicItemType)); + } else { + m.addAction(v as ActionType); + } + } + return m; + }; + + CodePad.CMDMenu = CMDMenu; + + CodePad.dependencies = [ + "os://scripts/ace/ace.js", + "os://scripts/ace/ext-language_tools.js", + "os://scripts/ace/ext-modelist.js", + "os://scripts/ace/ext-themelist.js", + ]; + + /** + * + * + * @class CommandPalette + * @extends {GUI.BasicDialog} + */ + export class CommandPalette extends GUI.BasicDialog { + private cmdlist: GUI.tag.ListViewTag; + private searchbox: HTMLInputElement; + + /** + *Creates an instance of CommandPalette. + * @memberof CommandPalette + */ + constructor() { + super("CommandPalete", CommandPalette.scheme); + } + + /** + * + * + * @memberof CommandPalette + */ + main(): void { + super.main(); + const win = (this.parent as BaseModel) + .scheme as GUI.tag.WindowTag; + const offset = $(".afx-window-content", win).offset(); + const pw = win.width / 5; + (this.scheme as GUI.tag.WindowTag).width = 3 * pw; + $(this.scheme).offset({ top: offset.top - 2, left: offset.left + pw }); + var cb = (e: JQuery.MouseEventBase) => { + if ($(e.target).closest(this.scheme).length > 0) { + return $(this.find("searchbox")).focus(); + } else { + $(document).unbind("mousedown", cb); + return this.quit(); + } + }; + $(document).on("mousedown", cb); + $(this.find("searchbox")).focus(); + this.cmdlist = this.find("container") as GUI.tag.ListViewTag; + if (this.data) { + this.cmdlist.data = this.data.nodes; + } + $(this.cmdlist).click((e) => { + return this.selectCommand(); + }); + + this.searchbox = this.find("searchbox") as HTMLInputElement; + $(this.searchbox).keyup((e) => { + return this.search(e); + }); + } + + /** + * + * + * @private + * @param {JQuery.KeyboardEventBase} e + * @returns {void} + * @memberof CommandPalette + */ + private search(e: JQuery.KeyboardEventBase): void { + let v: { text: string }; + switch (e.which) { + case 27: + // escape key + this.quit(); + if (this.data.parent && this.data.parent.run) { + return this.data.parent.run(this.parent); + } + break; + case 37: + return e.preventDefault(); + case 38: + this.cmdlist.selectPrev(); + return e.preventDefault(); + case 39: + return e.preventDefault(); + case 40: + this.cmdlist.selectNext(); + return e.preventDefault(); + case 13: + e.preventDefault(); + return this.selectCommand(); + default: + var text = this.searchbox.value; + if (text.length === 2) { + this.cmdlist.data = this.data.nodes; + return; + } + if (text.length < 3) { + return; + } + var result = []; + var term = new RegExp(text, "i"); + for (let v of this.data.nodes) { + if (v.text.match(term)) { + result.push(v); + } + } + this.cmdlist.data = result; + } + } + + /** + * + * + * @private + * @returns {void} + * @memberof CommandPalette + */ + private selectCommand(): void { + const el = this.cmdlist.selectedItem as GUI.tag.ListViewItemTag; + if (!el) { + return; + } + el.selected = false; + if (this.handle) { + this.handle({ data: { item: el } }); + } + return this.quit(); + } + } + + CommandPalette.scheme = `\ + + + + + +\ + `; + } +} diff --git a/src/packages/CodePad/extensions/templates/ext-extension.tpl b/src/packages/CodePad/templates/ext-extension.tpl similarity index 100% rename from src/packages/CodePad/extensions/templates/ext-extension.tpl rename to src/packages/CodePad/templates/ext-extension.tpl diff --git a/src/packages/CodePad/extensions/templates/ext-main.tpl b/src/packages/CodePad/templates/ext-main.tpl similarity index 86% rename from src/packages/CodePad/extensions/templates/ext-main.tpl rename to src/packages/CodePad/templates/ext-main.tpl index 5728ffb..26cbf8b 100644 --- a/src/packages/CodePad/extensions/templates/ext-main.tpl +++ b/src/packages/CodePad/templates/ext-main.tpl @@ -1,5 +1,5 @@ # import the CodePad application module -App = this.OS.APP.CodePad +App = this.OS.application.CodePad # define the extension class App.extensions.{0} extends App.BaseExtension diff --git a/src/packages/CodePad/extensions/templates/sdk-README.tpl b/src/packages/CodePad/templates/sdk-README.tpl similarity index 100% rename from src/packages/CodePad/extensions/templates/sdk-README.tpl rename to src/packages/CodePad/templates/sdk-README.tpl diff --git a/src/packages/CodePad/extensions/templates/sdk-main.tpl b/src/packages/CodePad/templates/sdk-main.tpl similarity index 65% rename from src/packages/CodePad/extensions/templates/sdk-main.tpl rename to src/packages/CodePad/templates/sdk-main.tpl index ae36e28..3119ae9 100644 --- a/src/packages/CodePad/extensions/templates/sdk-main.tpl +++ b/src/packages/CodePad/templates/sdk-main.tpl @@ -1,4 +1,4 @@ -class {0} extends this.OS.GUI.BaseApplication +class {0} extends this.OS.application.BaseApplication constructor: ( args ) -> super "{0}", args diff --git a/src/packages/CodePad/extensions/templates/sdk-package.tpl b/src/packages/CodePad/templates/sdk-package.tpl similarity index 100% rename from src/packages/CodePad/extensions/templates/sdk-package.tpl rename to src/packages/CodePad/templates/sdk-package.tpl diff --git a/src/packages/CodePad/extensions/templates/sdk-project.tpl b/src/packages/CodePad/templates/sdk-project.tpl similarity index 100% rename from src/packages/CodePad/extensions/templates/sdk-project.tpl rename to src/packages/CodePad/templates/sdk-project.tpl diff --git a/src/packages/CodePad/extensions/templates/sdk-scheme.tpl b/src/packages/CodePad/templates/sdk-scheme.tpl similarity index 100% rename from src/packages/CodePad/extensions/templates/sdk-scheme.tpl rename to src/packages/CodePad/templates/sdk-scheme.tpl diff --git a/src/packages/pkg.mk b/src/packages/pkg.mk index 8c3578f..c80d209 100644 --- a/src/packages/pkg.mk +++ b/src/packages/pkg.mk @@ -1,7 +1,7 @@ BLUE=\033[1;34m NC=\033[0m - +DIST=../../../dist/packages/$(PKG_NAME) main: title clean js css copy title: @@ -9,7 +9,7 @@ title: module: - mkdir build - for f in $(module_files); do (cat "../../../dist/packages/$(PKG_NAME)/$${f}"; echo) >>"build/main.js";done + for f in $(module_files); do (cat "$(DIST)/$${f}"; echo) >>"build/main.js";done js: module for f in $(libfiles); do (cat "$${f}"; echo) >> build/main.js; done diff --git a/tests/testTag.ts b/tests/testTag.ts index e26cb4c..d41b5c3 100644 --- a/tests/testTag.ts +++ b/tests/testTag.ts @@ -472,8 +472,16 @@ test("Test gridview setter/getter", () => { expect(grid.selectedCell).toBe(cell.domel); expect(grid.selectedRow).toBe(row.domel); expect(grid.selectedRows.length).toBe(2); - const toprow: any = [{ text: "text -3" }, { text: "text -2" }, { text: "text -1" }]; - const botrow: any = [{ text: "text 10" }, { text: "text 11" }, { text: "text 12" }]; + const toprow: any = [ + { text: "text -3" }, + { text: "text -2" }, + { text: "text -1" }, + ]; + const botrow: any = [ + { text: "text 10" }, + { text: "text 11" }, + { text: "text 12" }, + ]; grid.unshift(toprow); grid.push(botrow); expect(grid.rows.length).toBe(5); @@ -515,16 +523,229 @@ test("Test gridview behavior", () => { }); // Treeview -test("Treeview item setter/getter", ()=>{ - -}); -test("Treeview item behavior", ()=>{ - +test("Treeview item setter/getter", () => { + const item = new OS.GUI.tag.SimpleTreeViewItem(); + item.uify(); + const tdata = get_treedata(); + item.data = tdata; + expect(item.data).toBe(tdata); + expect(item.nodes).toBe(tdata.nodes); + expect(item.open).toBe(false); + expect(item.indent).toBe(0); + expect(item.treepath).toBe(item.aid.toString()); + expect(item.parent).toBeUndefined(); + item.update("expand"); + expect(item.open).toBe(true); + const child = item.nodes[2].domel; + expect(child.data).toBe(item.nodes[2]); + expect(child.indent).toBe(1); + expect(child.open).toBe(true); + const childtree = $(child).closest("afx-tree-view")[0]; + expect(childtree).toBeDefined(); + expect(child.treepath).toBe(`${item.aid}/${childtree.aid}`); + expect(child.parent).toBe(childtree); + child.selected = true; + expect(child.selected).toBe(true); }); // Treeview -test("Treeview setter/getter", ()=>{ +test("Treeview setter/getter", () => { + const item = new OS.GUI.tag.TreeViewTag(); + item.uify(); + const tdata = get_treedata(); + item.data = tdata; + item.expandAll(); + expect(item.selectedItem).toBeUndefined(); + expect(item.data).toBe(tdata); + expect(item.treeroot).toBeUndefined(); + expect(item.treepath).toBe(item.aid.toString()); + expect(item.indent).toBe(0); + expect(item.dragndrop).toBe(false); + expect(item.itemtag).toBe("afx-tree-view-item"); + const child = item.data.nodes[2].domel; + expect(child).toBeDefined(); + const childtree = $(child).closest("afx-tree-view")[0]; + expect(child.treeroot).toBe(item); + expect(childtree.is_leaf()).toBe(false); +}); +test("Treeview behavior", () => { + const item = new OS.GUI.tag.TreeViewTag(); + item.uify(); + const tdata = get_treedata(); + item.data = tdata; + item.expandAll(); + const child = item.data.nodes[2].domel; + const cb = jest.fn(); + item.ontreeselect = cb; + item.ontreedbclick = cb; + + $(">div", child).trigger("click"); + expect(cb).toBeCalledTimes(1); + expect(item.selectedItem).toBe(child); + const anotherchild = item.data.nodes[1].domel; + anotherchild.selected = true; + expect(cb).toBeCalledTimes(2); + expect(item.selectedItem).toBe(anotherchild); + expect(child.selected).toBe(false); + $(">div", child).trigger("dblclick"); + expect(cb).toBeCalledTimes(3); + expect(item.selectedItem).toBe(child); +}); + +// Calendar tag +test("Calendar tag setter/getter", () => { + const item = new OS.GUI.tag.CalendarTag(); + item.uify(); + const now = { + d: new Date().getDate(), + m: new Date().getMonth(), + y: new Date().getFullYear(), + }; + expect(item.selectedDate).toStrictEqual(new Date(now.y, now.m, now.d)); +}); + +test("Calendar tag behavior", () => { + const item = new OS.GUI.tag.CalendarTag(); + const cb = jest.fn(); + item.ondateselect = cb; + item.uify(); + expect(cb).toBeCalledTimes(1); +}); + +// File view tag + +function get_files_data() { + return JSON.parse(`\ + [{ + "permissions": " (755)", + "type": "dir", + "mtime": "2017-07-23T22:53:10", + "size": 102, + "path": "home:////desktop", + "ctime": "2017-07-23T22:53:10", + "filename": "desktop", + "perm": { + "owner": { + "write": true, + "read": true, + "exec": true + }, + "group": { + "write": false, + "read": true, + "exec": true + }, + "other": { + "write": false, + "read": true, + "exec": true + } + }, + "mime": "", + "uid": 501, + "gid": 20 + }, + { + "permissions": " (644)", + "type": "file", + "mtime": "2017-07-30T00:55:34", + "size": 2821, + "path": "home:////settings.json", + "ctime": "2017-07-30T00:55:34", + "filename": "settings.json", + "perm": { + "owner": { + "write": true, + "read": true, + "exec": false + }, + "group": { + "write": false, + "read": true, + "exec": false + }, + "other": { + "write": false, + "read": true, + "exec": false + } + }, + "mime": "application/json", + "uid": 501, + "gid": 20 + }, + { + "permissions": " (644)", + "type": "file", + "mtime": "2017-07-11T20:30:51", + "size": 575, + "path": "home:////helloworld.xml", + "ctime": "2017-07-11T20:30:51", + "filename": "helloworld.xml", + "perm": { + "owner": { + "write": true, + "read": true, + "exec": false + }, + "group": { + "write": false, + "read": true, + "exec": false + }, + "other": { + "write": false, + "read": true, + "exec": false + } + }, + "mime": "application/xml", + "uid": 501, + "gid": 20 + } +]`); +} +test("File view setter/getter", () => { + const fileview = new OS.GUI.tag.FileViewTag(); + fileview.uify(); + expect(fileview.data.length).toBe(0); + expect(fileview.status).toBe(true); + expect(fileview.view).toBe("list"); + expect(fileview.showhidden).toBe(false); + expect(fileview.chdir).toBe(true); + fileview.status = false; + expect(fileview.status).toBe(false); + fileview.view = "icon"; + expect(fileview.view).toBe("icon"); + fileview.showhidden = true; + expect(fileview.showhidden).toBe(true); + fileview.chdir = false; + expect(fileview.chdir).toBe(false); + expect(fileview.selectedFile).toBeUndefined(); + fileview.path = "home://"; + expect(fileview.path).toBe("home://"); + const data = get_files_data(); + fileview.data = data; + expect(fileview.data).toBe(data); }); -test("Treeview behavior", ()=>{ -}); \ No newline at end of file +test("File view behavior", () => { + const fileview = new OS.GUI.tag.FileViewTag(); + fileview.uify(); + fileview.view = "icon"; + const cb = jest.fn(); + fileview.onfileselect = cb; + fileview.onfileopen = cb; + const data = get_files_data(); + data[2].selected = true; + fileview.data = data; + expect(cb).toBeCalledTimes(1); + const el = fileview.selectedFile; + expect(el).toBe(data[2]); + $("li", el.domel).trigger("dblclick"); + expect(cb).toBeCalledTimes(2); + /* fileview.view = "tree"; + fileview.update("expand"); + const treeitem = fileview.data[0].domel; + expect(treeitem.tagName).toBe("AFX-TREE-VIEW-ITEM"); */ +});