aboutsummaryrefslogtreecommitdiff
path: root/html/payload_create.js
diff options
context:
space:
mode:
Diffstat (limited to 'html/payload_create.js')
-rw-r--r--html/payload_create.js229
1 files changed, 229 insertions, 0 deletions
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 <https://www.gnu.org/licenses/>.
+ *
+ * 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