refactor(editing-planner): 深化拆分,抽离全局配置与配音素材 Hook (#990)
This commit is contained in:
@@ -1,15 +1,21 @@
|
||||
/**
|
||||
* 模板编辑器 — 制作/编辑剪辑模板
|
||||
* 四行布局:顶栏(42px) → 模式栏(48px) → 三栏主体 → 底栏(40px)
|
||||
*
|
||||
* 主组件仅保留 Hook 组装与整体布局
|
||||
* 全局配置 → hooks/useGlobalSettings
|
||||
* 配音素材 → hooks/useVoiceMaterials
|
||||
* 撤销重做 → hooks/useUndoRedo
|
||||
* 抽屉管理 → hooks/useEditorDrawers
|
||||
* 播放控制 → hooks/usePlaybackControl
|
||||
* 片段操作 → hooks/useClipOperations
|
||||
* 模板管理 → hooks/useTemplateManagement
|
||||
*/
|
||||
import React, { useState } from "react"
|
||||
import { useSearchParams } from "react-router-dom"
|
||||
import { useQuery } from "@tanstack/react-query"
|
||||
import { MODE_LABELS } from "@/api/editing-planner"
|
||||
import { MODE_LIST } from "./constants"
|
||||
import type { MediaAsset, TitleConfig } from "@/api/template-editor"
|
||||
import { ensureDefaultLibrary, getAssetsByKind, type AssetItem } from "@/api/assets"
|
||||
import { getOrCreateDefaultProject } from "@/api/projects"
|
||||
import type { MediaAsset } from "@/api/template-editor"
|
||||
|
||||
import MediaPanel from "./components/MediaPanel"
|
||||
import PreviewPlayer from "./components/PreviewPlayer"
|
||||
@@ -26,36 +32,12 @@ import { useEditorDrawers } from "./hooks/useEditorDrawers"
|
||||
import { usePlaybackControl } from "./hooks/usePlaybackControl"
|
||||
import { useClipOperations } from "./hooks/useClipOperations"
|
||||
import { useTemplateManagement, FILTER_CATEGORIES } from "./hooks/useTemplateManagement"
|
||||
import { useGlobalSettings } from "./hooks/useGlobalSettings"
|
||||
import { useVoiceMaterials } from "./hooks/useVoiceMaterials"
|
||||
|
||||
import type {
|
||||
ClipData,
|
||||
WatermarkConfig,
|
||||
IntroOutroConfig,
|
||||
PipConfig,
|
||||
FilterConfig,
|
||||
ChromaKeyConfig,
|
||||
StickerConfig,
|
||||
CoverConfig,
|
||||
} from "./types"
|
||||
import {
|
||||
DEFAULT_WATERMARK,
|
||||
DEFAULT_INTRO_OUTRO,
|
||||
DEFAULT_PIP_CONFIG,
|
||||
DEFAULT_FILTER_CONFIG,
|
||||
DEFAULT_CHROMA_KEY_CONFIG,
|
||||
DEFAULT_STICKER_CONFIG,
|
||||
DEFAULT_COVER_CONFIG,
|
||||
} from "./types"
|
||||
|
||||
import type { SubtitleStyleConfig } from "./types/subtitle"
|
||||
import { DEFAULT_SUBTITLE_STYLE } from "./types/subtitle"
|
||||
import { DEFAULT_BGM_MIX_CONFIG, type BgmMixConfig } from "@/api/bgm"
|
||||
import type { ClipData } from "./types"
|
||||
import "./EditingPlanner.css"
|
||||
|
||||
/* ──────────── 常量 ──────────── */
|
||||
|
||||
/* ──────────── 组件 ──────────── */
|
||||
|
||||
const EditingPlanner: React.FC = () => {
|
||||
const [searchParams] = useSearchParams()
|
||||
const urlTemplateId = searchParams.get("templateId") || ""
|
||||
@@ -72,50 +54,29 @@ const EditingPlanner: React.FC = () => {
|
||||
reset: resetClips,
|
||||
} = useUndoRedo<ClipData[]>([])
|
||||
|
||||
/* ── 全局配置 state ── */
|
||||
const [titleConfig, setTitleConfig] = useState<TitleConfig>({
|
||||
ai_auto_select: false,
|
||||
content: "",
|
||||
position: "bottom",
|
||||
font_preset: "思源黑体",
|
||||
font_size: 28,
|
||||
font_color: "#ffffff",
|
||||
})
|
||||
|
||||
const [subtitleSettings, setSubtitleSettings] = useState<SubtitleStyleConfig>({
|
||||
...DEFAULT_SUBTITLE_STYLE,
|
||||
})
|
||||
|
||||
const [bgmSettings, setBgmSettings] = useState<BgmMixConfig>({
|
||||
...DEFAULT_BGM_MIX_CONFIG,
|
||||
})
|
||||
|
||||
const [watermarkSettings, setWatermarkSettings] = useState<WatermarkConfig>({
|
||||
...DEFAULT_WATERMARK,
|
||||
})
|
||||
const [introOutroSettings, setIntroOutroSettings] = useState<IntroOutroConfig>({
|
||||
...DEFAULT_INTRO_OUTRO,
|
||||
})
|
||||
|
||||
const [pipSettings, setPipSettings] = useState<PipConfig>({
|
||||
...DEFAULT_PIP_CONFIG,
|
||||
})
|
||||
|
||||
const [filterSettings, setFilterSettings] = useState<FilterConfig>({
|
||||
...DEFAULT_FILTER_CONFIG,
|
||||
})
|
||||
|
||||
const [chromaKeySettings, setChromaKeySettings] = useState<ChromaKeyConfig>({
|
||||
...DEFAULT_CHROMA_KEY_CONFIG,
|
||||
})
|
||||
|
||||
const [stickerSettings, setStickerSettings] = useState<StickerConfig>({
|
||||
...DEFAULT_STICKER_CONFIG,
|
||||
})
|
||||
|
||||
const [coverConfig, setCoverConfig] = useState<CoverConfig>({
|
||||
...DEFAULT_COVER_CONFIG,
|
||||
})
|
||||
/* ── 全局配置 ── */
|
||||
const {
|
||||
titleConfig,
|
||||
setTitleConfig,
|
||||
subtitleSettings,
|
||||
setSubtitleSettings,
|
||||
bgmSettings,
|
||||
setBgmSettings,
|
||||
watermarkSettings,
|
||||
setWatermarkSettings,
|
||||
introOutroSettings,
|
||||
setIntroOutroSettings,
|
||||
pipSettings,
|
||||
setPipSettings,
|
||||
filterSettings,
|
||||
setFilterSettings,
|
||||
chromaKeySettings,
|
||||
setChromaKeySettings,
|
||||
stickerSettings,
|
||||
setStickerSettings,
|
||||
coverConfig,
|
||||
setCoverConfig,
|
||||
} = useGlobalSettings()
|
||||
|
||||
/* ── 右侧栏 Tab ── */
|
||||
const [rightTab, setRightTab] = useState<"properties" | "clips">("properties")
|
||||
@@ -128,18 +89,9 @@ const EditingPlanner: React.FC = () => {
|
||||
setSelectedAssetIds(ids)
|
||||
}
|
||||
|
||||
/* ── 配音素材(queryKey 与 VoiceMaterialLibrary 共享缓存) ── */
|
||||
const voiceMaterialsQuery = useQuery({
|
||||
queryKey: ["assets", "voice"],
|
||||
queryFn: async () => {
|
||||
const project = await getOrCreateDefaultProject()
|
||||
await ensureDefaultLibrary({ project_id: project.id, kind: "voice" })
|
||||
const assets = await getAssetsByKind("voice")
|
||||
return assets
|
||||
},
|
||||
staleTime: 30_000,
|
||||
})
|
||||
const voiceMaterials: AssetItem[] = voiceMaterialsQuery.data ?? []
|
||||
/* ── 配音素材 ── */
|
||||
const { voiceMaterials, loading: voiceMaterialsLoading, refetch: refetchVoiceMaterials } =
|
||||
useVoiceMaterials()
|
||||
|
||||
/* ── 派生计算 ── */
|
||||
const totalDuration = clips.reduce((sum, c) => sum + c.duration, 0)
|
||||
@@ -179,31 +131,6 @@ const EditingPlanner: React.FC = () => {
|
||||
coverConfig,
|
||||
})
|
||||
|
||||
/* ── 配置变更 handlers ── */
|
||||
const handleWatermarkChange = (config: WatermarkConfig) => {
|
||||
setWatermarkSettings(config)
|
||||
}
|
||||
|
||||
const handleIntroOutroChange = (config: IntroOutroConfig) => {
|
||||
setIntroOutroSettings(config)
|
||||
}
|
||||
|
||||
const handlePipChange = (config: PipConfig) => {
|
||||
setPipSettings(config)
|
||||
}
|
||||
|
||||
const handleFilterChange = (config: FilterConfig) => {
|
||||
setFilterSettings(config)
|
||||
}
|
||||
|
||||
const handleChromaKeyChange = (config: ChromaKeyConfig) => {
|
||||
setChromaKeySettings(config)
|
||||
}
|
||||
|
||||
const handleStickerChange = (config: StickerConfig) => {
|
||||
setStickerSettings(config)
|
||||
}
|
||||
|
||||
/* ──────────── 渲染 ──────────── */
|
||||
|
||||
return (
|
||||
@@ -294,15 +221,15 @@ const EditingPlanner: React.FC = () => {
|
||||
totalDuration={totalDuration}
|
||||
currentMode={tpl.currentMode}
|
||||
onSubtitleSettingsChange={(partial) =>
|
||||
setSubtitleSettings((prev) => ({ ...prev, ...partial }) as SubtitleStyleConfig)
|
||||
setSubtitleSettings((prev) => ({ ...prev, ...partial }))
|
||||
}
|
||||
onBgmSettingsChange={(partial) => setBgmSettings((prev) => ({ ...prev, ...partial }))}
|
||||
onClipUpdate={clipOps.handleClipUpdate}
|
||||
onOpenBgmDrawer={() => drawers.setBgmDrawerOpen(true)}
|
||||
onOpenSubtitleDrawer={() => drawers.setSubtitleDrawerOpen(true)}
|
||||
voiceMaterials={voiceMaterials}
|
||||
voiceMaterialsLoading={voiceMaterialsQuery.isLoading}
|
||||
onRefreshVoiceMaterials={() => voiceMaterialsQuery.refetch()}
|
||||
voiceMaterialsLoading={voiceMaterialsLoading}
|
||||
onRefreshVoiceMaterials={refetchVoiceMaterials}
|
||||
onClipVoiceSelect={clipOps.handleClipVoiceSelect}
|
||||
onOpenTransitionDrawer={drawers.openTransitionDrawer}
|
||||
onOpenSpeedDrawer={drawers.openSpeedDrawer}
|
||||
@@ -382,28 +309,28 @@ const EditingPlanner: React.FC = () => {
|
||||
onCloseTtsDrawer={() => drawers.setTtsDrawerOpen(false)}
|
||||
watermarkDrawerOpen={drawers.watermarkDrawerOpen}
|
||||
watermarkSettings={watermarkSettings}
|
||||
onWatermarkChange={handleWatermarkChange}
|
||||
onWatermarkChange={setWatermarkSettings}
|
||||
onCloseWatermarkDrawer={() => drawers.setWatermarkDrawerOpen(false)}
|
||||
introOutroDrawerOpen={drawers.introOutroDrawerOpen}
|
||||
introOutroSettings={introOutroSettings}
|
||||
onIntroOutroChange={handleIntroOutroChange}
|
||||
onIntroOutroChange={setIntroOutroSettings}
|
||||
onCloseIntroOutroDrawer={() => drawers.setIntroOutroDrawerOpen(false)}
|
||||
pipDrawerOpen={drawers.pipDrawerOpen}
|
||||
pipSettings={pipSettings}
|
||||
totalDuration={totalDuration}
|
||||
onPipChange={handlePipChange}
|
||||
onPipChange={setPipSettings}
|
||||
onClosePipDrawer={() => drawers.setPipDrawerOpen(false)}
|
||||
filterDrawerOpen={drawers.filterDrawerOpen}
|
||||
filterSettings={filterSettings}
|
||||
onFilterChange={handleFilterChange}
|
||||
onFilterChange={setFilterSettings}
|
||||
onCloseFilterDrawer={() => drawers.setFilterDrawerOpen(false)}
|
||||
chromaKeyDrawerOpen={drawers.chromaKeyDrawerOpen}
|
||||
chromaKeySettings={chromaKeySettings}
|
||||
onChromaKeyChange={handleChromaKeyChange}
|
||||
onChromaKeyChange={setChromaKeySettings}
|
||||
onCloseChromaKeyDrawer={() => drawers.setChromaKeyDrawerOpen(false)}
|
||||
stickerDrawerOpen={drawers.stickerDrawerOpen}
|
||||
stickerSettings={stickerSettings}
|
||||
onStickerChange={handleStickerChange}
|
||||
onStickerChange={setStickerSettings}
|
||||
onCloseStickerDrawer={() => drawers.setStickerDrawerOpen(false)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* EditingPlanner 全局配置状态管理
|
||||
* 集中管理 9 个全局配置:标题/字幕/BGM/水印/片头片尾/画中画/滤镜/绿幕/贴纸/封面
|
||||
*/
|
||||
import { useState } from "react"
|
||||
import type { TitleConfig } from "@/api/template-editor"
|
||||
import type {
|
||||
WatermarkConfig,
|
||||
IntroOutroConfig,
|
||||
PipConfig,
|
||||
FilterConfig,
|
||||
ChromaKeyConfig,
|
||||
StickerConfig,
|
||||
CoverConfig,
|
||||
} from "../types"
|
||||
import {
|
||||
DEFAULT_WATERMARK,
|
||||
DEFAULT_INTRO_OUTRO,
|
||||
DEFAULT_PIP_CONFIG,
|
||||
DEFAULT_FILTER_CONFIG,
|
||||
DEFAULT_CHROMA_KEY_CONFIG,
|
||||
DEFAULT_STICKER_CONFIG,
|
||||
DEFAULT_COVER_CONFIG,
|
||||
} from "../types"
|
||||
import type { SubtitleStyleConfig } from "../types/subtitle"
|
||||
import { DEFAULT_SUBTITLE_STYLE } from "../types/subtitle"
|
||||
import { DEFAULT_BGM_MIX_CONFIG, type BgmMixConfig } from "@/api/bgm"
|
||||
|
||||
export interface GlobalSettings {
|
||||
titleConfig: TitleConfig
|
||||
setTitleConfig: (config: TitleConfig | ((prev: TitleConfig) => TitleConfig)) => void
|
||||
subtitleSettings: SubtitleStyleConfig
|
||||
setSubtitleSettings: (
|
||||
settings: SubtitleStyleConfig | ((prev: SubtitleStyleConfig) => SubtitleStyleConfig),
|
||||
) => void
|
||||
bgmSettings: BgmMixConfig
|
||||
setBgmSettings: (settings: BgmMixConfig | ((prev: BgmMixConfig) => BgmMixConfig)) => void
|
||||
watermarkSettings: WatermarkConfig
|
||||
setWatermarkSettings: (config: WatermarkConfig) => void
|
||||
introOutroSettings: IntroOutroConfig
|
||||
setIntroOutroSettings: (config: IntroOutroConfig) => void
|
||||
pipSettings: PipConfig
|
||||
setPipSettings: (config: PipConfig) => void
|
||||
filterSettings: FilterConfig
|
||||
setFilterSettings: (config: FilterConfig) => void
|
||||
chromaKeySettings: ChromaKeyConfig
|
||||
setChromaKeySettings: (config: ChromaKeyConfig) => void
|
||||
stickerSettings: StickerConfig
|
||||
setStickerSettings: (config: StickerConfig) => void
|
||||
coverConfig: CoverConfig
|
||||
setCoverConfig: (config: CoverConfig | ((prev: CoverConfig) => CoverConfig)) => void
|
||||
}
|
||||
|
||||
export const useGlobalSettings = (): GlobalSettings => {
|
||||
const [titleConfig, setTitleConfig] = useState<TitleConfig>({
|
||||
ai_auto_select: false,
|
||||
content: "",
|
||||
position: "bottom",
|
||||
font_preset: "思源黑体",
|
||||
font_size: 28,
|
||||
font_color: "#ffffff",
|
||||
})
|
||||
|
||||
const [subtitleSettings, setSubtitleSettings] = useState<SubtitleStyleConfig>({
|
||||
...DEFAULT_SUBTITLE_STYLE,
|
||||
})
|
||||
|
||||
const [bgmSettings, setBgmSettings] = useState<BgmMixConfig>({
|
||||
...DEFAULT_BGM_MIX_CONFIG,
|
||||
})
|
||||
|
||||
const [watermarkSettings, setWatermarkSettings] = useState<WatermarkConfig>({
|
||||
...DEFAULT_WATERMARK,
|
||||
})
|
||||
const [introOutroSettings, setIntroOutroSettings] = useState<IntroOutroConfig>({
|
||||
...DEFAULT_INTRO_OUTRO,
|
||||
})
|
||||
|
||||
const [pipSettings, setPipSettings] = useState<PipConfig>({
|
||||
...DEFAULT_PIP_CONFIG,
|
||||
})
|
||||
|
||||
const [filterSettings, setFilterSettings] = useState<FilterConfig>({
|
||||
...DEFAULT_FILTER_CONFIG,
|
||||
})
|
||||
|
||||
const [chromaKeySettings, setChromaKeySettings] = useState<ChromaKeyConfig>({
|
||||
...DEFAULT_CHROMA_KEY_CONFIG,
|
||||
})
|
||||
|
||||
const [stickerSettings, setStickerSettings] = useState<StickerConfig>({
|
||||
...DEFAULT_STICKER_CONFIG,
|
||||
})
|
||||
|
||||
const [coverConfig, setCoverConfig] = useState<CoverConfig>({
|
||||
...DEFAULT_COVER_CONFIG,
|
||||
})
|
||||
|
||||
return {
|
||||
titleConfig,
|
||||
setTitleConfig,
|
||||
subtitleSettings,
|
||||
setSubtitleSettings,
|
||||
bgmSettings,
|
||||
setBgmSettings,
|
||||
watermarkSettings,
|
||||
setWatermarkSettings,
|
||||
introOutroSettings,
|
||||
setIntroOutroSettings,
|
||||
pipSettings,
|
||||
setPipSettings,
|
||||
filterSettings,
|
||||
setFilterSettings,
|
||||
chromaKeySettings,
|
||||
setChromaKeySettings,
|
||||
stickerSettings,
|
||||
setStickerSettings,
|
||||
coverConfig,
|
||||
setCoverConfig,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* EditingPlanner 配音素材数据加载
|
||||
* queryKey 与 VoiceMaterialLibrary 共享缓存
|
||||
*/
|
||||
import { useQuery } from "@tanstack/react-query"
|
||||
import { ensureDefaultLibrary, getAssetsByKind, type AssetItem } from "@/api/assets"
|
||||
import { getOrCreateDefaultProject } from "@/api/projects"
|
||||
|
||||
export interface UseVoiceMaterialsReturn {
|
||||
voiceMaterials: AssetItem[]
|
||||
loading: boolean
|
||||
refetch: () => Promise<unknown>
|
||||
}
|
||||
|
||||
export const useVoiceMaterials = (): UseVoiceMaterialsReturn => {
|
||||
const query = useQuery({
|
||||
queryKey: ["assets", "voice"],
|
||||
queryFn: async () => {
|
||||
const project = await getOrCreateDefaultProject()
|
||||
await ensureDefaultLibrary({ project_id: project.id, kind: "voice" })
|
||||
const assets = await getAssetsByKind("voice")
|
||||
return assets
|
||||
},
|
||||
staleTime: 30_000,
|
||||
})
|
||||
|
||||
return {
|
||||
voiceMaterials: query.data ?? [],
|
||||
loading: query.isLoading,
|
||||
refetch: query.refetch,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user