From ea6354499818af15e7e7593c4b6f4deff0677da0 Mon Sep 17 00:00:00 2001 From: mkelvers Date: Thu, 28 May 2026 11:29:43 +0200 Subject: [PATCH] chore: format player skip editor --- static/player/skip/editor.ts | 152 ++++++++++++++++++----------------- 1 file changed, 77 insertions(+), 75 deletions(-) diff --git a/static/player/skip/editor.ts b/static/player/skip/editor.ts index 6b262ed..d69351d 100644 --- a/static/player/skip/editor.ts +++ b/static/player/skip/editor.ts @@ -1,40 +1,40 @@ -import { state } from '../state'; -import { formatTime, showControls } from '../controls'; -import { resolveActiveSegments, renderSegments } from './segments'; +import { state } from "../state"; +import { formatTime, showControls } from "../controls"; +import { resolveActiveSegments, renderSegments } from "./segments"; -type SkipType = 'op' | 'ed'; +type SkipType = "op" | "ed"; const qs = (root: ParentNode, sel: string): T | null => root.querySelector(sel) as T | null; export const setupSegmentEditor = (): void => { - const root = document.querySelector('[data-segment-editor-root]') as HTMLElement | null; + const root = document.querySelector("[data-segment-editor-root]") as HTMLElement | null; if (!root) return; - const toggleBtn = qs(root, '[data-segment-editor-toggle]'); - const panel = qs(root, '[data-segment-editor]'); + const toggleBtn = qs(root, "[data-segment-editor-toggle]"); + const panel = qs(root, "[data-segment-editor]"); if (!toggleBtn || !panel) return; - const closeBtn = qs(panel, '[data-segment-editor-close]'); - const typeValue = qs(panel, '[data-segment-type-value]'); - const typeLabel = qs(panel, '[data-segment-type-label]'); - const markStartBtn = qs(panel, '[data-segment-mark-start]'); - const markEndBtn = qs(panel, '[data-segment-mark-end]'); - const startLabel = qs(panel, '[data-segment-start]'); - const endLabel = qs(panel, '[data-segment-end]'); - const resetBtn = qs(panel, '[data-segment-reset]'); - const saveBtn = qs(panel, '[data-segment-save]'); - const errorBox = qs(panel, '[data-segment-error]'); + const closeBtn = qs(panel, "[data-segment-editor-close]"); + const typeValue = qs(panel, "[data-segment-type-value]"); + const typeLabel = qs(panel, "[data-segment-type-label]"); + const markStartBtn = qs(panel, "[data-segment-mark-start]"); + const markEndBtn = qs(panel, "[data-segment-mark-end]"); + const startLabel = qs(panel, "[data-segment-start]"); + const endLabel = qs(panel, "[data-segment-end]"); + const resetBtn = qs(panel, "[data-segment-reset]"); + const saveBtn = qs(panel, "[data-segment-save]"); + const errorBox = qs(panel, "[data-segment-error]"); const typeOptions = Array.from( - panel.querySelectorAll('[data-segment-type-option]') + panel.querySelectorAll("[data-segment-type-option]"), ) as HTMLButtonElement[]; const focusableSelector = [ - 'button:not([disabled])', - 'input:not([disabled])', - 'select:not([disabled])', - 'textarea:not([disabled])', + "button:not([disabled])", + "input:not([disabled])", + "select:not([disabled])", + "textarea:not([disabled])", '[tabindex]:not([tabindex="-1"])', - ].join(','); + ].join(","); let startTime: number | null = null; let endTime: number | null = null; @@ -43,56 +43,58 @@ export const setupSegmentEditor = (): void => { const setError = (msg: string | null): void => { if (!errorBox) return; if (!msg) { - errorBox.classList.add('hidden'); - errorBox.textContent = ''; + errorBox.classList.add("hidden"); + errorBox.textContent = ""; return; } errorBox.textContent = msg; - errorBox.classList.remove('hidden'); + errorBox.classList.remove("hidden"); }; const updateLabels = (): void => { - if (startLabel) startLabel.textContent = startTime == null ? '—' : formatTime(startTime); - if (endLabel) endLabel.textContent = endTime == null ? '—' : formatTime(endTime); + if (startLabel) startLabel.textContent = startTime == null ? "—" : formatTime(startTime); + if (endLabel) endLabel.textContent = endTime == null ? "—" : formatTime(endTime); }; const open = (): void => { lastActiveElement = document.activeElement instanceof HTMLElement ? document.activeElement : null; - panel.classList.remove('hidden'); - panel.classList.add('flex'); - panel.setAttribute('aria-hidden', 'false'); + panel.classList.remove("hidden"); + panel.classList.add("flex"); + panel.setAttribute("aria-hidden", "false"); setError(null); showControls(); const firstFocusable = panel.querySelector(focusableSelector) as HTMLElement | null; firstFocusable?.focus(); }; const close = (): void => { - panel.classList.add('hidden'); - panel.classList.remove('flex'); - panel.setAttribute('aria-hidden', 'true'); + panel.classList.add("hidden"); + panel.classList.remove("flex"); + panel.setAttribute("aria-hidden", "true"); setError(null); lastActiveElement?.focus(); }; - toggleBtn.addEventListener('click', () => { - if (panel.classList.contains('hidden')) open(); + toggleBtn.addEventListener("click", () => { + if (panel.classList.contains("hidden")) open(); else close(); }); - closeBtn?.addEventListener('click', close); + closeBtn?.addEventListener("click", close); - document.addEventListener('keydown', e => { - if (panel.classList.contains('hidden')) return; - if (e.key === 'Escape') { + document.addEventListener("keydown", (e) => { + if (panel.classList.contains("hidden")) return; + if (e.key === "Escape") { e.preventDefault(); close(); return; } - if (e.key !== 'Tab') return; + if (e.key !== "Tab") return; const focusables = Array.from(panel.querySelectorAll(focusableSelector)).filter( - el => - el instanceof HTMLElement && !el.hasAttribute('disabled') && !el.getAttribute('aria-hidden') + (el) => + el instanceof HTMLElement && + !el.hasAttribute("disabled") && + !el.getAttribute("aria-hidden"), ) as HTMLElement[]; if (focusables.length === 0) return; @@ -114,12 +116,12 @@ export const setupSegmentEditor = (): void => { }); // close when clicking the backdrop outside the modal content - document.addEventListener('pointerdown', e => { - if (panel.classList.contains('hidden')) return; + document.addEventListener("pointerdown", (e) => { + if (panel.classList.contains("hidden")) return; const target = e.target as Node | null; if (!target) return; if ( - (e.target as HTMLElement | null)?.closest('[data-segment-editor] [data-segment-editor-close]') + (e.target as HTMLElement | null)?.closest("[data-segment-editor] [data-segment-editor-close]") ) return; const content = panel.firstElementChild; @@ -128,15 +130,15 @@ export const setupSegmentEditor = (): void => { }); // dropdown type selector (uses ui-dropdown for visuals; we manage the value) - typeOptions.forEach(btn => { - btn.addEventListener('click', () => { - const v = (btn.getAttribute('data-value') || 'ed') as SkipType; + typeOptions.forEach((btn) => { + btn.addEventListener("click", () => { + const v = (btn.getAttribute("data-value") || "ed") as SkipType; if (typeValue) typeValue.value = v; - if (typeLabel) typeLabel.textContent = v === 'op' ? 'Opening (OP)' : 'Ending (ED)'; + if (typeLabel) typeLabel.textContent = v === "op" ? "Opening (OP)" : "Ending (ED)"; // close dropdown popover if it exists - const dropdown = btn.closest('ui-dropdown'); - const content = dropdown?.querySelector('[data-content]') as HTMLElement | null; - content?.classList.add('hidden'); + const dropdown = btn.closest("ui-dropdown"); + const content = dropdown?.querySelector("[data-content]") as HTMLElement | null; + content?.classList.add("hidden"); showControls(); }); }); @@ -147,19 +149,19 @@ export const setupSegmentEditor = (): void => { setError(null); updateLabels(); }; - resetBtn?.addEventListener('click', reset); + resetBtn?.addEventListener("click", reset); - markStartBtn?.addEventListener('click', () => { + markStartBtn?.addEventListener("click", () => { startTime = Math.max(0, state.video.currentTime); if (endTime != null && startTime >= endTime) endTime = null; setError(null); updateLabels(); showControls(); }); - markEndBtn?.addEventListener('click', () => { + markEndBtn?.addEventListener("click", () => { endTime = Math.max(0, state.video.currentTime); if (startTime != null && endTime <= startTime) { - setError('End must be after start.'); + setError("End must be after start."); return; } setError(null); @@ -167,24 +169,24 @@ export const setupSegmentEditor = (): void => { showControls(); }); - saveBtn?.addEventListener('click', async () => { - const skipType = ((typeValue?.value || 'ed') as SkipType) ?? 'ed'; + saveBtn?.addEventListener("click", async () => { + const skipType = ((typeValue?.value || "ed") as SkipType) ?? "ed"; if (startTime == null || endTime == null) { - setError('Mark start and end first.'); + setError("Mark start and end first."); return; } if (endTime <= startTime) { - setError('End must be after start.'); + setError("End must be after start."); return; } saveBtn.disabled = true; - saveBtn.classList.add('opacity-70'); + saveBtn.classList.add("opacity-70"); setError(null); try { - const res = await fetch('/api/watch/segments', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, + const res = await fetch("/api/watch/segments", { + method: "POST", + headers: { "Content-Type": "application/json" }, body: JSON.stringify({ mal_id: state.malID, episode: Number.parseInt(state.currentEpisode, 10), @@ -194,7 +196,7 @@ export const setupSegmentEditor = (): void => { }), }); if (!res.ok) { - let message = res.status === 401 ? 'Login required.' : 'Failed to save segment.'; + let message = res.status === 401 ? "Login required." : "Failed to save segment."; try { const payload = (await res.json()) as { error?: string }; if (payload?.error) message = payload.error; @@ -204,28 +206,28 @@ export const setupSegmentEditor = (): void => { } // Update local segments immediately so UI reflects the saved data. - const normalizedType = skipType === 'ed' ? 'ending' : 'opening'; - state.parsedSegments = (state.parsedSegments || []).filter(s => { - const t = (s.type || '').toLowerCase(); - if (normalizedType === 'ending') return t !== 'ed' && t !== 'ending' && t !== 'outro'; - return t !== 'op' && t !== 'opening' && t !== 'intro'; + const normalizedType = skipType === "ed" ? "ending" : "opening"; + state.parsedSegments = (state.parsedSegments || []).filter((s) => { + const t = (s.type || "").toLowerCase(); + if (normalizedType === "ending") return t !== "ed" && t !== "ending" && t !== "outro"; + return t !== "op" && t !== "opening" && t !== "intro"; }); state.parsedSegments.push({ type: normalizedType, start: startTime, end: endTime, - source: 'override', + source: "override", }); resolveActiveSegments(); renderSegments(); - window.showToast?.({ message: 'Segment saved.' }); + window.showToast?.({ message: "Segment saved." }); close(); } catch { - setError('Failed to save segment.'); + setError("Failed to save segment."); } finally { saveBtn.disabled = false; - saveBtn.classList.remove('opacity-70'); + saveBtn.classList.remove("opacity-70"); } });