Switch from coffee script to typescrit

- Better tooling support
- Stronger type checking
- Unit test with jest
- JSDocc support
This commit is contained in:
Xuan Sang LE 2020-05-29 22:22:00 +02:00
parent 759cd1fc6f
commit 1c32f2010c
82 changed files with 14880 additions and 7293 deletions

7
.gitignore vendored
View File

@ -1,3 +1,8 @@
build
node_modules
.DS_Store
.DS_Store
package-lock.json
dist
docs
coffees
.vscode

View File

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

View File

@ -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 :) ).

8
jest.config.js Normal file
View File

@ -0,0 +1,8 @@
module.exports = {
roots: ['<rootDir>'],
transform : {
'^.+\\.ts$': 'ts-jest'
},
testRegex: '(/tests/test.*|(\\.|/)(test|spec))\\.[tj]s?$',
moduleFileExtensions: ['js', 'ts'],
}

View File

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

View File

@ -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 <xsang.le AT gmail DOT com>
// 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;
}
};

292
src/core/Announcerment.ts Normal file
View File

@ -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 <xsang.le AT gmail DOT com>
// 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<ObservableEntryType>;
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;
}
}
}

View File

@ -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 <xsang.le AT gmail DOT com>
// 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;

379
src/core/BaseApplication.ts Normal file
View File

@ -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 <xsang.le AT gmail DOT com>
// 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<any>;
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<any>} promise
* @returns {Promise<any>}
* @memberof BaseApplication
*/
load(promise: Promise<any>): Promise<any> {
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;
}
}

View File

@ -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 <xsang.le AT gmail DOT com>
// 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 = `\
<afx-app-window width='200' height='150' apptitle = "Prompt">
<afx-vbox>
<afx-hbox>
<div data-width = "10" />
<afx-vbox>
<div data-height="10" />
<afx-label data-id = "lbl" />
<input type = "text" data-id= "txtInput" />
<div data-height="10" />
<afx-hbox data-height="30">
<div />
<afx-button data-id = "btnOk" text = "__(Ok)" data-width = "40" />
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" />
</afx-hbox>
</afx-vbox>
<div data-width = "10" />
</afx-hbox>
</afx-vbox>
</afx-app-window>\
`;
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 = `\
<afx-app-window data-id = "TextDialog" width='400' height='300'>
<afx-vbox>
<afx-hbox>
<div data-width = "10" />
<afx-vbox>
<div data-height="10" />
<textarea data-id= "txtInput" />
<div data-height="10" />
<afx-hbox data-height="30">
<div />
<afx-button data-id = "btnOk" text = "__(Ok)" data-width = "40" />
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" />
</afx-hbox>
</afx-vbox>
<div data-width = "10" />
</afx-hbox>
</afx-vbox>
</afx-app-window>\
`;
this.OS.register("TextDialog", TextDialog);
class CalendarDialog extends BasicDialog {
constructor() {
super("CalendarDialog");
}
main() {
super.main();
(this.find("btnOk")).set("onbtclick", e => {
const date = (this.find("cal")).get("selectedDate");
if (!date) { return this.notify(__("Please select a day")); }
if (this.handle) { this.handle(date); }
return this.quit();
});
return (this.find("btnCancel")).set("onbtclick", e => {
return this.quit();
});
}
}
CalendarDialog.scheme = `\
<afx-app-window width='300' height='230' apptitle = "Calendar" >
<afx-vbox>
<afx-hbox>
<div data-width = "10" />
<afx-vbox>
<div data-height="10" />
<afx-calendar-view data-id = "cal" />
<div data-height="10" />
<afx-hbox data-height="30">
<div />
<afx-button data-id = "btnOk" text = "__(Ok)" data-width = "40" />
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" />
</afx-hbox>
<div data-height="10" />
</afx-vbox>
<div data-width = "10" />
</afx-hbox>
</afx-vbox>
</afx-app-window>\
`;
this.OS.register("CalendarDialog", CalendarDialog);
class ColorPickerDialog extends BasicDialog {
constructor() {
super("ColorPickerDialog");
}
main() {
super.main();
(this.find("btnOk")).set("onbtclick", e => {
const color = (this.find("cpicker")).get("selectedColor");
if (!color) { return this.notify(__("Please select color")); }
if (this.handle) { this.handle(color); }
return this.quit();
});
return (this.find("btnCancel")).set("onbtclick", e => {
return this.quit();
});
}
}
ColorPickerDialog.scheme = `\
<afx-app-window width='320' height='250' apptitle = "Color picker" >
<afx-vbox>
<afx-hbox>
<div data-width = "10" />
<afx-vbox>
<div data-height="10" />
<afx-color-picker data-id = "cpicker" />
<div data-height="10" />
<afx-hbox data-height="30">
<div />
<afx-button data-id = "btnOk" text = "__(Ok)" data-width = "40" />
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" />
</afx-hbox>
<div data-height="10" />
</afx-vbox>
<div data-width = "10" />
</afx-hbox>
</afx-vbox>
</afx-app-window>\
`;
this.OS.register("ColorPickerDialog", ColorPickerDialog);
class InfoDialog extends BasicDialog {
constructor() {
super("InfoDialog");
}
main() {
super.main();
const rows = [];
if (this.data && this.data.title) { delete this.data.title; }
for (let k in this.data) { const v = this.data[k]; rows.push([ { text: k }, { text: v } ]); }
(this.find("grid")).set("header", [ { text: __("Name"), width: 70 }, { text: __("Value") } ]);
(this.find("grid")).set("rows", rows);
return (this.find("btnCancel")).set("onbtclick", e => {
return this.quit();
});
}
}
InfoDialog.scheme = `\
<afx-app-window width='250' height='300' apptitle = "Info" >
<afx-vbox>
<afx-hbox>
<div data-width = "10" />
<afx-vbox>
<div data-height="10" />
<afx-grid-view data-id = "grid" />
<div data-height="10" />
<afx-hbox data-height="30">
<div />
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" />
</afx-hbox>
<div data-height="10" />
</afx-vbox>
<div data-width = "10" />
</afx-hbox>
</afx-vbox>
</afx-app-window>\
`;
this.OS.register("InfoDialog", InfoDialog);
class YesNoDialog extends BasicDialog {
constructor() {
super("YesNoDialog");
}
main() {
super.main();
if (this.data) { this.find("lbl").set("*", this.data); }
(this.find("btnYes")).set("onbtclick", e => {
if (this.handle) { this.handle(true); }
return this.quit();
});
return (this.find("btnNo")).set("onbtclick", e => {
if (this.handle) { this.handle(false); }
return this.quit();
});
}
}
YesNoDialog.scheme = `\
<afx-app-window width='200' height='150' apptitle = "Prompt">
<afx-vbox>
<afx-hbox>
<div data-width = "10" />
<afx-vbox>
<div data-height="10" />
<afx-label data-id = "lbl" />
<div data-height="10" />
<afx-hbox data-height="30">
<div />
<afx-button data-id = "btnYes" text = "__(Yes)" data-width = "40" />
<afx-button data-id = "btnNo" text = "__(No)" data-width = "40" />
</afx-hbox>
</afx-vbox>
<div data-width = "10" />
</afx-hbox>
</afx-vbox>
</afx-app-window>\
`;
this.OS.register("YesNoDialog", YesNoDialog);
class SelectionDialog extends BasicDialog {
constructor() {
super("SelectionDialog");
}
main() {
super.main();
if (this.data && this.data.data) { (this.find("list")).set("data", this.data.data); }
const fn = e => {
const data = (this.find("list")).get("selectedItem");
if (!data) { return this.notify(__("Please select an item")); }
if (this.handle) { this.handle(data.get("data")); }
return this.quit();
};
(this.find("list")).set("onlistdbclick", fn);
(this.find("btnOk")).set("onbtclick", fn);
return (this.find("btnCancel")).set("onbtclick", e => {
return this.quit();
});
}
}
SelectionDialog.scheme = `\
<afx-app-window width='250' height='300' apptitle = "Selection">
<afx-vbox>
<afx-hbox>
<div data-width = "10" />
<afx-vbox>
<div data-height="10" />
<afx-list-view data-id = "list" />
<div data-height="10" />
<afx-hbox data-height="30">
<div />
<afx-button data-id = "btnOk" text = "__(Ok)" data-width = "40" />
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" />
</afx-hbox>
</afx-vbox>
<div data-width = "10" />
</afx-hbox>
</afx-vbox>
</afx-app-window>\
`;
this.OS.register("SelectionDialog", SelectionDialog);
class AboutDialog extends BasicDialog {
constructor() {
super("AboutDialog");
}
main() {
super.main();
const mt = this.meta();
this.scheme.set("apptitle", __("About: {0}", mt.name));
(this.find("mylabel")).set("*", {
icon: mt.icon,
iconclass: mt.iconclass,
text: `${mt.name}(v${mt.version})`
});
($(this.find("mydesc"))).html(mt.description);
// grid data for author info
if (!mt.info) { return; }
const rows = [];
for (let k in mt.info) { const v = mt.info[k]; rows.push([ { text: k }, { text: v } ]); }
(this.find("mygrid")).set("header", [ { text: "", width: 100 }, { text: "" } ]);
(this.find("mygrid")).set("rows", rows);
return (this.find("btnCancel")).set("onbtclick", e => {
return this.quit();
});
}
}
AboutDialog.scheme = `\
<afx-app-window data-id = 'about-window' width='300' height='200'>
<afx-vbox>
<div style="text-align:center; margin-top:10px;" data-height="50">
<h3 style = "margin:0;padding:0;">
<afx-label data-id = 'mylabel'></afx-label>
</h3>
<i><p style = "margin:0; padding:0" data-id = 'mydesc'></p></i>
</div>
<afx-hbox>
<div data-width="10"></div>
<afx-grid-view data-id = 'mygrid'></afx-grid-view>
</afx-hbox>
<afx-hbox data-height="30">
<div />
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "60" />
</afx-hbox>
<div data-height = "10"/>
</afx-vbox>
</afx-app-window>\
`;
this.OS.register("AboutDialog", AboutDialog);
class FileDialog extends BasicDialog {
constructor() {
super("FileDialog");
}
main() {
super.main();
const fileview = this.find("fileview");
const location = this.find("location");
const filename = this.find("filename");
fileview.set("fetch", path => new Promise(function(resolve, reject) {
if (!path) { return resolve(); }
return path.asFileHandle().read()
.then(function(d) {
if (d.error) { return reject(d); }
return resolve(d.result);}).catch(e => reject(__e(e)));
}));
const setroot = path => {
return path.asFileHandle().read().then(d => {
if(d.error) {
return this.error(__("Resource not found: {0}", path));
}
return fileview.set("path", path);
});
};
if (!this.data || !this.data.root) {
location.set("onlistselect", function(e) {
if (!e || !e.data.item) { return; }
return setroot(e.data.item.get("data").path);
});
location.set("data", ( Array.from(this.systemsetting.VFS.mountpoints).filter((i) => i.type !== "app") ));
if (location.get("selectedItem") === undefined) { location.set("selected", 0); }
} else {
$(location).hide();
this.trigger("resize");
setroot(this.data.root);
}
fileview.set("onfileselect", function(e) {
if (e.data.type === "file") { return ($(filename)).val(e.data.filename); }
});
(this.find("bt-ok")).set("onbtclick", e => {
const f = fileview.get("selectedFile");
if (!f) { return this.notify(__("Please select a file/fofler")); }
if (this.data && this.data.type && (this.data.type !== f.type)) {
return this.notify(__("Please select {0} only", this.data.type));
}
if (this.data && this.data.mimes) {
//verify the mime
let m = false;
if (f.mime) {
for (let v of Array.from(this.data.mimes)) {
if (f.mime.match((new RegExp(v, "g")))) {
m = true;
break;
}
}
}
if (!m) { return this.notify(__("Only {0} could be selected", this.data.mimes.join(","))); }
}
const name = $(filename).val();
if (this.handle) { this.handle({ file: f, name }); }
return this.quit();
});
(this.find("bt-cancel")).set("onbtclick", e => {
return this.quit();
});
if (this.data && this.data.file) {
($(filename)).css("display", "block").val(this.data.file.basename || "Untitled");
this.trigger("resize");
}
if (this.data && this.data.hidden) { return fileview.set("showhidden", this.data.hidden); }
}
}
FileDialog.scheme = `\
<afx-app-window width='400' height='300'>
<afx-hbox>
<afx-list-view data-id = "location" dropdown = "false" data-width = "120"></afx-list-view>
<afx-vbox>
<afx-file-view data-id = "fileview" view="tree" status = "false"></afx-file-view>
<input data-height = '26' type = "text" data-id = "filename" style="margin-left:5px; margin-right:5px;display:none;" />
<div data-height = '30' style=' text-align:right;padding:3px;'>
<afx-button data-id = "bt-ok" text = "__(Ok)"></afx-button>
<afx-button data-id = "bt-cancel" text = "__(Cancel)"></afx-button>
</div>
</afx-vbox>
</afx-hbox>
</afx-app-window>\
`;
this.OS.register("FileDialog", FileDialog);

949
src/core/BaseDialog.ts Normal file
View File

@ -0,0 +1,949 @@
/*
* 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 <xsang.le AT gmail DOT com>
// 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
* @abstract
* @class SubWindow
* @extends {BaseModel}
*/
export abstract class SubWindow extends BaseModel {
modal: false;
parent: BaseModel;
/**
*Creates an instance of SubWindow.
* @param {string} name
* @memberof SubWindow
*/
constructor(name: string) {
super(name, null);
this.parent = undefined;
this.modal = false;
}
/**
*
*
* @returns {void}
* @memberof SubWindow
*/
quit(): void {
const evt = new BaseEvent("exit", false);
this.onexit(evt);
if (!evt.prevent) {
delete this.observable;
if (this.scheme) {
$(this.scheme).remove();
}
if (this.dialog) {
return this.dialog.quit();
}
}
}
/**
*
*
* @abstract
* @memberof SubWindow
*/
abstract init(): void;
/**
*
*
* @abstract
* @memberof SubWindow
*/
abstract main(): void;
/**
*
*
* @returns {API.PackageMetaType}
* @memberof SubWindow
*/
meta(): API.PackageMetaType {
if (this.parent && this.parent.meta) {
return this.parent.meta();
}
}
/**
*
*
* @memberof SubWindow
*/
show(): void {
this.trigger("focus");
$(this.scheme).css("z-index", GUI.zindex + 2);
}
/**
*
*
* @returns {void}
* @memberof SubWindow
*/
hide(): void {
return this.trigger("hide");
}
}
SubWindow.type = ModelType.SubWindow;
/**
*
*
* @export
* @abstract
* @class BaseDialog
* @extends {SubWindow}
*/
export abstract class BaseDialog extends SubWindow {
handle: (d: any) => void;
data: GenericObject<any>;
title: string;
/**
*Creates an instance of BaseDialog.
* @param {string} name
* @memberof BaseDialog
*/
constructor(name: string) {
super(name);
this.handle = undefined;
}
/**
*
*
* @param {BaseEvent} e
* @returns {void}
* @memberof BaseDialog
*/
onexit(e: BaseEvent): void {
if (this.parent) {
return (this.parent.dialog = undefined);
}
}
}
/**
*
*
* @export
* @class BasicDialog
* @extends {BaseDialog}
*/
export class BasicDialog extends BaseDialog {
markup: string | OS.API.VFS.BaseFileHandle;
static scheme: string;
/**
*Creates an instance of BasicDialog.
* @param {string} name
* @param {(string | OS.API.VFS.BaseFileHandle)} [markup]
* @memberof BasicDialog
*/
constructor(
name: string,
markup?: string | OS.API.VFS.BaseFileHandle
) {
super(name);
this.markup = markup;
}
/**
*
*
* @returns {void}
* @memberof BasicDialog
*/
init(): void {
if (this.markup) {
if (typeof this.markup === "string") {
return GUI.htmlToScheme(
this.markup,
this,
this.host
);
} else {
// a file handle
return this.render(this.markup.path);
}
} else if (
GUI.dialogs[this.name] &&
GUI.dialogs[this.name].scheme
) {
const html: string = GUI.dialogs[this.name].scheme;
return GUI.htmlToScheme(
html.trim(),
this,
this.host
);
}
}
/**
*
*
* @memberof BasicDialog
*/
main(): void {
const win = this.scheme as tag.WindowTag;
if (this.data && this.data.title) {
win.apptitle = this.data.title;
}
win.resizable = false;
win.minimizable = false;
}
}
export namespace dialogs {
/**
*
*
* @export
* @class PromptDialog
* @extends {BasicDialog}
*/
export class PromptDialog extends BasicDialog {
/**
*Creates an instance of PromptDialog.
* @memberof PromptDialog
*/
constructor() {
super("PromptDialog");
}
/**
*
*
* @memberof PromptDialog
*/
main(): void {
super.main();
const $input = $(this.find("txtInput"));
if (this.data && this.data.label) {
(this.find(
"lbl"
) as tag.LabelTag).text = this.data.label;
}
if (this.data && this.data.value) {
$input.val(this.data.value);
}
(this.find("btnOk") as tag.ButtonTag).onbtclick = (e) => {
if (this.handle) {
this.handle($input.val());
}
return this.quit();
};
(this.find("btnCancel") as tag.ButtonTag).onbtclick = (
e
) => {
return this.quit();
};
$input.keyup((e) => {
if (e.which !== 13) {
return;
}
if (this.handle) {
this.handle($input.val());
}
return this.quit();
});
$input.focus();
}
}
PromptDialog.scheme = `\
<afx-app-window width='200' height='150' apptitle = "Prompt">
<afx-vbox>
<afx-hbox>
<div data-width = "10" />
<afx-vbox>
<div data-height="10" />
<afx-label data-id = "lbl" />
<input type = "text" data-id= "txtInput" />
<div data-height="10" />
<afx-hbox data-height="30">
<div />
<afx-button data-id = "btnOk" text = "__(Ok)" data-width = "40" />
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" />
</afx-hbox>
</afx-vbox>
<div data-width = "10" />
</afx-hbox>
</afx-vbox>
</afx-app-window>\
`;
/**
*
*
* @export
* @class TextDialog
* @extends {BasicDialog}
*/
export class TextDialog extends BasicDialog {
/**
*Creates an instance of TextDialog.
* @memberof TextDialog
*/
constructor() {
super("TextDialog");
}
/**
*
*
* @memberof TextDialog
*/
main(): void {
super.main();
const $input = $(this.find("txtInput"));
if (this.data && this.data.value) {
$input.val(this.data.value);
}
(this.find("btnOk") as tag.ButtonTag).onbtclick = (e) => {
const value = $input.val();
if (!value || value === "") {
return;
}
if (this.handle) {
this.handle(value);
}
return this.quit();
};
(this.find("btnCancel") as tag.ButtonTag).onbtclick = (
e
): void => {
return this.quit();
};
$input.focus();
}
}
TextDialog.scheme = `\
<afx-app-window data-id = "TextDialog" width='400' height='300'>
<afx-vbox>
<afx-hbox>
<div data-width = "10" />
<afx-vbox>
<div data-height="10" />
<textarea data-id= "txtInput" />
<div data-height="10" />
<afx-hbox data-height="30">
<div />
<afx-button data-id = "btnOk" text = "__(Ok)" data-width = "40" />
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" />
</afx-hbox>
</afx-vbox>
<div data-width = "10" />
</afx-hbox>
</afx-vbox>
</afx-app-window>\
`;
/**
*
*
* @export
* @class CalendarDialog
* @extends {BasicDialog}
*/
export class CalendarDialog extends BasicDialog {
/**
*Creates an instance of CalendarDialog.
* @memberof CalendarDialog
*/
constructor() {
super("CalendarDialog");
}
/**
*
*
* @memberof CalendarDialog
*/
main(): void {
super.main();
(this.find("btnOk") as tag.ButtonTag).onbtclick = (
e
): void => {
const date = (this.find("cal") as tag.CalendarTag)
.selectedDate;
if (!date) {
return this.notify(__("Please select a day"));
}
if (this.handle) {
this.handle(date);
}
return this.quit();
};
(this.find("btnCancel") as tag.ButtonTag).onbtclick = (
e
): void => {
return this.quit();
};
}
}
CalendarDialog.scheme = `\
<afx-app-window width='300' height='230' apptitle = "Calendar" >
<afx-vbox>
<afx-hbox>
<div data-width = "10" />
<afx-vbox>
<div data-height="10" />
<afx-calendar-view data-id = "cal" />
<div data-height="10" />
<afx-hbox data-height="30">
<div />
<afx-button data-id = "btnOk" text = "__(Ok)" data-width = "40" />
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" />
</afx-hbox>
<div data-height="10" />
</afx-vbox>
<div data-width = "10" />
</afx-hbox>
</afx-vbox>
</afx-app-window>\
`;
/**
*
*
* @export
* @class ColorPickerDialog
* @extends {BasicDialog}
*/
export class ColorPickerDialog extends BasicDialog {
/**
*Creates an instance of ColorPickerDialog.
* @memberof ColorPickerDialog
*/
constructor() {
super("ColorPickerDialog");
}
/**
*
*
* @memberof ColorPickerDialog
*/
main(): void {
super.main();
(this.find("btnOk") as tag.ButtonTag).onbtclick = (
e
): void => {
const color = (this.find(
"cpicker"
) as tag.ColorPickerTag).selectedColor;
if (!color) {
return this.notify(__("Please select color"));
}
if (this.handle) {
this.handle(color);
}
return this.quit();
};
(this.find("btnCancel") as tag.ButtonTag).onbtclick = (
e
): void => {
return this.quit();
};
}
}
ColorPickerDialog.scheme = `\
<afx-app-window width='320' height='250' apptitle = "Color picker" >
<afx-vbox>
<afx-hbox>
<div data-width = "10" />
<afx-vbox>
<div data-height="10" />
<afx-color-picker data-id = "cpicker" />
<div data-height="10" />
<afx-hbox data-height="30">
<div />
<afx-button data-id = "btnOk" text = "__(Ok)" data-width = "40" />
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" />
</afx-hbox>
<div data-height="10" />
</afx-vbox>
<div data-width = "10" />
</afx-hbox>
</afx-vbox>
</afx-app-window>\
`;
/**
*
*
* @export
* @class InfoDialog
* @extends {BasicDialog}
*/
export class InfoDialog extends BasicDialog {
/**
*Creates an instance of InfoDialog.
* @memberof InfoDialog
*/
constructor() {
super("InfoDialog");
}
/**
*
*
* @memberof InfoDialog
*/
main(): void {
super.main();
const rows = [];
if (this.data && this.data.title) {
delete this.data.title;
}
for (let k in this.data) {
const v = this.data[k];
rows.push([{ text: k }, { text: v }]);
}
const grid = this.find("grid") as tag.GridViewTag;
grid.header = [
{ text: __("Name"), width: 70 },
{ text: __("Value") },
];
grid.rows = rows;
(this.find("btnCancel") as tag.ButtonTag).onbtclick = (
e
): void => {
return this.quit();
};
}
}
InfoDialog.scheme = `\
<afx-app-window width='250' height='300' apptitle = "Info" >
<afx-vbox>
<afx-hbox>
<div data-width = "10" />
<afx-vbox>
<div data-height="10" />
<afx-grid-view data-id = "grid" />
<div data-height="10" />
<afx-hbox data-height="30">
<div />
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" />
</afx-hbox>
<div data-height="10" />
</afx-vbox>
<div data-width = "10" />
</afx-hbox>
</afx-vbox>
</afx-app-window>\
`;
export class YesNoDialog extends BasicDialog {
/**
*Creates an instance of YesNoDialog.
* @memberof YesNoDialog
*/
constructor() {
super("YesNoDialog");
}
/**
*
*
* @memberof YesNoDialog
*/
main(): void {
super.main();
if (this.data) {
(this.find("lbl") as tag.LabelTag).set(this.data);
}
(this.find("btnYes") as tag.ButtonTag).onbtclick = (
e
): void => {
if (this.handle) {
this.handle(true);
}
return this.quit();
};
(this.find("btnNo") as tag.ButtonTag).onbtclick = (
e
): void => {
if (this.handle) {
this.handle(false);
}
return this.quit();
};
}
}
YesNoDialog.scheme = `\
<afx-app-window width='200' height='150' apptitle = "Prompt">
<afx-vbox>
<afx-hbox>
<div data-width = "10" />
<afx-vbox>
<div data-height="10" />
<afx-label data-id = "lbl" />
<div data-height="10" />
<afx-hbox data-height="30">
<div />
<afx-button data-id = "btnYes" text = "__(Yes)" data-width = "40" />
<afx-button data-id = "btnNo" text = "__(No)" data-width = "40" />
</afx-hbox>
</afx-vbox>
<div data-width = "10" />
</afx-hbox>
</afx-vbox>
</afx-app-window>\
`;
/**
*
*
* @export
* @class SelectionDialog
* @extends {BasicDialog}
*/
export class SelectionDialog extends BasicDialog {
/**
*Creates an instance of SelectionDialog.
* @memberof SelectionDialog
*/
constructor() {
super("SelectionDialog");
}
/**
*
*
* @memberof SelectionDialog
*/
main(): void {
super.main();
const listview = this.find("list") as tag.ListViewTag;
if (this.data && this.data.data) {
listview.data = this.data.data;
}
const fn = (e: TagEventType) => {
const data = listview.selectedItem;
if (!data) {
return this.notify(__("Please select an item"));
}
if (this.handle) {
this.handle(data.data);
}
return this.quit();
};
listview.onlistdbclick = fn;
(this.find("btnOk") as tag.ButtonTag).onbtclick = fn;
(this.find("btnCancel") as tag.ButtonTag).onbtclick = (
e
): void => {
return this.quit();
};
}
}
SelectionDialog.scheme = `\
<afx-app-window width='250' height='300' apptitle = "Selection">
<afx-vbox>
<afx-hbox>
<div data-width = "10" />
<afx-vbox>
<div data-height="10" />
<afx-list-view data-id = "list" />
<div data-height="10" />
<afx-hbox data-height="30">
<div />
<afx-button data-id = "btnOk" text = "__(Ok)" data-width = "40" />
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" />
</afx-hbox>
</afx-vbox>
<div data-width = "10" />
</afx-hbox>
</afx-vbox>
</afx-app-window>\
`;
/**
*
*
* @export
* @class AboutDialog
* @extends {BasicDialog}
*/
export class AboutDialog extends BasicDialog {
/**
*Creates an instance of AboutDialog.
* @memberof AboutDialog
*/
constructor() {
super("AboutDialog");
}
/**
*
*
* @returns {void}
* @memberof AboutDialog
*/
main(): void {
super.main();
const mt = this.meta();
(this.scheme as tag.WindowTag).apptitle = __(
"About: {0}",
mt.name
);
(this.find("mylabel") as tag.LabelTag).set({
icon: mt.icon,
iconclass: mt.iconclass,
text: `${mt.name}(v${mt.version})`,
});
$(this.find("mydesc")).html(mt.description);
// grid data for author info
if (!mt.info) {
return;
}
const rows = [];
for (let k in mt.info) {
const v = mt.info[k];
rows.push([{ text: k }, { text: v }]);
}
const grid = this.find("mygrid") as tag.GridViewTag;
grid.header = [{ text: "", width: 100 }, { text: "" }];
grid.rows = rows;
(this.find("btnCancel") as tag.ButtonTag).onbtclick = (
e
): void => {
return this.quit();
};
}
}
AboutDialog.scheme = `\
<afx-app-window data-id = 'about-window' width='300' height='200'>
<afx-vbox>
<div style="text-align:center; margin-top:10px;" data-height="50">
<h3 style = "margin:0;padding:0;">
<afx-label data-id = 'mylabel'></afx-label>
</h3>
<i><p style = "margin:0; padding:0" data-id = 'mydesc'></p></i>
</div>
<afx-hbox>
<div data-width="10"></div>
<afx-grid-view data-id = 'mygrid'></afx-grid-view>
</afx-hbox>
<afx-hbox data-height="30">
<div />
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "60" />
</afx-hbox>
<div data-height = "10"/>
</afx-vbox>
</afx-app-window>\
`;
/**
*
*
* @export
* @class FileDialog
* @extends {BasicDialog}
*/
export class FileDialog extends BasicDialog {
/**
*Creates an instance of FileDialog.
* @memberof FileDialog
*/
constructor() {
super("FileDialog");
}
/**
*
*
* @returns {void}
* @memberof FileDialog
*/
main(): void {
super.main();
const fileview = this.find("fileview") as tag.FileViewTag;
const location = this.find("location") as tag.ListViewTag;
const filename = this.find("filename") as HTMLInputElement;
fileview.fetch = (path: string) =>
new Promise(function (resolve, reject) {
if (!path) {
return resolve();
}
return path
.asFileHandle()
.read()
.then(function (d) {
if (d.error) {
return reject(d);
}
return resolve(d.result);
})
.catch((e: Error): void => reject(__e(e)));
});
const setroot = async (path: string) => {
const d = await path.asFileHandle().read();
if (d.error) {
return this.error(
__("Resource not found: {0}", path)
);
}
return (fileview.path = path);
};
if (!this.data || !this.data.root) {
location.onlistselect = function (e): void {
if (!e || !e.data.item) {
return;
}
setroot(e.data.item.get("data").path);
};
location.data = this.systemsetting.VFS.mountpoints.filter(
(i) => i.type !== "app"
);
if (location.selectedItem === undefined) {
location.selected = 0;
}
} else {
$(location).hide();
this.trigger("resize");
setroot(this.data.root);
}
fileview.onfileselect = function (e) {
if (e.data.type === "file") {
return $(filename).val(e.data.filename);
}
};
(this.find("bt-ok") as tag.ButtonTag).onbtclick = (e) => {
const f = fileview.selectedFile;
if (!f) {
return this.notify(
__("Please select a file/fofler")
);
}
if (
this.data &&
this.data.type &&
this.data.type !== f.type
) {
return this.notify(
__("Please select {0} only", this.data.type)
);
}
if (this.data && this.data.mimes) {
//verify the mime
let m = false;
if (f.mime) {
for (let v of Array.from(this.data.mimes)) {
if (
f.mime.match(
new RegExp(v as string, "g")
)
) {
m = true;
break;
}
}
}
if (!m) {
return this.notify(
__(
"Only {0} could be selected",
this.data.mimes.join(",")
)
);
}
}
const name = $(filename).val();
if (this.handle) {
this.handle({ file: f, name });
}
return this.quit();
};
(this.find("bt-cancel") as tag.ButtonTag).onbtclick = (
e
) => {
return this.quit();
};
if (this.data && this.data.file) {
$(filename)
.css("display", "block")
.val(this.data.file.basename || "Untitled");
this.trigger("resize");
}
if (this.data && this.data.hidden) {
return (fileview.showhidden = this.data.hidden);
}
}
}
FileDialog.scheme = `\
<afx-app-window width='400' height='300'>
<afx-hbox>
<afx-list-view data-id = "location" dropdown = "false" data-width = "120"></afx-list-view>
<afx-vbox>
<afx-file-view data-id = "fileview" view="tree" status = "false"></afx-file-view>
<input data-height = '26' type = "text" data-id = "filename" style="margin-left:5px; margin-right:5px;display:none;" />
<div data-height = '30' style=' text-align:right;padding:3px;'>
<afx-button data-id = "bt-ok" text = "__(Ok)"></afx-button>
<afx-button data-id = "bt-cancel" text = "__(Cancel)"></afx-button>
</div>
</afx-vbox>
</afx-hbox>
</afx-app-window>\
`;
}
}
}

View File

@ -1,35 +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 <xsang.le AT gmail DOT com>
// 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 BaseEvent {
constructor(name, force) {
this.name = name;
this.force = force;
this.prevent = false;
}
preventDefault() {
if (!this.force) { return this.prevent = true; }
}
}
this.OS.GUI.BaseEvent = BaseEvent;

View File

@ -1,160 +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 <xsang.le AT gmail DOT com>
// 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 BaseModel {
constructor(name, args) {
this.name = name;
this.args = args;
this.observable = new Announcer();
this._api = Ant.OS.API;
this._gui = Ant.OS.GUI;
this.systemsetting = Ant.OS.setting;
this.on("exit", () => this.quit());
this.host = this._gui.workspace;
this.dialog = undefined;
}
render(p) {
return Ant.OS.GUI.loadScheme(p, this, this.host);
}
quit(force) {
const evt = new Ant.OS.GUI.BaseEvent("exit", force);
this.onexit(evt);
if (!evt.prevent) {
this.observable.off("*");
delete this.observable;
if (this.dialog) { this.dialog.quit(); }
return Ant.OS.PM.kill(this);
}
}
path() {
const mt = this.meta();
if (mt && mt.path) { return mt.path; }
return null;
}
// call a server side script
call(cmd, func) {
return this._api.apigateway(cmd, false, func);
}
// get a stream
stream() {
return this._api.apigateway(null, true, null);
}
init() {}
//implement by sub class
onexit(e) {}
//implement by subclass
one(e, f) { return this.observable.one(e, f); }
on(e, f) { return this.observable.on(e, f); }
off(e, f) {
if (!f) { return this.observable.off(e); }
return this.observable.off(e, f);
}
trigger(e, d) { return this.observable.trigger(e, d); }
subscribe(e, f) {
return Ant.OS.announcer.on(e, f, this);
}
openDialog(d, data) {
return new Promise((resolve, reject) => {
if (this.dialog) {
this.dialog.show();
return;
}
if (typeof d === "string") {
if (!Ant.OS.GUI.subwindows[d]) {
this.error(__("Dialog {0} not found", d));
return;
}
this.dialog = new (Ant.OS.GUI.subwindows[d])();
} else {
this.dialog = d;
}
//@dialog.observable = riot.observable() unless @dialog
this.dialog.parent = this;
this.dialog.handle = resolve;
this.dialog.reject = reject;
this.dialog.pid = this.pid;
this.dialog.data = data;
if (data && data.title) { this.dialog.title = data.title; }
return this.dialog.init();
});
}
ask(data) {
return this._gui.openDialog("YesNoDialog", data);
}
publish(t, m, e) {
const mt = this.meta();
let icon = undefined;
if (mt.icon) { icon = `${mt.path}/${mt.icon}`; }
return Ant.OS.announcer.trigger(t, {
id: this.pid,
name: this.name,
data: {
m,
icon,
iconclass: mt.iconclass,
e
}
});
}
notify(m) {
return this.publish("notification", m);
}
warn(m) {
return this.publish("warning", m);
}
error(m, e) {
return this.publish("error", m, e ? e : (this._api.throwe(m)));
}
fail(m) {
return this.publish("fail", m);
}
throwe() {
return this._api.throwe(this.name);
}
update() {
if (this.scheme) { return this.scheme.update(); }
}
find(id) { if (this.scheme) { return ($(`[data-id='${id}']`, this.scheme))[0]; } }
select(sel) { if (this.scheme) { return $(sel, this.scheme); } }
}
this.OS.GUI.BaseModel = BaseModel;

478
src/core/BaseModel.ts Normal file
View File

@ -0,0 +1,478 @@
/*
* 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 <xsang.le AT gmail DOT com>
// 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
* @interface AppArgumentsType
*/
export interface AppArgumentsType {
type?: string;
path: string;
[propName: string]: any;
}
/**
*
*
* @export
* @enum {number}
*/
export enum ModelType {
Application,
Service,
SubWindow
};
/**
*
*
* @export
* @class BaseEvent
*/
export class BaseEvent {
name: string;
private force: boolean;
prevent: boolean;
/**
*Creates an instance of BaseEvent.
* @param {string} name
* @param {boolean} force
* @memberof BaseEvent
*/
constructor(name: string, force: boolean) {
this.name = name;
this.force = force;
this.prevent = false;
}
/**
*
*
* @memberof BaseEvent
*/
preventDefault(): void {
if (!this.force) {
this.prevent = true;
}
}
}
/**
*
*
* @export
* @abstract
* @class BaseModel
*/
export abstract class BaseModel {
name: string;
args: AppArgumentsType[];
observable: API.Announcer;
_api: typeof API;
_gui: typeof GUI;
dialog: GUI.BaseDialog;
host: string;
pid: number;
scheme: HTMLElement;
systemsetting: typeof setting;
birth: number;
static type: ModelType;
static singleton: boolean;
static dependencies: string[];
static style: HTMLElement | string;
static meta: API.PackageMetaType;
/**
*Creates an instance of BaseModel.
* @param {string} name
* @param {AppArgumentsType[]} args
* @memberof BaseModel
*/
constructor(name: string, args: AppArgumentsType[]) {
this.name = name;
this.args = args;
this.observable = new API.Announcer();
this._api = API;
this._gui = GUI;
this.systemsetting = setting;
this.on("exit", () => this.quit(false));
this.host = this._gui.workspace;
this.dialog = undefined;
}
/**
*
*
* @param {string} p
* @returns {void}
* @memberof BaseModel
*/
render(p: string): void {
return GUI.loadScheme(p, this, this.host);
}
/**
*
*
* @param {boolean} force
* @returns {void}
* @memberof BaseModel
*/
quit(force: boolean): void {
const evt = new BaseEvent("exit", force);
this.onexit(evt);
if (!evt.prevent) {
this.observable.off("*");
delete this.observable;
if (this.dialog) {
this.dialog.quit();
}
return PM.kill(this);
}
}
/**
*
*
* @abstract
* @returns {API.PackageMetaType}
* @memberof BaseModel
*/
abstract meta(): API.PackageMetaType;
/**
*
*
* @returns {string}
* @memberof BaseModel
*/
path(): string {
const mt = this.meta();
if (mt && mt.path) {
return mt.path;
}
return null;
}
// call a server side script
/**
*
*
* @param {GenericObject<any>} cmd
* @returns {Promise<any>}
* @memberof BaseModel
*/
call(cmd: GenericObject<any>): Promise<any> {
return this._api.apigateway(cmd, false);
}
// get a stream
/**
*
*
* @returns {Promise<WebSocket>}
* @memberof BaseModel
*/
stream(): Promise<WebSocket> {
return this._api.apigateway(null, true) as Promise<WebSocket>;
}
/**
*
*
* @abstract
* @memberof BaseModel
*/
abstract init(): void;
/**
*
*
* @abstract
* @memberof BaseModel
*/
abstract main(): void;
/**
*
*
* @abstract
* @memberof BaseModel
*/
abstract show(): void;
/**
*
*
* @abstract
* @memberof BaseModel
*/
abstract hide(): void;
//implement by sub class
/**
*
*
* @abstract
* @param {BaseEvent} e
* @memberof BaseModel
*/
abstract onexit(e: BaseEvent): void;
//implement by subclass
/**
*
*
* @param {string} e
* @param {(d: any) => void} f
* @returns {void}
* @memberof BaseModel
*/
one(e: string, f: (d: any) => void): void {
return this.observable.one(e, f);
}
/**
*
*
* @param {string} e
* @param {(d: any) => void} f
* @returns {void}
* @memberof BaseModel
*/
on(e: string, f: (d: any) => void): void {
return this.observable.on(e, f);
}
/**
*
*
* @param {string} e
* @param {(d: any) => void} [f]
* @returns {void}
* @memberof BaseModel
*/
off(e: string, f?: (d: any) => void): void {
if (!f) {
return this.observable.off(e);
}
return this.observable.off(e, f);
}
/**
*
*
* @param {string} e
* @param {*} [d]
* @returns {void}
* @memberof BaseModel
*/
trigger(e: string, d?: any): void {
return this.observable.trigger(e, d);
}
/**
*
*
* @param {string} e
* @param {(d: any) => void} f
* @returns {void}
* @memberof BaseModel
*/
subscribe(e: string, f: (d: any) => void): void {
return announcer.on(e, f, this);
}
/**
*
*
* @param {(BaseDialog | string)} d
* @param {GenericObject<any>} [data]
* @returns {Promise<any>}
* @memberof BaseModel
*/
openDialog(
d: GUI.BaseDialog | string,
data?: GenericObject<any>
): Promise<any> {
return new Promise((resolve, reject) => {
if (this.dialog) {
this.dialog.show();
return;
}
if (typeof d === "string") {
if (!GUI.dialogs[d]) {
this.error(__("Dialog {0} not found", d));
return;
}
this.dialog = new OS.GUI.dialogs[d as string]();
} else {
this.dialog = d;
}
//@dialog.observable = riot.observable() unless @dialog
this.dialog.parent = this;
this.dialog.handle = resolve;
this.dialog.pid = this.pid;
this.dialog.data = data;
if (data && data.title) {
this.dialog.title = data.title;
}
return this.dialog.init();
});
}
/**
*
*
* @param {GenericObject<any>} data
* @returns {Promise<any>}
* @memberof BaseModel
*/
ask(data: GenericObject<any>): Promise<any> {
return this._gui.openDialog("YesNoDialog", data);
}
/**
*
*
* @param {string} t
* @param {(string | FormatedString)} m
* @param {Error} [e]
* @returns {void}
* @memberof BaseModel
*/
publish(t: string, m: string | FormatedString, e?: Error): void {
const mt = this.meta();
let icon: string = undefined;
if (mt.icon) {
icon = `${mt.path}/${mt.icon}`;
}
return announcer.trigger(t, {
id: this.pid,
name: this.name,
data: {
m: m,
icon: icon,
iconclass: mt.iconclass,
e: e,
},
});
}
/**
*
*
* @param {(string | FormatedString)} m
* @returns {void}
* @memberof BaseModel
*/
notify(m: string | FormatedString): void {
return this.publish("notification", m);
}
/**
*
*
* @param {(string | FormatedString)} m
* @returns {void}
* @memberof BaseModel
*/
warn(m: string | FormatedString): void {
return this.publish("warning", m);
}
/**
*
*
* @param {(string | FormatedString)} m
* @param {Error} [e]
* @returns
* @memberof BaseModel
*/
error(m: string | FormatedString, e?: Error) {
return this.publish("error", m, e ? e : this._api.throwe(m));
}
/**
*
*
* @param {string} m
* @param {Error} [e]
* @returns
* @memberof BaseModel
*/
fail(m: string, e?: Error) {
return this.publish("fail", m, e ? e : this._api.throwe(m));
}
/**
*
*
* @returns {Error}
* @memberof BaseModel
*/
throwe(): Error {
return this._api.throwe(this.name);
}
/**
*
*
* @returns {void}
* @memberof BaseModel
*/
update(): void {
if (this.scheme) {
return this.scheme.update();
}
}
/**
*
*
* @param {string} id
* @returns {HTMLElement}
* @memberof BaseModel
*/
find(id: string): HTMLElement {
if (this.scheme) {
return $(`[data-id='${id}']`, this.scheme)[0];
}
}
/**
*
*
* @param {string} sel
* @returns {HTMLElement}
* @memberof BaseModel
*/
select(sel: string): HTMLElement {
if (this.scheme) {
return $(sel, this.scheme)[0];
}
}
}
}

View File

@ -1,77 +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 <xsang.le AT gmail DOT com>
// 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 BaseService extends this.OS.GUI.BaseModel {
constructor(name, args) {
super(name, args);
this.icon = undefined;
this.iconclass = "fa-paper-plane-o";
this.text = "";
this.timer = undefined;
this.holder = undefined;
this.onmenuselect = d => {
return this.awake(d);
};
}
init(){}
//implement by user
// event registe, etc
// scheme loader
update() {
return this.domel.set("data", this);
}
meta() {
return Ant.OS.APP[this.name].meta;
}
attach(h) {
return this.holder = h;
}
watch( t, f) {
var func = () => {
f();
return this.timer = setTimeout((() => func()), t);
};
return func();
}
onexit(evt) {
if (this.timer) { console.log("clean timer"); }
if (this.timer) { clearTimeout(this.timer); }
this.cleanup(evt);
if (this.scheme) { return ($(this.scheme)).remove(); }
}
main() {}
show() {}
awake(e) {}
//implement by user to tart the service
cleanup(evt) {}
}
//implemeted by user
BaseService.type = 2;
BaseService.singleton = true;
this.OS.GUI.BaseService = BaseService;

180
src/core/BaseService.ts Normal file
View File

@ -0,0 +1,180 @@
/*
* 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 <xsang.le AT gmail DOT com>
// 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 BaseService
* @extends {BaseModel}
*/
export abstract class BaseService extends BaseModel {
icon: string;
iconclass: string;
text: string;
domel: HTMLElement;
private timer: number;
holder: HTMLElement;
onmenuselect: (d: OS.GUI.TagEventType) => void;
/**
*Creates an instance of BaseService.
* @param {string} name
* @param {AppArgumentsType[]} args
* @memberof BaseService
*/
constructor(name: string, args: AppArgumentsType[]) {
super(name, args);
this.icon = undefined;
this.iconclass = "fa-paper-plane-o";
this.text = "";
this.timer = undefined;
this.holder = undefined;
this.onmenuselect = (d) => {
return this.awake(d);
};
}
/**
*
*
* @memberof BaseService
*/
hide(): void {}
/**
*
*
* @abstract
* @memberof BaseService
*/
abstract init(): void;
//implement by user
// event registe, etc
// scheme loader
/**
*
*
* @memberof BaseService
*/
update(): void {
(this.domel as GUI.tag.MenuEntryTag).data = this;
}
/**
*
*
* @returns {API.PackageMetaType}
* @memberof BaseService
*/
meta(): API.PackageMetaType {
return application[this.name].meta;
}
/**
*
*
* @param {HTMLElement} h
* @memberof BaseService
*/
attach(h: HTMLElement): void {
this.holder = h;
}
/**
*
*
* @param {number} t
* @param {() => void} f
* @returns {number}
* @memberof BaseService
*/
watch(t: number, f: () => void): number {
var func = () => {
f();
return (this.timer = setTimeout(() => func(), t));
};
return func();
}
/**
*
*
* @param {BaseEvent} evt
* @returns
* @memberof BaseService
*/
onexit(evt: BaseEvent) {
if (this.timer) {
console.log("clean timer");
}
if (this.timer) {
clearTimeout(this.timer);
}
this.cleanup(evt);
if (this.scheme) {
return $(this.scheme).remove();
}
}
/**
*
*
* @memberof BaseService
*/
main(): void {}
/**
*
*
* @memberof BaseService
*/
show(): void {}
/**
*
*
* @abstract
* @param {GUI.TagEventType} e
* @memberof BaseService
*/
abstract awake(e: GUI.TagEventType): void;
//implement by user to tart the service
/**
*
*
* @param {BaseEvent} evt
* @memberof BaseService
*/
cleanup(evt: BaseEvent) {}
}
//implemeted by user
BaseService.type = ModelType.Service;
BaseService.singleton = true;
}
}

View File

@ -1,715 +0,0 @@
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS202: Simplify dynamic range loops
* 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 <xsang.le AT gmail DOT com>
// 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/.
'use strict';
const Ant = this;
class FormatedString {
constructor(fs, args) {
this.fs = fs;
this.values = [];
if (!args) { return; }
for (let i = 0, end = args.length - 1, asc = 0 <= end; asc ? i <= end : i >= end; asc ? i++ : i--) { this.values[i] = args[i]; }
}
toString() {
return this.__();
}
__() {
return this.fs.l().replace(/{(\d+)}/g, (match, number) => {
if (typeof this.values[number] !== 'undefined') { return this.values[number].__(); } else { return match; }
});
}
hash() {
return this.__().hash();
}
match(t) {
return this.__().match(t);
}
asBase64() {
return this.__().asBase64();
}
unescape() {
return this.__().unescape();
}
asUint8Array() {
return this.__().asUint8Array();
}
format() {
const args = arguments;
return __range__(0, args.length - 1, true).map((i) => (this.values[i] = args[i]));
}
}
class Version {
constructor(string) {
this.string = string;
const arr = this.string.split("-");
const br = {
"r": 3,
"b": 2,
"a": 1
};
this.branch = 3;
if ((arr.length === 2) && br[arr[1]]) { this.branch = br[arr[1]]; }
const mt = arr[0].match(/\d+/g);
if (!mt) { throw new Error(__("Version string is in invalid format: {0}", this.string)); }
this.major = 0;
this.minor = 0;
this.patch = 0;
if (mt.length >= 1) { this.major = Number(mt[0]); }
if (mt.length >= 2) { this.minor = Number(mt[1]); }
if (mt.length >= 3) { this.patch = Number(mt[2]); }
}
// this function return
// 0 if the version is unchanged
// 1 if the current version is newer
// -1 if the current version is older
compare(o) {
const other = o.__v();
if (this.branch > other.branch) { return 1; }
if (this.branch < other.branch) { return -1; }
if ((this.major === other.major) && (this.minor === other.minor) && (this.patch === other.patch)) { return 0; }
if (this.major > other.major) { return 1; }
if (this.major < other.major) { return -1; }
if (this.minor > other.minor) { return 1; }
if (this.minor < other.minor) { return -1; }
if (this.patch > other.patch) { return 1; }
return -1;
}
nt(o) {
return (this.compare(o)) === 1;
}
ot(o) {
return (this.compare(o)) === -1;
}
__v() { return this; }
toString() { return this.string; }
}
Object.defineProperty(Object.prototype, '__', {
value() {
return this.toString();
},
enumerable: false,
writable: true
}
);
String.prototype.hash = function() {
let hash = 5381;
let i = this.length;
while (i) { hash = (hash * 33) ^ this.charCodeAt(--i); }
return hash >>> 0;
};
String.prototype.__v = function() {
return new Version(this);
};
String.prototype.asBase64 = function() {
const tmp = encodeURIComponent(this);
return btoa(( tmp.replace(/%([0-9A-F]{2})/g, (match, p1) => String.fromCharCode((parseInt(p1, 16)))))
);
};
String.prototype.unescape = function() {
let d = this;
d = d.replace(/\\\\/g, "\\");
d = d.replace(/\\"/g, '"');
d = d.replace(/\\n/g, "\n");
d = d.replace(/\\t/g, "\t");
d = d.replace(/\\b/g, "\b");
d = d.replace(/\\f/g, "\f");
d = d.replace(/\\r/g, "\r");
return d;
};
String.prototype.asUint8Array = function() {
let bytes = [];
for (let i = 0, end = this.length - 1, asc = 0 <= end; asc ? i <= end : i >= end; asc ? i++ : i--) {
bytes.push(this.charCodeAt(i));
}
bytes = new Uint8Array(bytes);
return bytes;
};
if (!String.prototype.format) {
String.prototype.format = function() {
const args = arguments;
return this.replace(/{(\d+)}/g, function(match, number) {
if (typeof args[number] !== 'undefined') { return args[number].__(); } else { return match; }
});
};
}
String.prototype.f = function() {
const args = arguments;
return new FormatedString(this, args);
};
String.prototype.__ = function() {
const match = this.match(/^__\((.*)\)$/);
if (match) { return match[1].l(); }
return this;
};
String.prototype.l = function() {
if (!Ant.OS.API.lang[this]) { Ant.OS.API.lang[this] = this; }
return Ant.OS.API.lang[this];
};
// language directive
this.__ = function() {
const args = arguments;
if (!(args.length > 0)) { return "Undefined"; }
const d = args[0];
d.l();
return new FormatedString(d, (__range__(1, args.length - 1, true).map((i) => args[i])));
};
Date.prototype.toString = function() {
let dd = this.getDate();
let mm = this.getMonth() + 1;
const yyyy = this.getFullYear();
let hh = this.getHours();
let mi = this.getMinutes();
let se = this.getSeconds();
if (dd < 10) { dd = `0${dd}`; }
if (mm < 10) { mm = `0${mm}`; }
if (hh < 10) { hh = `0${hh}`; }
if (mi < 10) { mi = `0${mi}`; }
if (se < 10) { se = `0${se}`; }
return `${dd}/${mm}/${yyyy} ${hh}:${mi}:${se}`;
};
Date.prototype.timestamp = function() {
return (this.getTime() / 1000) | 0;
};
// chaning error
this.__e = function(e) {
const reason = new Error(e.toString());
reason.stack += "\nCaused By:\n" + e.stack;
return reason;
};
//define the OS object
if (!Ant.OS) { Ant.OS = {
API: {},
GUI: {},
APP: {},
setting: {
user: {},
applications: {},
desktop: {},
appearance: {},
VFS: {},
system: {}
},
register(name, x) {
if (x.type === 3) { return Ant.OS.GUI.subwindows[name] = x; } else { return Ant.OS.APP[name] = x; }
},
// import proprety from an App
PM: {
pidalloc: 0,
processes: {},
createProcess(app, cls, args) {
return new Promise(function(resolve, reject) {
const f = function() {
//if it is single ton
// and a process is existing
// just return it
let obj;
if (cls.singleton && Ant.OS.PM.processes[app] && (Ant.OS.PM.processes[app].length === 1)) {
obj = Ant.OS.PM.processes[app][0];
obj.show();
} else {
if (!Ant.OS.PM.processes[app]) { Ant.OS.PM.processes[app] = []; }
obj = new cls(args);
obj.birth = (new Date).getTime();
Ant.OS.PM.pidalloc++;
obj.pid = Ant.OS.PM.pidalloc;
Ant.OS.PM.processes[app].push(obj);
if (cls.type === 1) { Ant.OS.GUI.dock(obj, cls.meta); } else { Ant.OS.GUI.attachservice(obj); }
}
return obj;
};
if (cls.dependencies) {
const libs = (Array.from(cls.dependencies));
return Ant.OS.API.require(libs)
.then(() => resolve(f())).catch(e => reject(__e(e)));
} else {
return resolve(f());
}
});
},
appByPid(pid) {
let app = undefined;
const find = function(l) {
for (let a of Array.from(l)) { if (a.pid === pid) { return a; } }
};
for (let k in Ant.OS.PM.processes) {
const v = Ant.OS.PM.processes[k];
app = find(v);
if (app) { break; }
}
return app;
},
kill(app) {
if (!app.name || !Ant.OS.PM.processes[app.name]) { return; }
const i = Ant.OS.PM.processes[app.name].indexOf(app);
if (i >= 0) {
if (Ant.OS.APP[app.name].type === 1) { Ant.OS.GUI.undock(app); } else { Ant.OS.GUI.detachservice(app); }
Ant.OS.announcer.unregister(app);
delete Ant.OS.PM.processes[app.name][i];
return Ant.OS.PM.processes[app.name].splice(i, 1);
}
},
killAll(app, force) {
if (!Ant.OS.PM.processes[app]) { return; }
return Array.from(Ant.OS.PM.processes[app]).map((a) => a.quit(force));
}
},
cleanup() {
console.log("Clean up system");
for (let a in Ant.OS.PM.processes) { const v = Ant.OS.PM.processes[a]; Ant.OS.PM.killAll(a, true); }
if (Ant.OS.announcer.observable) { Ant.OS.announcer.observable.off("*"); }
$(window).off('keydown');
($("#workspace")).off("mouseover");
delete Ant.OS.announcer.observable;
($("#wrapper")).empty();
Ant.OS.GUI.clearTheme();
Ant.OS.announcer.observable = new Ant.OS.API.Announcer();
Ant.OS.announcer.quota = 0;
Ant.OS.APP = {};
Ant.OS.setting = {
user: {},
applications: {},
desktop: {},
appearance: {},
VFS: {},
system: {}
};
Ant.OS.PM.processes = {};
return Ant.OS.PM.pidalloc = 0;
},
boot() {
//first login
console.log("Booting sytem");
return Ant.OS.API.handle.auth()
.then(function(d) {
// in case someone call it more than once :)
if (d.error) {
// show login screen
return Ant.OS.GUI.login();
} else {
// startX :)
return Ant.OS.GUI.startAntOS(d.result);
}}).catch(e => console.error(e));
},
cleanupHandles: {},
exit() {
//do clean up first
for (let n in Ant.OS.cleanupHandles) { const f = Ant.OS.cleanupHandles[n]; f(); }
return Ant.OS.API.handle.setting()
.then(function(r) {
Ant.OS.cleanup();
return Ant.OS.API.handle.logout()
.then(d => Ant.OS.boot());}).catch(e => console.error(e));
},
onexit(n, f) {
if (!Ant.OS.cleanupHandles[n]) { return Ant.OS.cleanupHandles[n] = f; }
}
}; }
Ant.OS.API = {
// the handle object could be a any remote or local handle to
// fetch user data, used by the API to make requests
// handles are defined in /src/handles
handle: {},
shared: {}, // shared libraries
searchHandle: {},
lang: {},
//request a user data
mid() {
return Ant.OS.announcer.getMID();
},
post(p, d) {
return new Promise(function(resolve, reject) {
const q = Ant.OS.announcer.getMID();
Ant.OS.API.loading(q, p);
return $.ajax({
type: 'POST',
url: p,
contentType: 'application/json',
data: JSON.stringify(d,
function(k, v) {
if (k === "domel") { return undefined; }
return v;
}
, 4),
dataType: 'json',
success: null
})
.done(function(data) {
Ant.OS.API.loaded(q, p, "OK");
return resolve(data);}).fail(function(j, s, e) {
Ant.OS.API.loaded(q, p, "FAIL");
if (e) {
return reject(__e(e));
} else {
return reject(Ant.OS.API.throwe(s));
}
});
});
},
blob(p) {
return new Promise(function(resolve, reject) {
const q = Ant.OS.announcer.getMID();
const r = new XMLHttpRequest();
r.open("GET", p, true);
r.responseType = "arraybuffer";
r.onload = function(e) {
if ((this.status === 200) && (this.readyState === 4)) {
Ant.OS.API.loaded(q, p, "OK");
return resolve(this.response);
} else {
Ant.OS.API.loaded(q, p, "FAIL");
return reject(Ant.OS.API.throwe(__("Unable to get blob: {0}", p)));
}
};
Ant.OS.API.loading(q, p);
return r.send();
});
},
upload(p, d) {
return new Promise(function(resolve, reject) {
const q = Ant.OS.announcer.getMID();
//insert a temporal file selector
const o = ($('<input>')).attr('type', 'file').css("display", "none");
o.change(function() {
Ant.OS.API.loading(q, p);
const formd = new FormData();
formd.append('path', d);
// TODO: only one file is selected at this time
formd.append('upload', o[0].files[0]);
return $.ajax({
url: p,
data: formd,
type: 'POST',
contentType: false,
processData: false,
})
.done(function(data) {
Ant.OS.API.loaded(q, p, "OK");
resolve(data);
return o.remove();}).fail(function(j, s, e) {
Ant.OS.API.loaded(q, p, "FAIL");
if (e) {
reject(__e(e));
} else {
reject(Ant.OS.API.throwe(s));
}
return o.remove();
});
});
return o.click();
});
},
saveblob(name, b) {
const url = window.URL.createObjectURL(b);
const o = ($('<a>'))
.attr("href", url)
.attr("download", name)
.css("display", "none")
.appendTo("body");
o[0].click();
window.URL.revokeObjectURL(url);
return o.remove();
},
loading(q, p) {
return Ant.OS.announcer.trigger("loading", { id: q, data: { m: `${p}`, s: true }, name: "OS" });
},
loaded(q, p, m ) {
return Ant.OS.announcer.trigger("loaded", {
id: q, data: { m: `${m}: ${p}`, s: false }, name: "OS" });
},
get(p, t) {
return new Promise(function(resolve, reject) {
const conf = {
type: 'GET',
url: p
};
if (t) { conf.dataType = t; }
const q = Ant.OS.announcer.getMID();
Ant.OS.API.loading(q, p);
return $.ajax(conf)
.done(function(data) {
Ant.OS.API.loaded(q, p, "OK");
return resolve(data);}).fail(function(j, s, e) {
Ant.OS.API.loaded(q, p, "FAIL");
if (e) {
return reject(__e(e));
} else {
return reject(Ant.OS.API.throwe(s));
}
});
});
},
script(p) {
return Ant.OS.API.get(p, "script");
},
resource(r) {
const path = `resources/${r}`;
return Ant.OS.API.get(path);
},
libready(l) {
return Ant.OS.API.shared[l] || false;
},
requires(l) {
return new Promise(function(resolve, reject) {
if (!Ant.OS.API.shared[l]) {
const libfp = l.asFileHandle();
switch (libfp.ext) {
case "css":
return libfp.onready()
.then(function() {
$('<link>', {
rel: 'stylesheet',
type: 'text/css',
'href': `${libfp.getlink()}`
})
.appendTo('head');
Ant.OS.API.shared[l] = true;
console.log("Loaded :", l);
Ant.OS.announcer.trigger("sharedlibraryloaded", l);
return resolve(undefined);}).catch(e => reject(__e(e)));
case "js":
return Ant.OS.API.script(libfp.getlink())
.then(function(data) {
Ant.OS.API.shared[l] = true;
console.log("Loaded :", l);
Ant.OS.announcer.trigger("sharedlibraryloaded", l);
return resolve(data);}).catch(e => reject(__e(e)));
default:
return reject(Ant.OS.API.throwe(__("Invalid library: {0}", l)));
}
} else {
console.log(l, "Library exist, no need to load");
Ant.OS.announcer.trigger("sharedlibraryloaded", l);
return resolve();
}
});
},
require(libs) {
return new Promise(function(resolve, reject) {
if (!(libs.length > 0)) { return resolve(); }
Ant.OS.announcer.observable.one("sharedlibraryloaded", function(l) {
libs.splice(0, 1);
return Ant.OS.API.require(libs)
.catch(e => reject(__e(e)))
.then(r => resolve(r));
});
return Ant.OS.API.requires(libs[0])
.catch(e => reject(__e(e)));
});
},
packages: {
fetch() {
return Ant.OS.API.handle.packages({
command: "list", args: { paths: ((() => {
const result = [];
for (let k in Ant.OS.setting.system.pkgpaths) {
const v = Ant.OS.setting.system.pkgpaths[k];
result.push(v);
}
return result;
})()) }
});
},
cache() {
return Ant.OS.API.handle.packages({
command: "cache", args: { paths: ((() => {
const result = [];
for (let k in Ant.OS.setting.system.pkgpaths) {
const v = Ant.OS.setting.system.pkgpaths[k];
result.push(v);
}
return result;
})()) }
});
}
},
setting(f) {
return Ant.OS.API.handle.setting(f);
},
apigateway(d, ws) {
return Ant.OS.API.handle.apigateway(d, ws);
},
search(text) {
let r = [];
for (let k in Ant.OS.API.searchHandle) {
const v = Ant.OS.API.searchHandle[k];
const ret = Ant.OS.API.searchHandle[k](text);
if (ret.length > 0) {
ret.unshift({ text: k, class: "search-header", dataid: "header" });
r = r.concat(ret);
}
}
return r;
},
onsearch(name, fn) {
if (!Ant.OS.API.searchHandle[name]) { return Ant.OS.API.searchHandle[name] = fn; }
},
setLocale(name) {
return new Promise(function(resolve, reject) {
const path = `resources/languages/${name}.json`;
return Ant.OS.API.get(path, "json")
.then(function(d) {
Ant.OS.setting.system.locale = name;
Ant.OS.API.lang = d;
Ant.OS.announcer.trigger("systemlocalechange", name);
return resolve(d);}).catch(e => reject(__e(e)));
});
},
throwe(n) {
let err = undefined;
try {
throw new Error(n);
} catch (e) {
err = e;
}
if (!err) { return ""; }
return err;
},
setClipboard(v) {
const $el = $("#clipboard");
$el.val(v);
$el[0].select();
$el[0].setSelectionRange(0, 99999);
return document.execCommand("copy");
},
getClipboard() {
return new Promise(function(resolve, reject) {
const $el = $("#clipboard");
if (!navigator.clipboard) { return resolve($el.val()); }
return navigator.clipboard.readText().then(d => resolve(d)).catch(e => reject(__e(e)));
});
},
// utilities functioncs
switcher() {
let k, v;
const o = {};
const p = {};
for (let i = 0, end = arguments.length - 1, asc = 0 <= end; asc ? i <= end : i >= end; asc ? i++ : i--) { p[arguments[i]] = false; }
Object.defineProperty(o, "__p", {
enumerable: false,
value: p
});
const fn = function(o, v) {
return Object.defineProperty(o, v, {
enumerable: true,
set(value) {
for (let k in this.__p) {
const l = this.__p[k];
this.__p[k] = false;
}
return o.__p[v] = value;
}
, get() {
return o.__p[v];
}
});
};
for (k in o.__p) {
v = o.__p[k];
fn(o, k);
}
Object.defineProperty(o, "selected", {
configurable: true,
enumerable: false,
get() {
for (k in o.__p) {
v = o.__p[k];
if (v) { return k; }
}
}
});
return o;
}
};
function __range__(left, right, inclusive) {
let range = [];
let ascending = left < right;
let end = !inclusive ? right : ascending ? right + 1 : right - 1;
for (let i = left; ascending ? i < end : i > end; ascending ? i++ : i--) {
range.push(i);
}
return range;
}

1188
src/core/core.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,73 +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 <xsang.le AT gmail DOT com>
// 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 DB {
constructor(table) {
this.table = table;
}
save(d) {
return new Promise((resolve, reject) => {
return Ant.OS.API.handle.dbquery("save", { table: this.table, data: d })
.then(function(r) {
if (r.error) { return reject(Ant.OS.API.throwe(r.error)); }
return resolve(r.result);}).catch(e => reject(__e(e)));
});
}
delete(c) {
return new Promise((resolve, reject) => {
const rq = { table: this.table };
if (!c || (c === "")) { reject(Ant.OS.API.throwe("OS.DB: unkown condition")); }
if (isNaN(c)) {
rq.cond = c;
} else {
rq.id = c;
}
return Ant.OS.API.handle.dbquery("delete", rq)
.then(function(r) {
if (r.error) { return reject(Ant.OS.API.throwe(r.error)); }
return resolve(r.result);}).catch(e => reject(__e(e)));
});
}
get(id) {
return new Promise((resolve, reject) => {
return Ant.OS.API.handle.dbquery("get", { table: this.table, id })
.then(function(r) {
if (r.error) { return reject(Ant.OS.API.throwe(r.error)); }
return resolve(r.result);}).catch(e => reject(__e(e)));
});
}
find(cond) {
return new Promise((resolve, reject) => {
return Ant.OS.API.handle.dbquery("select", { table: this.table, cond })
.then(function(r) {
if (r.error) { return reject(Ant.OS.API.throwe(r.error)); }
return resolve(r.result);}).catch(e => reject(__e(e)));
});
}
}
Ant.OS.API.DB = DB;

155
src/core/db.ts Normal file
View File

@ -0,0 +1,155 @@
/*
* 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 <xsang.le AT gmail DOT com>
// 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
* @class DB
*/
export class DB {
table: GenericObject<any>;
/**
*Creates an instance of DB.
* @param {GenericObject<any>} table
* @memberof DB
*/
constructor(table: GenericObject<any>) {
this.table = table;
}
/**
*
*
* @param {*} d
* @returns {Promise<API.RequestResult>}
* @memberof DB
*/
save(d: any): Promise<API.RequestResult> {
return new Promise(async function (resolve, reject) {
try {
const r = await Ant.OS.API.handle.dbquery("save", {
table: this.table,
data: d,
});
if (r.error) {
return reject(
Ant.OS.API.throwe(r.error.toString())
);
}
return resolve(r);
} catch (e) {
return reject(__e(e));
}
});
}
/**
*
*
* @param {*} c
* @returns {Promise<API.RequestResult>}
* @memberof DB
*/
delete(c: any): Promise<API.RequestResult> {
return new Promise(async (resolve, reject) => {
const rq: any = { table: this.table };
if (!c || c === "") {
reject(Ant.OS.API.throwe("OS.DB: unkown condition"));
}
if (isNaN(c)) {
rq.cond = c;
} else {
rq.id = c;
}
try {
const r = await Ant.OS.API.handle.dbquery("delete", rq);
if (r.error) {
return reject(
Ant.OS.API.throwe(r.error.toString())
);
}
return resolve(r);
} catch (e) {
return reject(__e(e));
}
});
}
/**
*
*
* @param {number} id
* @returns {Promise<GenericObject<any>>}
* @memberof DB
*/
get(id: number): Promise<GenericObject<any>> {
return new Promise(async (resolve, reject) => {
try {
const r = await Ant.OS.API.handle.dbquery("get", {
table: this.table,
id: id,
});
if (r.error) {
return reject(
Ant.OS.API.throwe(r.error.toString())
);
}
return resolve(r.result as GenericObject<any>);
} catch (e) {
return reject(__e(e));
}
});
}
/**
*
*
* @param {GenericObject<any>} cond
* @returns {Promise<GenericObject<any>[]>}
* @memberof DB
*/
find(cond: GenericObject<any>): Promise<GenericObject<any>[]> {
return new Promise(async (resolve, reject) => {
try {
const r = await Ant.OS.API.handle.dbquery("select", {
table: this.table,
cond,
});
if (r.error) {
return reject(
Ant.OS.API.throwe(r.error.toString())
);
}
return resolve(r.result as GenericObject<any>[]);
} catch (e) {
return reject(__e(e));
}
});
}
}
}
}

View File

@ -1,611 +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 <xsang.le AT gmail DOT com>
// 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.GUI = {
subwindows: new Object(),
dialog: undefined,
fullscreen: false,
workspace: "#desktop",
shortcut: {
ALT: {},
CTRL: {},
SHIFT: {},
META: {}
},
htmlToScheme(html, app, parent) {
const scheme = $.parseHTML(html);
if (app.scheme) { $(app.scheme).remove(); }
($(parent)).append(scheme);
app.scheme = scheme[0];
scheme[0].uify(app.observable);
app.main();
return app.show();
},
loadScheme(path, app, parent) {
return path.asFileHandle().read()
.then(function(x) {
if (!x) { return null; }
return Ant.OS.GUI.htmlToScheme(x, app, parent);})
.catch(
(e) =>{
Ant.OS.announcer.oserror(__("Cannot load scheme: {0}", path), e);
console.log(e);
});
},
clearTheme() {
return $("head link#ostheme")
.attr("href", "");
},
loadTheme(name, force) {
if (force) { Ant.OS.GUI.clearTheme(); }
const path = `resources/themes/${name}/${name}.css`;
return $("head link#ostheme")
.attr("href", path);
},
pushServices(srvs) {
return new Promise(function(resolve, reject) {
if (!(srvs.length > 0)) { return resolve(); }
const srv = srvs.splice(0, 1)[0];
return Ant.OS.GUI.pushService(srv)
.then(d => Ant.OS.GUI.pushServices(srvs)
.then(() => resolve())
.catch(e => reject(__e(e)))).catch(function(e) {
Ant.OS.announcer.osfail(__("Unable to load: {0}", srv), e);
return Ant.OS.GUI.pushServices(srvs)
.then(() => resolve())
.catch(e => reject(__e(e)));
});
});
},
openDialog(d, data) {
return new Promise(function(resolve, reject) {
if (Ant.OS.GUI.dialog) {
Ant.OS.GUI.dialog.show();
return resolve();
}
if (!Ant.OS.GUI.subwindows[d]) {
const ex = Ant.OS.API.throwe("Dialog");
return reject(ex);
}
Ant.OS.GUI.dialog = new (Ant.OS.GUI.subwindows[d])();
Ant.OS.GUI.dialog.parent = Ant.OS.GUI;
Ant.OS.GUI.dialog.handle = resolve;
Ant.OS.GUI.dialog.reject = reject;
Ant.OS.GUI.dialog.pid = -1;
Ant.OS.GUI.dialog.data = data;
return Ant.OS.GUI.dialog.init();
});
},
pushService(ph) {
return new Promise(function(resolve, reject) {
const arr = ph.split("/");
const srv = arr[1];
const app = arr[0];
if (Ant.OS.APP[srv]) {
return Ant.OS.PM.createProcess(srv, Ant.OS.APP[srv])
.then(d => resolve(d))
.catch(e => reject(__e(e)));
} else {
return Ant.OS.GUI.loadApp(app)
.then(function(a) {
if (!Ant.OS.APP[srv]) {
return reject(Ant.OS.API.throwe(__("Service not found: {0}", ph)));
}
return Ant.OS.PM.createProcess(srv, Ant.OS.APP[srv])
.then(d => resolve(d))
.catch(e => reject(__e(e)));}).catch(e => reject(__e(e)));
}
});
},
appsByMime(mime) {
let m;
const metas = ((() => {
const result = [];
for (let k in Ant.OS.setting.system.packages) {
const v = Ant.OS.setting.system.packages[k];
if (v && v.app) {
result.push(v);
}
}
return result;
})());
const mimes = ((() => {
const result1 = [];
for (m of Array.from(metas)) { if (m) {
result1.push(m.mimes);
}
}
return result1;
})());
const apps = [];
// search app by mimes
const f = function( arr, idx ) {
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 Ant.OS.announcer.osfail(__("Error find app by mimes {0}", mime), e);
}
};
for (let i = 0; i < mimes.length; i++) { m = mimes[i]; if (m) { f(m, i); } }
return apps;
},
appsWithServices() {
const o = {};
for (let k in Ant.OS.setting.system.packages) { const v = Ant.OS.setting.system.packages[k]; if (v && v.services && (v.services.length > 0)) { o[k] = v; } }
return o;
},
openWith(it) {
if (!it) { return; }
if ((it.type === "app") && it.app) { return Ant.OS.GUI.launch(it.app); }
if (it.type === "app") { return Ant.OS.announcer.osinfo(__("Application {0} is not executable", it.text)); }
const apps = Ant.OS.GUI.appsByMime(( it.type === "dir" ? "dir" : it.mime ));
if (apps.length === 0) { return Ant.OS.announcer.osinfo(__("No application available to open {0}", it.filename)); }
if (apps.length === 1) { return Ant.OS.GUI.launch(apps[0].app, [it]); }
const list = ( Array.from(apps).map((e) => ({ text: e.app, icon: e.icon, iconclass: e.iconclass })) );
return Ant.OS.GUI.openDialog("SelectionDialog", {
title: __("Open with"),
data: list
}).then(d => Ant.OS.GUI.launch(d.text, [ { path: it.path, type: it.type }]));
},
forceLaunch(app, args) {
console.warn("This method is used for developing only, please use the launch method instead");
Ant.OS.GUI.unloadApp(app);
return Ant.OS.GUI.launch(app, args);
},
unloadApp(app) {
Ant.OS.PM.killAll(app, true);
if (Ant.OS.APP[app] && Ant.OS.APP[app].style) { ($(Ant.OS.APP[app].style)).remove(); }
return delete Ant.OS.APP[app];
},
loadApp(app) {
return new Promise(function(resolve, reject) {
let path;
if (Ant.OS.setting.system.packages[app].path) { ({
path
} = Ant.OS.setting.system.packages[app]); }
const js = path + "/main.js";
return js.asFileHandle().read("script")
.then(d => // load app meta data
`${path}/package.json`.asFileHandle().read("json")
.then(function(data) {
data.path = path;
if (Ant.OS.APP[app]) { Ant.OS.APP[app].meta = data; }
if (data.services) { for (let v of Array.from(data.services)) { Ant.OS.APP[v].meta = data; } }
//load css file
const css = `${path}/main.css`;
return css.asFileHandle().onready()
.then(function(d) {
const stamp = (new Date).timestamp();
const el = $('<link>', { rel: 'stylesheet', type: 'text/css', 'href': `${Ant.OS.API.handle.get}/${css}?stamp=${stamp}` })
.appendTo('head');
if (Ant.OS.APP[app]) { Ant.OS.APP[app].style = el[0]; }
return resolve(app);}).catch(e => resolve(app));}).catch(e => reject(__e(e)))).catch(e => reject(__e(e)));
});
},
launch(app, args) {
if (!Ant.OS.APP[app]) {
// first load it
return Ant.OS.GUI.loadApp(app).then(a => Ant.OS.PM.createProcess(a, Ant.OS.APP[a], args)).catch(e => Ant.OS.announcer.osfail(__("Unable to launch: {0}", app), e));
} else {
// now launch it
if (Ant.OS.APP[app]) {
return Ant.OS.PM.createProcess(app, Ant.OS.APP[app], args)
.catch(e => Ant.OS.announcer.osfail(__("Unable to launch: {0}", app), e));
}
}
},
dock(app, meta) {
// 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");
app.init();
return app.one("rendered", function() {
dock.get(0).newapp(data);
app.sysdock = dock.get(0);
app.appmenu = ($("[data-id = 'appmenu']", "#syspanel"))[0];
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); }
});
});
},
toggleFullscreen() {
const el = document.documentElement;
if (Ant.OS.GUI.fullscreen) {
if (document.exitFullscreen) { return document.exitFullscreen(); }
if (document.mozCancelFullScreen) { return document.mozCancelFullScreen(); }
if (document.webkitExitFullscreen) { return document.webkitExitFullscreen(); }
if (document.cancelFullScreen) { return document.cancelFullScreen(); }
} else {
if (el.requestFullscreen) { return el.requestFullscreen(); }
if (el.mozRequestFullScreen) { return el.mozRequestFullScreen(); }
if (el.webkitRequestFullscreen) { return el.webkitRequestFullscreen(); }
if (el.msRequestFullscreen) { return el.msRequestFullscreen(); }
}
},
undock(app) {
return ($("#sysdock")).get(0).removeapp(app);
},
attachservice(srv) {
($("#syspanel"))[0].attachservice(srv);
srv.init();
return srv.subscribe("systemlocalechange", name => srv.update());
},
detachservice(srv) {
return ($("#syspanel"))[0].detachservice(srv);
},
bindContextMenu(event) {
var handle = function(e) {
if (e.contextmenuHandle) {
return e.contextmenuHandle(event, ($("#contextmenu"))[0]);
} else {
const p = $(e).parent().get(0);
if (p !== ($("#workspace")).get(0)) { return handle(p); }
}
};
handle(event.target);
return event.preventDefault();
},
bindKey(k, f) {
const arr = k.split("-");
if (arr.length !== 2) { return; }
const fnk = arr[0].toUpperCase();
const c = arr[1].toUpperCase();
if (!Ant.OS.GUI.shortcut[fnk]) { return; }
return Ant.OS.GUI.shortcut[fnk][c] = f;
},
wallpaper(obj) {
if (obj) {
Ant.OS.setting.appearance.wp = obj;
}
const {
wp
} = Ant.OS.setting.appearance;
return $("body").css("background-image", `url(${Ant.OS.API.handle.get}/${wp.url})` )
.css("background-size", wp.size)
.css("background-repeat", wp.repeat);
},
showTooltip(el, text, e) {
let left, top;
el = el[0];
const label = ($("#systooltip"))[0];
var cb = function(ev) {
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.set("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;
}
return $(label).css("top", top + "px")
.css("left", left + "px");
},
initDM() {
const scheme = $.parseHTML(Ant.OS.GUI.schemes.ws);
($("#wrapper")).append(scheme);
Ant.OS.announcer.observable.one("sysdockloaded", () => ($(window)).bind('keydown', function(event) {
const dock = ($("#sysdock"))[0];
if (!dock) { return; }
const app = dock.get("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 (!Ant.OS.GUI.shortcut[fnk]) { return; }
if (!Ant.OS.GUI.shortcut[fnk][c]) { return; }
Ant.OS.GUI.shortcut[fnk][c](event);
return event.preventDefault();
}));
// system menu and dock
$("#syspanel")[0].uify();
$("#sysdock")[0].uify();
$("#systooltip")[0].uify();
$("#contextmenu")[0].uify();
($("#workspace")).contextmenu(e => Ant.OS.GUI.bindContextMenu(e));
// tooltip
($(document)).mouseover(function(e) {
const el = $(e.target).closest("[tooltip]");
if (!(el.length > 0)) { return; }
return Ant.OS.GUI.showTooltip(el, ($(el).attr("tooltip")), e);
});
const fp = Ant.OS.setting.desktop.path.asFileHandle();
// desktop default file manager
const desktop = $(Ant.OS.GUI.workspace);
desktop[0].fetch = function() {
const file = Ant.OS.setting.desktop.path.asFileHandle();
const fn = () => file.read().then(function(d) {
if (d.error) { return Ant.OS.announcer.osfail(d.error, (Ant.OS.API.throwe("OS.VFS")), d.error); }
const items = [];
$.each(d.result, function(i, v) {
if ((v.filename[0] === '.') && !Ant.OS.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[0].set("data", items);
return desktop[0].refresh();
});
return file.onready()
.then(() => fn())
.catch(function( e ) { // try to create the path
console.log(`${file.path} not found`);
const name = file.basename;
return file.parent().asFileHandle().mk(name).then(function(r) {
let ex;
return ex = Ant.OS.API.throwe("OS.VFS");}).catch(e => Ant.OS.announcer.osfail(e.toString(), e));
});
};
desktop[0].ready = function(e) {
e.observable = Ant.OS.announcer;
window.onresize = function() {
Ant.OS.announcer.trigger("desktopresize");
return e.refresh();
};
desktop[0].set("onlistselect", d => ($("#sysdock")).get(0).set("selectedApp", null));
desktop[0].set("onlistdbclick", function( d ) {
($("#sysdock")).get(0).set("selectedApp", null);
const it = desktop[0].get("selectedItem");
return Ant.OS.GUI.openWith(it.get("data"));
});
//($ "#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[0]) { return; }
desktop[0].unselect();
return ($("#sysdock")).get(0).set("selectedApp", null);
});
desktop[0].contextmenuHandle = function(e, m) {
if (e.target.tagName.toUpperCase() === "UL") { desktop[0].unselect(); }
($("#sysdock")).get(0).set("selectedApp", null);
let menu = [
{ text: __("Open"), dataid: "desktop-open" },
{ text: __("Refresh"), dataid: "desktop-refresh" }
];
menu = menu.concat(((() => {
const result = [];
for (let k in Ant.OS.setting.desktop.menu) {
const v = Ant.OS.setting.desktop.menu[k];
result.push(v);
}
return result;
})()));
m.set("items", menu);
m.set("onmenuselect", function(evt) {
const item = evt.data.item.get("data");
switch (item.dataid) {
case "desktop-open":
var it = desktop[0].get("selectedItem");
if (it) { return Ant.OS.GUI.openWith(it.get("data")); }
it = Ant.OS.setting.desktop.path.asFileHandle();
it.mime = "dir";
it.type = "dir";
return Ant.OS.GUI.openWith(it);
case "desktop-refresh":
return desktop[0].fetch();
default:
if (item.app) { return Ant.OS.GUI.launch(item.app, item.args); }
}
});
return m.show(e);
};
desktop[0].fetch();
Ant.OS.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 desktop[0].fetch(); }
});
return Ant.OS.announcer.ostrigger("desktoploaded");
};
// mount it
return desktop[0].uify();
},
refreshDesktop() {
return ($(Ant.OS.GUI.workspace))[0].fetch();
},
login() {
const scheme = $.parseHTML(Ant.OS.GUI.schemes.login);
($("#wrapper")).append(scheme);
($("#btlogin")).click(function() {
const data = {
username: ($("#txtuser")).val(),
password: ($("#txtpass")).val()
};
return Ant.OS.API.handle.login(data)
.then(function(d) {
if (d.error) { return ($("#login_error")).html(d.error); }
return Ant.OS.GUI.startAntOS(d.result);}).catch(e => ($("#login_error")).html("Login: server error"));
});
($("#txtpass")).keyup(function(e) {
if (e.which === 13) { return ($("#btlogin")).click(); }
});
return ($("#txtuser")).keyup(function(e) {
if (e.which === 13) { return ($("#btlogin")).click(); }
});
},
startAntOS(conf) {
// clean up things
Ant.OS.cleanup();
// get setting from conf
Ant.OS.systemSetting(conf);
//console.log Ant.OS.setting
// load theme
Ant.OS.GUI.loadTheme(Ant.OS.setting.appearance.theme);
Ant.OS.GUI.wallpaper();
Ant.OS.announcer.observable.one("syspanelloaded", function() {
// TODO load packages list then build system menu
Ant.OS.announcer.observable.on("systemlocalechange", name => ($("#syspanel"))[0].update());
return Ant.OS.API.packages.cache().then(function(ret) {
if (ret.result) {
return Ant.OS.API.packages.fetch().then(function(r) {
let v;
if (r.result) {
for (let k in r.result) {
v = r.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"; }
}
}
Ant.OS.setting.system.packages = r.result ? r.result : undefined;
// Ant.OS.GUI.refreshSystemMenu()
// Ant.OS.GUI.buildSystemMenu()
// push startup services
// TODO: get services list from user setting
Ant.OS.GUI.pushServices(((() => {
const result = [];
for (v of Array.from(Ant.OS.setting.system.startup.services)) { result.push(v);
}
return result;
})()));
return Array.from(Ant.OS.setting.system.startup.apps).map((a) => (Ant.OS.GUI.launch(a)));
});
}
});
});
//Ant.OS.GUI.launch "DummyApp"
// initDM
return Ant.OS.API.setLocale(Ant.OS.setting.system.locale)
.then(() => Ant.OS.GUI.initDM());
}
};
Ant.OS.GUI.schemes = {};
Ant.OS.GUI.schemes.ws = `\
<afx-sys-panel id = "syspanel"></afx-sys-panel>
<div id = "workspace">
<afx-apps-dock id="sysdock"></afx-apps-dock>
<afx-float-list id = "desktop" dir="vertical" ></afx-float-list>
</div>
<afx-menu id="contextmenu" data-id="contextmenu" context="true" style="display:none;"></afx-menu>
<afx-label id="systooltip" data-id="systooltip" style="display:none;position:absolute;"></afx-label>
<textarea id="clipboard"></textarea>\
`;
Ant.OS.GUI.schemes.login = `\
<div id = "login_form">
<p>Welcome to AntOS, please login</p>
<input id = "txtuser" type = "text" value = "demo" />
<input id = "txtpass" type = "password" value = "demo" />
<button id = "btlogin">Login</button>
<div id = "login_error"></div>
</div>\
`;

1091
src/core/gui.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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 <xsang.le AT gmail DOT com>
// 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);
}
};

View File

@ -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 <xsang.le AT gmail DOT com>
// 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<any>;
}
export interface RequestResult {
error: boolean | string;
result:
| string
| boolean
| GenericObject<any>
| 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<RequestResult> {
const path = `${REST}/VFS/scandir`;
return API.post(path, { path: p });
}
export function mkdir(p: string): Promise<RequestResult> {
const path = `${API.REST}/VFS/mkdir`;
return API.post(path, { path: p });
}
export function sharefile(
p: string,
pub: boolean
): Promise<RequestResult> {
const path = `${API.REST}/VFS/publish`;
return API.post(path, { path: p, publish: pub });
}
export function fileinfo(p: string): Promise<RequestResult> {
const path = `${API.REST}/VFS/fileinfo`;
return API.post(path, { path: p });
}
export function readfile(p: string, t: string): Promise<any> {
const path = `${API.REST}/VFS/get/`;
return API.get(path + p, t);
}
export function move(s: string, d: string): Promise<RequestResult> {
const path = `${API.REST}/VFS/move`;
return API.post(path, { src: s, dest: d });
}
export function remove(p: string): Promise<RequestResult> {
const path = `${API.REST}/VFS/delete`;
return API.post(path, { path: p });
}
export function fileblob(p: string): Promise<ArrayBuffer> {
const path = `${API.REST}/VFS/get/`;
return API.blob(path + p);
}
export function packages(
d: PackageCommandType
): Promise<RequestResult> {
const path = `${API.REST}/system/packages`;
return API.post(path, d);
}
export function upload(d: string): Promise<RequestResult> {
const path = `${API.REST}/VFS/upload`;
return API.upload(path, d);
}
export function write(
p: string,
d: string
): Promise<RequestResult> {
const path = `${API.REST}/VFS/write`;
return API.post(path, { path: p, data: d });
}
export function apigateway(
d: GenericObject<any>,
ws: boolean
): Promise<any> {
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<RequestResult> {
const p = `${API.REST}/user/auth`;
return API.post(p, {});
}
export function login(d: UserLoginType): Promise<RequestResult> {
const p = `${API.REST}/user/login`;
return API.post(p, d);
}
export function logout(): Promise<RequestResult> {
const p = `${API.REST}/user/logout`;
return API.post(p, {});
}
export function setting(): Promise<RequestResult> {
const p = `${API.REST}/system/settings`;
return API.post(p, setting);
}
export function dbquery(
cmd: string,
d: GenericObject<any>
): Promise<RequestResult> {
const path = `${API.REST}/VDB/${cmd}`;
return API.post(path, d);
}
}
}
}

136
src/core/pm.ts Normal file
View File

@ -0,0 +1,136 @@
namespace OS {
export namespace PM {
export type ProcessType = application.BaseApplication | application.BaseService;
export type ProcessTypeClass = {
new <T extends BaseModel>(args: AppArgumentsType[]): T;
};
export var pidalloc: number = 0;
export var processes: GenericObject<BaseModel[]> = {};
/**
*
*
* @export
* @param {string} app
* @param {ProcessTypeClass} cls
* @param {GUI.AppArgumentsType[]} [args]
* @returns {Promise<ProcessType>}
*/
export function createProcess(
app: string,
cls: ProcessTypeClass,
args?: AppArgumentsType[]
): Promise<ProcessType> {
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<any>) {
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));
}
}
}

View File

@ -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 <xsang.le AT gmail DOT com>
// 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;
});
};

334
src/core/settings.ts Normal file
View File

@ -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 <xsang.le AT gmail DOT com>
// 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<any> = {};
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;
});
}

View File

@ -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 = $("<afx-button>");
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);

206
src/core/tags/AppDockTag.ts Normal file
View File

@ -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 = $("<afx-button>");
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);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<string | number>[];
private _fetch: (p: string) => Promise<API.FileInfoType[]>;
/**
*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<API.FileInfoType[]>) {
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);
}
}
}

View File

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

View File

@ -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<any>[]) {}
/**
*
*
* @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<any>} v
* @returns
* @memberof FloatListTag
*/
push(v: GenericObject<any>) {
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);
}
}
}

View File

@ -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 = $("<afx-grid-row>")
.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);

View File

@ -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<T> {
domel: GenericObject<any>;
}
namespace OS {
export namespace GUI {
export namespace tag {
export class GridRowTag extends AFXTag {
data: GenericObject<any>[];
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<any>;
constructor() {
super();
}
set oncellselect(v: TagEventCallback) {
this._oncellselect = v;
}
set oncelldbclick(v: TagEventCallback) {
this._oncelldbclick = v;
}
set data(v: GenericObject<any>) {
if(!v) return;
this._data = v;
this.ondatachange();
if (!v.selected) {
return;
}
this.selected = v.selected;
}
get data(): GenericObject<any> {
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<any>[];
private _rows: GenericObject<any>[][];
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<any>[]) {
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<any>[][]) {
$(this.refs.grid).empty();
this._rows = rows;
rows.map((row) => this.push(row, false));
}
get rows(): GenericObject<any>[][] {
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<any>[], flag: boolean): void {
const rowel = $("<afx-grid-row>").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<any>[]): 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);
}
}
}

View File

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

88
src/core/tags/LabelTag.ts Normal file
View File

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

View File

@ -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 = $("<afx-button>").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);

1044
src/core/tags/ListViewTag.ts Normal file

File diff suppressed because it is too large Load Diff

View File

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

740
src/core/tags/MenuTag.ts Normal file
View File

@ -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<any>;
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<any>) {
this._data = data;
this.set(data);
}
/**
*
*
* @type {GenericObject<any>}
* @memberof MenuEntryTag
*/
get data(): GenericObject<any> {
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<any>[]) {
$(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<any>[]}
* @memberof MenuEntryTag
*/
get nodes(): GenericObject<any>[] {
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<any>[];
/**
*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<any>[]) {
this._items = data;
$(this.refs.container).empty();
data.map((item) => this.push(item, false));
}
/**
*
*
* @type {GenericObject<any>[]}
* @memberof MenuTag
*/
get items(): GenericObject<any>[] {
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<any>} item
* @memberof MenuTag
*/
unshift(item: GenericObject<any>): 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<any>} item
* @param {boolean} flag
* @returns {MenuEntryTag}
* @memberof MenuTag
*/
push(item: GenericObject<any>, 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);
}
}
}

View File

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

View File

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

View File

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

147
src/core/tags/OverlayTag.ts Normal file
View File

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

View File

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

206
src/core/tags/ResizerTag.ts Normal file
View File

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

View File

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

258
src/core/tags/SliderTag.ts Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

203
src/core/tags/TabBarTag.ts Normal file
View File

@ -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<any>[];
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<any>} item
* @memberof TabBarTag
*/
push(item: GenericObject<any>): 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<any>} item
* @memberof TabBarTag
*/
unshift(item: GenericObject<any>): ListViewItemTag {
item.closable = this.closable;
return (this.refs.list as ListViewTag).unshift(item);
}
/**
*
*
* @memberof TabBarTag
*/
set items(v: GenericObject<any>[]) {
this._items = v;
for (let i of v) {
i.closable = this.closable;
}
(this.refs.list as ListViewTag).data = v;
}
/**
*
*
* @type {GenericObject<any>[]}
* @memberof TabBarTag
*/
get items(): GenericObject<any>[] {
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);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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 = $("<afx-tree-view>").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);

View File

@ -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<TreeViewDataType[]>;
/**
*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 = $("<afx-tree-view>").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<TreeViewDataType[]>;
/**
*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);
}
}
}

View File

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

358
src/core/tags/WindowTag.ts Normal file
View File

@ -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<any>;
private _desktop_pos: GenericObject<any>;
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<any>): 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);
}
}
}

View File

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

235
src/core/tags/tag.ts Normal file
View File

@ -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<HTMLElement>;
protected _mounted: boolean;
constructor() {
super();
if (!this.observable) {
this.observable = new Ant.OS.API.Announcer();
}
this._mounted = false;
this.refs = {};
}
set(v: GenericObject<any>) {
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<T extends AFXTag>(
name: string,
cls: { new (): T }
): void {
customElements.define(name, cls);
}
}
}
}

View File

@ -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 <xsang.le AT gmail DOT com>
// 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);

1376
src/core/vfs.ts Normal file

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -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 <xsang.le AT gmail DOT com>
// 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 = `\
<div>
<afx-overlay data-id = "notifyzone" width = "250px">
<afx-hbox data-height="30">
<afx-button text = "__(Clear all)" data-id = "btclear" ></afx-button>
<afx-button iconclass = "fa fa-bug" data-id = "bterrlog" data-width = "25"></afx-button>
</afx-hbox>
<afx-list-view data-id="notifylist"></afx-list-view>
</afx-overlay>
<afx-overlay data-id = "feedzone" width = "250">
<afx-list-view data-id = "notifeed">
</afx-list-view>
</afx-overlay>
</div>\
`;
this.OS.register("PushNotification", PushNotification);

View File

@ -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 <xsang.le AT gmail DOT com>
// 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<any>[];
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<any>} o
* @memberof PushNotification
*/
private addLog(s: string, o: GenericObject<any>): 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<any>} o
* @memberof PushNotification
*/
private pushout(s: string, o: GenericObject<any>): 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<any>} d
* @memberof PushNotification
*/
private notifeed(d: GenericObject<any>): 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 = `\
<div>
<afx-overlay data-id = "notifyzone" width = "250px">
<afx-hbox data-height="30">
<afx-button text = "__(Clear all)" data-id = "btclear" ></afx-button>
<afx-button iconclass = "fa fa-bug" data-id = "bterrlog" data-width = "25"></afx-button>
</afx-hbox>
<afx-list-view data-id="notifylist"></afx-list-view>
</afx-overlay>
<afx-overlay data-id = "feedzone" width = "250">
<afx-list-view data-id = "notifeed">
</afx-list-view>
</afx-overlay>
</div>\
`;
}

View File

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

View File

@ -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<any>} log
* @memberof Syslog
*/
addLog(log: GenericObject<any>): void {
this.loglist.push(log);
}
/**
*
*
* @returns {void}
* @memberof Syslog
*/
cleanup(): void {
if (this.srv) {
return (this.srv.logmon = undefined);
}
}
}
Syslog.singleton = true;
}
}

View File

@ -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":[]
}
"mimes": []
}

View File

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

324
tests/testTag.ts Normal file
View File

@ -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",()=>{
})

11
tsconfig.json Normal file
View File

@ -0,0 +1,11 @@
{
"compilerOptions": {
"target": "es6",
"skipLibCheck": true,
"lib": [
"es6", "dom", "es2017"
],
"outDir": "dist"
},
"include": ["src/**/*.ts"]
}