refactor(frontend): extract logic to hooks, split inline components

Area 1 (tv/page.tsx 964→423 lines):
- hooks: use-fullscreen, use-idle, use-volume, use-quality, use-subtitles,
  use-channel-input, use-channel-passwords, use-tv-keyboard
- components: SubtitlePicker, VolumeControl, QualityPicker, TopControlBar,
  LogoWatermark, AutoplayPrompt, ChannelNumberOverlay, TvBaseLayer

Area 2 (edit-channel-sheet.tsx 1244→678 lines):
- hooks: use-channel-form (all form state + reset logic)
- lib/schemas.ts: extracted Zod schemas + extractErrors
- components: AlgorithmicFilterEditor, RecyclePolicyEditor, WebhookEditor,
  AccessSettingsEditor, LogoEditor

Area 3 (dashboard/page.tsx 406→261 lines):
- hooks: use-channel-order, use-import-channel, use-regenerate-all
- lib/channel-export.ts: pure export utility
- components: DashboardHeader
This commit is contained in:
2026-03-17 02:25:02 +01:00
parent ce92b43205
commit 8ed8da2d90
32 changed files with 2629 additions and 1689 deletions

View File

@@ -0,0 +1,78 @@
"use client";
import { useEffect, useRef } from "react";
import type { SubtitleTrack } from "@/app/(main)/tv/components/video-player";
interface UseTvKeyboardOptions {
nextChannel: () => void;
prevChannel: () => void;
toggleSchedule: () => void;
toggleFullscreen: () => void;
toggleMute: () => void;
setShowStats: React.Dispatch<React.SetStateAction<boolean>>;
subtitleTracks: SubtitleTrack[];
setActiveSubtitleTrack: React.Dispatch<React.SetStateAction<number>>;
handleDigit: (digit: string) => void;
}
export function useTvKeyboard(opts: UseTvKeyboardOptions) {
const optsRef = useRef(opts);
useEffect(() => {
optsRef.current = opts;
});
useEffect(() => {
const handleKey = (e: KeyboardEvent) => {
if (
e.target instanceof HTMLInputElement ||
e.target instanceof HTMLTextAreaElement
)
return;
const o = optsRef.current;
switch (e.key) {
case "ArrowUp":
case "PageUp":
e.preventDefault();
o.nextChannel();
break;
case "ArrowDown":
case "PageDown":
e.preventDefault();
o.prevChannel();
break;
case "s":
case "S":
o.setShowStats((v) => !v);
break;
case "g":
case "G":
o.toggleSchedule();
break;
case "f":
case "F":
o.toggleFullscreen();
break;
case "m":
case "M":
o.toggleMute();
break;
case "c":
case "C":
if (o.subtitleTracks.length > 0) {
o.setActiveSubtitleTrack((prev) =>
prev === -1 ? o.subtitleTracks[0].id : -1,
);
}
break;
default:
if (e.key >= "0" && e.key <= "9") {
o.handleDigit(e.key);
}
}
};
window.addEventListener("keydown", handleKey);
return () => window.removeEventListener("keydown", handleKey);
}, []);
}