antos-frontend/src/packages/Files/main.ts

700 lines
28 KiB
TypeScript
Raw Normal View History

/*
* decaffeinate suggestions:
* 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 application {
interface FilesClipboardType {
cut: boolean;
file: API.VFS.BaseFileHandle;
}
interface FilesViewType {
icon: boolean;
list: boolean;
tree: boolean;
}
/**
*
*
* @export
* @class Files
* @extends {BaseApplication}
*/
export class Files extends BaseApplication {
private view: GUI.tag.FileViewTag;
private navinput: HTMLInputElement;
private navbar: GUI.tag.HBoxTag;
private currdir: API.VFS.BaseFileHandle;
private favo: GUI.tag.ListViewTag;
private clipboard: FilesClipboardType;
private viewType: FilesViewType;
private vfs_event_flag: boolean;
constructor(args: AppArgumentsType[]) {
super("Files", args);
}
/**
*
*
* @returns
* @memberof Files
*/
main(): void {
this.view = this.find("fileview") as GUI.tag.FileViewTag;
this.navinput = this.find("navinput") as HTMLInputElement;
this.navbar = this.find("nav-bar") as GUI.tag.HBoxTag;
if (this.args && this.args.length > 0) {
this.currdir = this.args[0].path.asFileHandle();
} else {
this.currdir = "home://".asFileHandle();
}
this.favo = this.find("favouri") as GUI.tag.ListViewTag;
this.clipboard = undefined;
this.viewType = this._api.switcher("icon", "list", "tree");
this.viewType.list = true;
this.view.contextmenuHandle = (e, m) => {
const file = this.view.selectedFile;
if (!file) {
return;
}
const apps = [];
if (file.type === "dir") {
file.mime = "dir";
}
for (let v of this._gui.appsByMime(file.mime)) {
apps.push({
text: v.text,
app: v.app,
icon: v.icon,
iconclass: v.iconclass,
});
}
m.items = [
{
text: "__(Open with)",
nodes: apps,
onchildselect: (e: GUI.TagEventType<GUI.tag.MenuEventData>) => {
if (!e) {
return;
}
const it = e.data.item.data;
return this._gui.launch(it.app, [file]);
},
},
this.mnFile(),
this.mnEdit(),
];
m.show(e);
};
this.view.onfileopen = (e) => {
if (!e.data) {
return;
}
if (e.data.type === "dir") {
return;
}
return this._gui.openWith(e.data);
};
this.favo.onlistselect = (e) => {
return this.view.path = e.data.item.data.path;
};
(this.find("btback") as GUI.tag.ButtonTag).onbtclick = () => {
if (this.currdir.isRoot()) {
return;
}
const p = this.currdir.parent();
this.favo.selected = -1;
return this.view.path = p.path;
};
$(this.navinput).keyup((e) => {
if (e.keyCode === 13) {
return this.view.path = $(this.navinput).val() as string;
}
}); //enter
this.view.fetch = (path) => {
return new Promise((resolve, reject) => {
let dir = path.asFileHandle();
dir
.read()
.then((d) => {
if (d.error) {
return reject(d.error);
}
if (!dir.isRoot()) {
const p = dir.parent();
p.filename = "[..]";
p.type = "dir";
d.result.unshift(p);
}
this.currdir = dir;
$(this.navinput).val(dir.path);
return resolve(d.result);
})
.catch((e) => reject(__e(e)));
});
};
this.vfs_event_flag = true;
this.view.ondragndrop = (e) => {
if (!e) {
return;
}
const src = e.data.from.data;
const des = e.data.to.data;
if (des.type === "file") {
return;
}
const file = src.path.asFileHandle();
// disable the vfs event on
// we update it manually
this.vfs_event_flag = false;
return file
.move(`${des.path}/${file.basename}`)
.then(() => {
if (this.view.view === "icon") {
this.view.path = this.view.path;
} else {
this.view.update(file.parent().path);
this.view.update(des.path);
}
//reenable the vfs event
return (this.vfs_event_flag = true);
})
.catch((e: Error) => {
// reenable the vfs event
this.vfs_event_flag = true;
return this.error(
__(
"Unable to move: {0} -> {1}",
src.path,
des.path
),
e
);
});
};
// application setting
if (this.setting.sidebar === undefined) {
this.setting.sidebar = true;
}
if (this.setting.nav === undefined) {
this.setting.nav = true;
}
if (this.setting.showhidden === undefined) {
this.setting.showhidden = false;
}
this.applyAllSetting();
// VFS mount point and event
const mntpoints = [];
for(let v of this.systemsetting.VFS.mountpoints)
{
mntpoints.push({
text: v.text,
path: v.path,
selected: false
});
}
this.favo.data = mntpoints;
//@favo.set "selected", -1
if (this.setting.view) {
this.view.view = this.setting.view;
}
this.subscribe("VFS", (d) => {
if (!this.vfs_event_flag) {
return;
}
if (["read", "publish", "download"].includes(d.data.m)) {
return;
}
if (
d.data.file.hash() === this.currdir.hash() ||
d.data.file.parent().hash() === this.currdir.hash()
) {
return this.view.path = this.currdir.path;
}
});
// bind keyboard shortcut
this.bindKey("CTRL-F", () =>
this.actionFile(`${this.name}-mkf`)
);
this.bindKey("CTRL-D", () =>
this.actionFile(`${this.name}-mkdir`)
);
this.bindKey("CTRL-U", () =>
this.actionFile(`${this.name}-upload`)
);
this.bindKey("CTRL-S", () =>
this.actionFile(`${this.name}-share`)
);
this.bindKey("CTRL-I", () =>
this.actionFile(`${this.name}-info`)
);
this.bindKey("CTRL-R", () =>
this.actionEdit(`${this.name}-mv`)
);
this.bindKey("CTRL-M", () =>
this.actionEdit(`${this.name}-rm`)
);
this.bindKey("CTRL-X", () =>
this.actionEdit(`${this.name}-cut`)
);
this.bindKey("CTRL-C", () =>
this.actionEdit(`${this.name}-copy`)
);
this.bindKey("CTRL-P", () =>
this.actionEdit(`${this.name}-paste`)
);
(this.find("btgrid") as GUI.tag.ButtonTag).onbtclick = (e) => {
this.view.view = "icon";
this.viewType.icon = true;
};
(this.find("btlist") as GUI.tag.ButtonTag).onbtclick = (e) => {
this.view.view = "list";
this.viewType.list = true;
};
this.view.path = this.currdir.path;
}
protected applySetting(k: string): void{
// view setting
switch (k) {
case "showhidden":
return this.view.showhidden = this.setting.showhidden;
case "nav":
return this.toggleNav(this.setting.nav);
case "sidebar":
return this.toggleSidebar(this.setting.sidebar);
}
}
private mnFile(): GUI.BasicItemType{
//console.log file
const arr: GUI.BasicItemType = {
text: "__(File)",
nodes: [
{
text: "__(New file)",
dataid: `${this.name}-mkf`,
shortcut: "C-F",
},
{
text: "__(New folder)",
dataid: `${this.name}-mkdir`,
shortcut: "C-D",
},
{
text: "__(Upload)",
dataid: `${this.name}-upload`,
shortcut: "C-U",
},
{
text: "__(Download)",
dataid: `${this.name}-download`,
},
{
text: "__(Share file)",
dataid: `${this.name}-share`,
shortcut: "C-S",
},
{
text: "__(Properties)",
dataid: `${this.name}-info`,
shortcut: "C-I",
},
],
onchildselect: (e: GUI.TagEventType<GUI.tag.MenuEventData>) =>
this.actionFile(e.data.item.data.dataid),
};
return arr;
}
private mnEdit(): GUI.BasicItemType{
return {
text: "__(Edit)",
nodes: [
{
text: "__(Rename)",
dataid: `${this.name}-mv`,
shortcut: "C-R",
},
{
text: "__(Delete)",
dataid: `${this.name}-rm`,
shortcut: "C-M",
},
{
text: "__(Cut)",
dataid: `${this.name}-cut`,
shortcut: "C-X",
},
{
text: "__(Copy)",
dataid: `${this.name}-copy`,
shortcut: "C-C",
},
{
text: "__(Paste)",
dataid: `${this.name}-paste`,
shortcut: "C-P",
},
],
onchildselect: (e: GUI.TagEventType<GUI.tag.MenuEventData>) =>
this.actionEdit(e.data.item.data.dataid),
};
}
protected menu(): GUI.BasicItemType[]{
const menu = [
this.mnFile(),
this.mnEdit(),
{
text: "__(View)",
nodes: [
{
text: "__(Refresh)",
dataid: `${this.name}-refresh`,
},
{
text: "__(Sidebar)",
switch: true,
checked: this.setting.sidebar,
dataid: `${this.name}-side`,
},
{
text: "__(Navigation bar)",
switch: true,
checked: this.setting.nav,
dataid: `${this.name}-nav`,
},
{
text: "__(Hidden files)",
switch: true,
checked: this.setting.showhidden,
dataid: `${this.name}-hidden`,
},
{
text: "__(Type)",
nodes: [
{
text: "__(Icon view)",
radio: true,
checked: this.viewType.icon,
dataid: `${this.name}-icon`,
type: "icon",
},
{
text: "__(List view)",
radio: true,
checked: this.viewType.list,
dataid: `${this.name}-list`,
type: "list",
},
{
text: "__(Tree view)",
radio: true,
checked: this.viewType.tree,
dataid: `${this.name}-tree`,
type: "tree",
},
],
onchildselect: (e: GUI.TagEventType<GUI.tag.MenuEventData>) => {
const { type } = e.data.item.data;
this.view.view = type;
return (this.viewType[type] = true);
},
},
],
onchildselect: (e: GUI.TagEventType<GUI.tag.MenuEventData>) => this.actionView(e),
},
];
return menu;
}
private toggleSidebar(b: boolean): void {
if (b) {
$(this.favo).show();
} else {
$(this.favo).hide();
}
return this.trigger("resize");
}
private toggleNav(b: boolean): void {
if (b) {
$(this.navbar).show();
} else {
$(this.navbar).hide();
}
return this.trigger("resize");
}
private actionView(e: GUI.TagEventType<GUI.tag.MenuEventData>): void{
const data = e.data.item.data;
switch (data.dataid) {
case `${this.name}-hidden`:
//@.view.set "showhidden", e.item.data.checked
return this.registry("showhidden", data.checked);
//@.setting.showhidden = e.item.data.checked
case `${this.name}-refresh`:
this.view.path = this.currdir.path;
return;
case `${this.name}-side`:
return this.registry("sidebar", data.checked);
//@setting.sidebar = e.item.data.checked
//@toggleSidebar e.item.data.checked
case `${this.name}-nav`:
return this.registry("nav", data.checked);
}
}
//@setting.nav = e.item.data.checked
//@toggleNav e.item.data.checked
private actionEdit(e: string): void{
const file = this.view.selectedFile;
switch (e) {
case `${this.name}-mv`:
if (!file) {
return;
}
this.openDialog("PromptDialog", {
title: "__(Rename)",
label: "__(File name)",
value: file.filename,
}).then(async (d) => {
if (d === file.filename) {
return;
}
try {
return file.path
.asFileHandle()
.move(`${this.currdir.path}/${d}`);
}
catch (e) {
return this.error(__("Fail to rename: {0}", file.path), e);
}
});
break;
case `${this.name}-rm`:
if (!file) {
return;
}
this.openDialog("YesNoDialog", {
title: "__(Delete)",
iconclass: "fa fa-question-circle",
text: __(
"Do you really want to delete: {0}?",
file.filename
),
}).then(async (d) => {
if (!d) {
return;
}
try {
return file.path
.asFileHandle()
.remove();
}
catch (e) {
return this.error(__("Fail to delete: {0}", file.path), e);
}
});
break;
case `${this.name}-cut`:
if (!file) {
return;
}
this.clipboard = {
cut: true,
file: file.path.asFileHandle(),
};
return this.notify(__("File {0} cut", file.filename));
case `${this.name}-copy`:
if (!file && file.type !== "dir") {
return;
}
this.clipboard = {
cut: false,
file: file.path.asFileHandle(),
};
return this.notify(
__("File {0} copied", file.filename)
);
case `${this.name}-paste`:
if (!this.clipboard) {
return;
}
if (this.clipboard.cut) {
this.clipboard.file
.move(
`${this.currdir.path}/${this.clipboard.file.basename}`
)
.then((r) => {
return (this.clipboard = undefined);
})
.catch((e) => {
return this.error(
__(
"Fail to paste: {0}",
this.clipboard.file.path
),
e
);
});
} else {
this.clipboard.file
.read("binary")
.then(async (d) => {
const blob = new Blob([d], {
type: this.clipboard.file.info.mime,
});
const fp = `${this.currdir.path}/${this.clipboard.file.basename}`.asFileHandle();
fp.cache = blob;
try {
const r = await fp.write(this.clipboard.file.info.mime);
return (this.clipboard = undefined);
}
catch (e) {
return this.error(__("Fail to paste: {0}", this.clipboard.file.path), e);
}
})
.catch((e) => {
return this.error(
__(
"Fail to read: {0}",
this.clipboard.file.path
),
e
);
});
}
break;
default:
this._api.handle.setting();
}
}
private actionFile(e: string): void{
const file = this.view.selectedFile;
switch (e) {
case `${this.name}-mkdir`:
this.openDialog("PromptDialog", {
title: "__(New folder)",
label: "__(Folder name)",
}).then(async (d) => {
try {
return this.currdir.mk(d);
}
catch (e) {
return this.error(__("Fail to create: {0}", d), e);
}
});
break;
case `${this.name}-mkf`:
this.openDialog("PromptDialog", {
title: "__(New file)",
label: "__(File name)",
}).then(async (d) => {
const fp = `${this.currdir.path}/${d}`.asFileHandle();
try {
return fp.write("text/plain");
}
catch (e) {
return this.error(__("Fail to create: {0}", fp.path));
}
});
break;
case `${this.name}-info`:
if (!file) {
return;
}
this.openDialog("InfoDialog", file);
break;
case `${this.name}-upload`:
this.currdir.upload().catch((e) => {
return this.error(
__("Fail to upload: {0}", e.toString()),
e
);
});
break;
case `${this.name}-share`:
if (!file || file.type !== "file") {
return;
}
file.path
.asFileHandle()
.publish()
.then((r) => {
return this.notify(
__("Shared url: {0}", r.result)
);
})
.catch((e) => {
return this.error(
__("Fail to publish: {0}", file.path),
e
);
});
break;
case `${this.name}-download`:
if (file.type !== "file") {
return;
}
file.path
.asFileHandle()
.download()
.catch((e) => {
return this.error(
__("Fail to download: {0}", file.path),
e
);
});
break;
default:
return console.log(e);
}
}
}
}
}