diff --git a/.gitignore b/.gitignore index a0ef7b9..b76794e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ build node_modules -.DS_Store \ No newline at end of file +.DS_Store +package-lock.json +dist +docs +coffees +.vscode \ No newline at end of file diff --git a/Makefile b/Makefile index f37ff26..7b6800f 100644 --- a/Makefile +++ b/Makefile @@ -11,44 +11,44 @@ ifeq ($(UNAME_S),Darwin) endif -javascripts= src/core/core.js \ - src/core/settings.js \ - src/core/handles/RemoteHandle.js \ - src/core/Announcerment.js \ - src/core/vfs.js \ - src/core/vfs/GoogleDriveHandle.js \ - src/core/db.js \ - src/core/gui.js \ - src/core/BaseModel.js \ - src/core/BaseApplication.js \ - src/core/BaseService.js \ - src/core/BaseEvent.js \ - src/core/BaseDialog.js \ - src/core/tags/tag.js \ - src/core/tags/WindowTag.js \ - src/core/tags/TileLayoutTags.js \ - src/core/tags/ResizerTag.js \ - src/core/tags/LabelTag.js \ - src/core/tags/ButtonTag.js \ - src/core/tags/ListViewTag.js \ - src/core/tags/SwitchTag.js \ - src/core/tags/NSpinnerTag.js \ - src/core/tags/MenuTag.js \ - src/core/tags/GridViewTag.js \ - src/core/tags/TabBarTag.js \ - src/core/tags/TabContainerTag.js \ - src/core/tags/TreeViewTag.js \ - src/core/tags/SliderTag.js \ - src/core/tags/FloatListTag.js \ - src/core/tags/CalendarTag.js \ - src/core/tags/ColorPickerTag.js \ - src/core/tags/FileViewTag.js \ - src/core/tags/OverlayTag.js \ - src/core/tags/AppDockTag.js \ - src/core/tags/SystemPanelTag.js \ - src/antos.js +javascripts= dist/core/core.js \ + dist/core/settings.js \ + dist/core/handles/RemoteHandle.js \ + dist/core/Announcerment.js \ + dist/core/vfs.js \ + dist/core/db.js \ + dist/core/BaseModel.js \ + dist/core/BaseApplication.js \ + dist/core/BaseService.js \ + dist/core/BaseEvent.js \ + dist/core/BaseDialog.js \ + dist/core/tags/tag.js \ + dist/core/tags/WindowTag.js \ + dist/core/tags/TileLayoutTags.js \ + dist/core/tags/ResizerTag.js \ + dist/core/tags/LabelTag.js \ + dist/core/tags/ButtonTag.js \ + dist/core/tags/ListViewTag.js \ + dist/core/tags/SwitchTag.js \ + dist/core/tags/NSpinnerTag.js \ + dist/core/tags/MenuTag.js \ + dist/core/tags/GridViewTag.js \ + dist/core/tags/TabBarTag.js \ + dist/core/tags/TabContainerTag.js \ + dist/core/tags/TreeViewTag.js \ + dist/core/tags/SliderTag.js \ + dist/core/tags/FloatListTag.js \ + dist/core/tags/CalendarTag.js \ + dist/core/tags/ColorPickerTag.js \ + dist/core/tags/FileViewTag.js \ + dist/core/tags/OverlayTag.js \ + dist/core/tags/AppDockTag.js \ + dist/core/tags/SystemPanelTag.js \ + dist/core/gui.js \ + dist/core/pm.js \ + dist/bootstrap.js -packages = Syslog Files Setting CodePad MarketPlace +packages = Syslog #Files Setting CodePad MarketPlace main: initd build_javascripts build_themes libs build_packages languages - cp src/index.html $(BUILDDIR)/ @@ -61,14 +61,19 @@ lite: build_javascripts build_themes build_packages # coffee --compile $< build_javascripts: + -rm -rf dist + tsc @echo "$(BLUE)Bundling javascript files$(NC)" - mkdir $(BUILDDIR)/scripts - rm $(BUILDDIR)/scripts/antos.js - echo "(function() {" > $(BUILDDIR)/scripts/antos.js - for f in $(javascripts); do (cat "$${f}"; echo) >> $(BUILDDIR)/scripts/antos.js; done - echo "}).call(this);" >> $(BUILDDIR)/scripts/antos.js - - + #echo "(function() {" > $(BUILDDIR)/scripts/antos.js + for f in $(javascripts); do \ + (cat "$${f}"; echo) >> dist/antos.js;\ + rm "$${f}";\ + done + cp dist/antos.js $(BUILDDIR)/scripts/ + echo "if(exports){ exports.__esModule = true;exports.OS = OS; }" >> dist/antos.js + rm -r dist/core libs: @echo "$(BLUE)Copy lib files$(NC)" cp -rf src/libs/* $(BUILDDIR)/scripts/ diff --git a/README.md b/README.md index 0b26bd6..ba190be 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,18 @@ A demo of the web desktop is available at my page [https://os.lxsang.me](https: ## AntOS applications [https://github.com/lxsang/antosdk-apps](https://github.com/lxsang/antosdk-apps) - + +## Dependencies + +- npm install @types/jquery +- typescript +- sudo npm install terser -g (optional) +- uglifycss (optional) +- jest, tes-jest (typescript test) (optional) + - npm install @types/jest + - + + ## Build Note that this is only the client API, to make it work for your application, you need to implement all the system calls in core/handlers/RemoteHandler.coffee using a server side scripting language (e.g. PHP). I'm planning to release an API documentation which describes what need to be sent and what will be returned for each system call in near future (i'm kind of very busy right now :) ). diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..8bbaf40 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,8 @@ +module.exports = { + roots: [''], + transform : { + '^.+\\.ts$': 'ts-jest' + }, + testRegex: '(/tests/test.*|(\\.|/)(test|spec))\\.[tj]s?$', + moduleFileExtensions: ['js', 'ts'], + } \ No newline at end of file diff --git a/src/antos.js b/src/bootstrap.ts similarity index 83% rename from src/antos.js rename to src/bootstrap.ts index c071135..c9e90c5 100644 --- a/src/antos.js +++ b/src/bootstrap.ts @@ -11,7 +11,7 @@ // This program is free software: you can redistribute it and/or // modify it under the terms of the GNU General Public License as -// published by the Free Software Foundation, either version 3 of +// published by the Free Software Foundation, either version 3 of // the License, or (at your option) any later version. // This program is distributed in the hope that it will be useful, @@ -21,7 +21,11 @@ // You should have received a copy of the GNU General Public License //along with this program. If not, see https://www.gnu.org/licenses/. -this.onload = function() { - ($(document)).on('webkitfullscreenchange mozfullscreenchange fullscreenchange MSFullscreenChange', () => Ant.OS.GUI.fullscreen = !Ant.OS.GUI.fullscreen); + +this.onload = function () { + $(document).on( + "webkitfullscreenchange mozfullscreenchange fullscreenchange MSFullscreenChange", + () => (Ant.OS.GUI.fullscreen = !Ant.OS.GUI.fullscreen) + ); return Ant.OS.boot(); }; \ No newline at end of file diff --git a/src/core/Announcerment.js b/src/core/Announcerment.js deleted file mode 100644 index d597f88..0000000 --- a/src/core/Announcerment.js +++ /dev/null @@ -1,133 +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 - */ -// Copyright 2017-2018 Xuan Sang LE - -// AnTOS Web desktop is is licensed under the GNU General Public -// License v3.0, see the LICENCE file for more information - -// This program is free software: you can redistribute it and/or -// modify it under the terms of the GNU General Public License as -// published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. - -// You should have received a copy of the GNU General Public License -//along with this program. If not, see https://www.gnu.org/licenses/. -class Announcer { - constructor() { - this.observable = {}; - this.enable = true; - } - - disable() { - this.off("*"); - return this.enable = false; - } - - on(evtName, callback) { - if (!this.enable) { return; } - if (!this.observable[evtName]) { this.observable[evtName] = { one: new Set(), many: new Set() }; } - return this.observable[evtName].many.add(callback); - } - - one(evtName, callback) { - if (!this.enable) { return; } - if (!this.observable[evtName]) { this.observable[evtName] = { one: new Set(), many: new Set() }; } - return this.observable[evtName].one.add(callback); - } - - off(evtName, callback) { - const fn = (evt, cb) => { - if (!this.observable[evt]) { return; } - if (cb) { - this.observable[evt].one.delete(cb); - return this.observable[evt].many.delete(cb); - } else { - if (this.observable[evt]) { return delete this.observable[evt]; } - } - }; - if (evtName === "*") { return (() => { - const result = []; - for (let k in this.observable) { - const v = this.observable[k]; - result.push(fn(k, callback)); - } - return result; - })(); } else { return fn(evtName, callback); } - } - - trigger(evtName, data) { - const trig = (name, d) => { - const names = [name, "*"]; - return (() => { - const result = []; - for (let evt of Array.from(names)) { - if (!this.observable[evt]) { continue; } - this.observable[evt].one.forEach(f => f(d)); - this.observable[evt].one = new Set(); - result.push(this.observable[evt].many.forEach(f => f(d))); - } - return result; - })(); - }; - - if (evtName === "*") { - return (() => { - const result = []; - for (let k in this.observable) { - const v = this.observable[k]; - if (k !== "*") { - result.push(trig(k, data)); - } - } - return result; - })(); - } else { - return trig(evtName, data); - } - } -} - -Ant.OS.API.Announcer = Announcer; -Ant.OS.announcer = { - observable: new Ant.OS.API.Announcer(), - quota: 0, - listeners: {}, - on(e, f, a) { - if (!Ant.OS.announcer.listeners[a.pid]) { Ant.OS.announcer.listeners[a.pid] = []; } - Ant.OS.announcer.listeners[a.pid].push({ e, f }); - return Ant.OS.announcer.observable.on(e, f); - }, - trigger(e, d) { return Ant.OS.announcer.observable.trigger(e, d); }, - osfail(m, e) { - return Ant.OS.announcer.ostrigger("fail", { m, e }); - }, - oserror(m, e) { - return Ant.OS.announcer.ostrigger("error", { m, e }); - }, - osinfo(m) { - return Ant.OS.announcer.ostrigger("info", { m, e: null }); - }, - ostrigger(e, d) { - return Ant.OS.announcer.trigger(e, { id: 0, data: d, name: "OS" }); - }, - unregister(app) { - if (!Ant.OS.announcer.listeners[app.pid] || !(Ant.OS.announcer.listeners[app.pid].length > 0)) { return; } - for (let i of Array.from(Ant.OS.announcer.listeners[app.pid])) { Ant.OS.announcer.observable.off(i.e, i.f); } - return delete Ant.OS.announcer.listeners[app.pid]; - }, - // Ant.OS.announcer.listeners[app.pid] - getMID() { - Ant.OS.announcer.quota += 1; - return Ant.OS.announcer.quota; - } - }; \ No newline at end of file diff --git a/src/core/Announcerment.ts b/src/core/Announcerment.ts new file mode 100644 index 0000000..18fa8c5 --- /dev/null +++ b/src/core/Announcerment.ts @@ -0,0 +1,292 @@ +/* + * 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 + */ +// Copyright 2017-2018 Xuan Sang LE + +// AnTOS Web desktop is is licensed under the GNU General Public +// License v3.0, see the LICENCE file for more information + +// This program is free software: you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. + +// You should have received a copy of the GNU General Public License +//along with this program. If not, see https://www.gnu.org/licenses/. + +namespace OS { + export namespace API { + /** + * + * + * @export + * @interface ObservableEntryType + */ + export interface ObservableEntryType { + one: Set<(d: any) => void>; + many: Set<(d: any) => void>; + } + + /** + * + * + * @export + * @interface AnnouncerListenerType + */ + export interface AnnouncerListenerType { + [index: number]: { + e: string; + f: (d: any) => void; + }[]; + } + + /** + * + * + * @export + * @class Announcer + */ + export class Announcer { + observable: GenericObject; + enable: boolean; + constructor() { + this.observable = {}; + this.enable = true; + } + + /** + * + * + * @returns + * @memberof Announcer + */ + disable() { + this.off("*"); + return (this.enable = false); + } + + /** + * + * + * @param {string} evtName + * @param {(d: any) => void} callback + * @returns {void} + * @memberof Announcer + */ + on(evtName: string, callback: (d: any) => void): void { + if (!this.enable) { + return; + } + if (!this.observable[evtName]) { + this.observable[evtName] = { + one: new Set(), + many: new Set(), + }; + } + this.observable[evtName].many.add(callback); + } + + /** + * + * + * @param {string} evtName + * @param {(d: any) => void} callback + * @returns {void} + * @memberof Announcer + */ + one(evtName: string, callback: (d: any) => void): void { + if (!this.enable) { + return; + } + if (!this.observable[evtName]) { + this.observable[evtName] = { + one: new Set(), + many: new Set(), + }; + } + this.observable[evtName].one.add(callback); + } + + /** + * + * + * @param {string} evtName + * @param {(d: any) => void} [callback] + * @memberof Announcer + */ + off(evtName: string, callback?: (d: any) => void): void { + const fn = (evt: string, cb: (d: any) => void) => { + if (!this.observable[evt]) { + return; + } + if (cb) { + this.observable[evt].one.delete(cb); + return this.observable[evt].many.delete(cb); + } else { + if (this.observable[evt]) { + return delete this.observable[evt]; + } + } + }; + if (evtName === "*") { + for (let k in this.observable) { + fn(k, callback); + } + } else { + fn(evtName, callback); + } + } + + /** + * + * + * @param {string} evtName + * @param {*} data + * @returns {void} + * @memberof Announcer + */ + trigger(evtName: string, data: any): void { + const trig = (name: string, d: any) => { + const names = [name, "*"]; + for (let evt of Array.from(names)) { + if (!this.observable[evt]) { + continue; + } + this.observable[evt].one.forEach((f) => f(d)); + this.observable[evt].one = new Set(); + this.observable[evt].many.forEach((f) => f(d)); + } + }; + if (evtName === "*") { + for (let k in this.observable) { + const v = this.observable[k]; + if (k !== "*") { + trig(k, data); + } + } + } else { + return trig(evtName, data); + } + } + } + } + export namespace announcer { + export var observable: API.Announcer = new API.Announcer(); + export var quota: 0; + export var listeners: API.AnnouncerListenerType = {}; + + /** + * + * + * @export + * @param {string} e + * @param {(d: any) => void} f + * @param {GUI.BaseModel} a + */ + export function on( + e: string, + f: (d: any) => void, + a: BaseModel + ): void { + if (!announcer.listeners[a.pid]) { + announcer.listeners[a.pid] = []; + } + announcer.listeners[a.pid].push({ e, f }); + announcer.observable.on(e, f); + } + + /** + * + * + * @export + * @param {string} e + * @param {*} d + */ + export function trigger(e: string, d: any): void { + announcer.observable.trigger(e, d); + } + + /** + * + * + * @export + * @param {(string | FormatedString)} m + * @param {Error} e + */ + export function osfail(m: string | FormatedString, e: Error): void { + announcer.ostrigger("fail", { m, e }); + } + + /** + * + * + * @export + * @param {(string | FormatedString)} m + * @param {Error} e + */ + export function oserror(m: string | FormatedString, e: Error): void { + announcer.ostrigger("error", { m, e }); + } + + /** + * + * + * @export + * @param {(string | FormatedString)} m + */ + export function osinfo(m: string | FormatedString): void { + announcer.ostrigger("info", { m, e: null }); + } + + /** + * + * + * @export + * @param {string} e + * @param {*} d + */ + export function ostrigger(e: string, d: any): void { + announcer.trigger(e, { id: 0, data: d, name: "OS" }); + } + + /** + * + * + * @export + * @param {GUI.BaseModel} app + * @returns {void} + */ + export function unregister(app: BaseModel): void { + if ( + !announcer.listeners[app.pid] || + !(announcer.listeners[app.pid].length > 0) + ) { + return; + } + for (let i of Array.from(announcer.listeners[app.pid])) { + announcer.observable.off(i.e, i.f); + } + delete announcer.listeners[app.pid]; + } + + /** + * + * + * @export + * @returns {number} + */ + export function getMID(): number { + announcer.quota += 1; + return announcer.quota; + } + } +} diff --git a/src/core/BaseApplication.js b/src/core/BaseApplication.js deleted file mode 100644 index 663cea6..0000000 --- a/src/core/BaseApplication.js +++ /dev/null @@ -1,197 +0,0 @@ -/* - * decaffeinate suggestions: - * 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 - */ -// Copyright 2017-2018 Xuan Sang LE - -// AnTOS Web desktop is is licensed under the GNU General Public -// License v3.0, see the LICENCE file for more information - -// This program is free software: you can redistribute it and/or -// modify it under the terms of the GNU General Public License as -// published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. - -// You should have received a copy of the GNU General Public License -//along with this program. If not, see https://www.gnu.org/licenses/. -class BaseApplication extends this.OS.GUI.BaseModel { - constructor(name, args) { - super(name, args); - if ((!Ant.OS.setting.applications[this.name]) || (Array.isArray(OS.setting.applications[this.name]))) { - Ant.OS.setting.applications[this.name] = {}; - } - this.setting = Ant.OS.setting.applications[this.name]; - this.keycomb = { - ALT: {}, - CTRL: {}, - SHIFT: {}, - META: {} - }; - } - init() { - this.off("*"); - this.on("exit", () => this.quit()); - // first register some base event to the app - this.on("focus", () => { - this.sysdock.set("selectedApp", this); - this.appmenu.pid = this.pid; - this.appmenu.set("items", (this.baseMenu() || [])); - this.appmenu.set("onmenuselect", d => { - return this.trigger("menuselect", d); - }); - if (this.dialog) { return this.dialog.show(); } - }); - this.on("hide", () => { - this.sysdock.set("selectedApp", null); - this.appmenu.set("items", []); - if (this.dialog) { return this.dialog.hide(); } - }); - this.on("menuselect", d => { - switch (d.data.item.get("data").dataid) { - case `${this.name}-about`: return this.openDialog("AboutDialog"); - case `${this.name}-exit`: return this.trigger("exit"); - } - }); - this.on("apptitlechange", () => this.sysdock.update()); - this.updateLocale(this.systemsetting.system.locale); - return this.loadScheme(); - } - - loadScheme() { - //now load the scheme - const path = `${this.meta().path}/scheme.html`; - return this.render(path); - } - - load(promise) { - const q = this._api.mid(); - return new Promise((resolve, reject) => { - this._api.loading(q, this.name); - return promise.then(() => { - this._api.loaded(q, this.name, "OK"); - return resolve(); - }).catch(e => { - this._api.loaded(q, this.name, "FAIL"); - return reject(__e(e)); - }); - }); - } - - bindKey(k, f) { - const arr = k.split("-"); - if (arr.length !== 2) { return; } - const fnk = arr[0].toUpperCase(); - const c = arr[1].toUpperCase(); - if (!this.keycomb[fnk]) { return; } - return this.keycomb[fnk][c] = f; - } - - updateLocale(name) { - const meta = this.meta(); - if (!meta || !meta.locales) { return; } - if (!meta.locales[name]) { return; } - return (() => { - const result = []; - for (let k in meta.locales[name]) { - const v = meta.locales[name][k]; - result.push(this._api.lang[k] = v); - } - return result; - })(); - } - - shortcut(fnk, c, e) { - if (!this.keycomb[fnk]) { return true; } - if (!this.keycomb[fnk][c]) { return true; } - this.keycomb[fnk][c](e); - return false; - } - - applySetting(k) {} - applyAllSetting() { - return (() => { - const result = []; - for (let k in this.setting) { - const v = this.setting[k]; - result.push(this.applySetting(k)); - } - return result; - })(); - } - registry(k, v) { - this.setting[k] = v; - return this.publish("appregistry", k); - } - - show() { - return this.trigger("focus"); - } - - blur() { - if (this.appmenu && (this.pid === this.appmenu.pid)) { this.appmenu.set("items", []); } - return this.trigger("blur"); - } - - hide() { - return this.trigger("hide"); - } - - toggle() { - return this.trigger("toggle"); - } - - title() { - return this.scheme.get("apptitle"); - } - - onexit(evt) { - this.cleanup(evt); - if (!evt.prevent) { - if (this.pid === this.appmenu.pid) { this.appmenu.set("items", []); } - return ($(this.scheme)).remove(); - } - } - meta() { return Ant.OS.APP[this.name].meta; } - baseMenu() { - let mn = - [{ - text: Ant.OS.APP[this.name].meta.name, - child: [ - { text: "__(About)", dataid: `${this.name}-about` }, - { text: "__(Exit)", dataid: `${this.name}-exit` } - ] - }]; - mn = mn.concat(this.menu() || []); - return mn; - } - - main() {} - //main program - // implement by subclasses - menu() { - // implement by subclasses - // to add menu to application - return []; - } - open() {} - //implement by subclasses - data() {} - //implement by subclasses - // to return app data - - cleanup(e) {} -} - //implement by subclasses - // to handle the exit event - // use e.preventDefault() to - // discard the quit command -BaseApplication.type = 1; -this.OS.GUI.BaseApplication = BaseApplication; \ No newline at end of file diff --git a/src/core/BaseApplication.ts b/src/core/BaseApplication.ts new file mode 100644 index 0000000..e60441d --- /dev/null +++ b/src/core/BaseApplication.ts @@ -0,0 +1,379 @@ +/* + * decaffeinate suggestions: + * 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 + */ +// Copyright 2017-2018 Xuan Sang LE + +// AnTOS Web desktop is is licensed under the GNU General Public +// License v3.0, see the LICENCE file for more information + +// This program is free software: you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. + +// You should have received a copy of the GNU General Public License +//along with this program. If not, see https://www.gnu.org/licenses/. + +namespace OS { + export namespace application { + /** + * + * + * @export + * @abstract + * @class BaseApplication + * @extends {BaseModel} + */ + export abstract class BaseApplication extends BaseModel { + setting: GenericObject; + keycomb: GUI.ShortcutType; + sysdock: GUI.tag.AppDockTag; + appmenu: GUI.tag.MenuTag; + + /** + *Creates an instance of BaseApplication. + * @param {string} name + * @param {AppArgumentsType[]} args + * @memberof BaseApplication + */ + constructor(name: string, args: AppArgumentsType[]) { + super(name, args); + if (!setting.applications[this.name]) { + setting.applications[this.name] = {}; + } + this.setting = setting.applications[this.name]; + this.keycomb = { + ALT: {}, + CTRL: {}, + SHIFT: {}, + META: {}, + }; + } + + /** + * + * + * @returns {void} + * @memberof BaseApplication + */ + init(): void { + this.off("*"); + this.on("exit", () => this.quit(false)); + // first register some base event to the app + this.on("focus", () => { + console.log("focus"); + this.sysdock.selectedApp = this; + this.appmenu.pid = this.pid; + this.appmenu.items= this.baseMenu() || []; + this.appmenu.onmenuselect=(d: GUI.TagEventType): void => { + return this.trigger("menuselect", d); + } + if (this.dialog) { + return this.dialog.show(); + } + }); + this.on("hide", () => { + this.sysdock.selectedApp = null; + this.appmenu.items = []; + this.appmenu.pid = -1; + if (this.dialog) { + return this.dialog.hide(); + } + }); + this.on("menuselect", (d) => { + switch (d.data.item.data.dataid) { + case `${this.name}-about`: + return this.openDialog("AboutDialog"); + case `${this.name}-exit`: + return this.trigger("exit", undefined); + } + }); + this.on("apptitlechange", () => this.sysdock.update(undefined)); + this.updateLocale(this.systemsetting.system.locale); + return this.loadScheme(); + } + + /** + * + * + * @returns {void} + * @memberof BaseApplication + */ + loadScheme(): void { + //now load the scheme + const path = `${this.meta().path}/scheme.html`; + return this.render(path); + } + + /** + * + * + * @param {Promise} promise + * @returns {Promise} + * @memberof BaseApplication + */ + load(promise: Promise): Promise { + const q = this._api.mid(); + return new Promise(async (resolve, reject) => { + this._api.loading(q, this.name); + try { + await promise; + this._api.loaded(q, this.name, "OK"); + return resolve(); + } catch (e) { + this._api.loaded(q, this.name, "FAIL"); + return reject(__e(e)); + } + }); + } + + /** + * + * + * @param {string} k + * @param {(e: JQuery.MouseDownEvent) => void} f + * @returns {void} + * @memberof BaseApplication + */ + bindKey(k: string, f: (e: JQuery.MouseDownEvent) => void): void { + const arr = k.split("-"); + if (arr.length !== 2) { + return; + } + const fnk = arr[0].toUpperCase(); + const c = arr[1].toUpperCase(); + if (!this.keycomb[fnk]) { + return; + } + this.keycomb[fnk][c] = f; + } + + /** + * + * + * @param {string} name + * @returns {void} + * @memberof BaseApplication + */ + updateLocale(name: string): void { + const meta = this.meta(); + if (!meta || !meta.locales) { + return; + } + if (!meta.locales[name]) { + return; + } + + const result = []; + for (let k in meta.locales[name]) { + const v = meta.locales[name][k]; + result.push((this._api.lang[k] = v)); + } + } + + /** + * + * + * @param {string} fnk + * @param {string} c + * @param {JQuery.MouseDownEvent} e + * @returns {boolean} + * @memberof BaseApplication + */ + shortcut( + fnk: string, + c: string, + e: JQuery.KeyDownEvent + ): boolean { + if (!this.keycomb[fnk]) { + return true; + } + if (!this.keycomb[fnk][c]) { + return true; + } + this.keycomb[fnk][c](e); + return false; + } + + /** + * + * + * @param {string} k + * @memberof BaseApplication + */ + applySetting(k: string): void {} + + /** + * + * + * @memberof BaseApplication + */ + applyAllSetting(): void { + for (let k in this.setting) { + const v = this.setting[k]; + this.applySetting(k); + } + } + + /** + * + * + * @param {string} k + * @param {*} v + * @returns {void} + * @memberof BaseApplication + */ + registry(k: string, v: any): void { + this.setting[k] = v; + return this.publish("appregistry", k); + } + + /** + * + * + * @returns {void} + * @memberof BaseApplication + */ + show(): void { + return this.trigger("focus", undefined); + } + + /** + * + * + * @returns {void} + * @memberof BaseApplication + */ + blur(): void { + if (this.appmenu && this.pid === this.appmenu.pid) { + this.appmenu.items = []; + } + return this.trigger("blur", undefined); + } + + /** + * + * + * @returns {void} + * @memberof BaseApplication + */ + hide(): void { + return this.trigger("hide", undefined); + } + + /** + * + * + * @returns {void} + * @memberof BaseApplication + */ + toggle(): void { + return this.trigger("toggle", undefined); + } + + /** + * + * + * @returns {(string| FormatedString)} + * @memberof BaseApplication + */ + title(): string| FormatedString { + return (this.scheme as GUI.tag.WindowTag).apptitle; + } + + /** + * + * + * @param {BaseEvent} evt + * @memberof BaseApplication + */ + onexit(evt: BaseEvent): void { + this.cleanup(evt); + if (!evt.prevent) { + if (this.pid === this.appmenu.pid) { + this.appmenu.items = []; + } + $(this.scheme).remove(); + } + } + + /** + * + * + * @returns {API.PackageMetaType} + * @memberof BaseApplication + */ + meta(): API.PackageMetaType { + return application[this.name].meta; + } + + /** + * + * + * @returns {BasicItemType[]} + * @memberof BaseApplication + */ + baseMenu(): GUI.BasicItemType[] { + let mn: GUI.BasicItemType[] = [ + { + text: application[this.name].meta.name, + nodes: [ + { text: "__(About)", dataid: `${this.name}-about` }, + { text: "__(Exit)", dataid: `${this.name}-exit` }, + ], + }, + ]; + mn = mn.concat(this.menu() || []); + return mn; + } + + /** + * + * + * @abstract + * @memberof BaseApplication + */ + abstract main(): void; + //main program + // implement by subclasses + + /** + * + * + * @returns {BasicItemType[]} + * @memberof BaseApplication + */ + menu(): GUI.BasicItemType[] { + // implement by subclasses + // to add menu to application + return []; + } + + /** + * + * + * @memberof BaseApplication + */ + open(): void {} + + /** + * + * + * @param {BaseEvent} e + * @memberof BaseApplication + */ + cleanup(e: BaseEvent): void {} + } + + BaseApplication.type = ModelType.Application; + } +} diff --git a/src/core/BaseDialog.js b/src/core/BaseDialog.js deleted file mode 100644 index e6e9987..0000000 --- a/src/core/BaseDialog.js +++ /dev/null @@ -1,565 +0,0 @@ -/* - * decaffeinate suggestions: - * DS001: Remove Babel/TypeScript constructor workaround - * DS101: Remove unnecessary use of Array.from - * DS102: Remove unnecessary code created because of implicit returns - * DS208: Avoid top-level this - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ -// Copyright 2017-2018 Xuan Sang LE - -// AnTOS Web desktop is is licensed under the GNU General Public -// License v3.0, see the LICENCE file for more information - -// This program is free software: you can redistribute it and/or -// modify it under the terms of the GNU General Public License as -// published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. - -// You should have received a copy of the GNU General Public License -//along with this program. If not, see https://www.gnu.org/licenses/. -class SubWindow extends this.OS.GUI.BaseModel { - constructor(name) { - super(name, null); - this.parent = undefined; - this.modal = false; - } - - quit() { - const evt = new Ant.OS.GUI.BaseEvent("exit"); - this.onexit(evt); - if (!evt.prevent) { - delete this.observable; - if (this.scheme) { ($(this.scheme)).remove(); } - if (this.dialog) { return this.dialog.quit(); } - } - } - init() {} - main() {} - meta() { - if (this.parent && this.parent.meta) { return this.parent.meta(); } - return {}; - } - - show() { - this.trigger('focus'); - return ($(this.scheme)).css("z-index", Ant.OS.GUI.zindex + 2); - } - hide() { - return this.trigger('hide'); - } -} - -SubWindow.type = 3; -this.OS.GUI.SubWindow = SubWindow; - -class BaseDialog extends SubWindow { - constructor(name) { - super(name); - this.handle = undefined; - } - - onexit(e) { - if (this.parent) { return this.parent.dialog = undefined; } - } -} - -this.OS.GUI.BaseDialog = BaseDialog; - -class BasicDialog extends BaseDialog { - constructor( name, markup) { - super(name); - this.markup = markup; - } - - - init() { - if (this.markup) { - if (typeof this.markup === "string") { - return Ant.OS.GUI.htmlToScheme(this.markup, this, this.host); - } else { // a file handle - return this.render(this.markup.path); - } - } else if (Ant.OS.GUI.subwindows[this.name] && Ant.OS.GUI.subwindows[this.name].scheme) { - const { - scheme - } = Ant.OS.GUI.subwindows[this.name]; - return Ant.OS.GUI.htmlToScheme(scheme, this, this.host); - } - } - - main() { - if (this.data && this.data.title) { this.scheme.set("apptitle", this.data.title); } - this.scheme.set("resizable", false); - return this.scheme.set("minimizable", false); - } -} - -this.OS.GUI.BasicDialog = BasicDialog; - -class PromptDialog extends BasicDialog { - constructor() { - super("PromptDialog"); - } - - main() { - super.main(); - const $input = $(this.find("txtInput")); - if (this.data && this.data.label) { this.find("lbl").set("text", this.data.label); } - if (this.data && this.data.value) { $input.val(this.data.value); } - - (this.find("btnOk")).set("onbtclick", e => { - if (this.handle) { this.handle($input.val()); } - return this.quit(); - }); - - (this.find("btnCancel")).set("onbtclick", e => { - return this.quit(); - }); - - $input.keyup(e => { - if (e.which !== 13) { return; } - if (this.handle) { this.handle($input.val()); } - return this.quit(); - }); - - return $input.focus(); - } -} - - -PromptDialog.scheme = `\ - - - -
- -
- - -
- -
- - - - -
- - -\ -`; -this.OS.register("PromptDialog", PromptDialog); - -class TextDialog extends this.OS.GUI.BasicDialog { - constructor() { - super("TextDialog"); - } - - main() { - super.main(); - const $input = $(this.find("txtInput")); - if (this.data && this.data.value) { $input.val(this.data.value); } - - this.find("btnOk").set("onbtclick", e => { - const value = $input.val(); - if (!value || (value === "")) { return; } - if (this.handle) { this.handle(value); } - return this.quit(); - }); - - this.find("btnCancel").set("onbtclick", e => { - return this.quit(); - }); - - return $input.focus(); - } -} - -TextDialog.scheme = `\ - - - -
- -
- \ -`; - -Ant.OS.GUI.schemes.login = `\ -
-

Welcome to AntOS, please login

- - - -
-
\ -`; \ No newline at end of file diff --git a/src/core/gui.ts b/src/core/gui.ts new file mode 100644 index 0000000..892a87b --- /dev/null +++ b/src/core/gui.ts @@ -0,0 +1,1091 @@ +/* + * 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 + */ +// Copyright 2017-2018 Xuan Sang LE + +// AnTOS Web desktop is is licensed under the GNU General Public +// License v3.0, see the LICENCE file for more information + +// This program is free software: you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. + +// You should have received a copy of the GNU General Public License +//along with this program. If not, see https://www.gnu.org/licenses/. +namespace OS { + export namespace GUI { + /** + * + * + * @export + * @interface ShortcutType + */ + export interface ShortcutType { + ALT: GenericObject<(e: JQuery.MouseDownEvent) => void>; + CTRL: GenericObject<(e: JQuery.MouseDownEvent) => void>; + SHIFT: GenericObject<(e: JQuery.MouseDownEvent) => void>; + META: GenericObject<(e: JQuery.MouseDownEvent) => void>; + } + + /** + * + * + * @export + * @interface BasicItemType + */ + export interface BasicItemType { + text: string; + children?: BasicItemType[]; + nodes?: BasicItemType[]; + [propName: string]: any; + } + + export var workspace: string = "#desktop"; + + export var fullscreen = false; + + export var dialog: BaseDialog; + + var shortcut: ShortcutType = { + ALT: {}, + CTRL: {}, + SHIFT: {}, + META: {}, + }; + + /** + * + * + * @export + * @param {string} html + * @param {BaseModel} app + * @param {(Element | string)} parent + */ + export function htmlToScheme( + html: string, + app: BaseModel, + parent: Element | string + ): void { + const scheme = $.parseHTML(html); + + if (app.scheme) { + $(app.scheme).remove(); + } + $(parent as GenericObject).append(scheme); + app.scheme = scheme[0] as HTMLElement; + app.scheme.uify(app.observable); + app.main(); + app.show(); + } + + /** + * + * + * @export + * @param {string} path + * @param {BaseModel} app + * @param {(HTMLElement | string)} parent + */ + export function loadScheme( + path: string, + app: BaseModel, + parent: HTMLElement | string + ): void { + path.asFileHandle() + .read() + .then(function (x) { + if (!x) { + return; + } + htmlToScheme(x, app, parent); + }) + .catch((e) => { + announcer.oserror(__("Cannot load scheme: {0}", path), e); + }); + } + + /** + * + * + * @export + */ + export function clearTheme(): void { + $("head link#ostheme").attr("href", ""); + } + + /** + * + * + * @export + * @param {string} name + * @param {boolean} force + */ + export function loadTheme(name: string, force: boolean): void { + if (force) { + clearTheme(); + } + const path = `resources/themes/${name}/${name}.css`; + $("head link#ostheme").attr("href", path); + } + + /** + * + * + * @export + * @param {(string | BaseDialog)} d + * @param {GenericObject} data + * @returns {Promise} + */ + export function openDialog( + d: string | BaseDialog, + data: GenericObject + ): Promise { + return new Promise(function (resolve, reject) { + if (dialog) { + dialog.show(); + return resolve(); + } + if (typeof d === "string") { + if (!dialogs[d]) { + const ex = API.throwe("Dialog"); + return reject(ex); + } + dialog = new dialogs[d](); + } else { + dialog = d as GUI.BaseDialog; + } + + dialog.parent = undefined; + dialog.handle = resolve; + dialog.pid = -1; + dialog.data = data; + return dialog.init(); + }); + } + + /** + * + * + * @export + * @param {string} mime + * @returns {API.PackageMetaType[]} + */ + export function appsByMime(mime: string): API.PackageMetaType[] { + const metas: API.PackageMetaType[] = []; + for (let k in setting.system.packages) { + const v = setting.system.packages[k]; + if (v && v.app) { + metas.push(v); + } + } + let m: API.PackageMetaType; + const mimes: Array = []; + for (m of Array.from(metas)) { + if (m) { + mimes.push(m.mimes); + } + } + const apps: API.PackageMetaType[] = []; + // search app by mimes + const f = function (arr: string[], idx: number) { + try { + return arr.filter(function (m, i) { + if (mime.match(new RegExp(m, "g"))) { + if (apps.indexOf(metas[idx]) >= 0) { + return false; + } + apps.push(metas[idx]); + return false; + } + return false; + }); + } catch (e) { + return announcer.osfail( + __("Error find app by mimes {0}", mime), + e + ); + } + }; + let arr: string[]; + for (let i = 0; i < mimes.length; i++) { + arr = mimes[i]; + if (arr) { + f(arr, i); + } + } + return apps; + } + + /** + * + * + * @export + * @returns {{ + * [index: string]: API.PackageMetaType; + * }} + */ + export function appsWithServices(): { + [index: string]: API.PackageMetaType; + } { + const o: { [index: string]: API.PackageMetaType } = {}; + for (let k in setting.system.packages) { + const v = setting.system.packages[k]; + if (v && v.services && v.services.length > 0) { + o[k] = v; + } + } + return o; + } + + /** + * + * + * @export + * @param {AppArgumentsType} it + * @returns {void} + */ + export function openWith(it: AppArgumentsType): void { + if (!it) { + return; + } + if (it.type === "app" && it.app) { + return launch(it.app, []); + } + if (it.type === "app") { + return announcer.osinfo( + __("Application {0} is not executable", it.text) + ); + } + const apps = appsByMime(it.type === "dir" ? "dir" : it.mime); + if (apps.length === 0) { + return announcer.osinfo( + __("No application available to open {0}", it.filename) + ); + } + if (apps.length === 1) { + return launch(apps[0].app, [it]); + } + const list = Array.from(apps).map((e) => ({ + text: e.app, + icon: e.icon, + iconclass: e.iconclass, + })); + openDialog("SelectionDialog", { + title: __("Open with"), + data: list, + }).then((d) => launch(d.text, [it])); + } + + /** + * + * + * @export + * @param {string} app + * @param {AppArgumentsType[]} args + * @returns {void} + */ + export function forceLaunch( + app: string, + args: AppArgumentsType[] + ): void { + console.warn( + "This method is used for developing only, please use the launch method instead" + ); + unloadApp(app); + return launch(app, args); + } + + /** + * + * + * @param {string} app + */ + function unloadApp(app: string): void { + PM.killAll(app, true); + if (app[app] && app[app].style) { + $(app[app].style).remove(); + } + delete app[app]; + } + + /** + * + * + * @param {string} app + * @returns {Promise} + */ + function loadApp(app: string): Promise { + return new Promise(async function (resolve, reject) { + let path: string; + if (setting.system.packages[app].path) { + path = setting.system.packages[app].path; + } + const js = path + "/main.js"; + + try { + const d = await js.asFileHandle().read("script"); + try { + const data: API.PackageMetaType = await `${path}/package.json` + .asFileHandle() + .read("json"); + data.path = path; + if (application[app]) { + application[app].meta = data; + } + if (data.services) { + for (let v of data.services) { + application[v].meta = data; + } + } + //load css file + const css = `${path}/main.css`; + try { + const d_1 = await css.asFileHandle().onready(); + const stamp = new Date().timestamp(); + const el = $("", { + rel: "stylesheet", + type: "text/css", + href: `${API.handle.get}/${css}?stamp=${stamp}`, + }).appendTo("head"); + if (application[app]) { + application[app].style = el[0]; + } + return resolve(app); + } catch (e) { + return resolve(app); + } + } catch (e_1) { + return reject(__e(e_1)); + } + } catch (e_2) { + return reject(__e(e_2)); + } + }); + } + + /** + * + * + * @export + * @param {string} ph + * @returns {Promise} + */ + export function pushService(ph: string): Promise { + return new Promise(async function (resolve, reject) { + const arr = ph.split("/"); + const srv = arr[1]; + const app = arr[0]; + if (application[srv]) { + try { + const d = await OS.PM.createProcess( + srv, + application[srv] + ); + return resolve(d); + } catch (e) { + return reject(__e(e)); + } + } else { + try { + await loadApp(app); + if (!application[srv]) { + return reject( + API.throwe(__("Service not found: {0}", ph)) + ); + } + try { + const d_1 = await PM.createProcess( + srv, + application[srv] + ); + return resolve(d_1); + } catch (e_1) { + return reject(__e(e_1)); + } + } catch (e_2) { + return reject(__e(e_2)); + } + } + }); + } + + /** + * + * + * @export + * @param {string[]} srvs + * @returns {Promise} + */ + export function pushServices(srvs: string[]): Promise { + return new Promise(function (resolve, reject) { + if (!(srvs.length > 0)) { + return resolve(); + } + const srv = srvs.splice(0, 1)[0]; + return pushService(srv) + .then((d: any) => + pushServices(srvs) + .then(() => resolve()) + .catch((e) => reject(__e(e))) + ) + .catch(function (e: Error) { + announcer.osfail(__("Unable to load: {0}", srv), e); + return pushServices(srvs) + .then(() => resolve()) + .catch((e) => reject(__e(e))); + }); + }); + } + + /** + * + * + * @export + * @param {string} app + * @param {AppArgumentsType[]} args + */ + export function launch(app: string, args: AppArgumentsType[]): void { + if (!application[app]) { + // first load it + loadApp(app) + .then((a) => + PM.createProcess(app, application[app], args) + .catch((e) => + announcer.osfail( + __("Unable to launch: {0}", app), + e + ) + ) + ) + .catch((e) => + announcer.osfail(__("Unable to launch: {0}", app), e) + ); + } else { + // now launch it + if (application[app]) { + PM.createProcess( + app, + application[app], + args + ).catch((e: Error) => + announcer.osfail(__("Unable to launch: {0}", app), e) + ); + } else { + announcer.osfail( + __("Unable to find: {0}", app), + API.throwe("Application not found") + ); + } + } + } + + /** + * + * + * @export + * @param {BaseApplication} app + * @param {API.PackageMetaType} meta + * @returns {void} + */ + export function dock( + app: OS.application.BaseApplication, + meta: API.PackageMetaType + ): void { + // dock an application to a dock + // create a data object + const data = { + icon: null, + iconclass: meta.iconclass || "", + app, + onbtclick() { + return app.toggle(); + }, + }; + // TODO: this path is not good, need to create a blob of it + if (meta.icon) { + data.icon = `${meta.path}/${meta.icon}`; + } + // TODO: add default app icon class in system setting + // so that it can be themed + if (!meta.icon && !meta.iconclass) { + data.iconclass = "fa fa-cogs"; + } + const dock = $("#sysdock")[0] as tag.AppDockTag; + app.init(); + return app.one("rendered", function () { + dock.newapp(data); + app.sysdock = dock; + app.appmenu = $( + "[data-id = 'appmenu']", + "#syspanel" + )[0] as tag.MenuTag; + app.subscribe("systemlocalechange", function (name) { + app.updateLocale(name); + return app.update(); + }); + return app.subscribe("appregistry", function (m) { + if (m.name === app.name) { + return app.applySetting(m.data.m); + } + }); + }); + } + + /** + * + * + * @export + */ + export function toggleFullscreen(): void { + const el = document.documentElement; + if (fullscreen) { + if (document.exitFullscreen) { + document.exitFullscreen(); + } + if (document.mozCancelFullScreen) { + document.mozCancelFullScreen(); + } + if (document.webkitExitFullscreen) { + document.webkitExitFullscreen(); + } + if (document.cancelFullScreen) { + document.cancelFullScreen(); + } + } else { + if (el.requestFullscreen) { + el.requestFullscreen(); + } + if (el.mozRequestFullScreen) { + el.mozRequestFullScreen(); + } + if (el.webkitRequestFullscreen) { + el.webkitRequestFullscreen(); + } + if (el.msRequestFullscreen) { + el.msRequestFullscreen(); + } + } + } + + /** + * + * + * @export + * @param {BaseApplication} app + * @returns + */ + export function undock(app: application.BaseApplication) { + return ($("#sysdock")[0] as tag.AppDockTag).removeapp(app); + } + + /** + * + * + * @export + * @param {BaseService} srv + * @returns {void} + */ + export function attachservice(srv: application.BaseService): void { + ($("#syspanel")[0] as tag.SystemPanelTag).attachservice(srv); + srv.init(); + return srv.subscribe("systemlocalechange", (name) => srv.update()); + } + + /** + * + * + * @export + * @param {BaseService} srv + * @returns {void} + */ + export function detachservice(srv: application.BaseService): void { + return ($("#syspanel")[0] as tag.SystemPanelTag).detachservice(srv); + } + + /** + * + * + * @param {JQuery.MouseEventBase} event + * @returns {void} + */ + function bindContextMenu(event: JQuery.MouseEventBase): void { + var handle = function (e: HTMLElement) { + if (e.contextmenuHandle) { + return e.contextmenuHandle( + event, + $("#contextmenu")[0] as tag.MenuTag + ); + } else { + const p = $(e).parent().get(0); + if (p !== $("#workspace").get(0)) { + return handle(p); + } + } + }; + handle(event.target); + return event.preventDefault(); + } + + /** + * + * + * @export + * @param {string} k + * @param {(e: JQuery.MouseDownEvent) => void} f + * @returns {void} + */ + export function bindKey( + k: string, + f: (e: JQuery.MouseDownEvent) => void + ): void { + const arr = k.split("-"); + if (arr.length !== 2) { + return; + } + const fnk = arr[0].toUpperCase(); + const c = arr[1].toUpperCase(); + if (!shortcut[fnk]) { + return; + } + shortcut[fnk][c] = f; + } + + /** + * + * + * @export + * @param {setting.WPSettingType} obj + */ + export function wallpaper(obj: setting.WPSettingType): void { + if (obj) { + setting.appearance.wp = obj; + } + const wp = setting.appearance.wp; + $("body") + .css("background-image", `url(${API.handle.get}/${wp.url})`) + .css("background-size", wp.size) + .css("background-repeat", wp.repeat); + } + + /** + * + * + * @param {JQuery} el + * @param {string} text + * @param {JQuery.MouseEventBase} e + * @returns {void} + */ + function showTooltip( + el: JQuery, + text: string, + e: JQuery.MouseEventBase + ): void { + let left: number, top: number; + const label = $("#systooltip")[0] as tag.LabelTag; + var cb = function (ev: JQuery.MouseEventBase) { + if ($(ev.target).closest(el).length === 0) { + $(label).hide(); + return $(document).off("mousemove", cb); + } + }; + $(document).on("mousemove", cb); + const arr = text.split(/:(.+)/); + let tip = text; + if (arr.length > 1) { + tip = arr[1]; + } + const offset = $(el).offset(); + const w = $(el).width(); + const h = $(el).height(); + label.text = tip; + $(label).show(); + switch (arr[0]) { + case "cr": // center right of the element + left = offset.left + w + 5; + top = offset.top + h / 2 - $(label).height() / 2; + break; + case "ct": //ceter top + left = offset.left + w / 2 - $(label).width() / 2; + top = offset.top - $(label).height() - 5; + break; + default: + if (!e) { + return; + } + top = e.clientY + 5; + left = e.clientX + 5; + } + $(label) + .css("top", top + "px") + .css("left", left + "px"); + } + + /** + * + * + * @param {tag.FloatListTag} desktop + */ + function dkfetch(desktop: tag.FloatListTag): void { + const file = setting.desktop.path.asFileHandle(); + const fn = () => + file.read().then(function (d) { + if (d.error) { + return announcer.osfail(d.error, API.throwe(d.error)); + } + const items = []; + $.each(d.result, function (i, v) { + if ( + v.filename[0] === "." && + !setting.desktop.showhidden + ) { + return; + } + v.text = v.filename; + //v.text = v.text.substring(0,9) + "..." ifv.text.length > 10 + v.iconclass = v.type; + return items.push(v); + }); + desktop.data = items; + return desktop.calibrate(); + }); + + file.onready() + .then(() => fn()) + .catch(async function (e) { + // try to create the path + console.log(`${file.path} not found`); + const name = file.basename; + try { + const r = await file.parent().asFileHandle().mk(name); + return API.throwe("OS.VFS"); + } catch (e_1) { + return announcer.osfail(e_1.toString(), e_1); + } + }); + } + + /** + * + * + */ + function initDM(): void { + const scheme = $.parseHTML(schemes.ws); + $("#wrapper").append(scheme); + + announcer.observable.one("sysdockloaded", () => { + $(window).bind("keydown", function (event) { + const dock = $("#sysdock")[0] as tag.AppDockTag; + if (!dock) { + return; + } + const app = dock.selectedApp; + //return true unless app + const c = String.fromCharCode(event.which).toUpperCase(); + let fnk = undefined; + if (event.ctrlKey) { + fnk = "CTRL"; + } else if (event.metaKey) { + fnk = "META"; + } else if (event.shiftKey) { + fnk = "SHIFT"; + } else if (event.altKey) { + fnk = "ALT"; + } + + if (!fnk) { + return; + } + const r = app ? app.shortcut(fnk, c, event) : true; + if (!r) { + return event.preventDefault(); + } + if (!shortcut[fnk]) { + return; + } + if (!shortcut[fnk][c]) { + return; + } + shortcut[fnk][c](event); + return event.preventDefault(); + }) + } + ); + // system menu and dock + $("#syspanel")[0].uify(undefined); + $("#sysdock")[0].uify(undefined); + $("#systooltip")[0].uify(undefined); + $("#contextmenu")[0].uify(undefined); + + $("#workspace").contextmenu((e) => bindContextMenu(e)); + // tooltip + $(document).mouseover(function (e) { + const el: any = $(e.target).closest("[tooltip]"); + if (!(el.length > 0)) { + return; + } + return showTooltip( + el as JQuery, + $(el).attr("tooltip"), + e + ); + }); + + const fp = setting.desktop.path.asFileHandle(); + // desktop default file manager + const desktop = $(workspace)[0] as tag.FloatListTag; + + desktop.onready = function (e: tag.FloatListTag) { + e.observable = OS.announcer.observable; + window.onresize = function () { + announcer.trigger("desktopresize", undefined); + return e.calibrate(); + }; + + desktop.onlistselect = function (d: TagEventType) { + ($("#sysdock")[0] as tag.AppDockTag).selectedApp = null; + }; + + desktop.onlistdbclick = function (d: TagEventType) { + ($("#sysdock")[0] as tag.AppDockTag).selectedApp = null; + const it = desktop.selectedItem; + return openWith(it.data as AppArgumentsType); + }; + + //($ "#workingenv").on "click", (e) -> + // desktop[0].set "selected", -1 + + $(desktop).on("click", function (e) { + let el = $(e.target).parent(); + if (!(el.length > 0)) { + return; + } + el = el.parent(); + if (!(el.length > 0)) { + return; + } + if (el[0] !== desktop) { + return; + } + desktop.unselect(); + ($("#sysdock")[0] as tag.AppDockTag).selectedApp = null; + }); + + desktop.contextmenuHandle = function (e, m) { + if (e.target.tagName.toUpperCase() === "UL") { + desktop.unselect(); + } + ($("#sysdock")[0] as tag.AppDockTag).selectedApp = null; + let menu = [ + { text: __("Open"), dataid: "desktop-open" }, + { text: __("Refresh"), dataid: "desktop-refresh" }, + ]; + menu = menu.concat( + (() => { + const result = []; + for (let k in setting.desktop.menu) { + const v = setting.desktop.menu[k]; + result.push(v); + } + return result; + })() + ); + m.items = menu; + m.onmenuselect = function (evt: TagEventType) { + if(!evt.data || !evt.data.item) return; + const item = evt.data.item.data; + switch (item.dataid) { + case "desktop-open": + var it = desktop.selectedItem; + if (it) { + return openWith( + it.data as AppArgumentsType + ); + } + let arg = setting.desktop.path.asFileHandle() as AppArgumentsType; + arg.mime = "dir"; + arg.type = "dir"; + return openWith(arg); + case "desktop-refresh": + return dkfetch(desktop); + default: + if (item.app) { + return launch(item.app, item.args); + } + } + }; + return m.show(e); + }; + + dkfetch(desktop); + announcer.observable.on("VFS", function (d) { + if (["read", "publish", "download"].includes(d.data.m)) { + return; + } + if ( + d.data.file.hash() === fp.hash() || + d.data.file.parent().hash() === fp.hash() + ) { + return dkfetch(desktop); + } + }); + return announcer.ostrigger("desktoploaded", undefined); + }; + // mount it + desktop.uify(undefined); + } + + /** + * + * + * @export + */ + export function refreshDesktop(): void { + dkfetch($(workspace)[0] as tag.FloatListTag); + } + + /** + * + * + * @export + */ + export function login(): void { + const scheme = $.parseHTML(schemes.login); + $("#wrapper").append(scheme); + $("#btlogin").click(async function () { + const data: API.UserLoginType = { + username: $("#txtuser").val() as string, + password: $("#txtpass").val() as string, + }; + try { + const d = await API.handle.login(data); + if (d.error) { + return $("#login_error").html(d.error as string); + } + return startAntOS(d.result); + } catch (e) { + return $("#login_error").html("Login: server error"); + } + }); + $("#txtpass").keyup(function (e) { + if (e.which === 13) { + return $("#btlogin").click(); + } + }); + $("#txtuser").keyup(function (e) { + if (e.which === 13) { + return $("#btlogin").click(); + } + }); + } + + /** + * + * + * @export + * @param {*} conf + */ + export function startAntOS(conf: any): void { + // clean up things + OS.cleanup(); + // get setting from conf + OS.systemSetting(conf); + // load theme + loadTheme(setting.appearance.theme, true); + wallpaper(undefined); + OS.announcer.observable.one("syspanelloaded", async function () { + // TODO load packages list then build system menu + OS.announcer.observable.on("systemlocalechange", (name) => + $("#syspanel")[0].update() + ); + + const ret = await API.packages.cache(); + if (ret.result) { + return API.packages.fetch().then(function (r) { + let v: API.PackageMetaType; + if (r.result) { + const result = r.result as GenericObject< + API.PackageMetaType + >; + for (let k in result) { + v = result[k]; + v.text = v.name; + v.filename = k; + 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"; + } + } + setting.system.packages = result + ? result + : undefined; + } + + // GUI.refreshSystemMenu() + // GUI.buildSystemMenu() + // push startup services + // TODO: get services list from user setting + pushServices( + (() => { + const result = []; + for (let v of Array.from( + setting.system.startup.services + )) { + result.push(v); + } + return result; + })() + ); + return Array.from( + setting.system.startup.apps + ).map((a) => launch(a, [])); + }); + } + }); + //GUI.launch "DummyApp" + // initDM + API.setLocale(setting.system.locale).then(() => initDM()); + Ant.OS.announcer.observable.on("error", function(d) { + console.log(d.data.e) + }); + Ant.OS.announcer.observable.on("fail", function(d) { + console.log(d.data.e) + }); + } + + export const schemes: GenericObject = {}; + schemes.ws = `\ + +
+ + +
+ + +\ +`; + + schemes.login = `\ +
+

Welcome to AntOS, please login

+ + + +
+
\ +`; + } +} diff --git a/src/core/handles/RemoteHandle.js b/src/core/handles/RemoteHandle.js deleted file mode 100644 index 06b59f8..0000000 --- a/src/core/handles/RemoteHandle.js +++ /dev/null @@ -1,133 +0,0 @@ -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ -// Copyright 2017-2018 Xuan Sang LE - -// AnTOS Web desktop is is licensed under the GNU General Public -// License v3.0, see the LICENCE file for more information - -// This program is free software: you can redistribute it and/or -// modify it under the terms of the GNU General Public License as -// published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. - -// You should have received a copy of the GNU General Public License -//along with this program. If not, see https://www.gnu.org/licenses/. -Ant.OS.API.HOST = Ant.location.hostname + (Ant.location.port ?`:${Ant.location.port}` : ""); -Ant.OS.API.REST = `${Ant.location.protocol}//${Ant.OS.API.HOST}`; - -Ant.OS.API.handle = { - // get file, require authentification - get: `${Ant.OS.API.REST}/VFS/get`, - // get shared file with publish - shared: `${Ant.OS.API.REST}/VFS/shared`, - scandir(p) { - const path = `${Ant.OS.API.REST}/VFS/scandir`; - return Ant.OS.API.post(path, { path: p }); - }, - - mkdir(p) { - const path = `${Ant.OS.API.REST}/VFS/mkdir`; - return Ant.OS.API.post(path, { path: p }); - }, - - sharefile(p, pub) { - const path = `${Ant.OS.API.REST}/VFS/publish`; - return Ant.OS.API.post(path, { path: p , publish: pub }); - }, - - fileinfo(p) { - const path = `${Ant.OS.API.REST}/VFS/fileinfo`; - return Ant.OS.API.post(path, { path: p }); - }, - - readfile(p, t) { - const path = `${Ant.OS.API.REST}/VFS/get/`; - return Ant.OS.API.get(path + p, t); - }, - - move(s, d) { - const path = `${Ant.OS.API.REST}/VFS/move`; - return Ant.OS.API.post(path, { src: s, dest: d }); - }, - - delete(p) { - const path = `${Ant.OS.API.REST}/VFS/delete`; - return Ant.OS.API.post(path, { path: p }); - }, - - fileblob(p) { - const path = `${Ant.OS.API.REST}/VFS/get/`; - return Ant.OS.API.blob(path + p); - }, - - packages(d) { - const path = `${Ant.OS.API.REST}/system/packages`; - return Ant.OS.API.post(path, d); - }, - - upload(d) { - const path = `${Ant.OS.API.REST}/VFS/upload`; - return Ant.OS.API.upload(path, d); - }, - - write(p, d) { - const path = `${Ant.OS.API.REST}/VFS/write`; - return Ant.OS.API.post(path, { path: p, data: d }); - }, - - scanapp(p, c ) { - let path; - return path = `${Ant.OS.API.REST}/system/application`; - }, - - apigateway(d, ws) { - if (ws) { - return new Promise(function(resolve, reject) { - try { - const path = `${Ant.OS.API.HOST}/system/apigateway?ws=1`; - const proto = window.location.protocol === "https:" ? "wss://" : "ws://"; - const socket = new WebSocket(proto + path); - return resolve(socket); - } catch (e) { - return reject(__e(e)); - } - }); - } else { - const path = `${Ant.OS.API.REST}/system/apigateway?ws=0`; - return Ant.OS.API.post(path, d); - } - }, - - auth() { - const p = `${Ant.OS.API.REST}/user/auth`; - return Ant.OS.API.post(p, {}); - }, - - login(d) { - const p = `${Ant.OS.API.REST}/user/login`; - return Ant.OS.API.post(p, d); - }, - - logout() { - const p = `${Ant.OS.API.REST}/user/logout`; - return Ant.OS.API.post(p, {}); - }, - - setting() { - const p = `${Ant.OS.API.REST}/system/settings`; - return Ant.OS.API.post(p, Ant.OS.setting); - }, - - dbquery(cmd, d) { - const path = `${Ant.OS.API.REST}/VDB/${cmd}`; - return Ant.OS.API.post(path, d); - } -}; \ No newline at end of file diff --git a/src/core/handles/RemoteHandle.ts b/src/core/handles/RemoteHandle.ts new file mode 100644 index 0000000..dcce70b --- /dev/null +++ b/src/core/handles/RemoteHandle.ts @@ -0,0 +1,173 @@ +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +// Copyright 2017-2018 Xuan Sang LE + +// AnTOS Web desktop is is licensed under the GNU General Public +// License v3.0, see the LICENCE file for more information + +// This program is free software: you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. + +// You should have received a copy of the GNU General Public License +//along with this program. If not, see https://www.gnu.org/licenses/. + +namespace OS { + export namespace API { + export interface UserLoginType { + username: string; + password: string; + } + export interface PackageCommandType { + command: string; + args: GenericObject; + } + export interface RequestResult { + error: boolean | string; + result: + | string + | boolean + | GenericObject + | any[] + | FileInfoType + | FileInfoType[]; + } + + let loc: any = { hostname: "localhost", port: "80", protocol: "http" }; + + if (Ant.location) loc = Ant.location; + + export var HOST: string = + loc.hostname + (loc.port ? `:${loc.port}` : ""); + export var REST: string = `${loc.protocol}//${HOST}`; + + export namespace handle { + // get file, require authentification + export var get: string = `${REST}/VFS/get`; + // get shared file with publish + export var shared: string = `${REST}/VFS/shared`; + export function scandir(p: string): Promise { + const path = `${REST}/VFS/scandir`; + return API.post(path, { path: p }); + } + + export function mkdir(p: string): Promise { + const path = `${API.REST}/VFS/mkdir`; + return API.post(path, { path: p }); + } + + export function sharefile( + p: string, + pub: boolean + ): Promise { + const path = `${API.REST}/VFS/publish`; + return API.post(path, { path: p, publish: pub }); + } + + export function fileinfo(p: string): Promise { + const path = `${API.REST}/VFS/fileinfo`; + return API.post(path, { path: p }); + } + + export function readfile(p: string, t: string): Promise { + const path = `${API.REST}/VFS/get/`; + return API.get(path + p, t); + } + + export function move(s: string, d: string): Promise { + const path = `${API.REST}/VFS/move`; + return API.post(path, { src: s, dest: d }); + } + + export function remove(p: string): Promise { + const path = `${API.REST}/VFS/delete`; + return API.post(path, { path: p }); + } + + export function fileblob(p: string): Promise { + const path = `${API.REST}/VFS/get/`; + return API.blob(path + p); + } + + export function packages( + d: PackageCommandType + ): Promise { + const path = `${API.REST}/system/packages`; + return API.post(path, d); + } + + export function upload(d: string): Promise { + const path = `${API.REST}/VFS/upload`; + return API.upload(path, d); + } + + export function write( + p: string, + d: string + ): Promise { + const path = `${API.REST}/VFS/write`; + return API.post(path, { path: p, data: d }); + } + export function apigateway( + d: GenericObject, + ws: boolean + ): Promise { + if (ws) { + return new Promise(function (resolve, reject) { + try { + const path = `${API.HOST}/system/apigateway?ws=1`; + const proto = + window.location.protocol === "https:" + ? "wss://" + : "ws://"; + const socket = new WebSocket(proto + path); + return resolve(socket); + } catch (e) { + return reject(__e(e)); + } + }); + } else { + const path = `${API.REST}/system/apigateway?ws=0`; + return API.post(path, d); + } + } + + export function auth(): Promise { + const p = `${API.REST}/user/auth`; + return API.post(p, {}); + } + + export function login(d: UserLoginType): Promise { + const p = `${API.REST}/user/login`; + return API.post(p, d); + } + + export function logout(): Promise { + const p = `${API.REST}/user/logout`; + return API.post(p, {}); + } + + export function setting(): Promise { + const p = `${API.REST}/system/settings`; + return API.post(p, setting); + } + + export function dbquery( + cmd: string, + d: GenericObject + ): Promise { + const path = `${API.REST}/VDB/${cmd}`; + return API.post(path, d); + } + } + } +} diff --git a/src/core/pm.ts b/src/core/pm.ts new file mode 100644 index 0000000..2816ad0 --- /dev/null +++ b/src/core/pm.ts @@ -0,0 +1,136 @@ +namespace OS { + export namespace PM { + export type ProcessType = application.BaseApplication | application.BaseService; + export type ProcessTypeClass = { + new (args: AppArgumentsType[]): T; + }; + export var pidalloc: number = 0; + export var processes: GenericObject = {}; + /** + * + * + * @export + * @param {string} app + * @param {ProcessTypeClass} cls + * @param {GUI.AppArgumentsType[]} [args] + * @returns {Promise} + */ + export function createProcess( + app: string, + cls: ProcessTypeClass, + args?: AppArgumentsType[] + ): Promise { + return new Promise(function (resolve, reject) { + let metaclass = (cls as any) as typeof BaseModel; + const f = function () { + //if it is single ton + // and a process is existing + // just return it + let obj: ProcessType; + if ( + metaclass.singleton && + PM.processes[app] && + PM.processes[app].length === 1 + ) { + obj = PM.processes[ + app + ][0] as application.BaseApplication; + obj.show(); + } else { + if (!PM.processes[app]) { + PM.processes[app] = []; + } + obj = new cls(args); + obj.birth = new Date().getTime(); + PM.pidalloc++; + obj.pid = PM.pidalloc; + PM.processes[app].push(obj); + if (metaclass.type === ModelType.Application) { + GUI.dock( + obj as application.BaseApplication, + metaclass.meta + ); + } else { + GUI.attachservice(obj as application.BaseService); + } + } + return obj; + }; + if (metaclass.dependencies) { + const libs = Array.from(metaclass.dependencies); + return API.require(libs) + .then(() => resolve(f())) + .catch((e: Error) => reject(__e(e))); + } else { + return resolve(f()); + } + }); + } + + /** + * + * + * @export + * @param {number} pid + * @returns {BaseModel} + */ + export function appByPid(pid: number): BaseModel { + let app = undefined; + const find = function (l: Array) { + for (let a of Array.from(l)) { + if (a.pid === pid) { + return a; + } + } + }; + for (let k in PM.processes) { + const v = PM.processes[k]; + app = find(v); + if (app) { + break; + } + } + return app; + } + + /** + * + * + * @export + * @param {OS.GUI.BaseModel} app + * @returns {void} + */ + export function kill(app: BaseModel): void { + if (!app.name || !PM.processes[app.name]) { + return; + } + + const i = PM.processes[app.name].indexOf(app); + if (i >= 0) { + if (application[app.name].type === ModelType.Application) { + GUI.undock(app as application.BaseApplication); + } else { + GUI.detachservice(app as application.BaseService); + } + announcer.unregister(app); + delete PM.processes[app.name][i]; + PM.processes[app.name].splice(i, 1); + } + } + + /** + * + * + * @export + * @param {string} app + * @param {boolean} force + * @returns {void} + */ + export function killAll(app: string, force: boolean): void { + if (!PM.processes[app]) { + return; + } + Array.from(PM.processes[app]).map((a) => a.quit(force)); + } + } +} diff --git a/src/core/settings.js b/src/core/settings.js deleted file mode 100644 index bdec423..0000000 --- a/src/core/settings.js +++ /dev/null @@ -1,118 +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 - */ - // Copyright 2017-2018 Xuan Sang LE - -// AnTOS Web desktop is is licensed under the GNU General Public -// License v3.0, see the LICENCE file for more information - -// This program is free software: you can redistribute it and/or -// modify it under the terms of the GNU General Public License as -// published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. - -// You should have received a copy of the GNU General Public License -//along with this program. If not, see https://www.gnu.org/licenses/. - self.OS.systemSetting = function(conf) { - if (conf.desktop) { Ant.OS.setting.desktop = conf.desktop; } - if (conf.applications) { Ant.OS.setting.applications = conf.applications; } - if (conf.appearance) { Ant.OS.setting.appearance = conf.appearance; } - if (!Ant.OS.setting.appearance.wp) { Ant.OS.setting.appearance.wp = { - url: "os://resources/themes/system/wp/wp3.jpg", - size: "cover", - repeat: "repeat" - }; } - if (!Ant.OS.setting.appearance.wps) { Ant.OS.setting.appearance.wps = []; } - if (!Ant.OS.setting.applications) { Ant.OS.setting.applications = {}; } - Ant.OS.setting.user = conf.user; - if (conf.VFS) { Ant.OS.setting.VFS = conf.VFS; } - if (!Ant.OS.setting.desktop.path) { Ant.OS.setting.desktop.path = "home://.desktop"; } - if (!Ant.OS.setting.desktop.menu) { Ant.OS.setting.desktop.menu = {}; } - if (!Ant.OS.setting.VFS.mountpoints) { Ant.OS.setting.VFS.mountpoints = [ - //TODO: multi app try to write to this object, it neet to be cloned - { text: "__(Applications)", path: 'app://', iconclass: "fa fa-adn", type: "app" }, - { text: "__(Home)", path: 'home://', iconclass: "fa fa-home", type: "fs" }, - { text: "__(Desktop)", path: Ant.OS.setting.desktop.path , iconclass: "fa fa-desktop", type: "fs" }, - { text: "__(OS)", path: 'os://', iconclass: "fa fa-inbox", type: "fs" }, - { text: "__(Google Drive)", path: 'gdv://', iconclass: "fa fa-inbox", type: "fs" }, - { text: "__(Shared)", path: 'shared://' , iconclass: "fa fa-share-square", type: "fs" } - ]; } - - if (conf.system) { Ant.OS.setting.system = conf.system; } - if (!Ant.OS.setting.system.startup) { Ant.OS.setting.system.startup = { - services: [ - "Syslog/PushNotification", - "Syslog/Calendar" - ], - apps: [] - }; } - if (!Ant.OS.setting.system.error_report) { - Ant.OS.setting.system.error_report = "https://os.iohub.dev/report"; - } - if (!Ant.OS.setting.system.pkgpaths) { Ant.OS.setting.system.pkgpaths = { - user: "home://.packages", - system: "os://packages" - }; } - if (!Ant.OS.setting.system.locale) { Ant.OS.setting.system.locale = "en_GB"; } - if (!Ant.OS.setting.system.menu) { Ant.OS.setting.system.menu = {}; } - if (!Ant.OS.setting.system.repositories) { Ant.OS.setting.system.repositories = []; } - if (!Ant.OS.setting.appearance.theme) { Ant.OS.setting.appearance.theme = "antos_dark"; } - if (!Ant.OS.setting.appearance.themes) { - Ant.OS.setting.appearance.themes = [ - { - text: "AntOS light", - name: "antos_light" - }, - { - text: "AntOS dark", - name: "antos_dark" - } - ]; - } - if (!Ant.OS.setting.VFS.gdrive) { Ant.OS.setting.VFS.gdrive = { - CLIENT_ID: "", - API_KEY: "", - apilink: "https://apis.google.com/js/api.js", - DISCOVERY_DOCS: ["https://www.googleapis.com/discovery/v1/apis/drive/v3/rest"], - SCOPES: 'https://www.googleapis.com/auth/drive' - }; } - - //search for app - return Ant.OS.API.onsearch("__(Applications)", function(t) { - const ar = []; - const term = new RegExp(t, "i"); - for (let k in Ant.OS.setting.system.packages) { - const v = Ant.OS.setting.system.packages[k]; - if (v.app) {var e, k1, v1; - - if ((v.name.match(term)) || (v.description && v.description.match(term))) { - v1 = {}; - for (k1 in v) { e = v[k1]; if (k1 !== "selected") { v1[k1] = e; } } - v1.detail = [{ text: v1.path }]; - v1.complex = true; - ar.push(v1); - } else if (v.mimes) { - for (let m of Array.from(v.mimes)) { - if (t.match((new RegExp(m, "g")))) { - v1 = {}; - for (k1 in v) { e = v[k1]; if (k1 !== "selected") { v1[k1] = v[k1]; } } - v1.detail = [{ text: v1.path }]; - v1.complex = true; - ar.push(v1); - break; - } - } - } - } - } - return ar; - }); -}; \ No newline at end of file diff --git a/src/core/settings.ts b/src/core/settings.ts new file mode 100644 index 0000000..631813e --- /dev/null +++ b/src/core/settings.ts @@ -0,0 +1,334 @@ +/* + * 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 + */ +// Copyright 2017-2018 Xuan Sang LE + +// AnTOS Web desktop is is licensed under the GNU General Public +// License v3.0, see the LICENCE file for more information + +// This program is free software: you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. + +// You should have received a copy of the GNU General Public License +//along with this program. If not, see https://www.gnu.org/licenses/. + +namespace OS { + export namespace setting { + + /** + * + * + * @export + * @interface UserSettingType + */ + export interface UserSettingType { + name: string; + username: string; + id: number; + group?: { [index: number]: string }; + [propName: string]: any; + } + + /** + * + * + * @export + * @interface DesktopSettingType + */ + export interface DesktopSettingType { + path: string; + menu: any[]; + showhidden: boolean; + [propName: string]: any; + } + + /** + * + * + * @export + * @interface WPSettingType + */ + export interface WPSettingType { + repeat: string; + size: string; + url: string; + } + + /** + * + * + * @export + * @interface ThemeSettingType + */ + export interface ThemeSettingType { + name: string; + text: string; + } + + /** + * + * + * @export + * @interface AppearanceSettingType + */ + export interface AppearanceSettingType { + theme: string; + themes: ThemeSettingType[]; + wp: WPSettingType; + wps: string[]; + } + + /** + * + * + * @export + * @interface VFSMountPointSettingType + */ + export interface VFSMountPointSettingType { + path: string; + text: string; + [propName: string]: any; + } + + /** + * + * + * @export + * @interface VFSSettingType + */ + export interface VFSSettingType { + mountpoints: VFSMountPointSettingType[]; + [propName: string]: any; + } + + /** + * + * + * @export + * @interface SystemSettingType + */ + export interface SystemSettingType { + error_report: string; + locale: string; + menu: any[]; + packages: { [index: string]: API.PackageMetaType }; + pkgpaths: { + user: string; + system: string; + }; + repositories: { + text: string; + url: string; + }[]; + startup: { + apps: string[]; + services: string[]; + }; + } + + export var user: UserSettingType; + + export var applications: GenericObject = {}; + + export var desktop: DesktopSettingType; + + export var appearance: AppearanceSettingType; + + export var VFS: VFSSettingType; + + export var system: SystemSettingType; + } + + /** + * + * + * @export + */ + export function resetSetting(): void { + setting.desktop = { + path: "home://.desktop", + menu: [], + showhidden: false + }; + setting.user = { + name: undefined, + username: undefined, + id: 0, + }; + + setting.appearance = { + theme: "antos_dark", + themes: [ + { + text: "AntOS light", + name: "antos_light", + }, + { + text: "AntOS dark", + name: "antos_dark", + }, + ], + wp: { + url: "os://resources/themes/system/wp/wp3.jpg", + size: "cover", + repeat: "repeat", + }, + wps: [], + }; + + setting.VFS = { + mountpoints: [ + //TODO: multi app try to write to this object, it neet to be cloned + { + text: "__(Applications)", + path: "app://", + iconclass: "fa fa-adn", + type: "app", + }, + { + text: "__(Home)", + path: "home://", + iconclass: "fa fa-home", + type: "fs", + }, + { + text: "__(Desktop)", + path: setting.desktop.path, + iconclass: "fa fa-desktop", + type: "fs", + }, + { + text: "__(OS)", + path: "os://", + iconclass: "fa fa-inbox", + type: "fs", + }, + { + text: "__(Google Drive)", + path: "gdv://", + iconclass: "fa fa-inbox", + type: "fs", + }, + { + text: "__(Shared)", + path: "shared://", + iconclass: "fa fa-share-square", + type: "fs", + }, + ], + }; + + OS.setting.system = { + error_report: "https://os.iohub.dev/report", + locale: "en_GB", + menu: [], + packages: {}, + pkgpaths: { + user: "home://.packages", + system: "os://packages", + }, + repositories: [], + startup: { + apps: [], + services: [ + "Syslog/PushNotification", "Syslog/Calendar" + ], + }, + }; + } + + /** + * + * + * @export + * @param {*} conf + */ + export function systemSetting(conf: any) { + resetSetting(); + if (conf.desktop) { + setting.desktop = conf.desktop; + } + if (conf.applications) { + setting.applications = conf.applications; + } + if (conf.appearance) { + setting.appearance = conf.appearance; + } + if (conf.user) { + setting.user = conf.user; + } + if (conf.VFS) { + setting.VFS = conf.VFS; + } + + if (conf.system) { + setting.system = conf.system; + } + + if (!setting.VFS.gdrive) { + setting.VFS.gdrive = { + CLIENT_ID: "", + API_KEY: "", + apilink: "https://apis.google.com/js/api.js", + DISCOVERY_DOCS: [ + "https://www.googleapis.com/discovery/v1/apis/drive/v3/rest", + ], + SCOPES: "https://www.googleapis.com/auth/drive", + }; + } + } + + //search for app + API.onsearch("__(Applications)", function (t) { + const ar = []; + const term = new RegExp(t, "i"); + for (let k in setting.system.packages) { + const v = setting.system.packages[k]; + if (v.app) { + var e, k1, v1; + if ( + v.name.match(term) || + (v.description && v.description.match(term)) + ) { + v1 = {}; + for (k1 in v) { + e = v[k1]; + if (k1 !== "selected") { + v1[k1] = e; + } + } + v1.detail = [{ text: v1.path }]; + v1.complex = true; + ar.push(v1); + } else if (v.mimes) { + for (let m of Array.from(v.mimes)) { + if (t.match(new RegExp(m, "g"))) { + v1 = {}; + for (k1 in v) { + e = v[k1]; + if (k1 !== "selected") { + v1[k1] = v[k1]; + } + } + v1.detail = [{ text: v1.path }]; + v1.complex = true; + ar.push(v1); + break; + } + } + } + } + } + return ar; + }); +} diff --git a/src/core/tags/AppDockTag.js b/src/core/tags/AppDockTag.js deleted file mode 100644 index 1dc9f4e..0000000 --- a/src/core/tags/AppDockTag.js +++ /dev/null @@ -1,86 +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 - */ -class AppDockTag extends Ant.OS.GUI.BaseTag { - constructor(r, o) { - super(r, o); - this.setopt("onappselect", function(e) {}); - this.setopt("items", []); - this.setopt("selectedApp", undefined); - this.root.newapp = a => this.addApp(a); - this.root.removeapp = a => this.removeApp(a); - } - - __selectedApp__(v) { - let el = undefined; - for (let it of Array.from(this.get("items"))) { - it.app.blur(); - $(it.domel).removeClass(); - if (v && (v === it.app)) { el = it.domel; } - } - if (!el) { return; } - $(el).addClass("selected"); - return $(Ant.OS.GUI.workspace)[0].unselect(); - } - - addApp(item) { - this.get("items").push(item); - const el = $(""); - el.appendTo(this.root); - el[0].uify(this.observable); - el[0].set("*", item); - el.attr("tooltip", `cr:${item.app.title()}`); - item.domel = el[0]; - el[0].set("onbtclick", e => { - e.id = this.aid(); - e.data.app = item; - return item.app.show(); - }); - return this.set("selectedApp", item.app); - } - - removeApp(a) { - let i = -1; - const iterable = this.get("items"); - for (let k = 0; k < iterable.length; k++) { - const v = iterable[k]; - if (v.app.pid === a.pid) { - i = k; - break; - } - } - - if (i !== -1) { - const items = this.get("items"); - delete items[i].app; - items.splice(i, 1); - return $($(this.root).children()[i]).remove(); - } - } - - mount() { - this.root.contextmenuHandle = (e, m) => { - if (e.target === this.root) { return; } - const bt = $(e.target).closest("afx-button"); - const app = bt[0].get("app"); - m.set("items", [ - { text: "__(Show)", dataid: "show" }, - { text: "__(Hide)", dataid: "hide" }, - { text: "__(Close)", dataid: "quit" } - ]); - m.set("onmenuselect", function(evt) { - const item = evt.data.item.get("data"); - if(app[item.dataid]) { - return app[item.dataid](); - } - }); - return m.show(e); - }; - return Ant.OS.announcer.trigger("sysdockloaded"); - } -} - -Ant.OS.GUI.define("afx-apps-dock", AppDockTag); \ No newline at end of file diff --git a/src/core/tags/AppDockTag.ts b/src/core/tags/AppDockTag.ts new file mode 100644 index 0000000..0519a24 --- /dev/null +++ b/src/core/tags/AppDockTag.ts @@ -0,0 +1,206 @@ +/* + * 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 { + export namespace GUI { + /** + * + * + * @export + * @interface AppDockItemType + */ + export interface AppDockItemType { + app: application.BaseApplication; + domel?: AFXTag; + [propName: string]: any; + } + + export namespace tag { + /** + * + * + * @export + * @class AppDockTag + * @extends {AFXTag} + */ + export class AppDockTag extends AFXTag { + + private _onappselect: TagEventCallback; + private _items: AppDockItemType[]; + private _selectedApp: application.BaseApplication; + + /** + *Creates an instance of AppDockTag. + * @memberof AppDockTag + */ + constructor() { + super(); + this._onappselect = (e) => {}; + } + + /** + * + * + * @protected + * @param {*} [d] + * @memberof AppDockTag + */ + protected reload(d?: any): void { + } + + /** + * + * + * @protected + * @memberof AppDockTag + */ + protected init(): void { + this._items = []; + } + + /** + * + * + * @protected + * @returns {TagLayoutType[]} + * @memberof AppDockTag + */ + protected layout(): TagLayoutType[] { + return []; + } + + /** + * + * + * @protected + * @param {*} [d] + * @memberof AppDockTag + */ + protected refresh(d?: any): void {} + + /** + * + * + * @readonly + * @type {AppDockItemType[]} + * @memberof AppDockTag + */ + get items(): AppDockItemType[] { + return this._items; + } + + /** + * + * + * @memberof AppDockTag + */ + set selectedApp(v: application.BaseApplication) { + this._selectedApp = v; + let el = undefined; + for (let it of this.items) { + it.app.blur(); + $(it.domel).removeClass(); + if (v && v === it.app) { + el = it.domel; + } + } + if (!el) { + return; + } + $(el).addClass("selected"); + ($(Ant.OS.GUI.workspace)[0] as FloatListTag).unselect(); + } + + /** + * + * + * @type {BaseApplication} + * @memberof AppDockTag + */ + get selectedApp(): application.BaseApplication { + return this._selectedApp; + } + + /** + * + * + * @param {AppDockItemType} item + * @memberof AppDockTag + */ + newapp(item: AppDockItemType): void { + this.items.push(item); + const el = $(""); + const bt = el[0] as ButtonTag; + el.appendTo(this); + el[0].uify(this.observable); + bt.set(item); + el.attr("tooltip", `cr:${item.app.title()}`); + item.domel = bt; + bt.onbtclick = (e) => { + e.id = this.aid; + e.data.item = item; + item.app.show(); + }; + this.selectedApp = item.app; + } + + /** + * + * + * @param {BaseApplication} a + * @memberof AppDockTag + */ + removeapp(a: application.BaseApplication): void { + let i = -1; + const iterable = this.items; + for (let k = 0; k < iterable.length; k++) { + const v = iterable[k]; + if (v.app.pid === a.pid) { + i = k; + break; + } + } + + if (i !== -1) { + delete this.items[i].app; + this.items.splice(i, 1); + $($(this).children()[i]).remove(); + } + } + + /** + * + * + * @protected + * @memberof AppDockTag + */ + protected mount(): void { + this.contextmenuHandle = (e, m) => { + if (e.target === this) { + return; + } + const bt = $(e.target).closest("afx-button"); + const app = bt[0].get("app"); + m.items = [ + { text: "__(Show)", dataid: "show" }, + { text: "__(Hide)", dataid: "hide" }, + { text: "__(Close)", dataid: "quit" }, + ]; + m.onmenuselect = function (evt) { + const item = evt.data.item.get("data"); + if (app[item.dataid]) { + return app[item.dataid](); + } + }; + return m.show(e); + }; + announcer.trigger("sysdockloaded", undefined); + } + } + define("afx-apps-dock", AppDockTag); + } + } +} diff --git a/src/core/tags/ButtonTag.js b/src/core/tags/ButtonTag.js deleted file mode 100644 index 1c88b82..0000000 --- a/src/core/tags/ButtonTag.js +++ /dev/null @@ -1,78 +0,0 @@ -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ -class ButtonTag extends Ant.OS.GUI.BaseTag { - constructor(r, o) { - super(r, o); - this.setopt("color", undefined); - this.setopt("icon", undefined); - this.setopt("iconclass", undefined); - this.setopt("text", ""); - this.setopt("enable", true); - this.setopt("selected", false); - this.setopt("toggle", false); - this.setopt("onbtclick", function() {}); - } - - - __color__(v) { - return this.refs.label.set("color", v); - } - - __icon__(v) { - return this.refs.label.set("icon", v); - } - - __iconclass__(v) { - return this.refs.label.set("iconclass", v); - } - - __text__(v) { - return this.refs.label.set("text", v); - } - - __enable__(v) { - return $(this.refs.button).prop("disabled", !(this.get("enable"))); - } - - __selected__(v) { - $(this.button).removeClass(); - if (v) { return $(this.button).addClass("selected"); } - } - - mount() { - this.root.trigger = () => { - return $(this.refs.button).trigger("click"); - }; - - return $(this.refs.button).click(e => { - return this.btclickhd(e); - }); - } - - btclickhd(e) { - const hd = this.get("onbtclick"); - if (typeof hd === "string") { - eval(hd); - } else if (hd) { - hd({ id: this.aid(), data: e }); - } - this.observable.trigger("btclick", { id: this.aid(), data: e }); - if (this.toggle) { - return this.set("selected", !this.get("selected")); - } - } - - layout() { - return [{ - el: "Button", ref: "button", children: [ - { el: "afx-label", ref: "label" } - ] - }]; - } -} - - -Ant.OS.GUI.define("afx-button", ButtonTag); \ No newline at end of file diff --git a/src/core/tags/ButtonTag.ts b/src/core/tags/ButtonTag.ts new file mode 100644 index 0000000..4419b57 --- /dev/null +++ b/src/core/tags/ButtonTag.ts @@ -0,0 +1,95 @@ +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +namespace OS { + export namespace GUI { + export namespace tag { + export class ButtonTag extends AFXTag { + private _selected: boolean; + private _onbtclick: TagEventCallback; + constructor() { + super(); + } + set onbtclick(v: TagEventCallback) + { + this._onbtclick = v; + } + set icon(v: string) { + $(this).attr("icon", v); + (this.refs.label as LabelTag).icon = v; + } + + set iconclass(v: string) { + $(this).attr("iconclass", v); + (this.refs.label as LabelTag).iconclass = v; + } + + set text(v: string | FormatedString) { + (this.refs.label as LabelTag).text = v; + } + get text(): string| FormatedString { + return (this.refs.label as LabelTag).text; + } + set enable(v: boolean) { + $(this.refs.button).prop("disabled", !v); + } + + get enable(): boolean { + return !$(this.refs.button).prop("disabled"); + } + + set selected(v: boolean) { + $(this.refs.button).removeClass(); + this.attsw(v, "selected"); + if (v) { + $(this.refs.button).addClass("selected"); + } + } + get selected(): boolean { + return this.hasattr("selected"); + } + + set toggle(v: boolean) { + this.attsw(v, "toggle"); + } + + get toggle(): boolean { + return this.hasattr("toggle"); + } + + protected mount() { + this._onbtclick = (e) => {}; + $(this.refs.button).click((e) => { + const evt: TagEventType = { + id: this.aid, + data: e, + }; + this._onbtclick(evt); + this.observable.trigger("btclick", evt); + if (this.toggle) { + return (this.selected = !this.selected); + } + }); + } + protected init(): void { + this.enable = true; + this.toggle = false; + } + protected calibrate(): void {} + reload(d?: any): void {} + protected layout(): TagLayoutType[] { + return [ + { + el: "Button", + ref: "button", + children: [{ el: "afx-label", ref: "label" }], + }, + ]; + } + } + define("afx-button", ButtonTag); + } + } +} diff --git a/src/core/tags/CalendarTag.js b/src/core/tags/CalendarTag.js deleted file mode 100644 index 450767e..0000000 --- a/src/core/tags/CalendarTag.js +++ /dev/null @@ -1,150 +0,0 @@ -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * DS202: Simplify dynamic range loops - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ -class CalendarTag extends Ant.OS.GUI.BaseTag { - constructor(r, o) { - super(r, o); - this.setopt("day", 0); - this.setopt("ondateselect", function() {}); - this.setopt("selectedDate", undefined); - this.day = 0; - this.month = 0; - this.year = 0; - } - - mount() { - $(this.root).css("height", "100%"); - $(this.refs.grid).css("width", "100%"); - $(this.refs.prev).click(e => this.prevmonth()); - $(this.refs.next).click(e => this.nextmonth()); - this.refs.grid.set("header", [ - { text: "__(Sun)" }, - { text: "__(Mon)" }, - { text: "__(Tue)" }, - { text: "__(Wed)" }, - { text: "__(Thu)" }, - { text: "__(Fri)" }, - { text: "__(Sat)" } - ]); - this.refs.grid.set("oncellselect", e => { - return this.dateselect(e); - }); - - this.observable.on("resize", e => this.calibrate()); - this.calibrate(); - return this.calendar(null); - } - - dateselect(e) { - if (!e.data.item) { return; } - const value = e.data.item.get("data").text; - if (value === "") { return; } - const evt = { id: this.aid() , data: new Date(this.year, this.month, value) }; - this.get("ondateselect")(evt); - this.set("selectedDate", evt.data); - return this.observable.trigger("dateselect", evt); - } - - calibrate() { - return $(this.refs.grid) - .css("height", `${$(this.root).height() - $(this.refs.ctrl).height()}px`); - } - prevmonth() { - this.set("selectedDate", undefined); - this.month--; - if (this.month < 0) { - this.month = 11; - this.year--; - } - return this.calendar(new Date(this.year, this.month, 1)); - } - - nextmonth() { - this.set("selectedDate", undefined); - this.month++; - if (this.month > 11) { - this.month = 0; - this.year++; - } - return this.calendar(new Date(this.year, this.month, 1)); - } - - calendar(date) { - let week_day; - let asc, end; - if (!date) { date = new Date(); } - this.day = date.getDate(); - this.month = date.getMonth(); - this.year = date.getFullYear(); - - const now = { - d: (new Date()).getDate(), - m: (new Date()).getMonth(), - y: (new Date()).getFullYear() - }; - const months = [ - __("January"), - __("February"), - __("March"), - __("April"), - __("May"), - __("June"), - __("July"), - __("August"), - __("September"), - __("October"), - __("November"), - __("December") - ]; - const this_month = new Date(this.year, this.month, 1); - const next_month = new Date(this.year, this.month + 1, 1); - // Find out when this month starts and ends. - const first_week_day = this_month.getDay(); - const days_in_this_month = Math.round( - (next_month.getTime() - this_month.getTime()) / (1000 * 60 * 60 * 24)); - //self.mtext = months[self.month] - const rows = []; - let row = []; - // Fill the first week of the month with the appropriate number of blanks. - for (week_day = 0, end = first_week_day - 1, asc = 0 <= end; asc ? week_day <= end : week_day >= end; asc ? week_day++ : week_day--) { row.push({ text: "" }); } - week_day = first_week_day; - for (let day_counter = 1, end1 = days_in_this_month, asc1 = 1 <= end1; asc1 ? day_counter <= end1 : day_counter >= end1; asc1 ? day_counter++ : day_counter--) { - week_day %= 7; - if (week_day === 0) { - rows.push(row); - row = []; - } - // Do something different for the current day. - if ((now.d === day_counter) && (this.month === now.m) && (this.year === now.y)) { - row.push({ text: day_counter, selected: true }); - } else { - row.push({ text: day_counter }); - } - week_day++; - } - for (let i = 0, end2 = 7 - row.length, asc2 = 0 <= end2; asc2 ? i <= end2 : i >= end2; asc2 ? i++ : i--) { - row.push({ text: "" }); - } - rows.push(row); - this.refs.grid.set("rows", rows); - return this.refs.mlbl.set("text", `${months[this.month]} ${this.year}`); - } - - - layout() { - return [{ - el: "div", ref: "ctrl", children: [ - { el: "i", class: "prevmonth", ref: "prev" }, - { el: "afx-label", ref: "mlbl" }, - { el: "i", class: "nextmonth", ref: "next" } - ] - }, - { el: "afx-grid-view", ref: "grid" } - ]; - } -} - -Ant.OS.GUI.define("afx-calendar-view", CalendarTag); \ No newline at end of file diff --git a/src/core/tags/CalendarTag.ts b/src/core/tags/CalendarTag.ts new file mode 100644 index 0000000..1e7a00e --- /dev/null +++ b/src/core/tags/CalendarTag.ts @@ -0,0 +1,297 @@ +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS202: Simplify dynamic range loops + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +namespace OS { + export namespace GUI { + export namespace tag { + + /** + * + * + * @export + * @class CalendarTag + * @extends {AFXTag} + */ + export class CalendarTag extends AFXTag { + private _day: number; + private _month: number; + private _year: number; + private _selectedDate: Date; + private _ondateselect: TagEventCallback; + + /** + *Creates an instance of CalendarTag. + * @memberof CalendarTag + */ + constructor() { + super(); + this._day = 0; + this._month = 0; + this._year = 0; + this._ondateselect = (e) => {}; + } + + /** + * + * + * @protected + * @memberof CalendarTag + */ + protected init(): void { + $(this).css("height", "100%"); + $(this.refs.grid).css("width", "100%"); + } + + /** + * + * + * @protected + * @param {*} [d] + * @memberof CalendarTag + */ + protected reload(d?: any): void { + } + + /** + * + * + * @readonly + * @type {Date} + * @memberof CalendarTag + */ + get selectedDate(): Date { + return this._selectedDate; + } + + /** + * + * + * @memberof CalendarTag + */ + set ondateselect(v: TagEventCallback) { + this._ondateselect = v; + } + + /** + * + * + * @protected + * @memberof CalendarTag + */ + protected mount(): void { + $(this.refs.prev).click((e) => this.prevmonth()); + $(this.refs.next).click((e) => this.nextmonth()); + const grid = this.refs.grid as GridViewTag; + grid.header = [ + { text: "__(Sun)" }, + { text: "__(Mon)" }, + { text: "__(Tue)" }, + { text: "__(Wed)" }, + { text: "__(Thu)" }, + { text: "__(Fri)" }, + { text: "__(Sat)" }, + ]; + grid.oncellselect = (e) => { + this.dateselect(e); + }; + + this.observable.on("resize", (e) => this.calibrate()); + this.calibrate(); + this.calendar(null); + } + + /** + * + * + * @private + * @param {TagEventType} e + * @returns {void} + * @memberof CalendarTag + */ + private dateselect(e: TagEventType): void { + if (!e.data.item) { + return; + } + const value = e.data.item.data.text; + if (value === "") { + return; + } + const evt = { + id: this.aid, + data: new Date(this._year, this._month, parseInt(value)), + }; + this._ondateselect(evt); + this._selectedDate = evt.data; + return this.observable.trigger("dateselect", evt); + } + + /** + * + * + * @protected + * @memberof CalendarTag + */ + protected calibrate(): void { + $(this.refs.grid).css( + "height", + `${$(this).height() - $(this.refs.ctrl).height()}px` + ); + } + + /** + * + * + * @private + * @memberof CalendarTag + */ + private prevmonth(): void { + this._selectedDate = undefined; + this._month--; + if (this._month < 0) { + this._month = 11; + this._year--; + } + this.calendar(new Date(this._year, this._month, 1)); + } + + + /** + * + * + * @private + * @returns + * @memberof CalendarTag + */ + private nextmonth() { + this._selectedDate = undefined; + this._month++; + if (this._month > 11) { + this._month = 0; + this._year++; + } + return this.calendar(new Date(this._year, this._month, 1)); + } + + /** + * + * + * @private + * @param {Date} date + * @memberof CalendarTag + */ + private calendar(date: Date) { + let week_day: number; + let asc: any, end: any; + if (!date) { + date = new Date(); + } + this._day = date.getDate(); + this._month = date.getMonth(); + this._year = date.getFullYear(); + + const now = { + d: new Date().getDate(), + m: new Date().getMonth(), + y: new Date().getFullYear(), + }; + const months = [ + __("January"), + __("February"), + __("March"), + __("April"), + __("May"), + __("June"), + __("July"), + __("August"), + __("September"), + __("October"), + __("November"), + __("December"), + ]; + const this_month = new Date(this._year, this._month, 1); + const next_month = new Date(this._year, this._month + 1, 1); + // Find out when this month starts and ends. + const first_week_day = this_month.getDay(); + const days_in_this_month = Math.round( + (next_month.getTime() - this_month.getTime()) / + (1000 * 60 * 60 * 24) + ); + //self.mtext = months[self.month] + const rows = []; + let row = []; + // Fill the first week of the month with the appropriate number of blanks. + for ( + week_day = 0, end = first_week_day - 1, asc = 0 <= end; + asc ? week_day <= end : week_day >= end; + asc ? week_day++ : week_day-- + ) { + row.push({ text: "" }); + } + week_day = first_week_day; + for ( + let day_counter = 1, + end1 = days_in_this_month, + asc1 = 1 <= end1; + asc1 ? day_counter <= end1 : day_counter >= end1; + asc1 ? day_counter++ : day_counter-- + ) { + week_day %= 7; + if (week_day === 0) { + rows.push(row); + row = []; + } + // Do something different for the current day. + if ( + now.d === day_counter && + this._month === now.m && + this._year === now.y + ) { + row.push({ text: day_counter, selected: true }); + } else { + row.push({ text: day_counter }); + } + week_day++; + } + for ( + let i = 0, end2 = 7 - row.length, asc2 = 0 <= end2; + asc2 ? i <= end2 : i >= end2; + asc2 ? i++ : i-- + ) { + row.push({ text: "" }); + } + rows.push(row); + const grid = this.refs.grid as GridViewTag; + grid.rows = rows; + (this.refs.mlbl as LabelTag).text = `${months[this._month]} ${this._year}`; + } + + /** + * + * + * @protected + * @returns {TagLayoutType[]} + * @memberof CalendarTag + */ + protected layout(): TagLayoutType[] { + return [ + { + el: "div", + ref: "ctrl", + children: [ + { el: "i", class: "prevmonth", ref: "prev" }, + { el: "afx-label", ref: "mlbl" }, + { el: "i", class: "nextmonth", ref: "next" }, + ], + }, + { el: "afx-grid-view", ref: "grid" }, + ]; + } + } + + define("afx-calendar-view", CalendarTag); + } + } +} diff --git a/src/core/tags/ColorPickerTag.js b/src/core/tags/ColorPickerTag.js deleted file mode 100644 index 3bef7e9..0000000 --- a/src/core/tags/ColorPickerTag.js +++ /dev/null @@ -1,133 +0,0 @@ -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ -class ColorPickerTag extends Ant.OS.GUI.BaseTag { - constructor(r, o) { - super(r, o); - this.colorctx = undefined; - this.setopt("oncolorselect", function(e) {}); - this.setopt("selectedColor", undefined); - } - - mount() { - $(this.refs.wrapper) - .css("width", "310px") - .css("height", "190px") - .css("display", "block") - .css("padding", "3px"); - $(this.refs.palette) - .css("width", "284px") - .css("height", "155px") - .css("float", "left"); - $(this.refs.colorval) - .css("width", "15px") - .css("height", "155px") - .css("text-align", "center") - .css("margin-left", "3px") - .css("display", "block") - .css("float", "left"); - - $(this.refs.inputwrp) - .css("margin-top", "3px"); - - $(this.refs.hextext) - .css("width", "70px") - .css("margin-left", "3px") - .css("margin-right", "5px"); - - return this.build_palette(); - } - - build_palette() { - const colorctx = $(this.refs.palette).get(0).getContext('2d'); - let gradient = colorctx.createLinearGradient(0, 0, $(this.refs.palette).width(), 0); - // fill color - gradient.addColorStop(0, "rgb(255, 0, 0)"); - gradient.addColorStop(0.15, "rgb(255, 0, 255)"); - gradient.addColorStop(0.33, "rgb(0, 0, 255)"); - gradient.addColorStop(0.49, "rgb(0, 255, 255)"); - gradient.addColorStop(0.67, "rgb(0, 255, 0)"); - gradient.addColorStop(0.84, "rgb(255, 255, 0)"); - gradient.addColorStop(1, "rgb(255, 0, 0)"); - gradient.addColorStop(0, "rgb(0, 0, 0)"); - // Apply gradient to canvas - colorctx.fillStyle = gradient; - colorctx.fillRect(0, 0, colorctx.canvas.width, colorctx.canvas.height); - - // Create semi transparent gradient (white -> trans. -> black) - gradient = colorctx.createLinearGradient(0, 0, 0, $(this.refs.palette).width()); - gradient.addColorStop(0, "rgba(255, 255, 255, 1)"); - gradient.addColorStop(0.5, "rgba(255, 255, 255, 0)"); - gradient.addColorStop(0.5, "rgba(0, 0, 0, 0)"); - gradient.addColorStop(1, "rgba(0, 0, 0, 1)"); - // Apply gradient to canvas - colorctx.fillStyle = gradient; - colorctx.fillRect(0, 0, colorctx.canvas.width, colorctx.canvas.height); - // now add mouse move event - const getHex = function(c) { - let s = c.toString(16); - if (s.length === 1) { s = "0" + s; } - return s; - }; - - const pick_color = e => { - $(this.refs.palette).css("cursor", "crosshair"); - const offset = $(this.refs.palette).offset(); - const x = e.pageX - offset.left; - const y = e.pageY - offset.top; - const color = colorctx.getImageData(x, y, 1, 1); - const data = { - r: color.data[0], - g: color.data[1], - b: color.data[2], - text: 'rgb(' + color.data[0] + ', ' + color.data[1] + ', ' + color.data[2] + ')', - hex: '#' + getHex(color.data[0]) + getHex(color.data[1]) + getHex(color.data[2]) - }; - return data; - }; - - const mouse_move_h = e => { - const data = pick_color(e); - return $(this.refs.colorval).css("background-color", data.text); - }; - - $(this.refs.palette).mouseenter(e => { - return $(this.refs.palette).on("mousemove", mouse_move_h); - }); - - $(this.refs.palette).mouseout(e => { - $(this.refs.palette).unbind("mousemove", mouse_move_h); - if (this.get("selectedColor")) { - return $(this.refs.colorval).css("background-color", this.get("selectedColor").text); - } - }); - - return $(this.refs.palette).on("click", e => { - const data = pick_color(e); - $(this.refs.rgbtext).html(data.text); - $(this.refs.hextext).val(data.hex); - this.set("selectedColor", data); - const evt = { id: this.aid(), data }; - this.get("oncolorselect")(evt); - return this.observable.trigger("colorselect", data); - }); - } - - layout() { - return [{ - el: "div", ref: "wrapper", children: [ - { el: "canvas", class: "color-palette", ref: "palette" }, - { el: "color-sample", ref: "colorval" }, - { el: "div", class: "afx-clear" }, - { el: "div", ref: "inputwrp", children: [ - { el: "input", ref: "hextext" }, - { el: "span", ref: "rgbtext" } - ] } - ] - }]; - } -} - -Ant.OS.GUI.define("afx-color-picker", ColorPickerTag); \ No newline at end of file diff --git a/src/core/tags/ColorPickerTag.ts b/src/core/tags/ColorPickerTag.ts new file mode 100644 index 0000000..da37c9a --- /dev/null +++ b/src/core/tags/ColorPickerTag.ts @@ -0,0 +1,276 @@ +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +namespace OS { + export namespace GUI { + /** + * + * + * @export + * @interface ColorType + */ + export interface ColorType { + r: number; + g: number; + b: number; + a?: number; + text?: string; + hex?: string; + } + export namespace tag { + /** + * + * + * @export + * @class ColorPickerTag + * @extends {AFXTag} + */ + export class ColorPickerTag extends AFXTag { + private _selectedColor: ColorType; + private _oncolorselect: TagEventCallback; + + /** + *Creates an instance of ColorPickerTag. + * @memberof ColorPickerTag + */ + constructor() { + super(); + this._oncolorselect = (e) => {}; + } + + /** + * + * + * @protected + * @memberof ColorPickerTag + */ + protected init(): void {} + + /** + * + * + * @protected + * @param {*} [d] + * @memberof ColorPickerTag + */ + protected reload(d?: any): void {} + + /** + * + * + * @readonly + * @type {ColorType} + * @memberof ColorPickerTag + */ + get selectedColor(): ColorType { + return this._selectedColor; + } + + /** + * + * + * @memberof ColorPickerTag + */ + set oncolorselect(v: TagEventCallback) { + this._oncolorselect = v; + } + + /** + * + * + * @protected + * @memberof ColorPickerTag + */ + protected mount(): void { + $(this.refs.wrapper) + .css("width", "310px") + .css("height", "190px") + .css("display", "block") + .css("padding", "3px"); + $(this.refs.palette) + .css("width", "284px") + .css("height", "155px") + .css("float", "left"); + $(this.refs.colorval) + .css("width", "15px") + .css("height", "155px") + .css("text-align", "center") + .css("margin-left", "3px") + .css("display", "block") + .css("float", "left"); + + $(this.refs.inputwrp).css("margin-top", "3px"); + + $(this.refs.hextext) + .css("width", "70px") + .css("margin-left", "3px") + .css("margin-right", "5px"); + + this.build_palette(); + } + + /** + * + * + * @private + * @memberof ColorPickerTag + */ + private build_palette(): void { + const colorctx = ($(this.refs.palette).get( + 0 + ) as HTMLCanvasElement).getContext("2d"); + let gradient = colorctx.createLinearGradient( + 0, + 0, + $(this.refs.palette).width(), + 0 + ); + // fill color + gradient.addColorStop(0, "rgb(255, 0, 0)"); + gradient.addColorStop(0.15, "rgb(255, 0, 255)"); + gradient.addColorStop(0.33, "rgb(0, 0, 255)"); + gradient.addColorStop(0.49, "rgb(0, 255, 255)"); + gradient.addColorStop(0.67, "rgb(0, 255, 0)"); + gradient.addColorStop(0.84, "rgb(255, 255, 0)"); + gradient.addColorStop(1, "rgb(255, 0, 0)"); + gradient.addColorStop(0, "rgb(0, 0, 0)"); + // Apply gradient to canvas + colorctx.fillStyle = gradient; + colorctx.fillRect( + 0, + 0, + colorctx.canvas.width, + colorctx.canvas.height + ); + + // Create semi transparent gradient (white -> trans. -> black) + gradient = colorctx.createLinearGradient( + 0, + 0, + 0, + $(this.refs.palette).width() + ); + gradient.addColorStop(0, "rgba(255, 255, 255, 1)"); + gradient.addColorStop(0.5, "rgba(255, 255, 255, 0)"); + gradient.addColorStop(0.5, "rgba(0, 0, 0, 0)"); + gradient.addColorStop(1, "rgba(0, 0, 0, 1)"); + // Apply gradient to canvas + colorctx.fillStyle = gradient; + colorctx.fillRect( + 0, + 0, + colorctx.canvas.width, + colorctx.canvas.height + ); + // now add mouse move event + const getHex = function (c) { + let s = c.toString(16); + if (s.length === 1) { + s = "0" + s; + } + return s; + }; + + const pick_color = (e) => { + $(this.refs.palette).css("cursor", "crosshair"); + const offset = $(this.refs.palette).offset(); + const x = e.pageX - offset.left; + const y = e.pageY - offset.top; + const color = colorctx.getImageData(x, y, 1, 1); + const data: ColorType = { + r: color.data[0], + g: color.data[1], + b: color.data[2], + text: + "rgb(" + + color.data[0] + + ", " + + color.data[1] + + ", " + + color.data[2] + + ")", + hex: + "#" + + getHex(color.data[0]) + + getHex(color.data[1]) + + getHex(color.data[2]), + }; + return data; + }; + + const mouse_move_h = (e) => { + const data = pick_color(e); + return $(this.refs.colorval).css( + "background-color", + data.text + ); + }; + + $(this.refs.palette).mouseenter((e) => { + return $(this.refs.palette).on( + "mousemove", + mouse_move_h + ); + }); + + $(this.refs.palette).mouseout((e) => { + $(this.refs.palette).unbind("mousemove", mouse_move_h); + if (this.selectedColor) { + return $(this.refs.colorval).css( + "background-color", + this.selectedColor.text + ); + } + }); + + $(this.refs.palette).on("click", (e) => { + const data = pick_color(e); + $(this.refs.rgbtext).html(data.text); + $(this.refs.hextext).val(data.hex); + this._selectedColor = data; + const evt = { id: this.aid, data }; + this._oncolorselect(evt); + return this.observable.trigger("colorselect", data); + }); + } + + /** + * + * + * @protected + * @returns {TagLayoutType[]} + * @memberof ColorPickerTag + */ + protected layout(): TagLayoutType[] { + return [ + { + el: "div", + ref: "wrapper", + children: [ + { + el: "canvas", + class: "color-palette", + ref: "palette", + }, + { el: "color-sample", ref: "colorval" }, + { el: "div", class: "afx-clear" }, + { + el: "div", + ref: "inputwrp", + children: [ + { el: "input", ref: "hextext" }, + { el: "span", ref: "rgbtext" }, + ], + }, + ], + }, + ]; + } + } + + define("afx-color-picker", ColorPickerTag); + } + } +} diff --git a/src/core/tags/FileViewTag.js b/src/core/tags/FileViewTag.js deleted file mode 100644 index d6a871a..0000000 --- a/src/core/tags/FileViewTag.js +++ /dev/null @@ -1,264 +0,0 @@ -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ -class FileViewTag extends Ant.OS.GUI.BaseTag { - constructor(r, o) { - super(r, o); - this.setopt("onfileselect", function(){}); - this.setopt("onfileopen", function() {}); - this.setopt("ondragndrop", function() {}); - this.setopt("selectedFile", undefined); - this.setopt("data", []); - this.setopt("status", true); - this.setopt("showhidden", false); - this.setopt("fetch", undefined); - this.setopt("path", undefined); - this.setopt("chdir", true); - this.setopt("view", "list"); - this.preventUpdate = false; - this.header = [ - { text: "__(File name)" }, - { text: "__(Type)", width: 150 }, - { text: "__(Size)", width: 70 } - ]; - } - - view() { return this.get("view"); } - - __view__(v) { - return this.switchView(); - } - - __status__(v) { - if (v) { return $(this.refs.status).show(); } - return $(this.refs.status).hide(); - } - - __showhidden__(v) { - if (!this.get("data")) { return; } - return this.switchView(); - } - - __path__(v) { - if (!v) { return; } - if (!this.get("fetch")) { return; } - return this.get("fetch")(v) - .then(data => { - if (!data) { return; } - this.set("data", data); - if (this.get("status")) { return this.refs.status.set("text", " "); } - }).catch(e => // this should be handled by the OS - Ant.OS.announcer.oserror(e.toString(), e)); - } - - __data__(v) { - if (!v) { return; } - return this.refreshData(); - } - - __ondragndrop__(v) { - this.refs.treeview.set("ondragndrop", v); - return this.refs.listview.set("ondragndrop", v); - } - - sortByType(a, b) { - if (a.type < b.type) { - return -1; - } else if (a.type > b.type) { - return 1; - } else { - return 0; - } - } - - calibrate() { - let h = $(this.root).outerHeight(); - const w = $(this.root).width(); - if (this.get("status")) { h -= ($(this.refs.status).height() + 10); } - $(this.refs.listview).css("height", h + "px"); - $(this.refs.gridview).css("height", h + "px"); - $(this.refs.treecontainer).css("height", h + "px"); - $(this.refs.listview).css("width", w + "px"); - $(this.refs.gridview).css("width", w + "px"); - return $(this.refs.treecontainer).css("width", w + "px"); - } - - refreshList() { - const items = []; - $.each(this.get("data"), (i, v) => { - if ((v.filename[0] === '.') && !this.get("showhidden")) { return; } - v.text = v.filename; - if (v.text.length > 10) { v.text = v.text.substring(0, 9) + "..."; } - v.iconclass = v.iconclass ? v.iconclass : v.type; - v.icon = v.icon; - return items.push(v); - }); - return this.refs.listview.set("data", items); - } - - refreshGrid() { - const rows = []; - $.each(this.get("data"), (i, v) => { - if ((v.filename[0] === '.') && !this.get("showhidden")) { return; } - v.text = v.filename; - v.iconclass = v.iconclass ? v.iconclass : v.type; - const row = [ - v, - { - text: v.mime, - data: v - }, - { - text: v.size, - data: v - } - ]; - return rows.push(row); - }); - return this.refs.gridview.set("rows", rows); - } - - refreshTree() { - //@treeview.root.set("selectedItem", null) - const tdata = {}; - tdata.name = this.get("path"); - tdata.path = tdata.name; - tdata.open = true; - tdata.nodes = this.getTreeData( this.get("data")); - return this.refs.treeview.set("data", tdata); - } - - getTreeData(data) { - const nodes = []; - const me = this; - $.each(data, (i, v) => { - if ((v.filename[0] === '.') && !this.get("showhidden")) { return; } - v.name = v.filename; - if (v.type === 'dir') { - v.nodes = []; - v.open = false; - } - v.iconclass = v.iconclass ? v.iconclass : v.type; - v.icon = v.icon; - return nodes.push(v); - }); - return nodes; - } - - refreshData() { - if (!this.get("data")) { return; } - this.get("data").sort(this.sortByType); - switch (this.get("view")) { - case "icon": - return this.refreshList(); - case "list": - return this.refreshGrid(); - default: - return this.refreshTree(); - } - } - - switchView() { - $(this.refs.listview).hide(); - $(this.refs.gridview).hide(); - $(this.refs.treecontainer).hide(); - this.set("selectedFile", undefined); - switch (this.get("view")) { - case 'icon': - $(this.refs.listview).show(); - break; - case 'list': - $(this.refs.gridview).show(); - break; - default: - $(this.refs.treecontainer).show(); - } - this.refreshData(); - this.calibrate(); - if (this.get("status")) { return this.refs.status.set("text", " "); } - } - - fileselect(e) { - if (e.path === this.get("path")) { - e.type = "dir"; - e.mime = "dir"; - } - if (this.get("status")) { - this.refs.status.set("text", __( - "Selected: {0} ({1} bytes)", - e.filename, - e.size ? e.size : "0" ) - ); - } - const evt = { id: this.aid(), data: e }; - this.set("selectedFile", e); - this.get("onfileselect")(evt); - return this.observable.trigger("fileselect", evt); - } - - filedbclick(e) { - if (e.path === this.get("path")) { - e.type = "dir"; - e.mime = "dir"; - } - if ((e.type === "dir") && this.get("chdir")) { - return this.set("path", e.path); - } else { - const evt = { id: this.aid(), data: e }; - this.get("onfileopen")(evt); - return this.observable.trigger("fileopen", evt); - } - } - - mount() { - this.observable.on("resize", e => this.calibrate()); - this.refs.treeview.set("fetch", v => { - return new Promise((resolve, reject) => { - if (!this.get("fetch")) { return resolve(undefined); } - if (!v.get("data").path) { return resolve(undefined); } - return this.get("fetch")(v.get("data").path) - .then(d => resolve(this.getTreeData(d.sort(this.sortByType)))) - .catch(e => reject(__e(e))); - }); - }); - this.refs.gridview.set("header", this.header); - this.refs.treeview.set("dragndrop", true); - this.refs.listview.set("dragndrop", true); - // even handles - this.refs.listview.set("onlistselect", e => { - return this.fileselect(e.data.item.get("data")); - }); - this.refs.gridview.set("onrowselect", e => { - return this.fileselect($(e.data.item).children()[0].get("data")); - }); - this.refs.treeview.set("ontreeselect", e => { - return this.fileselect(e.data.item.get("data")); - }); - // dblclick - this.refs.listview.set("onlistdbclick", e => { - return this.filedbclick(e.data.item.get("data")); - }); - this.refs.gridview.set("oncelldbclick", e => { - return this.filedbclick(e.data.item.get("data")); - }); - this.refs.treeview.set("ontreedbclick", e => { - return this.filedbclick(e.data.item.get("data")); - }); - return this.switchView(); - } - - layout() { - return [ - { el: "afx-list-view", ref: "listview" }, - { el: "div", class: "treecontainer", ref: "treecontainer", children: [ - { el: "afx-tree-view", ref: "treeview" } - ] }, - { el: "afx-grid-view", ref: "gridview" }, - { el: "afx-label", class: "status", ref: "status" } - ]; - } -} - -Ant.OS.GUI.define("afx-file-view", FileViewTag); \ No newline at end of file diff --git a/src/core/tags/FileViewTag.ts b/src/core/tags/FileViewTag.ts new file mode 100644 index 0000000..6b80299 --- /dev/null +++ b/src/core/tags/FileViewTag.ts @@ -0,0 +1,577 @@ +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +namespace OS { + export namespace GUI { + export namespace tag { + /** + * + * + * @export + * @class FileViewTag + * @extends {AFXTag} + */ + export class FileViewTag extends AFXTag { + private _onfileselect: TagEventCallback; + private _onfileopen: TagEventCallback; + private _selectedFile: API.FileInfoType; + private _data: API.FileInfoType[]; + private _path: string; + private _header: GenericObject[]; + private _fetch: (p: string) => Promise; + + /** + *Creates an instance of FileViewTag. + * @memberof FileViewTag + */ + constructor() { + super(); + } + + /** + * + * + * @protected + * @memberof FileViewTag + */ + protected init(): void { + this.data = []; + this.status = true; + this.showhidden = false; + this.chdir = true; + this.view = "list"; + this._onfileopen = this._onfileselect = (e) => {}; + this._header = [ + { text: "__(File name)" }, + { text: "__(Type)", width: 150 }, + { text: "__(Size)", width: 70 }, + ]; + } + + /** + * + * + * @protected + * @param {*} [d] + * @memberof FileViewTag + */ + protected reload(d?: any): void {} + /** + * + * + * @memberof FileViewTag + */ + set fetch(v: (p: string) => Promise) { + this._fetch = v; + } + + /** + * + * + * @memberof FileViewTag + */ + set onfileselect(e: TagEventCallback) { + this._onfileselect = e; + } + + /** + * + * + * @memberof FileViewTag + */ + set onfileopen(e: TagEventCallback) { + this._onfileopen = e; + } + + /** + * + * + * @memberof FileViewTag + */ + set view(v: string) { + $(this).attr("view", v); + this.switchView(); + } + + /** + * + * + * @type {string} + * @memberof FileViewTag + */ + get view(): string { + return $(this).attr("view"); + } + + /** + * + * + * @memberof FileViewTag + */ + set chdir(v: boolean) { + this.attsw(v, "chdir"); + } + + /** + * + * + * @type {boolean} + * @memberof FileViewTag + */ + get chdir(): boolean { + return this.hasattr("chdir"); + } + + /** + * + * + * @memberof FileViewTag + */ + set status(v: boolean) { + this.attsw(v, "status"); + if (v) { + $(this.refs.status).show(); + return; + } + $(this.refs.status).hide(); + } + + /** + * + * + * @type {boolean} + * @memberof FileViewTag + */ + get status(): boolean { + return this.hasattr("status"); + } + + /** + * + * + * @memberof FileViewTag + */ + set showhidden(v: boolean) { + this.attsw(v, "showhidden"); + if (!this.data) { + return; + } + this.switchView(); + } + + /** + * + * + * @type {boolean} + * @memberof FileViewTag + */ + get showhidden(): boolean { + return this.hasattr("showhidden"); + } + + /** + * + * + * @readonly + * @type {API.FileInfoType} + * @memberof FileViewTag + */ + get selectedFile(): API.FileInfoType { + return this._selectedFile; + } + + /** + * + * + * @memberof FileViewTag + */ + set path(v: string) { + if (!v) { + return; + } + this._path = v; + if (!this._fetch) { + return; + } + this._fetch(v) + .then((data: API.FileInfoType[]) => { + if (!data) { + return; + } + this.data = data; + if (this.status) { + (this.refs.status as LabelTag).text = " "; + } + }) + .catch((e: Error) => + announcer.oserror(e.toString(), e) + ); + } + + /** + * + * + * @type {string} + * @memberof FileViewTag + */ + get path(): string { + return this._path; + } + + /** + * + * + * @memberof FileViewTag + */ + set data(v: API.FileInfoType[]) { + if (!v) { + return; + } + this._data = v; + this.refreshData(); + } + + /** + * + * + * @type {API.FileInfoType[]} + * @memberof FileViewTag + */ + get data(): API.FileInfoType[] { + return this._data; + } + + /** + * + * + * @memberof FileViewTag + */ + set ondragndrop(v: TagEventCallback) { + (this.refs.treeview as TreeViewTag).ondragndrop = v; + (this.refs.listview as ListViewTag).ondragndrop = v; + } + + /** + * + * + * @private + * @param {API.FileInfoType} a + * @param {API.FileInfoType} b + * @returns {(0|-1|1)} + * @memberof FileViewTag + */ + private sortByType( + a: API.FileInfoType, + b: API.FileInfoType + ): 0 | -1 | 1 { + if (a.type < b.type) { + return -1; + } else if (a.type > b.type) { + return 1; + } else { + return 0; + } + } + + /** + * + * + * @memberof FileViewTag + */ + calibrate(): void { + let h = $(this).outerHeight(); + const w = $(this).width(); + if (this.status) { + h -= $(this.refs.status).height() + 10; + } + $(this.refs.listview).css("height", h + "px"); + $(this.refs.gridview).css("height", h + "px"); + $(this.refs.treecontainer).css("height", h + "px"); + $(this.refs.listview).css("width", w + "px"); + $(this.refs.gridview).css("width", w + "px"); + $(this.refs.treecontainer).css("width", w + "px"); + } + + /** + * + * + * @private + * @memberof FileViewTag + */ + private refreshList(): void { + const items = []; + $.each(this.data, (i, v) => { + if (v.filename[0] === "." && !this.showhidden) { + return; + } + v.text = v.filename; + if (v.text.length > 10) { + v.text = v.text.substring(0, 9) + "..."; + } + v.iconclass = v.iconclass ? v.iconclass : v.type; + v.icon = v.icon; + items.push(v); + }); + (this.refs.listview as ListViewTag).data = items; + } + + /** + * + * + * @private + * @memberof FileViewTag + */ + private refreshGrid(): void { + const rows = []; + $.each(this.data, (i, v) => { + if (v.filename[0] === "." && !this.showhidden) { + return; + } + v.text = v.filename; + v.iconclass = v.iconclass ? v.iconclass : v.type; + const row = [ + v, + { + text: v.mime, + data: v, + }, + { + text: v.size, + data: v, + }, + ]; + return rows.push(row); + }); + (this.refs.gridview as GridViewTag).rows = rows; + } + + /** + * + * + * @private + * @memberof FileViewTag + */ + private refreshTree(): void { + //@treeview.root.set("selectedItem", null) + const tdata: TreeViewDataType = { + name: this.path, + path: this.path, + open: true, + nodes: this.getTreeData(this.data), + }; + (this.refs.treeview as TreeViewTag).data = tdata; + } + + /** + * + * + * @private + * @param {API.FileInfoType[]} data + * @returns {TreeViewDataType[]} + * @memberof FileViewTag + */ + private getTreeData( + data: API.FileInfoType[] + ): TreeViewDataType[] { + const nodes = []; + const me = this; + $.each(data, (i, v) => { + if (v.filename[0] === "." && !this.showhidden) { + return undefined; + } + v.name = v.filename; + if (v.type === "dir") { + v.nodes = []; + v.open = false; + } + v.iconclass = v.iconclass ? v.iconclass : v.type; + v.icon = v.icon; + return nodes.push(v); + }); + return nodes; + } + + /** + * + * + * @private + * @returns {void} + * @memberof FileViewTag + */ + private refreshData(): void { + if (!this.data) { + return; + } + this.data.sort(this.sortByType); + switch (this.view) { + case "icon": + return this.refreshList(); + case "list": + return this.refreshGrid(); + default: + return this.refreshTree(); + } + } + + /** + * + * + * @private + * @memberof FileViewTag + */ + private switchView(): void { + $(this.refs.listview).hide(); + $(this.refs.gridview).hide(); + $(this.refs.treecontainer).hide(); + this._selectedFile = undefined; + switch (this.view) { + case "icon": + $(this.refs.listview).show(); + break; + case "list": + $(this.refs.gridview).show(); + break; + default: + $(this.refs.treecontainer).show(); + } + this.refreshData(); + this.calibrate(); + if (this.status) { + (this.refs.status as LabelTag).text = " "; + } + } + + /** + * + * + * @private + * @param {API.FileInfoType} e + * @memberof FileViewTag + */ + private fileselect(e: API.FileInfoType): void { + if (e.path === this.path) { + e.type = "dir"; + e.mime = "dir"; + } + if (this.status) { + (this.refs.status as LabelTag).text = __( + "Selected: {0} ({1} bytes)", + e.filename, + e.size ? e.size : "0" + ); + } + const evt = { id: this.aid, data: e }; + this._selectedFile = e; + this._onfileselect(evt); + this.observable.trigger("fileselect", evt); + } + + /** + * + * + * @private + * @param {API.FileInfoType} e + * @memberof FileViewTag + */ + private filedbclick(e: API.FileInfoType): void { + if (e.path === this.path) { + e.type = "dir"; + e.mime = "dir"; + } + if (e.type === "dir" && this.chdir) { + this.path = e.path; + } else { + const evt = { id: this.aid, data: e }; + this._onfileopen(evt); + this.observable.trigger("fileopen", evt); + } + } + + /** + * + * + * @protected + * @memberof FileViewTag + */ + protected mount(): void { + this.observable.on("resize", (e) => this.calibrate()); + const tree = this.refs.treeview as TreeViewTag; + tree.fetch = (v) => { + return new Promise((resolve, reject) => { + if (!this.fetch) { + return resolve(undefined); + } + if (!v.data.path) { + return resolve(undefined); + } + return this.fetch(v.data.path) + .then((d: API.FileInfoType[]) => + resolve( + this.getTreeData( + d.sort(this.sortByType) + ) + ) + ) + .catch((e: Error) => reject(__e(e))); + }); + }; + const grid = this.refs.gridview as GridViewTag; + const list = this.refs.listview as ListViewTag; + grid.header = this._header; + tree.dragndrop = true; + list.dragndrop = true; + // even handles + list.onlistselect = (e) => { + this.fileselect(e.data.item.get("data")); + }; + grid.onrowselect = (e) => { + this.fileselect( + $(e.data.item).children()[0].get("data") + ); + }; + tree.ontreeselect = (e) => { + this.fileselect(e.data.item.get("data")); + }; + // dblclick + list.onlistdbclick = (e) => { + this.filedbclick(e.data.item.get("data")); + }; + grid.oncelldbclick = (e) => { + this.filedbclick(e.data.item.get("data")); + }; + tree.ontreedbclick = (e) => { + this.filedbclick(e.data.item.get("data")); + }; + this.switchView(); + } + + /** + * + * + * @protected + * @returns {TagLayoutType[]} + * @memberof FileViewTag + */ + protected layout(): TagLayoutType[] { + return [ + { el: "afx-list-view", ref: "listview" }, + { + el: "div", + class: "treecontainer", + ref: "treecontainer", + children: [ + { el: "afx-tree-view", ref: "treeview" }, + ], + }, + { el: "afx-grid-view", ref: "gridview" }, + { el: "afx-label", class: "status", ref: "status" }, + ]; + } + } + + define("afx-file-view", FileViewTag); + } + } +} diff --git a/src/core/tags/FloatListTag.js b/src/core/tags/FloatListTag.js deleted file mode 100644 index 66bf197..0000000 --- a/src/core/tags/FloatListTag.js +++ /dev/null @@ -1,110 +0,0 @@ -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ -class FloatListTag extends ListViewTag { - constructor(r, o) { - super(r, o); - - this.setopt("dir", "horizontal"); - this.root.refresh = () => this.calibrate(); - this.root.push = e => this.refs.mlist.push(e); - this.root.unshift = e => this.refs.mlist.unshift(e); - this.root.remove = e => this.refs.mlist.remove(e); - } - - // disable some uneccessary functions - __dropdown__(v) { if (v) { return this.set("dropdown", false); } } - __buttons__(v) {} - showlist(e) {} - dropoff(e) {} - __data__(v) { - super.__data__(v); - return this.calibrate(); - } - __dir__(v) { - return this.calibrate(); - } - - mount() { - $(this.refs.container) - .css("width", "100%") - .css("height", "100%"); - $(this.refs.mlist) - .css("position", "absolute") - .css("display", "block") - .css("width", "100%"); - this.observable.on("resize", e => this.calibrate()); - if (this.root.ready) { return this.root.ready(this.root); } - } - - push(v) { - const el = super.push(v); - this.enable_drag(el); - return el; - } - - enable_drag(el) { - return $(el) - .css("user-select", "none") - .css("cursor", "default") - .css("display", "block") - .css("position", "absolute") - .on("mousedown", evt => { - const globalof = $(this.refs.mlist).offset(); - evt.preventDefault(); - const offset = $(el).offset(); - offset.top = evt.clientY - offset.top; - offset.left = evt.clientX - offset.left; - const mouse_move = function(e) { - let top = e.clientY - offset.top - globalof.top; - let left = e.clientX - globalof.left - offset.left; - left = left < 0 ? 0 : left; - top = top < 0 ? 0 : top; - return $(el) - .css("top", `${top}px`) - .css("left", `${left}px`); - }; - - var mouse_up = function(e) { - $(window).unbind("mousemove", mouse_move); - return $(window).unbind("mouseup", mouse_up); - }; - $(window).on("mousemove", mouse_move); - return $(window).on("mouseup", mouse_up); - }); - } - - calibrate() { - let ctop = 20; - let cleft = 20; - $(this.refs.mlist) - .css("height", `${$(this.refs.container).height()}px`); - const gw = $(this.refs.mlist).width(); - const gh = $(this.refs.mlist).height(); - - return $(this.refs.mlist).children().each((i, e) => { - $(e) - .css("top", `${ctop}px`) - .css("left", `${cleft}px`); - const w = $(e).width(); - const h = $(e).height(); - if (this.get("dir") === "vertical") { - ctop += h + 20; - if (ctop > gh) { - ctop = 20; - return cleft += w + 20; - } - } else { - cleft += w + 20; - if (cleft > gw) { - cleft = 20; - return ctop += h + 20; - } - } - }); - } -} - -Ant.OS.GUI.define("afx-float-list", FloatListTag); diff --git a/src/core/tags/FloatListTag.ts b/src/core/tags/FloatListTag.ts new file mode 100644 index 0000000..024a790 --- /dev/null +++ b/src/core/tags/FloatListTag.ts @@ -0,0 +1,219 @@ +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +namespace OS { + export namespace GUI { + export namespace tag { + /** + * + * + * @export + * @class FloatListTag + * @extends {ListViewTag} + */ + export class FloatListTag extends ListViewTag { + protected reload(d?: any): void { + } + private _onready: (e: FloatListTag) => void; + + /** + *Creates an instance of FloatListTag. + * @memberof FloatListTag + */ + constructor() { + super(); + } + + /** + * + * + * @memberof FloatListTag + */ + set onready(v: (e: FloatListTag) => void) { + this._onready = v; + } + + /** + * + * + * @memberof FloatListTag + */ + set dir(v: string) { + $(this).attr("dir", v); + this.calibrate(); + } + + /** + * + * + * @type {string} + * @memberof FloatListTag + */ + get dir(): string { + return $(this).attr("dir"); + } + // disable some uneccessary functions + + /** + * + * + * @memberof FloatListTag + */ + set dropdown(v: boolean) {} + + /** + * + * + * @memberof FloatListTag + */ + set buttons(v: GenericObject[]) {} + + /** + * + * + * @protected + * @param {*} e + * @memberof FloatListTag + */ + protected showlist(e: any) {} + + /** + * + * + * @protected + * @param {*} e + * @memberof FloatListTag + */ + protected dropoff(e: any) {} + + /** + * + * + * @protected + * @memberof FloatListTag + */ + protected ondatachange(): void { + this.calibrate(); + } + + /** + * + * + * @protected + * @returns {void} + * @memberof FloatListTag + */ + protected mount(): void { + $(this.refs.container) + .css("width", "100%") + .css("height", "100%"); + $(this.refs.mlist) + .css("position", "absolute") + .css("display", "block") + .css("width", "100%"); + this.observable.on("resize", (e) => this.calibrate()); + if (this._onready) { + return this._onready(this); + } + } + + /** + * + * + * @param {GenericObject} v + * @returns + * @memberof FloatListTag + */ + push(v: GenericObject) { + const el = super.push(v); + this.enable_drag(el); + return el; + } + + /** + * + * + * @private + * @param {ListViewItemTag} el + * @memberof FloatListTag + */ + private enable_drag(el: ListViewItemTag): void { + $(el) + .css("user-select", "none") + .css("cursor", "default") + .css("display", "block") + .css("position", "absolute") + .on("mousedown", (evt) => { + const globalof = $(this.refs.mlist).offset(); + evt.preventDefault(); + const offset = $(el).offset(); + offset.top = evt.clientY - offset.top; + offset.left = evt.clientX - offset.left; + const mouse_move = function ( + e: JQuery.MouseEventBase + ) { + let top = e.clientY - offset.top - globalof.top; + let left = + e.clientX - globalof.left - offset.left; + left = left < 0 ? 0 : left; + top = top < 0 ? 0 : top; + return $(el) + .css("top", `${top}px`) + .css("left", `${left}px`); + }; + + var mouse_up = function (e: JQuery.MouseEventBase) { + $(window).unbind("mousemove", mouse_move); + return $(window).unbind("mouseup", mouse_up); + }; + $(window).on("mousemove", mouse_move); + return $(window).on("mouseup", mouse_up); + }); + } + + /** + * + * + * @memberof FloatListTag + */ + calibrate(): void { + let ctop = 20; + let cleft = 20; + $(this.refs.mlist).css( + "height", + `${$(this.refs.container).height()}px` + ); + const gw = $(this.refs.mlist).width(); + const gh = $(this.refs.mlist).height(); + + $(this.refs.mlist) + .children() + .each((i, e) => { + $(e) + .css("top", `${ctop}px`) + .css("left", `${cleft}px`); + const w = $(e).width(); + const h = $(e).height(); + if (this.dir === "vertical") { + ctop += h + 20; + if (ctop > gh) { + ctop = 20; + cleft += w + 20; + } + } else { + cleft += w + 20; + if (cleft > gw) { + cleft = 20; + ctop += h + 20; + } + } + }); + } + } + + define("afx-float-list", FloatListTag); + } + } +} diff --git a/src/core/tags/GridViewTag.js b/src/core/tags/GridViewTag.js deleted file mode 100644 index e371124..0000000 --- a/src/core/tags/GridViewTag.js +++ /dev/null @@ -1,290 +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 GridRowTag extends Ant.OS.GUI.BaseTag { - constructor(r, o) { - super(r, o); - this.setopt("data", []); - this.refs.yield = this.root; - } -} - -class GridCellPrototype extends Ant.OS.GUI.BaseTag { - constructor(r, o) { - super(r, o); - this.setopt("class", "afx-grid-cell"); - this.setopt("oncellselect", function(e) {}); - this.setopt("oncelldbclick", function(e) {}); - this.setopt("data", {}); - this.setopt("selected", false); - } - - __data__(v) { - if (!v.selected) { return; } - return this.set("selected", v.selected); - } - - __selected__(v) { - this.get("data").selected = v; - if (!v) { return; } - return this.cellseleck({}, false); - } - - update() { - return this.set("data", this.get("data")); - } - - mount() { - $(this.root).css("display", "block"); - $(this.root).click(e => { - return this.cellseleck(e, false); - }); - return $(this.root).dblclick(e => { - return this.cellseleck(e, true); - }); - } - - - cellseleck(e, flag) { - e.item = this.root; - const evt = { id: this.aid(), data: e }; - if (!flag) { return this.get("oncellselect")(evt); } - return this.get("oncelldbclick")(evt); - } - - __class__(v) { - return $(this.root).removeClass().addClass(this.get("class")); - } -} - -class SimpleGridCellTag extends GridCellPrototype { - constructor(r, o) { - super(r, o); - this.setopt("header", false); - } - - __header__(v) {} - - - __data(d) { - return (() => { - const result = []; - for (let k in d) { - const v = d[k]; - result.push(this.refs.cell.set(k, v)); - } - return result; - })(); - } - - layout() { - return [{ - el: "afx-label", ref: "cell" - }]; - } -} - -class GridViewTag extends Ant.OS.GUI.BaseTag { - constructor(r, o) { - super(r, o); - this.setopt("header", []); - this.setopt("headeritem", "afx-grid-cell"); - this.setopt("cellitem", "afx-grid-cell"); - this.setopt("selectedCell", undefined); - this.setopt("selectedRows", []); - this.setopt("selectedRow", undefined); - this.setopt("rows", []); - this.setopt("oncellselect", function(e) {}); - this.setopt("onrowselect", function(e) {}); - this.setopt("oncelldbclick", function(e) {}); - this.setopt("multiselect", false); - this.root.push = r => this.push(r, false); - this.root.unshift = r => this.unshift(r); - this.root.remove = r => this.remove(r); - } - - __header__(v) { - if (!v || (v.length === 0)) { return $(this.refs.header).hide(); } - $(this.refs.header).empty(); - for (let item of Array.from(v)) { - const el = $(`<${this.get("headeritem")}>`).appendTo(this.refs.header); - el[0].uify(undefined); - el[0].set("data", item); - item.domel = el[0]; - } - return this.calibrate(); - } - - __rows__(rows) { - $(this.refs.grid).empty(); - return Array.from(rows).map((row) => - this.push(row, false)); - } - - remove(row) { - if (!row) { return; } - const rowdata = row.get("data"); - const data = this.get("rows"); - if (this.get("selectedRow") === row) { this.set("selectedRow", undefined); } - if ($(this.get("selectedCell")).parent()[0] === row) { this.set("selectedCell", undefined); } - const list = this.get("selectedRows"); - if (list.includes(row)) { list.splice(list.indexOf(row), 1); } - if (data.includes(rowdata)) { - data.splice(data.indexOf(rowdata), 1); - } - return $(row).remove(); - } - - - push(row, flag) { - const rowel = $("") - .css("display", "contents"); - rowel[0].uify(undefined); - rowel[0].set("data", row); - row.domel = rowel[0]; - - for (let cell of Array.from(row)) { - let tag = this.get("cellitem"); - if (cell.tag) { ({ - tag - } = cell); } - const el = $(`<${tag}>`).appendTo(rowel); - cell.domel = el[0]; - el[0].uify(undefined); - el[0].set("oncellselect", e => this.cellselect(e, false)); - el[0].set("oncelldbclick", e => this.cellselect(e, true)); - el[0].set("data", cell); - } - if (flag) { - return $(this.refs.grid).prepend(rowel[0]); - } else { - return rowel.appendTo(this.refs.grid); - } - } - - unshift(row) { - return this.push(row, true); - } - - multiselect() { - return this.get("multiselect"); - } - - cellselect(e, flag) { - e.id = this.aid(); - const selectedCell = this.get("selectedCell"); - // return if e.data.item is selectedCell and not flag - if (selectedCell) { selectedCell.set("class", "afx-grid-cell"); } - this.set("selectedCell", e.data.item); - $(e.data.item).addClass("afx-grid-cell-selected"); - if (flag) { - this.observable.trigger("celldbclick", e); - return this.get("oncelldbclick")(e); - } else { - this.observable.trigger("cellselect", e); - this.get("oncellselect")(e); - return this.rowselect(e); - } - } - - rowselect(e) { - if (!e.data.item) { return; } - const selectedRow = this.get("selectedRow"); - const selectedRows = this.get("selectedRows"); - const evt = { id: this.aid(), data: {} }; - const row = $(e.data.item).parent()[0]; - if (this.multiselect()) { - if (selectedRows.includes(row)) { - selectedRows.splice(selectedRows.indexOf(row) , 1); - $(row).removeClass(); - } else { - selectedRows.push(row); - $(row).removeClass().addClass("afx-grid-row-selected"); - } - evt.data.items = this.get("selectedRows"); - } else { - if (selectedRow === row) { return; } - $(selectedRow).removeClass(); - this.set("selectedRow", row); - this.set("selectedRows", [row]); - evt.data.item = row; - evt.data.items = [ row ]; - $(row).removeClass().addClass("afx-grid-row-selected"); - } - this.get("onrowselect")(evt); - return this.observable.trigger("rowselect", evt); - } - - has_header() { - const h = this.get("header"); - return h && (h.length > 0); - } - calibrate() { - this.calibrate_header(); - if (this.has_header()) { - return $(this.refs.container).css("height", ($(this.root).height() - $(this.refs.header).height()) + "px"); - } else { - return $(this.refs.container).css("height", $(this.root).height() + "px"); - } - } - - calibrate_header() { - const header = this.get("header"); - if (!header || (header.length === 0)) { return; } - const colssize = []; - let ocw = 0; - let nauto = 0; - const totalw = $(this.root).parent().width(); - $.each(header, function(i, item) { - if (item.width) { - colssize.push(item.width); - return ocw += item.width; - } else { - colssize.push(-1); - return nauto++; - } - }); - if (nauto > 0) { - const cellw = parseInt((totalw - ocw) / nauto); - $.each(colssize, function(i, e) { - if (e !== -1) { return; } - return colssize[i] = cellw; - }); - } - let template = ""; - for (let v of Array.from(colssize)) { template += `${v}px `; } - $(this.refs.grid).css("grid-template-columns", template); - return $(this.refs.header).css("grid-template-columns", template); - } - - mount() { - $(this.root) - .css("overflow", "hidden"); - - $(this.refs.grid).css("display", "grid"); - $(this.refs.header).css("display", "grid"); - this.observable.on("resize", e => this.calibrate()); - $(this.refs.container) - .css("width", "100%") - .css("overflow-x", "hidden") - .css("overflow-y", "auto"); - return this.calibrate(); - } - - layout() { - return [ - { el: "div", ref: "header", class: "grid_row_header" }, - { el: "div", ref: "container", children: [ - { el: "div", ref: "grid" } - ] } - ]; - } -} -Ant.OS.GUI.define("afx-grid-view", GridViewTag); -Ant.OS.GUI.define("afx-grid-cell", SimpleGridCellTag); -Ant.OS.GUI.define("afx-grid-row", GridRowTag); -Ant.OS.GUI.define("afx-grid-cell-proto", GridCellPrototype); \ No newline at end of file diff --git a/src/core/tags/GridViewTag.ts b/src/core/tags/GridViewTag.ts new file mode 100644 index 0000000..382d474 --- /dev/null +++ b/src/core/tags/GridViewTag.ts @@ -0,0 +1,417 @@ +/* + * 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 + */ +interface Array { + domel: GenericObject; +} +namespace OS { + export namespace GUI { + export namespace tag { + export class GridRowTag extends AFXTag { + data: GenericObject[]; + constructor() { + super(); + + this.refs.yield = this; + } + + protected mount(): void {} + protected init(): void {this.data = [];} + protected layout(): TagLayoutType[] { + return []; + } + protected calibrate(): void {} + protected reload(d?: any): void {} + } + + export abstract class GridCellPrototype extends AFXTag { + private _oncellselect: TagEventCallback; + private _oncelldbclick: TagEventCallback; + private _data: GenericObject; + constructor() { + super(); + + } + + set oncellselect(v: TagEventCallback) { + this._oncellselect = v; + } + set oncelldbclick(v: TagEventCallback) { + this._oncelldbclick = v; + } + set data(v: GenericObject) { + if(!v) return; + this._data = v; + this.ondatachange(); + if (!v.selected) { + return; + } + this.selected = v.selected; + } + + get data(): GenericObject { + return this._data; + } + + set selected(v: boolean) { + this.attsw(v, "selected"); + if(this._data) + this._data.selected = v; + if (!v) { + return; + } + this.cellselect({ id: this.aid, data: this }, false); + } + + get selected(): boolean { + return this.hasattr("selected"); + } + protected reload(d: any): void { + this.data = this.data; + } + + protected mount(): void { + $(this).attr("class", "afx-grid-cell"); + this.oncelldbclick = this.oncellselect = ( + e: TagEventType + ): void => {}; + this.selected = false; + $(this).css("display", "block"); + $(this).click((e) => { + let evt = { id: this.aid, data: this }; + return this.cellselect(evt, false); + }); + $(this).dblclick((e) => { + let evt = { id: this.aid, data: this }; + return this.cellselect(evt, true); + }); + } + + private cellselect(e: TagEventType, flag: boolean): void { + const evt = { id: this.aid, data: { item: e.data } }; + if (!flag) { + return this._oncellselect(evt); + } + return this._oncelldbclick(evt); + } + + protected abstract ondatachange(): void; + } + + export class SimpleGridCellTag extends GridCellPrototype { + constructor() { + super(); + } + + protected ondatachange(): void { + (this.refs.cell as LabelTag).set(this.data); + } + protected init(): void {} + protected calibrate(): void {} + layout() { + return [ + { + el: "afx-label", + ref: "cell", + }, + ]; + } + } + + export class GridViewTag extends AFXTag { + private _header: GenericObject[]; + private _rows: GenericObject[][]; + private _selectedRow: GridRowTag; + private _selectedRows: GridRowTag[]; + private _selectedCell: GridCellPrototype; + private _oncellselect: TagEventCallback; + private _onrowselect: TagEventCallback; + private _oncelldbclick: TagEventCallback; + constructor() { + super(); + } + protected init(): void { + this._header = []; + this.headeritem = "afx-grid-cell"; + this.cellitem = "afx-grid-cell"; + this._selectedCell = undefined; + this._selectedRows = []; + this._selectedRow = undefined; + this._rows = []; + this._oncellselect = this._onrowselect = this._oncelldbclick = ( + e: TagEventType + ): void => {}; + } + protected reload(d?: any): void {} + set oncellselect(v: TagEventCallback) { + this._oncellselect = v; + } + set onrowselect(v: TagEventCallback) { + this._onrowselect = v; + } + set oncelldbclick(v: TagEventCallback) { + this._oncelldbclick = v; + } + set headeritem(v: string) { + $(this).attr("headeritem", v); + } + + get headeritem(): string { + return $(this).attr("headeritem"); + } + + set cellitem(v: string) { + $(this).attr("cellitem", v); + } + + get cellitem(): string { + return $(this).attr("cellitem"); + } + + set header(v: GenericObject[]) { + this._header = v; + if (!v || v.length === 0) { + $(this.refs.header).hide(); + return; + } + $(this.refs.header).empty(); + for (let item of Array.from(v)) { + const el = $(`<${this.headeritem}>`).appendTo( + this.refs.header + ); + const element = el[0] as GridCellPrototype; + element.uify(this.observable); + element.data = item; + item.domel = element; + } + this.calibrate(); + } + get selectedRows(): GridRowTag[] { + return this._selectedRows; + } + get selectedRow(): GridRowTag { + return this._selectedRow; + } + get selectedCell(): GridCellPrototype { + return this._selectedCell; + } + set rows(rows: GenericObject[][]) { + $(this.refs.grid).empty(); + this._rows = rows; + rows.map((row) => this.push(row, false)); + } + get rows(): GenericObject[][] { + return this._rows; + } + set multiselect(v: boolean) { + this.attsw(v, "multiselect"); + } + get multiselect(): boolean { + return this.hasattr("multiselect"); + } + delete(row: GridRowTag): void { + if (!row) { + return; + } + const rowdata = row.data; + const data = this.rows; + if (this.selectedRow === row) { + this._selectedRow = undefined; + } + let parentRow: any = $(this.selectedCell).parent()[0]; + if ((parentRow as GridRowTag) === row) { + this._selectedCell = undefined; + } + const list = this.selectedRows; + if (list.includes(row)) { + list.splice(list.indexOf(row), 1); + } + if (data.includes(rowdata)) { + data.splice(data.indexOf(rowdata), 1); + } + $(row).remove(); + } + + push(row: GenericObject[], flag: boolean): void { + const rowel = $("").css( + "display", + "contents" + ); + if (flag) { + $(this.refs.grid).prepend(rowel[0]); + } else { + rowel.appendTo(this.refs.grid); + } + + const el = rowel[0] as GridRowTag; + rowel[0].uify(this.observable); + el.data = row; + row.domel = rowel[0]; + + for (let cell of Array.from(row)) { + let tag = this.cellitem; + if (cell.tag) { + ({ tag } = cell); + } + const el = $(`<${tag}>`).appendTo(rowel); + cell.domel = el[0]; + const element = el[0] as GridCellPrototype; + element.uify(this.observable); + element.oncellselect = (e) => this.cellselect(e, false); + element.oncelldbclick = (e) => this.cellselect(e, true); + element.data = cell; + } + } + + unshift(row: GenericObject[]): void { + this.push(row, true); + } + + cellselect(e: TagEventType, flag: boolean): void { + e.id = this.aid; + // return if e.data.item is selectedCell and not flag + if (this.selectedCell) { + $(this.selectedCell).attr("class", "afx-grid-cell"); + } + this._selectedCell = e.data.item; + $(e.data.item).addClass("afx-grid-cell-selected"); + if (flag) { + this.observable.trigger("celldbclick", e); + return this._oncelldbclick(e); + } else { + this.observable.trigger("cellselect", e); + this._oncellselect(e); + return this.rowselect(e); + } + } + + rowselect(e: TagEventType): void { + if (!e.data.item) { + return; + } + const evt = { + id: this.aid, + data: { + item: undefined, + items: [], + }, + }; + const row = $(e.data.item).parent()[0]; + if (this.multiselect) { + if (this.selectedRows.includes(row)) { + this.selectedRows.splice( + this.selectedRows.indexOf(row), + 1 + ); + $(row).removeClass(); + } else { + this.selectedRows.push(row); + $(row) + .removeClass() + .addClass("afx-grid-row-selected"); + } + evt.data.items = this.selectedRows; + } else { + if (this.selectedRow === row) { + return; + } + $(this.selectedRow).removeClass(); + this._selectedRow = row; + this._selectedRows = [row]; + evt.data.item = row; + evt.data.items = [row]; + $(row).removeClass().addClass("afx-grid-row-selected"); + } + this._onrowselect(evt); + return this.observable.trigger("rowselect", evt); + } + + private has_header(): boolean { + const h = this._header; + return h && h.length > 0; + } + protected calibrate(): void { + this.calibrate_header(); + if (this.has_header()) { + $(this.refs.container).css( + "height", + $(this).height() - + $(this.refs.header).height() + + "px" + ); + } else { + $(this.refs.container).css( + "height", + $(this).height() + "px" + ); + } + } + + private calibrate_header(): void { + if (!this.has_header()) { + return; + } + const colssize = []; + let ocw = 0; + let nauto = 0; + const totalw = $(this).parent().width(); + $.each(this._header, function (i, item) { + if (item.width) { + colssize.push(item.width); + return (ocw += item.width); + } else { + colssize.push(-1); + return nauto++; + } + }); + if (nauto > 0) { + const cellw = Math.round((totalw - ocw) / nauto); + $.each(colssize, function (i, e) { + if (e !== -1) { + return; + } + return (colssize[i] = cellw); + }); + } + let template = ""; + for (let v of colssize) { + template += `${v}px `; + } + $(this.refs.grid).css("grid-template-columns", template); + $(this.refs.header).css("grid-template-columns", template); + } + + protected mount(): void { + $(this).css("overflow", "hidden"); + + $(this.refs.grid).css("display", "grid"); + $(this.refs.header).css("display", "grid"); + this.observable.on("resize", (e) => this.calibrate()); + $(this.refs.container) + .css("width", "100%") + .css("overflow-x", "hidden") + .css("overflow-y", "auto"); + return this.calibrate(); + } + + protected layout(): TagLayoutType[] { + return [ + { el: "div", ref: "header", class: "grid_row_header" }, + { + el: "div", + ref: "container", + children: [{ el: "div", ref: "grid" }], + }, + ]; + } + } + define("afx-grid-view", GridViewTag); + define("afx-grid-cell", SimpleGridCellTag); + define("afx-grid-row", GridRowTag); + } + } +} diff --git a/src/core/tags/LabelTag.js b/src/core/tags/LabelTag.js deleted file mode 100644 index 341382c..0000000 --- a/src/core/tags/LabelTag.js +++ /dev/null @@ -1,78 +0,0 @@ -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ -class LabelTag extends Ant.OS.GUI.BaseTag { - constructor(r, o) { - super(r, o); - this.setopt("color", undefined); - this.setopt("icon", undefined); - this.setopt("iconclass", undefined); - this.setopt("class", undefined); - this.setopt("text", undefined); - } - - mount() {} - - update() { - return this.set("text", this.get("text")); - } - - __class__(v) { - $(this.root).removeClass(); - if (v) { return $(this.root).addClass(v); } - } - - __color__(v) { - if (!v) { return; } - return $(this.refs.container).css("color", v); - } - - __icon__(v) { - $(this.refs.i).attr("style", ""); - if (v) { - $(this.refs.i) - .css("background", `url(${Ant.OS.API.handle.get}/${v})`) - .css("background-size", "100% 100%") - .css("background-repeat", "no-repeat"); - return $(this.refs.i).show(); - } else { - return $(this.refs.i).hide(); - } - } - - __iconclass__(v) { - $(this.refs.iclass).removeClass(); - if (v) { - $(this.refs.iclass).addClass(v); - return $(this.refs.iclass).show(); - } else { - return $(this.refs.iclass).hide(); - } - } - - - - __text__(v) { - if (v && (v !== "")) { - $(this.refs.text).show(); - return $(this.refs.text).html(v.__()); - } else { - return $(this.refs.text).hide(); - } - } - - layout() { - return [{ - el: "span", ref: "container", children: [ - { el: "i", ref: "iclass" }, - { el: "i", ref: "i", class: "icon-style" }, - { el: "i", ref: "text", class: "label-text" } - ] - }]; - } -} - - -Ant.OS.GUI.define("afx-label", LabelTag); \ No newline at end of file diff --git a/src/core/tags/LabelTag.ts b/src/core/tags/LabelTag.ts new file mode 100644 index 0000000..9d98ad3 --- /dev/null +++ b/src/core/tags/LabelTag.ts @@ -0,0 +1,88 @@ +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +namespace OS { + export namespace GUI { + export namespace tag { + export class LabelTag extends AFXTag { + private _text: string | FormatedString; + constructor() { + super(); + } + + protected mount() { + + } + + protected reload(d: any): void { + this.text = this.text; + } + + protected init(): void { + this.icon = undefined; + this.iconclass = undefined; + this.text = undefined; + } + + protected calibrate(): void {} + + set icon(v: string) { + $(this.refs.i).attr("style", ""); + $(this).attr("icon", v); + if (v) { + $(this.refs.i) + .css("background", `url(${API.handle.get}/${v})`) + .css("background-size", "100% 100%") + .css("background-repeat", "no-repeat"); + $(this.refs.i).show(); + } else { + $(this.refs.i).hide(); + } + } + + set iconclass(v: string) { + $(this).attr("iconclass", v); + $(this.refs.iclass).removeClass(); + if (v) { + $(this.refs.iclass).addClass(v); + $(this.refs.iclass).show(); + } else { + $(this.refs.iclass).hide(); + } + } + + set text(v: string | FormatedString) { + this._text = v; + if (v && v !== "") { + $(this.refs.text).show(); + $(this.refs.text).html(v.__()); + } else { + $(this.refs.text).hide(); + } + } + + get text(): string| FormatedString { + return this._text; + } + + protected layout(): TagLayoutType[] { + return [ + { + el: "span", + ref: "container", + children: [ + { el: "i", ref: "iclass" }, + { el: "i", ref: "i", class: "icon-style" }, + { el: "i", ref: "text", class: "label-text" }, + ], + }, + ]; + } + } + + define("afx-label", LabelTag); + } + } +} diff --git a/src/core/tags/ListViewTag.js b/src/core/tags/ListViewTag.js deleted file mode 100644 index 50a56ba..0000000 --- a/src/core/tags/ListViewTag.js +++ /dev/null @@ -1,418 +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 ListViewItemTag extends Ant.OS.GUI.BaseTag { - constructor(r, o) { - super(r, o); - this.setopt("data", {}); - this.setopt("oncontextmenu", function(e) {}); - this.setopt("onclick", function(e) {}); - this.setopt("onselect", function(e) {}); - this.setopt("ondbclick", function(e) {}); - this.setopt("onclose", function(e) {}); - this.setopt("index", 0); - this.setopt("closable", false); - this.setopt("selected", false); - } - - __closable__(v) { - if (v) { return $(this.refs.btcl).show(); } else { return $(this.refs.btcl).hide(); } - } - - __selected__(v) { - $(this.refs.item).removeClass(); - if (!v) { return; } - $(this.refs.item).addClass("selected"); - return this.get("onselect")({ item: this.root }); - } - - mount() { - $(this.refs.item).attr("dataref", "afx-list-item"); - $(this.refs.item).contextmenu(e => { - e.item = this.root; - return this.get("oncontextmenu")(e); - }); - - $(this.refs.item).click(e => { - e.item = this.root; - return this.get("onclick")(e); - }); - - $(this.refs.item).dblclick(e => { - e.item = this.root; - return this.get("ondbclick")(e); - }); - return $(this.refs.btcl).click(e => { - e.item = this.root; - return this.get("onclose")(e); - }); - } - layout() { - return [{ - el: "li", ref: "item", children: [ - this.itemlayout(), - { el: "i", class: "closable", ref: "btcl" } - ] - }]; - } - - itemlayout() {} -} - -class SimpleListItemTag extends ListViewItemTag { - constructor(r, o) { - super(r, o); - } - - __data__(v) { - if (!v) { return; } - if (v.class) { this.refs.label.set("class", v.class); } - if (v.color) { this.refs.label.set("color", v.color); } - if (v.iconclass) { this.refs.label.set("iconclass", v.iconclass); } - if (v.icon) { this.refs.label.set("icon", v.icon); } - if (v.text) { this.refs.label.set("text", v.text); } - if (v.selected) { this.set("selected", v.selected); } - if (v.closable) { return this.set("closable", v.closable); } - } - - __selected(v) { - return this.get("data").selected = v; - } - - update() { - return this.set("data", this.get("data")); - } - - itemlayout() { - return { el: "afx-label", ref: "label" }; - } -} - -class ListViewTag extends Ant.OS.GUI.BaseTag { - constructor(r, o) { - super(r, o); - this.setopt("onlistselect", function() {}); - this.setopt("onlistdbclick", function() {}); - this.setopt("ondragndrop", function() {}); - this.setopt("onitemclose", () => true); - this.setopt("buttons", []); - this.setopt("data", []); - this.setopt("dropdown", false); - this.setopt("itemtag", "afx-list-item"); - this.setopt("multiselect", false); - this.setopt("selectedItem", undefined); - this.setopt("selectedItems", []); - this.setopt("selected", -1); - this.setopt("dragndrop", false); - $(this.root) - .css("display", "flex") - .css("flex-direction", "column"); - this.root.push = e => this.push(e); - this.root.remove = e => this.remove(e); - this.root.unshift = e => this.unshift(e); - this.root.unselect = () => this.unselect(); - this.root.selectNext = () => this.selectNext(); - this.root.selectPrev = () => this.selectPrev(); - } - - multiselect() { - if (this.get("dropdown")) { return false; } - return this.get("multiselect"); - } - - unshift(item) { - return this.push(item, true); - } - - has_data(v) { - return this.get("data").includes(v); - } - - push(item, flag) { - let tag = this.get("itemtag"); - if (item.tag) { ({ - tag - } = item); } - const el = $(`<${tag}>`); - if (flag) { - if (!this.has_data(item)) { this.get("data").unshift(item); } - $(this.refs.mlist).prepend(el[0]); - } else { - if (!this.has_data(item)) { this.get("data").push(item); } - el.appendTo(this.refs.mlist); - } - el[0].uify(this.observable); - el[0] - .set("oncontextmenu", e => { - return this.iclick(e, true); - }).set("ondbclick", e => { - return this.idbclick(e, false); - }).set("onclick", e => { - return this.iclick(e, false); - }).set("onselect", e => { - return this.iselect(e); - }).set("onclose", e => { - return this.iclose(e); - }).set("data", item); - item.domel = el[0]; - return el[0]; - } - - remove(item) { - const el = item.get("data"); - const data = this.get("data"); - if (this.get("selectedItem") === item) { this.set("selectedItem", undefined); } - const list = this.get("selectedItems"); - if (list.includes(item)) { list.splice(list.indexOf(item), 1); } - if (data.includes(el)) { - data.splice(data.indexOf(el), 1); - } - return $(item).remove(); - } - - selectNext() { - if (this.multiselect()) { return; } - const el = this.get("selectedItem"); - let idx = 0; - if (el) { idx = $(el).index() + 1; } - return this.set("selected", idx); - } - - selectPrev() { - if (this.multiselect()) { return; } - const el = this.get("selectedItem"); - let idx = 0; - if (el) { idx = $(el).index() - 1; } - return this.set("selected", idx); - } - - __selected__(idx) { - if (idx < 0) { return this.unselect(); } - const data = this.get("data"); - if (idx >= data.length) { return; } - return data[idx].domel.set("selected", true); - } - - __buttons__(v) { - if (this.get("dropdown")) { return; } - if (!(v.length > 0)) { return; } - return (() => { - const result = []; - for (let item of Array.from(v)) { - $(this.refs.btlist).show(); - const bt = $("").appendTo(this.refs.btlist); - bt[0].uify(this.observable); - bt[0].set("*", item); - result.push(item.domel = bt[0]); - } - return result; - })(); - } - - - __data__(data) { - $( this.refs.mlist).empty(); - for (let item of Array.from(data)) { - this.push(item, false); - } - $(this.refs.container).off("mousedown", this.onmousedown); - if (this.__("dragndrop") && !this.__("dropdown")) { - return $(this.refs.container).on("mousedown", this.onmousedown); - } - } - - - unselect() { - for (let v of Array.from(this.get("selectedItems"))) { v.set("selected", false); } - this.set("selectedItems", []); - return this.set("selectedItem", undefined); - } - - iclick(e, flag) { - if (!e.item) { return; } - const list = this.get("selectedItems"); - if (this.multiselect() && list.includes(e.item) && !flag) { - list.splice(list.indexOf(e.item), 1); - return e.item.set("selected", false); - } - return e.item.set("selected", true); - } - - idbclick(e) { - const evt = { id: this.aid(), data: e }; - this.get("onlistdbclick")(evt); - return this.observable.trigger("listdbclick", evt); - } - iselect(e) { - if (!e.item) { return; } - if (this.multiselect()) { - if (this.get("selectedItems").includes(e.item)) { return; } - this.set("selectedItem", e.item); - this.get("selectedItems").push(e.item); - e.items = this.get("selectedItems"); - } else { - if (this.get("selectedItem") === e.item) { return; } - if (this.get("selectedItem")) { this.get("selectedItem").set("selected", false); } - this.set("selectedItem", e.item); - this.set("selectedItems", [e.item]); - e.items = [e.item]; - //scroll element - const li = $(e.item).children()[0]; - const offset = $(this.refs.container).offset(); - const top = $(this.refs.container).scrollTop(); - if (($(li).offset().top + $(li).height()) > ($(this.refs.container).height() + offset.top)) { - $(this.refs.container).scrollTop((top + $(this.refs.container).height()) - $(li).height()); - } else if ($(li).offset().top < offset.top) { - $(this.refs.container).scrollTop((top - $(this.refs.container).height()) + $(li).height()); - } - } - - if (this.get("dropdown")) { - this.refs.drlabel.set("*", e.item.get("data")); - $(this.refs.mlist).hide(); - } - - const evt = { id: this.aid(), data: e }; - this.get("onlistselect")(evt); - return this.observable.trigger("listselect", evt); - } - - mount() { - this.dnd = {}; - this.onmousedown = e => { - let el = $(e.target).closest("li[dataref='afx-list-item']"); - if (el.length === 0) { return; } - el = el.parent()[0]; - this.dnd.from = el; - this.dnd.to = undefined; - $(window).on("mouseup", this.onmouseup); - return $(window).on("mousemove", this.onmousemove); - }; - - this.onmouseup = e => { - $(window).off("mouseup", this.onmouseup); - $(window).off("mousemove", this.onmousemove); - ($("#systooltip")).hide(); - let el = $(e.target).closest("li[dataref='afx-list-item']"); - if (el.length === 0) { return; } - el = el.parent()[0]; - if (el === this.dnd.from) { return; } - this.dnd.to = el; - this.__("ondragndrop")({ id: this.aid(), data: this.dnd }); - return this.dnd = {}; - }; - - this.onmousemove = e => { - if (!e) { return; } - if (!this.dnd.from) { return; } - const data = this.dnd.from.get("data"); - const $label = $("#systooltip"); - const top = e.clientY + 5; - const left = e.clientX + 5; - $label.show(); - $label[0].set("*", data); - return $label - .css("top", top + "px") - .css("left", left + "px"); - }; - - $(this.refs.btlist).hide(); - this.observable.on("resize", e => this.calibrate()); - return this.calibrate(); - } - - iclose(e) { - if (!e.item) { return; } - const evt = { id: this.aid(), data: e }; - const r = this.get("onitemclose")(evt); - if (!r) { return; } - this.observable.trigger("itemclose", evt); - return this.remove(e.item); - } - - __dropdown__(v) { - $(this.refs.container).removeAttr("style"); - $(this.refs.mlist).removeAttr("style"); - $(this.refs.container).css("flex", 1); - $(this.root).removeClass(); - const drop = e => { - return this.dropoff(e); - }; - const show = e => { - return this.showlist(e); - }; - if (v) { - $(this.root).addClass("dropdown"); - $(this.refs.current).show(); - $(document).on("click", drop); - $(this.refs.current).on("click", show); - $(this.refs.container) - .css("position", "absolute") - .css("display", "inline-block"); - $(this.refs.mlist) - .css("position", "absolute") - .css("display", "none") - .css("top", "100%") - .css("left", "0"); - return this.calibrate(); - } else { - $(this.refs.current).hide(); - $(document).off("click", drop); - return $(this.refs.current).off("click", show); - } - } - - showlist(e) { - if (!this.get("dropdown")) { return; } - const desktoph = $(Ant.OS.GUI.workspace).height(); - const offset = $(this.root).offset().top + $(this.refs.mlist).height(); - if (offset > desktoph) { - $(this.refs.mlist) - .css("top", `-${$(this.refs.mlist).outerHeight()}px`); - } else { - $(this.mlist).css("top", "100%"); - } - return $(this.refs.mlist).show(); - } - - dropoff(e) { - if ($(e.target).closest(this.refs.container).length === 0) { return $(this.refs.mlist).hide(); } - } - - - calibrate(e) { - if (!this.get("dropdown")) { return; } - const w = `${$(this.root).width()}px`; - $(this.refs.container).css("width", w); - $(this.refs.current).css("width", w); - return $(this.refs.mlist).css("width", w); - } - - - layout() { - return [ - { - el: "div", - class: "list-container", - ref: "container", - children: [ - { - el: "div", ref: "current", children: [ - { el: "afx-label", ref: "drlabel" } - ] - }, - { el: "ul", ref: "mlist" } - ] - }, - { el: "div", class: "button_container", ref: "btlist" } - ]; - } -} - -Ant.OS.GUI.define("afx-list-view", ListViewTag); -Ant.OS.GUI.define("afx-list-item-proto", ListViewItemTag); -Ant.OS.GUI.define("afx-list-item", SimpleListItemTag); \ No newline at end of file diff --git a/src/core/tags/ListViewTag.ts b/src/core/tags/ListViewTag.ts new file mode 100644 index 0000000..eadcc98 --- /dev/null +++ b/src/core/tags/ListViewTag.ts @@ -0,0 +1,1044 @@ +/* + * 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 + */ +namespace OS { + export namespace GUI { + export namespace tag { + /** + * + * + * @export + * @abstract + * @class ListViewItemTag + * @extends {AFXTag} + */ + export abstract class ListViewItemTag extends AFXTag { + private _data: GenericObject; + private _onselect: TagEventCallback; + private _onctxmenu: TagEventCallback; + private _onclick: TagEventCallback; + private _ondbclick: TagEventCallback; + private _onclose: TagEventCallback; + + /** + *Creates an instance of ListViewItemTag. + * @memberof ListViewItemTag + */ + constructor() { + super(); + this._onselect = this._onctxmenu = this._onclick = this._ondbclick = this._onclose = ( + e + ) => {}; + } + + /** + * + * + * @memberof ListViewItemTag + */ + set closable(v: boolean) { + this.attsw(v, "closable"); + if (v) { + $(this.refs.btcl).show(); + } else { + $(this.refs.btcl).hide(); + } + } + get closable(): boolean { + return this.hasattr("closable"); + } + /** + * + * + * @memberof ListViewItemTag + */ + set onitemselect(v: TagEventCallback) { + this._onselect = v; + } + + /** + * + * + * @memberof ListViewItemTag + */ + set selected(v: boolean) { + this.attsw(v, "selected"); + $(this.refs.item).removeClass(); + this._data.selected = v; + if (!v) { + return; + } + $(this.refs.item).addClass("selected"); + this._onselect({ id: this.aid, data: this }); + } + get selected(): boolean { + return this.hasattr("selected"); + } + /** + * + * + * @memberof ListViewItemTag + */ + set onctxmenu(v: TagEventCallback) { + this._onctxmenu = v; + } + + /** + * + * + * @memberof ListViewItemTag + */ + set onitemclick(v: TagEventCallback) { + this._onclick = v; + } + + /** + * + * + * @memberof ListViewItemTag + */ + set onitemdbclick(v: TagEventCallback) { + this._ondbclick = v; + } + + /** + * + * + * @memberof ListViewItemTag + */ + set onitemclose(v: TagEventCallback) { + this._onclose = v; + } + + /** + * + * + * @protected + * @memberof ListViewItemTag + */ + protected mount(): void { + $(this.refs.item).attr("dataref", "afx-list-item"); + $(this.refs.item).contextmenu((e) => { + this._onctxmenu({ id: this.aid, data: this }); + }); + + $(this.refs.item).click((e) => { + this._onclick({ id: this.aid, data: this }); + }); + + $(this.refs.item).dblclick((e) => { + this._ondbclick({ id: this.aid, data: this }); + }); + $(this.refs.btcl).click((e) => { + this._onclose({ id: this.aid, data: this }); + e.preventDefault(); + e.stopPropagation(); + }); + } + + /** + * + * + * @protected + * @returns {TagLayoutType[]} + * @memberof ListViewItemTag + */ + protected layout(): TagLayoutType[] { + return [ + { + el: "li", + ref: "item", + children: [ + this.itemlayout(), + { el: "i", class: "closable", ref: "btcl" }, + ], + }, + ]; + } + + /** + * + * + * @memberof ListViewItemTag + */ + set data(v: GenericObject) { + this._data = v; + this.ondatachange(); + } + + /** + * + * + * @type {GenericObject} + * @memberof ListViewItemTag + */ + get data(): GenericObject { + return this._data; + } + + /** + * + * + * @protected + * @abstract + * @returns {TagLayoutType} + * @memberof ListViewItemTag + */ + protected abstract itemlayout(): TagLayoutType; + + /** + * + * + * @protected + * @abstract + * @memberof ListViewItemTag + */ + protected abstract ondatachange(): void; + } + + /** + * + * + * @export + * @class SimpleListItemTag + * @extends {ListViewItemTag} + */ + export class SimpleListItemTag extends ListViewItemTag { + constructor() { + super(); + } + + /** + * + * + * @protected + * @memberof SimpleListItemTag + */ + protected init(): void { + this.closable = false; + this.data = {}; + } + + /** + * + * + * @protected + * @memberof SimpleListItemTag + */ + protected calibrate(): void {} + + /** + * + * + * @protected + * @returns {void} + * @memberof SimpleListItemTag + */ + protected ondatachange(): void { + const v = this.data; + if (!v) { + return; + } + const label = this.refs.label as LabelTag; + label.set(v); + if (v.selected) { + this.selected = v.selected; + } + if (v.closable) { + this.closable = v.closable; + } + } + + /** + * + * + * @protected + * @memberof SimpleListItemTag + */ + protected reload(): void { + this.data = this.data; + } + + /** + * + * + * @protected + * @returns {TagLayoutType} + * @memberof SimpleListItemTag + */ + protected itemlayout(): TagLayoutType { + return { el: "afx-label", ref: "label" }; + } + } + + /** + * + * + * @export + * @class ListViewTag + * @extends {AFXTag} + */ + export class ListViewTag extends AFXTag { + private _onlistselect: TagEventCallback; + private _onlistdbclick: TagEventCallback; + private _ondragndrop: TagEventCallback; + private _onitemclose: (e: TagEventType) => boolean; + private _onmousedown: (e: JQuery.MouseEventBase) => void; + private _onmouseup: (e: JQuery.MouseEventBase) => void; + private _onmousemove: (e: JQuery.MouseEventBase) => void; + private _selectedItem: ListViewItemTag; + private _selectedItems: ListViewItemTag[]; + private _data: GenericObject[]; + private _dnd: { from: ListViewItemTag; to: ListViewItemTag }; + + /** + *Creates an instance of ListViewTag. + * @memberof ListViewTag + */ + constructor() { + super(); + this._onlistdbclick = this._onlistselect = this._ondragndrop = ( + e: TagEventType + ) => {}; + this._onitemclose = (e: TagEventType) => { + return true; + }; + this._onmousedown = this._onmouseup = this._onmousemove = ( + e: JQuery.MouseEventBase + ) => {}; + this._selectedItems = []; + this._selectedItem = undefined; + } + + /** + * + * + * @protected + * @memberof ListViewTag + */ + protected init(): void { + this.data = []; + this.multiselect = false; + this.dropdown = false; + this.selected = -1; + this.dragndrop = false; + $(this) + .css("display", "flex") + .css("flex-direction", "column"); + this.itemtag = "afx-list-item"; + } + + /** + * + * + * @protected + * @param {*} [d] + * @memberof ListViewTag + */ + protected reload(d?: any): void {} + + /** + * + * + * @memberof ListViewTag + */ + set dropdown(v: boolean) { + this.attsw(v, "dropdown"); + $(this.refs.container).removeAttr("style"); + $(this.refs.mlist).removeAttr("style"); + $(this.refs.container).css("flex", 1); + $(this).removeClass("dropdown"); + const drop = (e: any) => { + return this.dropoff(e); + }; + const show = (e: any) => { + return this.showlist(e); + }; + if (v) { + $(this).addClass("dropdown"); + $(this.refs.current).show(); + $(document).on("click", drop); + $(this.refs.current).on("click", show); + $(this.refs.container) + .css("position", "absolute") + .css("display", "inline-block"); + $(this.refs.mlist) + .css("position", "absolute") + .css("display", "none") + .css("top", "100%") + .css("left", "0"); + this.calibrate(); + } else { + $(this.refs.current).hide(); + $(document).off("click", drop); + $(this.refs.current).off("click", show); + } + } + + /** + * + * + * @memberof ListViewTag + */ + set ondragndrop(v: TagEventCallback) { + this._ondragndrop = v; + } + + /** + * + * + * @memberof ListViewTag + */ + set onlistselect(v: TagEventCallback) { + this._onlistselect = v; + } + + /** + * + * + * @memberof ListViewTag + */ + set onlistdbclick(v: TagEventCallback) { + this._onlistdbclick = v; + } + + /** + * + * + * @memberof ListViewTag + */ + set onitemclose(v: (e: TagEventType) => boolean) { + this._onitemclose = v; + } + + /** + * + * + * @type {boolean} + * @memberof ListViewTag + */ + get dropdown(): boolean { + return this.hasAttribute("dropdown"); + } + + /** + * + * + * @memberof ListViewTag + */ + set itemtag(v: string) { + $(this).attr("itemtag", v); + } + + /** + * + * + * @type {string} + * @memberof ListViewTag + */ + get itemtag(): string { + return $(this).attr("itemtag"); + } + + /** + * + * + * @memberof ListViewTag + */ + set multiselect(v: boolean) { + this.attsw(v, "multiselect"); + } + + /** + * + * + * @memberof ListViewTag + */ + get multiselect() { + if (this.dropdown) { + return false; + } + return this.hasattr("multiselect"); + } + + /** + * + * + * @memberof ListViewTag + */ + set dragndrop(v: boolean) { + this.attsw(v, "dragndrop"); + } + + /** + * + * + * @type {boolean} + * @memberof ListViewTag + */ + get dragndrop(): boolean { + return this.hasattr("dragndrop"); + } + + /** + * + * + * @memberof ListViewTag + */ + set buttons(v: GenericObject[]) { + if (this.dropdown) { + return; + } + if (!v || !(v.length > 0)) { + return; + } + $(this.refs.btlist).empty(); + for (let item of Array.from(v)) { + $(this.refs.btlist).show(); + const bt = $("").appendTo(this.refs.btlist); + (bt[0] as ButtonTag).set(item); + } + } + + /** + * + * + * @type {GenericObject[]} + * @memberof ListViewTag + */ + get data(): GenericObject[] { + return this._data; + } + + /** + * + * + * @memberof ListViewTag + */ + set data(data: GenericObject[]) { + this._data = data; + this._selectedItem = undefined; + this._selectedItems = []; + $(this.refs.mlist).empty(); + for (let item of Array.from(data)) { + this.push(item, false); + } + $(this.refs.container).off("mousedown", this._onmousedown); + if (this.dragndrop && !this.dropdown) { + $(this.refs.container).on( + "mousedown", + this._onmousedown + ); + } + this.ondatachange(); + } + + /** + * + * + * @protected + * @memberof ListViewTag + */ + protected ondatachange(): void {} + + /** + * + * + * @memberof ListViewTag + */ + set selected(idx: number | number[]) { + if (!this.data) { + return; + } + const select = (i: number) => { + if (i < 0) { + this.unselect(); + return; + } + const data = this.data; + if (i >= data.length) { + return; + } + const el = data[i].domel as ListViewItemTag; + el.selected = true; + }; + if (Array.isArray(idx)) { + if (this.multiselect) { + for (const i of idx as number[]) { + select(i); + } + } + } else { + select(idx as number); + } + } + + /** + * + * + * @readonly + * @type {ListViewItemTag} + * @memberof ListViewTag + */ + get selectedItem(): ListViewItemTag { + return this._selectedItem; + } + + /** + * + * + * @readonly + * @type {ListViewItemTag[]} + * @memberof ListViewTag + */ + get selectedItems(): ListViewItemTag[] { + return this._selectedItems; + } + + /** + * + * + * @type {(number | number[])} + * @memberof ListViewTag + */ + get selected(): number | number[] { + if (this.multiselect) { + return this.selectedItems.map(function ( + it: ListViewItemTag + ) { + return $(it).index(); + }); + } + return $(this.selectedItem).index(); + } + + /** + * + * + * @param {GenericObject} item + * @returns + * @memberof ListViewTag + */ + unshift(item: GenericObject) { + return this.push(item, true); + } + + /** + * + * + * @private + * @param {GenericObject} v + * @returns + * @memberof ListViewTag + */ + private has_data(v: GenericObject) { + return this.data && this.data.includes(v); + } + + /** + * + * + * @param {GenericObject} item + * @param {boolean} [flag] + * @returns {ListViewItemTag} + * @memberof ListViewTag + */ + push( + item: GenericObject, + flag?: boolean + ): ListViewItemTag { + let tag = this.itemtag; + if (item.tag) tag = item.tag; + const el = $(`<${tag}>`); + el.appendTo(this.refs.mlist); + if (flag) { + if (!this.has_data(item)) { + this.data.unshift(item); + } + $(this.refs.mlist).prepend(el[0]); + } else { + if (!this.has_data(item)) { + this.data.push(item); + } + } + el[0].uify(this.observable); + const element = el[0] as ListViewItemTag; + element.onctxmenu = (e) => { + return this.iclick(e, true); + }; + element.onitemdbclick = (e) => { + this.idbclick(e); + this.iclick(e, false); + }; + element.onitemclick = (e) => { + return this.iclick(e, false); + }; + element.onitemselect = (e) => { + return this.iselect(e); + }; + element.onitemclose = (e) => { + return this.iclose(e); + }; + element.data = item; + item.domel = el[0]; + return element; + } + + /** + * + * + * @param {ListViewItemTag} item + * @memberof ListViewTag + */ + delete(item: ListViewItemTag): void { + const el = item.data; + const data = this.data; + if (this.selectedItem === item) { + this._selectedItem = undefined; + } + const list = this.selectedItems; + if (list.includes(item)) { + list.splice(list.indexOf(item), 1); + } + if (data.includes(el)) { + data.splice(data.indexOf(el), 1); + } + $(item).remove(); + } + + /** + * + * + * @returns {void} + * @memberof ListViewTag + */ + selectNext(): void { + if (this.multiselect) { + return; + } + const el = this.selectedItem; + let idx = 0; + if (el) { + idx = $(el).index() + 1; + } + this.selected = idx; + } + + /** + * + * + * @returns {void} + * @memberof ListViewTag + */ + selectPrev(): void { + if (this.multiselect) { + return; + } + const el = this.selectedItem; + let idx = 0; + if (el) { + idx = $(el).index() - 1; + } + this.selected = idx; + } + + /** + * + * + * @returns {void} + * @memberof ListViewTag + */ + unselect(): void { + for (let v of this.selectedItems) { + v.selected = false; + } + this._selectedItems = []; + return (this._selectedItem = undefined); + } + + /** + * + * + * @private + * @param {TagEventType} e + * @param {boolean} flag + * @returns {void} + * @memberof ListViewTag + */ + private iclick(e: TagEventType, flag: boolean): void { + if (!e.data) { + return; + } + const list = this.selectedItems; + if (this.multiselect && list.includes(e.data) && !flag) { + list.splice(list.indexOf(e.data), 1); + e.data.selected = false; + return; + } + e.data.selected = true; + } + + /** + * + * + * @private + * @param {TagEventType} e + * @returns + * @memberof ListViewTag + */ + private idbclick(e: TagEventType) { + const evt = { id: this.aid, data: e }; + this._onlistdbclick(evt); + return this.observable.trigger("listdbclick", evt); + } + + /** + * + * + * @private + * @param {TagEventType} e + * @returns + * @memberof ListViewTag + */ + private iselect(e: TagEventType) { + if (!e.data) { + return; + } + var edata = { item: e.data, items: [] }; + if (this.multiselect) { + if (this.selectedItems.includes(e.data)) { + return; + } + this._selectedItem = e.data; + this.selectedItems.push(e.data); + edata.items = this.selectedItems; + } else { + if (this.selectedItem === e.data) { + return; + } + if (this.selectedItem) { + this.selectedItem.selected = false; + } + this._selectedItem = e.data; + this._selectedItems = [e.data]; + edata.items = [e.data]; + //scroll element + const li = $(e.data).children()[0]; + const offset = $(this.refs.container).offset(); + const top = $(this.refs.container).scrollTop(); + if ( + $(li).offset().top + $(li).height() > + $(this.refs.container).height() + offset.top + ) { + $(this.refs.container).scrollTop( + top + + $(this.refs.container).height() - + $(li).height() + ); + } else if ($(li).offset().top < offset.top) { + $(this.refs.container).scrollTop( + top - + $(this.refs.container).height() + + $(li).height() + ); + } + } + + if (this.dropdown) { + const label = this.refs.drlabel as LabelTag; + label.set(e.data.data); + $(this.refs.mlist).hide(); + } + + const evt = { id: this.aid, data: edata }; + this._onlistselect(evt); + return this.observable.trigger("listselect", evt); + } + + /** + * + * + * @protected + * @returns {void} + * @memberof ListViewTag + */ + protected mount(): void { + this._dnd = { + from: undefined, + to: undefined, + }; + this._onmousedown = (e) => { + let el: any = $(e.target).closest( + "li[dataref='afx-list-item']" + ); + if (el.length === 0) { + return; + } + el = el.parent()[0] as ListViewItemTag; + this._dnd.from = el; + this._dnd.to = undefined; + $(window).on("mouseup", this._onmouseup); + $(window).on("mousemove", this._onmousemove); + }; + + this._onmouseup = (e) => { + $(window).off("mouseup", this._onmouseup); + $(window).off("mousemove", this._onmousemove); + $("#systooltip").hide(); + let el: any = $(e.target).closest( + "li[dataref='afx-list-item']" + ); + if (el.length === 0) { + return; + } + el = el.parent()[0]; + if (el === this._dnd.from) { + return; + } + this._dnd.to = el; + this._ondragndrop({ id: this.aid, data: this._dnd }); + this._dnd = { + from: undefined, + to: undefined, + }; + }; + + this._onmousemove = (e) => { + if (!e) { + return; + } + if (!this._dnd.from) { + return; + } + const data = this._dnd.from.data; + const $label = $("#systooltip"); + const top = e.clientY + 5; + const left = e.clientX + 5; + $label.show(); + const label = $label[0] as LabelTag; + label.set(data); + return $label + .css("top", top + "px") + .css("left", left + "px"); + }; + + $(this.refs.btlist).hide(); + this.observable.on("resize", (e) => this.calibrate()); + return this.calibrate(); + } + + /** + * + * + * @private + * @param {TagEventType} e + * @returns {void} + * @memberof ListViewTag + */ + private iclose(e: TagEventType): void { + if (!e.data) { + return; + } + const evt = { id: this.aid, data: e }; + const r = this._onitemclose(evt); + if (!r) { + return; + } + this.observable.trigger("itemclose", evt); + return this.delete(e.data); + } + + /** + * + * + * @protected + * @param {*} e + * @returns {void} + * @memberof ListViewTag + */ + protected showlist(e: any): void { + if (!this.dropdown) { + return; + } + const desktoph = $(Ant.OS.GUI.workspace).height(); + const offset = + $(this).offset().top + $(this.refs.mlist).height(); + if (offset > desktoph) { + $(this.refs.mlist).css( + "top", + `-${$(this.refs.mlist).outerHeight()}px` + ); + } else { + $(this.refs.mlist).css("top", "100%"); + } + $(this.refs.mlist).show(); + } + + /** + * + * + * @protected + * @param {*} e + * @memberof ListViewTag + */ + protected dropoff(e: any): void { + if ($(e.target).closest(this.refs.container).length === 0) { + $(this.refs.mlist).hide(); + } + } + + /** + * + * + * @protected + * @returns {void} + * @memberof ListViewTag + */ + protected calibrate(): void { + if (!this.dropdown) { + return; + } + const w = `${$(this).width()}px`; + $(this.refs.container).css("width", w); + $(this.refs.current).css("width", w); + $(this.refs.mlist).css("width", w); + } + + /** + * + * + * @protected + * @returns {TagLayoutType[]} + * @memberof ListViewTag + */ + protected layout(): TagLayoutType[] { + return [ + { + el: "div", + class: "list-container", + ref: "container", + children: [ + { + el: "div", + ref: "current", + children: [ + { el: "afx-label", ref: "drlabel" }, + ], + }, + { el: "ul", ref: "mlist" }, + ], + }, + { el: "div", class: "button_container", ref: "btlist" }, + ]; + } + } + + define("afx-list-view", ListViewTag); + define("afx-list-item", SimpleListItemTag); + } + } +} diff --git a/src/core/tags/MenuTag.js b/src/core/tags/MenuTag.js deleted file mode 100644 index d997c0b..0000000 --- a/src/core/tags/MenuTag.js +++ /dev/null @@ -1,309 +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 MenuEntryTag extends Ant.OS.GUI.BaseTag { - constructor(r, o) { - super(r, o); - this.setopt("data", {}); - this.setopt("onmenuselect", function() {}); - this.setopt("onchildselect", function() {}); - this.setopt("children", undefined); - this.setopt("child", undefined); - this.setopt("parent", undefined); - this.setopt("root", undefined); - } - - __data__(data) { - return (() => { - const result = []; - for (let k in data) { - const v = data[k]; - result.push(this.set(k, v)); - } - return result; - })(); - } - - __child__(v) { - return this.set("children", v); - } - - has_children() { - const ch = this.get("children"); - return ch && (ch.length > 0); - } - - is_root() { - if (this.get("parent")) { return false; } else { return true; } - } - - layout() { - return [{ - el: "li", ref: "container", children: [ - { - el: "a", ref: "entry", children: this.itemlayout() - }, - { el: "afx-menu", ref: "submenu" } - ] - }]; - } - __children__(v) { - $(this.refs.container).removeClass("afx_submenu"); - if (!v || !(v.length > 0)) { return $(this.refs.submenu).hide(); } - $(this.refs.container).addClass("afx_submenu"); - $(this.refs.submenu) - .show() - .attr("style", ""); - this.refs.submenu.set("parent", this); - this.refs.submenu.set("root", this.get("root")); - this.refs.submenu.set("items", v); - if (this.is_root()) { - return $(this.refs.container).mouseleave(e => { - return $(this.refs.submenu).attr("style", ""); - }); - } - } - - mount() { - return $(this.refs.entry).click(e => this.select(e)); - } - - submenuoff() { - const p = this.get("parent"); - if (!p) { return $(this.refs.submenu).attr("style", ""); } - return p.submenuoff(); - } - - select(e) { - e.item = this.root; - const evt = { id: this.aid(), data: e }; - e.preventDefault(); - if (this.is_root() && this.has_children() && !this.get("context")) { - $(this.refs.submenu).show(); - } else { - this.submenuoff(); - } - this.get("onmenuselect")(evt); - if (this.get("parent")) { - this.get("parent").get("onchildselect")(evt); - } - if (this.get("root")) { - return this.get("root").get("onmenuitemselect")(evt); - } - } - - itemlayout() {} -} - -class SimpleMenuEntryTag extends MenuEntryTag { - constructor(r, o) { - super(r, o); - this.setopt("switch", false); - this.setopt("radio", false); - this.setopt("color", undefined); - this.setopt("icon", undefined); - this.setopt("iconclass", undefined); - this.setopt("text", ""); - this.setopt("shortcut", undefined); - this.setopt("checked", false); - } - - __switch__(v) { - if (this.get("radio") || v) { - return $(this.refs.switch).show(); - } else { - return $(this.refs.switch).hide(); - } - } - - __radio__(v) { - if (this.get("switch") || v) { - return $(this.refs.switch).show(); - } else { - return $(this.refs.switch).hide(); - } - } - - __checked__(v) { - this.get("data").checked = v; - if (!this.get("radio") && !this.get("switch")) { return; } - return this.refs.switch.set("swon", v); - } - - __color__(v) { - if (!v) { return; } - return this.refs.label.set("color", v); - } - - __icon__(v) { - $(this.refs.container).removeClass("fix_padding"); - if (!v) { return; } - this.refs.label.set("icon", v); - return $(this.refs.container).addClass("fix_padding"); - } - - __iconclass__(v) { - if (!v) { return; } - return this.refs.label.set("iconclass", v); - } - - __text__(v) { - if (v === undefined) { return; } - return this.refs.label.set("text", v); - } - - __shortcut__(v) { - $(this.refs.shortcut).hide(); - if (!v) { return; } - $(this.refs.shortcut).show(); - return $(this.refs.shortcut).text(v); - } - - reset_radio() { - if (!this.has_children()) { return; } - for (let v of Array.from(this.get("children"))) { - if (!v.domel.get("radio")) { return; } - v.domel.set("checked", false); - } - } - - mount() { - super.mount(); - return this.refs.switch.set("enable", false); - } - - select(e) { - if (this.get("switch")) { - this.set("checked", !this.get("checked")); - } else if (this.get("radio")) { - const p = this.get("parent"); - if (p) { p.reset_radio(); } - this.set("checked", !this.get("checked")); - } - return super.select(e); - } - - itemlayout() { - return [ - { el: "afx-switch", ref: "switch" }, - { el: "afx-label", ref: "label" }, - { el: "span", class: "shortcut", ref: "shortcut" } - ]; - } -} - -class MenuTag extends Ant.OS.GUI.BaseTag { - constructor(r, o) { - super(r, o); - this.setopt("context", false); - this.setopt("parent", undefined); - this.setopt("root", undefined); - this.setopt("contentag", "afx-menu-entry"); - this.setopt("onmenuitemselect", e => this.handleselect(e)); - this.setopt("onmenuselect", function(e) {}); - this.setopt("items", []); - this.root.show = e => { - return this.showctxmenu(e); - }; - this.root.push = e => this.push(e); - this.root.remove = e => this.remove(e); - this.root.unshift = e => this.unshift(e); - } - - handleselect(e) { - if (this.isctxmenu()) { $(this.root).hide(); } - e.id = this.aid(); - this.get("onmenuselect")(e); - return this.observable.trigger("menuselect", e); - } - - showctxmenu(e) { - if (!this.get("context")) { return; } - return $(this.root) - .css("top", (e.clientY - 15) + "px") - .css("left", (e.clientX - 5) + "px") - .show(); - } - - isctxmenu() { - return this.get("context"); - } - - is_root() { - return this.get("root") === undefined; - } - - mount() { - $(this.refs.container).css("display", "contents"); - if (!this.isctxmenu()) { return; } - return $(this.refs.wrapper).mouseleave(e => { - if (!this.is_root()) { return; } - return $(this.root).hide(); - }); - } - - __context__(v) { - $(this.refs.wrapper).removeClass("context"); - if (!v) { return; } - $(this.refs.wrapper).addClass("context"); - return $(this.root).hide(); - } - - unshift(item) { - return this.push(item, true); - } - - remove(item) { - const el = item.get("data"); - const data = this.get("items"); - if (data.includes(el)) { - data.splice(data.indexOf(el), 1); - } - return $(item).remove(); - } - - push(item, flag) { - let tag = this.get("contentag"); - if (item.tag) { ({ - tag - } = item); } - const items = this.get("items"); - const el = $(`<${tag}>`); - if (flag) { - $(this.refs.container).prepend(el[0]); - if (!items.includes(item)) { this.get("items").unshift(item); } - } else { - el.appendTo(this.refs.container); - if (!items.includes(item)) { this.get("items").push(item); } - } - el[0].uify(undefined); - el[0].set("parent", this.get("parent")); - el[0].set("root", this.get("parent") ? this.get("parent").get("root") : this); - el[0].set("data", item); - item.domel = el[0]; - return el[0]; - } - - __items__(data) { - $(this.refs.container).empty(); - return Array.from(data).map((item) => - this.push(item, false)); - } - - layout() { - return [{ el: "ul", ref: "wrapper", children: [ - { el: "li", class: "afx-corner-fix" }, - { el: "div", ref: "container" }, - { el: "li", class: "afx-corner-fix" } - ] }]; - } -} - -Ant.OS.GUI.define("afx-menu", MenuTag); -Ant.OS.GUI.define("afx-menu-entry-proto", MenuEntryTag); -Ant.OS.GUI.define("afx-menu-entry", SimpleMenuEntryTag); \ No newline at end of file diff --git a/src/core/tags/MenuTag.ts b/src/core/tags/MenuTag.ts new file mode 100644 index 0000000..6b45d2b --- /dev/null +++ b/src/core/tags/MenuTag.ts @@ -0,0 +1,740 @@ +/* + * 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 + */ +namespace OS { + export namespace GUI { + export namespace tag { + /** + * + * + * @export + * @abstract + * @class MenuEntryTag + * @extends {AFXTag} + */ + export abstract class MenuEntryTag extends AFXTag { + private _data: GenericObject; + private _onmenuselect: TagEventCallback; + private _onchildselect: TagEventCallback; + parent: MenuEntryTag; + root: MenuTag; + + /** + *Creates an instance of MenuEntryTag. + * @memberof MenuEntryTag + */ + constructor() { + super(); + this._onmenuselect = this._onchildselect = ( + e: TagEventType + ): void => {}; + } + + /** + * + * + * @protected + * @memberof MenuEntryTag + */ + protected init(): void { + this.nodes = undefined; + } + /** + * + * + * @memberof MenuEntryTag + */ + set onmenuselect(v: TagEventCallback) { + this._onmenuselect = v; + } + + /** + * + * + * @memberof MenuEntryTag + */ + set onchildselect(v: TagEventCallback) { + this._onchildselect = v; + } + + /** + * + * + * @type {TagEventCallback} + * @memberof MenuEntryTag + */ + get onchildselect(): TagEventCallback { + return this._onchildselect; + } + /** + * + * + * @memberof MenuEntryTag + */ + set data(data: GenericObject) { + this._data = data; + this.set(data); + } + + /** + * + * + * @type {GenericObject} + * @memberof MenuEntryTag + */ + get data(): GenericObject { + return this._data; + } + + /** + * + * + * @protected + * @returns {boolean} + * @memberof MenuEntryTag + */ + protected has_nodes(): boolean { + const ch = this.nodes; + return ch && ch.length > 0; + } + + /** + * + * + * @protected + * @returns + * @memberof MenuEntryTag + */ + protected is_root() { + if (this.parent) { + return false; + } else { + return true; + } + } + + /** + * + * + * @protected + * @returns {TagLayoutType[]} + * @memberof MenuEntryTag + */ + protected layout(): TagLayoutType[] { + return [ + { + el: "li", + ref: "container", + children: [ + { + el: "a", + ref: "entry", + children: this.itemlayout(), + }, + { el: "afx-menu", ref: "submenu" }, + ], + }, + ]; + } + + /** + * + * + * @memberof MenuEntryTag + */ + set nodes(v: GenericObject[]) { + $(this.refs.container).removeClass("afx_submenu"); + if (!v || !(v.length > 0)) { + $(this.refs.submenu).hide(); + return; + } + $(this.refs.container).addClass("afx_submenu"); + $(this.refs.submenu).show().attr("style", ""); + const element = this.refs.submenu as MenuTag; + element.parent = this; + element.root = this.root; + element.items = v; + // ensure that the data is in sync + this._data.nodes = v; + if (this.is_root()) { + $(this.refs.container).mouseleave((e) => { + return $(this.refs.submenu).attr("style", ""); + }); + } + } + + /** + * + * + * @type {GenericObject[]} + * @memberof MenuEntryTag + */ + get nodes(): GenericObject[] { + if (this.data && this.data.nodes) { + return this.data.nodes; + } + return undefined; + } + /** + * + * + * @protected + * @memberof MenuEntryTag + */ + protected mount(): void { + $(this.refs.entry).click((e) => this.select(e)); + } + + /** + * + * + * @private + * @returns {void} + * @memberof MenuEntryTag + */ + private submenuoff(): void { + const p = this.parent; + if (!p) { + $(this.refs.submenu).attr("style", ""); + return; + } + return p.submenuoff(); + } + + /** + * + * + * @protected + * @param {JQuery.ClickEvent} e + * @memberof MenuEntryTag + */ + protected select(e: JQuery.ClickEvent): void { + const evt = { + id: this.aid, + data: { item: this, event: e }, + }; + e.preventDefault(); + if (this.is_root() && this.has_nodes()) { + console.log("show submenu"); + $(this.refs.submenu).show(); + } else { + this.submenuoff(); + } + this._onmenuselect(evt); + if (this.parent) { + this.parent.onchildselect(evt); + } + if (this.root) { + this.root.onmenuitemselect(evt); + } + } + + protected abstract itemlayout(): TagLayoutType[]; + } + + /** + * + * + * @class SimpleMenuEntryTag + * @extends {MenuEntryTag} + */ + export class SimpleMenuEntryTag extends MenuEntryTag { + /** + *Creates an instance of SimpleMenuEntryTag. + * @memberof SimpleMenuEntryTag + */ + constructor() { + super(); + } + protected init(): void { + super.init(); + this.switch = false; + this.radio = false; + this.checked = false; + } + protected calibrate(): void {} + protected reload(d?: any): void {} + + /** + * + * + * @memberof SimpleMenuEntryTag + */ + set switch(v: boolean) { + this.attsw(v, "switch"); + if (this.radio || v) { + $(this.refs.switch).show(); + } else { + $(this.refs.switch).hide(); + } + } + + /** + * + * + * @type {boolean} + * @memberof SimpleMenuEntryTag + */ + get switch(): boolean { + return this.hasattr("switch"); + } + + /** + * + * + * @memberof SimpleMenuEntryTag + */ + set radio(v: boolean) { + this.attsw(v, "radio"); + if (this.switch || v) { + $(this.refs.switch).show(); + } else { + $(this.refs.switch).hide(); + } + } + + /** + * + * + * @type {boolean} + * @memberof SimpleMenuEntryTag + */ + get radio(): boolean { + return this.hasattr("radio"); + } + + /** + * + * + * @memberof SimpleMenuEntryTag + */ + set checked(v: boolean) { + this.attsw(v, "checked"); + if (this.data) this.data.checked = v; + if (!this.radio && !this.switch) { + return; + } + (this.refs.switch as SwitchTag).swon = v; + } + + /** + * + * + * @type {boolean} + * @memberof SimpleMenuEntryTag + */ + get checked(): boolean { + return this.hasattr("checked"); + } + + /** + * + * + * @memberof SimpleMenuEntryTag + */ + set icon(v: string) { + $(this.refs.container).removeClass("fix_padding"); + if (!v) { + return; + } + //$(this).attr("icon", v); + const label = this.refs.label as LabelTag; + label.icon = v; + $(this.refs.container).addClass("fix_padding"); + } + + /** + * + * + * @memberof SimpleMenuEntryTag + */ + set iconclass(v: string) { + if (!v) { + return; + } + const label = this.refs.label as LabelTag; + label.iconclass = v; + } + + /** + * + * + * @memberof SimpleMenuEntryTag + */ + set text(v: string) { + if (v === undefined) { + return; + } + const label = this.refs.label as LabelTag; + label.text = v; + } + + /** + * + * + * @memberof SimpleMenuEntryTag + */ + set shortcut(v: string) { + $(this.refs.shortcut).hide(); + if (!v) { + return; + } + $(this.refs.shortcut).show(); + $(this.refs.shortcut).text(v); + } + + /** + * + * + * @returns {void} + * @memberof SimpleMenuEntryTag + */ + reset_radio(): void { + if (!this.has_nodes()) { + return; + } + for (let v of this.nodes) { + if (!v.domel.get("radio")) { + return; + } + v.domel.set("checked", false); + } + } + + /** + * + * + * @protected + * @memberof SimpleMenuEntryTag + */ + protected mount(): void { + super.mount(); + (this.refs.switch as SwitchTag).enable = false; + } + + /** + * + * + * @protected + * @param {JQuery.ClickEvent} e + * @returns {void} + * @memberof SimpleMenuEntryTag + */ + protected select(e: JQuery.ClickEvent): void { + if (this.switch) { + this.checked = !this.checked; + } else if (this.radio) { + const p = this.parent as SimpleMenuEntryTag; + if (p) { + p.reset_radio(); + } + this.checked = !this.checked; + } + return super.select(e); + } + + /** + * + * + * @returns + * @memberof SimpleMenuEntryTag + */ + itemlayout() { + return [ + { el: "afx-switch", ref: "switch" }, + { el: "afx-label", ref: "label" }, + { el: "span", class: "shortcut", ref: "shortcut" }, + ]; + } + } + + /** + * + * + * @export + * @class MenuTag + * @extends {AFXTag} + */ + export class MenuTag extends AFXTag { + parent: MenuEntryTag; + root: MenuTag; + pid: number; + private _onmenuselect: TagEventCallback; + private _items: GenericObject[]; + + /** + *Creates an instance of MenuTag. + * @memberof MenuTag + */ + constructor() { + super(); + } + + /** + * + * + * @protected + * @memberof MenuTag + */ + protected init(): void { + this.contentag = "afx-menu-entry"; + this.context = false; + this._items = []; + this._onmenuselect = (e: TagEventType): void => {}; + } + + /** + * + * + * @protected + * @memberof MenuTag + */ + protected calibrate(): void {} + + /** + * + * + * @protected + * @param {*} [d] + * @memberof MenuTag + */ + protected reload(d?: any): void {} + + /** + * + * + * @memberof MenuTag + */ + set items(data: GenericObject[]) { + this._items = data; + $(this.refs.container).empty(); + data.map((item) => this.push(item, false)); + } + + /** + * + * + * @type {GenericObject[]} + * @memberof MenuTag + */ + get items(): GenericObject[] { + return this._items; + } + + /** + * + * + * @memberof MenuTag + */ + set context(v: boolean) { + this.attsw(v, "context"); + $(this.refs.wrapper).removeClass("context"); + if (!v) { + return; + } + $(this.refs.wrapper).addClass("context"); + $(this).hide(); + } + + /** + * + * + * @type {boolean} + * @memberof MenuTag + */ + get context(): boolean { + return this.hasattr("context"); + } + + /** + * + * + * @memberof MenuTag + */ + set onmenuselect(v: TagEventCallback) { + this._onmenuselect = v; + } + + /** + * + * + * @memberof MenuTag + */ + set contentag(v: string) { + $(this).attr("contentag", v); + } + + /** + * + * + * @type {string} + * @memberof MenuTag + */ + get contentag(): string { + return $(this).attr("contentag"); + } + + /** + * + * + * @readonly + * @type {TagEventCallback} + * @memberof MenuTag + */ + get onmenuitemselect(): TagEventCallback { + return this.handleselect; + } + + /** + * + * + * @private + * @param {TagEventType} e + * @memberof MenuTag + */ + private handleselect(e: TagEventType): void { + if (this.context) { + $(this).hide(); + } + e.id = this.aid; + this._onmenuselect(e); + this.observable.trigger("menuselect", e); + } + + /** + * + * + * @param {JQuery.MouseEventBase} e + * @returns {void} + * @memberof MenuTag + */ + show(e: JQuery.MouseEventBase): void { + if (!this.context) { + return; + } + $(this) + .css("top", e.clientY - 15 + "px") + .css("left", e.clientX - 5 + "px") + .show(); + } + + /** + * + * + * @private + * @returns {boolean} + * @memberof MenuTag + */ + private is_root(): boolean { + return this.root === undefined; + } + + /** + * + * + * @protected + * @returns {void} + * @memberof MenuTag + */ + protected mount(): void { + $(this.refs.container).css("display", "contents"); + if (!this.context) { + return; + } + $(this.refs.wrapper).mouseleave((e) => { + if (!this.is_root()) { + return; + } + return $(this).hide(); + }); + } + + /** + * + * + * @param {GenericObject} item + * @memberof MenuTag + */ + unshift(item: GenericObject): void { + this.push(item, true); + } + + /** + * + * + * @param {MenuEntryTag} item + * @memberof MenuTag + */ + delete(item: MenuEntryTag): void { + const el = item.data; + const data = this.items; + if (data.includes(el)) { + data.splice(data.indexOf(el), 1); + } + $(item).remove(); + } + + /** + * + * + * @param {GenericObject} item + * @param {boolean} flag + * @returns {MenuEntryTag} + * @memberof MenuTag + */ + push(item: GenericObject, flag: boolean): MenuEntryTag { + let tag = this.contentag; + if (item.tag) { + tag = item.tag; + } + const el = $(`<${tag}>`); + if (flag) { + $(this.refs.container).prepend(el[0]); + if (!this.items.includes(item)) { + this.items.unshift(item); + } + } else { + el.appendTo(this.refs.container); + if (!this.items.includes(item)) { + this.items.push(item); + } + } + const entry = el[0] as MenuEntryTag; + entry.uify(this.observable); + entry.parent = this.parent; + entry.root = this.parent ? this.parent.root : this; + entry.data = item; + item.domel = entry; + return entry; + } + + /** + * + * + * @returns + * @memberof MenuTag + */ + layout() { + return [ + { + el: "ul", + ref: "wrapper", + children: [ + { el: "li", class: "afx-corner-fix" }, + { el: "div", ref: "container" }, + { el: "li", class: "afx-corner-fix" }, + ], + }, + ]; + } + } + + define("afx-menu", MenuTag); + define("afx-menu-entry", SimpleMenuEntryTag); + } + } +} diff --git a/src/core/tags/NSpinnerTag.js b/src/core/tags/NSpinnerTag.js deleted file mode 100644 index 76c9ac5..0000000 --- a/src/core/tags/NSpinnerTag.js +++ /dev/null @@ -1,95 +0,0 @@ -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ - -class NSpinnerTag extends Ant.OS.GUI.BaseTag { - constructor(r, o) { - super(r, o); - this.setopt("onchange", function(e) {}); - this.setopt("value", 0); - this.setopt("step", 1); - } - - mount() { - $(this.refs.holder).attr("type", "text"); - $(this.refs.incr).click(e => { - return this.set("value", (this.get("value") + this.get("step") )); - }); - - $(this.refs.decr).click(e => { - return this.set("value", (this.get("value") - this.get("step") )); - }); - - // @observable.on "calibrate", () -> @calibrate() - this.observable.on("resize", () => this.calibrate()); - - $(this.refs.holder).on('keyup', e => { - if (e.keyCode === 13) { - let val = this.refs.holder.value; - if (!isNaN(val)) { - val = parseInt(val); - if (val < 0) { val = this.value; } - return this.set("value", val); - } - } - }); - return this.calibrate(); - } - - calibrate() { - $(this.refs.holder).css("width", ($(this.root).width() - 20) + "px"); - $(this.refs.holder).css("height", $(this.root).height() + "px"); - $(this.refs.spinner) - .css("width", "20px") - .css("height", $(this.root).height() + "px"); - $(this.refs.incr) - .css("height", (($(this.root).height() / 2) - 2) + "px") - .css("position", "relative"); - $(this.refs.decr).css("height", (($(this.root).height() / 2) - 2) + "px") - .css("position", "relative"); - $(this.refs.spinner).find("li") - .css("display", "block") - .css("text-align", "center") - .css("vertical-align", "middle"); - $(this.refs.spinner).find("i") - .css("font-size", "16px") - .css("position", "absolute"); - const fn = function(ie, pos) { - const el = $(ie).find("i"); - return el - .css(pos, (($(ie).height() - el.height()) / 2) + "px") - .css("left", (($(ie).width() - el.width()) / 2) + "px"); - }; - fn(this.refs.decr, "bottom"); - return fn(this.refs.incr, "top"); - } - - __value__(v) { - $(this.refs.holder).val(this.get("value")); - const evt = { id: this.aid(), data: v }; - this.get("onchange")(evt); - return this.observable.trigger("nspin", evt); - } - - layout() { - return [ - { - el: "input", ref: "holder" - }, - { - el: "ul", ref: "spinner", children: [ - { el: "li", class: "incr", ref: "incr", children: [ - { el: "i" } - ] }, - { el: "li", class: "decr", ref: "decr", children: [ - { el: "i" } - ] } - ] - } - ]; - } -} - -Ant.OS.GUI.define("afx-nspinner", NSpinnerTag); \ No newline at end of file diff --git a/src/core/tags/NSpinnerTag.ts b/src/core/tags/NSpinnerTag.ts new file mode 100644 index 0000000..37e9a24 --- /dev/null +++ b/src/core/tags/NSpinnerTag.ts @@ -0,0 +1,193 @@ +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ + +namespace OS { + export namespace GUI { + export namespace tag { + /** + * + * + * @export + * @class NSpinnerTag + * @extends {AFXTag} + */ + export class NSpinnerTag extends AFXTag { + private _onchange: TagEventCallback; + private _value: number; + step: number; + + /** + *Creates an instance of NSpinnerTag. + * @memberof NSpinnerTag + */ + constructor() { + super(); + this._onchange = (e) => {}; + } + + /** + * + * + * @protected + * @memberof NSpinnerTag + */ + protected init(): void {} + + /** + * + * + * @protected + * @param {*} [d] + * @memberof NSpinnerTag + */ + protected reload(d?: any): void {} + + /** + * + * + * @memberof NSpinnerTag + */ + set onvaluechange(f: TagEventCallback) { + this._onchange = f; + } + + /** + * + * + * @protected + * @memberof NSpinnerTag + */ + protected mount(): void { + $(this.refs.holder).attr("type", "text"); + $(this.refs.incr).click((e) => { + this.value = this.value + this.step; + }); + + $(this.refs.decr).click((e) => { + return (this.value = this.value - this.step); + }); + + // @observable.on "calibrate", () -> @calibrate() + this.observable.on("resize", () => this.calibrate()); + + $(this.refs.holder).on("keyup", (e) => { + if (e.keyCode === 13) { + let val = parseInt( + (this.refs.holder as HTMLInputElement).value + ); + if (!isNaN(val)) { + if (val < 0) { + val = this.value; + } + return (this.value = val); + } + } + }); + this.calibrate(); + } + + /** + * + * + * @memberof NSpinnerTag + */ + calibrate(): void { + $(this.refs.holder).css( + "width", + $(this).width() - 20 + "px" + ); + $(this.refs.holder).css("height", $(this).height() + "px"); + $(this.refs.spinner) + .css("width", "20px") + .css("height", $(this).height() + "px"); + $(this.refs.incr) + .css("height", $(this).height() / 2 - 2 + "px") + .css("position", "relative"); + $(this.refs.decr) + .css("height", $(this).height() / 2 - 2 + "px") + .css("position", "relative"); + $(this.refs.spinner) + .find("li") + .css("display", "block") + .css("text-align", "center") + .css("vertical-align", "middle"); + $(this.refs.spinner) + .find("i") + .css("font-size", "16px") + .css("position", "absolute"); + const fn = function (ie: HTMLElement, pos: string) { + const el = $(ie).find("i"); + el.css( + pos, + ($(ie).height() - el.height()) / 2 + "px" + ).css("left", ($(ie).width() - el.width()) / 2 + "px"); + }; + fn(this.refs.decr, "bottom"); + fn(this.refs.incr, "top"); + } + + /** + * + * + * @memberof NSpinnerTag + */ + set value(v: number) { + this._value = v; + $(this.refs.holder).val(this._value); + const evt = { id: this.aid, data: v }; + this._onchange(evt); + this.observable.trigger("nspin", evt); + } + + /** + * + * + * @type {number} + * @memberof NSpinnerTag + */ + get value(): number { + return this._value; + } + + /** + * + * + * @protected + * @returns {TagLayoutType[]} + * @memberof NSpinnerTag + */ + protected layout(): TagLayoutType[] { + return [ + { + el: "input", + ref: "holder", + }, + { + el: "ul", + ref: "spinner", + children: [ + { + el: "li", + class: "incr", + ref: "incr", + children: [{ el: "i" }], + }, + { + el: "li", + class: "decr", + ref: "decr", + children: [{ el: "i" }], + }, + ], + }, + ]; + } + } + + define("afx-nspinner", NSpinnerTag); + } + } +} diff --git a/src/core/tags/OverlayTag.js b/src/core/tags/OverlayTag.js deleted file mode 100644 index 804aa8c..0000000 --- a/src/core/tags/OverlayTag.js +++ /dev/null @@ -1,56 +0,0 @@ -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ -class OverlayTag extends Ant.OS.GUI.BaseTag { - constructor(r, o) { - super(r, o); - this.setopt("width", undefined); - this.setopt("height", undefined); - $(this.refs.yield) - .css("position", "relative") - .css("width", "100%" ) - .css("height", "100%"); - $(this.root) - .css("position", "absolute") - .css("z-index", 1000000); - } - //.css "display", "flex" - //.css "flex-direction", "column" - //$(@refs.yield).css "flex", "1" - - __width__(v) { - if (!v) { return; } - return this.calibrate(); - } - - __height__(v) { - if (!v) { return; } - return this.calibrate(); - } - - mount() { - return this.calibrate(); - } - - calibrate() { - $(this.root) - .css("width", this.get("width") ) - .css("height", this.get("height")); - return this.observable.trigger("resize", { - id: this.aid(), - data: { - w: this.get("width"), - h: this.get("height") - } - }); - } - - layout() { - return [{ - el: "afx-vbox", ref: "yield" - }]; - } -} -Ant.OS.GUI.define("afx-overlay", OverlayTag); \ No newline at end of file diff --git a/src/core/tags/OverlayTag.ts b/src/core/tags/OverlayTag.ts new file mode 100644 index 0000000..7ea58e2 --- /dev/null +++ b/src/core/tags/OverlayTag.ts @@ -0,0 +1,147 @@ +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +namespace OS { + export namespace GUI { + export namespace tag { + /** + * + * + * @export + * @class OverlayTag + * @extends {AFXTag} + */ + export class OverlayTag extends AFXTag { + private _width: string; + private _height: string; + + /** + *Creates an instance of OverlayTag. + * @memberof OverlayTag + */ + constructor() { + super(); + } + //.css "display", "flex" + //.css "flex-direction", "column" + //$(@refs.yield).css "flex", "1" + + /** + * + * + * @protected + * @memberof OverlayTag + */ + protected init(): void { + $(this.refs.yield) + .css("position", "relative") + .css("width", "100%") + .css("height", "100%"); + $(this).css("position", "absolute").css("z-index", 1000000); + } + + /** + * + * + * @protected + * @param {*} [d] + * @memberof OverlayTag + */ + protected reload(d?: any): void {} + + /** + * + * + * @memberof OverlayTag + */ + set width(v: string) { + if (!v) { + return; + } + this._width = v; + this.calibrate(); + } + + /** + * + * + * @type {string} + * @memberof OverlayTag + */ + get width(): string { + return this._width; + } + + /** + * + * + * @memberof OverlayTag + */ + set height(v: string) { + if (!v) { + return; + } + this._height = v; + this.calibrate(); + } + + /** + * + * + * @type {string} + * @memberof OverlayTag + */ + get height(): string { + return this._height; + } + + /** + * + * + * @protected + * @returns {void} + * @memberof OverlayTag + */ + protected mount(): void { + return this.calibrate(); + } + + /** + * + * + * @returns {void} + * @memberof OverlayTag + */ + calibrate(): void { + $(this).css("width", this.width).css("height", this.height); + return this.observable.trigger("resize", { + id: this.aid, + data: { + w: this.width, + h: this.height, + }, + }); + } + + /** + * + * + * @protected + * @returns {TagLayoutType[]} + * @memberof OverlayTag + */ + protected layout(): TagLayoutType[] { + return [ + { + el: "afx-vbox", + ref: "yield", + }, + ]; + } + } + define("afx-overlay", OverlayTag); + } + } +} diff --git a/src/core/tags/ResizerTag.js b/src/core/tags/ResizerTag.js deleted file mode 100644 index bf2f854..0000000 --- a/src/core/tags/ResizerTag.js +++ /dev/null @@ -1,89 +0,0 @@ -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ -class ResizerTag extends Ant.OS.GUI.BaseTag { - constructor(r, o) { - super(r, o); - this.dir = "hz"; - this.resizable_el = undefined; - this.parent = $(this.root).parent().parent(); - this.minsize = 0; - } - - mount() { - let att; - $(this.root).css(" display", "block"); - const tagname = $(this.parent).prop("tagName"); - this.resizable_el = $(this.root).prev().length === 1 ? $(this.root).prev()[0] : undefined; - if (tagname === "AFX-HBOX") { - this.dir = "hz"; - $(this.root).css("cursor", "col-resize"); - $(this.root).addClass("horizontal"); - if (this.resizable_el) { - att = $(this.resizable_el).attr("min-width"); - if (att) { this.minsize = parseInt(att); } - } - } else if (tagname === "AFX-VBOX") { - this.dir = "ve"; - $(this.root).css("cursor", "row-resize"); - $(this.root).addClass("vertical"); - if (this.resizable_el) { - att = $(this.resizable_el).attr("min-height"); - if (att) { this.minsize = parseInt(att); } - } - } else { - this.dir = "none"; - } - if (this.minsize === 0) { this.minsize = 10; } - return this.draggable(); - } - - draggable() { - $(this.root).css("user-select", "none"); - return $(this.root).on("mousedown", e => { - e.preventDefault(); - $(window).on("mousemove", evt => { - if (!this.resizable_el) { return; } - if (this.dir === "hz") { - return this.horizontalResize(evt); - } else if (this.dir === "ve") { - return this.verticalResize(evt); - } - }); - - return $(window).on("mouseup", function(evt) { - $(window).unbind("mousemove", null); - $(window).unbind("mouseup", null); - - return $(window).unbind("mouseup", null); - }); - }); - } - - horizontalResize(e) { - if (!this.resizable_el) { return; } - const offset = $(this.resizable_el).offset(); - let w = Math.round(e.clientX - offset.left); - if (w < this.minsize) { w = this.minsize; } - $(this.resizable_el).attr("data-width", w.toString()); - return this.observable.trigger("resize", { id: this.aid(), data: { w } }); - } - - - verticalResize(e) { - if (!this.resizable_el) { return; } - const offset = $(this.resizable_el).offset(); - let h = Math.round(e.clientY - offset.top); - if (h < this.minsize) { h = this.minsize; } - $(this.resizable_el).attr("data-height", h.toString()); - return this.observable.trigger("resize", { id: this.aid(), data: { w } }); - } - - layout() { - return []; - } -} - -Ant.OS.GUI.define("afx-resizer", ResizerTag); \ No newline at end of file diff --git a/src/core/tags/ResizerTag.ts b/src/core/tags/ResizerTag.ts new file mode 100644 index 0000000..c9d877e --- /dev/null +++ b/src/core/tags/ResizerTag.ts @@ -0,0 +1,206 @@ +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +namespace OS { + export namespace GUI { + export namespace tag { + /** + * + * + * @export + * @class ResizerTag + * @extends {AFXTag} + */ + export class ResizerTag extends AFXTag { + private _resizable_el: any; + private _parent: any; + private _minsize: number; + + /** + *Creates an instance of ResizerTag. + * @memberof ResizerTag + */ + constructor() { + super(); + } + + /** + * + * + * @protected + * @memberof ResizerTag + */ + protected init(): void { + this.dir = "hz"; + this._resizable_el = undefined; + this._parent = $(this).parent().parent()[0]; + this._minsize = 0; + } + + /** + * + * + * @protected + * @param {*} [d] + * @memberof ResizerTag + */ + protected reload(d?: any): void { + } + /** + * + * + * @memberof ResizerTag + */ + set dir(v: string) { + $(this).attr("dir", v); + } + + /** + * + * + * @type {string} + * @memberof ResizerTag + */ + get dir(): string { + return $(this).attr("dir"); + } + + /** + * + * + * @protected + * @memberof ResizerTag + */ + protected mount(): void { + let att: string; + $(this).css(" display", "block"); + const tagname = $(this._parent).prop("tagName"); + this._resizable_el = + $(this).prev().length === 1 + ? $(this).prev()[0] + : undefined; + if (tagname === "AFX-HBOX") { + this.dir = "hz"; + $(this).css("cursor", "col-resize"); + $(this).addClass("horizontal"); + if (this._resizable_el) { + att = $(this._resizable_el).attr("min-width"); + if (att) { + this._minsize = parseInt(att); + } + } + } else if (tagname === "AFX-VBOX") { + this.dir = "ve"; + $(this).css("cursor", "row-resize"); + $(this).addClass("vertical"); + if (this._resizable_el) { + att = $(this._resizable_el).attr("min-height"); + if (att) { + this._minsize = parseInt(att); + } + } + } else { + this.dir = "none"; + } + if (this._minsize === 0) { + this._minsize = 10; + } + this.make_draggable(); + } + + /** + * + * + * @private + * @memberof ResizerTag + */ + private make_draggable(): void { + $(this).css("user-select", "none"); + $(this).on("mousedown", (e) => { + e.preventDefault(); + $(window).on("mousemove", (evt) => { + if (!this._resizable_el) { + return; + } + if (this.dir === "hz") { + return this.horizontalResize(evt); + } else if (this.dir === "ve") { + return this.verticalResize(evt); + } + }); + + return $(window).on("mouseup", function (evt) { + $(window).unbind("mousemove", null); + $(window).unbind("mouseup", null); + + return $(window).unbind("mouseup", null); + }); + }); + } + + /** + * + * + * @private + * @param {JQuery.MouseEventBase} e + * @returns {void} + * @memberof ResizerTag + */ + private horizontalResize(e: JQuery.MouseEventBase): void { + if (!this._resizable_el) { + return; + } + const offset = $(this._resizable_el).offset(); + let w = Math.round(e.clientX - offset.left); + if (w < this._minsize) { + w = this._minsize; + } + $(this._resizable_el).attr("data-width", w.toString()); + this.observable.trigger("resize", { + id: this.aid, + data: { w }, + }); + } + + /** + * + * + * @protected + * @param {JQuery.MouseEventBase} e + * @returns {void} + * @memberof ResizerTag + */ + protected verticalResize(e: JQuery.MouseEventBase): void { + if (!this._resizable_el) { + return; + } + const offset = $(this._resizable_el).offset(); + let h = Math.round(e.clientY - offset.top); + if (h < this._minsize) { + h = this._minsize; + } + $(this._resizable_el).attr("data-height", h.toString()); + return this.observable.trigger("resize", { + id: this.aid, + data: { h }, + }); + } + + /** + * + * + * @protected + * @returns {TagLayoutType[]} + * @memberof ResizerTag + */ + protected layout(): TagLayoutType[] { + return []; + } + } + + define("afx-resizer", ResizerTag); + } + } +} diff --git a/src/core/tags/SliderTag.js b/src/core/tags/SliderTag.js deleted file mode 100644 index 169fea6..0000000 --- a/src/core/tags/SliderTag.js +++ /dev/null @@ -1,112 +0,0 @@ -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ -class SliderTag extends Ant.OS.GUI.BaseTag { - constructor(r, o) { - super(r, o); - this.setopt("dragable", true); - this.setopt("max", 100); - this.setopt("value", 0); - this.setopt("onchanging", function(e) {}); - this.setopt("onchange", function(e) {}); - } - - - __value__() { - return this.calibrate(); - } - - __max__() { - return this.calibrate(); - } - - __dragable__(v) { - if (v) { - return $(this.root) - .mouseover(() => { - return $(this.refs.point).show(); - }).mouseout(() => { - return $(this.refs.point).hide(); - }); - } else { - $(this.refs.point).hide(); - return $(this.root) - .unbind("mouseover") - .ubbind("mouseout"); - } - } - - mount() { - this.enable_dragging(); - $(this.refs.point).css("position", "absolute"); - $(this.refs.point).hide(); - this.observable.on("resize", e => { - return this.calibrate(); - }); - $(this.refs.container).click(e => { - const offset = $(this.refs.container).offset(); - const left = e.clientX - offset.left; - const maxw = $(this.refs.container).width(); - this.set("value", (left * this.get("max")) / maxw); - this.calibrate(); - const evt = { id: this.aid(), data: this.get("value") }; - this.get("onchange")(evt); - return this.get("onchanging")(evt); - }); - return this.calibrate(); - } - - calibrate() { - if (this.get("value") > this.get("max")) { this.set("value", this.get("max")); } - $(this.refs.container).css("width", $(this.root).width() + "px"); - const w = ($(this.refs.container).width() * this.get("value")) / this.get("max"); - $(this.refs.prg) - .css("width", w + "px") - .css("height", $(this.refs.container).height() + "px"); - if (this.get("dragable")) { - const ow = w - ($(this.refs.point).width() / 2); - const top = Math.floor(($(this.refs.prg).height() - $(this.refs.point).height()) / 2); - return $(this.refs.point) - .css("left", ow + "px") - .css("top", top + "px"); - } - } - - enable_dragging() { - $(this.refs.point) - .css("user-select", "none") - .css("cursor", "default"); - return $(this.refs.point).on("mousedown", e => { - e.preventDefault(); - const offset = $(this.refs.container).offset(); - $(window).on("mousemove", e => { - let left = e.clientX - offset.left; - left = left < 0 ? 0 : left; - const maxw = $(this.refs.container).width(); - left = left > maxw ? maxw : left; - this.set("value", (left * this.get("max")) / maxw); - this.calibrate(); - return this.get("onchanging")({ id: this.aid(), data: this.get("value") }); - }); - - return $(window).on("mouseup", e => { - this.get("onchange")({ id: this.aid(), data: this.get("value") }); - $(window).unbind("mousemove", null); - return $(window).unbind("mouseup", null); - }); - }); - } - - layout() { - return [{ - el: "div", class: "container", ref: "container", children: [ - { el: "div", class: "progress", ref: "prg" }, - { el: "div", class: "dragpoint", ref: "point" } - ] - }]; - } -} - -Ant.OS.GUI.define("afx-slider", SliderTag); \ No newline at end of file diff --git a/src/core/tags/SliderTag.ts b/src/core/tags/SliderTag.ts new file mode 100644 index 0000000..4c2f857 --- /dev/null +++ b/src/core/tags/SliderTag.ts @@ -0,0 +1,258 @@ +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +namespace OS { + export namespace GUI { + export namespace tag { + /** + * + * + * @class SliderTag + * @extends {AFXTag} + */ + class SliderTag extends AFXTag { + private _max: number; + private _value: number; + private _onchange: TagEventCallback; + private _onchanging: TagEventCallback; + + /** + *Creates an instance of SliderTag. + * @memberof SliderTag + */ + constructor() { + super(); + } + + /** + * + * + * @protected + * @memberof SliderTag + */ + protected init(): void { + this.enable = true; + this._max = 100; + this._value = 0; + this._onchange = this._onchanging = () => {}; + } + + /** + * + * + * @protected + * @param {*} [d] + * @memberof SliderTag + */ + protected reload(d?: any): void {} + + /** + * + * + * @memberof SliderTag + */ + set onvaluechange(f: TagEventCallback) { + this._onchange = f; + } + + /** + * + * + * @memberof SliderTag + */ + set onvaluechanging(f: TagEventCallback) { + this._onchanging = f; + } + + /** + * + * + * @memberof SliderTag + */ + set enable(v: boolean) { + this.attsw(v, "enable"); + if (v) { + $(this) + .mouseover(() => { + return $(this.refs.point).show(); + }) + .mouseout(() => { + return $(this.refs.point).hide(); + }); + } else { + $(this.refs.point).hide(); + $(this).unbind("mouseover").unbind("mouseout"); + } + } + + /** + * + * + * @type {boolean} + * @memberof SliderTag + */ + get enable(): boolean { + return this.hasattr("enable"); + } + + /** + * + * + * @memberof SliderTag + */ + set value(v: number) { + this._value = v; + this.calibrate(); + } + + /** + * + * + * @type {number} + * @memberof SliderTag + */ + get value(): number { + return this._value; + } + + /** + * + * + * @memberof SliderTag + */ + set max(v: number) { + this._max = v; + this.calibrate(); + } + + /** + * + * + * @type {number} + * @memberof SliderTag + */ + get max(): number { + return this._max; + } + + /** + * + * + * @protected + * @memberof SliderTag + */ + protected mount(): void { + + this.enable_dragging(); + $(this.refs.point).css("position", "absolute"); + $(this.refs.point).hide(); + this.observable.on("resize", (e) => { + return this.calibrate(); + }); + $(this.refs.container).click((e) => { + const offset = $(this.refs.container).offset(); + const left = e.clientX - offset.left; + const maxw = $(this.refs.container).width(); + this.value = (left * this.max) / maxw; + this.calibrate(); + const evt = { id: this.aid, data: this.value }; + this._onchange(evt); + return this._onchanging(evt); + }); + this.calibrate(); + } + + /** + * + * + * @memberof SliderTag + */ + calibrate(): void { + if (this.value > this.max) { + this.value = this.max; + } + $(this.refs.container).css("width", $(this).width() + "px"); + const w = + ($(this.refs.container).width() * this.value) / + this.max; + $(this.refs.prg) + .css("width", w + "px") + .css("height", $(this.refs.container).height() + "px"); + if (this.enable) { + const ow = w - $(this.refs.point).width() / 2; + const top = Math.floor( + ($(this.refs.prg).height() - + $(this.refs.point).height()) / + 2 + ); + $(this.refs.point) + .css("left", ow + "px") + .css("top", top + "px"); + } + } + + /** + * + * + * @private + * @memberof SliderTag + */ + private enable_dragging(): void { + $(this.refs.point) + .css("user-select", "none") + .css("cursor", "default"); + $(this.refs.point).on("mousedown", (e) => { + e.preventDefault(); + const offset = $(this.refs.container).offset(); + $(window).on("mousemove", (e) => { + let left = e.clientX - offset.left; + left = left < 0 ? 0 : left; + const maxw = $(this.refs.container).width(); + left = left > maxw ? maxw : left; + this.value = (left * this.max) / maxw; + this.calibrate(); + return this._onchanging({ + id: this.aid, + data: this.value, + }); + }); + + $(window).on("mouseup", (e) => { + this._onchange({ + id: this.aid, + data: this.value, + }); + $(window).unbind("mousemove", null); + return $(window).unbind("mouseup", null); + }); + }); + } + + /** + * + * + * @protected + * @returns {TagLayoutType[]} + * @memberof SliderTag + */ + protected layout(): TagLayoutType[] { + return [ + { + el: "div", + class: "container", + ref: "container", + children: [ + { el: "div", class: "progress", ref: "prg" }, + { el: "div", class: "dragpoint", ref: "point" }, + ], + }, + ]; + } + } + + define("afx-slider", SliderTag); + } + } +} diff --git a/src/core/tags/SwitchTag.js b/src/core/tags/SwitchTag.js deleted file mode 100644 index 0d91c6d..0000000 --- a/src/core/tags/SwitchTag.js +++ /dev/null @@ -1,41 +0,0 @@ -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ - -class SwitchTag extends Ant.OS.GUI.BaseTag { - constructor(r, o) { - super(r, o); - this.setopt("swon", false); - this.setopt("enable", true); - this.setopt("onchange", function(e) {}); - } - - mount() { - return $(this.refs.switch).click(e => { - return this.onchange(e); - }); - } - - onchange(e) { - if (!this.get("enable")) { return; } - this.setopt("swon", !this.get("swon")); - const evt = { id: this.aid(), data: this.get("swon") }; - this.get("onchange")(evt); - return this.observable.trigger("switch", evt); - } - - __swon__(v) { - $(this.refs.switch).removeClass(); - if (v) { return $(this.refs.switch).addClass("swon"); } - } - - layout() { - return [{ - el: "span", ref: "switch" - }]; - } -} - -Ant.OS.GUI.define("afx-switch", SwitchTag); \ No newline at end of file diff --git a/src/core/tags/SwitchTag.ts b/src/core/tags/SwitchTag.ts new file mode 100644 index 0000000..e5020e3 --- /dev/null +++ b/src/core/tags/SwitchTag.ts @@ -0,0 +1,77 @@ +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +namespace OS { + export namespace GUI { + export namespace tag { + export class SwitchTag extends AFXTag { + private _onchange: TagEventCallback; + private _onchanging: TagEventCallback; + constructor() { + super(); + + } + + set swon(v: boolean) { + this.attsw(v, "swon"); + $(this.refs.switch).removeClass(); + if (v) { + $(this.refs.switch).addClass("swon"); + } + } + + get swon(): boolean { + return this.hasattr("swon"); + } + + set enable(v: boolean) { + this.attsw(v, "enable"); + } + + get enable(): boolean { + return this.hasattr("enable"); + } + + set onswchange(v: TagEventCallback) { + this._onchange = v; + } + + protected mount(): void { + $(this.refs.switch).click((e) => { + return this.makechange(e); + }); + } + + private makechange(e: JQuery.ClickEvent) { + if (!this.enable) { + return; + } + this.swon = !this.swon; + const evt = { id: this.aid, data: this.swon }; + this._onchange(evt); + return this.observable.trigger("switch", evt); + } + + protected layout() { + return [ + { + el: "span", + ref: "switch", + }, + ]; + } + + protected init(): void { + this.swon = false; + this.enable = true; + } + protected calibrate(): void {} + protected reload(d?: any): void {} + } + + define("afx-switch", SwitchTag); + } + } +} diff --git a/src/core/tags/SystemPanelTag.js b/src/core/tags/SystemPanelTag.js deleted file mode 100644 index d8aaa6f..0000000 --- a/src/core/tags/SystemPanelTag.js +++ /dev/null @@ -1,211 +0,0 @@ -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ -class SystemPanelTag extends Ant.OS.GUI.BaseTag { - constructor(r, o) { - super(r, o); - this.setopt("osmenu", { - text: __("Start"), - iconclass: "fa fa-circle" - }); - this.setopt("appmenu", []); - this.setopt("systray", []); - this.root.attachservice = s => this.attachservice(s); - this.root.detachservice = s => this.detachservice(s); - this.view = false; - } - - __osmenu__(v) { - return this.refs.osmenu.set("items", [v]); - } - - __appmenu__(v) { - return this.refs.appmenu.set("items", v); - } - - __systray__(v) { - return this.refs.systray.set("items", v); - } - - attachservice(s) { - this.refs.systray.unshift(s); - return s.attach(this.refs.systray); - } - - open() { - const el = this.refs.applist.get("selectedItem"); - if (!el) { return; } - const data = el.get("data"); - if (!data || (data.dataid === "header")) { return; } - this.toggle(false); - // launch the app or open the file - Ant.OS.GUI.openWith(data); - return this.refs.applist.unselect(); - } - - search(e) { - switch (e.which) { - case 27: - // escape key - return this.toggle(false); - - case 37: - return e.preventDefault(); - case 38: - this.refs.applist.selectPrev(); - return e.preventDefault(); - case 39: - return e.preventDefault(); - case 40: - this.refs.applist.selectNext(); - return e.preventDefault(); - case 13: - e.preventDefault(); - return this.open(); - default: - var text = this.refs.search.value; - if (!(text.length >= 3)) { return this.refreshAppList(); } - var result = Ant.OS.API.search(text); - if (result.length === 0) { return; } - return this.refs.applist.set("data", result); - } - } - - detachservice(s) { - return this.refs.systray.remove(s.domel); - } - - layout() { - return [ - { - el: "div", ref: "panel", children: [ - { el: "afx-menu", ref: "osmenu", class: "afx-panel-os-menu" }, - { el: "afx-menu", id: "appmenu", ref: "appmenu", class: "afx-panel-os-app" }, - { el: "afx-menu", id: "systray", ref: "systray", class: "afx-panel-os-stray" } - ] - }, - { - el: "afx-overlay", id: "start-panel", ref: "overlay", children: [ - { - el: "afx-hbox", height: 30, children: [ - { el: "div", width: 30, id: "searchicon" }, - { el: "input", ref: "search" } - ] - }, - { el: "afx-list-view", id: "applist", ref: "applist" }, - { - el: "afx-hbox", id: "btlist", height: 30, children: [ - { - el: "afx-button", - ref: "btscreen", - tooltip: __("ct:Toggle fullscreen") - }, - { - el: "afx-button", - ref: "btuser", - tooltip: __("ct:User: {0}", Ant.OS.setting.user.username) - }, - { el: "afx-button", ref: "btlogout", tooltip: __("ct:Logout") } - ] - } - ] - } - ]; - } - - refreshAppList() { - let k, v; - const list = []; - for (k in Ant.OS.setting.system.packages) { v = Ant.OS.setting.system.packages[k]; if (v && v.app) { list.push(v); } } - for (k in Ant.OS.setting.system.menu) { v = Ant.OS.setting.system.menu[k]; list.push(v); } - list.sort(function(a, b) { - if (a.text < b.text) { - return -1; - } else if (a.text > b.text) { - return 1; - } else { - return 0; - } - }); - return this.refs.applist.set("data", list); - } - - toggle(flag) { - this.view = flag; - if (flag) { - this.refreshAppList(); - $(this.refs.overlay).show(); - this.calibrate(); - $(document).on("click", this.cb); - this.refs.search.value = ""; - return $(this.refs.search).focus(); - } else { - $(this.refs.overlay).hide(); - return $(document).unbind("click", this.cb); - } - } - - calibrate() { - return this.refs.overlay.set("height", `${$(window).height() - $(this.refs.panel).height()}px`); - } - - mount() { - this.cb = e => { - if (!($(e.target)).closest($(this.refs.overlay)).length && !($(e.target)).closest(this.refs.osmenu).length) { - return this.toggle(false); - } else { - return $(this.refs.search).focus(); - } - }; - $(this.refs.appmenu).css("z-index", 1000000); - $(this.refs.systray).css("z-index", 1000000); - this.refs.btscreen.set("*", { - iconclass: "fa fa-tv", - onbtclick: e => { - this.toggle(false); - return Ant.OS.GUI.toggleFullscreen(); - } - }); - this.refs.btuser.set("*", { - iconclass: "fa fa-user-circle-o", - onbtclick: e => { - this.toggle(false); - return Ant.OS.GUI.openDialog("InfoDialog", Ant.OS.setting.user); - } - }); - this.refs.btlogout.set("*", { - iconclass: "fa fa-power-off", - onbtclick: e => { - this.toggle(false); - return Ant.OS.exit(); - } - }); - this.refs.osmenu.set("onmenuselect", e => { - return this.toggle(true); - }); - - ($(this.refs.overlay)).css("left", 0) - .css("top", `${$(this.refs.panel).height()}px`) - .css("bottom", "0") - .hide(); - ($(this.refs.search)).keyup(e => { - return this.search(e); - }); - - $(this.refs.applist).click(e => { - return this.open(); - }); - Ant.OS.GUI.bindKey("CTRL- ", e => { - if (this.view === false) { - return this.toggle(true); - } else { - return this.toggle(false); - } - }); - return Ant.OS.announcer.trigger("syspanelloaded"); - } -} - -Ant.OS.GUI.define("afx-sys-panel", SystemPanelTag); \ No newline at end of file diff --git a/src/core/tags/SystemPanelTag.ts b/src/core/tags/SystemPanelTag.ts new file mode 100644 index 0000000..6efdae9 --- /dev/null +++ b/src/core/tags/SystemPanelTag.ts @@ -0,0 +1,363 @@ +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +namespace OS { + export namespace GUI { + export namespace tag { + + /** + * + * + * @export + * @class SystemPanelTag + * @extends {AFXTag} + */ + export class SystemPanelTag extends AFXTag { + private _osmenu: GenericObject; + private _view: boolean; + private _cb: (e: JQuery.MouseEventBase) => void; + + /** + *Creates an instance of SystemPanelTag. + * @memberof SystemPanelTag + */ + constructor() { + super(); + this._osmenu = { + text: __("Start"), + iconclass: "fa fa-circle", + }; + this._view = false; + } + + /** + * + * + * @protected + * @memberof SystemPanelTag + */ + protected init(): void {} + + /** + * + * + * @protected + * @param {*} [d] + * @memberof SystemPanelTag + */ + protected reload(d?: any): void {} + + /** + * + * + * @param {BaseService} s + * @returns + * @memberof SystemPanelTag + */ + attachservice(s: application.BaseService) { + (this.refs.systray as MenuTag).unshift(s); + return s.attach(this.refs.systray); + } + + /** + * + * + * @private + * @returns {void} + * @memberof SystemPanelTag + */ + private open(): void { + const applist = this.refs.applist as ListViewTag; + const el = applist.selectedItem; + if (!el) { + return; + } + if (!el.data || el.data.dataid === "header") { + return; + } + this.toggle(false); + // launch the app or open the file + Ant.OS.GUI.openWith(el.data as AppArgumentsType); + applist.unselect(); + } + + /** + * + * + * @private + * @param {JQuery.KeyboardEventBase} e + * @returns {void} + * @memberof SystemPanelTag + */ + private search(e: JQuery.KeyboardEventBase): void { + const applist = this.refs.applist as ListViewTag; + switch (e.which) { + case 27: + // escape key + return this.toggle(false); + + case 37: + return e.preventDefault(); + case 38: + applist.selectPrev(); + return e.preventDefault(); + case 39: + return e.preventDefault(); + case 40: + applist.selectNext(); + return e.preventDefault(); + case 13: + e.preventDefault(); + return this.open(); + default: + var text = (this.refs.search as HTMLInputElement) + .value; + if (!(text.length >= 3)) { + return this.refreshAppList(); + } + var result = Ant.OS.API.search(text); + if (result.length === 0) { + return; + } + applist.data = result; + } + } + + /** + * + * + * @param {BaseService} s + * @memberof SystemPanelTag + */ + detachservice(s: application.BaseService): void { + (this.refs.systray as MenuTag).delete( + s.domel as MenuEntryTag + ); + } + + /** + * + * + * @protected + * @returns {TagLayoutType[]} + * @memberof SystemPanelTag + */ + protected layout(): TagLayoutType[] { + return [ + { + el: "div", + ref: "panel", + children: [ + { + el: "afx-menu", + ref: "osmenu", + class: "afx-panel-os-menu", + }, + { + el: "afx-menu", + id: "appmenu", + ref: "appmenu", + class: "afx-panel-os-app", + }, + { + el: "afx-menu", + id: "systray", + ref: "systray", + class: "afx-panel-os-stray", + }, + ], + }, + { + el: "afx-overlay", + id: "start-panel", + ref: "overlay", + children: [ + { + el: "afx-hbox", + height: 30, + children: [ + { + el: "div", + width: 30, + id: "searchicon", + }, + { el: "input", ref: "search" }, + ], + }, + { + el: "afx-list-view", + id: "applist", + ref: "applist", + }, + { + el: "afx-hbox", + id: "btlist", + height: 30, + children: [ + { + el: "afx-button", + ref: "btscreen", + tooltip: __("ct:Toggle fullscreen"), + }, + { + el: "afx-button", + ref: "btuser", + tooltip: __( + "ct:User: {0}", + Ant.OS.setting.user.username + ), + }, + { + el: "afx-button", + ref: "btlogout", + tooltip: __("ct:Logout"), + }, + ], + }, + ], + }, + ]; + } + + /** + * + * + * @private + * @memberof SystemPanelTag + */ + private refreshAppList(): void { + let k: string, v: API.PackageMetaType; + const list = []; + for (k in Ant.OS.setting.system.packages) { + v = Ant.OS.setting.system.packages[k]; + if (v && v.app) { + list.push(v); + } + } + for (k in Ant.OS.setting.system.menu) { + v = Ant.OS.setting.system.menu[k]; + list.push(v); + } + list.sort(function (a, b) { + if (a.text < b.text) { + return -1; + } else if (a.text > b.text) { + return 1; + } else { + return 0; + } + }); + (this.refs.applist as ListViewTag).data = list; + } + + /** + * + * + * @private + * @param {boolean} flag + * @memberof SystemPanelTag + */ + private toggle(flag: boolean): void { + this._view = flag; + if (flag) { + $(this.refs.overlay).show(); + this.refreshAppList(); + + this.calibrate(); + $(document).on("click", this._cb); + (this.refs.search as HTMLInputElement).value = ""; + $(this.refs.search).focus(); + } else { + $(this.refs.overlay).hide(); + $(document).unbind("click", this._cb); + } + } + + /** + * + * + * @memberof SystemPanelTag + */ + calibrate(): void { + (this.refs.overlay as OverlayTag).height = `${ + $(window).height() - $(this.refs.panel).height() + }px`; + } + + /** + * + * + * @protected + * @memberof SystemPanelTag + */ + protected mount(): void { + (this.refs.osmenu as MenuTag).items = [this._osmenu]; + this._cb = (e) => { + if ( + !$(e.target).closest($(this.refs.overlay)).length && + !$(e.target).closest(this.refs.osmenu).length + ) { + return this.toggle(false); + } else { + return $(this.refs.search).focus(); + } + }; + $(this.refs.appmenu).css("z-index", 1000000); + $(this.refs.systray).css("z-index", 1000000); + (this.refs.btscreen as ButtonTag).set({ + iconclass: "fa fa-tv", + onbtclick: (e) => { + this.toggle(false); + return Ant.OS.GUI.toggleFullscreen(); + }, + }); + (this.refs.btuser as ButtonTag).set({ + iconclass: "fa fa-user-circle-o", + onbtclick: (e) => { + this.toggle(false); + return Ant.OS.GUI.openDialog( + "InfoDialog", + Ant.OS.setting.user + ); + }, + }); + (this.refs.btlogout as ButtonTag).set({ + iconclass: "fa fa-power-off", + onbtclick: (e) => { + this.toggle(false); + return Ant.OS.exit(); + }, + }); + (this.refs.osmenu as MenuTag).onmenuselect = (e) => { + return this.toggle(true); + }; + + $(this.refs.search).keyup((e) => { + return this.search(e); + }); + + $(this.refs.applist).click((e) => { + return this.open(); + }); + Ant.OS.GUI.bindKey("CTRL- ", (e) => { + if (this._view === false) { + return this.toggle(true); + } else { + return this.toggle(false); + } + }); + Ant.OS.announcer.trigger("syspanelloaded", undefined); + $(this.refs.overlay) + .css("left", 0) + .css("top", `${$(this.refs.panel).height()}px`) + .css("bottom", "0") + .hide(); + } + } + + define("afx-sys-panel", SystemPanelTag); + } + } +} diff --git a/src/core/tags/TabBarTag.js b/src/core/tags/TabBarTag.js deleted file mode 100644 index 6336d56..0000000 --- a/src/core/tags/TabBarTag.js +++ /dev/null @@ -1,51 +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 - */ -class TabBarTag extends Ant.OS.GUI.BaseTag { - constructor(r, o) { - super(r, o); - this.setopt("closable", false); - this.setopt("ontabselect", function(e) {}); - this.setopt("ontabclose", function(e) {}); - this.setopt("items", []); - this.setopt("selected", -1); - this.root.push = e => { - e.closable = this.get("closable"); - return this.refs.list.push(e); - }; - this.root.remove = e => this.refs.list.remove(e); - this.root.unshift = e => this.refs.list.unshift(e); - this.refs.list.set("onlistselect", e => { - this.get("ontabselect")(e); - return this.observable.trigger("tabselect", e); - }); - } - - __items__(v) { - for (let i of Array.from(v)) { i.closable = this.get("closable"); } - return this.refs.list.set("data", v); - } - - __selected__(v) { - return this.refs.list.set("selected", v); - } - - mount() { - $(this.refs.list).css("height", "100%"); - return this.refs.list.set("onitemclose", e => { - e.id = this.aid(); - return this.get("ontabclose")(e); - }); - } - - layout() { - return [{ - el: "afx-list-view", ref: "list" - }]; - } -} - -Ant.OS.GUI.define("afx-tab-bar", TabBarTag); \ No newline at end of file diff --git a/src/core/tags/TabBarTag.ts b/src/core/tags/TabBarTag.ts new file mode 100644 index 0000000..7c9f5a9 --- /dev/null +++ b/src/core/tags/TabBarTag.ts @@ -0,0 +1,203 @@ +/* + * 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 { + export namespace GUI { + export namespace tag { + + /** + * + * + * @export + * @class TabBarTag + * @extends {AFXTag} + */ + export class TabBarTag extends AFXTag { + private _items: GenericObject[]; + private _selected: number; + private _ontabclose: (e: TagEventType) => boolean; + private _ontabselect: TagEventCallback; + + /** + *Creates an instance of TabBarTag. + * @memberof TabBarTag + */ + constructor() { + super(); + this._ontabclose = (e) => true; + this._ontabselect = (e) => {}; + this._items = []; + } + + /** + * + * + * @protected + * @memberof TabBarTag + */ + protected init(): void { + this.selected = -1; + } + + /** + * + * + * @protected + * @param {*} [d] + * @memberof TabBarTag + */ + protected reload(d?: any): void {} + + /** + * + * + * @memberof TabBarTag + */ + set closable(v: boolean) { + this.attsw(v, "closable"); + } + + /** + * + * + * @type {boolean} + * @memberof TabBarTag + */ + get closable(): boolean { + return this.hasattr("closable"); + } + + /** + * + * + * @param {GenericObject} item + * @memberof TabBarTag + */ + push(item: GenericObject): ListViewItemTag { + item.closable = this.closable; + return (this.refs.list as ListViewTag).push(item); + } + + /** + * + * + * @param {ListViewItemTag} el + * @memberof TabBarTag + */ + delete(el: ListViewItemTag) { + (this.refs.list as ListViewTag).delete(el); + } + + /** + * + * + * @param {GenericObject} item + * @memberof TabBarTag + */ + unshift(item: GenericObject): ListViewItemTag { + item.closable = this.closable; + return (this.refs.list as ListViewTag).unshift(item); + } + + /** + * + * + * @memberof TabBarTag + */ + set items(v: GenericObject[]) { + this._items = v; + for (let i of v) { + i.closable = this.closable; + } + (this.refs.list as ListViewTag).data = v; + } + + /** + * + * + * @type {GenericObject[]} + * @memberof TabBarTag + */ + get items(): GenericObject[] { + return this._items; + } + + /** + * + * + * @memberof TabBarTag + */ + set selected(v: number | number[]) { + (this.refs.list as ListViewTag).selected = v; + } + + /** + * + * + * @type {(number | number[])} + * @memberof TabBarTag + */ + get selected(): number | number[] { + return (this.refs.list as ListViewTag).selected; + } + + /** + * + * + * @memberof TabBarTag + */ + set ontabclose(v: (e: TagEventType) => boolean) { + this._ontabclose = v; + } + + /** + * + * + * @memberof TabBarTag + */ + set ontabselect(v: TagEventCallback) { + this._ontabselect = v; + } + + /** + * + * + * @protected + * @memberof TabBarTag + */ + protected mount(): void { + $(this.refs.list).css("height", "100%"); + (this.refs.list as ListViewTag).onitemclose = (e) => { + e.id = this.aid; + return this._ontabclose(e); + }; + (this.refs.list as ListViewTag).onlistselect = (e) => { + this._ontabselect(e); + return this.observable.trigger("tabselect", e); + }; + } + + /** + * + * + * @protected + * @returns {TagLayoutType[]} + * @memberof TabBarTag + */ + protected layout(): TagLayoutType[] { + return [ + { + el: "afx-list-view", + ref: "list", + }, + ]; + } + } + + define("afx-tab-bar", TabBarTag); + } + } +} diff --git a/src/core/tags/TabContainerTag.js b/src/core/tags/TabContainerTag.js deleted file mode 100644 index 38b9f8a..0000000 --- a/src/core/tags/TabContainerTag.js +++ /dev/null @@ -1,77 +0,0 @@ -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ -class TabContainerTag extends Ant.OS.GUI.BaseTag { - constructor(r, o) { - super(r, o); - this.setopt("dir", "column"); // or row - this.setopt("selectedTab", undefined); - this.setopt("tabbarwidth", undefined); - this.setopt("tabbarheight", undefined); - this.setopt("ontabselect", function() {}); - this.refs.bar.set("ontabselect", e => { - const data = e.data.item.get("data"); - this.set("selectedTab", data); - return this.get("ontabselect")({ data, id: this.aid() }); - }); - } - - __selectedTab(v) { - if (!v) { return; } - const selected = this.get("selectedTab"); - if (selected) { $(selected.container).hide(); } - $(v.container).show(); - return this.observable.trigger("resize"); - } - - __tabbarwidth__(v) { - if (!v) { return; } - $(this.refs.bar).attr("data-width", `${this.get("tabbarwidth")}`); - return this.refs.wrapper.calibrate(); - } - - __tabbarheight__(v) { - $(this.refs.bar).attr("data-height", `${this.get("tabbarheight")}`); - return this.refs.wrapper.calibrate(); - } - - __dir__(v) { - if (!v) { return; } - this.refs.wrapper.set("dir", v); - return this.set("tabsize", this.get("tabsize")); - } - - mount() { - $(this.children).each((i, e) => { - const item = {}; - if ($(e).attr("tabname")) { item.text = $(e).attr("tabname"); } - if ($(e).attr("icon")) { item.icon = $(e).attr("icon"); } - if ($(e).attr("iconclass")) { item.iconclass = $(e).attr("iconclass"); } - item.container = e; - $(e) - .css("width", "100%") - .css("height", "100%"); - const el = this.refs.bar.push(item); - return el.set("selected", true); - }); - this.observable.on("resize", e => this.calibrate()); - return this.calibrate(); - } - - calibrate() { - return $(this.refs.wrapper).css("height", `${$(this.root).height()}px`); - } - - layout() { - return [{ - el: "afx-tile", ref: "wrapper", children: [ - { el: "afx-tab-bar", ref: "bar" }, - { el: "div", ref: "yield" } - ] - }]; - } -} - -Ant.OS.GUI.define("afx-tab-container", TabContainerTag); \ No newline at end of file diff --git a/src/core/tags/TabContainerTag.ts b/src/core/tags/TabContainerTag.ts new file mode 100644 index 0000000..2cb648b --- /dev/null +++ b/src/core/tags/TabContainerTag.ts @@ -0,0 +1,201 @@ +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +namespace OS { + export namespace GUI { + export interface TabContainerTabType { + container: HTMLElement; + [propName: string]: any; + } + export namespace tag { + /** + * + * + * @export + * @class TabContainerTag + * @extends {AFXTag} + */ + export class TabContainerTag extends AFXTag { + private _selectedTab: TabContainerTabType; + private _ontabselect: TagEventCallback; + + /** + *Creates an instance of TabContainerTag. + * @memberof TabContainerTag + */ + constructor() { + super(); + this.dir = "column"; // or row + this._ontabselect = (e) => {}; + + } + + /** + * + * + * @protected + * @memberof TabContainerTag + */ + protected init(): void {} + + /** + * + * + * @protected + * @param {*} [d] + * @memberof TabContainerTag + */ + protected reload(d?: any): void {} + + /** + * + * + * @memberof TabContainerTag + */ + set ontabselect(f: TagEventCallback) { + this._ontabselect = f; + } + + /** + * + * + * @memberof TabContainerTag + */ + set dir(v: "row" | "column") { + $(this).attr("dir", v); + if (!v) { + return; + } + (this.refs.wrapper as TileLayoutTag).dir = v; + } + + /** + * + * + * @type {("row"| "column")} + * @memberof TabContainerTag + */ + get dir(): "row" | "column" { + return $(this).attr("dir") as any; + } + + /** + * + * + * @memberof TabContainerTag + */ + set selectedTab(v: TabContainerTabType) { + if (!v) { + return; + } + const selected = this._selectedTab; + this._selectedTab = v; + if (selected) { + $(selected.container).hide(); + } + $(v.container).show(); + this.observable.trigger("resize", undefined); + } + + /** + * + * + * @type {TabContainerTabType} + * @memberof TabContainerTag + */ + get selectedTab(): TabContainerTabType { + return this._selectedTab; + } + + /** + * + * + * @memberof TabContainerTag + */ + set tabbarwidth(v: number) { + if (!v) { + return; + } + $(this.refs.bar).attr("data-width", `${v}`); + (this.refs.wrapper as TileLayoutTag).calibrate(); + } + + /** + * + * + * @memberof TabContainerTag + */ + set tabbarheigh(v: number) { + $(this.refs.bar).attr("data-height", `${v}`); + (this.refs.wrapper as TileLayoutTag).calibrate(); + } + + /** + * + * + * @protected + * @memberof TabContainerTag + */ + protected mount(): void { + (this.refs.bar as TabBarTag).ontabselect = (e) => { + const data = (e.data.item as ListViewItemTag) + .data as TabContainerTabType; + this.selectedTab = data; + return this._ontabselect({ data: data, id: this.aid }); + }; + $(this.children).each((i, e) => { + const item = {} as GenericObject; + if ($(e).attr("tabname")) { + item.text = $(e).attr("tabname"); + } + if ($(e).attr("icon")) { + item.icon = $(e).attr("icon"); + } + if ($(e).attr("iconclass")) { + item.iconclass = $(e).attr("iconclass"); + } + item.container = e; + $(e).css("width", "100%").css("height", "100%"); + const el = (this.refs.bar as TabBarTag).push(item); + el.selected = true; + }); + this.observable.on("resize", (e) => this.calibrate()); + this.calibrate(); + } + + /** + * + * + * @memberof TabContainerTag + */ + calibrate(): void { + $(this.refs.wrapper).css("height", `${$(this).height()}px`); + } + + /** + * + * + * @protected + * @returns {TagLayoutType[]} + * @memberof TabContainerTag + */ + protected layout(): TagLayoutType[] { + return [ + { + el: "afx-tile", + ref: "wrapper", + children: [ + { el: "afx-tab-bar", ref: "bar" }, + { el: "div", ref: "yield" }, + ], + }, + ]; + } + } + + define("afx-tab-container", TabContainerTag); + } + } +} diff --git a/src/core/tags/TileLayoutTags.js b/src/core/tags/TileLayoutTags.js deleted file mode 100644 index b0c0e1b..0000000 --- a/src/core/tags/TileLayoutTags.js +++ /dev/null @@ -1,125 +0,0 @@ -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ -class TileLayoutTag extends Ant.OS.GUI.BaseTag { - constructor(r, o) { - super(r, o); - this.setopt("name", undefined); - this.setopt("dir", undefined); - $(this.root).css("display", "block"); - $(this.refs.yield) - .css("display", "flex") - .css("width", "100%"); - } - // @setopt @conf.opt, "grow" - - __name__(v) { - if (!v) { return; } - $(this.refs.yield) - .removeClass() - .addClass(`afx-${v}-container`); - return this.calibrate(); - } - - __dir__(v) { - if (!v) { return; } - $(this.refs.yield) - .css("flex-direction", v); - return this.calibrate(); - } - - mount() { - this.observable.on("resize", e => this.calibrate()); - return this.calibrate(); - } - - calibrate() { - if (this.get("dir") === "row") { return this.hcalibrate(); } - if (this.get("dir") === "column") { return this.vcalibrate(); } - } - - hcalibrate() { - const auto_width = []; - let ocwidth = 0; - const avaiheight = $(this.root).height(); - const avaiWidth = $(this.root).width(); - $(this.refs.yield).css("height", `${avaiheight}px`); - $(this.refs.yield) - .children() - .each(function(e) { - let dw = $(this).attr("data-width"); - if (dw && (dw !== "grow")) { - if (dw[dw.length - 1] === "%") { dw = (Number(dw.slice(0, -1)) * avaiWidth) / 100; } - $(this).css("width", `${dw}px`); - return ocwidth += Number(dw); - } else { - $(this).css("flex-grow", "1"); - return auto_width.push(this); - } - }); - - const csize = (avaiWidth - ocwidth) / auto_width.length; - if (csize > 0) { - $.each(auto_width, (i, v) => $(v).css("width", `${csize}px`)); - } - return this.observable.trigger("hboxchange", { id: this.aid(), data: { w: avaiWidth, h: avaiheight } }); - } - - vcalibrate() { - const auto_height = []; - let ocheight = 0; - const avaiheight = $(this.root).height(); - const avaiwidth = $(this.root).width(); - $(this.refs.yield).css("height", `${avaiheight}px`); - $(this.refs.yield) - .children() - .each(function(e) { - let dh = $(this).attr("data-height"); - if (dh && (dh !== "grow")) { - if (dh[dh.length - 1] === "%") { dh = (Number(dh.slice(0, -1)) * avaiheight) / 100; } - $(this).css("height", `${dh}px`); - return ocheight += Number(dh); - } else { - $(this).css("flex-grow", "1"); - return auto_height.push(this); - } - }); - - const csize = (avaiheight - ocheight) / auto_height.length; - if (csize > 0) { - $.each(auto_height, (i, v) => $(v).css("height", `${csize}px`)); - } - - return this.observable.trigger("vboxchange", { id: this.aid(), data: { w: avaiwidth, h: avaiheight } }); - } - - layout() { - return [{ - el: "div", ref: "yield" - }]; - } -} - - -class HBoxTag extends TileLayoutTag { - constructor(r, o) { - super(r, o); - this.set("dir", "row"); - this.set("name", "hbox"); - } -} - -class VBoxTag extends TileLayoutTag { - constructor(r, o) { - super(r, o); - this.set("dir", "column"); - this.set("name", "vbox"); - } -} - - -Ant.OS.GUI.define("afx-tile", TileLayoutTag); -Ant.OS.GUI.define("afx-hbox", HBoxTag); -Ant.OS.GUI.define("afx-vbox", VBoxTag); \ No newline at end of file diff --git a/src/core/tags/TileLayoutTags.ts b/src/core/tags/TileLayoutTags.ts new file mode 100644 index 0000000..8a1e6f3 --- /dev/null +++ b/src/core/tags/TileLayoutTags.ts @@ -0,0 +1,183 @@ +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +namespace OS { + export namespace GUI { + export namespace tag { + export class TileLayoutTag extends AFXTag { + constructor() { + super(); + } + // @setopt @conf.opt, "grow" + protected init(): void { + } + protected reload(d?: any): void {} + set name(v: string) { + if (!v) { + return; + } + $(this).attr("name", v); + $(this.refs.yield) + .removeClass() + .addClass(`afx-${v}-container`); + this.calibrate(); + } + + get name(): string { + return $(this).attr("name"); + } + set dir(v: "row"| "column") { + if (!v) { + return; + } + $(this).attr("dir", v); + $(this.refs.yield).css("flex-direction", v); + this.calibrate(); + } + get dir(): "row"| "column" + { + return $(this).attr("dir") as any; + } + protected mount(): void { + $(this).css("display", "block"); + $(this.refs.yield) + .css("display", "flex") + .css("width", "100%"); + this.observable.on("resize", (e) => this.calibrate()); + return this.calibrate(); + } + + calibrate(): void { + if (this.dir === "row") { + return this.hcalibrate(); + } + if (this.dir === "column") { + return this.vcalibrate(); + } + } + + private hcalibrate(): void { + const auto_width = []; + let ocwidth = 0; + const avaiheight = $(this).height(); + const avaiWidth = $(this).width(); + $(this.refs.yield).css("height", `${avaiheight}px`); + $(this.refs.yield) + .children() + .each(function (e) { + let attv = $(this).attr("data-width"); + let dw = 0; + if (attv && attv !== "grow") { + if (attv[attv.length - 1] === "%") { + dw = + (parseInt(attv.slice(0, -1)) * + avaiWidth) / + 100; + } else { + dw = parseInt(attv); + } + $(this).css("width", `${dw}px`); + ocwidth += dw; + } else { + $(this).css("flex-grow", "1"); + auto_width.push(this); + } + }); + + const csize = (avaiWidth - ocwidth) / auto_width.length; + if (csize > 0) { + $.each(auto_width, (i, v) => + $(v).css("width", `${csize}px`) + ); + } + return this.observable.trigger("hboxchange", { + id: this.aid, + data: { w: avaiWidth, h: avaiheight }, + }); + } + + private vcalibrate(): void { + const auto_height = []; + let ocheight = 0; + const avaiheight = $(this).height(); + const avaiwidth = $(this).width(); + $(this.refs.yield).css("height", `${avaiheight}px`); + $(this.refs.yield) + .children() + .each(function (e) { + let dh = 0; + let attv = $(this).attr("data-height"); + if (attv && attv !== "grow") { + if (attv[attv.length - 1] === "%") { + dh = + (parseInt(attv.slice(0, -1)) * + avaiheight) / + 100; + } else { + dh = parseInt(attv); + } + $(this).css("height", `${dh}px`); + ocheight += dh; + } else { + $(this).css("flex-grow", "1"); + auto_height.push(this); + } + }); + + const csize = (avaiheight - ocheight) / auto_height.length; + if (csize > 0) { + $.each(auto_height, (i, v) => + $(v).css("height", `${csize}px`) + ); + } + + return this.observable.trigger("vboxchange", { + id: this.aid, + data: { w: avaiwidth, h: avaiheight }, + }); + } + + layout() { + return [ + { + el: "div", + ref: "yield", + }, + ]; + } + } + + class HBoxTag extends TileLayoutTag { + constructor() { + super(); + } + + protected mount(): void { + super.mount(); + this.dir = "row"; + this.name = "hbox"; + + } + } + + class VBoxTag extends TileLayoutTag { + constructor() { + super(); + + } + protected mount(): void { + super.mount(); + this.dir = "column"; + this.name = "vbox"; + + } + } + + define("afx-tile", TileLayoutTag); + define("afx-hbox", HBoxTag); + define("afx-vbox", VBoxTag); + } + } +} diff --git a/src/core/tags/TreeViewTag.js b/src/core/tags/TreeViewTag.js deleted file mode 100644 index 79cbf6e..0000000 --- a/src/core/tags/TreeViewTag.js +++ /dev/null @@ -1,337 +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 TreeViewItemPrototype extends Ant.OS.GUI.BaseTag { - constructor(r, o) { - super(r, o); - this.setopt("data", undefined); - this.setopt("nodes", undefined); - this.setopt("treeroot", undefined); - this.setopt("indent", 0); - this.setopt("toggle", false); - this.setopt("fetch", undefined); - this.setopt("open", true); - this.setopt("itemindex", 0); - this.setopt("parent", undefined); - this.setopt("selected", false); - this.setopt("treepath", this.aid()); - } - - update(p) { - if (!p) { return; } - switch (p) { - case "expand": - return this.set("open", true); - case "collapse": - return this.set("open", false); - default: - if (p !== this.get("treepath")) { return; } - return this.set("open", true); - } - } - - __data__(v) { - if (!v) { return; } - if (v.nodes) { this.set("nodes", v.nodes); } - this.set("open", v.open); - if (v.path) { this.set("treepath", v.path); } - return this.set("selected", v.selected); - } - - __selected__(v) { - if (!this.opts.data) { return; } - $(this.refs.wrapper).removeClass(); - this.opts.data.selected = v; - if (v) { - this.get("treeroot").unselect(); - // set selectedItem but not trigger the update - this.get("treeroot").set("selectedItem", this.root, true); - return $(this.refs.wrapper).addClass("afx_tree_item_selected"); - } - } - - __open__(v) { - if (!this.is_folder()) { return; } - $(this.refs.toggle) - .removeClass(); - if(v) { - if (this.get("fetch")) { - this.get("fetch")(this.root) - .then(d => { - if (!d) { return; } - return this.set("nodes", d); - }).catch(e => Ant.OS.announcer.oserror(e.toString(), e)); - } else { - this.set("nodes", this.__("nodes")); - } - $(this.refs.childnodes).show(); - } else { - $(this.refs.childnodes).hide(); - } - if (v) { return $(this.refs.toggle).addClass("afx-tree-view-folder-open"); } - return $(this.refs.toggle).addClass("afx-tree-view-folder-close"); - } - - __itemindex__(v) { - if (!v) { return; } - if ((v % 2) !== 0) { return $(this.refs.wrapper).addClass("afx_tree_item_odd"); } - } - - __indent__(v) { - if (!v) { return; } - return $(this.refs.padding) - .css("display", "inline-block") - .css("height", "1px") - .css("padding", 0) - .css("margin", 0) - .css("background-color", "transparent") - .css("width", (v * 15) + "px" ); - } - - is_folder() { - if (this.get("nodes")) { return true; } else { return false; } - } - - - __nodes__(nodes) { - if (!nodes) { return; } - // return unless @get("nodes") and @get("nodes").length > 0 - $(this.refs.childnodes).empty(); - $(this.refs.wrapper).addClass("afx_folder_item"); - const root = this.get("treeroot"); - return (() => { - const result = []; - for (let v of Array.from(nodes)) { - const el = $("").appendTo(this.refs.childnodes); - el[0].uify(undefined); - el[0].set("treeroot", root); - el[0].set("indent", (this.get("indent") + 1)); - root.indexcounter++; - el[0].set("parent", this.get("parent")); - el[0].set("itemindex", root.indexcounter); - el[0].set("treepath", `${this.get("treepath")}/${el[0].aid()}`); - el[0].set("fetch", this.get("fetch")); - result.push(el[0].set("data", v)); - } - return result; - })(); - } - - - mount() { - super.mount(); - $(this.refs.container) - .css("padding", 0) - .css("margin", 0) - .css("white-space", "nowrap"); - $(this.refs.itemholder) - .css("display", "inline-block"); - $(this.refs.wrapper) - .click(e => { - e.item = this.root; - return this.get("treeroot").itemclick(e, false); - }); - $(this.refs.wrapper) - .dblclick(e => { - e.item = this.root; - return this.get("treeroot").itemclick(e, true); - }); - - return $(this.refs.toggle) - .css("display", "inline-block") - .css("width", "15px") - .addClass("afx-tree-view-item") - .click(e => { - this.set("open", !this.get("open")); - e.preventDefault(); - return e.stopPropagation(); - }); - } - - - layout() { - return [ { - el: "div", ref: "wrapper", children: [ - { - el: "ul", ref: "container", children: [ - { el: "li", ref: "padding" }, - { el: "li", ref: "toggle" }, - { el: "li", ref: "itemholder", class: "itemname", children: this.itemlayout() } - ] - } - ] }, - { - el: "ul", ref: "childnodes" - } - ]; - } - - itemlayout() {} -} - -class SimpleTreeViewItem extends TreeViewItemPrototype { - constructor(r, o) { - super(r, o); - } - - - __data__(v) { - if (!v) { return; } - super.__data__(v); - if (v.color) { this.refs.label.set("color", v.color); } - if (v.name) { this.refs.label.set("text", v.name); } - if (v.icon) { this.refs.label.set("icon", v.icon); } - if (v.iconclass) { return this.refs.label.set("iconclass", v.iconclass); } - } - - itemlayout() { - return [{ el: "afx-label", ref: "label" }]; - } -} - - -class TreeViewTag extends Ant.OS.GUI.BaseTag { - constructor(r, o) { - super(r, o); - this.setopt("itemtag", "afx-tree-view-item"); - this.setopt("data", undefined); - this.setopt("treeroot", undefined); - this.setopt("parent", undefined); - this.setopt("indent", 0); - this.setopt("open", true); - this.setopt("itemindex", 0); - this.setopt("ontreeselect", function() {}); - this.setopt("ontreedbclick", function() {}); - this.setopt("ondragndrop", function() {}); - this.setopt("selectedItem", undefined); - this.setopt("fetch", undefined); - this.setopt("dragndrop", false); - this.setopt("treepath", this.aid()); - this.root.is_leaf = () => this.is_leaf(); - this.root.expandAll = () => this.expandAll(); - this.root.collapseAll = () => this.collapseAll(); - this.root.unselect = () => this.unselect(); - this.indexcounter = 0; - } - - - unselect() { - if (this.get("selectedItem")) { return this.get("selectedItem").set("selected", false); } - } - - __selectedItem(v) { - if (!v) { return; } - if (v === this.get("selectedItem")) { return; } - return v.set("selected", true); - } - - expandAll() { - if (this.is_leaf()) { return; } - return this.root.update("expand"); - } - - collapseAll() { - if (this.is_leaf()) { return; } - return this.root.update("collapse"); - } - - itemclick(e, flag) { - if (!e || !e.item) { return; } - if ((e.item === this.get("selectedItem")) && !flag) { return; } - this.set("selectedItem", e.item); - const evt = { id: this.aid(), data: e }; - if (flag) { - this.get("ontreedbclick")(evt); - return this.observable.trigger("treedbclick", evt); - } else { - this.get("ontreeselect")(evt); - return this.observable.trigger("treeselect", evt); - } - } - - is_root() { - return this.get("treeroot") === undefined; - } - - is_leaf() { - const data = this.get("data"); - if (!data) { return true; } - if (data.nodes) { return false; } else { return true; } - } - - __data__(v) { - if (!v) { return; } - $(this.root).empty(); - if (v.path) { this.set("treepath", v.path); } - let tag = this.get("itemtag"); - if (v.tag) { ({ - tag - } = v); } - const el = $(`<${tag}>`).appendTo(this.root); - el[0].uify(undefined); - el[0].set("treeroot", this.is_root() ? this : this.get("treeroot")); - el[0].set("indent", this.get("indent")); - el[0].set("itemindex", this.get("itemindex")); - el[0].set("treepath", this.get("treepath")); - el[0].set("open", this.get("open")); - el[0].set("fetch", this.get("fetch")); - el[0].set("parent", this.root); - el[0].set("data", v); - if (this.is_root()) { - $(this.root).off("mousedown", this.treemousedown); - if (this.get("dragndrop")) { return $(this.root).on("mousedown", this.treemousedown); } - } - } - - mount() { - this.dnd = {}; - this.treemousedown = e => { - let el = $(e.target).closest("afx-tree-view"); - if (el.length === 0) { return; } - el = el[0]; - if (el === this.root) { return; } - this.dnd.from = el; - this.dnd.to = undefined; - $(window).on("mouseup", this.treemouseup); - return $(window).on("mousemove", this.treemousemove); - }; - - this.treemouseup = e => { - $(window).off("mouseup", this.treemouseup); - $(window).off("mousemove", this.treemousemove); - ($("#systooltip")).hide(); - let el = $(e.target).closest("afx-tree-view"); - if (el.length === 0) { return; } - el = el[0]; - if (el.is_leaf()) { el = el.get("parent"); } - if ((el === this.dnd.from) || (el === this.dnd.from.get("parent"))) { return; } - this.dnd.to = el; - this.__("ondragndrop")({ id: this.aid(), data: this.dnd }); - return this.dnd = {}; - }; - - return this.treemousemove = e => { - if (!e) { return; } - if (!this.dnd.from) { return; } - const data = this.dnd.from.get("data"); - const $label = $("#systooltip"); - const top = e.clientY + 5; - const left = e.clientX + 5; - $label.show(); - $label[0].set("text", data.name); - if (data.icon) { $label[0].set("icon", data.icon); } - if (data.iconclass) { $label[0].set("iconclass", data.iconclass); } - return $label - .css("top", top + "px") - .css("left", left + "px"); - }; - } -} - -Ant.OS.GUI.define("afx-tree-view", TreeViewTag); -Ant.OS.GUI.define("afx-tree-view-item-proto", TreeViewItemPrototype); -Ant.OS.GUI.define("afx-tree-view-item", SimpleTreeViewItem); \ No newline at end of file diff --git a/src/core/tags/TreeViewTag.ts b/src/core/tags/TreeViewTag.ts new file mode 100644 index 0000000..7b523ee --- /dev/null +++ b/src/core/tags/TreeViewTag.ts @@ -0,0 +1,777 @@ +/* + * 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 + */ +namespace OS { + export namespace GUI { + export namespace tag { + /** + * + * + * @export + * @interface TreeViewDataType + */ + export interface TreeViewDataType { + nodes?: TreeViewDataType[]; + open?: boolean; + path?: string; + selected?: boolean; + [propName: string]: any; + } + + /** + * + * + * @class TreeViewItemPrototype + * @extends {AFXTag} + */ + export abstract class TreeViewItemPrototype extends AFXTag { + private _data: TreeViewDataType; + private _indent: number; + private _evt: TagEventType; + treeroot: TreeViewTag; + treepath: string; + parent: TreeViewTag; + fetch: ( + d: TreeViewItemPrototype + ) => Promise; + /** + *Creates an instance of TreeViewItemPrototype. + * @memberof TreeViewItemPrototype + */ + constructor() { + super(); + + } + + /** + * + * + * @protected + * @param {*} p + * @returns {void} + * @memberof TreeViewItemPrototype + */ + protected reload(p: any): void { + if (!p || typeof p !== "string") { + return; + } + switch (p) { + case "expand": + this.open = true; + break; + case "collapse": + this.open = false; + break; + default: + if (p !== this.treepath) { + return; + } + this.open = true; + } + } + + /** + * + * + * @memberof TreeViewItemPrototype + */ + set data(v: TreeViewDataType) { + this._data = v; + if (!v) { + return; + } + this.open = v.open; + if (v.path) { + this.treepath = v.path; + } + this.selected = v.selected; + this.ondatachange(); + } + + /** + * + * + * @type {TreeViewDataType} + * @memberof TreeViewItemPrototype + */ + get data(): TreeViewDataType { + return this._data; + } + + /** + * + * + * @memberof TreeViewItemPrototype + */ + set selected(v: boolean) { + if (!this._data) { + return; + } + this.attsw(v, "selected"); + $(this.refs.wrapper).removeClass(); + this._data.selected = v; + if (v) { + this.treeroot.unselect(); + // set selectedItem but not trigger the update + this.treeroot.itemclick(this._evt); + this._evt.data.dblclick = false; + $(this.refs.wrapper).addClass("afx_tree_item_selected"); + } + } + + /** + * + * + * @type {boolean} + * @memberof TreeViewItemPrototype + */ + get selected(): boolean { + return this.hasattr("selected"); + } + + /** + * + * + * @memberof TreeViewItemPrototype + */ + set open(v: boolean) { + if (!this.is_folder()) { + return; + } + this.attsw(v, "open"); + $(this.refs.toggle).removeClass(); + if (v) { + if (this.fetch) { + this.fetch(this) + .then((d: TreeViewDataType[]) => { + if (!d) { + return; + } + return (this.nodes = d); + }) + .catch((e: Error) => + announcer.oserror(e.toString(), e) + ); + } else { + this.nodes = this.nodes; + } + $(this.refs.childnodes).show(); + } else { + $(this.refs.childnodes).hide(); + } + if (v) { + $(this.refs.toggle).addClass( + "afx-tree-view-folder-open" + ); + } + $(this.refs.toggle).addClass("afx-tree-view-folder-close"); + } + + /** + * + * + * @type {number} + * @memberof TreeViewItemPrototype + */ + get indent(): number { + return this._indent; + } + + /** + * + * + * @memberof TreeViewItemPrototype + */ + set indent(v: number) { + if (!v) { + return; + } + this._indent = v; + $(this.refs.padding) + .css("display", "inline-block") + .css("height", "1px") + .css("padding", 0) + .css("margin", 0) + .css("background-color", "transparent") + .css("width", v * 15 + "px"); + } + + /** + * + * + * @private + * @returns {boolean} + * @memberof TreeViewItemPrototype + */ + private is_folder(): boolean { + if (this.nodes) { + return true; + } else { + return false; + } + } + + /** + * + * + * @type {TreeViewDataType[]} + * @memberof TreeViewItemPrototype + */ + get nodes(): TreeViewDataType[] { + if (!this._data) return undefined; + return this._data.nodes; + } + + /** + * + * + * @memberof TreeViewItemPrototype + */ + set nodes(nodes: TreeViewDataType[]) { + if (!nodes || !this.data) { + return; + } + this._data.nodes = nodes; + // return unless @get("nodes") and @get("nodes").length > 0 + $(this.refs.childnodes).empty(); + $(this.refs.wrapper).addClass("afx_folder_item"); + const root = this.treeroot; + const result = []; + for (let v of nodes) { + const el = $("").appendTo( + this.refs.childnodes + ); + el[0].uify(this.observable); + const element = el[0] as TreeViewTag; + element.treeroot = root; + element.indent = this.indent + 1; + element.open = this.open; + element.parent = this.parent; + element.treepath = `${this.treepath}/${element.aid}`; + element.fetch = this.fetch; + element.data = v; + } + } + + protected init(): void { + this.treeroot = undefined; + this.treepath = this.aid.toString(); + this._evt = { + id: this.aid, + data: { item: this, dblclick: false }, + }; + this.indent = 0; + } + /** + * + * + * @protected + * @memberof TreeViewItemPrototype + */ + protected mount(): void { + $(this.refs.container) + .css("padding", 0) + .css("margin", 0) + .css("white-space", "nowrap"); + $(this.refs.itemholder).css("display", "inline-block"); + $(this.refs.wrapper).click((e) => { + this.selected = true; + }); + $(this.refs.wrapper).dblclick((e) => { + this._evt.data.dblclick = true; + this.selected = true; + }); + + $(this.refs.toggle) + .css("display", "inline-block") + .css("width", "15px") + .addClass("afx-tree-view-item") + .click((e) => { + this.open = !this.open; + e.preventDefault(); + return e.stopPropagation(); + }); + } + + /** + * + * + * @protected + * @returns {TagLayoutType[]} + * @memberof TreeViewItemPrototype + */ + protected layout(): TagLayoutType[] { + return [ + { + el: "div", + ref: "wrapper", + children: [ + { + el: "ul", + ref: "container", + children: [ + { el: "li", ref: "padding" }, + { el: "li", ref: "toggle" }, + { + el: "li", + ref: "itemholder", + class: "itemname", + children: this.itemlayout(), + }, + ], + }, + ], + }, + { + el: "ul", + ref: "childnodes", + }, + ]; + } + + /** + * + * + * @protected + * @abstract + * @returns {TagLayoutType[]} + * @memberof TreeViewItemPrototype + */ + protected abstract itemlayout(): TagLayoutType[]; + + /** + * + * + * @protected + * @abstract + * @memberof TreeViewItemPrototype + */ + protected abstract ondatachange(): void; + } + + + /** + * + * + * @export + * @class SimpleTreeViewItem + * @extends {TreeViewItemPrototype} + */ + export class SimpleTreeViewItem extends TreeViewItemPrototype { + /** + *Creates an instance of SimpleTreeViewItem. + * @memberof SimpleTreeViewItem + */ + constructor() { + super(); + } + + /** + * + * + * @protected + * @returns {void} + * @memberof SimpleTreeViewItem + */ + protected ondatachange(): void { + if (!this.data) { + return; + } + const v = this.data; + const label = this.refs.label as LabelTag; + label.set(v); + } + + /** + * + * + * @protected + * @returns + * @memberof SimpleTreeViewItem + */ + protected itemlayout() { + return [{ el: "afx-label", ref: "label" }]; + } + } + + + /** + * + * + * @export + * @class TreeViewTag + * @extends {AFXTag} + */ + export class TreeViewTag extends AFXTag { + private _selectedItem: TreeViewItemPrototype; + private _ontreeselect: TagEventCallback; + private _ontreedbclick: TagEventCallback; + private _ondragndrop: TagEventCallback; + private _data: TreeViewDataType; + private _treemousedown: (e: JQuery.MouseEventBase) => void; + private _treemouseup: (e: JQuery.MouseEventBase) => void; + private _treemousemove: (e: JQuery.MouseEventBase) => void; + private _dnd: { from: TreeViewTag; to: TreeViewTag }; + parent: TreeViewTag; + treeroot: TreeViewTag; + treepath: string; + indent: number; + open: boolean; + fetch: ( + d: TreeViewItemPrototype + ) => Promise; + + /** + *Creates an instance of TreeViewTag. + * @memberof TreeViewTag + */ + constructor() { + super(); + + } + + /** + * + * + * @protected + * @memberof TreeViewTag + */ + protected init(): void { + this.itemtag = "afx-tree-view-item"; + this._ontreeselect = this._ondragndrop = this._ontreedbclick = ( + e + ) => {}; + + this.indent = 0; + this.open = true; + this.treepath = this.aid.toString(); + } + + /** + * + * + * @protected + * @returns {TagLayoutType[]} + * @memberof TreeViewTag + */ + protected layout(): TagLayoutType[] { + return []; + } + + /** + * + * + * @protected + * @param {*} [d] + * @memberof TreeViewTag + */ + protected reload(d?: any): void {} + /** + * + * + * @memberof TreeViewTag + */ + set dragndrop(v: boolean) { + this.attsw(v, "dragndrop"); + } + + /** + * + * + * @type {boolean} + * @memberof TreeViewTag + */ + get dragndrop(): boolean { + return this.hasattr("dragndrop"); + } + + /** + * + * + * @memberof TreeViewTag + */ + set ontreeselect(v: TagEventCallback) { + this._ontreeselect = v; + } + + /** + * + * + * @memberof TreeViewTag + */ + set ontreedbclick(v: TagEventCallback) { + this._ontreedbclick = v; + } + + /** + * + * + * @memberof TreeViewTag + */ + set itemtag(v: string) { + $(this).attr("itemtag", v); + } + + /** + * + * + * @type {string} + * @memberof TreeViewTag + */ + get itemtag(): string { + return $(this).attr("itemtag"); + } + + /** + * + * + * @memberof TreeViewTag + */ + unselect(): void { + if (this.selectedItem) { + this._selectedItem.selected = false; + } + } + + /** + * + * + * @type {TreeViewItemPrototype} + * @memberof TreeViewTag + */ + get selectedItem(): TreeViewItemPrototype { + return this._selectedItem; + } + + /** + * + * + * @memberof TreeViewTag + */ + set selectedItem(v: TreeViewItemPrototype) { + if (!v) { + return; + } + if (v === this.selectedItem) { + return; + } + v.selected = true; + } + + /** + * + * + * @returns {void} + * @memberof TreeViewTag + */ + expandAll(): void { + if (this.is_leaf()) { + return; + } + return this.update("expand"); + } + + /** + * + * + * @returns {void} + * @memberof TreeViewTag + */ + collapseAll(): void { + if (this.is_leaf()) { + return; + } + return this.update("collapse"); + } + + /** + * + * + * @param {TagEventType} e + * @returns {void} + * @memberof TreeViewTag + */ + itemclick(e: TagEventType): void { + if (!e || !e.data) { + return; + } + if (e.data.item === this.selectedItem && !e.data.dblclick) { + return; + } + this.selectedItem = e.data.item; + const evt = { id: this.aid, data: e.data }; + if (e.data.dblclick) { + this._ontreedbclick(evt); + return this.observable.trigger("treedbclick", evt); + } else { + this._ontreeselect(evt); + return this.observable.trigger("treeselect", evt); + } + } + + /** + * + * + * @returns {boolean} + * @memberof TreeViewTag + */ + is_root(): boolean { + return this.treeroot === undefined; + } + + /** + * + * + * @returns {boolean} + * @memberof TreeViewTag + */ + is_leaf(): boolean { + const data = this.data; + if (!data) { + return true; + } + if (data.nodes) { + return false; + } else { + return true; + } + } + + /** + * + * + * @memberof TreeViewTag + */ + set ondragndrop(v: TagEventCallback) { + this._ondragndrop = v; + } + + /** + * + * + * @memberof TreeViewTag + */ + set data(v: TreeViewDataType) { + if (!v) { + return; + } + this._data = v; + $(this).empty(); + if (v.path) { + this.treepath = v.path; + } + let tag = this.itemtag; + if (v.tag) { + ({ tag } = v); + } + const el = $(`<${tag}>`).appendTo(this); + el[0].uify(this.observable); + const element = el[0] as TreeViewItemPrototype; + element.treeroot = this.is_root() ? this : this.treeroot; + element.indent = this.indent; + element.treepath = this.treepath; + element.open = this.open; + element.fetch = this.fetch; + element.parent = this; + element.data = v; + if (this.is_root()) { + $(this).off("mousedown", this._treemousedown); + if (this.dragndrop) { + $(this).on("mousedown", this._treemousedown); + } + } + } + + /** + * + * + * @type {TreeViewDataType} + * @memberof TreeViewTag + */ + get data(): TreeViewDataType { + return this._data; + } + + /** + * + * + * @protected + * @memberof TreeViewTag + */ + protected mount(): void { + this._dnd = { + from: undefined, + to: undefined, + }; + this._treemousedown = (e) => { + let obj: any = $(e.target).closest("afx-tree-view"); + if (obj.length === 0) { + return; + } + let el = obj[0] as TreeViewTag; + if (el === this) { + return; + } + this._dnd.from = el; + this._dnd.to = undefined; + $(window).on("mouseup", this._treemouseup); + return $(window).on("mousemove", this._treemousemove); + }; + + this._treemouseup = (e) => { + $(window).off("mouseup", this._treemouseup); + $(window).off("mousemove", this._treemousemove); + $("#systooltip").hide(); + let obj = $(e.target).closest("afx-tree-view"); + if (obj.length === 0) { + return; + } + let el = obj[0] as TreeViewTag; + if (el.is_leaf()) { + el = el.parent; + } + if ( + el === this._dnd.from || + el === this._dnd.from.parent + ) { + return; + } + this._dnd.to = el; + this._ondragndrop({ + id: this.aid, + data: this._dnd, + }); + this._dnd = { + from: undefined, + to: undefined, + }; + }; + + this._treemousemove = (e) => { + if (!e) { + return; + } + if (!this._dnd.from) { + return; + } + const data = this._dnd.from.data; + const $label = $("#systooltip"); + const top = e.clientY + 5; + const left = e.clientX + 5; + $label.show(); + const label = $label[0] as LabelTag; + label.set(data); + $label.css("top", top + "px").css("left", left + "px"); + }; + } + } + + define("afx-tree-view", TreeViewTag); + define("afx-tree-view-item", SimpleTreeViewItem); + } + } +} diff --git a/src/core/tags/WindowTag.js b/src/core/tags/WindowTag.js deleted file mode 100644 index b09bd31..0000000 --- a/src/core/tags/WindowTag.js +++ /dev/null @@ -1,245 +0,0 @@ -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ -class WindowTag extends Ant.OS.GUI.BaseTag { - constructor(r, o) { - super(r, o); - this.setopt("minimizable", true); - this.setopt("resizable", true); - this.setopt("apptitle", "Untitled"); - this.setopt("desktop", Ant.OS.GUI.workspace); - this.setopt("width", 400); - this.setopt("height", 300); - this.shown = false; - this.isMaxi = false; - this.history = {}; - this.desktop = $(this.get("desktop")); - this.desktop_pos = this.desktop.offset(); - } - - resize() { - const ch = $(this.refs["yield"]).height() / $(this.refs["yield"]).children().length; - return $(this.refs["yield"]).children().each(function(e) { - return $(this).css("height", `${ch}px`); - }); - } - - mount() { - this.root.contextmenuHandle = function(e) {}; - $(this.refs["minbt"]).click(e => { - return this.observable.trigger("hide", { id: this.aid() }); - }); - - $(this.refs["maxbt"]).click(e => { - return this.toggle_window(); - }); - - $(this.refs["closebt"]).click(e => { - return this.observable.trigger("exit", { id: this.aid() }); - }); - const left = ($(this.desktop).width() - (this.get("width"))) / 2; - const top = ($(this.desktop).height() - (this.get("height"))) / 2; - $(this.root) - .css("position", 'absolute') - .css("left", `${left}px`) - .css("top", `${top}px`) - .css("z-index", Ant.OS.GUI.zindex++); - $(this.root).on("mousedown", e => { - if (this.shown) { return; } - return this.observable.trigger("focus", { id: this.aid() }); - }); - - $(this.refs["dragger"]).dblclick(e => { - return this.toggle_window(); - }); - - - this.observable.on("resize", e => this.resize()); - - this.observable.on("focus", () => { - Ant.OS.GUI.zindex++; - $(this.root) - .show() - .css("z-index", Ant.OS.GUI.zindex) - .removeClass("unactive"); - return this.shown = true; - }); - - this.observable.on("blur", () => { - this.shown = false; - return $(this.root) - .addClass("unactive"); - }); - this.observable.on("hide", () => { - $(this.root).hide(); - return this.shown = false; - }); - - this.observable.on("toggle", () => { - if (this.shown) { - return this.observable.trigger("hide", { id: this.aid() }); - } else { - return this.observable.trigger("focus", { id: this.aid() }); - } - }); - this.enable_dragging(); - this.enable_resize(); - this.setsize({ w: (this.get("width")), h: (this.get("height")) }); - return this.observable.trigger("rendered", { id: this.aid() }); - } - - __minimizable__(value) { - if (value) { return $(this.refs["minbt"]).show(); } else { return $(this.refs["minbt"]).hide(); } - } - - __width__(v) { - if (!v) { return; } - return this.setsize({ w: v, h: this.get("height") }); - } - - __height__(v) { - if (!v) { return; } - return this.setsize({ w: this.get("width"), h: v }); - } - - setsize(o) { - if (!o) { return; } - this.opts.width = o.w; - this.opts.height = o.h; - $(this.root) - .css("width", `${o.w}px`) - .css("height", `${o.h}px`); - return this.observable.trigger("resize", { id: this.aid(), data: o }); - } - - __resizable__(value) { - if (value) { - $(this.refs["maxbt"]).show(); - return $(this.refs["grip"]).show(); - } else { - $(this.refs["maxbt"]).hide(); - return $(this.refs["grip"]).hide(); - } - } - - __apptitle__(value) { - if (value) { return this.refs["txtTitle"].set("text", value); } - } - - enable_dragging() { - $(this.refs["dragger"]) - .css("user-select", "none") - .css("cursor", "default"); - return $(this.refs["dragger"]).on("mousedown", e => { - e.preventDefault(); - const offset = $(this.root).offset(); - offset.top = e.clientY - offset.top; - offset.left = e.clientX - offset.left; - $(window).on("mousemove", e => { - let left, top; - if (this.isMaxi) { - this.toggle_window(); - top = 0; - const letf = e.clientX - ($(this.root).width() / 2); - offset.top = 10; - offset.left = $(this.root).width() / 2; - } else { - top = e.clientY - offset.top - this.desktop_pos.top; - left = e.clientX - this.desktop_pos.top - offset.left; - left = left < 0 ? 0 : left; - top = top < 0 ? 0 : top; - } - - return $(this.root) - .css("top", `${top}px`) - .css("left", `${left}px`); - }); - return $(window).on("mouseup", function(e) { - $(window).unbind("mousemove", null); - return $(window).unbind("mouseup", null); - }); - }); - } - - enable_resize() { - $(this.refs["grip"]) - .css("user-select", "none") - .css("cursor", "default") - .css("position", "absolute") - .css("bottom", "0") - .css("right", "0") - .css("cursor", "nwse-resize"); - - return $(this.refs["grip"]).on("mousedown", e => { - e.preventDefault(); - const offset = { top: 0, left: 0 }; - offset.top = e.clientY; - offset.left = e.clientX; - $(window).on("mousemove", e => { - let w = ($(this.root).width() + e.clientX) - offset.left; - let h = ($(this.root).height() + e.clientY) - offset.top; - w = w < 100 ? 100 : w; - h = h < 100 ? 100 : h; - offset.top = e.clientY; - offset.left = e.clientX; - this.isMaxi = false; - return this.setsize({ w, h }); - }); - - return $(window).on("mouseup", function(e) { - $(window).unbind("mousemove", null); - return $(window).unbind("mouseup", null); - }); - }); - } - - toggle_window() { - let h, w; - if (!this.get("resizable")) { return; } - if (this.isMaxi === false) { - this.history = { - top: $(this.root).css("top"), - left: $(this.root).css("left"), - width: $(this.root).css("width"), - height: $(this.root).css("height") - }; - w = $(this.desktop).width(); - h = $(this.desktop).height(); - $(this.root) - .css("top", "0") - .css("left", "0"); - this.setsize({ w, h }); - return this.isMaxi = true; - } else { - this.isMaxi = false; - $(this.root) - .css("top", this.history.top) - .css("left", this.history.left); - return this.setsize({ w: parseInt(this.history.width), h: parseInt(this.history.height) }); - } - } - - layout() { - return [{ - el: "div", class: "afx-window-wrapper", children: [ - { - el: "ul", class: "afx-window-top", children: [ - { el: "li", class: "afx-window-close", ref: "closebt" }, - { el: "li", class: "afx-window-minimize", ref: "minbt" }, - { el: "li", class: "afx-window-maximize", ref: "maxbt" }, - { el: "li", class: "afx-window-title", ref: "dragger", children: [{ - el: "afx-label", ref: "txtTitle" - }] } - ] - }, - { el: "div", class: "afx-clear" }, - { el: "div", ref: "yield", class: "afx-window-content" }, - { el: "div", ref: "grip", class: "afx-window-grip" } - ] - }]; - } -} - -Ant.OS.GUI.define("afx-app-window", WindowTag); \ No newline at end of file diff --git a/src/core/tags/WindowTag.ts b/src/core/tags/WindowTag.ts new file mode 100644 index 0000000..64bc9b5 --- /dev/null +++ b/src/core/tags/WindowTag.ts @@ -0,0 +1,358 @@ +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +namespace OS { + export namespace GUI { + export namespace tag { + export class WindowTag extends AFXTag { + desktop: string; + private _width: number; + private _height: number; + private _shown: boolean; + private _isMaxi: boolean; + private _history: GenericObject; + private _desktop_pos: GenericObject; + constructor() { + super(); + } + + protected init(): void { + this._shown = false; + this._isMaxi = false; + this._history = {}; + this.desktop = GUI.workspace; + this._desktop_pos = $(this.desktop).offset(); + this.minimizable = true; + this.resizable = true; + this.apptitle = "Untitled"; + + } + protected calibrate(): void {} + protected reload(d?: any): void {} + + set width(v: number) { + this._width = v; + if (!v) { + return; + } + this.setsize({ w: v, h: this.height }); + } + get width(): number { + return this._width; + } + + set height(v: number) { + this._height = v; + if (!v) { + return; + } + this.setsize({ + w: this.width, + h: v, + }); + } + + get height(): number { + return this._height; + } + set minimizable(v: boolean) { + this.attsw(v, "minimizable"); + if (v) { + $(this.refs["minbt"]).show(); + } else { + $(this.refs["minbt"]).hide(); + } + } + get minimizable(): boolean { + return this.hasattr("minimizable"); + } + set resizable(v: boolean) { + this.attsw(v, "resizable"); + if (v) { + $(this.refs["maxbt"]).show(); + $(this.refs["grip"]).show(); + } else { + $(this.refs["maxbt"]).hide(); + $(this.refs["grip"]).hide(); + } + } + get resizable(): boolean { + return this.hasattr("resizable"); + } + set apptitle(v: string| FormatedString) { + $(this).attr("apptitle", v.__()); + if (v) { + (this.refs["txtTitle"] as LabelTag).text = v; + } + } + get apptitle(): string| FormatedString { + return $(this).attr("apptitle"); + } + + private resize(): void { + const ch = + $(this.refs["yield"]).height() / + $(this.refs["yield"]).children().length; + $(this.refs["yield"]) + .children() + .each(function (e) { + $(this).css("height", `${ch}px`); + }); + } + + protected mount(): void { + + this.contextmenuHandle = function (e) {}; + $(this.refs["minbt"]).click((e) => { + return this.observable.trigger("hide", { + id: this.aid, + }); + }); + + $(this.refs["maxbt"]).click((e) => { + return this.toggle_window(); + }); + + $(this.refs["closebt"]).click((e) => { + return this.observable.trigger("exit", { + id: this.aid, + }); + }); + const left = ($(this.desktop).width() - this.width) / 2; + const top = ($(this.desktop).height() - this.height) / 2; + $(this) + .css("position", "absolute") + .css("left", `${left}px`) + .css("top", `${top}px`) + .css("z-index", Ant.OS.GUI.zindex++); + $(this).on("mousedown", (e) => { + if (this._shown) { + return; + } + return this.observable.trigger("focus", { + id: this.aid, + }); + }); + + $(this.refs["dragger"]).dblclick((e) => { + return this.toggle_window(); + }); + + this.observable.on("resize", (e) => this.resize()); + + this.observable.on("focus", () => { + Ant.OS.GUI.zindex++; + $(this) + .show() + .css("z-index", Ant.OS.GUI.zindex) + .removeClass("unactive"); + this._shown = true; + }); + + this.observable.on("blur", () => { + this._shown = false; + return $(this).addClass("unactive"); + }); + this.observable.on("hide", () => { + $(this).hide(); + return (this._shown = false); + }); + + this.observable.on("toggle", () => { + if (this._shown) { + return this.observable.trigger("hide", { + id: this.aid, + }); + } else { + return this.observable.trigger("focus", { + id: this.aid, + }); + } + }); + this.enable_dragging(); + this.enable_resize(); + this.setsize({ + w: this.width, + h: this.height, + }); + return this.observable.trigger("rendered", { + id: this.aid, + }); + } + + private setsize(o: GenericObject): void { + if (!o) { + return; + } + this._width = o.w; + this._height = o.h; + $(this).css("width", `${o.w}px`).css("height", `${o.h}px`); + this.observable.trigger("resize", { + id: this.aid, + data: o, + }); + } + + private enable_dragging(): void { + $(this.refs["dragger"]) + .css("user-select", "none") + .css("cursor", "default"); + $(this.refs["dragger"]).on("mousedown", (e) => { + e.preventDefault(); + const offset = $(this).offset(); + offset.top = e.clientY - offset.top; + offset.left = e.clientX - offset.left; + $(window).on("mousemove", (e) => { + let left: number, top: number; + if (this._isMaxi) { + this.toggle_window(); + top = 0; + const letf = e.clientX - $(this).width() / 2; + offset.top = 10; + offset.left = $(this).width() / 2; + } else { + top = + e.clientY - + offset.top - + this._desktop_pos.top; + left = + e.clientX - + this._desktop_pos.top - + offset.left; + left = left < 0 ? 0 : left; + top = top < 0 ? 0 : top; + } + + return $(this) + .css("top", `${top}px`) + .css("left", `${left}px`); + }); + return $(window).on("mouseup", function (e) { + $(window).unbind("mousemove", null); + return $(window).unbind("mouseup", null); + }); + }); + } + + private enable_resize(): void { + $(this.refs["grip"]) + .css("user-select", "none") + .css("cursor", "default") + .css("position", "absolute") + .css("bottom", "0") + .css("right", "0") + .css("cursor", "nwse-resize"); + + $(this.refs["grip"]).on("mousedown", (e) => { + e.preventDefault(); + const offset = { top: 0, left: 0 }; + offset.top = e.clientY; + offset.left = e.clientX; + $(window).on("mousemove", (e) => { + let w = $(this).width() + e.clientX - offset.left; + let h = $(this).height() + e.clientY - offset.top; + w = w < 100 ? 100 : w; + h = h < 100 ? 100 : h; + offset.top = e.clientY; + offset.left = e.clientX; + this._isMaxi = false; + this.setsize({ w, h }); + }); + + $(window).on("mouseup", function (e) { + $(window).unbind("mousemove", null); + return $(window).unbind("mouseup", null); + }); + }); + } + + private toggle_window(): void { + let h: number, w: number; + if (!this.resizable) { + return; + } + if (this._isMaxi === false) { + this._history = { + top: $(this).css("top"), + left: $(this).css("left"), + width: $(this).css("width"), + height: $(this).css("height"), + }; + w = $(this.desktop).width(); + h = $(this.desktop).height(); + $(this).css("top", "0").css("left", "0"); + this.setsize({ w, h }); + this._isMaxi = true; + } else { + this._isMaxi = false; + $(this) + .css("top", this._history.top) + .css("left", this._history.left); + this.setsize({ + w: parseInt(this._history.width), + h: parseInt(this._history.height), + }); + } + } + + protected layout(): TagLayoutType[] { + return [ + { + el: "div", + class: "afx-window-wrapper", + children: [ + { + el: "ul", + class: "afx-window-top", + children: [ + { + el: "li", + class: "afx-window-close", + ref: "closebt", + }, + { + el: "li", + class: "afx-window-minimize", + ref: "minbt", + }, + { + el: "li", + class: "afx-window-maximize", + ref: "maxbt", + }, + { + el: "li", + class: "afx-window-title", + ref: "dragger", + children: [ + { + el: "afx-label", + ref: "txtTitle", + }, + ], + }, + ], + }, + { el: "div", class: "afx-clear" }, + { + el: "div", + ref: "yield", + class: "afx-window-content", + }, + { + el: "div", + ref: "grip", + class: "afx-window-grip", + }, + ], + }, + ]; + } + } + + define("afx-app-window", WindowTag); + } + } +} diff --git a/src/core/tags/tag.js b/src/core/tags/tag.js deleted file mode 100644 index 792c8c8..0000000 --- a/src/core/tags/tag.js +++ /dev/null @@ -1,163 +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 - */ -Ant.OS.GUI.tag = {}; -Ant.OS.GUI.zindex = 10; -Ant.OS.GUI.BaseTag = class BaseTag { - constructor(root, observable) { - this.root = root; - this.observable = observable; - this.opts = {}; - if (!this.observable) { this.observable = new Ant.OS.API.Announcer(); } - // export to rootnode - this.root.observable = this.observable; - this.root.set = (k, v) => this.set(k, v); - this.root.get = k => this.get(k); - this.root.aid = () => this.aid(); - this.root.calibrate = () => this.calibrate(); - this.root.sync = d => this.sync(d); - this.mounted = false; - this.root.setup = () => this.setup(); - this.refs = {}; - this.setopt("data-id", (Math.floor(Math.random() * 100000) + 1).toString()); - this.setopt("tooltip", undefined); - //$(@root).attr "data-id", @get("data-id") - this.children = $(this.root).children(); - - for (let obj of Array.from(this.layout())) { - const dom = this.mkui(obj); - if (dom) { - $(dom).appendTo(this.root); - } - } - if (this.refs.yield) { - for (let v of Array.from(this.children)) { $(v).detach().appendTo(this.refs.yield); } - } else { - this.children = []; - } - $(this.root).children().each((i, e) => e.mkui(this.observable)); - } - - __(k, v) { - if (v) { this.set(k, v); } - return this.get(k); - } - - __tooltip__(v) { - if (!v) { return; } - return $(this.root).attr("tooltip", v); - } - - setopt(name, val) { - let value = val; - if ($(this.root).attr(name)) { - const v = $(this.root).attr(name); - try { - value = JSON.parse(v); - } catch (e) { - value = v; - } - } - return this.set(name, value); - } - - set(opt, value, flag) { - if (opt === "*") { - for (let k in value) { const v = value[k]; this.set(k, v); } - } else { - if (this[`__${opt}`] && !flag) { this[`__${opt}`](value); } - this.opts[opt] = value; - if (this[`__${opt}__`] && !flag) { this[`__${opt}__`](value); } - } - return this; - } - - aid() { - return this.get("data-id"); - } - - calibrate() {} - - update() {} - - get(opt) { - if (opt === "*") { return this.opts; } - return this.opts[opt]; - } - - sync(d) { - this.update(d); - $(this.root).children().each(function() { return this.update(d); }); - return this.root; - } - - setup() { - if (this.mounted) { return; } - this.mounted = true; - this.mount(); - $(this.root).children().each(function() { return this.mount(); }); - return this.root; - } - - mount() {} - - layout() { - return []; - } - // should be defined by subclasses - - mkui(tag) { - if (!tag) { return undefined; } - const dom = $(`<${tag.el}>`); - if (tag.class) { $(dom).addClass(tag.class); } - if (tag.id) { $(dom).attr("data-id", tag.id); } - if (tag.height) { $(dom).attr("data-height", tag.height); } - if (tag.width) { $(dom).attr("data-width", tag.width); } - if (tag.tooltip) { $(dom).attr("tooltip", tag.tooltip); } - if (tag.children) { - for (let v of Array.from(tag.children)) { $(this.mkui(v)).appendTo(dom); } - } - if (tag.ref) { - this.refs[tag.ref] = dom[0]; - } - // dom.mount @observable - return dom[0]; //.uify(@observable) - } -}; - -Element.prototype.mkui = function(observable) { - const tag = this.tagName.toLowerCase(); - if (RegExp("afx-*", "i" ).test(tag) && Ant.OS.GUI.tag[tag]) { - const o = new (Ant.OS.GUI.tag[tag])(this, observable); - return o.root; - } else { - $(this).children().each(function() { - return this.mkui(observable); - }); - } - return this; -}; - - -Element.prototype.mount = function() { - if (this.setup) { return this.setup(); } - $(this).children().each(function() { return this.mount(); }); - return this; -}; - -Element.prototype.update = function(d) { - if (this.sync) { return this.sync(d); } - $(this).children().each(function() { return this.update(d); }); - return this; -}; - -Element.prototype.uify = function(observable) { - this.mkui(observable); - return this.mount(); -}; - -Ant.OS.GUI.define = (name, cls) => Ant.OS.GUI.tag[name] = cls; - diff --git a/src/core/tags/tag.ts b/src/core/tags/tag.ts new file mode 100644 index 0000000..b6c1963 --- /dev/null +++ b/src/core/tags/tag.ts @@ -0,0 +1,235 @@ +/* + * 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 + */ +interface HTMLElement { + update(d?: any): void; + contextmenuHandle(e: JQuery.MouseEventBase, m: OS.GUI.tag.MenuTag): void; + sync(): void; + afxml(o: OS.API.Announcer): void; + uify(o: OS.API.Announcer): void; + mozRequestFullScreen: any; + webkitRequestFullscreen: any; + msRequestFullscreen: any; +} + +interface Document { + mozCancelFullScreen: any; + webkitExitFullscreen: any; + cancelFullScreen: any; +} +namespace OS { + export namespace GUI { + export interface TagLayoutType { + el: string; + children?: TagLayoutType[]; + ref?: string; + class?: string; + id?: string | number; + tooltip?: string | FormatedString; + width?: number; + height?: number; + } + export interface TagEventType { + id: number | string; + data: any; + } + + export type TagEventCallback = (e: TagEventType) => void; + export var zindex: number = 10; + + export abstract class AFXTag extends HTMLElement { + observable: API.Announcer; + protected refs: GenericObject; + protected _mounted: boolean; + constructor() { + super(); + + if (!this.observable) { + this.observable = new Ant.OS.API.Announcer(); + } + this._mounted = false; + this.refs = {}; + } + + set(v: GenericObject) { + for (let k in v) { + let descriptor = this.descriptor_of(k); + if (descriptor && descriptor.set) this[k] = v[k]; + } + } + + set tooltip(v: string) { + if (!v) { + return; + } + $(this).attr("tooltip", v); + } + private descriptor_of(k: string) { + let desc: PropertyDescriptor; + let obj = this; + do { + desc = Object.getOwnPropertyDescriptor(obj, k); + } while (!desc && (obj = Object.getPrototypeOf(obj))); + return desc; + } + set aid(v: string| number) { + $(this).attr("data-id", v); + } + + get aid(): string | number { + return $(this).attr("data-id"); + } + sync(): void { + if(this._mounted) + { + return; + } + this._mounted = true; + // reflect attributes + this.mount(); + super.sync(); + } + afxml(o: API.Announcer): void { + if(o) + this.observable = o; + if(!this.aid) + this.aid = (Math.floor(Math.random() * 100000) + 1).toString(); + const children = $(this).children(); + for (let obj of this.layout()) { + const dom = this.mkui(obj); + if (dom) { + $(dom).appendTo(this); + } + } + if (this.refs.yield) { + for (let v of children) { + $(v).detach().appendTo(this.refs.yield); + } + } + const attrs = {}; + for (let i = 0; i < this.attributes.length; i++) { + const element = this.attributes[i]; + let descriptor = this.descriptor_of(element.nodeName); + if (descriptor && descriptor.set) { + let value = ""; + try { + value = JSON.parse(element.nodeValue); + } catch (e) { + value = element.nodeValue; + } + attrs[element.nodeName] = value; + } + } + super.afxml(this.observable); + this.init(); + for(let k in attrs) + { + this[k] = attrs[k]; + } + + } + update(d: any): void { + this.reload(d); + super.update(d); + } + protected abstract init(): void; + protected abstract mount(): void; + + protected abstract layout(): TagLayoutType[]; + + protected abstract reload(d?: any): void; + // should be defined by subclasses + + protected calibrate(): void {} + + private mkui(tag: TagLayoutType): Element { + if (!tag) { + return undefined; + } + const dom = $(`<${tag.el}>`); + if (tag.class) { + $(dom).addClass(tag.class); + } + if (tag.id) { + $(dom).attr("data-id", tag.id); + } + if (tag.height) { + $(dom).attr("data-height", tag.height); + } + if (tag.width) { + $(dom).attr("data-width", tag.width); + } + if (tag.tooltip) { + $(dom).attr("tooltip", tag.tooltip.__()); + } + if (tag.children) { + for (let v of Array.from(tag.children)) { + $(this.mkui(v)).appendTo(dom); + } + } + if (tag.ref) { + this.refs[tag.ref] = dom[0]; + } + // dom.mount @observable + return dom[0]; //.uify(@observable) + } + + protected attsw(flag: boolean, v: string, el?: HTMLElement): void { + if (flag) this.atton(v, el); + else this.attoff(v, el); + } + protected atton(v: string, el?: HTMLElement): void { + const element = el ? el : this; + $(element).attr(v, ""); + } + + protected attoff(v: string, el?: HTMLElement): void { + const element = el ? el : this; + element.removeAttribute(v); + } + + protected hasattr(v: string, el?: HTMLElement): boolean { + const element = el ? el : this; + return element.hasAttribute(v); + } + } + + HTMLElement.prototype.update = function (d):void { + $(this) + .children() + .each(function () { + return this.update(d); + }); + }; + HTMLElement.prototype.sync = function (): void { + $(this) + .children() + .each(function () { + return this.sync(); + }); + }; + HTMLElement.prototype.afxml = function(o: API.Announcer): void { + $(this) + .children() + .each(function () { + return this.afxml(o); + }); + } + HTMLElement.prototype.uify = function(o: API.Announcer): void { + this.afxml(o); + this.sync(); + } + + export namespace tag { + export function define( + name: string, + cls: { new (): T } + ): void { + customElements.define(name, cls); + } + } + } +} diff --git a/src/core/vfs.js b/src/core/vfs.js deleted file mode 100644 index 45beaed..0000000 --- a/src/core/vfs.js +++ /dev/null @@ -1,567 +0,0 @@ -/* - * decaffeinate suggestions: - * 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 - */ -// Copyright 2017-2018 Xuan Sang LE - -// AnTOS Web desktop is is licensed under the GNU General Public -// License v3.0, see the LICENCE file for more information - -// This program is free software: you can redistribute it and/or -// modify it under the terms of the GNU General Public License as -// published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. - -// You should have received a copy of the GNU General Public License -//along with this program. If not, see https://www.gnu.org/licenses/. -String.prototype.asFileHandle = function() { - const list = this.split("://"); - const handles = Ant.OS.API.VFS.findHandles(list[0]); - if (!handles || (handles.length === 0)) { - Ant.OS.announcer.osfail(__("VFS unknown handle: {0}", this), (Ant.OS.API.throwe("OS.VFS")), this); - return null; - } - return new (handles[0])(this); -}; - -this.OS.API.VFS = { - handles: { }, - register( protos, cls ) { - return Ant.OS.API.VFS.handles[protos] = cls; - }, // if typeof protos is "string" - //Ant.OS.API.VFS.handles[v] = cls for v in protos - findHandles(proto) { - const l = ((() => { - const result = []; - for (let k in Ant.OS.API.VFS.handles) { - const v = Ant.OS.API.VFS.handles[k]; - if (proto.trim().match((new RegExp(k , "g")))) { - result.push(v); - } - } - return result; - })()); - return l; - } -}; - -class BaseFileHandle { - constructor(path) { - this.dirty = false; - this.cache = undefined; - this.setPath(path); - } - - setPath(p) { - this.ready = false; - if (!p) { return; } - this.path = p.toString(); - const list = this.path.split("://"); - this.protocol = list[0]; - if (!(list.length > 1)) { return; } - const re = list[1].replace(/^\/+|\/+$/g, ''); - if (re === "") { return; } - this.genealogy = re.split("/"); - if (!this.isRoot()) { this.basename = this.genealogy[this.genealogy.length - 1]; } - if ((this.basename.lastIndexOf(".") !== 0) && (this.basename.indexOf( "." ) !== -1)) { return this.ext = this.basename.split( "." ).pop(); } - } - - filename() { - if (!this.basename) { return "Untitled"; } - return this.basename; - } - - setCache(v) { - this.cache = v; - return this; - } - - asFileHandle() { return this; } - - isRoot() { return (!this.genealogy) || (this.genealogy.size === 0); } - - child(name) { - if (this.isRoot()) { - return this.path + name; - } else { - return this.path + "/" + name; - } - } - - isHidden() { - if (!this.basename) { return false; } - return this.basename[0] === "."; - } - - hash() { - if (!this.path) { return -1; } - return this.path.hash(); - } - - b64(t) { - // t is object or mime type - return new Promise((resolve, reject) => { - const m = t === "object" ? "text/plain" : t; - if (!this.cache) { return resolve(""); } - if ((t === "object") || (typeof this.cache === "string")) { - let b64; - if (t === "object") { - b64 = JSON.stringify(this.cache, undefined, 4).asBase64(); - } else { - b64 = this.cache.asBase64(); - } - b64 = `data:${m};base64,${b64}`; - return resolve(b64); - } else { - const reader = new FileReader(); - reader.readAsDataURL(this.cache); - reader.onload = () => resolve(reader.result); - return reader.onerror = e => reject(e); - } - }); - } - - parent() { - if (this.isRoot()) { return this; } - return (this.protocol + "://" + (this.genealogy.slice(0 , this.genealogy.length - 1)).join("/")) - .asFileHandle(); - } - - onready() { - // read meta data - return new Promise((resolve, reject) => { - if (this.ready) { return resolve(this.info); } - return this.meta() - .then(d => { - if (d.errors) { return reject(Ant.OS.API.throwe(__("{0}: {1}", d.error, this.path))); } - this.info = d.result; - this.ready = true; - return resolve(d.result); - }).catch(e => reject(__e(e))); - }); - } - - read(t) { - return new Promise((resolve, reject) => { - return this.onready() - .then(r => { - return this._rd(t) - .then(d => // Ant.OS.announcer.ostrigger "VFS", { m: "read", file: me } - resolve(d)).catch(e => reject(__e(e))); - }).catch(e => reject(__e(e))); - }); - } - - write(t) { - return new Promise((resolve, reject) => { - return this._wr(t) - .then(r => { - Ant.OS.announcer.ostrigger("VFS", { m: "write", file: this }); - return resolve(r); - }).catch(e => reject(__e(e))); - }); - } - - mk(d) { - return new Promise((resolve, reject) => { - return this.onready() - .then(r => { - return this._mk(d) - .then(d => { - Ant.OS.announcer.ostrigger("VFS", { m: "mk", file: this }); - return resolve(d); - }).catch(e => reject(__e(e))); - }).catch(e => reject(__e(e))); - }); - } - - remove() { - return new Promise((resolve, reject) => { - return this.onready() - .then(r => { - return this._rm() - .then(d => { - Ant.OS.announcer.ostrigger("VFS", { m: "remove", file: this }); - return resolve(d); - }).catch(e => reject(__e(e))); - }).catch(e => reject(__e(e))); - }); - } - - upload() { - return new Promise((resolve, reject) => { - return this.onready() - .then(r => { - return this._up() - .then(d => { - Ant.OS.announcer.ostrigger("VFS", { m: "upload", file: this }); - return resolve(d); - }).catch(e => reject(__e(e))); - }).catch(e => reject(__e(e))); - }); - } - - publish() { - return new Promise((resolve, reject) => { - return this.onready() - .then(r => { - return this._pub() - .then(d => { - Ant.OS.announcer.ostrigger("VFS", { m: "publish", file: this }); - return resolve(d); - }).catch(e => reject(__e(e))); - }).catch(e => reject(__e(e))); - }); - } - - download() { - return new Promise((resolve, reject) => { - return this.onready() - .then(r => { - return this._down() - .then(d => { - Ant.OS.announcer.ostrigger("VFS", { m: "download", file: this }); - return resolve(d); - }).catch(e => reject(__e(e))); - }).catch(e => reject(__e(e))); - }); - } - - move(d) { - return new Promise((resolve, reject) => { - return this.onready() - .then(r => { - return this._mv(d) - .then(data => { - Ant.OS.announcer.ostrigger("VFS", { m: "move", file: d.asFileHandle() }); - return resolve(data); - }).catch(e => reject(__e(e))); - }).catch(e => reject(__e(e))); - }); - } - - execute() { - return new Promise((resolve, reject) => { - return this.onready() - .then(r => { - return this._exec() - .then(d => { - Ant.OS.announcer.ostrigger("VFS", { m: "execute", file: this }); - return resolve(d); - }).catch(e => reject(__e(e))); - }).catch(e => reject(__e(e))); - }); - } - - getlink() { return this.path; } - - unsupported(t) { - return new Promise((resolve, reject) => { - return reject(Ant.OS.API.throwe(__("Action {0} is unsupported on: {1}", t, this.path))); - }); - } - // actions must be implemented by subclasses - - _rd(t) { return this.unsupported("read"); } - _wr(d, t) { return this.unsupported("write"); } - _mk(d) { return this.unsupported("mk"); } - _rm() { return this.unsupported("remove"); } - _mv(d) { return this.unsupported("move"); } - _up() { return this.unsupported("upload"); } - _down() { return this.unsupported("download"); } - _exec() { return this.unsupported("execute"); } - _pub() { return this.unsupported("publish"); } -} - -// now export the class -Ant.OS.API.VFS.BaseFileHandle = BaseFileHandle; - -// Remote file handle -class RemoteFileHandle extends Ant.OS.API.VFS.BaseFileHandle { - constructor(path) { - super(path); - } - - meta() { - return new Promise((resolve, reject) => { - return Ant.OS.API.handle.fileinfo(this.path) - .then(d => { - if (d.error) { return reject(Ant.OS.API.throwe(__("{0}: {1}", d.error, this.path))); } - return resolve(d); - }).catch(e => reject(__e(e))); - }); - } - - - getlink() { - return Ant.OS.API.handle.get + "/" + this.path; - } - - _rd(t) { - // t: binary, text, any type - if (!this.info) { - return new Promise((resolve, reject) => { - return reject(Ant.OS.API.throwe(__( - "file meta-data not found: {0}", this.path) - ) - ); - }); - } - if (this.info.type === "dir") { return Ant.OS.API.handle.scandir(this.path); } - //read the file - if (t === "binary") { return Ant.OS.API.handle.fileblob(this.path); } - return Ant.OS.API.handle.readfile(this.path, t ? t : "text"); - } - - _wr(t) { - // t is base64 or undefined - return new Promise((resolve, reject) => { - if (t === "base64") { - return Ant.OS.API.handle.write(this.path, this.cache).then(d => { - if (d.error) { return reject(Ant.OS.API.throwe(__("{0}: {1}", d.error, this.path))); } - return resolve(d); - }).catch(e => reject(__e(e))); - } else { - return this.b64(t) - .then(r => { - return Ant.OS.API.handle.write(this.path, r) - .then(result => { - if (result.error) { - return reject(Ant.OS.API.throwe(__( - "{0}: {1}", result.error, this.path) - ) - ); - } - return resolve(result); - }).catch(e => reject(__e(e))); - }).catch(e => reject(__e(e))); - } - }); - } - - _mk(d) { - return new Promise((resolve, reject) => { - if (!this.info) { - return reject(Ant.OS.API.throwe(__( - "file meta-data not found: {0}", this.path) - ) - ); - } - if (this.info.type === "file") { - return reject(Ant.OS.API.throwe(__("{0} is not a directory", this.path))); - } - return Ant.OS.API.handle.mkdir(`${this.path}/${d}`) - .then(d => { - if (d.error) { return reject(Ant.OS.API.throwe(__("{0}: {1}", d.error, this.path))); } - return resolve(d); - }).catch(e => reject(__e(e))); - }); - } - - _rm() { - return new Promise((resolve, reject) => { - return Ant.OS.API.handle.delete(this.path) - .then(d => { - if (d.error) { return reject(Ant.OS.API.throwe(__("{0}: {1}", d.error, this.path))); } - return resolve(d); - }).catch(e => reject(__e(e))); - }); - } - - - _mv(d) { - return new Promise((resolve, reject) => { - return Ant.OS.API.handle.move(this.path, d) - .then(d => { - if (d.error) { return reject(Ant.OS.API.throwe(__("{0}: {1}", d.error, this.path))); } - return resolve(d); - }).catch(e => reject(__e(e))); - }); - } - - - _up() { - return new Promise((resolve, reject) => { - if (this.info.type !== "dir") { - return reject(Ant.OS.API.throwe(__("{0} is not a file", this.path))); - } - return Ant.OS.API.handle.upload(this.path) - .then(d => { - if (d.error) { return reject(Ant.OS.API.throwe(__("{0}: {1}", d.error, this.path))); } - return resolve(d); - }).catch(e => reject(__e(e))); - }); - } - - _down() { - return new Promise((resolve, reject) => { - if (this.info.type === "dir") { - return Ant.OS.API.throwe(__("{0} is not a file", this.path)); - } - return Ant.OS.API.handle.fileblob(this.path) - .then(d => { - const blob = new Blob([d], { type: "octet/stream" }); - Ant.OS.API.saveblob(this.basename, blob); - return resolve(); - }).catch(e => reject(__e(e))); - }); - } - - _pub() { - return new Promise((resolve, reject) => { - return Ant.OS.API.handle.sharefile(this.path, true) - .then(d => { - if (d.error) { return reject(Ant.OS.API.throwe(__("{0}: {1}", d.error, this.path))); } - return resolve(d); - }).catch(e => reject(__e(e))); - }); - } -} - -Ant.OS.API.VFS.register("^(home|desktop|os|Untitled)$", RemoteFileHandle); - -// Application Handle -class ApplicationHandle extends Ant.OS.API.VFS.BaseFileHandle { - constructor(path) { - super(path); - if (this.basename) { this.info = Ant.OS.setting.system.packages[this.basename]; } - this.ready = true; - } - - _rd(t) { - return new Promise((resolve, reject) => { - if (this.info) { return resolve({ result: this.info }); } - if (!this.isRoot()) { return reject(Ant.OS.API.throwe(__("Application meta data isnt found"))); } - return resolve({ result: ((() => { - const result = []; - for (let k in Ant.OS.setting.system.packages) { - const v = Ant.OS.setting.system.packages[k]; - result.push(v); - } - return result; - })()) }); - }); - } -} - - -Ant.OS.API.VFS.register("^app$", ApplicationHandle); - -class BufferFileHandle extends Ant.OS.API.VFS.BaseFileHandle { - constructor(path, mime, data) { - super(path); - if (data) { this.cache = data; } - this.info = { - mime, - path, - size: data ? data.length : 0, - name: this.basename, - type: "file" - }; - } - - _rd(t) { - return new Promise((resolve, reject) => { - return resolve({ result: this.cache }); - }); - } - - _wr(d, t) { - this.cache = d; - if (this.onchange) { this.onchange(this); } - return new Promise((resolve, reject) => resolve({ result: true })); - } - - _down() { - return new Promise((resolve, reject) => { - const blob = new Blob([this.cache], { type: "octet/stream" }); - Ant.OS.API.saveblob(this.basename, blob); - return resolve(); - }); - } - - onchange(f) { - return this.onchange = f; - } -} - -Ant.OS.API.VFS.register("^mem$", BufferFileHandle); - -class URLFileHandle extends Ant.OS.API.VFS.BaseFileHandle { - constructor(path) { - super(path); - this.ready = true; - } - - _rd(t) { - return Ant.OS.API.get(this.path, t ? t : "text"); - } -} - -Ant.OS.API.VFS.register("^(http|https|ftp)$", URLFileHandle); - - -class SharedFileHandle extends Ant.OS.API.VFS.BaseFileHandle { - constructor(path) { - super(path); - if (this.isRoot()) { this.ready = true; } - } - - meta() { - return Ant.OS.API.handle.fileinfo(this.path); - } - - _rd(t) { - if (this.isRoot()) { return Ant.OS.API.get(`${Ant.OS.API.handle.shared}/all`, t); } - //read the file - if (t === "binary") { return Ant.OS.API.handle.fileblob(this.path); } - return Ant.OS.API.handle.readfile(this.path, t ? t : "text"); - } - - _wr(d, t) { - return new Promise((resolve, reject) => { - return Ant.OS.API.handle.write(this.path, d) - .then(d => { - if (d.error) { return reject(Ant.OS.API.throwe(__("{0}: {1}", d.error, this.path))); } - return resolve(d); - }).catch(e => reject(__e(e))); - }); - } - - _rm() { - return new Promise((resolve, reject) => { - return Ant.OS.API.handle.sharefile(this.basename, false) - .then(d => { - if (d.error) { return reject(Ant.OS.API.throwe(__("{0}: {1}", d.error, this.path))); } - return resolve(d); - }).catch(e => reject(__e(e))); - }); - } - - _down() { - return new Promise((resolve, reject) => { - if (this.info.type === "dir") { - return reject(Ant.OS.API.throwe(__("{0} is not a file", this.path))); - } - return Ant.OS.API.handle.fileblob(this.path) - .then(data => { - const blob = new Blob([data], { type: "octet/stream" }); - Ant.OS.API.saveblob(this.basename, blob); - return resolve(); - }).catch(e => reject(__e(e))); - }); - } - _pub() { - return new Promise((resolve, reject) => resolve({ result: this.basename })); - } -} - -Ant.OS.API.VFS.register("^shared$", SharedFileHandle); \ No newline at end of file diff --git a/src/core/vfs.ts b/src/core/vfs.ts new file mode 100644 index 0000000..d34e2e6 --- /dev/null +++ b/src/core/vfs.ts @@ -0,0 +1,1376 @@ +/* + * decaffeinate suggestions: + * 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 + */ +// Copyright 2017-2018 Xuan Sang LE + +// AnTOS Web desktop is is licensed under the GNU General Public +// License v3.0, see the LICENCE file for more information + +// This program is free software: you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. + +// You should have received a copy of the GNU General Public License +//along with this program. If not, see https://www.gnu.org/licenses/. +type VFSFileHandleClass = { new (...args: any[]): OS.API.VFS.BaseFileHandle }; +interface String { + asFileHandle(): OS.API.VFS.BaseFileHandle; +} +namespace OS { + export namespace API { + + /** + * + * + * @export + * @interface UserPermissionType + */ + export interface UserPermissionType { + read: boolean; + write: boolean; + exec: boolean; + } + + /** + * + * + * @export + * @interface FileInfoType + */ + export interface FileInfoType { + mime: string; + size: number; + name: string; + path: string; + type: string; + perm?: { + group: UserPermissionType; + owner: UserPermissionType; + other: UserPermissionType; + }; + ctime?: string; + mtime?: string; + gid?: number; + uid?: number; + [propName: string]: any; + } + + export namespace VFS { + + String.prototype.asFileHandle = function (): BaseFileHandle { + const list = this.split("://"); + const handles = API.VFS.findHandles(list[0]); + if (!handles || handles.length === 0) { + announcer.osfail( + __("VFS unknown handle: {0}", this), + API.throwe("OS.VFS") + ); + return null; + } + return new handles[0](this); + }; + + export const handles: GenericObject = {}; + + /** + * + * + * @export + * @param {string} protos + * @param {VFSFileHandleClass} cls + */ + export function register( + protos: string, + cls: VFSFileHandleClass + ): void { + handles[protos] = cls; + } + + /** + * + * + * @export + * @param {string} proto + * @returns {VFSFileHandleClass[]} + */ + export function findHandles(proto: string): VFSFileHandleClass[] { + const l = (() => { + const result = []; + for (let k in handles) { + const v = handles[k]; + if (proto.trim().match(new RegExp(k, "g"))) { + result.push(v); + } + } + return result; + })(); + return l; + } + + /** + * + * + * @export + * @abstract + * @class BaseFileHandle + */ + export abstract class BaseFileHandle { + dirty: boolean; + cache: any; + ready: boolean; + path: string; + protocol: string; + genealogy: string[]; + basename: string; + info: FileInfoType; + ext: string; + + /** + *Creates an instance of BaseFileHandle. + * @param {string} path + * @memberof BaseFileHandle + */ + constructor(path: string) { + this.dirty = false; + this.cache = undefined; + this.setPath(path); + } + + /** + * + * + * @param {string} p + * @returns {void} + * @memberof BaseFileHandle + */ + setPath(p: string): void { + this.ready = false; + if (!p) { + return; + } + this.path = p.toString(); + const list = this.path.split("://"); + this.protocol = list[0]; + if (!(list.length > 1)) { + return; + } + const re = list[1].replace(/^\/+|\/+$/g, ""); + if (re === "") { + return; + } + this.genealogy = re.split("/"); + if (!this.isRoot()) { + this.basename = this.genealogy[ + this.genealogy.length - 1 + ]; + } + if ( + this.basename.lastIndexOf(".") !== 0 && + this.basename.indexOf(".") !== -1 + ) { + this.ext = this.basename.split(".").pop(); + } + } + + /** + * + * + * @returns {string} + * @memberof BaseFileHandle + */ + filename(): string { + if (!this.basename) { + return "Untitled"; + } + return this.basename; + } + + /** + * + * + * @param {*} v + * @returns {BaseFileHandle} + * @memberof BaseFileHandle + */ + setCache(v: any): BaseFileHandle { + this.cache = v; + return this; + } + + /** + * + * + * @returns {BaseFileHandle} + * @memberof BaseFileHandle + */ + asFileHandle(): BaseFileHandle { + return this; + } + + /** + * + * + * @returns {boolean} + * @memberof BaseFileHandle + */ + isRoot(): boolean { + return !this.genealogy || this.genealogy.length === 0; + } + + /** + * + * + * @param {string} name + * @returns {string} + * @memberof BaseFileHandle + */ + child(name: string): string { + if (this.isRoot()) { + return this.path + name; + } else { + return this.path + "/" + name; + } + } + + /** + * + * + * @returns {boolean} + * @memberof BaseFileHandle + */ + isHidden(): boolean { + if (!this.basename) { + return false; + } + return this.basename[0] === "."; + } + + /** + * + * + * @returns {number} + * @memberof BaseFileHandle + */ + hash(): number { + if (!this.path) { + return -1; + } + return this.path.hash(); + } + + /** + * + * + * @param {string} t + * @returns {(Promise)} + * @memberof BaseFileHandle + */ + b64(t: string): Promise { + // t is object or mime type + return new Promise((resolve, reject) => { + const m = t === "object" ? "text/plain" : t; + if (!this.cache) { + return resolve(""); + } + if (t === "object" || typeof this.cache === "string") { + let b64: string; + if (t === "object") { + b64 = JSON.stringify( + this.cache, + undefined, + 4 + ).asBase64(); + } else { + b64 = this.cache.asBase64(); + } + b64 = `data:${m};base64,${b64}`; + return resolve(b64); + } else { + const reader = new FileReader(); + reader.readAsDataURL(this.cache); + reader.onload = () => resolve(reader.result); + return (reader.onerror = (e) => reject(e)); + } + }); + } + + /** + * + * + * @returns {BaseFileHandle} + * @memberof BaseFileHandle + */ + parent(): BaseFileHandle { + if (this.isRoot()) { + return this; + } + return ( + this.protocol + + "://" + + this.genealogy + .slice(0, this.genealogy.length - 1) + .join("/") + ).asFileHandle(); + } + + /** + * + * + * @returns {Promise} + * @memberof BaseFileHandle + */ + onready(): Promise { + // read meta data + return new Promise((resolve, reject) => { + if (this.ready) { + return resolve(this.info); + } + return this.meta() + .then((d: RequestResult) => { + this.info = d.result as FileInfoType; + this.ready = true; + return resolve(d.result as FileInfoType); + }) + .catch((e: Error) => reject(__e(e))); + }); + } + + /** + * + * + * @param {string} [t] + * @returns {Promise} + * @memberof BaseFileHandle + */ + read(t?: string): Promise { + return new Promise(async (resolve, reject) => { + try { + const r = await this.onready(); + try { + const d = await this._rd(t); + return resolve(d); + } catch (e) { + return reject(__e(e)); + } + } catch (e_1) { + return reject(__e(e_1)); + } + }); + } + + /** + * + * + * @param {string} t + * @returns {Promise} + * @memberof BaseFileHandle + */ + write(t: string): Promise { + return new Promise(async (resolve, reject) => { + try { + const r: RequestResult = await this._wr(t); + announcer.ostrigger("VFS", { + m: "write", + file: this, + }); + return resolve(r); + } catch (e) { + return reject(__e(e)); + } + }); + } + + /** + * + * + * @param {string} d + * @returns {Promise} + * @memberof BaseFileHandle + */ + mk(d: string): Promise { + return new Promise(async (resolve, reject) => { + try { + const r = await this.onready(); + try { + const d_1 = await this._mk(d); + announcer.ostrigger("VFS", { + m: "mk", + file: this, + }); + return resolve(d_1); + } catch (e) { + return reject(__e(e)); + } + } catch (e_1) { + return reject(__e(e_1)); + } + }); + } + + /** + * + * + * @returns {Promise} + * @memberof BaseFileHandle + */ + remove(): Promise { + return new Promise(async (resolve, reject) => { + try { + const r = await this.onready(); + try { + const d = await this._rm(); + announcer.ostrigger("VFS", { + m: "remove", + file: this, + }); + return resolve(d); + } catch (e) { + return reject(__e(e)); + } + } catch (e_1) { + return reject(__e(e_1)); + } + }); + } + + /** + * + * + * @returns {Promise} + * @memberof BaseFileHandle + */ + upload(): Promise { + return new Promise(async (resolve, reject) => { + try { + const r = await this.onready(); + try { + const d = await this._up(); + announcer.ostrigger("VFS", { + m: "upload", + file: this, + }); + return resolve(d); + } catch (e) { + return reject(__e(e)); + } + } catch (e_1) { + return reject(__e(e_1)); + } + }); + } + + /** + * + * + * @returns {Promise} + * @memberof BaseFileHandle + */ + publish(): Promise { + return new Promise(async (resolve, reject) => { + try { + const r = await this.onready(); + try { + const d = await this._pub(); + announcer.ostrigger("VFS", { + m: "publish", + file: this, + }); + return resolve(d); + } catch (e) { + return reject(__e(e)); + } + } catch (e_1) { + return reject(__e(e_1)); + } + }); + } + + /** + * + * + * @returns {Promise} + * @memberof BaseFileHandle + */ + download(): Promise { + return new Promise(async (resolve, reject) => { + try { + const r = await this.onready(); + try { + const d = await this._down(); + announcer.ostrigger("VFS", { + m: "download", + file: this, + }); + return resolve(d); + } catch (e) { + return reject(__e(e)); + } + } catch (e_1) { + return reject(__e(e_1)); + } + }); + } + + /** + * + * + * @param {string} d + * @returns {Promise} + * @memberof BaseFileHandle + */ + move(d: string): Promise { + return new Promise(async (resolve, reject) => { + try { + const r = await this.onready(); + try { + const data = await this._mv(d); + announcer.ostrigger("VFS", { + m: "move", + file: d.asFileHandle(), + }); + return resolve(data); + } catch (e) { + return reject(__e(e)); + } + } catch (e_1) { + return reject(__e(e_1)); + } + }); + } + + /** + * + * + * @returns {Promise} + * @memberof BaseFileHandle + */ + execute(): Promise { + return new Promise(async (resolve, reject) => { + try { + const r = await this.onready(); + try { + const d = await this._exec(); + announcer.ostrigger("VFS", { + m: "execute", + file: this, + }); + return resolve(d); + } catch (e) { + return reject(__e(e)); + } + } catch (e_1) { + return reject(__e(e_1)); + } + }); + } + + /** + * + * + * @returns {string} + * @memberof BaseFileHandle + */ + getlink(): string { + return this.path; + } + + /** + * + * + * @param {string} t + * @returns {Promise} + * @memberof BaseFileHandle + */ + unsupported(t: string): Promise { + return new Promise((resolve, reject) => { + return reject( + API.throwe( + __( + "Action {0} is unsupported on: {1}", + t, + this.path + ) + ) + ); + }); + } + // actions must be implemented by subclasses + + /** + * + * + * @param {string} t + * @returns {Promise} + * @memberof BaseFileHandle + */ + _rd(t: string): Promise { + return this.unsupported("read"); + } + + /** + * + * + * @param {string} t + * @param {*} [d] + * @returns {Promise} + * @memberof BaseFileHandle + */ + _wr(t: string, d?: any): Promise { + return this.unsupported("write"); + } + /** + * + * + * @param {string} d + * @returns {Promise} + * @memberof BaseFileHandle + */ + _mk(d: string): Promise { + return this.unsupported("mk"); + } + /** + * + * + * @returns {Promise} + * @memberof BaseFileHandle + */ + _rm(): Promise { + return this.unsupported("remove"); + } + + /** + * + * + * @param {string} d + * @returns {Promise} + * @memberof BaseFileHandle + */ + _mv(d: string): Promise { + return this.unsupported("move"); + } + + /** + * + * + * @returns {Promise} + * @memberof BaseFileHandle + */ + _up(): Promise { + return this.unsupported("upload"); + } + + /** + * + * + * @returns {Promise} + * @memberof BaseFileHandle + */ + _down(): Promise { + return this.unsupported("download"); + } + + /** + * + * + * @returns {Promise} + * @memberof BaseFileHandle + */ + _exec(): Promise { + return this.unsupported("execute"); + } + + /** + * + * + * @returns {Promise} + * @memberof BaseFileHandle + */ + _pub(): Promise { + return this.unsupported("publish"); + } + abstract meta(): Promise; + } + + // Remote file handle + /** + * + * + * @class RemoteFileHandle + * @extends {BaseFileHandle} + */ + class RemoteFileHandle extends BaseFileHandle { + constructor(path: string) { + super(path); + } + + /** + * + * + * @returns {Promise} + * @memberof RemoteFileHandle + */ + meta(): Promise { + return new Promise(async (resolve, reject) => { + try { + const d = await API.handle.fileinfo( + this.path + ); + if (d.error) { + return reject( + API.throwe( + __("{0}: {1}", d.error, this.path) + ) + ); + } + return resolve(d); + } catch (e) { + return reject(__e(e)); + } + }); + } + + /** + * + * + * @returns {string} + * @memberof RemoteFileHandle + */ + getlink(): string { + return API.handle.get + "/" + this.path; + } + + _rd(t: string): Promise { + // t: binary, text, any type + if (!this.info) { + return new Promise((resolve, reject) => { + return reject( + API.throwe( + __( + "file meta-data not found: {0}", + this.path + ) + ) + ); + }); + } + if (this.info.type === "dir") { + return API.handle.scandir(this.path); + } + //read the file + if (t === "binary") { + return API.handle.fileblob(this.path); + } + return API.handle.readfile( + this.path, + t ? t : "text" + ); + } + + /** + * + * + * @param {string} t + * @returns {Promise} + * @memberof RemoteFileHandle + */ + _wr(t: string): Promise { + // t is base64 or undefined + return new Promise(async (resolve, reject) => { + if (t === "base64") { + try { + const d = await API.handle.write( + this.path, + this.cache + ); + if (d.error) { + return reject( + API.throwe( + __("{0}: {1}", d.error, this.path) + ) + ); + } + return resolve(d); + } catch (e) { + return reject(__e(e)); + } + } else { + try { + const r = await this.b64(t); + try { + const result = await API.handle.write( + this.path, + r as string + ); + if (result.error) { + return reject( + API.throwe( + __( + "{0}: {1}", + result.error, + this.path + ) + ) + ); + } + return resolve(result); + } catch (e_1) { + return reject(__e(e_1)); + } + } catch (e_2) { + return reject(__e(e_2)); + } + } + }); + } + + /** + * + * + * @param {string} d + * @returns {Promise} + * @memberof RemoteFileHandle + */ + _mk(d: string): Promise { + return new Promise((resolve, reject) => { + if (!this.info) { + return reject( + API.throwe( + __( + "file meta-data not found: {0}", + this.path + ) + ) + ); + } + if (this.info.type === "file") { + return reject( + API.throwe( + __("{0} is not a directory", this.path) + ) + ); + } + return API.handle + .mkdir(`${this.path}/${d}`) + .then((d) => { + if (d.error) { + return reject( + API.throwe( + __("{0}: {1}", d.error, this.path) + ) + ); + } + return resolve(d); + }) + .catch((e) => reject(__e(e))); + }); + } + + /** + * + * + * @returns {Promise} + * @memberof RemoteFileHandle + */ + _rm(): Promise { + return new Promise(async (resolve, reject) => { + try { + const d = await API.handle.remove(this.path); + if (d.error) { + return reject( + API.throwe( + __("{0}: {1}", d.error, this.path) + ) + ); + } + return resolve(d); + } catch (e) { + return reject(__e(e)); + } + }); + } + + /** + * + * + * @param {string} d + * @returns {Promise} + * @memberof RemoteFileHandle + */ + _mv(d: string): Promise { + return new Promise(async (resolve, reject) => { + try { + const r = await API.handle.move( + this.path, + d + ); + if (r.error) { + return reject( + API.throwe( + __("{0}: {1}", r.error, this.path) + ) + ); + } + return resolve(r); + } catch (e) { + return reject(__e(e)); + } + }); + } + + /** + * + * + * @returns {Promise} + * @memberof RemoteFileHandle + */ + _up(): Promise { + return new Promise((resolve, reject) => { + if (this.info.type !== "dir") { + return reject( + API.throwe( + __("{0} is not a file", this.path) + ) + ); + } + return API.handle + .upload(this.path) + .then((d) => { + if (d.error) { + return reject( + API.throwe( + __("{0}: {1}", d.error, this.path) + ) + ); + } + return resolve(d); + }) + .catch((e) => reject(__e(e))); + }); + } + + /** + * + * + * @returns {Promise} + * @memberof RemoteFileHandle + */ + _down(): Promise { + return new Promise((resolve, reject) => { + if (this.info.type === "dir") { + return API.throwe( + __("{0} is not a file", this.path) + ); + } + return API.handle + .fileblob(this.path) + .then((d) => { + const blob = new Blob([d], { + type: "octet/stream", + }); + API.saveblob(this.basename, blob); + return resolve(); + }) + .catch((e) => reject(__e(e))); + }); + } + + /** + * + * + * @returns {Promise} + * @memberof RemoteFileHandle + */ + _pub(): Promise { + return new Promise(async (resolve, reject) => { + try { + const d = await API.handle.sharefile( + this.path, + true + ); + if (d.error) { + return reject( + API.throwe( + __("{0}: {1}", d.error, this.path) + ) + ); + } + return resolve(d); + } catch (e) { + return reject(__e(e)); + } + }); + } + } + + register("^(home|desktop|os|Untitled)$", RemoteFileHandle); + + // Application Handle + /** + * + * + * @class ApplicationHandle + * @extends {BaseFileHandle} + */ + class ApplicationHandle extends BaseFileHandle { + + /** + *Creates an instance of ApplicationHandle. + * @param {string} path + * @memberof ApplicationHandle + */ + constructor(path: string) { + super(path); + if (this.basename) { + let v: any = + OS.setting.system.packages[this.basename]; + v.type = "app"; + v.mime = "antos/app"; + v.size = 0; + this.info = v as FileInfoType; + } + this.ready = true; + } + + /** + * + * + * @returns {Promise} + * @memberof ApplicationHandle + */ + meta(): Promise { + return new Promise((resolve, reject) => + resolve({ + result: this.info, + error: false, + }) + ); + } + + /** + * + * + * @param {string} t + * @returns {Promise} + * @memberof ApplicationHandle + */ + _rd(t: string): Promise { + return new Promise((resolve, reject) => { + if (this.info) { + return resolve({ + result: this.info, + error: false, + }); + } + if (!this.isRoot()) { + return reject( + API.throwe( + __("Application meta data isnt found") + ) + ); + } + const result = []; + for (let k in OS.setting.system.packages) { + const v = OS.setting.system.packages[k]; + result.push(v); + } + return resolve({ + result: result, + error: false, + }); + }); + } + } + + register("^app$", ApplicationHandle); + + /** + * + * + * @class BufferFileHandle + * @extends {BaseFileHandle} + */ + class BufferFileHandle extends BaseFileHandle { + constructor(path: string, mime: string, data: any) { + super(path); + if (data) { + this.cache = data; + } + this.info = { + mime: mime, + path: path, + size: data ? data.length : 0, + name: this.basename, + type: "file", + }; + } + + /** + * + * + * @returns {Promise} + * @memberof BufferFileHandle + */ + meta(): Promise { + return new Promise((resolve, reject) => + resolve({ + result: this.info, + error: false, + }) + ); + } + + /** + * + * + * @param {string} t + * @returns {Promise} + * @memberof BufferFileHandle + */ + _rd(t: string): Promise { + return new Promise((resolve, reject) => { + return resolve({ + result: this.cache, + error: false, + }); + }); + } + + /** + * + * + * @param {string} t + * @param {*} d + * @returns {Promise} + * @memberof BufferFileHandle + */ + _wr(t: string, d: any): Promise { + this.cache = d; + return new Promise((resolve, reject) => + resolve({ + result: true, + error: false, + }) + ); + } + + /** + * + * + * @returns {Promise} + * @memberof BufferFileHandle + */ + _down(): Promise { + return new Promise((resolve, reject) => { + const blob = new Blob([this.cache], { + type: "octet/stream", + }); + API.saveblob(this.basename, blob); + return resolve(); + }); + } + } + + API.VFS.register("^mem$", BufferFileHandle); + + /** + * + * + * @class URLFileHandle + * @extends {BaseFileHandle} + */ + class URLFileHandle extends BaseFileHandle { + constructor(path: string) { + super(path); + this.ready = true; + this.info = { + path: path, + name: path, + mime: "url", + type: "url", + size: 0, + }; + } + + /** + * + * + * @returns {Promise} + * @memberof URLFileHandle + */ + meta(): Promise { + return new Promise((resolve, reject) => + resolve({ + result: this.info, + error: false, + }) + ); + } + _rd(t: string): Promise { + return API.get(this.path, t ? t : "text"); + } + } + + API.VFS.register("^(http|https|ftp)$", URLFileHandle); + + /** + * + * + * @class SharedFileHandle + * @extends {API.VFS.BaseFileHandle} + */ + class SharedFileHandle extends API.VFS.BaseFileHandle { + constructor(path: string) { + super(path); + if (this.isRoot()) { + this.ready = true; + } + } + + /** + * + * + * @returns {Promise} + * @memberof SharedFileHandle + */ + meta(): Promise { + return API.handle.fileinfo(this.path); + } + + /** + * + * + * @param {string} t + * @returns {Promise} + * @memberof SharedFileHandle + */ + _rd(t: string): Promise { + if (this.isRoot()) { + return API.get( + `${API.handle.shared}/all`, + t + ); + } + //read the file + if (t === "binary") { + return API.handle.fileblob(this.path); + } + return API.handle.readfile( + this.path, + t ? t : "text" + ); + } + + /** + * + * + * @param {string} t + * @param {string} d + * @returns {Promise} + * @memberof SharedFileHandle + */ + _wr(t: string, d: string): Promise { + return new Promise(async (resolve, reject) => { + try { + const r = await API.handle.write( + this.path, + d + ); + if (r.error) { + return reject( + API.throwe( + __("{0}: {1}", r.error, this.path) + ) + ); + } + return resolve(r); + } catch (e) { + return reject(__e(e)); + } + }); + } + + /** + * + * + * @returns {Promise} + * @memberof SharedFileHandle + */ + _rm(): Promise { + return new Promise(async (resolve, reject) => { + try { + const d = await API.handle.sharefile( + this.basename, + false + ); + if (d.error) { + return reject( + API.throwe( + __("{0}: {1}", d.error, this.path) + ) + ); + } + return resolve(d); + } catch (e) { + return reject(__e(e)); + } + }); + } + + /** + * + * + * @returns {Promise} + * @memberof SharedFileHandle + */ + _down(): Promise { + return new Promise((resolve, reject) => { + if (this.info.type === "dir") { + return reject( + API.throwe( + __("{0} is not a file", this.path) + ) + ); + } + return API.handle + .fileblob(this.path) + .then((data) => { + const blob = new Blob([data], { + type: "octet/stream", + }); + API.saveblob(this.basename, blob); + return resolve(); + }) + .catch((e) => reject(__e(e))); + }); + } + + /** + * + * + * @returns {Promise} + * @memberof SharedFileHandle + */ + _pub(): Promise { + return new Promise((resolve, reject) => + resolve({ + result: this.basename, + error: false, + }) + ); + } + } + + API.VFS.register("^shared$", SharedFileHandle); + } + } +} diff --git a/src/packages/Syslog/Calendar.js b/src/packages/Syslog/Calendar.ts similarity index 52% rename from src/packages/Syslog/Calendar.js rename to src/packages/Syslog/Calendar.ts index ebf30af..1e5dd1c 100644 --- a/src/packages/Syslog/Calendar.js +++ b/src/packages/Syslog/Calendar.ts @@ -11,7 +11,7 @@ // This program is free software: you can redistribute it and/or // modify it under the terms of the GNU General Public License as -// published by the Free Software Foundation, either version 3 of +// published by the Free Software Foundation, either version 3 of // the License, or (at your option) any later version. // This program is distributed in the hope that it will be useful, @@ -21,32 +21,32 @@ // You should have received a copy of the GNU General Public License //along with this program. If not, see https://www.gnu.org/licenses/. -class Calendar extends this.OS.GUI.BaseService { - constructor(args) { - super("Calendar", args); - //@iconclass = "fa fa-commenting" - this.text = ""; - this.iconclass = "fa fa-calendar"; - } - init() { - //update time each second - return this.watch(1000, () => { - const now = new Date; - this.text = now.toString(); - return this.domel.set("text", this.text); - }); - } +namespace OS { + export namespace application { + export class Calendar extends BaseService { + constructor(args: AppArgumentsType[]) { + super("Calendar", args); + //@iconclass = "fa fa-commenting" + this.text = ""; + this.iconclass = "fa fa-calendar"; + } + init(): void { + //update time each second + this.watch(1000, () => { + const now = new Date(); + this.text = now.toString(); + (this.domel as GUI.tag.SimpleMenuEntryTag).text = this.text; + }); + } - awake(e) { - return this.openDialog("CalendarDialog" ) - .then(d => console.log(d)); - } - // do nothing - cleanup(evt) { - return console.log("cleanup for quit"); + awake(e: GUI.TagEventType): void { + this.openDialog("CalendarDialog").then((d) => console.log(d)); + } + // do nothing + cleanup(evt: BaseEvent): void { + return console.log("cleanup for quit"); + } + } } } - // do nothing - -this.OS.register("Calendar", Calendar); \ No newline at end of file diff --git a/src/packages/Syslog/PushNotification.js b/src/packages/Syslog/PushNotification.js deleted file mode 100644 index 7947d00..0000000 --- a/src/packages/Syslog/PushNotification.js +++ /dev/null @@ -1,175 +0,0 @@ -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * DS208: Avoid top-level this - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ -// Copyright 2017-2018 Xuan Sang LE - -// AnTOS Web desktop is is licensed under the GNU General Public -// License v3.0, see the LICENCE file for more information - -// This program is free software: you can redistribute it and/or -// modify it under the terms of the GNU General Public License as -// published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. - -// You should have received a copy of the GNU General Public License -//along with this program. If not, see https://www.gnu.org/licenses/. - -class PushNotification extends this.OS.GUI.BaseService { - constructor(args) { - super("PushNotification", args); - this.iconclass = "fa fa-bars"; - this.cb = undefined; - this.pending = []; - this.logs = []; - this.logmon = undefined; - } - init() { - this.view = false; - return this._gui.htmlToScheme(PushNotification.scheme, this, this.host); - } - - spin(b) { - if (b && (this.iconclass === "fa fa-bars")) { - this.iconclass = "fa fa-spinner fa-spin"; - this.update(); - return $(this._gui.workspace).css("cursor", "wait"); - } else if (!b && (this.iconclass === "fa fa-spinner fa-spin")) { - this.iconclass = "fa fa-bars"; - this.update(); - return $(this._gui.workspace).css("cursor", "auto"); - } - } - - main() { - this.mlist = this.find("notifylist"); - this.mfeed = this.find("notifeed"); - this.nzone = this.find("notifyzone"); - this.fzone = this.find("feedzone"); - (this.find("btclear")).set("onbtclick", e => this.mlist.set("data", [])); - (this.find("bterrlog")).set("onbtclick", e => this.showLogReport()); - this.subscribe("notification", o => this.pushout('INFO', o)); - this.subscribe("fail", o => this.pushout('FAIL', o)); - this.subscribe("error", o => this.pushout('ERROR', o)); - this.subscribe("info", o => this.pushout('INFO', o)); - - - this.subscribe("loading", o => { - this.pending.push(o.id); - return this.spin(true); - }); - - this.subscribe("loaded", o => { - const i = this.pending.indexOf(o.id); - if (i >= 0) { this.pending.splice(i, 1); } - if (this.pending.length === 0) { return this.spin(false); } - }); - - this.nzone.set("height", "100%"); - this.fzone.set("height", "100%"); - - ($(this.nzone)).css("right", 0) - .css("top", "0") - .css("bottom", "0") - .hide(); - return ($(this.fzone)) - //.css("z-index", 99999) - .css("bottom", "0") - .css("bottom", "0") - .hide(); - } - - showLogReport() { - return this._gui.launch("Syslog"); - } - - addLog(s, o) { - const logtime = new Date(); - const log = { - type: s, - name: o.name, - text: `${o.data.m}`, - id: o.id, - icon: o.data.icon, - iconclass: o.data.iconclass, - error: o.data.e, - time: logtime, - closable: true, - tag: "afx-bug-list-item" - }; - if (this.logmon) { - return this.logmon.addLog(log); - } else { - return this.logs.push(log); - } - } - - pushout(s, o) { - const d = { - text: `[${s}] ${o.name} (${o.id}): ${o.data.m}`, - icon: o.data.icon, - iconclass: o.data.iconclass, - closable: true - }; - if (s !== "INFO") { this.addLog(s, o); } - this.mlist.unshift(d); - return this.notifeed(d); - } - - notifeed(d) { - let timer; - this.mfeed.unshift(d, true); - ($(this.fzone)).show(); - return timer = setTimeout(() => { - this.mfeed.remove(d.domel); - if (this.mfeed.get("data").length === 0) { ($(this.fzone)).hide(); } - return clearTimeout(timer); - } - , 3000); - } - - awake(evt) { - if (this.view) { ($(this.nzone)).hide(); } else { ($(this.nzone)).show(); } - this.view = !this.view; - if (!this.cb) { - this.cb = e => { - if (!($(e.target)).closest($(this.nzone)).length && !($(e.target)).closest(evt.data.item).length) { - ($(this.nzone)).hide(); - $(document).unbind("click", this.cb); - return this.view = !this.view; - } - }; - } - if (this.view) { - return $(document).on("click", this.cb); - } else { - return $(document).unbind("click", this.cb); - } - } - - cleanup(evt) {} -} - // do nothing -PushNotification.scheme = `\ -
- - - - - - - - - - - -
\ -`; -this.OS.register("PushNotification", PushNotification); \ No newline at end of file diff --git a/src/packages/Syslog/PushNotification.ts b/src/packages/Syslog/PushNotification.ts new file mode 100644 index 0000000..8fa1407 --- /dev/null +++ b/src/packages/Syslog/PushNotification.ts @@ -0,0 +1,280 @@ +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS208: Avoid top-level this + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +// Copyright 2017-2018 Xuan Sang LE + +// AnTOS Web desktop is is licensed under the GNU General Public +// License v3.0, see the LICENCE file for more information + +// This program is free software: you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. + +// You should have received a copy of the GNU General Public License +//along with this program. If not, see https://www.gnu.org/licenses/. + +namespace OS { + export namespace application { + import TAG = GUI.tag; + + /** + * + * + * @export + * @class PushNotification + * @extends {BaseService} + */ + export class PushNotification extends BaseService { + private pending: number[]; + private cb: (e: JQuery.ClickEvent) => void; + private view: boolean; + private mlist: TAG.ListViewTag; + private mfeed: TAG.ListViewTag; + private nzone: TAG.OverlayTag; + private fzone: TAG.OverlayTag; + + logs: GenericObject[]; + logmon: Syslog; + + /** + *Creates an instance of PushNotification. + * @param {AppArgumentsType[]} args + * @memberof PushNotification + */ + constructor(args: AppArgumentsType[]) { + super("PushNotification", args); + this.iconclass = "fa fa-bars"; + this.cb = undefined; + this.pending = []; + this.logs = []; + this.logmon = undefined; + } + + /** + * + * + * @returns {void} + * @memberof PushNotification + */ + init(): void { + this.view = false; + return this._gui.htmlToScheme(scheme, this, this.host); + } + + /** + * + * + * @private + * @param {boolean} b + * @memberof PushNotification + */ + private spin(b: boolean): void { + if (b && this.iconclass === "fa fa-bars") { + this.iconclass = "fa fa-spinner fa-spin"; + this.update(); + $(this._gui.workspace).css("cursor", "wait"); + } else if (!b && this.iconclass === "fa fa-spinner fa-spin") { + this.iconclass = "fa fa-bars"; + this.update(); + $(this._gui.workspace).css("cursor", "auto"); + } + } + + /** + * + * + * @memberof PushNotification + */ + main(): void { + this.mlist = this.find("notifylist") as TAG.ListViewTag; + this.mfeed = this.find("notifeed") as TAG.ListViewTag; + this.nzone = this.find("notifyzone") as TAG.OverlayTag; + this.fzone = this.find("feedzone") as TAG.OverlayTag; + (this.find("btclear") as TAG.ButtonTag).onbtclick = (e) => + (this.mlist.data = []); + (this.find("bterrlog") as TAG.ButtonTag).onbtclick = (e) => + this.showLogReport(); + this.subscribe("notification", (o) => this.pushout("INFO", o)); + this.subscribe("fail", (o) => this.pushout("FAIL", o)); + this.subscribe("error", (o) => this.pushout("ERROR", o)); + this.subscribe("info", (o) => this.pushout("INFO", o)); + + this.subscribe("loading", (o) => { + this.pending.push(o.id); + return this.spin(true); + }); + + this.subscribe("loaded", (o) => { + const i = this.pending.indexOf(o.id); + if (i >= 0) { + this.pending.splice(i, 1); + } + if (this.pending.length === 0) { + return this.spin(false); + } + }); + + this.nzone.height = "100%"; + this.fzone.height = "100%"; + + $(this.nzone) + .css("right", 0) + .css("top", "0") + .css("bottom", "0") + .hide(); + $(this.fzone) + //.css("z-index", 99999) + .css("bottom", "0") + .css("bottom", "0") + .hide(); + } + + /** + * + * + * @private + * @returns {void} + * @memberof PushNotification + */ + private showLogReport(): void { + return this._gui.launch("Syslog", []); + } + + /** + * + * + * @private + * @param {string} s + * @param {GenericObject} o + * @memberof PushNotification + */ + private addLog(s: string, o: GenericObject): void { + const logtime = new Date(); + const log = { + type: s, + name: o.name, + text: `${o.data.m}`, + id: o.id, + icon: o.data.icon, + iconclass: o.data.iconclass, + error: o.data.e, + time: logtime, + closable: true, + tag: "afx-bug-list-item", + }; + if (this.logmon) { + this.logmon.addLog(log); + } else { + this.logs.push(log); + } + } + + /** + * + * + * @private + * @param {string} s + * @param {GenericObject} o + * @memberof PushNotification + */ + private pushout(s: string, o: GenericObject): void { + const d = { + text: `[${s}] ${o.name} (${o.id}): ${o.data.m}`, + icon: o.data.icon, + iconclass: o.data.iconclass, + closable: true, + }; + if (s !== "INFO") { + this.addLog(s, o); + } + this.mlist.unshift(d); + this.notifeed(d); + } + + /** + * + * + * @private + * @param {GenericObject} d + * @memberof PushNotification + */ + private notifeed(d: GenericObject): void { + let timer: number; + this.mfeed.unshift(d); + $(this.fzone).show(); + timer = setTimeout(() => { + this.mfeed.delete(d.domel); + if (this.mfeed.data.length === 0) { + $(this.fzone).hide(); + } + return clearTimeout(timer); + }, 3000); + } + + /** + * + * + * @param {GUI.TagEventType} evt + * @memberof PushNotification + */ + awake(evt: GUI.TagEventType): void { + if (this.view) { + $(this.nzone).hide(); + } else { + $(this.nzone).show(); + } + this.view = !this.view; + if (!this.cb) { + this.cb = (e) => { + if ( + !$(e.target).closest($(this.nzone)).length && + !$(e.target).closest(evt.data.item).length + ) { + $(this.nzone).hide(); + $(document).unbind("click", this.cb); + this.view = !this.view; + } + }; + } + if (this.view) { + $(document).on("click", this.cb); + } else { + $(document).unbind("click", this.cb); + } + } + + /** + * + * + * @param {BaseEvent} evt + * @memberof PushNotification + */ + cleanup(evt: BaseEvent): void {} + } + } + // do nothing + const scheme = `\ +
+ + + + + + + + + + + +
\ +`; +} diff --git a/src/packages/Syslog/Syslog.js b/src/packages/Syslog/Syslog.js deleted file mode 100644 index 6778844..0000000 --- a/src/packages/Syslog/Syslog.js +++ /dev/null @@ -1,132 +0,0 @@ -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * DS208: Avoid top-level this - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ -Ant = this - -class BugListItemTag extends this.OS.GUI.tag["afx-list-item-proto"] { - constructor(r, o) { - super(r, o); - } - - __data__(v) { - if (!v) { return; } - this.refs.error.set("text", v.text); - this.refs.time.set("text", v.time); - if (v.icon) { this.refs.error.set("icon", v.icon); } - if (!v.icon) { - this.refs.error.set("iconclass", v.iconclass ? v.iconclass : "fa fa-bug"); - } - return this.set("closable", v.closable); - } - - __selected(v) { - return this.get("data").selected = v; - } - - - itemlayout() { - return { - el: "div", children: [ - { el: "afx-label", ref: "error", class: "afx-bug-list-item-error" }, - { el: "afx-label", ref: "time", class: "afx-bug-list-item-time" } - ] - }; - } -} - - -this.OS.GUI.define("afx-bug-list-item", BugListItemTag); - -class Syslog extends this.OS.GUI.BaseApplication { - constructor(args) { - super("Syslog", args); - } - - main() { - this.loglist = this.find("loglist"); - this.logdetail = this.find("logdetail"); - - this._gui.pushService("Syslog/PushNotification") - .then(srv => { - this.srv = srv; - if (this.srv && this.srv.logs) { this.loglist.set("data", this.srv.logs); } - return this.srv.logmon = this; - }).catch(e => { - this.error(__("Unable to load push notification service"), e); - return this.quit(); - }); - - $(this.find("txturi")).val(Ant.OS.setting.system.error_report); - this.loglist.set("onlistselect", e => { - let data; - if (e && e.data) { data = e.data.item.get("data"); } - if (!data) { return; } - let stacktrace = "None"; - if (data.error) { stacktrace = data.error.stack; } - return $(this.logdetail).text(Syslog.template.format( - data.text, - data.type, - data.time, - data.name, - data.id, - stacktrace - ) - ); - }); - this.loglist.set("onitemclose", e => { - let el; - if (e && e.data) { el = e.data.item; } - if (!el) { return true; } - const data = el.get("data"); - console.log(data); - if (!data.selected) { return true; } - $(this.logdetail).text(""); - return true; - }); - - this.find("btnreport").set("onbtclick", e => { - const uri = $(this.find("txturi")).val(); - if (uri === "") { return; } - const el = this.loglist.get("selectedItem"); - if (!el) { return; } - const data = el.get("data"); - if (!data) { return; } - return Ant.OS.API.post(uri, data) - .then(d => { - return this.notify(__("Error reported")); - }).catch(e => { - return this.notify(__("Unable to report error: {0}", e.toString())); - }); - }); - - return this.find("btclean").set("onbtclick", e => { - if (!this.srv) { return; } - this.srv.logs = []; - this.loglist.set("data", this.srv.logs); - return $(this.logdetail).text(""); - }); - } - - addLog(log) { - return this.loglist.push(log); - } - - cleanup() { - if (this.srv) { return this.srv.logmon = undefined; } - } -} - -Syslog.template = `\ -{0} -Log type: {1} -Log time: {2} -Process: {3} ({4}) -detail: - -{5}\ -`; -Syslog.singleton = true; -this.OS.register("Syslog", Syslog); \ No newline at end of file diff --git a/src/packages/Syslog/Syslog.ts b/src/packages/Syslog/Syslog.ts new file mode 100644 index 0000000..3fc1310 --- /dev/null +++ b/src/packages/Syslog/Syslog.ts @@ -0,0 +1,256 @@ +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS208: Avoid top-level this + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ + +namespace OS { + export namespace GUI { + export namespace tag { + /** + * + * + * @class BugListItemTag + * @extends {ListViewItemTag} + */ + class BugListItemTag extends ListViewItemTag { + /** + *Creates an instance of BugListItemTag. + * @memberof BugListItemTag + */ + constructor() { + super(); + } + + /** + * + * + * @protected + * @memberof BugListItemTag + */ + protected init(): void {} + + /** + * + * + * @protected + * @param {*} [d] + * @memberof BugListItemTag + */ + protected reload(d?: any): void {} + + /** + * + * + * @protected + * @returns {void} + * @memberof BugListItemTag + */ + protected ondatachange(): void { + if (!this.data) { + return; + } + const etag = this.refs.error as LabelTag; + const ttag = this.refs.time as LabelTag; + etag.text = this.data.text; + ttag.text = this.data.time; + if (this.data.icon) { + etag.icon = this.data.icon; + } + if (!this.data.icon) { + etag.iconclass = this.data.iconclass + ? this.data.iconclass + : "fa fa-bug"; + } + this.closable = this.data.closable; + } + + /** + * + * + * @protected + * @returns + * @memberof BugListItemTag + */ + protected itemlayout() { + return { + el: "div", + children: [ + { + el: "afx-label", + ref: "error", + class: "afx-bug-list-item-error", + }, + { + el: "afx-label", + ref: "time", + class: "afx-bug-list-item-time", + }, + ], + }; + } + } + define("afx-bug-list-item", BugListItemTag); + } + } + const template = `\ +{0} +Log type: {1} +Log time: {2} +Process: {3} ({4}) +detail: + +{5}\ +`; + export namespace application { + import TAG = GUI.tag; + + /** + * + * + * @export + * @class Syslog + * @extends {BaseApplication} + */ + export class Syslog extends BaseApplication { + private loglist: TAG.ListViewTag; + private logdetail: HTMLElement; + private srv: PushNotification; + constructor(args: AppArgumentsType[]) { + super("Syslog", args); + } + + /** + * + * + * @memberof Syslog + */ + /** + * + * + * @memberof Syslog + */ + main(): void { + this.loglist = this.find("loglist") as TAG.ListViewTag; + this.logdetail = this.find("logdetail"); + + this._gui + .pushService("Syslog/PushNotification") + .then((srv) => { + + this.srv = srv as PushNotification; + if (this.srv && this.srv.logs) { + this.loglist.data = this.srv.logs; + } + this.srv.logmon = this; + }) + .catch((e) => { + this.error( + __("Unable to load push notification service"), + e + ); + this.quit(false); + }); + + $(this.find("txturi")).val(Ant.OS.setting.system.error_report); + this.loglist.onlistselect = (e) => { + let data; + if (e && e.data) { + data = e.data.item.data; + } + if (!data) { + return; + } + let stacktrace = "None"; + if (data.error) { + stacktrace = data.error.stack; + } + $(this.logdetail).text( + template.format( + data.text, + data.type, + data.time, + data.name, + data.id, + stacktrace + ) + ); + }; + + this.loglist.onitemclose = (e) => { + let el; + if (e && e.data) { + el = e.data.item; + } + if (!el) { + return true; + } + const data = el.get("data"); + console.log(data); + if (!data.selected) { + return true; + } + $(this.logdetail).text(""); + return true; + }; + const bt = this.find("btnreport") as TAG.ButtonTag; + bt.onbtclick = async (e) => { + const uri = $(this.find("txturi")).val(); + if (uri === "") { + return; + } + const el = this.loglist.selectedItem; + if (!el) { + return; + } + const data = el.data; + if (!data) { + return; + } + try { + const d = await Ant.OS.API.post(uri as string, data); + return this.notify(__("Error reported")); + } catch (e) { + return this.notify( + __("Unable to report error: {0}", e.toString()) + ); + } + }; + + (this.find("btclean") as TAG.ButtonTag).onbtclick = (e) => { + if (!this.srv) { + return; + } + this.srv.logs = []; + this.loglist.data = this.srv.logs; + return $(this.logdetail).text(""); + }; + } + + /** + * + * + * @param {GenericObject} log + * @memberof Syslog + */ + addLog(log: GenericObject): void { + this.loglist.push(log); + } + + /** + * + * + * @returns {void} + * @memberof Syslog + */ + cleanup(): void { + if (this.srv) { + return (this.srv.logmon = undefined); + } + } + } + + Syslog.singleton = true; + } +} diff --git a/src/packages/Syslog/package.json b/src/packages/Syslog/package.json index 513f93b..4206c96 100644 --- a/src/packages/Syslog/package.json +++ b/src/packages/Syslog/package.json @@ -1,17 +1,20 @@ { - "app":"Syslog", + "app": "Syslog", "pkgname": "Syslog", - "services": [ "Calendar", "PushNotification" ], + "services": [ + "Calendar", + "PushNotification" + ], "name": "System log", - "description":"Core services and system log", - "info":{ + "description": "Core services and system log", + "info": { "author": "Xuan Sang LE", "email": "xsang.le@gmail.com", "credit": "dedicated to some one here", "licences": "GPLv3" }, - "version":"0.0.1-a", - "category":"System", + "version": "0.0.1-a", + "category": "System", "iconclass": "fa fa-bug", - "mimes":[] -} \ No newline at end of file + "mimes": [] +} diff --git a/src/packages/pkg.mk b/src/packages/pkg.mk index d33ca44..8c3578f 100644 --- a/src/packages/pkg.mk +++ b/src/packages/pkg.mk @@ -9,9 +9,7 @@ title: module: - mkdir build - echo "(function() {" > "build/main.js" - for f in $(module_files); do (cat "$${f}"; echo) >>"build/main.js";done - echo "}).call(this);" >> "build/main.js" + for f in $(module_files); do (cat "../../../dist/packages/$(PKG_NAME)/$${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 new file mode 100644 index 0000000..2003ec2 --- /dev/null +++ b/tests/testTag.ts @@ -0,0 +1,324 @@ +import * as JQuery from "../src/libs/jquery-3.2.1.min" +import {OS as _OS_} from "../dist/antos"; +// some global variable +const _w_ = window as any; +_w_.$ = JQuery; +const OS = _OS_ as any; +_w_.OS = OS; + +// an example tag +class ExampleTag extends OS.GUI.AFXTag +{ + private _prop1: number; + private _prop2: string; + constructor() { + super(); + } + set prop1(v: number) { + this._prop1 = v; + } + set prop2(v: string) { + this._prop2 = v; + } + get prop1(): number { + return this._prop1; + } + get prop2(): string { + return this._prop2; + } + layout() { + return []; + } + init() { + this._prop1 = 0; + this._prop2 = "test"; + } + mount() {} +} + +OS.GUI.tag.define("afx-example-tag", ExampleTag); + +test("Test base tag getter/setter", ()=>{ + const tag = new ExampleTag(); + tag.uify(); + expect(tag.aid).toBeDefined(); + tag.aid = "test"; + expect(tag.aid).toBe("test"); + expect(tag.prop1).toBe(0); + expect(tag.prop2).toBe("test"); + tag.set({ + prop1: 10, + prop2: "Hello", + prop3: "test" + }); + expect(tag.prop1).toBe(10); + expect(tag.prop2).toBe("Hello"); + expect(tag.prop3).toBeUndefined(); + tag.tooltip = "tooltip"; + expect(tag.tooltip).toBeUndefined(); + expect($(tag).attr("tooltip")).toBe("tooltip"); +}); + +// Button test + +test("Test button tag setter/getter", () =>{ + const bt = new OS.GUI.tag.ButtonTag(); + bt.uify(); + expect(bt.enable).toBe(true); + expect(bt.toggle).toBe(false); + expect(bt).toBeDefined(); + bt.text = "test"; + expect(bt.text).toBe("test"); + bt.enable = true; + expect(bt.enable).toBe(true); + bt.enable = false; + expect(bt.enable).toBe(false); + bt.icon = "test"; + bt.iconclass = "test"; + expect(bt.icon).toBeUndefined(); + expect(bt.iconclass).toBeUndefined(); + bt.selected = true; + expect(bt.selected).toBe(true); + bt.selected = false; + expect(bt.selected).toBe(false); + + bt.toggle = true; + expect(bt.toggle).toBe(true); + bt.toggle = false; + expect(bt.toggle).toBe(false); +}); + +test("Test button tag behavior", () =>{ + const bt = new OS.GUI.tag.ButtonTag(); + bt.uify(); + const cb = jest.fn(); + bt.onbtclick = cb + $("button",bt).trigger("click"); + expect(cb).toBeCalledTimes(1); +}); + + // Label test + +test("Test label tag setter/getter", () =>{ + const lbl = new OS.GUI.tag.LabelTag(); + expect(lbl).toBeDefined(); + lbl.uify(); + expect(lbl.icon).toBeUndefined(); + expect(lbl.iconclass).toBeUndefined(); + expect(lbl.text).toBeUndefined(); + lbl.icon = "test"; + lbl.iconclass = "test"; + lbl.text = "test"; + expect(lbl.icon).toBeUndefined(); + expect(lbl.iconclass).toBeUndefined(); + expect(lbl.text).toBe("test"); +}); + +// switch test +test("Test switcher getter/setter", () =>{ + const sw = new OS.GUI.tag.SwitchTag(); + sw.uify(); + expect(sw.swon).toBe(false); + sw.swon = true; + expect(sw.swon).toBe(true); + sw.swon = false; + expect(sw.swon).toBe(false); + expect(sw.enable).toBe(true); + sw.enable = false; + expect(sw.enable).toBe(false); +}); + +test("Test switch behavior", ()=>{ + const sw = new OS.GUI.tag.SwitchTag(); + sw.uify(); + const cb = jest.fn(); + sw.onswchange = cb; + $("span", sw).trigger("click"); + expect(cb).toBeCalledTimes(1); + expect(sw.swon).toBe(true); +}) + +// List view item test +test("Test simple list view item setter/getter", ()=>{ + const item = new OS.GUI.tag.SimpleListItemTag(); + item.uify(); + expect(item.closable).toBe(false); + expect(item.selected).toBe(false); + item.closable = true; + expect(item.closable).toBe(true); + expect(item.data).toBeDefined(); + item.closable = false; + item.selected = false; + const data = { text: "Hello", closable: true, selected: true }; + item.data = data; + expect(item.closable).toBe(true); + expect(item.selected).toBe(true); + expect(($("afx-label",item)[0] as any).text).toBe("Hello"); +}); + + +test("Test simple list view item behaviour", ()=>{ + const item = new OS.GUI.tag.SimpleListItemTag(); + item.uify(); + const cb = jest.fn(); + item.onitemselect = cb; + item.onitemclick = cb; + item.onitemdbclick = cb; + item.onitemclose = cb; + const data = { text: "hello", closable: true, selected: true }; + item.data = data; + expect(cb).toBeCalledTimes(1); + $("li", item).trigger("click"); + expect(cb).toBeCalledTimes(2); + $("li", item).trigger("dblclick"); + expect(cb).toBeCalledTimes(3); + $("i.closable", item).trigger("click"); + expect(cb).toBeCalledTimes(4); +}); + +// list view test +test("Test list view setter/getter", ()=>{ + const item = new OS.GUI.tag.ListViewTag(); + item.uify(); + expect(item.data).toBeDefined(); + expect(item.data.length).toBe(0); + expect(item.multiselect).toBe(false); + expect(item.dropdown).toBe(false); + expect(item.selected).toBe(-1); + expect(item.dragndrop).toBe(false); + expect(item.itemtag).toBe("afx-list-item"); + expect(item.selectedItem).toBeUndefined(); + expect(item.selectedItems).toBeDefined(); + expect(item.selectedItems.length).toBe(0); + item.multiselect = true; + expect(item.multiselect).toBe(true); + item.dragndrop = true; + item.dropdown = true; + item.itemtag = "afx-sample"; + expect(item.multiselect).toBe(false); + expect(item.dropdown).toBe(true); + expect(item.dragndrop).toBe(true); + expect(item.itemtag).toBe("afx-sample"); +}) + +test("Test list view behaviour", () =>{ + const item = new OS.GUI.tag.ListViewTag(); + item.uify(); + const cb = jest.fn(); + item.onlistselect = cb; + item.onlistdbclick = cb; + item.onlist + const data = [ + { text: "Item 1", closable: true, selected: false }, + { text: "Item 2", closable: true, selected: true }, + { text: "Item 3", closable: true, selected: false } + ]; + item.data = data; + expect(item.data).toBe(data); + expect(cb).toBeCalledTimes(1); + expect(item.selectedItem).toBe((data[1] as any).domel); + expect(item.selectedItems.length).toBe(1); + expect(item.selectedItems[0]).toBe((data[1] as any).domel); + expect(item.selected).toBe(1); + item.multiselect = true; + data[2].selected = true; + item.data = data; + expect(cb).toBeCalledTimes(3); + expect(item.selectedItem).toBe((data[2] as any).domel); + expect(item.selectedItems.length).toBe(2); + var el = (data[0] as any).domel + $("li", el).trigger("click"); + expect(cb).toBeCalledTimes(4); + expect(item.selectedItems.length).toBe(3); + expect(item.selectedItem).toBe((data[0] as any).domel); + item.unselect(); + expect(item.selectedItems.length).toBe(0); + expect(item.selectedItem).toBeUndefined(); + item.multiselect = false; + data[0].selected = true; + data[2].selected = true; + item.dragndrop = true; + item.data = data; + expect(item.selectedItem).toBe((data[2] as any).domel); + expect(item.selectedItems.length).toBe(1); + el = (data[1] as any).domel + $("li", el).trigger("dblclick"); + expect(cb).toBeCalledTimes(8); + expect(item.selectedItem).toBe(el); + item.selectPrev(); + expect(item.selectedItem).toBe((data[0] as any).domel); + expect(item.selectedItems.length).toBe(1); + item.selectNext(); + expect(item.selectedItem).toBe((data[1] as any).domel); + expect(item.selectedItems.length).toBe(1); + // close an element + var close_cb = jest.fn((x) => false); + item.onitemclose = close_cb; + el = (data[0] as any).domel + $("i.closable", el).trigger("click"); + expect(close_cb).toBeCalledTimes(1); + expect(item.data.length).toBe(3); + expect(item.selectedItem).toBe((data[1] as any).domel); + close_cb = jest.fn((x) => true); + item.onitemclose = close_cb; + $("i.closable", el).trigger("click"); + expect(item.data.length).toBe(2); + expect(item.selectedItem).toBe((data[0] as any).domel); + expect(close_cb).toBeCalledTimes(1); + el = (data[0] as any).domel; + $("i.closable", el).trigger("click"); + expect(item.data.length).toBe(1); + expect(item.selectedItem).toBeUndefined(); + expect(close_cb).toBeCalledTimes(2); +}) + +// test Menu item +const tree_data = { + name: 'My Tree', + nodes: [ + { name: 'child 1', iconclass:'fa fa-car'}, + { name: 'child 2' }, + { + name: 'sub tree 1', + nodes: [ + { + name: 'sub sub tree 1', + nodes: [ + { name: 'leaf 1' }, + { name: 'leaf 2' } + ] + }, + { name: 'leaf 3' }, + { name: 'leaf 4' } + ] + } + ] +} +test("Test menu item setter/getter",()=>{ + const item = new OS.GUI.tag.SimpleMenuEntryTag(); + item.uify(); + expect(item.data).toBeUndefined(); + expect(item.switch).toBe(false); + expect(item.radio).toBe(false); + expect(item.checked).toBe(false); + expect(item.nodes).toBeUndefined(); + expect(item.parent).toBeUndefined(); + expect(item.root).toBeUndefined(); + const leaf = tree_data.nodes[0]; + item.data = leaf; + expect(item.data).toBe(leaf); + expect(item.data.nodes).toBeUndefined(); +}) + +test("Test menu item behaviour",()=>{ + +}) + +// test Menu +test("Test menu setter/getter",()=>{ + +}) + +test("Test menu behavior",()=>{ + +}) \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..e32d147 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "es6", + "skipLibCheck": true, + "lib": [ + "es6", "dom", "es2017" + ], + "outDir": "dist" + }, + "include": ["src/**/*.ts"] +} \ No newline at end of file