From e500af610210296f824145d90aeb1dceee70ca5d Mon Sep 17 00:00:00 2001 From: mkelvers Date: Thu, 28 May 2026 11:29:14 +0200 Subject: [PATCH] chore: format player episode nav and ui --- static/player/episodes/nav.ts | 62 +++++++++++++++++------------------ static/player/episodes/ui.ts | 46 +++++++++++++------------- 2 files changed, 54 insertions(+), 54 deletions(-) diff --git a/static/player/episodes/nav.ts b/static/player/episodes/nav.ts index b5fcc44..ac15678 100644 --- a/static/player/episodes/nav.ts +++ b/static/player/episodes/nav.ts @@ -1,12 +1,12 @@ -import { state } from '../state'; -import type { SkipSegment } from '../types'; -import { resolveActiveSegments, renderSegments } from '../skip/segments'; -import { updateSubtitleOptions } from '../subtitles'; -import { updateQualityOptions } from '../quality'; -import { updateModeButtons } from '../mode'; -import { updateOverlay, isAutoplayEnabled, switchEpisodeRange } from './ui'; -import { markEpisodeTransition } from '../progress'; -import { safeLocalStorage } from '../storage'; +import { state } from "../state"; +import type { SkipSegment } from "../types"; +import { resolveActiveSegments, renderSegments } from "../skip/segments"; +import { updateSubtitleOptions } from "../subtitles"; +import { updateQualityOptions } from "../quality"; +import { updateModeButtons } from "../mode"; +import { updateOverlay, isAutoplayEnabled, switchEpisodeRange } from "./ui"; +import { markEpisodeTransition } from "../progress"; +import { safeLocalStorage } from "../storage"; /** * Handles video end: either marks complete or loads next episode. @@ -18,7 +18,7 @@ export const goToNextEpisode = async (): Promise => { // final episode: trigger completion flow if (state.totalEpisodes > 0 && currentEp >= state.totalEpisodes) { - import('./complete').then(m => m.completeAnime(currentEp)); + import("./complete").then((m) => m.completeAnime(currentEp)); return; } @@ -30,13 +30,13 @@ export const goToNextEpisode = async (): Promise => { try { const res = await fetch( - `/api/watch/episode/${state.malID}/${nextEp}?mode=${encodeURIComponent(state.currentMode)}` + `/api/watch/episode/${state.malID}/${nextEp}?mode=${encodeURIComponent(state.currentMode)}`, ); if (!res.ok) { // fallback: full page navigation - sessionStorage.setItem('mal:autoplay-next', 'true'); + sessionStorage.setItem("mal:autoplay-next", "true"); const url = new URL(window.location.href); - url.searchParams.set('ep', String(nextEp)); + url.searchParams.set("ep", String(nextEp)); window.location.href = url.toString(); return; } @@ -47,21 +47,21 @@ export const goToNextEpisode = async (): Promise => { state.modeSources = data.mode_sources ?? {}; state.availableModes = data.available_modes ?? []; - const backendMode = typeof data.initial_mode === 'string' ? data.initial_mode : ''; + const backendMode = typeof data.initial_mode === "string" ? data.initial_mode : ""; const fallback = state.modeSources[backendMode]?.token ? backendMode - : state.availableModes.find(m => state.modeSources[m]?.token); + : state.availableModes.find((m) => state.modeSources[m]?.token); if (!fallback) { - sessionStorage.setItem('mal:autoplay-next', 'true'); + sessionStorage.setItem("mal:autoplay-next", "true"); const url = new URL(window.location.href); - url.searchParams.set('ep', String(nextEp)); + url.searchParams.set("ep", String(nextEp)); window.location.href = url.toString(); return; } state.currentEpisode = String(nextEp); state.currentMode = fallback; - if (data.mode_switched_from === 'dub' && fallback === 'sub') { + if (data.mode_switched_from === "dub" && fallback === "sub") { window.showToast?.({ message: `Episode ${nextEp} is only available in sub, switched from dub.`, }); @@ -72,8 +72,8 @@ export const goToNextEpisode = async (): Promise => { state.container.dataset.startTimeSeconds = String(state.startTimeSeconds); // load new video (keep preferences) - const preferredQuality = safeLocalStorage.getItem('mal:preferred-quality') || 'best'; - state.video.src = `${state.streamURL}?mode=${encodeURIComponent(fallback)}&token=${encodeURIComponent(state.modeSources[fallback].token)}${preferredQuality !== 'best' ? `&quality=${encodeURIComponent(preferredQuality)}` : ''}`; + const preferredQuality = safeLocalStorage.getItem("mal:preferred-quality") || "best"; + state.video.src = `${state.streamURL}?mode=${encodeURIComponent(fallback)}&token=${encodeURIComponent(state.modeSources[fallback].token)}${preferredQuality !== "best" ? `&quality=${encodeURIComponent(preferredQuality)}` : ""}`; state.video.load(); if (!state.video.paused) { state.video.play().catch(() => undefined); @@ -88,7 +88,7 @@ export const goToNextEpisode = async (): Promise => { updateSubtitleOptions(); updateQualityOptions(); updateModeButtons(); - updateOverlay(state.currentEpisode, data.episode_title ?? ''); + updateOverlay(state.currentEpisode, data.episode_title ?? ""); // update skip segments if (data.segments?.length) { @@ -101,28 +101,28 @@ export const goToNextEpisode = async (): Promise => { // highlight new episode in list/grid state.episodeList - ?.querySelectorAll('[data-episode-id]') - .forEach(el => el.classList.remove('bg-accent/20')); + ?.querySelectorAll("[data-episode-id]") + .forEach((el) => el.classList.remove("bg-accent/20")); const newListEl = state.episodeList?.querySelector(`[data-episode-id="${nextEp}"]`); - newListEl?.classList.add('bg-accent/20'); + newListEl?.classList.add("bg-accent/20"); if (state.episodeGrid) { - state.episodeGrid.querySelectorAll('[data-episode-id]').forEach(el => { - el.classList.remove('bg-accent/20', 'ring-2', 'ring-accent', 'text-accent'); + state.episodeGrid.querySelectorAll("[data-episode-id]").forEach((el) => { + el.classList.remove("bg-accent/20", "ring-2", "ring-accent", "text-accent"); }); switchEpisodeRange(Math.floor((nextEp - 1) / 100)); const newGridEl = state.episodeGrid.querySelector(`[data-episode-id="${nextEp}"]`); - newGridEl?.classList.add('bg-accent/20', 'ring-2', 'ring-accent', 'text-accent'); + newGridEl?.classList.add("bg-accent/20", "ring-2", "ring-accent", "text-accent"); } // update URL without reload const url = new URL(window.location.href); - url.searchParams.set('ep', String(nextEp)); - history.pushState(null, '', url.toString()); + url.searchParams.set("ep", String(nextEp)); + history.pushState(null, "", url.toString()); } catch { - sessionStorage.setItem('mal:autoplay-next', 'true'); + sessionStorage.setItem("mal:autoplay-next", "true"); const url = new URL(window.location.href); - url.searchParams.set('ep', String(nextEp)); + url.searchParams.set("ep", String(nextEp)); window.location.href = url.toString(); } }; diff --git a/static/player/episodes/ui.ts b/static/player/episodes/ui.ts index 6bc984c..ec71cf7 100644 --- a/static/player/episodes/ui.ts +++ b/static/player/episodes/ui.ts @@ -1,26 +1,26 @@ -import { state } from '../state'; -import { qs } from '../../q'; -import { safeLocalStorage } from '../storage'; +import { state } from "../state"; +import { qs } from "../../q"; +import { safeLocalStorage } from "../storage"; /** * Syncs autoplay checkbox with localStorage on init. * Default is enabled (not 'false'). */ export const setupAutoplayButton = (): void => { - const btn = document.querySelector('[data-autoplay]') as HTMLInputElement | null; + const btn = document.querySelector("[data-autoplay]") as HTMLInputElement | null; if (!btn) return; - btn.checked = safeLocalStorage.getItem('mal:autoplay-enabled') !== 'false'; + btn.checked = safeLocalStorage.getItem("mal:autoplay-enabled") !== "false"; }; export const isAutoplayEnabled = (): boolean => - safeLocalStorage.getItem('mal:autoplay-enabled') !== 'false'; + safeLocalStorage.getItem("mal:autoplay-enabled") !== "false"; /** * Updates video overlay text (shown briefly on episode change). */ export const updateOverlay = (episode: string, title: string): void => { if (!state.videoOverlay) return; - const p = state.videoOverlay.querySelector('p'); + const p = state.videoOverlay.querySelector("p"); if (!p) return; p.textContent = title ? `Episode ${episode}, ${title}` : `Episode ${episode}`; }; @@ -30,8 +30,8 @@ const getEpisodeEls = () => { const grid = state.episodeGrid; const list = state.episodeList; return { - gridEls: grid ? Array.from(grid.querySelectorAll('[data-episode-id]')) : [], - listEls: list ? Array.from(list.querySelectorAll('[data-episode-id]')) : [], + gridEls: grid ? Array.from(grid.querySelectorAll("[data-episode-id]")) : [], + listEls: list ? Array.from(list.querySelectorAll("[data-episode-id]")) : [], }; }; @@ -42,17 +42,17 @@ const getEpisodeEls = () => { export const updateEpisodeHighlight = (num: number): void => { const { gridEls, listEls } = getEpisodeEls(); // clear old highlights - [...gridEls, ...listEls].forEach(el => - el.classList.remove('ring-2', 'ring-accent', 'bg-accent/20', 'text-accent') + [...gridEls, ...listEls].forEach((el) => + el.classList.remove("ring-2", "ring-accent", "bg-accent/20", "text-accent"), ); // apply new highlight const gridEl = state.episodeGrid?.querySelector(`[data-episode-id="${num}"]`); const listEl = state.episodeList?.querySelector(`[data-episode-id="${num}"]`); - gridEl?.classList.add('ring-2', 'ring-accent'); - listEl?.classList.add('ring-2', 'ring-accent'); + gridEl?.classList.add("ring-2", "ring-accent"); + listEl?.classList.add("ring-2", "ring-accent"); // scroll into view - (gridEl ?? listEl)?.scrollIntoView({ behavior: 'smooth', block: 'center' }); + (gridEl ?? listEl)?.scrollIntoView({ behavior: "smooth", block: "center" }); }; /** @@ -60,23 +60,23 @@ export const updateEpisodeHighlight = (num: number): void => { * Updates dropdown label and hides/shows episode cards. */ export const switchEpisodeRange = (idx: number): void => { - const dropdown = qs('[data-episode-dropdown]'); + const dropdown = qs("[data-episode-dropdown]"); if (!dropdown) return; - const btns = Array.from(dropdown.querySelectorAll('.episode-range-btn')) as HTMLButtonElement[]; + const btns = Array.from(dropdown.querySelectorAll(".episode-range-btn")) as HTMLButtonElement[]; const target = btns[idx]; if (!target) return; - const start = Number.parseInt(target.dataset.rangeStart ?? '1', 10); - const end = Number.parseInt(target.dataset.rangeEnd ?? '100', 10); + const start = Number.parseInt(target.dataset.rangeStart ?? "1", 10); + const end = Number.parseInt(target.dataset.rangeEnd ?? "100", 10); // update label (e.g., "01-100") - const label = dropdown.querySelector('[data-dropdown-label]') as HTMLElement | null; + const label = dropdown.querySelector("[data-dropdown-label]") as HTMLElement | null; if (label) - label.textContent = `${String(start).padStart(2, '0')}-${String(end).padStart(2, '0')}`; + label.textContent = `${String(start).padStart(2, "0")}-${String(end).padStart(2, "0")}`; // show/hide episodes in range - state.episodeGrid?.querySelectorAll('[data-episode-id]').forEach(el => { - const n = Number.parseInt((el as HTMLElement).dataset.episodeId ?? '0', 10); - el.classList.toggle('hidden', n < start || n > end); + state.episodeGrid?.querySelectorAll("[data-episode-id]").forEach((el) => { + const n = Number.parseInt((el as HTMLElement).dataset.episodeId ?? "0", 10); + el.classList.toggle("hidden", n < start || n > end); }); };