add more type, all defaults apps are now in typescript

This commit is contained in:
Xuan Sang LE
2020-06-04 17:49:48 +02:00
parent 6e95994892
commit fb462fe31b
50 changed files with 2612 additions and 1734 deletions

View File

@ -1,4 +1,4 @@
module_files = dialog.js main.js
module_files = main.js dialog.js
libfiles =

View File

@ -1,100 +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 RepositoryDialog extends this.OS.GUI.subwindows.SelectionDialog {
constructor() {
super();
}
main() {
super.main();
this.list = this.find("list");
$((this.find("btnOk"))).hide();
return this.list.set("buttons", [
{
text: "+",
onbtclick: () => {
return this.openDialog("PromptDialog", {
title: __("Add repository"),
label: __("Format : [name] url")
}).then(e => {
const m = e.match(/\[([^\]]*)\]\s*(.+)/);
if (!m || (m.length !== 3)) {
return this.error(__("Wrong format: it should be [name] url"));
}
const repo = {
url: m[2],
text: m[1]
};
this.systemsetting.system.repositories.push(repo);
return this.list.push(repo);
});
}
},
{
text: "-",
onbtclick: () => {
const el = this.list.get("selectedItem");
if (!el) { return; }
const selidx = $(el).index();
if (!(selidx >= 0)) { return; }
this.systemsetting.system.repositories.splice(selidx, selidx);
return this.list.remove(el);
}
},
{
iconclass: "fa fa-pencil",
onbtclick: () => this.editRepo()
}
]);
}
editRepo() {
const el = this.list.get("selectedItem");
if (!el) { return; }
const selidx = $(el).index();
if (!(selidx >= 0)) { return; }
const data = el.get("data");
const sel = this.systemsetting.system.repositories[selidx];
return this.openDialog("PromptDialog", {
title: __("Edit repository"),
label: __("Format : [name] url"),
value: `[${data.text}] ${data.url}`
}).then(e => {
const m = e.match(/\[([^\]]*)\]\s*(.+)/);
if (!m || (m.length !== 3)) {
return this.error(__("Wrong format: it should be [name] url"));
}
data.text = m[1];
data.url = m[2];
this.list.update();
return this.list.unselect();
});
}
onexit(e) {
this.parent.refreshRepoList();
return super.onexit(e);
}
}

View File

@ -0,0 +1,108 @@
/*
* 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 {
const App = OS.application.MarketPlace;
export type MarketPlaceRepoDialog = typeof RepositoryDialog;
class RepositoryDialog extends OS.GUI.dialogs.SelectionDialog {
private list: GUI.tag.ListViewTag;
constructor() {
super();
}
main() {
super.main();
this.list = this.find("list") as GUI.tag.ListViewTag;
$((this.find("btnOk"))).hide();
return this.list.buttons = [
{
text: "+",
onbtclick: () => {
return this.openDialog("PromptDialog", {
title: __("Add repository"),
label: __("Format : [name] url")
}).then(e => {
const m = e.match(/\[([^\]]*)\]\s*(.+)/);
if (!m || (m.length !== 3)) {
return this.error(__("Wrong format: it should be [name] url"));
}
const repo = {
url: m[2],
text: m[1]
};
this.systemsetting.system.repositories.push(repo);
return this.list.push(repo);
});
}
},
{
text: "-",
onbtclick: () => {
const el = this.list.selectedItem;
if (!el) { return; }
const selidx = $(el).index();
if (!(selidx >= 0)) { return; }
this.systemsetting.system.repositories.splice(selidx, selidx);
return this.list.delete(el);
}
},
{
iconclass: "fa fa-pencil",
onbtclick: () => this.editRepo()
}
];
}
private editRepo(): void{
const el = this.list.selectedItem;
if (!el) { return; }
const selidx = $(el).index();
if (!(selidx >= 0)) { return; }
const data = el.data;
const sel = this.systemsetting.system.repositories[selidx];
this.openDialog("PromptDialog", {
title: __("Edit repository"),
label: __("Format : [name] url"),
value: `[${data.text}] ${data.url}`
}).then(e => {
const m = e.match(/\[([^\]]*)\]\s*(.+)/);
if (!m || (m.length !== 3)) {
return this.error(__("Wrong format: it should be [name] url"));
}
data.text = m[1];
data.url = m[2];
this.list.update(undefined);
return this.list.unselect();
});
}
protected onexit(e: BaseEvent): void{
(this.parent as OS.application.MarketPlace).refreshRepoList();
return super.onexit(e);
}
}
App.RepoDialog = RepositoryDialog;
}

View File

@ -9,7 +9,7 @@ afx-app-window[data-id="marketplace-win"] afx-vbox[data-id='container'] {
afx-app-window[data-id="marketplace-win"] afx-vbox[data-id='container'] afx-hbox {
padding-left: 10px;
}
afx-app-window[data-id="marketplace-win"] div[data-id='appname'] {
afx-app-window[data-id="marketplace-win"] afx-label[data-id='appname'] i.label-text{
font-weight: bold;
font-size: 20px;
padding: 10px;

View File

@ -1,433 +0,0 @@
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS205: Consider reworking code to avoid use of IIFEs
* DS208: Avoid top-level this
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
// 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 MarketPlace extends this.OS.GUI.BaseApplication {
constructor(args) {
super("MarketPlace", args);
}
main() {
this.installdir = this.systemsetting.system.pkgpaths.user;
// test repository
this.apps_meta = [];
this.repo = this.find("repo");
this.repo.set("onlistselect", e => {
const data = e.data.item.get("data");
if (!data) { return; }
return this.fetchApps(data);
});
this.refreshRepoList();
this.applist = this.find("applist");
this.applist.set("onlistselect", e => {
const data = e.data.item.get("data");
return this.appDetail(data);
});
this.container = this.find("container");
this.appname = this.find("appname");
this.appdesc = this.find("app-desc");
this.appdetail = this.find("app-detail");
this.btinstall = this.find("bt-install");
this.btremove = this.find("bt-remove");
this.btexec = this.find("bt-exec");
this.searchbox = this.find("searchbox");
($(this.container)).css("visibility", "hidden");
this.btexec.set("onbtclick", e => {
const el = this.applist.get("selectedItem");
if (!el) { return; }
const app = el.get("data");
if (app.pkgname) { return this._gui.launch(app.pkgname); }
});
this.btinstall.set("onbtclick", e => {
if (this.btinstall.get("dirty")) {
return this.updatePackage()
.then(() => this.notify(__("Package updated")))
.catch(e => this.error(e.toString(), e));
}
return this.remoteInstall()
.then(n => this.notify(__("Package installed: {0}", n)))
.catch(e => this.error(e.toString(), e));
});
this.btremove.set("onbtclick", e => {
return this.uninstall()
.then(() => this.notify(__("Packaged uninstalled")))
.catch(e => this.error(e.toString(), e));
});
this.bindKey("CTRL-R", () => {
return this.menuOptionsHandle("repos");
});
return $(this.searchbox).keyup(e => this.search(e));
}
refreshRepoList() {
const list = (Array.from(this.systemsetting.system.repositories));
list.unshift({
text: "Installed"
});
return this.repo.set("data", list);
}
search(e) {
let v;
switch (e.which) {
case 37:
return e.preventDefault();
case 38:
this.applist.selectPrev();
return e.preventDefault();
case 39:
return e.preventDefault();
case 40:
this.applist.selectNext();
return e.preventDefault();
case 13:
return e.preventDefault();
default:
var text = this.searchbox.value;
if (text.length === 2) { this.applist.set("data", ((() => {
const result1 = [];
for (v of Array.from(this.apps_meta)) { result1.push(v);
}
return result1;
})())); }
if (text.length < 3) { return; }
var result = [];
var term = new RegExp(text, 'i');
for (v of Array.from(this.apps_meta)) { if (v.text.match(term)) { result.push(v); } }
return this.applist.set("data", result);
}
}
fetchApps(data) {
let v;
if (!data.url) {
const pkgcache = this.systemsetting.system.packages;
const list = [];
for (let k in pkgcache) {
v = pkgcache[k];
list.push({
pkgname: v.pkgname ? v.pkgname : v.app,
name: v.name,
text: v.name,
icon: v.icon,
iconclass: v.iconclass,
category: v.category,
author: v.info.author,
version: v.version,
description: `${v.path}/REAME.md`
});
}
this.apps_meta = list;
this.applist.set("data", list);
return;
}
return this._api.get((data.url + "?_=" + (new Date().getTime())) , "json")
.then(d => {
for (v of Array.from(d)) {
v.text = v.name;
v.iconclass = "fa fa-adn";
}
this.apps_meta = d;
return this.applist.set("data", d);
}).catch(e => {
return this.error(__("Fail to fetch packages list from: {0}", data.url), e);
});
}
appDetail(d) {
($(this.container)).css("visibility", "visible");
( $(this.appname) ).html(d.name);
(this.find("vstat")).set("text", "");
if (d.description) {
d.description.asFileHandle().read().then(text => {
const converter = new showdown.Converter();
return ($(this.appdesc)).html(converter.makeHtml(text));
}).catch(e => {
this.notify(__("Unable to read package description"));
return ($(this.appdesc)).empty();
});
} else {
($(this.appdesc)).empty();
}
const pkgcache = this.systemsetting.system.packages;
this.btinstall.set("text", "__(Install)");
this.btinstall.set("dirty", false);
if (pkgcache[d.pkgname]) {
let vs = pkgcache[d.pkgname].version;
let ovs = d.version;
($(this.btinstall)).hide();
if (vs && ovs) {
vs = vs.__v();
ovs = ovs.__v();
if (ovs.nt(vs)) {
this.btinstall.set("dirty", true);
this.btinstall.set("text", "__(Update)");
($(this.btinstall)).show();
(this.find("vstat")).set("text",
__("Your application version is older ({0} < {1})", vs, ovs));
}
}
($(this.btremove)).show();
($(this.btexec)).show();
} else {
($(this.btinstall)).show();
($(this.btremove)).hide();
($(this.btexec)).hide();
}
($(this.appdetail)).empty();
return (() => {
const result = [];
for (let k in d) {
const v = d[k];
if ((k !== "name") && (k !== "description") && (k !== "domel")) {
result.push(($(this.appdetail)).append(
$("<li>")
.append(($("<span class= 'info-header'>")).html(k))
.append($("<span>").html(v))
));
}
}
return result;
})();
}
menu() {
return [
{
text: "__(Options)", child: [
{ text: "__(Repositories)", shortcut: "C-R", id: "repos" },
{ text: "__(Install from zip)", shortcut: "C-I", id: "install" }
] , onchildselect: e => {
return this.menuOptionsHandle(e.data.item.get("data").id);
}
}
];
}
menuOptionsHandle(id) {
switch (id) {
case "repos":
return this.openDialog(new RepositoryDialog(), {
title: __("Repositories"),
data: this.systemsetting.system.repositories
});
case "install":
return this.localInstall().then(n => {
return this.notify(__("Package installed: {0}", n));
}).catch(e => this.error(__("Unable to install package"), e));
default:
}
}
remoteInstall() {
const el = this.applist.get("selectedItem");
if (!el) { return; }
const app = el.get("data");
if (!app) { return; }
// get blob file
return new Promise((resolve, reject) => {
return this._api.blob(app.download + "?_=" + (new Date().getTime()))
.then(data => {
return this.install(data, app)
.then(n => resolve(n))
.catch(e => reject(__e(e)));
}).catch(e => reject(__e(e)));
});
}
localInstall() {
return new Promise((resolve, reject) => {
return this.openDialog("FileDialog", {
title: "__(Select package archive)",
mimes: [".*/zip"]
}).then(d => {
return d.file.path.asFileHandle().read("binary").then(data => {
return this.install(data)
.then(n => {
this.repo.unselect();
this.repo.set("selected", 0);
const apps = (Array.from(this.applist.get("data")).map((v) => v.pkgname));
const idx = apps.indexOf(n);
if (idx >= 0) {
this.applist.set("selected", idx);
}
return resolve(n);
}).catch(e => reject(__e(e)))
.catch(e => reject(__e(e)));
}).catch(e => reject(__e(e)));
});
});
}
install(data, meta) {
return new Promise((resolve, reject) => {
return JSZip.loadAsync(data).then(zip => {
return zip.file("package.json").async("string").then(d => {
let name;
const v = JSON.parse(d);
const pth = `${this.installdir}/${v.app}`;
const dir = [pth];
const files = [];
for (name in zip.files) {
const file = zip.files[name];
if (file.dir) {
dir.push(pth + "/" + name);
} else {
files.push(name);
}
}
// create all directory
return this.mkdirs(dir).then(() => {
return this.installFile(v.app, zip, files).then(() => {
const app_meta = {
pkgname: v.app,
name: v.name,
text: v.name,
icon: v.icon,
iconclass: v.iconclass,
category: v.category,
author: v.info.author,
version: v.version,
description: meta ? meta.description : undefined,
download: meta ? meta.download : undefined
};
v.text = v.name;
v.filename = v.app;
v.type = "app";
v.mime = "antos/app";
if (!v.iconclass && !v.icon) { v.iconclass = "fa fa-adn"; }
v.path = pth;
this.systemsetting.system.packages[v.app] = v;
this.appDetail(app_meta);
return resolve(v.name);
}).catch(e => reject(__e(e)));
}).catch(e => reject(__e(e)));
}).catch(err => reject(__e(err)));
}).catch(e => reject(__e(e)));
});
}
uninstall() {
return new Promise((resolve, reject) => {
const el = this.applist.get("selectedItem");
if (!el) { return; }
const sel = el.get("data");
if (!sel) { return; }
const name = sel.pkgname;
const app = this.systemsetting.system.packages[sel.pkgname];
if (!app) { return; }
return this.openDialog("YesNoDialog", {
title: __("Uninstall") ,
text: __("Uninstall: {0}?", app.name)
}).then(d => {
if (!d) { return; }
return app.path.asFileHandle().remove().then(r => {
if (r.error) {
return reject(this._api.throwe(__("Cannot uninstall package: {0}", r.error)));
}
this.notify(__("Package uninstalled"));
// stop all the services if any
if (app.services) {
for (let srv of Array.from(app.services)) {
this._gui.unloadApp(srv);
}
}
delete this.systemsetting.system.packages[name];
this._gui.unloadApp(name);
if (sel.download) {
this.appDetail(sel);
} else {
this.applist.remove(el);
($(this.container)).css("visibility", "hidden");
}
return resolve();
}).catch(e => reject(__e(e)));
}).catch(e => reject(__e(e)));
});
}
updatePackage() {
return new Promise((resolve, reject) => {
return this.uninstall().then(() => {
return this.remoteInstall()
.then(() => resolve())
.catch(e => reject(__e(e)));
}).catch(e => reject(__e(e)));
});
}
mkdirs(list) {
return new Promise((resolve, reject) => {
if (list.length === 0) { return resolve(); }
const dir = (list.splice(0, 1))[0].asFileHandle();
const path = dir.parent();
const dname = dir.basename;
return path.asFileHandle().mk(dname)
.then(r => {
if (r.error) { return reject(this._api.throwe(__("Cannot create {0}", `${path}/${dir}`))); }
return this.mkdirs(list)
.then(() => resolve())
.catch(e => reject(__e(e)));
}).catch(e => reject(__e(e)));
});
}
installFile(n, zip, files) {
return new Promise((resolve, reject) => {
if (files.length === 0) { return resolve(); }
const file = (files.splice(0, 1))[0];
const path = `${this.installdir}/${n}/${file}`;
return zip.file(file).async("uint8array").then(d => {
const fp = path.asFileHandle();
fp.cache = new Blob([d], { type: "octet/stream" });
return fp.write("text/plain")
.then(r => {
if (r.error) { return reject(this._api.throwe(__("Cannot install {0}", path))); }
return this.installFile(n, zip, files)
.then(() => resolve())
.catch(e => reject( __e(e)));
}).catch(e => reject(__e(e)));
}).catch(e => reject(__e(e)));
});
}
}
MarketPlace.dependencies = [
"os://scripts/jszip.min.js",
"os://scripts/showdown.min.js"
];
MarketPlace.singleton = true;
this.OS.register("MarketPlace", MarketPlace);

View File

@ -0,0 +1,614 @@
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS205: Consider reworking code to avoid use of IIFEs
* DS208: Avoid top-level this
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
// 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 {
declare var showdown: any;
declare var JSZip: any;
export class MarketPlace extends BaseApplication {
private installdir: string;
private apps_meta: GenericObject<any>[];
private repo: GUI.tag.ListViewTag;
private applist: GUI.tag.ListViewTag;
private container: GUI.tag.VBoxTag;
private appname: GUI.tag.LabelTag;
private appdetail: HTMLUListElement;
private appdesc: HTMLParagraphElement;
private btinstall: GUI.tag.ButtonTag;
private btremove: GUI.tag.ButtonTag;
private btexec: GUI.tag.ButtonTag;
private searchbox: HTMLInputElement;
static RepoDialog: MarketPlaceRepoDialog;
constructor(args: AppArgumentsType[]) {
super("MarketPlace", args);
}
main(): void {
this.installdir = this.systemsetting.system.pkgpaths.user;
// test repository
this.apps_meta = [];
this.repo = this.find("repo") as GUI.tag.ListViewTag;
this.repo.onlistselect = (e) => {
const data = e.data.item.data;
if (!data) {
return;
}
return this.fetchApps(data);
};
this.refreshRepoList();
this.applist = this.find("applist") as GUI.tag.ListViewTag;
this.applist.onlistselect = (e) => {
const data = e.data.item.data;
return this.appDetail(data);
};
this.container = this.find("container") as GUI.tag.VBoxTag;
this.appname = this.find("appname") as GUI.tag.LabelTag;
this.appdesc = this.find("app-desc") as HTMLParagraphElement;
this.appdetail = this.find("app-detail") as HTMLUListElement;
this.btinstall = this.find("bt-install") as GUI.tag.ButtonTag;
this.btremove = this.find("bt-remove") as GUI.tag.ButtonTag;
this.btexec = this.find("bt-exec") as GUI.tag.ButtonTag;
this.searchbox = this.find("searchbox") as HTMLInputElement;
$(this.container).css("visibility", "hidden");
this.btexec.onbtclick = (e) => {
const el = this.applist.selectedItem;
if (!el) {
return;
}
const app = el.data;
if (app.pkgname) {
return this._gui.launch(app.pkgname, []);
}
};
this.btinstall.onbtclick = async () => {
if (this.btinstall.data.dirty) {
try {
await this.updatePackage();
return this.notify(__("Package updated"));
} catch (e) {
return this.error(e.toString(), e);
}
}
try {
const n = await this.remoteInstall();
return this.notify(__("Package installed: {0}", n));
} catch (e_1) {
return this.error(e_1.toString(), e_1);
}
};
this.btremove.onbtclick = async () => {
try {
await this.uninstall();
return this.notify(__("Packaged uninstalled"));
} catch (e) {
return this.error(e.toString(), e);
}
};
this.bindKey("CTRL-R", () => {
return this.menuOptionsHandle("repos");
});
$(this.searchbox).keyup((e) => this.search(e));
}
refreshRepoList(): void {
const list = Array.from(this.systemsetting.system.repositories);
list.unshift({
text: "Installed",
url: undefined,
});
this.repo.data = list;
}
private search(e: JQuery.KeyboardEventBase) {
let v: GenericObject<any>;
switch (e.which) {
case 37:
return e.preventDefault();
case 38:
this.applist.selectPrev();
return e.preventDefault();
case 39:
return e.preventDefault();
case 40:
this.applist.selectNext();
return e.preventDefault();
case 13:
return e.preventDefault();
default:
var text = this.searchbox.value;
if (text.length === 2) {
this.applist.data = (() => {
const result1 = [];
for (v of this.apps_meta) {
result1.push(v);
}
return result1;
})();
}
if (text.length < 3) {
return;
}
var result = [];
var term = new RegExp(text, "i");
for (v of this.apps_meta) {
if (v.text.match(term)) {
result.push(v);
}
}
this.applist.data = result;
}
}
private fetchApps(data: GenericObject<any>): void {
let v: API.PackageMetaType;
if (!data.url) {
const pkgcache = this.systemsetting.system.packages;
const list = [];
for (let k in pkgcache) {
v = pkgcache[k];
list.push({
pkgname: v.pkgname ? v.pkgname : v.app,
name: v.name,
text: v.name,
icon: v.icon,
iconclass: v.iconclass,
category: v.category,
author: v.info.author,
version: v.version,
description: `${v.path}/REAME.md`,
});
}
this.apps_meta = list;
this.applist.data = list;
return;
}
this._api
.get(data.url + "?_=" + new Date().getTime(), "json")
.then((d) => {
for (v of d) {
v.text = v.name;
v.iconclass = "fa fa-adn";
}
this.apps_meta = d;
return (this.applist.data = d);
})
.catch((e) => {
return this.error(
__(
"Fail to fetch packages list from: {0}",
data.url
),
e
);
});
}
private appDetail(d: GenericObject<any>): void {
$(this.container).css("visibility", "visible");
this.appname.text = d.name;
const status = this.find("vstat") as GUI.tag.LabelTag;
status.text = "";
if (d.description) {
d.description
.asFileHandle()
.read()
.then((text) => {
const converter = new showdown.Converter();
return $(this.appdesc).html(
converter.makeHtml(text)
);
})
.catch((e) => {
this.notify(
__("Unable to read package description")
);
return $(this.appdesc).empty();
});
} else {
$(this.appdesc).empty();
}
const pkgcache = this.systemsetting.system.packages;
this.btinstall.text = "__(Install)";
this.btinstall.data = { dirty: false };
if (pkgcache[d.pkgname]) {
let vs: Version, ovs: Version;
if (pkgcache[d.pkgname].version)
vs = pkgcache[d.pkgname].version.__v();
if (d.version) ovs = d.version.__v();
$(this.btinstall).hide();
if (vs && ovs) {
if (ovs.nt(vs)) {
this.btinstall.data = { dirty: true };
this.btinstall.text = "__(Update)";
$(this.btinstall).show();
status.text = __(
"Your application version is older ({0} < {1})",
vs,
ovs
);
}
}
$(this.btremove).show();
$(this.btexec).show();
} else {
$(this.btinstall).show();
$(this.btremove).hide();
$(this.btexec).hide();
}
$(this.appdetail).empty();
for (let k in d) {
const v = d[k];
if (k !== "name" && k !== "description" && k !== "domel") {
$(this.appdetail).append(
$("<li>")
.append(
$("<span class= 'info-header'>").html(k)
)
.append($("<span>").html(v))
);
}
}
}
protected menu(): GUI.BasicItemType[] {
return [
{
text: "__(Options)",
nodes: [
{
text: "__(Repositories)",
shortcut: "C-R",
id: "repos",
},
{
text: "__(Install from zip)",
shortcut: "C-I",
id: "install",
},
],
onchildselect: (
e: GUI.TagEventType<GUI.tag.MenuEventData>
) => {
return this.menuOptionsHandle(e.data.item.data.id);
},
},
];
}
private menuOptionsHandle(id: string): void {
switch (id) {
case "repos":
this.openDialog(new MarketPlace.RepoDialog(), {
title: __("Repositories"),
data: this.systemsetting.system.repositories,
});
break;
case "install":
this.localInstall()
.then((n) => {
return this.notify(
__("Package installed: {0}", n)
);
})
.catch((e) =>
this.error(__("Unable to install package"), e)
);
break;
default:
}
}
private remoteInstall(): Promise<string> {
const el = this.applist.selectedItem;
if (!el) {
return;
}
const app = el.data;
if (!app) {
return;
}
// get blob file
return new Promise(async (resolve, reject) => {
try {
const data = await this._api.blob(
app.download + "?_=" + new Date().getTime()
);
try {
const n = await this.install(data, app);
return resolve(n);
} catch (e) {
return reject(__e(e));
}
} catch (e_1) {
return reject(__e(e_1));
}
});
}
private localInstall(): Promise<string> {
return new Promise((resolve, reject) => {
return this.openDialog("FileDialog", {
title: "__(Select package archive)",
mimes: [".*/zip"],
}).then((d) => {
return d.file.path
.asFileHandle()
.read("binary")
.then((data: Uint8Array) => {
return this.install(data)
.then((n) => {
this.repo.unselect();
this.repo.selected = 0;
const apps = this.applist.data.map(
(v) => v.pkgname
);
const idx = apps.indexOf(n);
if (idx >= 0) {
this.applist.selected = idx;
}
return resolve(n);
})
.catch((e: Error) => reject(__e(e)))
.catch((e: Error) => reject(__e(e)));
})
.catch((e: Error) => reject(__e(e)));
});
});
}
private install(
data: ArrayBuffer,
meta?: GenericObject<any>
): Promise<string> {
return new Promise((resolve, reject) => {
return JSZip.loadAsync(data)
.then((zip: any) => {
return zip
.file("package.json")
.async("string")
.then((d: string) => {
let name: string;
const v = JSON.parse(d);
const pth = `${this.installdir}/${v.app}`;
const dir = [pth];
const files = [];
for (name in zip.files) {
const file = zip.files[name];
if (file.dir) {
dir.push(pth + "/" + name);
} else {
files.push(name);
}
}
// create all directory
return this.mkdirs(dir)
.then(() => {
return this.installFile(
v.app,
zip,
files
)
.then(() => {
const app_meta = {
pkgname: v.app,
name: v.name,
text: v.name,
icon: v.icon,
iconclass: v.iconclass,
category: v.category,
author: v.info.author,
version: v.version,
description: meta
? meta.description
: undefined,
download: meta
? meta.download
: undefined,
};
v.text = v.name;
v.filename = v.app;
v.type = "app";
v.mime = "antos/app";
if (
!v.iconclass &&
!v.icon
) {
v.iconclass =
"fa fa-adn";
}
v.path = pth;
this.systemsetting.system.packages[
v.app
] = v;
this.appDetail(app_meta);
return resolve(v.name);
})
.catch((e) => reject(__e(e)));
})
.catch((e) => reject(__e(e)));
})
.catch((err: Error) => reject(__e(err)));
})
.catch((e: Error) => reject(__e(e)));
});
}
private uninstall(): Promise<any> {
return new Promise(async (resolve, reject) => {
const el = this.applist.selectedItem;
if (!el) {
return;
}
const sel = el.data;
if (!sel) {
return;
}
const name = sel.pkgname;
const app = this.systemsetting.system.packages[sel.pkgname];
if (!app) {
return;
}
try {
const d = await this.openDialog("YesNoDialog", {
title: __("Uninstall"),
text: __("Uninstall: {0}?", app.name),
});
if (!d) {
return;
}
try {
const r = await app.path
.asFileHandle()
.remove();
if (r.error) {
return reject(this._api.throwe(__("Cannot uninstall package: {0}", r.error)));
}
this.notify(__("Package uninstalled"));
// stop all the services if any
if (app.services) {
for (let srv of Array.from(app.services)) {
this._gui.unloadApp(srv);
}
}
delete this.systemsetting.system.packages[name];
this._gui.unloadApp(name);
if (sel.download) {
this.appDetail(sel);
}
else {
this.applist.delete(el);
$(this.container).css("visibility", "hidden");
}
return resolve();
}
catch (e) {
return reject(__e(e));
}
}
catch (e_1) {
return reject(__e(e_1));
}
});
}
private updatePackage(): Promise<any>{
return new Promise(async (resolve, reject) => {
try {
await this.uninstall();
try {
await this.remoteInstall();
return resolve();
}
catch (e) {
return reject(__e(e));
}
}
catch (e_1) {
return reject(__e(e_1));
}
});
}
private mkdirs(list: string[]): Promise<any> {
return new Promise((resolve, reject) => {
if (list.length === 0) {
return resolve();
}
const dir = list.splice(0, 1)[0].asFileHandle();
const path = dir.parent();
const dname = dir.basename;
return path
.asFileHandle()
.mk(dname)
.then((r) => {
if (r.error) {
return reject(
this._api.throwe(
__(
"Cannot create {0}",
`${path}/${dir}`
)
)
);
}
return this.mkdirs(list)
.then(() => resolve())
.catch((e) => reject(__e(e)));
})
.catch((e) => reject(__e(e)));
});
}
private installFile(n: string, zip: any, files: string[]): Promise<any>{
return new Promise((resolve, reject) => {
if (files.length === 0) {
return resolve();
}
const file = files.splice(0, 1)[0];
const path = `${this.installdir}/${n}/${file}`;
return zip
.file(file)
.async("uint8array")
.then((d: Uint8Array) => {
const fp = path.asFileHandle();
fp.cache = new Blob([d], { type: "octet/stream" });
return fp
.write("text/plain")
.then((r) => {
if (r.error) {
return reject(
this._api.throwe(
__("Cannot install {0}", path)
)
);
}
return this.installFile(n, zip, files)
.then(() => resolve())
.catch((e) => reject(__e(e)));
})
.catch((e) => reject(__e(e)));
})
.catch((e: Error) => reject(__e(e)));
});
}
}
MarketPlace.dependencies = [
"os://scripts/jszip.min.js",
"os://scripts/showdown.min.js",
];
MarketPlace.singleton = true;
}
}

View File

@ -10,7 +10,7 @@
</afx-vbox>
<afx-resizer data-width = "3" ></afx-resizer>
<afx-vbox data-id = "container">
<div data-id = "appname" data-height = "25"></div>
<afx-label data-id = "appname" data-height = "25"></afx-label>
<afx-hbox data-height = "30">
<div style = "text-align:left;">
<afx-button data-id = "bt-remove" text = "__(Uninstall)"></afx-button>