/** * Effect Manager Panel - главная панель управления эффектами * Интегрирует новую унифицированную систему эффектов */ import { Filter, Plus, Search, Settings2, Upload } from "lucide-react" import { useCallback, useMemo, useState } from "react" import { useTranslation } from "react-i18next" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { ScrollArea } from "@/components/ui/scroll-area" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" import { useEffectCategories, useEffects } from "../hooks/use-effects" import { useEffectsImport } from "../hooks/use-effects-import" import { BaseEffect } from "../types" import { EffectDetail } from "./effect-detail" import { EffectGroup } from "./effect-group" // Категории эффектов с локализацией const EFFECT_CATEGORIES = { all: { en: "All Effects", ru: "Все эффекты" }, color_correction: { en: "Color Correction", ru: "Цветокоррекция" }, color_grading: { en: "Color Grading", ru: "Цветовой грейдинг" }, luts: { en: "LUTs", ru: "LUT таблицы" }, blur_sharpen: { en: "Blur & Sharpen", ru: "Размытие и резкость" }, stylize: { en: "Stylize", ru: "Стилизация" }, distort: { en: "Distort", ru: "Искажение" }, lighting: { en: "Lighting", ru: "Освещение" }, noise_grain: { en: "Noise & Grain", ru: "Шум и зерно" }, transform: { en: "Transform", ru: "Трансформация" }, keying: { en: "Keying", ru: "Кеинг" }, motion: { en: "Motion", ru: "Движение" }, temporal: { en: "Temporal", ru: "Временные" }, } interface EffectManagerPanelProps { onApplyEffect: (effect: BaseEffect, preset?: string, customParams?: Record) => void selectedCategory?: string previewSize?: number } export function EffectManagerPanel({ onApplyEffect, selectedCategory = "all", previewSize = 120, }: EffectManagerPanelProps) { const { t, i18n } = useTranslation() const { effects, loading, error } = useEffects() const categories = useEffectCategories() const { importEffectsFile, importEffectFile, isImporting } = useEffectsImport() const [searchQuery, setSearchQuery] = useState("") const [filterScope, setFilterScope] = useState<"all" | "clip" | "track" | "global">("all") const [activeCategory, setActiveCategory] = useState(selectedCategory) const [selectedEffect, setSelectedEffect] = useState(null) // Фильтрация эффектов const filteredEffects = useMemo(() => { let result = effects // Фильтр по категории if (activeCategory !== "all") { result = result.filter((effect) => effect.category === activeCategory) } // Фильтр по области применения if (filterScope !== "all") { result = result.filter((effect) => effect.scope.includes(filterScope as any)) } // Поиск if (searchQuery) { const query = searchQuery.toLowerCase() result = result.filter((effect) => { const name = effect.name[i18n.language] || effect.name.en const description = effect.description?.[i18n.language] || effect.description?.en || "" const tags = effect.tags.join(" ") return ( name.toLowerCase().includes(query) || description.toLowerCase().includes(query) || tags.toLowerCase().includes(query) ) }) } return result }, [effects, activeCategory, filterScope, searchQuery, i18n.language]) // Группировка эффектов по категориям const groupedEffects = useMemo(() => { const groups: Record = {} filteredEffects.forEach((effect) => { if (!groups[effect.category]) { groups[effect.category] = [] } groups[effect.category].push(effect) }) return groups }, [filteredEffects]) // Обработчики const handleEffectClick = useCallback((effect: BaseEffect) => { setSelectedEffect(effect) }, []) const handleImportClick = useCallback(async () => { const result = await importEffectsFile() if (result.success) { // TODO: Показать уведомление об успешном импорте console.log(result.message) } }, [importEffectsFile]) const handleImportLUTClick = useCallback(async () => { const result = await importEffectFile() if (result.success) { // TODO: Показать уведомление об успешном импорте console.log(result.message) } }, [importEffectFile]) if (loading) { return (
{t("effects.loading", "Загрузка эффектов...")}
) } if (error) { return (
{t("effects.error", "Ошибка загрузки эффектов")}: {error}
) } return (
{/* Панель инструментов */}
setSearchQuery(e.target.value)} className="pl-10" />
{/* Категории */} {EFFECT_CATEGORIES.all[i18n.language as "en" | "ru"]} {effects.length} {Object.entries(categories).map(([key, categoryEffects]) => { const categoryInfo = EFFECT_CATEGORIES[key as keyof typeof EFFECT_CATEGORIES] if (!categoryInfo || categoryEffects.length === 0) return null return ( {categoryInfo[i18n.language as "en" | "ru"]} {categoryEffects.length} ) })} {/* Контент */}
{activeCategory === "all" ? ( // Показываем все категории Object.entries(groupedEffects).map(([category, categoryEffects]) => { const categoryInfo = EFFECT_CATEGORIES[category as keyof typeof EFFECT_CATEGORIES] if (!categoryInfo || categoryEffects.length === 0) return null return ( ) }) ) : ( // Показываем эффекты выбранной категории
{filteredEffects.map((effect, _index) => (
handleEffectClick(effect)} >
{/* TODO: Добавить превью эффекта */}

{effect.name[i18n.language] || effect.name.en}

{effect.author &&

{effect.author}

}
{/* Кнопка применения при наведении */}
))}
)}
{/* Детали выбранного эффекта */} {selectedEffect && }
) }