style: format static/player/main.ts

This commit is contained in:
2026-06-21 02:04:43 +02:00
committed by Milas Holsting
parent 0d343dfff9
commit d3e294b7c9

View File

@@ -1,46 +1,52 @@
import { state, initState, showEndState, hideEndState } from "./state"; import { onHtmxLoad, onReady } from "../utils";
import { invalidateBounds, updateTimeline } from "./timeline";
import { setupControls, showControls } from "./controls"; import { setupControls, showControls } from "./controls";
import { formatTime } from "./controls";
import { goToNextEpisode } from "./episodes/nav";
import { setupThumbnails } from "./episodes/thumbnails";
import { setupAutoplayButton, updateEpisodeHighlight, switchEpisodeRange } from "./episodes/ui";
import { setupKeyboard } from "./keyboard"; import { setupKeyboard } from "./keyboard";
import { setupSubtitles, updateSubtitleOptions, updateSubtitleRender } from "./subtitles";
import { setupSkip, updateSkipButton, updateAutoSkipButton } from "./skip";
import { setupQuality, updateQualityOptions } from "./quality";
import { import {
ensurePreferredModeSource, ensurePreferredModeSource,
hydrateAlternateMode, hydrateAlternateMode,
setupMode, setupMode,
updateModeButtons, updateModeButtons,
} from "./mode"; } from "./mode";
import { setupAutoplayButton, updateEpisodeHighlight, switchEpisodeRange } from "./episodes/ui";
import { goToNextEpisode } from "./episodes/nav";
import { resolveActiveSegments, renderSegments } from "./skip/segments";
import { setupSegmentEditor } from "./skip/editor";
import { setupThumbnails } from "./episodes/thumbnails";
import { markEpisodeTransition, saveEndedProgress, setupProgress } from "./progress"; import { markEpisodeTransition, saveEndedProgress, setupProgress } from "./progress";
import { setupQuality, updateQualityOptions } from "./quality";
import { setupSkip, updateSkipButton, updateAutoSkipButton } from "./skip";
import { setupSegmentEditor } from "./skip/editor";
import { resolveActiveSegments, renderSegments } from "./skip/segments";
import { state, initState, showEndState, hideEndState } from "./state";
import { safeLocalStorage } from "./storage"; import { safeLocalStorage } from "./storage";
import { destroyVideoSource, loadVideoSource } from "./video"; import { setupSubtitles, updateSubtitleOptions, updateSubtitleRender } from "./subtitles";
import { invalidateBounds, updateTimeline } from "./timeline";
import { import {
absoluteTimeFromDisplay, absoluteTimeFromDisplay,
absoluteTimeFromRatio, absoluteTimeFromRatio,
getBounds, getBounds,
displayTimeFromAbsolute, displayTimeFromAbsolute,
} from "./timeline"; } from "./timeline";
import { formatTime } from "./controls"; import { destroyVideoSource, loadVideoSource } from "./video";
import { onHtmxLoad, onReady } from "../utils";
let currentContainer: HTMLElement | null = null; let currentContainer: HTMLElement | null = null;
let cleanup: (() => void) | null = null; let cleanup: (() => void) | null = null;
type ClosableDropdown = HTMLElement & { close: () => void }; type ClosableDropdown = HTMLElement & { close: () => void };
const isClosableDropdown = (el: Element | null): el is ClosableDropdown => { const isClosableDropdown = (el: Element | null): el is ClosableDropdown => {
if (!el) return false; if (!el) {
if (!(el instanceof HTMLElement)) return false; return false;
}
if (!(el instanceof HTMLElement)) {
return false;
}
const maybe = el as Partial<{ close: unknown }>; const maybe = el as Partial<{ close: unknown }>;
return typeof maybe.close === "function"; return typeof maybe.close === "function";
}; };
const hidePreviewPopover = (): void => { const hidePreviewPopover = (): void => {
if (!state.elements.previewPopover) return; if (!state.elements.previewPopover) {
return;
}
state.elements.previewPopover.classList.add("hidden"); state.elements.previewPopover.classList.add("hidden");
state.elements.previewPopover.classList.add("opacity-0"); state.elements.previewPopover.classList.add("opacity-0");
state.elements.previewPopover.classList.remove("opacity-100"); state.elements.previewPopover.classList.remove("opacity-100");
@@ -48,7 +54,9 @@ const hidePreviewPopover = (): void => {
}; };
const showPreviewPopover = (): void => { const showPreviewPopover = (): void => {
if (!state.elements.previewPopover) return; if (!state.elements.previewPopover) {
return;
}
state.elements.previewPopover.classList.remove("hidden"); state.elements.previewPopover.classList.remove("hidden");
state.elements.previewPopover.classList.remove("opacity-0"); state.elements.previewPopover.classList.remove("opacity-0");
state.elements.previewPopover.classList.add("opacity-100"); state.elements.previewPopover.classList.add("opacity-100");
@@ -95,8 +103,12 @@ const updatePreviewUI = (ratio: number): void => {
const initPlayer = async (): Promise<void> => { const initPlayer = async (): Promise<void> => {
const container = document.querySelector("[data-video-player]") as HTMLElement | null; const container = document.querySelector("[data-video-player]") as HTMLElement | null;
if (!container) return; if (!container) {
if (container === currentContainer) return; return;
}
if (container === currentContainer) {
return;
}
teardownPlayer(); teardownPlayer();
if (!initState(container)) { if (!initState(container)) {
@@ -105,14 +117,16 @@ const initPlayer = async (): Promise<void> => {
} }
currentContainer = container; currentContainer = container;
const abortController = new AbortController(); const abortController = new AbortController();
const signal = abortController.signal; const { signal } = abortController;
cleanup = null; cleanup = null;
const loading = container.querySelector("[data-loading]") as HTMLElement | null; const loading = container.querySelector("[data-loading]") as HTMLElement | null;
const progressWrap = container.querySelector("[data-progress-wrap]") as HTMLElement | null; const progressWrap = container.querySelector("[data-progress-wrap]") as HTMLElement | null;
const scrubToPointer = (clientX: number, shouldShowControls: boolean): void => { const scrubToPointer = (clientX: number, shouldShowControls: boolean): void => {
if (!progressWrap) return; if (!progressWrap) {
return;
}
const rect = progressWrap.getBoundingClientRect(); const rect = progressWrap.getBoundingClientRect();
state.elements.video.currentTime = absoluteTimeFromRatio( state.elements.video.currentTime = absoluteTimeFromRatio(
Math.max(0, Math.min(1, (clientX - rect.left) / rect.width)), Math.max(0, Math.min(1, (clientX - rect.left) / rect.width)),
@@ -159,7 +173,9 @@ const initPlayer = async (): Promise<void> => {
const resumeAfterModeSwitch = (() => { const resumeAfterModeSwitch = (() => {
try { try {
const raw = sessionStorage.getItem("mal:resume-after-mode-switch"); const raw = sessionStorage.getItem("mal:resume-after-mode-switch");
if (raw === null) return null; if (raw === null) {
return null;
}
sessionStorage.removeItem("mal:resume-after-mode-switch"); sessionStorage.removeItem("mal:resume-after-mode-switch");
const parsed = Number(raw); const parsed = Number(raw);
return Number.isFinite(parsed) && parsed >= 0 ? parsed : null; return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
@@ -303,13 +319,15 @@ const initPlayer = async (): Promise<void> => {
"pointerdown", "pointerdown",
(e) => { (e) => {
// ignore right/middle click // ignore right/middle click
if ("button" in e && e.button !== 0) return; if ("button" in e && e.button !== 0) {
return;
}
state.ui.isScrubbing = true; state.ui.isScrubbing = true;
try { try {
(e.currentTarget as HTMLElement).setPointerCapture((e as PointerEvent).pointerId); (e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);
} catch (e) { } catch (error) {
console.error("failed to capture pointer:", e); console.error("failed to capture pointer:", error);
throw e; throw error;
} }
scrubToPointer(e.clientX, true); scrubToPointer(e.clientX, true);
}, },
@@ -331,7 +349,9 @@ const initPlayer = async (): Promise<void> => {
"pointerup", "pointerup",
() => { () => {
// ensure we finish the seek even if no window mousemove fired // ensure we finish the seek even if no window mousemove fired
if (!progressWrap) return; if (!progressWrap) {
return;
}
state.ui.isScrubbing = false; state.ui.isScrubbing = false;
}, },
{ signal }, { signal },
@@ -341,7 +361,9 @@ const initPlayer = async (): Promise<void> => {
window.addEventListener( window.addEventListener(
"pointermove", "pointermove",
(e) => { (e) => {
if (!state.ui.isScrubbing || !progressWrap) return; if (!state.ui.isScrubbing || !progressWrap) {
return;
}
scrubToPointer(e.clientX, false); scrubToPointer(e.clientX, false);
}, },
{ signal }, { signal },
@@ -351,18 +373,30 @@ const initPlayer = async (): Promise<void> => {
document.addEventListener( document.addEventListener(
"click", "click",
(e) => { (e) => {
const target = e.target; const { target } = e;
if (!(target instanceof Element)) return; if (!(target instanceof Element)) {
return;
}
const anchor = target.closest("a[href]"); const anchor = target.closest("a[href]");
if (!(anchor instanceof HTMLAnchorElement)) return; if (!(anchor instanceof HTMLAnchorElement)) {
return;
}
const url = new URL(anchor.href, location.origin); const url = new URL(anchor.href, location.origin);
if (url.origin !== location.origin) return; if (url.origin !== location.origin) {
return;
}
const parts = url.pathname.split("/").filter(Boolean); const parts = url.pathname.split("/").filter(Boolean);
if (parts[0] !== "anime" || parts[2] !== "watch") return; if (parts[0] !== "anime" || parts[2] !== "watch") {
if (Number.parseInt(parts[1], 10) !== state.episode.malID) return; return;
}
if (Number.parseInt(parts[1], 10) !== state.episode.malID) {
return;
}
const nextEpisode = Number.parseInt(url.searchParams.get("ep") ?? "1", 10); const nextEpisode = Number.parseInt(url.searchParams.get("ep") ?? "1", 10);
const currentEpisode = Number.parseInt(state.episode.current, 10); const currentEpisode = Number.parseInt(state.episode.current, 10);
if (nextEpisode === currentEpisode + 1) markEpisodeTransition(nextEpisode); if (nextEpisode === currentEpisode + 1) {
markEpisodeTransition(nextEpisode);
}
}, },
{ signal }, { signal },
); );
@@ -380,7 +414,7 @@ const initPlayer = async (): Promise<void> => {
clearTimeout(searchDebounce); clearTimeout(searchDebounce);
// debounce to avoid excessive range switches while typing // debounce to avoid excessive range switches while typing
searchDebounce = window.setTimeout(() => { searchDebounce = window.setTimeout(() => {
const val = searchInput.value.replace(/\D/g, ""); const val = searchInput.value.replaceAll(/\D/g, "");
if (!val) { if (!val) {
// clear: jump to current episode range // clear: jump to current episode range
const cur = Number.parseInt(state.episode.current, 10); const cur = Number.parseInt(state.episode.current, 10);
@@ -389,7 +423,9 @@ const initPlayer = async (): Promise<void> => {
return; return;
} }
const ep = Number.parseInt(val, 10); const ep = Number.parseInt(val, 10);
if (!ep || ep <= 0) return; if (!ep || ep <= 0) {
return;
}
const maxEp = state.episode.total > 0 ? state.episode.total : 500; const maxEp = state.episode.total > 0 ? state.episode.total : 500;
const clamped = Math.min(ep, maxEp); const clamped = Math.min(ep, maxEp);
searchInput.value = String(clamped); searchInput.value = String(clamped);
@@ -412,7 +448,9 @@ const initPlayer = async (): Promise<void> => {
const idx = Number.parseInt((btn as HTMLElement).dataset.rangeIndex ?? "0", 10); const idx = Number.parseInt((btn as HTMLElement).dataset.rangeIndex ?? "0", 10);
switchEpisodeRange(idx); switchEpisodeRange(idx);
const dd = btn.closest("ui-dropdown"); const dd = btn.closest("ui-dropdown");
if (isClosableDropdown(dd)) dd.close(); if (isClosableDropdown(dd)) {
dd.close();
}
}, },
{ signal }, { signal },
); );