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
157 lines
4.4 KiB
TypeScript
157 lines
4.4 KiB
TypeScript
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>
|
|
);
|
|
}
|