From 723941747cb364f66cb4c16e450f278b5a1c1d91 Mon Sep 17 00:00:00 2001 From: joshuaboud Date: Wed, 14 Jul 2021 17:10:56 -0300 Subject: [PATCH] create dynamic prompts for input --- navigator/components/ModalPrompt.js | 104 ++++++++++++++++++++++++- navigator/components/NavContextMenu.js | 13 +++- navigator/components/NavWindow.js | 86 +++++++++++++++++--- navigator/scripts/paste.py | 18 ++--- 4 files changed, 195 insertions(+), 26 deletions(-) diff --git a/navigator/components/ModalPrompt.js b/navigator/components/ModalPrompt.js index 6af428b..6893992 100644 --- a/navigator/components/ModalPrompt.js +++ b/navigator/components/ModalPrompt.js @@ -2,7 +2,10 @@ export class ModalPrompt { constructor() { this.ok = document.createElement("button"); this.ok.innerText = "OK"; - this.ok.classList.add("pf-c-button", "pf-m-secondary"); + this.ok.classList.add("pf-c-button", "pf-m-primary"); + this.cancel = document.createElement("button"); + this.cancel.innerText = "Cancel"; + this.cancel.classList.add("pf-c-button", "pf-m-secondary"); this.yes = document.createElement("button"); this.yes.innerText = "Yes"; this.yes.classList.add("pf-c-button", "pf-m-primary"); @@ -55,11 +58,21 @@ export class ModalPrompt { this.header.innerText = header; } + /** + * + * @param {string} message + */ set_body(message) { this.body.innerHTML = ""; this.body.innerText = message; } + /** + * + * @param {string} header + * @param {string} message + * @returns {Promise} + */ alert(header, message = "") { this.set_header(header); this.set_body(message); @@ -74,12 +87,18 @@ export class ModalPrompt { }); } + /** + * + * @param {string} header + * @param {string} message + * @returns {Promise} + */ confirm(header, message = "") { this.set_header(header); this.set_body(message); this.footer.innerHTML = ""; - this.footer.appendChild(this.no); - this.footer.appendChild(this.yes); + this.footer.append(this.no, this.yes); + // this.footer.appendChild(this.yes); this.show(); return new Promise((resolve, reject) => { let resolve_true = () => { @@ -90,9 +109,86 @@ export class ModalPrompt { resolve(false); this.hide(); } - this.confirm.onclick = this.yes.onclick = resolve_true; + this.yes.onclick = resolve_true; this.no.onclick = resolve_false; }); } + + /** + * + * @param {string} header + * @param {Object.} request + * @returns {Promise} + */ + prompt(header, requests) { + this.set_header(header); + this.body.innerHTML = ""; + this.footer.innerHTML = ""; + this.footer.append(this.cancel, this.ok); + let inputs = []; + + if (typeof requests === "object") { + let req_holder = document.createElement("div"); + req_holder.style.display = "flex"; + req_holder.style.flexFlow = "column nowrap"; + req_holder.style.alignItems = "stretch"; + this.body.appendChild(req_holder); + for(let key of Object.keys(requests)) { + let row = document.createElement("div"); + row.style.display = "flex"; + row.style.alignItems = "baseline"; + row.style.padding = "2px"; + let request = requests[key]; + let label = document.createElement("label"); + label.for = key; + label.innerText = request.label; + label.style.marginRight = "1em"; + label.style.flexBasis = "0"; + label.style.flexGrow = "1"; + let req = document.createElement("input"); + req.id = key; + req.type = request.type; + req.style.flexBasis = "0"; + if (request.hasOwnProperty("default")) { + req.value = request.default; + } + row.append(label, req); + req_holder.appendChild(row); + inputs.push(req); + switch (request.type) { + case "text": + req.style.flexGrow = "3"; + break; + default: + break; + } + } + } + + this.show(); + inputs[0].focus(); + return new Promise((resolve, reject) => { + this.ok.onclick = () => { + let response = {}; + for (let input of inputs) { + switch (input.type) { + case "checkbox": + response[input.id] = input.checked; + break; + case "text": + default: + response[input.id] = input.value; + break; + } + } + resolve(response); + this.hide(); + } + this.cancel.onclick = () => { + resolve(null); + this.hide(); + } + }); + } } diff --git a/navigator/components/NavContextMenu.js b/navigator/components/NavContextMenu.js index 2753b5b..59aff55 100644 --- a/navigator/components/NavContextMenu.js +++ b/navigator/components/NavContextMenu.js @@ -71,9 +71,18 @@ export class NavContextMenu { async rename() { this.hide(); - var new_name = window.prompt("New Name: ", this.target.filename()); - if (new_name === null) + let response = await this.nav_window_ref.modal_prompt.prompt("Renaming " + this.target.filename(), + { + new_name: { + label: "New Name: ", + type: "text", + default: this.target.filename() + } + } + ); + if (response === null) return; + var new_name = response.new_name; if (new_name.includes("/")) { this.nav_window_ref.modal_prompt.alert("File name can't contain `/`."); return; diff --git a/navigator/components/NavWindow.js b/navigator/components/NavWindow.js index de582e1..d10479e 100644 --- a/navigator/components/NavWindow.js +++ b/navigator/components/NavWindow.js @@ -392,9 +392,21 @@ export class NavWindow { } async mkdir() { - var new_dir_name = window.prompt("Directory Name: "); - if (new_dir_name === null) + let response = await this.modal_prompt.prompt("Creating Directory", + { + new_name: { + label: "Name: ", + type: "text" + } + } + ); + if (response === null) return; + var new_dir_name = response.new_name; + if (new_dir_name === "") { + this.modal_prompt.alert("Directory name can't be empty."); + return; + } if (new_dir_name.includes("/")) { this.modal_prompt.alert("Directory name can't contain `/`."); return; @@ -420,9 +432,21 @@ export class NavWindow { } async touch() { - var new_file_name = window.prompt("File Name: "); - if (new_file_name === null) + let response = await this.modal_prompt.prompt("Creating File", + { + new_name: { + label: "Name: ", + type: "text" + } + } + ); + if (response === null) return; + var new_file_name = response.new_name; + if (new_file_name === "") { + this.modal_prompt.alert("File name can't be empty."); + return; + } if (new_file_name.includes("/")) { this.modal_prompt.alert("File name can't contain `/`."); return; @@ -448,12 +472,31 @@ export class NavWindow { } async ln(default_target = "") { - var link_target = window.prompt("Link Target: ", default_target); - if (link_target === null) + let response = await this.modal_prompt.prompt("Creating Symbolic Link", + { + target: { + label: "Target: ", + type: "text", + default: default_target + }, + name: { + label: "Name: ", + type: "text" + } + } + ); + if (response === null) return; - var link_name = window.prompt("Link Name: "); - if (link_name === null) + var link_target = response.target; + if (link_target === "") { + this.modal_prompt.alert("Link target can't be empty."); return; + } + var link_name = response.name; + if (link_name === "") { + this.modal_prompt.alert("Link name can't be empty."); + return; + } if (link_name.includes("/")) { this.modal_prompt.alert("Link name can't contain `/`."); return; @@ -499,7 +542,6 @@ export class NavWindow { } async paste_clipboard() { - this.start_load(); var cmd = ["/usr/share/cockpit/navigator/scripts/paste.py"]; var dest = this.pwd().path_str(); if (this.copy_or_move === "move") { @@ -519,8 +561,30 @@ export class NavWindow { proc.stream(async (data) => { var payload = JSON.parse(data); if (payload["wants-response"]) { - var user_response = await this.modal_prompt.confirm(payload["message"]); - proc.input(JSON.stringify(user_response) + "\n", true); + if (payload.hasOwnProperty("conflicts")) { + let requests = {}; + for (let conflict of payload["conflicts"]) { + requests[conflict[0]] = { + label: conflict[1], + type: "checkbox", + default: false + } + } + let responses = await this.modal_prompt.prompt("Overwrite?", requests); + if (responses === null) { + proc.input(JSON.stringify("abort") + "\n"); + return; + } + let keepers = []; + for (let response of Object.keys(responses)) { + if (responses[response]) + keepers.push(response) + } + proc.input(JSON.stringify(keepers) + "\n", true); + } else { + var user_response = await this.modal_prompt.confirm(payload["message"]); + proc.input(JSON.stringify(user_response) + "\n", true); + } } else { await this.modal_prompt.alert(payload["message"]); } diff --git a/navigator/scripts/paste.py b/navigator/scripts/paste.py index a832c34..beae6be 100755 --- a/navigator/scripts/paste.py +++ b/navigator/scripts/paste.py @@ -28,15 +28,19 @@ from optparse import OptionParser import json import subprocess -def prompt_user(message, wants_response): +def prompt_user(message, wants_response, conflicts = None): payload = { "wants-response": wants_response, "message": message } + if conflicts != None: + payload["conflicts"] = conflicts print(json.dumps(payload) + "\n") if wants_response: - response = input() - return json.loads(response) + response = json.loads(input()) + if isinstance(response, str) and response == "abort": + sys.exit(0) + return response return def split_paths_at_cwd(paths, cwd): @@ -70,12 +74,8 @@ def filter_existing(args, cwd): dest = args[-1] (conflicts, non_conflicts) = recursive_get_conflicts(sources, cwd, dest) if len(conflicts): - check_continue = prompt_user("Conflicts were found while pasting. `Cancel` to abort operation, `OK` to overwrite selectively.", True) - if not check_continue: - sys.exit(0) - for conflict in conflicts: - if prompt_user("Overwrite " + conflict[1] + "?", True): - non_conflicts.append(conflict[0]) + conflicts = prompt_user("Overwrite?", True, conflicts) + non_conflicts.extend(conflicts) if not len(non_conflicts): sys.exit(0) # exit if nothing to copy filtered_args = [*split_paths_at_cwd(non_conflicts, cwd), dest]