@@ -1,326 +0,0 @@
|
||||
/**
|
||||
* 模板编辑器 — 制作/编辑剪辑模板
|
||||
* 四行布局:顶栏(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 { MODE_LABELS } from "@/api/editing-planner"
|
||||
import { MODE_LIST } from "./constants"
|
||||
import MediaPanel from "./components/MediaPanel"
|
||||
import PreviewPlayer from "./components/PreviewPlayer"
|
||||
import TimelinePanel from "./components/TimelinePanel"
|
||||
import TopBar from "./components/TopBar"
|
||||
import ModeBar from "./components/ModeBar"
|
||||
import RightPanel from "./components/RightPanel"
|
||||
import StatusBar from "./components/StatusBar"
|
||||
import EditorDrawers from "./components/EditorDrawers"
|
||||
import SaveModal from "./components/SaveModal"
|
||||
|
||||
import { useUndoRedo } from "./hooks/useUndoRedo"
|
||||
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 } from "./types"
|
||||
import "./EditingPlanner.css"
|
||||
|
||||
const EditingPlanner: React.FC = () => {
|
||||
const [searchParams] = useSearchParams()
|
||||
const urlTemplateId = searchParams.get("templateId") || ""
|
||||
const urlPlanId = searchParams.get("planId") || ""
|
||||
|
||||
/* ── 片段(撤销/重做) ── */
|
||||
const {
|
||||
state: clips,
|
||||
set: setClips,
|
||||
undo,
|
||||
redo,
|
||||
canUndo,
|
||||
canRedo,
|
||||
reset: resetClips,
|
||||
} = useUndoRedo<ClipData[]>([])
|
||||
|
||||
/* ── 全局配置 ── */
|
||||
const {
|
||||
titleConfig,
|
||||
setTitleConfig,
|
||||
subtitleSettings,
|
||||
setSubtitleSettings,
|
||||
bgmSettings,
|
||||
setBgmSettings,
|
||||
watermarkSettings,
|
||||
setWatermarkSettings,
|
||||
introOutroSettings,
|
||||
setIntroOutroSettings,
|
||||
pipSettings,
|
||||
setPipSettings,
|
||||
filterSettings,
|
||||
setFilterSettings,
|
||||
chromaKeySettings,
|
||||
setChromaKeySettings,
|
||||
stickerSettings,
|
||||
setStickerSettings,
|
||||
} = useGlobalSettings()
|
||||
|
||||
/* ── 右侧栏 Tab ── */
|
||||
const [rightTab, setRightTab] = useState<"properties" | "clips">("properties")
|
||||
|
||||
/* ── 配音素材 ── */
|
||||
const {
|
||||
voiceMaterials,
|
||||
loading: voiceMaterialsLoading,
|
||||
refetch: refetchVoiceMaterials,
|
||||
} = useVoiceMaterials()
|
||||
|
||||
/* ── 派生计算 ── */
|
||||
const totalDuration = clips.reduce((sum, c) => sum + c.duration, 0)
|
||||
|
||||
/* ── Hook: 抽屉管理 ── */
|
||||
const drawers = useEditorDrawers()
|
||||
|
||||
/* ── Hook: 播放控制 ── */
|
||||
const playback = usePlaybackControl(totalDuration)
|
||||
|
||||
/* ── Hook: 片段操作 ── */
|
||||
const clipOps = useClipOperations({ clips, setClips })
|
||||
|
||||
/* ── Hook: 模板管理 ── */
|
||||
const tpl = useTemplateManagement({
|
||||
urlTemplateId,
|
||||
urlPlanId,
|
||||
resetClips,
|
||||
setClips,
|
||||
setSelectedClipId: clipOps.setSelectedClipId,
|
||||
setTitleConfig,
|
||||
setSubtitleSettings,
|
||||
setBgmSettings,
|
||||
clips,
|
||||
totalDuration,
|
||||
titleConfig,
|
||||
subtitleSettings,
|
||||
bgmSettings,
|
||||
watermarkSettings,
|
||||
introOutroSettings,
|
||||
pipSettings,
|
||||
filterSettings,
|
||||
chromaKeySettings,
|
||||
stickerSettings,
|
||||
})
|
||||
|
||||
/* ──────────── 渲染 ──────────── */
|
||||
|
||||
return (
|
||||
<div className="ep-v8-root">
|
||||
{/* ═══ 第1行:顶栏 42px ═══ */}
|
||||
<TopBar
|
||||
currentTemplate={tpl.currentTemplate}
|
||||
canUndo={canUndo}
|
||||
canRedo={canRedo}
|
||||
onUndo={undo}
|
||||
onRedo={redo}
|
||||
onOpenSaveModal={tpl.handleOpenSaveModal}
|
||||
/>
|
||||
|
||||
{/* ═══ 第2行:模式栏 56px ═══ */}
|
||||
<ModeBar
|
||||
modeList={MODE_LIST}
|
||||
currentMode={tpl.currentMode}
|
||||
onModeChange={tpl.handleModeChange}
|
||||
/>
|
||||
|
||||
{/* ═══ 第3行:三栏主体 ═══ */}
|
||||
<div className="ep-main-body">
|
||||
{/* 左栏 220px:模板列表 */}
|
||||
<MediaPanel
|
||||
templates={tpl.filteredTemplates}
|
||||
loading={tpl.loadingTemplates}
|
||||
searchQuery={tpl.searchQuery}
|
||||
currentFilter={tpl.currentFilter}
|
||||
filterCategories={FILTER_CATEGORIES}
|
||||
loadedTemplateId={tpl.loadedTemplateId}
|
||||
onLoadTemplate={tpl.handleLoadTemplate}
|
||||
onSearchChange={tpl.setSearchQuery}
|
||||
onFilterChange={tpl.setCurrentFilter}
|
||||
/>
|
||||
|
||||
{/* 中栏 flex-1 */}
|
||||
<div className="ep-center-col">
|
||||
{/* 上半部:视频预览 + 封面预览 */}
|
||||
<PreviewPlayer
|
||||
clips={clips}
|
||||
selectedClipId={clipOps.selectedClipId}
|
||||
isPlaying={playback.isPlaying}
|
||||
titleConfig={titleConfig}
|
||||
subtitleSettings={{
|
||||
enabled: subtitleSettings.enabled,
|
||||
position: subtitleSettings.position,
|
||||
font: subtitleSettings.font,
|
||||
size: subtitleSettings.fontSize,
|
||||
animation: subtitleSettings.animation,
|
||||
}}
|
||||
onClipSelect={clipOps.handleClipSelect}
|
||||
onPlayPause={() => playback.setIsPlaying(!playback.isPlaying)}
|
||||
/>
|
||||
|
||||
{/* 下半部:水平时间线 */}
|
||||
<TimelinePanel
|
||||
clips={clips}
|
||||
selectedClipId={clipOps.selectedClipId}
|
||||
currentMode={tpl.currentMode}
|
||||
onClipSelect={clipOps.handleClipSelect}
|
||||
onClipReorder={clipOps.handleClipReorder}
|
||||
onClipRemove={clipOps.handleClipRemove}
|
||||
onAddClip={clipOps.handleAddClip}
|
||||
onClipTrim={clipOps.handleClipTrim}
|
||||
onClipSplit={clipOps.handleClipSplit}
|
||||
onClipResetTrim={clipOps.handleClipResetTrim}
|
||||
currentTime={playback.currentTime}
|
||||
pixelsPerSecond={playback.pixelsPerSecond}
|
||||
onZoomChange={playback.handleZoomChange}
|
||||
onSeek={playback.handleSeek}
|
||||
totalDuration={totalDuration}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 右栏 260px:设置面板 */}
|
||||
<RightPanel
|
||||
titleConfig={titleConfig}
|
||||
onTitleConfigChange={setTitleConfig}
|
||||
rightTab={rightTab}
|
||||
onTabChange={setRightTab}
|
||||
selectedClip={clipOps.selectedClip}
|
||||
subtitleSettings={subtitleSettings}
|
||||
bgmSettings={bgmSettings}
|
||||
clipsCount={clips.length}
|
||||
totalDuration={totalDuration}
|
||||
currentMode={tpl.currentMode}
|
||||
onSubtitleSettingsChange={(partial) =>
|
||||
setSubtitleSettings((prev) => ({ ...prev, ...partial }))
|
||||
}
|
||||
onBgmSettingsChange={(partial) => setBgmSettings((prev) => ({ ...prev, ...partial }))}
|
||||
onClipUpdate={clipOps.handleClipUpdate}
|
||||
onOpenBgmDrawer={() => drawers.setBgmDrawerOpen(true)}
|
||||
onOpenSubtitleDrawer={() => drawers.setSubtitleDrawerOpen(true)}
|
||||
voiceMaterials={voiceMaterials}
|
||||
voiceMaterialsLoading={voiceMaterialsLoading}
|
||||
onRefreshVoiceMaterials={refetchVoiceMaterials}
|
||||
onClipVoiceSelect={clipOps.handleClipVoiceSelect}
|
||||
onOpenTransitionDrawer={drawers.openTransitionDrawer}
|
||||
onOpenSpeedDrawer={drawers.openSpeedDrawer}
|
||||
onOpenTtsDrawer={drawers.openTtsDrawer}
|
||||
onOpenWatermarkDrawer={() => drawers.setWatermarkDrawerOpen(true)}
|
||||
onOpenIntroOutroDrawer={() => drawers.setIntroOutroDrawerOpen(true)}
|
||||
onOpenPipDrawer={() => drawers.setPipDrawerOpen(true)}
|
||||
onOpenFilterDrawer={() => drawers.setFilterDrawerOpen(true)}
|
||||
onOpenGreenScreenDrawer={() => drawers.setChromaKeyDrawerOpen(true)}
|
||||
onOpenStickerDrawer={() => drawers.setStickerDrawerOpen(true)}
|
||||
clips={clips}
|
||||
selectedClipId={clipOps.selectedClipId}
|
||||
onClipSelect={clipOps.handleClipSelect}
|
||||
onClipMoveUp={(clipId) => {
|
||||
const idx = clips.findIndex((c) => c.id === clipId)
|
||||
if (idx > 0) clipOps.handleClipReorder(idx, idx - 1)
|
||||
}}
|
||||
onClipMoveDown={(clipId) => {
|
||||
const idx = clips.findIndex((c) => c.id === clipId)
|
||||
if (idx < clips.length - 1) clipOps.handleClipReorder(idx, idx + 1)
|
||||
}}
|
||||
onClipRemove={clipOps.handleClipRemove}
|
||||
onClipAdd={() => clipOps.handleAddClip("pip", 3)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* ═══ 第4行:底栏 40px ═══ */}
|
||||
<StatusBar
|
||||
clipsCount={clips.length}
|
||||
totalDuration={totalDuration}
|
||||
currentModeLabel={MODE_LABELS[tpl.currentMode]}
|
||||
templateSegments={tpl.currentTemplate?.segments.length || 0}
|
||||
/>
|
||||
|
||||
{/* ═══ 弹窗 ═══ */}
|
||||
<SaveModal
|
||||
open={tpl.saveModalOpen}
|
||||
loading={tpl.saveLoading}
|
||||
isUpdate={!!tpl.loadedTemplateId}
|
||||
draftName={tpl.draftName}
|
||||
draftCategory={tpl.draftCategory}
|
||||
draftTags={tpl.draftTags}
|
||||
categories={tpl.categories}
|
||||
estimatedDuration={totalDuration}
|
||||
onNameChange={tpl.setDraftName}
|
||||
onCategoryChange={tpl.setDraftCategory}
|
||||
onTagsChange={tpl.setDraftTags}
|
||||
onSave={tpl.handleSave}
|
||||
onCancel={() => tpl.setSaveModalOpen(false)}
|
||||
/>
|
||||
|
||||
{/* ═══ Drawer 集合 ═══ */}
|
||||
<EditorDrawers
|
||||
bgmDrawerOpen={drawers.bgmDrawerOpen}
|
||||
bgmSettings={bgmSettings}
|
||||
onBgmSettingsChange={setBgmSettings}
|
||||
onCloseBgmDrawer={() => drawers.setBgmDrawerOpen(false)}
|
||||
subtitleDrawerOpen={drawers.subtitleDrawerOpen}
|
||||
subtitleSettings={subtitleSettings}
|
||||
onSubtitleSettingsChange={setSubtitleSettings}
|
||||
onCloseSubtitleDrawer={() => drawers.setSubtitleDrawerOpen(false)}
|
||||
transitionDrawerOpen={drawers.transitionDrawerOpen}
|
||||
transitionTargetClipId={drawers.transitionTargetClipId}
|
||||
clips={clips}
|
||||
onTransitionChange={(config) =>
|
||||
clipOps.handleTransitionChange(drawers.transitionTargetClipId, config)
|
||||
}
|
||||
onCloseTransitionDrawer={() => drawers.setTransitionDrawerOpen(false)}
|
||||
speedDrawerOpen={drawers.speedDrawerOpen}
|
||||
speedTargetClipId={drawers.speedTargetClipId}
|
||||
onSpeedChange={(config) => clipOps.handleSpeedChange(drawers.speedTargetClipId, config)}
|
||||
onApplySpeedAll={clipOps.handleApplySpeedAll}
|
||||
onCloseSpeedDrawer={() => drawers.setSpeedDrawerOpen(false)}
|
||||
ttsDrawerOpen={drawers.ttsDrawerOpen}
|
||||
ttsTargetClipId={drawers.ttsTargetClipId}
|
||||
onTtsChange={(config) => clipOps.handleTtsChange(drawers.ttsTargetClipId, config)}
|
||||
onCloseTtsDrawer={() => drawers.setTtsDrawerOpen(false)}
|
||||
watermarkDrawerOpen={drawers.watermarkDrawerOpen}
|
||||
watermarkSettings={watermarkSettings}
|
||||
onWatermarkChange={setWatermarkSettings}
|
||||
onCloseWatermarkDrawer={() => drawers.setWatermarkDrawerOpen(false)}
|
||||
introOutroDrawerOpen={drawers.introOutroDrawerOpen}
|
||||
introOutroSettings={introOutroSettings}
|
||||
onIntroOutroChange={setIntroOutroSettings}
|
||||
onCloseIntroOutroDrawer={() => drawers.setIntroOutroDrawerOpen(false)}
|
||||
pipDrawerOpen={drawers.pipDrawerOpen}
|
||||
pipSettings={pipSettings}
|
||||
totalDuration={totalDuration}
|
||||
onPipChange={setPipSettings}
|
||||
onClosePipDrawer={() => drawers.setPipDrawerOpen(false)}
|
||||
filterDrawerOpen={drawers.filterDrawerOpen}
|
||||
filterSettings={filterSettings}
|
||||
onFilterChange={setFilterSettings}
|
||||
onCloseFilterDrawer={() => drawers.setFilterDrawerOpen(false)}
|
||||
chromaKeyDrawerOpen={drawers.chromaKeyDrawerOpen}
|
||||
chromaKeySettings={chromaKeySettings}
|
||||
onChromaKeyChange={setChromaKeySettings}
|
||||
onCloseChromaKeyDrawer={() => drawers.setChromaKeyDrawerOpen(false)}
|
||||
stickerDrawerOpen={drawers.stickerDrawerOpen}
|
||||
stickerSettings={stickerSettings}
|
||||
onStickerChange={setStickerSettings}
|
||||
onCloseStickerDrawer={() => drawers.setStickerDrawerOpen(false)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default EditingPlanner
|
||||
Reference in New Issue
Block a user