From 19304cd1ae4e4ba4f6dcf4f1db14de1e4e70c250 Mon Sep 17 00:00:00 2001 From: Wojtek Kosior Date: Mon, 10 Jan 2022 23:38:56 +0100 Subject: improve item list styling; add payload creation form; exend dialog mechanism --- html/payload_create.js | 229 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 229 insertions(+) create mode 100644 html/payload_create.js (limited to 'html/payload_create.js') diff --git a/html/payload_create.js b/html/payload_create.js new file mode 100644 index 0000000..db63a82 --- /dev/null +++ b/html/payload_create.js @@ -0,0 +1,229 @@ +/** + * This file is part of Haketilo. + * + * Function: Driving the site payload creation form. + * + * Copyright (C) 2022 Wojtek Kosior + * + * 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. + * + * As additional permission under GNU GPL version 3 section 7, you + * may distribute forms of that code without the copy of the GNU + * GPL normally required by section 4, provided you include this + * license notice and, in case of non-source distribution, a URL + * through which recipients can access the Corresponding Source. + * If you modify file(s) with this exception, you may extend this + * exception to your version of the file(s), but you are not + * obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + * + * As a special exception to the GPL, any HTML file which merely + * makes function calls to this code, and for that purpose + * includes it by reference shall be deemed a separate work for + * copyright law purposes. If you modify this code, you may extend + * this exception to your version of the code, but you are not + * obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * I, Wojtek Kosior, thereby promise not to sue for violation of this file's + * license. Although I request that you do not make use of this code in a + * proprietary program, I am not going to enforce this in court. + */ + +#IMPORT html/dialog.js +#IMPORT common/indexeddb.js AS haketilodb + +#FROM html/DOM_helpers.js IMPORT clone_template +#FROM common/sha256.js IMPORT sha256 +#FROM common/patterns.js IMPORT deconstruct_url + +/* https://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid */ +/* This is a helper function used by uuidv4(). */ +function uuid_replace_num(num) +{ + const randbyte = crypto.getRandomValues(new Uint8Array(1))[0]; + return (num ^ randbyte & 15 >> num / 4).toString(16); +} + +/* Generate a new UUID. */ +function uuidv4() +{ + return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, uuid_replace_num); +} + +function collect_form_data(form_ctx) +{ + const identifier_nonprepended = form_ctx.identifier.value; + if (!identifier_nonprepended) + throw "The 'identifier' field is required!"; + if (!/[-a-zA-Z]+/.test(identifier_nonprepended)) + throw "Identifier may only contain digits 0-9, lowercase letters a-z and hyphens '-'!" + const identifier = `local-${identifier_nonprepended}`; + + const long_name = form_ctx.long_name.value || identifier_nonprepended; + + const description = form_ctx.description.value; + + const url_patterns = form_ctx.patterns.value.split("\n").filter(i => i); + if (url_patterns.length === 0) + throw "The 'URL patterns' field is required!"; + + const payloads = {}; + + for (const pattern of url_patterns) { + try { + deconstruct_url(pattern); + } catch(e) { + const patterns_doc_url = + "https://hydrillabugs.koszko.org/projects/haketilo/wiki/URL_patterns"; + const patterns_doc_link = document.createElement("a"); + patterns_doc_link.href = patterns_doc_url; + patterns_doc_link.innerText = "here"; + const msg = document.createElement("span"); + msg.prepend(`'${pattern}' is not a valid URL pattern. See `, + patterns_doc_link, " for more details."); + throw msg; + } + + if (pattern in payloads) + throw `Pattern '${pattern}' soecified multiple times!`; + + payloads[pattern] = {identifier}; + } + + const script = form_ctx.script.value; + if (!script) + throw "The 'script' field is required!"; + const hash_key = `sha256-${sha256(script)}`; + + const resource = { + source_name: identifier, + source_copyright: [], + type: "resource", + identifier, + long_name, + uuid: uuidv4(), + version: [1], + description, + dependencies: [], + scripts: [{file: "payload.js", hash_key}] + }; + + const mapping = { + source_name: identifier, + source_copyright: [], + type: "mapping", + identifier, + long_name, + uuid: uuidv4(), + version: [1], + description, + payloads + }; + + return {identifier, resource, mapping, files: {[hash_key]: script}}; +} + +async function save_payload(saving) +{ + const db = await haketilodb.get(); + const tx_starter = haketilodb.start_items_transaction; + const tx_ctx = await tx_starter(["resources", "mappings"], saving.files); + + for (const [type, store_name] of + [["resource", "resources"], ["mapping", "mappings"]]) { + if (!saving[`override_${type}`] && + (await haketilodb.idb_get(tx_ctx.transaction, store_name, + saving.identifier))) { + saving.ask_override = "resource"; + return; + } + } + + await haketilodb.save_item(saving.resource, tx_ctx); + await haketilodb.save_item(saving.mapping, tx_ctx); + + return haketilodb.finalize_transaction(tx_ctx); +} + +function override_question(saving) +{ + return saving.ask_override === "resource" ? + `Resource '${saving.identifier}' alredy exists. Override?` : + `Mapping '${saving.identifier}' alredy exists. Override?`; +} + +async function create_clicked(form_ctx) +{ + if (form_ctx.dialog_ctx.shown) + return; + + dialog.loader(form_ctx.dialog_ctx, "Saving payload..."); + + try { + var saving = collect_form_data(form_ctx); + } catch(e) { + dialog.error(form_ctx.dialog_ctx, e); + dialog.close(form_ctx.dialog_ctx); + return; + } + + try { + do { + if (saving.ask_override) { + const override_prom = dialog.ask(form_ctx.dialog_ctx, + override_question(saving)); + dialog.loader(form_ctx.dialog_ctx, "Saving payload..."); + dialog.close(form_ctx.dialog_ctx); + if (!(await override_prom)) + throw "Saving would override existing data."; + + saving[`override_${saving.ask_override}`] = true; + delete saving.ask_override; + } + + await save_payload(saving); + } while (saving.ask_override); + + dialog.info(form_ctx.dialog_ctx, "Successfully saved payload!"); + } catch(e) { + console.error(e); + dialog.error(form_ctx.dialog_ctx, "Failed to save payload :(") + } + + dialog.close(form_ctx.dialog_ctx); +} + +function on_show_hide(form_ctx, what_to_show) +{ + for (const item_id of ["form", "dialog"]) { + const action = item_id === what_to_show ? "remove" : "add"; + form_ctx[`${item_id}_container`].classList[action]("hide"); + } +} + +function payload_create_form() +{ + const form_ctx = clone_template("payload_create"); + + form_ctx.dialog_ctx = dialog.make(() => on_show_hide(form_ctx, "dialog"), + () => on_show_hide(form_ctx, "form")); + form_ctx.dialog_container.prepend(form_ctx.dialog_ctx.main_div); + + form_ctx.create_but.addEventListener("click", + () => create_clicked(form_ctx)); + + return form_ctx; +} +#EXPORT payload_create_form -- cgit v1.2.3