/** * 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();