aboutsummaryrefslogtreecommitdiff
path: root/html
diff options
context:
space:
mode:
Diffstat (limited to 'html')
-rw-r--r--html/DOM_helpers.js2
-rw-r--r--html/base.css82
-rw-r--r--html/dialog.html72
-rw-r--r--html/dialog.js112
-rw-r--r--html/grid.css75
-rw-r--r--html/item_list.html61
-rw-r--r--html/item_list.js189
-rw-r--r--html/item_preview.html89
-rw-r--r--html/item_preview.js151
9 files changed, 766 insertions, 67 deletions
diff --git a/html/DOM_helpers.js b/html/DOM_helpers.js
index 55320cb..88092e5 100644
--- a/html/DOM_helpers.js
+++ b/html/DOM_helpers.js
@@ -70,7 +70,7 @@ function clone_template(template_id)
while (to_process.length > 0) {
const element = to_process.pop();
- const template_key = element.getAttribute("data-template");
+ let template_key = element.getAttribute("data-template") || element.id;
if (template_key)
result_object[template_key] = element;
diff --git a/html/base.css b/html/base.css
index dde50e7..6085f5f 100644
--- a/html/base.css
+++ b/html/base.css
@@ -34,101 +34,51 @@ body {
overflow: auto;
}
-.bold, h2 {
+.bold, h1, h2, h3 {
font-weight: bold;
}
+h1, h2, h3 {
+ margin: 0.3em;
+ text-shadow: 0 0 0 #454;
+}
+
h2 {
- margin: 8px;
font-size: 120%;
}
h3 {
- padding: 5px;
font-size: 108%;
- text-shadow: 0 0 0 #454;
}
-textarea {
- font-family: monospace;
+li {
+ margin-top: 0.25em;
+ margin-bottom: 0.25em;
}
-input[type="checkbox"], input[type="radio"], .hide {
- display: none;
-}
-
-.camouflage {
- visibility: hidden;
+li:first-child {
+ margin-top: 0;
}
-.show_next:not(:checked)+* {
+.hide {
display: none;
}
-.show_hide_next2:not(:checked)+* {
- display: none;
-}
-
-.show_hide_next2:checked+*+* {
- display: none;
-}
-
-button, .button {
+button {
background-color: #4CAF50;
border: none;
- border-radius: 8px;
+ border-radius: 0.4em;
color: white;
text-align: center;
text-decoration: none;
display: inline-block;
- padding: 6px 12px;
- margin: 2px 0px;
+ padding: 0.4em 0.8em;
-moz-user-select: none;
user-select: none;
cursor: pointer;
- font: 400 15px sans-serif;
-}
-
-button.slimbutton, .button.slimbutton {
- padding: 2px 4px;
- margin: 0;
+ font: 400 0.9em sans-serif;
}
button:hover, .button:hover {
box-shadow: 0 6px 8px 0 rgba(0,0,0,0.24), 0 17px 50px 0 rgba(0,0,0,0.19);
}
-
-aside {
- background: #3f8dc6;
- margin: 5px 0;
- padding: 0.3em 1em;
- border-radius: 3px;
- color: #fff;
-}
-
-textarea: {
- resize: none;
-}
-
-.has_bottom_line::after, .has_upper_line::before {
- content: "";
- display: block;
- height: 8px;
- background: linear-gradient(transparent, #555);
-}
-
-.has_bottom_line::after {
- background: linear-gradient(#555, transparent);
-}
-
-.has_bottom_thin_line {
- border-bottom: dashed #4CAF50 1px;
-}
-
-.has_upper_thin_line {
- border-top: dashed #4CAF50 1px;
-}
-
-.nowrap {
- white-space: nowrap;
-}
diff --git a/html/dialog.html b/html/dialog.html
new file mode 100644
index 0000000..d4e69b9
--- /dev/null
+++ b/html/dialog.html
@@ -0,0 +1,72 @@
+#IF !DIALOG_LOADED
+#DEFINE DIALOG_LOADED
+<!--
+ SPDX-License-Identifier: GPL-3.0-or-later OR CC-BY-SA-4.0
+
+ Show an error/info/question dalog.
+
+ This file is part of Haketilo.
+
+ Copyright (C) 2022 Wojtek Kosior <koszko@koszko.org>
+
+ File is dual-licensed. You can choose either GPLv3+, CC BY-SA or both.
+
+ 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/>.
+
+ I, Wojtek Kosior, thereby promise not to sue for violation of this file's
+ licenses. 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.
+ -->
+
+<!--
+ This is not a standalone page. This file is meant to be imported into other
+ HTML code.
+ -->
+
+#LOADCSS html/reset.css
+#LOADCSS html/base.css
+<style>
+ .left_space {
+ margin-left: 3em;
+ }
+ .right_space {
+ margin-right: 3em;
+ }
+ .dialog_buts {
+ margin-right: auto;
+ margin-left: auto;
+ max-width: -moz-fit-content;
+ max-width: fit-content;
+ }
+ .dialog_msg {
+ margin-bottom: 2em;
+ text-align: center;
+ }
+ .dialog_main_div {
+ margin: 1.4em;
+ }
+</style>
+<template>
+ <div id="dialog" data-template="main_div" class="dialog_main_div">
+ <div data-template="msg" class="dialog_msg"></div>
+ <div data-template="ask_buts" class="dialog_buts">
+ <button data-template="yes_but" class="right_space">Yes</button>
+ <button data-template="no_but" class="left_space">No</button>
+ </div>
+ <div data-template="conf_buts" class="dialog_buts">
+ <button data-template="ok_but">Ok</button>
+ </div>
+ </div>
+</template>
+#ENDIF
diff --git a/html/dialog.js b/html/dialog.js
new file mode 100644
index 0000000..6345b2d
--- /dev/null
+++ b/html/dialog.js
@@ -0,0 +1,112 @@
+/**
+ * This file is part of Haketilo.
+ *
+ * Function: Showing and error/info/confirmation dialog.
+ *
+ * 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.
+ */
+
+#FROM html/DOM_helpers.js IMPORT clone_template
+#FROM common/lock.js IMPORT make_lock, lock, unlock
+
+function make(on_dialog_show, on_dialog_hide)
+{
+ const dialog_context = clone_template("dialog");
+ Object.assign(dialog_context, {
+ on_dialog_show,
+ on_dialog_hide,
+ shown: false,
+ queue: 0,
+ lock: make_lock(),
+ callback: null
+ });
+
+ for (const [id, val] of [["yes", true], ["no", false], ["ok", undefined]]) {
+ const but = dialog_context[`${id}_but`];
+ but.haketilo_dialog_result = val;
+ but.addEventListener("click", e => close_dialog(dialog_context, e));
+ }
+
+ return dialog_context;
+}
+#EXPORT make
+
+function close_dialog(dialog_context, event)
+{
+ if (dialog_context.queue > 0)
+ dialog_context.callback(event.target.haketilo_dialog_result);
+}
+
+async function show_dialog(dialog_context, shown_buts_id, msg)
+{
+ dialog_context.queue++;
+
+ if (!dialog_context.shown) {
+ dialog_context.shown = true;
+ dialog_context.on_dialog_show();
+ }
+
+ await lock(dialog_context.lock);
+
+ dialog_context.msg.innerText = msg;
+ for (const buts_id of ["ask_buts", "conf_buts"]) {
+ const action = buts_id == shown_buts_id ? "remove" : "add";
+ dialog_context[buts_id].classList[action]("hide");
+ }
+
+ const result = await new Promise(cb => dialog_context.callback = cb);
+
+ if (--dialog_context.queue == 0) {
+ dialog_context.shown = false;
+ dialog_context.on_dialog_hide();
+ }
+
+ unlock(dialog_context.lock);
+
+ return result;
+}
+
+const error = (ctx, msg) => show_dialog(ctx, "conf_buts", msg);
+#EXPORT error
+
+/* info() and error() are the same for now, we might later change that. */
+const info = error;
+#EXPORT info
+
+const ask = (ctx, msg) => show_dialog(ctx, "ask_buts", msg);
+#EXPORT ask
diff --git a/html/grid.css b/html/grid.css
new file mode 100644
index 0000000..59b5bb7
--- /dev/null
+++ b/html/grid.css
@@ -0,0 +1,75 @@
+/*
+ * SPDX-License-Identifier: GPL-3.0-or-later OR CC-BY-SA-4.0
+ *
+ * Styling for use with `display: grid`.
+ *
+ * This file is part of Haketilo.
+ *
+ * Copyright (C) 2022 Wojtek Kosior <koszko@koszko.org>
+ *
+ * File is dual-licensed. You can choose either GPLv3+, CC BY-SA or both.
+ *
+ * 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/>.
+ *
+ * I, Wojtek Kosior, thereby promise not to sue for violation of this file's
+ * licenses. 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.
+ */
+
+.grid_2 {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+}
+
+.grid_col_1 {
+ grid-column: 1 / span 1;
+}
+
+.grid_col_2 {
+ grid-column: 2 / span 1;
+}
+
+.grid_col_both {
+ grid-column: 1 / span 2;
+}
+
+span.grid_col_1 {
+ text-align: right;
+}
+
+.grid_col_both {
+ text-align: center;
+}
+
+div.grid_col_both {
+ text-align: initial;
+}
+
+.grid_2>span {
+ margin-top: 0.75em;
+}
+
+.grid_form>span {
+ margin-top: 1.5em;
+ margin-left: 1em;
+ margin-right: 1em;
+}
+
+.grid_form>span {
+ font-weight: bold;
+}
+
+span.grid_col_2 {
+ font-weight: initial;
+}
diff --git a/html/item_list.html b/html/item_list.html
new file mode 100644
index 0000000..41c7734
--- /dev/null
+++ b/html/item_list.html
@@ -0,0 +1,61 @@
+#IF !ITEM_LIST_LOADED
+#DEFINE ITEM_LIST_LOADED
+<!--
+ SPDX-License-Identifier: GPL-3.0-or-later OR CC-BY-SA-4.0
+
+ Show a list of resources/mappings.
+
+ This file is part of Haketilo.
+
+ Copyright (C) 2022 Wojtek Kosior <koszko@koszko.org>
+
+ File is dual-licensed. You can choose either GPLv3+, CC BY-SA or both.
+
+ 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/>.
+
+ I, Wojtek Kosior, thereby promise not to sue for violation of this file's
+ licenses. 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.
+ -->
+
+<!--
+ This is not a standalone page. This file is meant to be imported into other
+ HTML code.
+ -->
+
+#INCLUDE html/item_preview.html
+#INCLUDE html/dialog.html
+
+#LOADCSS html/reset.css
+#LOADCSS html/base.css
+#LOADCSS html/grid.css
+<style>
+ .item_li_highlight {
+ background-color: #c0c0c0;
+ }
+</style>
+<template>
+ <div id="item_list" data-template="main_div" class="grid_2">
+ <ul data-template="ul"></ul>
+ <div data-template="preview_container">
+ <!-- preview div will be dynamically inserted here -->
+
+ <button data-template="remove_but">Remove</button>
+ <!--<button data-template="export_but">Export</button>-->
+ </div>
+ <div data-template="dialog_container" class="hide">
+ </div>
+ </div>
+</template>
+#ENDIF
diff --git a/html/item_list.js b/html/item_list.js
new file mode 100644
index 0000000..f6b9bd3
--- /dev/null
+++ b/html/item_list.js
@@ -0,0 +1,189 @@
+/**
+ * This file is part of Haketilo.
+ *
+ * Function: Showing a list of resources/mappings in a browser.
+ *
+ * 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 common/indexeddb.js AS haketilodb
+#IMPORT html/dialog.js
+
+#FROM html/item_preview.js IMPORT resource_preview, mapping_preview
+#FROM html/DOM_helpers.js IMPORT clone_template
+
+function preview_item(list_ctx, item, ignore_dialog=false)
+{
+ if (list_ctx.dialog_ctx.shown && !ignore_dialog)
+ return;
+
+ list_ctx.preview_ctx =
+ list_ctx.preview_cb(item.definition, list_ctx.preview_ctx);
+ list_ctx.preview_container
+ .prepend(list_ctx.preview_ctx.main_div);
+
+ if (list_ctx.previewed_item !== null)
+ list_ctx.previewed_item.li.classList.remove("item_li_highlight");
+
+ list_ctx.previewed_item = item;
+ item.li.classList.add("item_li_highlight");
+
+ list_ctx.preview_container.classList.remove("hide");
+}
+
+function insert_item(list_ctx, definition, idx)
+{
+ const li = document.createElement("li");
+ li.innerText = definition.long_name;
+ if (idx)
+ list_ctx.items[idx - 1].li.after(li);
+ else
+ list_ctx.ul.prepend(li);
+
+ const item = {definition, li};
+ list_ctx.items.splice(idx, 0, item);
+ list_ctx.by_identifier.set(definition.identifier, item);
+
+ li.addEventListener("click", () => preview_item(list_ctx, item));
+
+ return item;
+}
+
+const coll = new Intl.Collator();
+
+function item_cmp(def1, def2)
+{
+ return coll.compare(def1.long_name, def2.long_name) ||
+ coll.compare(def1.identifier, def2.identifier);
+}
+
+function find_item_idx(definition)
+{
+ /* Perform a binary search of item's (new or not) index in sorted array. */
+ let left = 0, right = list_ctx.items.length;
+
+ while (left < right) {
+ const mid = (left + right) >> 1;
+ if (item_cmp(definition, list_ctx.items[mid].definition) > 0)
+ left = mid + 1;
+ else /* <= 0 */
+ right = mid;
+ }
+
+ return left;
+}
+
+function item_changed(list_ctx, change)
+{
+
+ /* Remove item. */
+ const old_item = list_ctx.by_identifier.get(change.key);
+ if (old_item !== undefined) {
+ list_ctx.items.splice(find_item_idx(old_item.definition), 1);
+ list_ctx.by_identifier.delete(change.key);
+ old_item.li.remove();
+
+ if (list_ctx.previewed_item === old_item) {
+ list_ctx.preview_container.classList.add("hide");
+ list_ctx.previewed_item = null;
+ }
+ }
+
+ if (change.new_val === undefined)
+ return;
+
+ const new_item = insert_item(list_ctx, change.new_val,
+ find_item_idx(change.new_val));
+ if (list_ctx.previewed_item === old_item)
+ preview_item(list_ctx, new_item, true);
+}
+
+async function item_list(preview_cb, track_cb)
+{
+ const list_ctx = clone_template("item_list");
+
+ const [tracking, definitions] =
+ await track_cb(ch => item_changed(list_ctx, ch));
+
+ definitions.sort(item_cmp);
+
+ Object.assign(list_ctx, {
+ items: [],
+ by_identifier: new Map(),
+ tracking,
+ previewed_item: null,
+ preview_cb,
+ dialog_ctx: dialog.make(() => on_dialog_show(list_ctx),
+ () => on_dialog_hide(list_ctx))
+ });
+ list_ctx.dialog_container.append(list_ctx.dialog_ctx.main_div);
+
+ for (const def of definitions)
+ insert_item(list_ctx, def, list_ctx.items.length);
+
+ return list_ctx;
+}
+
+function on_dialog_show(list_ctx)
+{
+ list_ctx.ul;
+ list_ctx.preview_container.classList.add("hide");
+ list_ctx.dialog_container.classList.remove("hide");
+}
+
+function on_dialog_hide(list_ctx)
+{
+ list_ctx.ul;
+ list_ctx.preview_container.classList.remove("hide");
+ list_ctx.dialog_container.classList.add("hide");
+}
+
+const resource_list =
+ () => item_list(resource_preview, haketilodb.track.resources);
+#EXPORT resource_list
+
+const mapping_list =
+ () => item_list(mapping_preview, haketilodb.track.mappings);
+#EXPORT mapping_list
+
+function destroy_list(list_ctx)
+{
+ haketilodb.untrack(list_ctx.tracking);
+ list_ctx.main_div.remove();
+}
+#EXPORT destroy_list
diff --git a/html/item_preview.html b/html/item_preview.html
new file mode 100644
index 0000000..76c6da6
--- /dev/null
+++ b/html/item_preview.html
@@ -0,0 +1,89 @@
+#IF !ITEM_PREVIEW_LOADED
+#DEFINE ITEM_PREVIEW_LOADED
+<!--
+ SPDX-License-Identifier: GPL-3.0-or-later OR CC-BY-SA-4.0
+
+ Show preview of a resource/mapping.
+
+ This file is part of Haketilo.
+
+ Copyright (C) 2022 Wojtek Kosior <koszko@koszko.org>
+
+ File is dual-licensed. You can choose either GPLv3+, CC BY-SA or both.
+
+ 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/>.
+
+ I, Wojtek Kosior, thereby promise not to sue for violation of this file's
+ licenses. 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.
+ -->
+
+<!--
+ This is not a standalone page. This file is meant to be imported into other
+ HTML code.
+ -->
+
+#LOADCSS html/reset.css
+#LOADCSS html/base.css
+#LOADCSS html/grid.css
+<style>
+ .dialog_main_div {
+ margin: 1.4em;
+ }
+</style>
+<template>
+ <div id="resource_preview_div" data-template="main_div"
+ class="grid_2 grid_form preview_main_div">
+ <h3 class="grid_col_both">resource preview</h3>
+ <span class="grid_col_1">identifier:</span>
+ <span data-template="identifier" class="grid_col_2">...</span>
+ <span class="grid_col_1">long name:</span>
+ <span data-template="long_name" class="grid_col_2">...</span>
+ <span class="grid_col_1">UUID:</span>
+ <span data-template="uuid" class="grid_col_2">...</span>
+ <span class="grid_col_1">version:</span>
+ <span data-template="version" class="grid_col_2">...</span>
+ <span class="grid_col_1">description:</span>
+ <span data-template="description" class="grid_col_2">...</span>
+ <span class="grid_col_1">dependencies:</span>
+ <span class="grid_col_2"><ul data-template="dependencies"></ul></span>
+ <span class="grid_col_1">scripts:</span>
+ <span class="grid_col_2"><ul data-template="scripts"></ul></span>
+ <span class="grid_col_1">source name:</span>
+ <span data-template="source_name" class="grid_col_2">...</span>
+ <span class="grid_col_1">copyright:</span>
+ <span class="grid_col_2"><ul data-template="copyright"></ul></span>
+ </div>
+ <div id="mapping_preview_div" data-template="main_div"
+ class="grid_2 grid_form">
+ <h3 class="grid_col_both">mapping preview</h3>
+ <span class="grid_col_1">identifier:</span>
+ <span data-template="identifier" class="grid_col_2">...</span>
+ <span class="grid_col_1">long name:</span>
+ <span data-template="long_name" class="grid_col_2">...</span>
+ <span class="grid_col_1">UUID:</span>
+ <span data-template="uuid" class="grid_col_2">...</span>
+ <span class="grid_col_1">version:</span>
+ <span data-template="version" class="grid_col_2">...</span>
+ <span class="grid_col_1">description:</span>
+ <span data-template="description" class="grid_col_2">...</span>
+ <span class="grid_col_both">payloads:</span>
+ <div data-template="payloads" class="grid_col_both grid_2"></div>
+ <span class="grid_col_1">source name:</span>
+ <span data-template="source_name" class="grid_col_2">...</span>
+ <span class="grid_col_1">copyright:</span>
+ <span class="grid_col_2"><ul data-template="copyright"></ul></span>
+ </div>
+</template>
+#ENDIF
diff --git a/html/item_preview.js b/html/item_preview.js
new file mode 100644
index 0000000..f59e30e
--- /dev/null
+++ b/html/item_preview.js
@@ -0,0 +1,151 @@
+/**
+ * This file is part of Haketilo.
+ *
+ * Function: Showing resource/mapping details in a browser.
+ *
+ * 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 common/indexeddb.js AS haketilodb
+#IMPORT html/dialog.js
+
+#FROM html/DOM_helpers.js IMPORT clone_template
+
+function populate_list(ul, items)
+{
+ for (const item of items) {
+ const li = document.createElement("li");
+ li.append(item);
+ ul.append(li);
+ }
+}
+
+async function file_link_clicked(preview_object, file_ref, event)
+{
+ event.preventDefault();
+
+ const db = await haketilodb.get();
+ const file = await haketilodb.idb_get(db.transaction("files"),
+ "files", file_ref.hash_key);
+ if (file === undefined) {
+ dialog.error(preview_object.dialog_context,
+ "File missing from Haketilo's inernal database :(");
+ } else {
+ const encoded_file = encodeURIComponent(file.contents);
+ open(`data:text/plain;charset=utf8,${encoded_file}`, '_blank');
+ }
+}
+
+function make_file_link(preview_object, file_ref)
+{
+ const a = document.createElement("a");
+ a.href = "javascript:void(0)";
+ a.innerText = file_ref.file;
+ a.addEventListener("click",
+ e => file_link_clicked(preview_object, file_ref, e));
+
+ return a;
+}
+
+function resource_preview(resource, preview_object, dialog_context)
+{
+ if (preview_object === undefined)
+ preview_object = clone_template("resource_preview_div");
+
+ preview_object.identifier.innerText = resource.identifier;
+ preview_object.long_name.innerText = resource.long_name;
+ preview_object.uuid.innerText = resource.uuid;
+ preview_object.version.innerText =
+ `${resource.version.join(".")}-${resource.revision}`;
+ preview_object.description.innerText = resource.description;
+ preview_object.source_name.innerText = resource.source_name;
+
+ [...preview_object.dependencies.childNodes].forEach(n => n.remove());
+ populate_list(preview_object.dependencies, resource.dependencies);
+
+ const link_maker = file_ref => make_file_link(preview_object, file_ref);
+
+ [...preview_object.scripts.childNodes].forEach(n => n.remove());
+ populate_list(preview_object.scripts, resource.scripts.map(link_maker));
+
+ [...preview_object.copyright.childNodes].forEach(n => n.remove());
+ populate_list(preview_object.copyright,
+ resource.source_copyright.map(link_maker));
+
+ preview_object.dialog_context = dialog_context;
+
+ return preview_object;
+}
+#EXPORT resource_preview
+
+function mapping_preview(mapping, preview_object, dialog_context)
+{
+ if (preview_object === undefined)
+ preview_object = clone_template("mapping_preview_div");
+
+ preview_object.identifier.innerText = mapping.identifier;
+ preview_object.long_name.innerText = mapping.long_name;
+ preview_object.uuid.innerText = mapping.uuid;
+ preview_object.version.innerText = mapping.version.join(".");
+ preview_object.description.innerText = mapping.description;
+ preview_object.source_name.innerText = mapping.source_name;
+
+ [...preview_object.payloads.childNodes].forEach(n => n.remove());
+ for (const [pattern, payload] of Object.entries(mapping.payloads).sort()) {
+ /* We use a non-breaking space because normal space would be ignored. */
+ const [nbsp, rarrow] = [160, 0x2192].map(n => String.fromCodePoint(n));
+ const texts = [`${pattern}${nbsp}`, `${rarrow} ${payload.identifier}`];
+ for (let i = 0; i < texts.length; i++) {
+ const span = document.createElement("span");
+ span.innerText = texts[i];
+ span.classList.add(`grid_col_${i + 1}`);
+ preview_object.payloads.append(span);
+ }
+ }
+
+ const link_maker = file_ref => make_file_link(preview_object, file_ref);
+
+ [...preview_object.copyright.childNodes].forEach(n => n.remove());
+ populate_list(preview_object.copyright,
+ mapping.source_copyright.map(link_maker));
+
+ preview_object.dialog_context = dialog_context;
+
+ return preview_object;
+}
+#EXPORT mapping_preview