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

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