From 5e02184c8fe357e0ea2d3948bf971b846ea082d3 Mon Sep 17 00:00:00 2001 From: Maxim Vershinin Date: Sun, 30 Aug 2026 15:26:02 +0500 Subject: [PATCH] REFACTOR (FRONTEND): exports some functionality from web/frontend/src/components/OptionsPanel.tsx into separated hooks --- web/frontend/src/components/OptionsPanel.tsx | 58 ++----------------- web/frontend/src/hooks/useFilterCodecs.ts | 31 ++++++++++ web/frontend/src/hooks/useFormatValidation.ts | 56 ++++++++++++++++++ 3 files changed, 91 insertions(+), 54 deletions(-) create mode 100644 web/frontend/src/hooks/useFilterCodecs.ts create mode 100644 web/frontend/src/hooks/useFormatValidation.ts diff --git a/web/frontend/src/components/OptionsPanel.tsx b/web/frontend/src/components/OptionsPanel.tsx index eaec8a2..5742bf8 100644 --- a/web/frontend/src/components/OptionsPanel.tsx +++ b/web/frontend/src/components/OptionsPanel.tsx @@ -18,6 +18,8 @@ import { useUploadStore } from '../stores/uploadStore' import { useValidationStore } from '../stores/validationStore' import { getFormats } from '../api/client' import { SplitOptions } from '../types' +import { useFilterCodecs } from '../hooks/useFilterCodecs' +import { useFormatValidation } from '../hooks/useFormatValidation' // ------------------------------------------------------------------------------ // Section component (collapsible) @@ -91,64 +93,12 @@ export const OptionsPanel: React.FC = () => { // -------------------------------------------------------------- // Filter codec options based on selected container // -------------------------------------------------------------- - const filteredAudioCodecs = useMemo(() => { - const entry = compatibility?.[options.container || ''] - if (!entry) return codecs.filter(c => c.supports_transcoding) - const audioList = entry.audio - if (audioList === null) return codecs.filter(c => c.supports_transcoding) - return codecs.filter(c => c.supports_transcoding && audioList.includes(c.name)) - }, [compatibility, options.container, codecs]) - - const filteredVideoCodecs = useMemo(() => { - const entry = compatibility?.[options.container || ''] - if (!entry) return codecs.filter(c => c.supports_video) - const videoList = entry.video - if (videoList === null) return codecs.filter(c => c.supports_video) - return codecs.filter(c => c.supports_video && videoList.includes(c.name)) - }, [compatibility, options.container, codecs]) + const { filteredAudioCodecs, filteredVideoCodecs } = useFilterCodecs(compatibility, options.container, codecs) // -------------------------------------------------------------- // Validate compatibility // -------------------------------------------------------------- - useEffect(() => { - const selectedContainer = containers.find(c => c.name === options.container) - if (selectedContainer?.audio_only && hasVideo && !options.drop_video) { - setFormatError( - `Container '${options.container}' does not support video streams. Please enable "Drop video" or choose a container that supports video.` - ) - return - } - - // Check audio codec compatibility - if (options.audio_codec && options.audio_codec !== 'copy' && options.container) { - const entry = compatibility?.[options.container] - if (entry) { - const audioList = entry.audio - if (audioList !== null && !audioList.includes(options.audio_codec)) { - setFormatError( - `Audio codec '${options.audio_codec}' is not supported by container '${options.container}'.` - ) - return - } - } - } - - // Check video codec compatibility - if (options.video_codec && options.video_codec !== 'copy' && options.container && hasVideo && !options.drop_video) { - const entry = compatibility?.[options.container] - if (entry) { - const videoList = entry.video - if (videoList !== null && !videoList.includes(options.video_codec)) { - setFormatError( - `Video codec '${options.video_codec}' is not supported by container '${options.container}'.` - ) - return - } - } - } - - setFormatError(null) - }, [options.container, options.audio_codec, options.video_codec, options.drop_video, hasVideo, compatibility, containers, setFormatError]) + useFormatValidation(options, hasVideo, compatibility, containers, setFormatError) // -------------------------------------------------------------- // Handlers diff --git a/web/frontend/src/hooks/useFilterCodecs.ts b/web/frontend/src/hooks/useFilterCodecs.ts new file mode 100644 index 0000000..a6fec63 --- /dev/null +++ b/web/frontend/src/hooks/useFilterCodecs.ts @@ -0,0 +1,31 @@ +import { useMemo } from 'react' +import { CodecInfo } from '../types' + +export interface CompatibilityEntry { + audio: string[] | null + video: string[] | null +} + +export function useFilterCodecs( + compatibility: Record, + container: string | null, + codecs: CodecInfo[] +) { + const filteredAudioCodecs = useMemo(() => { + const entry = compatibility?.[container || ''] + if (!entry) return codecs.filter(c => c.supports_transcoding) + const audioList = entry.audio + if (audioList === null) return codecs.filter(c => c.supports_transcoding) + return codecs.filter(c => c.supports_transcoding && audioList.includes(c.name)) + }, [compatibility, container, codecs]) + + const filteredVideoCodecs = useMemo(() => { + const entry = compatibility?.[container || ''] + if (!entry) return codecs.filter(c => c.supports_video) + const videoList = entry.video + if (videoList === null) return codecs.filter(c => c.supports_video) + return codecs.filter(c => c.supports_video && videoList.includes(c.name)) + }, [compatibility, container, codecs]) + + return { filteredAudioCodecs, filteredVideoCodecs } +} diff --git a/web/frontend/src/hooks/useFormatValidation.ts b/web/frontend/src/hooks/useFormatValidation.ts new file mode 100644 index 0000000..17a015c --- /dev/null +++ b/web/frontend/src/hooks/useFormatValidation.ts @@ -0,0 +1,56 @@ +import { useEffect } from 'react' +import { ContainerInfo } from '../types' +import { CompatibilityEntry } from './useFilterCodecs' + +export function useFormatValidation( + options: { + container: string | null + audio_codec: string + video_codec: string + drop_video: boolean + }, + hasVideo: boolean, + compatibility: Record, + containers: ContainerInfo[], + setFormatError: (error: string | null) => void +) { + useEffect(() => { + const selectedContainer = containers.find(c => c.name === options.container) + if (selectedContainer?.audio_only && hasVideo && !options.drop_video) { + setFormatError( + `Container '${options.container}' does not support video streams. Please enable "Drop video" or choose a container that supports video.` + ) + return + } + + // Check audio codec compatibility + if (options.audio_codec && options.audio_codec !== 'copy' && options.container) { + const entry = compatibility?.[options.container] + if (entry) { + const audioList = entry.audio + if (audioList !== null && !audioList.includes(options.audio_codec)) { + setFormatError( + `Audio codec '${options.audio_codec}' is not supported by container '${options.container}'.` + ) + return + } + } + } + + // Check video codec compatibility + if (options.video_codec && options.video_codec !== 'copy' && options.container && hasVideo && !options.drop_video) { + const entry = compatibility?.[options.container] + if (entry) { + const videoList = entry.video + if (videoList !== null && !videoList.includes(options.video_codec)) { + setFormatError( + `Video codec '${options.video_codec}' is not supported by container '${options.container}'.` + ) + return + } + } + } + + setFormatError(null) + }, [options.container, options.audio_codec, options.video_codec, options.drop_video, hasVideo, compatibility, containers, setFormatError]) +}