diff options
author | Wojtek Kosior <wk@koszkonutek-tmp.pl.eu.org> | 2021-05-10 18:07:05 +0200 |
---|---|---|
committer | Wojtek Kosior <wk@koszkonutek-tmp.pl.eu.org> | 2021-05-10 18:18:52 +0200 |
commit | 01937dc9d5215ef96ce756e3ccda51bf29032f58 (patch) | |
tree | 609ec5bb48c692796520f7982c06b30633038588 /html | |
download | browser-extension-01937dc9d5215ef96ce756e3ccda51bf29032f58.tar.gz browser-extension-01937dc9d5215ef96ce756e3ccda51bf29032f58.zip |
initial commit
Diffstat (limited to 'html')
-rw-r--r-- | html/display-panel.html | 11 | ||||
-rw-r--r-- | html/display-panel.mjs | 16 | ||||
-rw-r--r-- | html/options.html | 175 | ||||
-rw-r--r-- | html/options_main.mjs | 398 |
4 files changed, 600 insertions, 0 deletions
diff --git a/html/display-panel.html b/html/display-panel.html new file mode 100644 index 0000000..9c8de5e --- /dev/null +++ b/html/display-panel.html @@ -0,0 +1,11 @@ +<!doctype html> +<html> + <head> + <meta charset="utf-8"/> + <title>Myext popup</title> + </head> + <body> + <button id="settings_but" type="button">Settings</button> + <script src="./display-panel.mjs" type="module"></script> + </body> +</html> diff --git a/html/display-panel.mjs b/html/display-panel.mjs new file mode 100644 index 0000000..38f2a42 --- /dev/null +++ b/html/display-panel.mjs @@ -0,0 +1,16 @@ +/** +* Myext display panel logic +* +* Copyright (C) 2021 Wojtek Kosior +* +* Dual-licensed under: +* - 0BSD license +* - GPLv3 or (at your option) any later version +*/ + +"use strict"; + +import browser from '/common/browser.mjs'; + +document.getElementById("settings_but") + .addEventListener("click", (e) => browser.runtime.openOptionsPage()); diff --git a/html/options.html b/html/options.html new file mode 100644 index 0000000..921b423 --- /dev/null +++ b/html/options.html @@ -0,0 +1,175 @@ +<!doctype html> +<html> + <head> + <meta charset="utf-8"/> + <title>Myext options</title> + <style> + input[type="checkbox"], input[type="radio"], + .hide { + display: none; + } + + /* pages list */ + #page_components_ul { + max-height: 80vh; + overflow-y: auto; + } + #page_components_ul li.dragover_li { + border-top: 2px solid blue; + } + #page_components_ul li { + border-top: 2px solid white; + } + li[draggable=true] * { + pointer-events: none; + } + li[draggable=true] label, + li[draggable=true] button { + pointer-events: auto; + } + + /* tabbed view */ + #show_pages:not(:checked) ~ #pages, + #show_bundles:not(:checked) ~ #bundles, + #show_scripts:not(:checked) ~ #scripts { + display: none; + } + + #show_pages:checked ~ #pages_lbl, + #show_bundles:checked ~ #bundles_lbl, + #show_scripts:checked ~ #scripts_lbl { + border-left: 2px solid green; + border-right: 2px solid green; + } + + body > div { + border-top: 2px solid green; + } + + .tab_head { + display: inline-block; + } + + /* popup window with list of selectable components for adding */ + #select_components_window { + position: fixed; + width: 100vw; + height: 100vh; + left: 0; + top: 0; + background-color: rgba(0,0,0,0.4); + z-index: 1; + overflow: auto; + vertical-align: center; + horizontal-align: center; + } + + #select_components_frame { + background-color: white; + width: 50vw; + } + </style> + </head> + <body> + <!-- The invisible div below is for elements that will be cloned. --> + <div style="display: none;"> + <li id="item_li_template"> + <span></span> + <button> Edit </button> + <button> Remove </button> + </li> + <li id="component_li_template"> + <span></span> + <button> Remove </button> + </li> + <li id="selectable_component_li_template"> + <input type="checkbox" style="display: inline;"></input> + <span></span> + </li> + </div> + + <input type="radio" name="tabs" id="show_pages" checked></input> + <input type="radio" name="tabs" id="show_bundles"></input> + <input type="radio" name="tabs" id="show_scripts"></input> + <label for="show_pages" id="pages_lbl" + class="tab_head"> Pages </label> + <label for="show_bundles" id="bundles_lbl" + class="tab_head"> Bundles </label> + <label for="show_scripts" id="scripts_lbl" + class="tab_head"> Scripts </label> + + <div id="pages"> + <ul id="pages_ul"> + <li id="work_page_li" class="hide"> + <label for="page_url_field">URL: </label> + <input id="page_url_field"></input> + <ul id="page_components_ul"> + <li id="empty_page_component_li" class="hide"></li> + </ul> + <input id="page_allow_chbx" type="checkbox" style="display: inline;"></input> + <label for="page_allow_chbx">Allow native scripts</label> + <button id="page_select_components_but"> + Add scripts + </button> + <br/> + <button id="page_save_but" type="button"> Save </button> + <button id="page_discard_but" type="button"> Cancel </button> + </li> + </ul> + <button id="add_page_but" type="button"> Add page </button> + </div> + + <div id="bundles"> + <ul id="bundles_ul"> + <li id="work_bundle_li" class="hide"> + <label for="bundle_name_field"> Name: </label> + <input id="bundle_name_field"></input> + <ul id="bundle_components_ul"> + <li id="empty_bundle_component_li" class="hide"></li> + </ul> + <button id="bundle_select_components_but"> + Add scripts + </button> + <br/> + <button id="bundle_save_but"> Save </button> + <button id="bundle_discard_but"> Cancel </button> + </li> + </ul> + <button id="add_bundle_but" type="button"> Add bundle </button> + </div> + + <div id="scripts"> + <ul id="scripts_ul"> + <li id="work_script_li" class="hide"> + <label for="script_name_field"> Name: </label> + <input id="script_name_field"></input> + <br/> + <label for="script_url_field"> URL: </label> + <input id="script_url_field"></input> + <br/> + <label for="script_sha256_field"> sha256: </label> + <input id="script_sha256_field"></input> + <br/> + <label for="script_contents_field"> contents: </label> + <textarea id="script_contents_field" rows="20" cols="80"></textarea> + <br/> + <button id="script_save_but"> Save </button> + <button id="script_discard_but"> Cancel </button> + </li> + </ul> + <button id="add_script_but" type="button"> Add script </button> + </div> + + <div id="select_components_window" class="hide" position="absolute"> + <div id="select_components_frame"> + <ul id="selectable_components_ul"> + + </ul> + <button id="commit_components_but"> Add </button> + <button id="cancel_components_but"> Cancel </button> + </div> + </div> + + <script src="./options_main.mjs" type="module"></script> + </body> +</html> diff --git a/html/options_main.mjs b/html/options_main.mjs new file mode 100644 index 0000000..f288971 --- /dev/null +++ b/html/options_main.mjs @@ -0,0 +1,398 @@ +/** +* Myext HTML options page main script +* +* Copyright (C) 2021 Wojtek Kosior +* +* Dual-licensed under: +* - 0BSD license +* - GPLv3 or (at your option) any later version +*/ + +"use strict"; + +import get_storage from '/common/storage_client.mjs'; +import {TYPE_PREFIX, TYPE_NAME, list_prefixes} from '/common/stored_types.mjs'; + +var storage; + +const item_li_template = document.getElementById("item_li_template"); +const component_li_template = document.getElementById("component_li_template"); +const selectable_component_li_template = + document.getElementById("selectable_component_li_template"); +/* Make sure they are later cloned without id. */ +item_li_template.removeAttribute("id"); +component_li_template.removeAttribute("id"); +selectable_component_li_template.removeAttribute("id"); + +function item_li_id(prefix, item) +{ + return `li_${prefix}_${item}`; +} + +function add_li(prefix, item, at_the_end=false) +{ + let ul = ul_by_prefix[prefix]; + let li = item_li_template.cloneNode(true); + li.id = item_li_id(prefix, item); + + let span = li.firstElementChild; + span.textContent = item; + + let edit_button = span.nextElementSibling; + edit_button.addEventListener("click", () => edit_item(prefix, item)); + + let remove_button = edit_button.nextElementSibling; + remove_button.addEventListener("click", () => storage.remove(prefix, item)); + + if (!at_the_end) { + for (let element of ul.ul.children) { + if (element.id < li.id || element.id.startsWith("work_")) + continue; + + ul.ul.insertBefore(li, element); + return; + } + } + + ul.ul.appendChild(li); +} + +const selectable_components_ul = + document.getElementById("selectable_components_ul"); + +function selectable_li_id(prefix, item) +{ + return `sli_${prefix}_${item}`; +} + +function add_selectable(prefix, name) +{ + if (prefix === TYPE_PREFIX.PAGE) + return; + + let li = selectable_component_li_template.cloneNode(true); + li.id = selectable_li_id(prefix, name); + li.setAttribute("data-prefix", prefix); + li.setAttribute("data-name", name); + + let chbx = li.firstElementChild; + let span = chbx.nextElementSibling; + + span.textContent = `${name} (${TYPE_NAME[prefix]})`; + + selectable_components_ul.appendChild(li); +} + +/* + * Used to construct and update components list of edited + * bundle as well as edited page. + */ +function add_components(ul, components) +{ + let components_ul = ul.work_name_input.nextElementSibling; + + for (let component of components) { + let [prefix, name] = component; + let li = component_li_template.cloneNode(true); + li.setAttribute("data-prefix", prefix); + li.setAttribute("data-name", name); + let span = li.firstElementChild; + span.textContent = `${name} (${TYPE_NAME[prefix]})`; + let remove_but = span.nextElementSibling; + remove_but.addEventListener("click", () => + components_ul.removeChild(li)); + components_ul.appendChild(li); + } + + components_ul.appendChild(ul.work_empty_component_li); +} + +/* Used to reset edited bundle as well as edited page. */ +function generic_reset_work_li(ul, item, components) +{ + if (item === undefined) { + item = ""; + components = []; + }; + + ul.work_name_input.value = item; + let old_components_ul = ul.work_name_input.nextElementSibling; + let components_ul = old_components_ul.cloneNode(false); + + ul.work_li.insertBefore(components_ul, old_components_ul); + ul.work_li.removeChild(old_components_ul); + + add_components(ul, components); +} + +function reset_work_page_li(ul, item, settings) +{ + ul.work_page_allow_chbx.checked = !!settings?.allow; + generic_reset_work_li(ul, item, settings?.components); +} + +/* Used to get edited bundle as well as edited page data for saving. */ +function generic_work_li_data(ul) +{ + let components_ul = ul.work_name_input.nextElementSibling; + let component_li = components_ul.firstElementChild; + + let components = []; + + /* Last list element is empty li with id set. */ + while (component_li.id === '') { + components.push([component_li.getAttribute("data-prefix"), + component_li.getAttribute("data-name")]); + component_li = component_li.nextElementSibling; + } + + return [ul.work_name_input.value, components]; +} + +function work_page_li_data(ul) +{ + let [url, components] = generic_work_li_data(ul); + let settings = {components, allow : !!ul.work_page_allow_chbx.checked}; + + return [url, settings]; +} + +const script_url_input = document.getElementById("script_url_field"); +const script_sha256_input = document.getElementById("script_sha256_field"); +const script_contents_field = document.getElementById("script_contents_field"); + +function maybe_string(maybe_defined) +{ + return maybe_defined === undefined ? "" : maybe_defined + ""; +} + +function reset_work_script_li(ul, name, data) +{ + ul.work_name_input.value = maybe_string(name); + script_url_input.value = maybe_string(data?.url); + script_sha256_input.value = maybe_string(data?.hash); + script_contents_field.value = maybe_string(data?.text); +} + +function work_script_li_data(ul) +{ + return [ul.work_name_input.value, { + url : script_url_input.value, + hash : script_sha256_input.value, + text : script_contents_field.value + }]; +} + +function cancel_work(prefix) +{ + let ul = ul_by_prefix[prefix]; + + if (ul.state === UL_STATE.IDLE) + return; + + if (ul.state === UL_STATE.EDITING_ENTRY) { + add_li(prefix, ul.edited_item); + } + + ul.work_li.classList.add("hide"); + ul.state = UL_STATE.IDLE; +} + +function save_work(prefix) +{ + let ul = ul_by_prefix[prefix]; + + if (ul.state === UL_STATE.IDLE) + return; + + let [item, data] = ul.get_work_li_data(ul); + + if (prefix == TYPE_PREFIX.PAGE) + + /* Here we fire promises and return without waiting. */ + + if (ul.state === UL_STATE.EDITING_ENTRY) + storage.replace(prefix, ul.edited_item, item, data); + + if (ul.state === UL_STATE.ADDING_ENTRY) + storage.set(prefix, item, data); + + cancel_work(prefix); +} + +function edit_item(prefix, item) +{ + cancel_work(prefix); + + let ul = ul_by_prefix[prefix]; + let li = document.getElementById(item_li_id(prefix, item)); + ul.reset_work_li(ul, item, storage.get(prefix, item)); + ul.ul.insertBefore(ul.work_li, li); + ul.ul.removeChild(li); + ul.work_li.classList.remove("hide"); + + ul.state = UL_STATE.EDITING_ENTRY; + ul.edited_item = item; +} + +function add_new_item(prefix) +{ + cancel_work(prefix); + + let ul = ul_by_prefix[prefix]; + ul.reset_work_li(ul); + ul.work_li.classList.remove("hide"); + ul.ul.appendChild(ul.work_li); + + ul.state = UL_STATE.ADDING_ENTRY; +} + +const select_components_window = + document.getElementById("select_components_window"); +var select_prefix; + +function select_components(prefix) +{ + select_prefix = prefix; + select_components_window.classList.remove("hide"); + + for (let li of selectable_components_ul.children) { + let chbx = li.firstElementChild; + chbx.checked = false; + } +} + +function commit_components() +{ + let selected = []; + + for (let li of selectable_components_ul.children) { + let chbx = li.firstElementChild; + if (!chbx.checked) + continue; + + selected.push([li.getAttribute("data-prefix"), + li.getAttribute("data-name")]); + } + + add_components(ul_by_prefix[select_prefix], selected); + cancel_components(); +} + +function cancel_components() +{ + select_components_window.classList.add("hide"); +} + +const UL_STATE = { + EDITING_ENTRY : 0, + ADDING_ENTRY : 1, + IDLE : 2 +}; + +const ul_by_prefix = { + [TYPE_PREFIX.PAGE] : { + ul : document.getElementById("pages_ul"), + work_li : document.getElementById("work_page_li"), + work_name_input : document.getElementById("page_url_field"), + work_empty_component_li : + document.getElementById("empty_page_component_li"), + work_page_allow_chbx : document.getElementById("page_allow_chbx"), + reset_work_li : reset_work_page_li, + get_work_li_data : work_page_li_data, + state : UL_STATE.IDLE, + edited_item : undefined, + }, + [TYPE_PREFIX.BUNDLE] : { + ul : document.getElementById("bundles_ul"), + work_li : document.getElementById("work_bundle_li"), + work_name_input : document.getElementById("bundle_name_field"), + work_empty_component_li : + document.getElementById("empty_bundle_component_li"), + reset_work_li : generic_reset_work_li, + get_work_li_data : generic_work_li_data, + state : UL_STATE.IDLE, + edited_item : undefined, + }, + [TYPE_PREFIX.SCRIPT] : { + ul : document.getElementById("scripts_ul"), + work_li : document.getElementById("work_script_li"), + work_name_input : document.getElementById("script_name_field"), + reset_work_li : reset_work_script_li, + get_work_li_data : work_script_li_data, + state : UL_STATE.IDLE, + edited_item : undefined, + } +} + +async function main() +{ + storage = await get_storage(); + + for (let prefix of list_prefixes) { + for (let item of storage.get_all_names(prefix).sort()) { + add_li(prefix, item, true); + add_selectable(prefix, item); + } + } + + storage.add_change_listener(handle_change); + + let commit_components_but = + document.getElementById("commit_components_but"); + let cancel_components_but = + document.getElementById("cancel_components_but"); + commit_components_but.addEventListener("click", commit_components); + cancel_components_but.addEventListener("click", cancel_components); + + for (let prefix of list_prefixes) { + let add_but = document.getElementById(`add_${TYPE_NAME[prefix]}_but`); + let discard_but = + document.getElementById(`${TYPE_NAME[prefix]}_discard_but`); + let save_but = document.getElementById(`${TYPE_NAME[prefix]}_save_but`); + let select_components_but = document.getElementById( + `${TYPE_NAME[prefix]}_select_components_but` + ); + + add_but.addEventListener("click", () => add_new_item(prefix)); + discard_but.addEventListener("click", () => cancel_work(prefix)); + save_but.addEventListener("click", () => save_work(prefix)); + if (select_components_but === null) + continue; + + select_components_but.addEventListener( + "click", + () => select_components(prefix) + ); + } +} + +function handle_change(change) +{ + if (change.old_val === undefined) { + add_li(change.prefix, change.item); + add_selectable(change.prefix, change.item); + return; + } + + if (change.new_val !== undefined) + return; + + let ul = ul_by_prefix[change.prefix]; + if (ul.state === UL_STATE.EDITING_ENTRY && ul.edited_item === change.item) { + ul.state = UL_STATE.ADDING_ENTRY; + return; + } + + let li = document.getElementById(item_li_id(change.prefix, change.item)); + ul.ul.removeChild(li); + + if (change.prefix === TYPE_PREFIX.PAGE) + return; + + let sli = document.getElementById(selectable_li_id(change.prefix, + change.item)); + selectable_components_ul.removeChild(sli); +} + +main(); |