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:
156
k-tv-frontend/app/(main)/tv/components/top-control-bar.tsx
Normal file
156
k-tv-frontend/app/(main)/tv/components/top-control-bar.tsx
Normal file
@@ -0,0 +1,156 @@
|
||||
import { Cast, Info, Maximize2, Minimize2 } from "lucide-react";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import { SubtitlePicker } from "./subtitle-picker";
|
||||
import { VolumeControl } from "./volume-control";
|
||||
import { QualityPicker } from "./quality-picker";
|
||||
import type { SubtitleTrack } from "./video-player";
|
||||
|
||||
interface TopControlBarProps {
|
||||
// Subtitles
|
||||
subtitleTracks: SubtitleTrack[];
|
||||
activeSubtitleTrack: number;
|
||||
showSubtitlePicker: boolean;
|
||||
onToggleSubtitlePicker: () => void;
|
||||
onChangeSubtitleTrack: (id: number) => void;
|
||||
// Volume
|
||||
volume: number;
|
||||
isMuted: boolean;
|
||||
VolumeIcon: LucideIcon;
|
||||
showVolumeSlider: boolean;
|
||||
onToggleVolumeSlider: () => void;
|
||||
onToggleMute: () => void;
|
||||
onVolumeChange: (v: number) => void;
|
||||
// Fullscreen
|
||||
isFullscreen: boolean;
|
||||
onToggleFullscreen: () => void;
|
||||
// Cast
|
||||
castAvailable: boolean;
|
||||
isCasting: boolean;
|
||||
castDeviceName?: string | null;
|
||||
streamUrl?: string | null;
|
||||
onRequestCast: (url: string) => void;
|
||||
onStopCasting: () => void;
|
||||
// Quality
|
||||
quality: string;
|
||||
qualityOptions: { value: string; label: string }[];
|
||||
showQualityPicker: boolean;
|
||||
onToggleQualityPicker: () => void;
|
||||
onChangeQuality: (q: string) => void;
|
||||
// Stats & guide
|
||||
showStats: boolean;
|
||||
onToggleStats: () => void;
|
||||
showSchedule: boolean;
|
||||
onToggleSchedule: () => void;
|
||||
}
|
||||
|
||||
export function TopControlBar({
|
||||
subtitleTracks,
|
||||
activeSubtitleTrack,
|
||||
showSubtitlePicker,
|
||||
onToggleSubtitlePicker,
|
||||
onChangeSubtitleTrack,
|
||||
volume,
|
||||
isMuted,
|
||||
VolumeIcon,
|
||||
showVolumeSlider,
|
||||
onToggleVolumeSlider,
|
||||
onToggleMute,
|
||||
onVolumeChange,
|
||||
isFullscreen,
|
||||
onToggleFullscreen,
|
||||
castAvailable,
|
||||
isCasting,
|
||||
castDeviceName,
|
||||
streamUrl,
|
||||
onRequestCast,
|
||||
onStopCasting,
|
||||
quality,
|
||||
qualityOptions,
|
||||
showQualityPicker,
|
||||
onToggleQualityPicker,
|
||||
onChangeQuality,
|
||||
showStats,
|
||||
onToggleStats,
|
||||
showSchedule,
|
||||
onToggleSchedule,
|
||||
}: TopControlBarProps) {
|
||||
return (
|
||||
<div className="flex justify-end gap-2 p-4">
|
||||
<SubtitlePicker
|
||||
tracks={subtitleTracks}
|
||||
activeTrack={activeSubtitleTrack}
|
||||
show={showSubtitlePicker}
|
||||
onToggle={onToggleSubtitlePicker}
|
||||
onChangeTrack={(id) => {
|
||||
onChangeSubtitleTrack(id);
|
||||
if (id !== activeSubtitleTrack) onToggleSubtitlePicker();
|
||||
}}
|
||||
/>
|
||||
|
||||
<VolumeControl
|
||||
volume={volume}
|
||||
isMuted={isMuted}
|
||||
VolumeIcon={VolumeIcon}
|
||||
showSlider={showVolumeSlider}
|
||||
onToggleSlider={onToggleVolumeSlider}
|
||||
onToggleMute={onToggleMute}
|
||||
onVolumeChange={(v) => {
|
||||
onVolumeChange(v);
|
||||
}}
|
||||
/>
|
||||
|
||||
<button
|
||||
className="pointer-events-auto rounded-md bg-black/50 p-1.5 text-zinc-400 backdrop-blur transition-colors hover:bg-black/70 hover:text-white"
|
||||
onClick={onToggleFullscreen}
|
||||
title={isFullscreen ? "Exit fullscreen [F]" : "Fullscreen [F]"}
|
||||
>
|
||||
{isFullscreen ? (
|
||||
<Minimize2 className="h-4 w-4" />
|
||||
) : (
|
||||
<Maximize2 className="h-4 w-4" />
|
||||
)}
|
||||
</button>
|
||||
|
||||
{castAvailable && (
|
||||
<button
|
||||
className={`pointer-events-auto rounded-md bg-black/50 p-1.5 backdrop-blur transition-colors hover:bg-black/70 hover:text-white ${
|
||||
isCasting ? "text-blue-400" : "text-zinc-400"
|
||||
}`}
|
||||
onClick={() =>
|
||||
isCasting ? onStopCasting() : streamUrl && onRequestCast(streamUrl)
|
||||
}
|
||||
title={
|
||||
isCasting
|
||||
? `Stop casting to ${castDeviceName ?? "TV"}`
|
||||
: "Cast to TV"
|
||||
}
|
||||
>
|
||||
<Cast className="h-4 w-4" />
|
||||
</button>
|
||||
)}
|
||||
|
||||
<QualityPicker
|
||||
quality={quality}
|
||||
options={qualityOptions}
|
||||
showPicker={showQualityPicker}
|
||||
onToggle={onToggleQualityPicker}
|
||||
onChangeQuality={onChangeQuality}
|
||||
/>
|
||||
|
||||
<button
|
||||
className={`pointer-events-auto rounded-md bg-black/50 p-1.5 backdrop-blur transition-colors hover:bg-black/70 hover:text-white ${showStats ? "text-violet-400" : "text-zinc-400"}`}
|
||||
onClick={onToggleStats}
|
||||
title="Stats for nerds [S]"
|
||||
>
|
||||
<Info className="h-4 w-4" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
className="pointer-events-auto rounded-md bg-black/50 px-3 py-1.5 text-xs text-zinc-400 backdrop-blur transition-colors hover:bg-black/70 hover:text-white"
|
||||
onClick={onToggleSchedule}
|
||||
>
|
||||
{showSchedule ? "Hide guide" : "Guide [G]"}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user