mirror of
https://github.com/KuekHaoYang/KVideo.git
synced 2026-08-12 23:33:43 +08:00
feat: Add player settings page for default fullscreen mode selection and refine player fullscreen UI.
This commit is contained in:
@@ -26,6 +26,7 @@ export function useSettingsPage() {
|
||||
// Display settings
|
||||
const [realtimeLatency, setRealtimeLatency] = useState(false);
|
||||
const [searchDisplayMode, setSearchDisplayMode] = useState<SearchDisplayMode>('normal');
|
||||
const [fullscreenType, setFullscreenType] = useState<'native' | 'window'>('native');
|
||||
|
||||
useEffect(() => {
|
||||
const settings = settingsStore.getSettings();
|
||||
@@ -36,6 +37,7 @@ export function useSettingsPage() {
|
||||
setAccessPasswords(settings.accessPasswords);
|
||||
setRealtimeLatency(settings.realtimeLatency);
|
||||
setSearchDisplayMode(settings.searchDisplayMode);
|
||||
setFullscreenType(settings.fullscreenType);
|
||||
|
||||
// Fetch env password status
|
||||
fetch('/api/config')
|
||||
@@ -279,6 +281,15 @@ export function useSettingsPage() {
|
||||
});
|
||||
};
|
||||
|
||||
const handleFullscreenTypeChange = (type: 'native' | 'window') => {
|
||||
setFullscreenType(type);
|
||||
const currentSettings = settingsStore.getSettings();
|
||||
settingsStore.saveSettings({
|
||||
...currentSettings,
|
||||
fullscreenType: type,
|
||||
});
|
||||
};
|
||||
|
||||
const handleRestoreDefaults = () => {
|
||||
const defaults = getDefaultSources();
|
||||
handleSourcesChange(defaults);
|
||||
@@ -329,5 +340,7 @@ export function useSettingsPage() {
|
||||
handleEditSource,
|
||||
handleRealtimeLatencyChange,
|
||||
handleSearchDisplayModeChange,
|
||||
fullscreenType,
|
||||
handleFullscreenTypeChange,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { SortSettings } from '@/components/settings/SortSettings';
|
||||
import { DataSettings } from '@/components/settings/DataSettings';
|
||||
import { PasswordSettings } from '@/components/settings/PasswordSettings';
|
||||
import { DisplaySettings } from '@/components/settings/DisplaySettings';
|
||||
import { PlayerSettings } from '@/components/settings/PlayerSettings';
|
||||
import { SettingsHeader } from '@/components/settings/SettingsHeader';
|
||||
import { useSettingsPage } from './hooks/useSettingsPage';
|
||||
|
||||
@@ -22,6 +23,7 @@ export default function SettingsPage() {
|
||||
envPasswordSet,
|
||||
realtimeLatency,
|
||||
searchDisplayMode,
|
||||
fullscreenType,
|
||||
isAddModalOpen,
|
||||
isExportModalOpen,
|
||||
isImportModalOpen,
|
||||
@@ -52,6 +54,7 @@ export default function SettingsPage() {
|
||||
setEditingSource,
|
||||
handleRealtimeLatencyChange,
|
||||
handleSearchDisplayModeChange,
|
||||
handleFullscreenTypeChange,
|
||||
} = useSettingsPage();
|
||||
|
||||
return (
|
||||
@@ -60,6 +63,12 @@ export default function SettingsPage() {
|
||||
{/* Header */}
|
||||
<SettingsHeader />
|
||||
|
||||
{/* Player Settings */}
|
||||
<PlayerSettings
|
||||
fullscreenType={fullscreenType}
|
||||
onFullscreenTypeChange={handleFullscreenTypeChange}
|
||||
/>
|
||||
|
||||
{/* Password Settings */}
|
||||
<PasswordSettings
|
||||
enabled={passwordAccess}
|
||||
|
||||
@@ -117,13 +117,14 @@ export function DesktopVideoPlayer({
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={`relative aspect-video bg-black rounded-[var(--radius-2xl)] group ${data.isFullscreen && fullscreenType === 'window' ? 'is-web-fullscreen' : ''
|
||||
className={`kvideo-container relative aspect-video bg-black rounded-[var(--radius-2xl)] group ${data.isFullscreen && fullscreenType === 'window' ? 'is-web-fullscreen' : ''
|
||||
}`}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseLeave={() => isPlaying && setShowControls(false)}
|
||||
>
|
||||
{/* Clipping Wrapper for video and overlays - Restores the 'Liquid Glass' rounded look */}
|
||||
<div className="absolute inset-0 rounded-[var(--radius-2xl)] overflow-hidden pointer-events-none">
|
||||
<div className={`absolute inset-0 overflow-hidden pointer-events-none ${data.isFullscreen && fullscreenType === 'window' ? 'rounded-0' : 'rounded-[var(--radius-2xl)]'
|
||||
}`}>
|
||||
<div className="absolute inset-0 pointer-events-auto">
|
||||
{/* Video Element */}
|
||||
<video
|
||||
|
||||
@@ -147,6 +147,25 @@ export function DesktopMoreMenu({
|
||||
{/* Divider */}
|
||||
<div className="h-px bg-[var(--glass-border)] my-1.5 sm:my-2" />
|
||||
|
||||
{/* Fullscreen Mode Selector */}
|
||||
<div className="px-3 py-2 sm:px-4 sm:py-2.5 flex items-center justify-between gap-4 sm:gap-6">
|
||||
<div className="flex items-center gap-2 sm:gap-3 text-xs sm:text-sm text-[var(--text-color)]">
|
||||
<Icons.Settings size={16} className="sm:w-[18px] sm:h-[18px]" />
|
||||
<span>全屏方式</span>
|
||||
</div>
|
||||
<div className="relative">
|
||||
<button
|
||||
onClick={() => {
|
||||
setFullscreenType(fullscreenType === 'native' ? 'window' : 'native');
|
||||
}}
|
||||
className="flex items-center gap-1 sm:gap-1.5 bg-[var(--glass-bg)] border border-[var(--glass-border)] text-[var(--text-color)] text-[10px] sm:text-xs rounded-[var(--radius-2xl)] px-2 sm:px-2.5 py-1 sm:py-1.5 outline-none hover:border-[var(--accent-color)] hover:bg-[color-mix(in_srgb,var(--accent-color)_5%,transparent)] transition-all cursor-pointer whitespace-nowrap"
|
||||
>
|
||||
<span>{fullscreenType === 'native' ? '系统全屏' : '网页全屏'}</span>
|
||||
<Icons.Maximize size={12} className="text-[var(--text-color-secondary)]" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Show Mode Indicator Switch */}
|
||||
<div className="px-3 py-2 sm:px-4 sm:py-2.5 flex items-center justify-between gap-4 sm:gap-6">
|
||||
<div className="flex items-center gap-2 sm:gap-3 text-xs sm:text-sm text-[var(--text-color)]">
|
||||
@@ -271,44 +290,6 @@ export function DesktopMoreMenu({
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Fullscreen Mode Selector */}
|
||||
<div className="px-3 py-2 sm:px-4 sm:py-2.5 flex items-center justify-between gap-4 sm:gap-6">
|
||||
<div className="flex items-center gap-2 sm:gap-3 text-xs sm:text-sm text-[var(--text-color)]">
|
||||
<Icons.Settings size={16} className="sm:w-[18px] sm:h-[18px]" />
|
||||
<span>全屏方式</span>
|
||||
</div>
|
||||
<div className="relative">
|
||||
<button
|
||||
onClick={() => {
|
||||
setFullscreenType(fullscreenType === 'native' ? 'window' : 'native');
|
||||
}}
|
||||
className="flex items-center gap-1 sm:gap-1.5 bg-[var(--glass-bg)] border border-[var(--glass-border)] text-[var(--text-color)] text-[10px] sm:text-xs rounded-[var(--radius-2xl)] px-2 sm:px-2.5 py-1 sm:py-1.5 outline-none hover:border-[var(--accent-color)] hover:bg-[color-mix(in_srgb,var(--accent-color)_5%,transparent)] transition-all cursor-pointer whitespace-nowrap"
|
||||
>
|
||||
<span>{fullscreenType === 'native' ? '系统全屏' : '网页全屏'}</span>
|
||||
<Icons.Maximize size={12} className="text-[var(--text-color-secondary)]" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Fullscreen Mode Selector */}
|
||||
<div className="px-3 py-2 sm:px-4 sm:py-2.5 flex items-center justify-between gap-4 sm:gap-6">
|
||||
<div className="flex items-center gap-2 sm:gap-3 text-xs sm:text-sm text-[var(--text-color)]">
|
||||
<Icons.Settings size={16} className="sm:w-[18px] sm:h-[18px]" />
|
||||
<span>全屏方式</span>
|
||||
</div>
|
||||
<div className="relative">
|
||||
<button
|
||||
onClick={() => {
|
||||
setFullscreenType(fullscreenType === 'native' ? 'window' : 'native');
|
||||
}}
|
||||
className="flex items-center gap-1 sm:gap-1.5 bg-[var(--glass-bg)] border border-[var(--glass-border)] text-[var(--text-color)] text-[10px] sm:text-xs rounded-[var(--radius-2xl)] px-2 sm:px-2.5 py-1 sm:py-1.5 outline-none hover:border-[var(--accent-color)] hover:bg-[color-mix(in_srgb,var(--accent-color)_5%,transparent)] transition-all cursor-pointer whitespace-nowrap"
|
||||
>
|
||||
<span>{fullscreenType === 'native' ? '系统全屏' : '网页全屏'}</span>
|
||||
<Icons.Maximize size={12} className="text-[var(--text-color-secondary)]" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Skip Outro Switch */}
|
||||
<div className="px-3 py-2 sm:px-4 sm:py-2.5">
|
||||
<div className="flex items-center justify-between gap-4 sm:gap-6">
|
||||
@@ -348,7 +329,7 @@ export function DesktopMoreMenu({
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div >
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -52,7 +52,7 @@ export function DisplaySettings({
|
||||
<button
|
||||
onClick={() => onSearchDisplayModeChange('normal')}
|
||||
className={`px-4 py-3 rounded-[var(--radius-2xl)] border text-left font-medium transition-all duration-200 cursor-pointer ${searchDisplayMode === 'normal'
|
||||
? 'bg-[var(--accent-color)] border-[var(--accent-color)] text-white'
|
||||
? 'bg-[var(--accent-color)] border-[var(--accent-color)] text-white shadow-[0_4px_12px_rgba(var(--accent-color-rgb),0.3)]'
|
||||
: 'bg-[var(--glass-bg)] border-[var(--glass-border)] text-[var(--text-color)] hover:bg-[color-mix(in_srgb,var(--accent-color)_10%,transparent)]'
|
||||
}`}
|
||||
>
|
||||
@@ -62,7 +62,7 @@ export function DisplaySettings({
|
||||
<button
|
||||
onClick={() => onSearchDisplayModeChange('grouped')}
|
||||
className={`px-4 py-3 rounded-[var(--radius-2xl)] border text-left font-medium transition-all duration-200 cursor-pointer ${searchDisplayMode === 'grouped'
|
||||
? 'bg-[var(--accent-color)] border-[var(--accent-color)] text-white'
|
||||
? 'bg-[var(--accent-color)] border-[var(--accent-color)] text-white shadow-[0_4px_12px_rgba(var(--accent-color-rgb),0.3)]'
|
||||
: 'bg-[var(--glass-bg)] border-[var(--glass-border)] text-[var(--text-color)] hover:bg-[color-mix(in_srgb,var(--accent-color)_10%,transparent)]'
|
||||
}`}
|
||||
>
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
'use client';
|
||||
|
||||
/**
|
||||
* PlayerSettings - Global settings for the video player
|
||||
* Following Liquid Glass design system
|
||||
*/
|
||||
|
||||
import { Icons } from '@/components/ui/Icon';
|
||||
|
||||
interface PlayerSettingsProps {
|
||||
fullscreenType: 'native' | 'window';
|
||||
onFullscreenTypeChange: (type: 'native' | 'window') => void;
|
||||
}
|
||||
|
||||
export function PlayerSettings({
|
||||
fullscreenType,
|
||||
onFullscreenTypeChange,
|
||||
}: PlayerSettingsProps) {
|
||||
return (
|
||||
<div className="bg-[var(--glass-bg)] border border-[var(--glass-border)] rounded-[var(--radius-2xl)] shadow-[var(--shadow-sm)] p-6 mb-6">
|
||||
<h2 className="text-xl font-semibold text-[var(--text-color)] mb-4">播放器设置</h2>
|
||||
|
||||
{/* Fullscreen Mode Selection */}
|
||||
<div>
|
||||
<h3 className="font-medium text-[var(--text-color)] mb-2 inline-flex items-center gap-2">
|
||||
<Icons.Maximize size={18} className="text-[var(--accent-color)]" />
|
||||
默认全屏方式
|
||||
</h3>
|
||||
<p className="text-sm text-[var(--text-color-secondary)] mb-4">
|
||||
选择在桌面端点击播放器全屏按钮时的行为
|
||||
</p>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
<button
|
||||
onClick={() => onFullscreenTypeChange('native')}
|
||||
className={`px-4 py-3 rounded-[var(--radius-2xl)] border text-left font-medium transition-all duration-200 cursor-pointer ${fullscreenType === 'native'
|
||||
? 'bg-[var(--accent-color)] border-[var(--accent-color)] text-white shadow-[0_4px_12px_rgba(var(--accent-color-rgb),0.3)]'
|
||||
: 'bg-[var(--glass-bg)] border-[var(--glass-border)] text-[var(--text-color)] hover:bg-[color-mix(in_srgb,var(--accent-color)_10%,transparent)]'
|
||||
}`}
|
||||
>
|
||||
<div className="font-semibold">系统全屏</div>
|
||||
<div className="text-sm opacity-80 mt-1">进入浏览器原生全屏状态</div>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onFullscreenTypeChange('window')}
|
||||
className={`px-4 py-3 rounded-[var(--radius-2xl)] border text-left font-medium transition-all duration-200 cursor-pointer ${fullscreenType === 'window'
|
||||
? 'bg-[var(--accent-color)] border-[var(--accent-color)] text-white shadow-[0_4px_12px_rgba(var(--accent-color-rgb),0.3)]'
|
||||
: 'bg-[var(--glass-bg)] border-[var(--glass-border)] text-[var(--text-color)] hover:bg-[color-mix(in_srgb,var(--accent-color)_10%,transparent)]'
|
||||
}`}
|
||||
>
|
||||
<div className="font-semibold">网页全屏</div>
|
||||
<div className="text-sm opacity-80 mt-1">播放器填满当前浏览器窗口</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user