aboutsummaryrefslogtreecommitdiff
path: root/_wui
diff options
context:
space:
mode:
authorShulhan <ms@kilabit.info>2023-10-30 13:29:34 +0700
committerShulhan <ms@kilabit.info>2023-10-30 13:29:34 +0700
commit3a679a4ea25287c2cdd1d00c7c757d87bceab629 (patch)
tree60a05e381801a1ef0b56be273cd84b732896fd0b /_wui
parent7680ad00edcd2087385f938327d869a181a46d1d (diff)
downloadawwan-3a679a4ea25287c2cdd1d00c7c757d87bceab629.tar.xz
_wui: fix saving on control+s on editor
Diffstat (limited to '_wui')
-rw-r--r--_wui/awwan.js242
-rw-r--r--_wui/awwan.ts46
-rw-r--r--_wui/main.js127
-rw-r--r--_wui/main.ts4
-rw-r--r--_wui/tsconfig.json4
m---------_wui/wui0
6 files changed, 243 insertions, 180 deletions
diff --git a/_wui/awwan.js b/_wui/awwan.js
index df34bce..2629660 100644
--- a/_wui/awwan.js
+++ b/_wui/awwan.js
@@ -4,66 +4,60 @@ import { WuiEditor } from "./wui/editor/editor.js";
import { WuiNotif } from "./wui/notif/notif.js";
import { WuiVfs } from "./wui/vfs/vfs.js";
const CLASS_EDITOR_ACTION = "editor_action";
-const ID_BTN_CLEAR_SELECTION = "com_btn_clear_selection";
const ID_BTN_EXEC_LOCAL = "com_btn_local";
const ID_BTN_EXEC_REMOTE = "com_btn_remote";
const ID_BTN_NEW_DIR = "com_btn_new_dir";
const ID_BTN_NEW_FILE = "com_btn_new_file";
const ID_BTN_REMOVE = "com_btn_remove";
const ID_BTN_SAVE = "com_btn_save";
+const ID_COM_RESIZE = "com_resize";
const ID_EDITOR = "com_editor";
+const ID_INP_LINE_RANGE = "com_inp_line_range";
const ID_INP_VFS_NEW = "com_inp_vfs_new";
const ID_VFS = "com_vfs";
const ID_VFS_PATH = "vfs_path";
-const ID_STDOUT = "stdout";
-const ID_STDERR = "stderr";
+const ID_OUTPUT = "output";
+const ID_OUTPUT_WRAPPER = "output_wrapper";
const MAX_FILE_SIZE = 3000000;
export function renderHtml() {
const el = document.createElement("div");
el.classList.add("awwan");
el.innerHTML = `
- <div class="awwan_nav_left">
- <div id="${ID_VFS}"></div>
+ <div class="awwan_nav_left">
+ <div id="${ID_VFS}"></div>
- <br/>
- <div class="${ID_INP_VFS_NEW}">
- <input id="${ID_INP_VFS_NEW}" />
- </div>
- <button id="${ID_BTN_NEW_DIR}">New directory</button>
- <button id="${ID_BTN_NEW_FILE}">New file</button>
- <button id="${ID_BTN_REMOVE}">Remove</button>
- </div>
- <div class="awwan_content">
- <div class="editor_file">
- File: <span id="${ID_VFS_PATH}">-</span>
- <button id="${ID_BTN_SAVE}" disabled="true">Save</button>
- </div>
- <div id="${ID_EDITOR}"></div>
- <div>
- <div class="${CLASS_EDITOR_ACTION}">
- <button id="${ID_BTN_CLEAR_SELECTION}">Clear selection</button>
- </div>
- <div class="${CLASS_EDITOR_ACTION}">
- Execute script on
- <button id="${ID_BTN_EXEC_LOCAL}" disabled="true">Local</button>
- or
- <button id="${ID_BTN_EXEC_REMOTE}" disabled="true">Remote</button>
- </div>
- </div>
- <p>Hints:</p>
- <ul>
- <li>
- Click and drag on the line numbers to select the specific line to be
- executed.
- </li>
- <li>Press ESC to clear selection.</li>
- </ul>
- <div class="boxheader">Standard output:</div>
- <div id="${ID_STDOUT}"></div>
- <div class="boxheader">Standard error:</div>
- <div id="${ID_STDERR}"></div>
- </div>
- `;
+ <br/>
+ <div class="${ID_INP_VFS_NEW}">
+ <input id="${ID_INP_VFS_NEW}" />
+ </div>
+ <button id="${ID_BTN_NEW_DIR}">New directory</button>
+ <button id="${ID_BTN_NEW_FILE}">New file</button>
+ <button id="${ID_BTN_REMOVE}">Remove</button>
+ </div>
+ <div class="awwan_content">
+ <div class="boxheader">
+ File: <span id="${ID_VFS_PATH}">-</span>
+ <button id="${ID_BTN_SAVE}" disabled="true">Save</button>
+ </div>
+ <div id="${ID_EDITOR}"></div>
+ <div>
+ <div class="${CLASS_EDITOR_ACTION}">
+ Execute
+ <input id="${ID_INP_LINE_RANGE}" />
+ on
+ <button id="${ID_BTN_EXEC_LOCAL}" disabled="true">Local</button>
+ or
+ <button id="${ID_BTN_EXEC_REMOTE}" disabled="true">Remote</button>
+
+ </div>
+ </div>
+ <button id="${ID_COM_RESIZE}">&#9868;</button>
+ <div id="${ID_OUTPUT_WRAPPER}" class="output">
+ <div class="boxheader">Output:</div>
+ <div id="${ID_OUTPUT}"></div>
+ </div>
+ </div>
+ `;
document.body.appendChild(el);
}
export class Awwan {
@@ -75,14 +69,9 @@ export class Awwan {
content: "",
line_range: "",
};
- let el = document.getElementById(ID_BTN_CLEAR_SELECTION);
- if (el) {
- this.comBtnClear = el;
- this.comBtnClear.onclick = () => {
- this.editor.clearSelection();
- };
- }
- el = document.getElementById(ID_BTN_EXEC_LOCAL);
+ this.orgContent = "";
+ this._posy = 0;
+ let el = document.getElementById(ID_BTN_EXEC_LOCAL);
if (el) {
this.comBtnLocal = el;
this.comBtnLocal.onclick = () => {
@@ -124,6 +113,12 @@ export class Awwan {
this.onClickSave();
};
}
+ el = document.getElementById(ID_INP_LINE_RANGE);
+ if (!el) {
+ console.error(`failed to get element by ID #${ID_INP_LINE_RANGE}`);
+ return;
+ }
+ this.comInputLineRange = el;
el = document.getElementById(ID_INP_VFS_NEW);
if (el) {
this.comInputVfsNew = el;
@@ -132,23 +127,27 @@ export class Awwan {
if (el) {
this.comFilePath = el;
}
- el = document.getElementById(ID_STDOUT);
+ el = document.getElementById(ID_OUTPUT);
if (el) {
- this.comStdout = el;
+ this.comOutput = el;
}
- el = document.getElementById(ID_STDERR);
+ el = document.getElementById(ID_OUTPUT_WRAPPER);
if (el) {
- this.comStderr = el;
+ this.comOutputWrapper = el;
}
const editorOpts = {
id: ID_EDITOR,
is_editable: true,
- onSelection: (beginAt, endAt) => {
- this.editorOnSelection(beginAt, endAt);
+ onSave: (content) => {
+ this.editorOnSave(content);
},
- onSave: this.editorOnSave,
+ onSelection: () => { },
};
this.editor = new WuiEditor(editorOpts);
+ el = document.getElementById(ID_EDITOR);
+ if (el) {
+ this.comEditor = el;
+ }
this.notif = new WuiNotif();
const vfsOpts = {
id: ID_VFS,
@@ -166,6 +165,17 @@ export class Awwan {
const url = new URL(hashchange.newURL);
this.onHashChange(url.hash);
};
+ const elResize = document.getElementById(ID_COM_RESIZE);
+ if (elResize) {
+ elResize.addEventListener("mousedown", () => {
+ this._posy = 0;
+ document.addEventListener("mousemove", this.doResize, false);
+ });
+ document.addEventListener("mouseup", () => {
+ document.removeEventListener("mousemove", this.doResize, false);
+ this._posy = 0;
+ });
+ }
}
onHashChange(hash) {
if (hash === "") {
@@ -174,6 +184,23 @@ export class Awwan {
hash = hash.substring(1);
this.vfs.openDir(hash);
}
+ // confirmWhenDirty check if the editor content has changes before opening
+ // new file.
+ // If yes, display dialog box to confirm whether continuing opening file
+ // or cancel it.
+ // It will return true to continue opening file or false if user wants to
+ // cancel it.
+ confirmWhenDirty() {
+ if (this.request.script === "") {
+ // No file opened yet.
+ return true;
+ }
+ const newContent = this.editor.getContent();
+ if (this.orgContent == newContent) {
+ return true;
+ }
+ return window.confirm("File has changes, continue without save?");
+ }
// open fetch the node content from remote server.
async open(path, isDir) {
const httpRes = await fetch("/awwan/api/fs?path=" + path);
@@ -197,6 +224,7 @@ export class Awwan {
this.comFilePath.innerText = path;
this.request.script = path;
this.editor.open(node);
+ this.orgContent = this.editor.getContent();
this.comBtnLocal.disabled = false;
this.comBtnRemote.disabled = false;
this.comBtnSave.disabled = false;
@@ -205,12 +233,18 @@ export class Awwan {
// openNode is an handler that will called when user click on of the
// item in the list.
async openNode(node) {
- const resAllow = this.isEditAllowed(node);
- if (resAllow.code != 200) {
- this.notif.error(resAllow.message);
- return resAllow;
+ let res = this.isEditAllowed(node);
+ if (res.code != 200) {
+ this.notif.error(res.message);
+ return res;
}
- const res = await this.open(node.path, node.is_dir);
+ if (!node.is_dir) {
+ const ok = this.confirmWhenDirty();
+ if (!ok) {
+ return res;
+ }
+ }
+ res = await this.open(node.path, node.is_dir);
return res;
}
isEditAllowed(node) {
@@ -272,21 +306,21 @@ export class Awwan {
return null;
}
this.notif.info(`File ${path} has been saved.`);
+ this.orgContent = content;
return res;
}
- editorOnSelection(begin, end) {
- const stmts = this.editor.lines.slice(begin, end + 1);
- for (const stmt of stmts) {
- console.log("stmt:", stmt.x, stmt.text);
- }
- }
// execLocal request to execute the selected script on local system.
execLocal() {
if (this.request.script == "") {
this.notif.error(`Execute on local: no file selected`);
return;
}
- this.httpApiExecute("local");
+ const lineRange = this.comInputLineRange.value.trim();
+ if (lineRange === "") {
+ this.notif.error(`Empty line range`);
+ return;
+ }
+ this.httpApiExecute("local", lineRange);
}
// execRemote request to execute the selected script on remote system.
execRemote() {
@@ -294,28 +328,18 @@ export class Awwan {
this.notif.error(`Execute on remote: no file selected`);
return;
}
- this.httpApiExecute("remote");
- }
- async httpApiExecute(mode) {
- let beginAt = 0;
- let endAt = 0;
- const selectionRange = this.editor.getSelectionRange();
- if (selectionRange.begin_at > 0) {
- beginAt = selectionRange.begin_at + 1;
- }
- if (selectionRange.end_at > 0) {
- endAt = selectionRange.end_at + 1;
+ const lineRange = this.comInputLineRange.value.trim();
+ if (lineRange === "") {
+ this.notif.error(`Empty line range`);
+ return;
}
- this.comStdout.innerText = "";
- this.comStderr.innerText = "";
+ this.httpApiExecute("remote", lineRange);
+ }
+ async httpApiExecute(mode, lineRange) {
+ this.comOutput.innerText = "";
this.request.mode = mode;
this.request.content = btoa(this.editor.getContent());
- if (beginAt === endAt) {
- this.request.line_range = `${beginAt}`;
- }
- else {
- this.request.line_range = `${beginAt}-${endAt}`;
- }
+ this.request.line_range = lineRange;
const httpRes = await fetch("/awwan/api/execute", {
method: "POST",
headers: {
@@ -329,11 +353,8 @@ export class Awwan {
this.notif.error(`Execute failed: ${res.message}`);
return;
}
- if (res.data.stdout) {
- this.comStdout.innerText = atob(res.data.stdout);
- }
- if (res.data.stderr) {
- this.comStderr.innerText = atob(res.data.stderr);
+ if (res.data.output) {
+ this.comOutput.innerText = atob(res.data.output);
}
this.notif.info(`Successfully execute ${this.request.script} on ${mode}.`);
}
@@ -407,5 +428,36 @@ export class Awwan {
this.notif.error(`remove: ${res.message}`);
return;
}
+ this.notif.info(`${res.message}`);
+ this.vfs.openDir(this.currentNode.path);
+ }
+ doResize(ev) {
+ if (this._posy == 0) {
+ this._posy = ev.screenY;
+ return true;
+ }
+ const diff = this._posy - ev.screenY;
+ if (diff > 0) {
+ this.resizeUp(diff);
+ }
+ else if (diff < 0) {
+ this.resizeDown(diff * -1);
+ }
+ this._posy = ev.screenY;
+ return true;
+ }
+ resizeUp(diff) {
+ if (this.comEditor.clientHeight <= 126) {
+ return;
+ }
+ this.comEditor.style.height = `${this.comEditor.clientHeight - diff}px`;
+ this.comOutputWrapper.style.height = `${this.comOutputWrapper.clientHeight + diff}px`;
+ }
+ resizeDown(diff) {
+ if (this.comOutputWrapper.clientHeight <= 126) {
+ return;
+ }
+ this.comEditor.style.height = `${this.comEditor.clientHeight + diff}px`;
+ this.comOutputWrapper.style.height = `${this.comOutputWrapper.clientHeight - diff}px`;
}
}
diff --git a/_wui/awwan.ts b/_wui/awwan.ts
index bb34796..a059d3c 100644
--- a/_wui/awwan.ts
+++ b/_wui/awwan.ts
@@ -98,10 +98,11 @@ export class Awwan {
content: "",
line_range: "",
};
- private editor: WuiEditor;
- private notif: WuiNotif;
- private vfs: WuiVfs;
- private orgContent: string;
+ private editor!: WuiEditor;
+ private notif!: WuiNotif;
+ private vfs!: WuiVfs;
+ private orgContent: string = "";
+ private _posy: number = 0;
constructor() {
let el = document.getElementById(ID_BTN_EXEC_LOCAL);
@@ -181,9 +182,15 @@ export class Awwan {
onSave: (content) => {
this.editorOnSave(content);
},
+ onSelection: () => {},
};
+
this.editor = new WuiEditor(editorOpts);
- this.comEditor = document.getElementById(ID_EDITOR);
+
+ el = document.getElementById(ID_EDITOR);
+ if (el) {
+ this.comEditor = el;
+ }
this.notif = new WuiNotif();
@@ -196,6 +203,7 @@ export class Awwan {
return this.openNode(node);
},
};
+
this.vfs = new WuiVfs(vfsOpts);
window.onhashchange = (ev: Event) => {
@@ -206,16 +214,17 @@ export class Awwan {
};
const elResize = document.getElementById(ID_COM_RESIZE);
+ if (elResize) {
+ elResize.addEventListener("mousedown", () => {
+ this._posy = 0;
+ document.addEventListener("mousemove", this.doResize, false);
+ });
- elResize.addEventListener("mousedown", () => {
- document._posy = 0;
- document.addEventListener("mousemove", this.doResize, false);
- });
-
- document.addEventListener("mouseup", () => {
- document.removeEventListener("mousemove", this.doResize, false);
- document._posy = 0;
- });
+ document.addEventListener("mouseup", () => {
+ document.removeEventListener("mousemove", this.doResize, false);
+ this._posy = 0;
+ });
+ }
}
onHashChange(hash: string) {
@@ -286,11 +295,11 @@ export class Awwan {
async openNode(node: WuiVfsNodeInterface): Promise<WuiResponseInterface> {
let res = this.isEditAllowed(node);
if (res.code != 200) {
- this.notif.error(resAllow.message);
+ this.notif.error(res.message);
return res;
}
- if (!node.isDir) {
+ if (!node.is_dir) {
const ok = this.confirmWhenDirty();
if (!ok) {
return res;
@@ -521,11 +530,12 @@ export class Awwan {
}
const diff = this._posy - ev.screenY;
if (diff > 0) {
- this._awwan.resizeUp(diff);
+ this.resizeUp(diff);
} else if (diff < 0) {
- this._awwan.resizeDown(diff * -1);
+ this.resizeDown(diff * -1);
}
this._posy = ev.screenY;
+ return true;
}
resizeUp(diff: number) {
diff --git a/_wui/main.js b/_wui/main.js
index 50c0e2a..ffc76ec 100644
--- a/_wui/main.js
+++ b/_wui/main.js
@@ -31,6 +31,9 @@ var awwan = (() => {
}
this.sel = sel;
this.range = document.createRange();
+ document.onkeydown = (ev) => {
+ this.onKeydownDocument(this, ev);
+ };
document.onkeyup = (ev) => {
this.onKeyupDocument(this, ev);
};
@@ -103,24 +106,9 @@ var awwan = (() => {
this.deleteLine(x);
this.setCaret(elTextPrev, off);
return false;
- case "Control":
- this.is_key_control = false;
- break;
case "Enter":
ev.preventDefault();
break;
- case "r":
- if (this.is_key_control) {
- ev.preventDefault();
- return;
- }
- break;
- case "z":
- if (this.is_key_control) {
- ev.preventDefault();
- return;
- }
- break;
default:
if (this.is_key_control) {
break;
@@ -173,9 +161,6 @@ var awwan = (() => {
this.el.scrollTop += 25;
}
return false;
- case "Control":
- this.is_key_control = true;
- break;
case "Delete":
ev.preventDefault();
isJoinLineAfter = false;
@@ -232,30 +217,6 @@ var awwan = (() => {
this.raw_lines[x] = textAfter;
this.setCaret(elText, off + 1);
break;
- case "r":
- if (this.is_key_control) {
- ev.preventDefault();
- this.doRedo();
- return;
- }
- break;
- case "s":
- if (this.is_key_control) {
- ev.preventDefault();
- ev.stopPropagation();
- if (this.opts.onSave) {
- this.opts.onSave(this.getContent());
- }
- return false;
- }
- break;
- case "z":
- if (this.is_key_control) {
- ev.preventDefault();
- this.doUndo();
- return;
- }
- break;
}
return true;
}
@@ -440,14 +401,44 @@ var awwan = (() => {
this.render();
this.setCaret(newline.elText, 0);
}
+ onKeydownDocument(ed, ev) {
+ switch (ev.key) {
+ case "Control":
+ ed.is_key_control = true;
+ return;
+ case "r":
+ if (ed.is_key_control) {
+ ev.preventDefault();
+ ed.doRedo();
+ }
+ return;
+ case "s":
+ if (ed.is_key_control) {
+ ev.preventDefault();
+ ev.stopPropagation();
+ if (ed.opts.onSave) {
+ ed.opts.onSave(ed.getContent());
+ }
+ }
+ return;
+ case "z":
+ if (ed.is_key_control) {
+ ev.preventDefault();
+ ed.doUndo();
+ }
+ return;
+ }
+ }
onKeyupDocument(ed, ev) {
switch (ev.key) {
+ case "Control":
+ ed.is_key_control = false;
+ return;
case "Escape":
ev.preventDefault();
ed.clearSelection();
- break;
+ return;
}
- return true;
}
render() {
this.el.innerHTML = "";
@@ -874,6 +865,8 @@ var awwan = (() => {
content: "",
line_range: ""
};
+ this.orgContent = "";
+ this._posy = 0;
let el = document.getElementById(ID_BTN_EXEC_LOCAL);
if (el) {
this.comBtnLocal = el;
@@ -943,10 +936,15 @@ var awwan = (() => {
is_editable: true,
onSave: (content) => {
this.editorOnSave(content);
+ },
+ onSelection: () => {
}
};
this.editor = new WuiEditor(editorOpts);
- this.comEditor = document.getElementById(ID_EDITOR);
+ el = document.getElementById(ID_EDITOR);
+ if (el) {
+ this.comEditor = el;
+ }
this.notif = new WuiNotif();
const vfsOpts = {
id: ID_VFS,
@@ -965,14 +963,16 @@ var awwan = (() => {
this.onHashChange(url.hash);
};
const elResize = document.getElementById(ID_COM_RESIZE);
- elResize.addEventListener("mousedown", () => {
- document._posy = 0;
- document.addEventListener("mousemove", this.doResize, false);
- });
- document.addEventListener("mouseup", () => {
- document.removeEventListener("mousemove", this.doResize, false);
- document._posy = 0;
- });
+ if (elResize) {
+ elResize.addEventListener("mousedown", () => {
+ this._posy = 0;
+ document.addEventListener("mousemove", this.doResize, false);
+ });
+ document.addEventListener("mouseup", () => {
+ document.removeEventListener("mousemove", this.doResize, false);
+ this._posy = 0;
+ });
+ }
}
onHashChange(hash) {
if (hash === "") {
@@ -1012,10 +1012,10 @@ var awwan = (() => {
window.location.hash = "#" + path;
return res;
}
- const resAllow2 = this.isEditAllowed(node);
- if (resAllow2.code != 200) {
- this.notif.error(resAllow2.message);
- return resAllow2;
+ const resAllow = this.isEditAllowed(node);
+ if (resAllow.code != 200) {
+ this.notif.error(resAllow.message);
+ return resAllow;
}
this.comFilePath.innerText = path;
this.request.script = path;
@@ -1031,10 +1031,10 @@ var awwan = (() => {
async openNode(node) {
let res = this.isEditAllowed(node);
if (res.code != 200) {
- this.notif.error(resAllow.message);
+ this.notif.error(res.message);
return res;
}
- if (!node.isDir) {
+ if (!node.is_dir) {
const ok = this.confirmWhenDirty();
if (!ok) {
return res;
@@ -1229,11 +1229,12 @@ var awwan = (() => {
}
const diff = this._posy - ev.screenY;
if (diff > 0) {
- this._awwan.resizeUp(diff);
+ this.resizeUp(diff);
} else if (diff < 0) {
- this._awwan.resizeDown(diff * -1);
+ this.resizeDown(diff * -1);
}
this._posy = ev.screenY;
+ return true;
}
resizeUp(diff) {
if (this.comEditor.clientHeight <= 126) {
@@ -1253,6 +1254,6 @@ var awwan = (() => {
// _wui/main.ts
renderHtml();
- document._awwan = new Awwan();
- document._awwan.onHashChange(window.location.hash);
+ var awwan = new Awwan();
+ awwan.onHashChange(window.location.hash);
})();
diff --git a/_wui/main.ts b/_wui/main.ts
index c946217..85e64a5 100644
--- a/_wui/main.ts
+++ b/_wui/main.ts
@@ -5,7 +5,7 @@ import { renderHtml, Awwan } from "./awwan";
renderHtml();
-document._awwan = new Awwan();
+const awwan = new Awwan();
// Open path based on hash.
-document._awwan.onHashChange(window.location.hash);
+awwan.onHashChange(window.location.hash);
diff --git a/_wui/tsconfig.json b/_wui/tsconfig.json
index 7e87de6..ee9ad8b 100644
--- a/_wui/tsconfig.json
+++ b/_wui/tsconfig.json
@@ -1,8 +1,7 @@
{
"compilerOptions": {
"target": "es2018",
- "module": "es2020",
- "isolatedModules": true,
+ "module": "amd",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
@@ -18,5 +17,6 @@
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"skipLibCheck": true,
+ "outFile": "main.js"
}
}
diff --git a/_wui/wui b/_wui/wui
-Subproject 85c3fc0431e7e75a41894d4669f6a46bbda5440
+Subproject fde0b661b85ec2c813a806d5406a47e8023ba9a