refactor(editing-planner): 深化拆分,抽离全局配置与配音素材 Hook (#990)

This commit is contained in:
2026-07-27 07:21:16 +08:00
parent 8d7e13ea73
commit dcab4180e5
3 changed files with 201 additions and 121 deletions
@@ -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,
}
}