fix: make material intake a rail drawer

This commit is contained in:
2026-05-21 11:41:36 +08:00
parent 8421af2af8
commit d83e56169d
3 changed files with 97 additions and 87 deletions

File diff suppressed because one or more lines are too long

View File

@@ -573,17 +573,30 @@ nextjs-portal {
}
.skg-board-rail {
width: 397px;
width: 65px;
min-height: 514px;
overflow: hidden;
transition: width 220ms ease;
}
.skg-board-rail.is-open {
width: 397px;
}
.skg-board-rail__strip {
width: 397px;
width: 65px;
min-height: 514px;
border: 1px solid #383838;
border-radius: 0 34px 34px 0;
border-radius: 0 70px 70px 0;
background: #383838;
box-shadow: 10px 10px 10px rgba(0, 0, 0, 0.3);
overflow: hidden;
transition: width 220ms ease, border-radius 220ms ease;
}
.skg-board-rail.is-open .skg-board-rail__strip {
width: 397px;
border-radius: 0 34px 34px 0;
}
.skg-board-rail__iconbar {
@@ -612,6 +625,21 @@ nextjs-portal {
transform: translateX(2px);
}
.skg-board-rail__drawer {
animation: skgRailDrawerIn 220ms ease both;
}
@keyframes skgRailDrawerIn {
from {
opacity: 0;
transform: translateX(-12px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.skg-glass-card {
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 20px;

View File

@@ -2224,6 +2224,7 @@ export function AdRecreationBoard({
const [runtimeModels, setRuntimeModels] = useState<RuntimeModels | undefined>()
const [boardTheme, setBoardTheme] = useState<BoardThemeMode>("dark")
const [libraryOpen, setLibraryOpen] = useState(false)
const [materialRailOpen, setMaterialRailOpen] = useState(false)
const fileRef = useRef<HTMLInputElement | null>(null)
const selectedFrames = job
? job.frames.filter((frame) => data.selectedFrames.has(frame.index)).sort((a, b) => a.timestamp - b.timestamp)
@@ -2479,12 +2480,12 @@ export function AdRecreationBoard({
<div className="relative z-10 mx-auto min-h-screen w-full min-w-[1280px] max-w-[1920px] px-4 py-4 xl:px-5">
<div className="skg-board-shell flex gap-4 p-4">
<WorkbenchRail
job={job}
jobsCount={jobs.length}
audioReady={audioReady}
visualReady={visualReady}
subjectAssetCount={subjectAssetCount}
generatedVideoCount={generatedVideos.length}
materialOpen={materialRailOpen}
onToggleMaterial={() => setMaterialRailOpen((open) => !open)}
onOpenLibrary={() => setLibraryOpen(true)}
onToggleTheme={toggleBoardTheme}
boardTheme={boardTheme}
materialPanel={
<MaterialColumn
data={data}
@@ -2497,9 +2498,6 @@ export function AdRecreationBoard({
fileRef={fileRef}
onSubmitUrl={submitUrl}
onStartProduction={startProduction}
onOpenLibrary={() => setLibraryOpen(true)}
onToggleTheme={toggleBoardTheme}
boardTheme={boardTheme}
/>
}
/>
@@ -2585,55 +2583,69 @@ export function AdRecreationBoard({
}
function WorkbenchRail({
job,
jobsCount,
audioReady,
visualReady,
subjectAssetCount,
generatedVideoCount,
materialOpen,
onToggleMaterial,
onOpenLibrary,
onToggleTheme,
boardTheme,
materialPanel,
}: {
job: Job | null
jobsCount: number
audioReady: boolean
visualReady: boolean
subjectAssetCount: number
generatedVideoCount: number
materialOpen: boolean
onToggleMaterial: () => void
onOpenLibrary: () => void
onToggleTheme: () => void
boardTheme: BoardThemeMode
materialPanel: ReactNode
}) {
const railItems = [
{ key: "jobs", label: "素材任务", icon: <Link2 className="h-[18px] w-[18px]" />, active: jobsCount > 0 },
{ key: "source", label: "源视频", icon: <Play className="h-[18px] w-[18px]" />, active: !!job?.video_url },
{ key: "audio", label: "音频文案", icon: <Mic className="h-[18px] w-[18px]" />, active: audioReady },
{ key: "frames", label: "参考帧", icon: <ImageIcon className="h-[18px] w-[18px]" />, active: visualReady },
{ key: "subject", label: "主体套图", icon: <Sparkles className="h-[18px] w-[18px]" />, active: subjectAssetCount > 0 },
{ key: "video", label: "视频候选", icon: <Film className="h-[18px] w-[18px]" />, active: generatedVideoCount > 0 },
]
return (
<aside className="skg-board-rail sticky top-4 flex shrink-0 items-stretch" aria-label="工作台导航与素材输入">
<aside className={`skg-board-rail sticky top-4 flex shrink-0 items-stretch ${materialOpen ? "is-open" : ""}`} aria-label="工作台工具栏">
<div className="skg-board-rail__strip flex shrink-0 items-stretch gap-3" aria-label="工作台导航与素材输入">
<div className="skg-board-rail__iconbar flex shrink-0 flex-col items-center py-5" aria-label="工作台导航">
<div className="skg-board-rail__logo mb-8 flex h-9 w-9 items-center justify-center rounded-full text-[12px] font-semibold" title="SKG Marketing Studio">
S
</div>
<nav className="flex flex-1 flex-col items-center gap-4">
{railItems.map((item) => (
<button
key={item.key}
type="button"
className={`skg-board-rail__button inline-flex h-9 w-9 items-center justify-center rounded-full ${item.active ? "is-active" : ""}`}
title={item.label}
aria-label={item.label}
>
{item.icon}
</button>
))}
<button
type="button"
onClick={onToggleMaterial}
className={`skg-board-rail__button inline-flex h-9 w-9 items-center justify-center rounded-full ${materialOpen ? "is-active" : ""}`}
title={jobsCount ? `素材任务 · ${jobsCount}` : "素材任务"}
aria-label="素材任务"
aria-expanded={materialOpen}
aria-controls="skg-material-rail-panel"
>
<Link2 className="h-[18px] w-[18px]" />
</button>
</nav>
<div className="mt-8 flex flex-col items-center gap-3">
<button
type="button"
onClick={onOpenLibrary}
className="skg-board-rail__button inline-flex h-9 w-9 items-center justify-center rounded-full"
title="资源库"
aria-label="打开资源库"
>
<BookOpen className="h-[18px] w-[18px]" />
</button>
<button
type="button"
onClick={onToggleTheme}
className="skg-board-rail__button inline-flex h-9 w-9 items-center justify-center rounded-full"
title={boardTheme === "dark" ? "切换到明亮模式" : "切换到暗色模式"}
aria-label={boardTheme === "dark" ? "切换到明亮模式" : "切换到暗色模式"}
>
{boardTheme === "dark" ? <Sun className="h-[18px] w-[18px]" /> : <Moon className="h-[18px] w-[18px]" />}
</button>
</div>
</div>
<div className="w-[320px] shrink-0">
{materialPanel}
</div>
{materialOpen ? (
<div id="skg-material-rail-panel" className="skg-board-rail__drawer w-[320px] shrink-0">
{materialPanel}
</div>
) : null}
</div>
</aside>
)
@@ -2650,9 +2662,6 @@ function MaterialColumn({
fileRef,
onSubmitUrl,
onStartProduction,
onOpenLibrary,
onToggleTheme,
boardTheme,
}: {
data: NodeData
step: WorkflowStep
@@ -2664,9 +2673,6 @@ function MaterialColumn({
fileRef: RefObject<HTMLInputElement | null>
onSubmitUrl: () => void
onStartProduction: () => void
onOpenLibrary: () => void
onToggleTheme: () => void
boardTheme: BoardThemeMode
}) {
const actionLabel = !url.trim() && job?.status === "failed"
? job.video_url ? "重新解析" : "重新下载"
@@ -2674,36 +2680,12 @@ function MaterialColumn({
return (
<section className="skg-board-panel flex h-full min-h-[514px] flex-col gap-3 rounded-[20px] border border-white/10 bg-white/[0.035] p-3 shadow-2xl">
<header className="shrink-0 border-b border-white/10 pb-3">
<div className="flex items-start justify-between gap-3">
<div className="min-w-0">
<div className="mb-2 flex items-center gap-2">
<span className="inline-flex h-8 w-8 items-center justify-center rounded-full border border-white/12 bg-white/[0.07] text-[#c8cd19]"><Plus className="h-4 w-4" /></span>
<WorkflowStepBadge step={step} compact />
</div>
<h2 className="text-[15px] font-semibold leading-tight text-white"></h2>
<p className="mt-1 text-[12px] leading-snug text-white/42"></p>
</div>
<div className="flex shrink-0 gap-1.5">
<button
type="button"
onClick={onOpenLibrary}
className="skg-secondary-action inline-flex h-8 w-8 items-center justify-center transition"
title="资源库"
aria-label="打开资源库"
>
<BookOpen className="h-3.5 w-3.5" />
</button>
<button
type="button"
onClick={onToggleTheme}
className="skg-secondary-action inline-flex h-8 w-8 items-center justify-center transition"
title={boardTheme === "dark" ? "切换到明亮模式" : "切换到暗色模式"}
aria-label={boardTheme === "dark" ? "切换到明亮模式" : "切换到暗色模式"}
>
{boardTheme === "dark" ? <Sun className="h-3.5 w-3.5" /> : <Moon className="h-3.5 w-3.5" />}
</button>
</div>
<div className="mb-2 flex items-center gap-2">
<span className="inline-flex h-8 w-8 items-center justify-center rounded-full border border-white/12 bg-white/[0.07] text-[#c8cd19]"><Plus className="h-4 w-4" /></span>
<WorkflowStepBadge step={step} compact />
</div>
<h2 className="text-[15px] font-semibold leading-tight text-white"></h2>
<p className="mt-1 text-[12px] leading-snug text-white/42"></p>
</header>
<div className="flex gap-2">