b57b24d051
Add all source files for a feature-rich, reusable video player built with React, TypeScript, and Vite. Includes core components, context, hooks, utilities, styles, demo app, and configuration files.
202 lines
7.3 KiB
TypeScript
202 lines
7.3 KiB
TypeScript
import React, { useEffect, useRef, useState } from 'react'
|
||
import { usePlayerContext } from '../../contexts/PlayerContext'
|
||
import { SpeedIcon, SubtitlesIcon, CheckIcon, AudioIcon } from '../../icons'
|
||
import type { AudioTrack } from '../../types'
|
||
import './SettingsMenu.css'
|
||
|
||
interface SettingsMenuProps {
|
||
subtitles?: Array<{ src: string; lang: string; label: string }>
|
||
audioTracks?: AudioTrack[]
|
||
}
|
||
|
||
type MenuView = 'main' | 'speed' | 'subtitles' | 'audio'
|
||
|
||
export const SettingsMenu: React.FC<SettingsMenuProps> = ({ subtitles = [], audioTracks = [] }) => {
|
||
const { uiState, videoState, settings, setPlaybackRate, setSubtitle, setAudioTrack, toggleSettings } = usePlayerContext()
|
||
const menuRef = useRef<HTMLDivElement>(null)
|
||
const [currentView, setCurrentView] = useState<MenuView>('main')
|
||
|
||
const playbackRates = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2]
|
||
|
||
// Close menu when clicking outside
|
||
useEffect(() => {
|
||
if (!uiState.settingsOpen) return
|
||
|
||
const handleClickOutside = (e: MouseEvent) => {
|
||
if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
|
||
toggleSettings()
|
||
setCurrentView('main')
|
||
}
|
||
}
|
||
|
||
document.addEventListener('mousedown', handleClickOutside)
|
||
return () => document.removeEventListener('mousedown', handleClickOutside)
|
||
}, [uiState.settingsOpen, toggleSettings])
|
||
|
||
// Reset to main view when menu closes
|
||
useEffect(() => {
|
||
if (!uiState.settingsOpen) {
|
||
setCurrentView('main')
|
||
}
|
||
}, [uiState.settingsOpen])
|
||
|
||
const goBack = () => {
|
||
setCurrentView('main')
|
||
}
|
||
|
||
if (!uiState.settingsOpen) return null
|
||
|
||
return (
|
||
<div ref={menuRef} className="settings-menu">
|
||
{/* Main Menu */}
|
||
{currentView === 'main' && (
|
||
<>
|
||
<div className="settings-menu-header">
|
||
<h3>Ayarlar</h3>
|
||
</div>
|
||
<div className="settings-main-options">
|
||
<button className="settings-main-option" onClick={() => setCurrentView('speed')}>
|
||
<div className="settings-main-option-icon">
|
||
<SpeedIcon size={20} color="var(--player-text)" />
|
||
</div>
|
||
<div className="settings-main-option-content">
|
||
<span className="settings-main-option-label">Hız</span>
|
||
<span className="settings-main-option-value">
|
||
{videoState.playbackRate === 1 ? 'Normal' : `${videoState.playbackRate}x`}
|
||
</span>
|
||
</div>
|
||
<div className="settings-main-option-arrow">›</div>
|
||
</button>
|
||
|
||
<button className="settings-main-option" onClick={() => setCurrentView('subtitles')}>
|
||
<div className="settings-main-option-icon">
|
||
<SubtitlesIcon size={20} color="var(--player-text)" />
|
||
</div>
|
||
<div className="settings-main-option-content">
|
||
<span className="settings-main-option-label">Altyazı</span>
|
||
<span className="settings-main-option-value">
|
||
{settings.subtitle ? settings.subtitle.label : 'Kapalı'}
|
||
</span>
|
||
</div>
|
||
<div className="settings-main-option-arrow">›</div>
|
||
</button>
|
||
|
||
{audioTracks.length > 0 && (
|
||
<button className="settings-main-option" onClick={() => setCurrentView('audio')}>
|
||
<div className="settings-main-option-icon">
|
||
<AudioIcon size={20} color="var(--player-text)" />
|
||
</div>
|
||
<div className="settings-main-option-content">
|
||
<span className="settings-main-option-label">Ses</span>
|
||
<span className="settings-main-option-value">
|
||
{settings.audioTrack ? settings.audioTrack.name : 'Varsayılan'}
|
||
</span>
|
||
</div>
|
||
<div className="settings-main-option-arrow">›</div>
|
||
</button>
|
||
)}
|
||
</div>
|
||
</>
|
||
)}
|
||
|
||
{/* Speed Submenu */}
|
||
{currentView === 'speed' && (
|
||
<>
|
||
<div className="settings-menu-header">
|
||
<button className="settings-back-button" onClick={goBack}>
|
||
‹
|
||
</button>
|
||
<h3>Oynatma Hızı</h3>
|
||
</div>
|
||
<div className="settings-options">
|
||
{playbackRates.map((rate) => (
|
||
<button
|
||
key={rate}
|
||
className={`settings-option ${videoState.playbackRate === rate ? 'active' : ''}`}
|
||
onClick={() => {
|
||
setPlaybackRate(rate)
|
||
setTimeout(() => goBack(), 150)
|
||
}}
|
||
>
|
||
<span>{rate === 1 ? 'Normal' : `${rate}x`}</span>
|
||
{videoState.playbackRate === rate && <CheckIcon size={16} color="var(--player-primary)" />}
|
||
</button>
|
||
))}
|
||
</div>
|
||
</>
|
||
)}
|
||
|
||
{/* Subtitles Submenu */}
|
||
{currentView === 'subtitles' && (
|
||
<>
|
||
<div className="settings-menu-header">
|
||
<button className="settings-back-button" onClick={goBack}>
|
||
‹
|
||
</button>
|
||
<h3>Altyazı</h3>
|
||
</div>
|
||
<div className="settings-options">
|
||
<button
|
||
className={`settings-option ${!settings.subtitle ? 'active' : ''}`}
|
||
onClick={() => {
|
||
setSubtitle(null)
|
||
setTimeout(() => goBack(), 150)
|
||
}}
|
||
>
|
||
<span>Kapalı</span>
|
||
{!settings.subtitle && <CheckIcon size={16} color="var(--player-primary)" />}
|
||
</button>
|
||
{subtitles.length > 0 ? (
|
||
subtitles.map((subtitle) => (
|
||
<button
|
||
key={subtitle.lang}
|
||
className={`settings-option ${settings.subtitle?.lang === subtitle.lang ? 'active' : ''}`}
|
||
onClick={() => {
|
||
setSubtitle(subtitle)
|
||
setTimeout(() => goBack(), 150)
|
||
}}
|
||
>
|
||
<span>{subtitle.label}</span>
|
||
{settings.subtitle?.lang === subtitle.lang && <CheckIcon size={16} color="var(--player-primary)" />}
|
||
</button>
|
||
))
|
||
) : (
|
||
<div className="settings-empty-state">
|
||
<span>Altyazı mevcut değil</span>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</>
|
||
)}
|
||
|
||
{/* Audio Submenu */}
|
||
{currentView === 'audio' && (
|
||
<>
|
||
<div className="settings-menu-header">
|
||
<button className="settings-back-button" onClick={goBack}>
|
||
‹
|
||
</button>
|
||
<h3>Ses</h3>
|
||
</div>
|
||
<div className="settings-options">
|
||
{audioTracks.map((track) => (
|
||
<button
|
||
key={track.language}
|
||
className={`settings-option ${settings.audioTrack?.language === track.language ? 'active' : ''}`}
|
||
onClick={() => {
|
||
setAudioTrack(track)
|
||
setTimeout(() => goBack(), 150)
|
||
}}
|
||
>
|
||
<span>{track.name}</span>
|
||
{settings.audioTrack?.language === track.language && <CheckIcon size={16} color="var(--player-primary)" />}
|
||
</button>
|
||
))}
|
||
</div>
|
||
</>
|
||
)}
|
||
|
||
</div>
|
||
)
|
||
}
|