From 8a02b645f3f948e71faa87ed96e31f2837128255 Mon Sep 17 00:00:00 2001 From: CenTdemeern1 Date: Tue, 28 Jan 2025 23:26:21 +0100 Subject: [PATCH] Kinda rough but it works (mostly) --- src/main.rs | 8 ++- static/config.html | 111 +++++++++++++++++++++++++++++++++++++ static/config.mts | 133 +++++++++++++++++++++++++++++++++++++++++++++ static/dom.mts | 7 +++ 4 files changed, 258 insertions(+), 1 deletion(-) create mode 100644 static/config.html create mode 100644 static/config.mts diff --git a/src/main.rs b/src/main.rs index 2c6d115..3ef9570 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,6 +24,11 @@ async fn route_for_known_instance_software( NamedFile::open("static/crossroad.html").await.ok() } +#[get("/")] +async fn configure_page() -> Option { + NamedFile::open("static/config.html").await.ok() +} + #[get("//", rank = 2)] fn route_for_unknown_instance_software(instance: &str, route: PathBuf) -> (ContentType, String) { ( @@ -46,7 +51,8 @@ fn rocket() -> _ { routes![ known_software_json, route_for_known_instance_software, - route_for_unknown_instance_software + route_for_unknown_instance_software, + configure_page ], ) } diff --git a/static/config.html b/static/config.html new file mode 100644 index 0000000..84aefaa --- /dev/null +++ b/static/config.html @@ -0,0 +1,111 @@ + + + + + + + FeDirect + + + + + +
+ +
+
+

FeDirect

+

  By Nekomata

+
+ +
+
+
+
+
    +
    + +
    +
    +
    +
    +
    + + + +
    +
    +
    +
    +
    +
    + +

    Add an instance

    +
    + + +
    + + +

    + + +
    +
    + +

    Confirm instance details

    +
    +
    +
    + +
    + +

    + +
    + +
    + + +

    + +
    + +
    +
    +
    +
    + +
    + Icon for the selected instance +
    +
    +
    +
    +
    +
    + + +
    +
    + + + \ No newline at end of file diff --git a/static/config.mts b/static/config.mts new file mode 100644 index 0000000..30630e9 --- /dev/null +++ b/static/config.mts @@ -0,0 +1,133 @@ +import { parseHost } from "./add_an_instance.mjs"; +import { initializeAddInstanceFlow } from "./add_instance_flow.mjs"; +import { initializeInstanceDetailsDialog } from "./confirm_instance_details.mjs"; +import { findButtonOrFail, findDialogOrFail, findOlOrFail } from "./dom.mjs"; +import storageManager from "./storage_manager.mjs"; + +let reordering = false; +// Dragging code is a heavily modified version of https://stackoverflow.com/a/28962290 +let elementBeingDragged: HTMLLIElement | undefined; + +const detailsDialog = findDialogOrFail(document.body, "#instanceDetails"); +const addDialog = findDialogOrFail(document.body, "#addInstance"); +const instanceList = findOlOrFail(document.body, "#instanceList"); +const saveButton = findButtonOrFail(document.body, "#save"); +const reorderButton = findButtonOrFail(document.body, "#reorder"); + +saveButton.addEventListener("click", e => { + storageManager.save(); +}); + +reorderButton.addEventListener("click", () => { + reordering = !reordering; + if (!reordering) applyReordering(); + updateInstanceList(); + reorderButton.innerText = reordering ? "Finish reordering" : "Reorder"; +}); + +export const getMainDialog = () => findDialogOrFail(document.body, "#mainDialog"); + +const { + showInstanceDetailsDialog, + hideInstanceDetailsDialog, + populateInstanceDetailsDialog, +} = initializeInstanceDetailsDialog(detailsDialog, () => { }); + +export const { + showAddInstanceDialog, + hideAddInstanceDialog +} = initializeAddInstanceFlow(detailsDialog, addDialog); + +updateInstanceList(); +storageManager.addSaveCallback(updateInstanceList); + +function updateInstanceList() { + instanceList.replaceChildren(); // Erase all child nodes + instanceList.style.listStyleType = reordering ? "\"≡ \"" : "disc"; + for (let n = 0; n < storageManager.storage.instances.length; n++) { + const instance = storageManager.storage.instances[n]; + const li = document.createElement("li"); + li.setAttribute("x-option", n.toString()); + const label = document.createElement("label"); + label.htmlFor = instance.origin; + label.innerText = instance.name + " "; + label.style.cursor = "inherit"; + if (instance.iconURL) { + const img = new Image(); + img.src = instance.iconURL; + img.alt = `${instance.name} icon`; + img.className = "inlineIcon medium-height"; + label.append(img, " "); + } + if (reordering) { + li.draggable = true; + li.addEventListener("dragstart", e => { + if (e.dataTransfer === null) return; + if (!(e.target instanceof HTMLLIElement)) return; + e.dataTransfer.effectAllowed = "move"; + e.dataTransfer.setData("text/plain", ""); + elementBeingDragged = e.target; + }); + li.addEventListener("dragover", e => { + if (elementBeingDragged === undefined) return; + if (!(e.target instanceof HTMLElement)) return; + const listElement = e.target.closest("li"); + if (listElement === null) return; + if (listElement.parentNode === null) return; + if (isBefore(elementBeingDragged, listElement)) + listElement.parentNode.insertBefore(elementBeingDragged, listElement); + else + listElement.parentNode.insertBefore(elementBeingDragged, listElement.nextSibling); + e.preventDefault(); + }); + li.addEventListener("dragenter", e => e.preventDefault()); + li.style.cursor = "grab"; + } else { + const editLink = document.createElement("a"); + editLink.innerText = `Edit`; + editLink.href = "#"; + editLink.addEventListener("click", e => { + const host = parseHost(instance.origin)!; + populateInstanceDetailsDialog( + instance.name, + host.host, + host.secure, + instance.software, + instance.iconURL ?? null + ); + showInstanceDetailsDialog(); + }); + const deleteLink = document.createElement("a"); + deleteLink.innerText = `Delete`; + deleteLink.href = "#"; + deleteLink.addEventListener("click", e => { + storageManager.storage.instances.splice( + storageManager.storage.instances.indexOf(instance) + ); + updateInstanceList(); + }); + label.append(editLink, " ", deleteLink); + } + li.appendChild(label); + instanceList.appendChild(li); + } +} + +function isBefore(el1: HTMLLIElement, el2: HTMLLIElement) { + if (el2.parentNode === el1.parentNode) + for (let cur = el1.previousSibling; cur && cur.nodeType !== 9; cur = cur.previousSibling) + if (cur === el2) + return true; + return false; +} + +function applyReordering() { + const indices: number[] = []; + for (const el of instanceList.children) { + if (!(el instanceof HTMLLIElement)) continue; + const option = el.getAttribute("x-option"); + if (option === null) continue; + indices.push(parseInt(option)); + } + storageManager.storage.instances = indices.map(i => storageManager.storage.instances[i]); +} diff --git a/static/dom.mts b/static/dom.mts index 53d4c36..2c4267c 100644 --- a/static/dom.mts +++ b/static/dom.mts @@ -1,6 +1,13 @@ // I would've LOVED to use generics for this but unfortunately that's not possible. // Type safety, but at what cost... >~< thanks TypeScript +export function findOlOrFail(on: Element, selector: string): HTMLOListElement { + const element = on.querySelector(selector); + if (!(element instanceof HTMLOListElement)) + throw new Error(`${selector} isn't an ol`); + return element; +} + export function findPreOrFail(on: Element, selector: string): HTMLPreElement { const element = on.querySelector(selector); if (!(element instanceof HTMLPreElement))