aboutsummaryrefslogtreecommitdiff
path: root/ui/client-scripts/src/transition.ts
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2026-01-18 23:43:12 +0100
committermetamuffin <metamuffin@disroot.org>2026-01-18 23:43:12 +0100
commited19a428cb5eef84c8cf3fed5fda3afd5fc96305 (patch)
tree39e3167a4f8b7423a15b3a5f56e973554bdb3195 /ui/client-scripts/src/transition.ts
parent901dff07ed357694eb35284a58c3cc6c003c53ce (diff)
downloadjellything-ed19a428cb5eef84c8cf3fed5fda3afd5fc96305.tar
jellything-ed19a428cb5eef84c8cf3fed5fda3afd5fc96305.tar.bz2
jellything-ed19a428cb5eef84c8cf3fed5fda3afd5fc96305.tar.zst
Move client scripts to build-crate
Diffstat (limited to 'ui/client-scripts/src/transition.ts')
-rw-r--r--ui/client-scripts/src/transition.ts108
1 files changed, 108 insertions, 0 deletions
diff --git a/ui/client-scripts/src/transition.ts b/ui/client-scripts/src/transition.ts
new file mode 100644
index 0000000..dadb266
--- /dev/null
+++ b/ui/client-scripts/src/transition.ts
@@ -0,0 +1,108 @@
+/*
+ This file is part of jellything (https://codeberg.org/metamuffin/jellything)
+ which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
+ Copyright (C) 2026 metamuffin <metamuffin.org>
+*/
+/// <reference lib="dom" />
+
+import { e } from "./jshelper/src/element.ts";
+
+interface HistoryState {
+ top: number
+}
+
+const DURATION = 200
+globalThis.addEventListener("DOMContentLoaded", () => {
+ patch_page()
+})
+
+globalThis.addEventListener("popstate", async e => {
+ await transition_to(globalThis.location.href, e.state)
+})
+
+let disable_transition = false
+globalThis.addEventListener("navigationrequiresreload", () => {
+ disable_transition = true
+})
+
+function patch_page() {
+ document.querySelectorAll("a").forEach(el => {
+ if (!el.href.startsWith("http")) return
+ if (el.target && el.target != "_self") return
+ el.addEventListener("click", async ev => {
+ ev.preventDefault()
+ await transition_to(el.href)
+ })
+ })
+}
+
+async function transition_to(href: string, state?: HistoryState) {
+ stop() // nice!
+ if (disable_transition) return globalThis.location.href = href
+ const trigger_load = prepare_load(href, state)
+ await fade(false)
+ trigger_load()
+ disable_transition = false;
+}
+
+function show_message(mesg: string, mode: "error" | "success" = "error") {
+ clear_spinner()
+ disable_transition = true
+ document.body.append(e("span", { class: ["jst-message", mode] }, mesg))
+}
+
+let i = 0;
+function prepare_load(href: string, state?: HistoryState) {
+ const r_promise = fetch(href, { headers: { accept: "text/html" }, redirect: "manual" })
+ return async () => {
+ let rt = ""
+ try {
+ const r = await r_promise
+ if (r.type == "opaqueredirect") {
+ globalThis.location.href = href
+ show_message("Native Player Started.", "success")
+ setTimeout(() => globalThis.location.reload(), 500)
+ return
+ }
+ if (!r.ok) return show_message("Error response. Try again.")
+ rt = await r.text()
+ } catch (e) {
+ if (e instanceof TypeError) return show_message("Navigation failed. Check your connection.")
+ return show_message("unknown error when fetching page")
+ }
+ const [head, body] = rt.split("<head>")[1].split("</head>")
+ globalThis.history.replaceState({ top: globalThis.scrollY, index: i++ } as HistoryState, "")
+ if (!state) globalThis.history.pushState({}, "", href)
+ clear_spinner()
+ document.head.innerHTML = head
+ document.body.outerHTML = body
+ globalThis.dispatchEvent(new Event("DOMContentLoaded"))
+ globalThis.scrollTo({ top: state?.top ?? 0 });
+ fade(true)
+ }
+}
+
+let spinner_timeout: number | undefined, spinner_element: HTMLElement | undefined;
+function clear_spinner() {
+ if (spinner_timeout) clearTimeout(spinner_timeout)
+ if (spinner_element) spinner_element.remove()
+ spinner_element = spinner_timeout = undefined;
+}
+function fade(dir: boolean) {
+ const overlay = document.createElement("div")
+ overlay.classList.add("jst-fade")
+ overlay.style.backgroundColor = dir ? "black" : "transparent"
+ overlay.style.animationName = dir ? "jst-fadeout" : "jst-fadein"
+ overlay.style.animationFillMode = "forwards"
+ overlay.style.animationDuration = `${DURATION}ms`
+ document.body.appendChild(overlay)
+ return new Promise<void>(res => {
+ setTimeout(() => {
+ if (dir) document.body.removeChild(overlay)
+ spinner_timeout = setTimeout(() => {
+ overlay.append(spinner_element = e("div", { class: "jst-spinner" }, "This is a spinner."))
+ }, 500)
+ res()
+ }, DURATION)
+ })
+}