Files
Genarrative/src/components/image-editor/ImageCanvasTopbarView.tsx
kdletters d8b935317d 拆分编辑器前端画布视图
抽出素材栏、生成器、舞台工具栏和画布世界视图

补充各拆分视图的聚焦测试

更新 TRACKING.md 记录第三十四阶段验证
2026-06-17 17:48:12 +08:00

150 lines
4.7 KiB
TypeScript

import { Check, ChevronLeft, Download, Pencil, X } from 'lucide-react';
import { PlatformStatusMessage } from '../common/PlatformStatusMessage';
import { PlatformTextField } from '../common/PlatformTextField';
import { EditorIconButton } from './ImageCanvasEditorPrimitives';
import type { CanvasLayer } from './ImageCanvasEditorTypes';
import type { AssetExportStatus } from './useImageCanvasAssetExportWorkflow';
export type ImageCanvasTopbarViewProps = {
projectId: string | null;
projectTitle: string;
projectRenameValue: string;
isRenamingProject: boolean;
isProjectRenameSaving: boolean;
projectRenameError: string | null;
layers: CanvasLayer[];
assetExportStatus: AssetExportStatus | null;
isExportingAssets: boolean;
setProjectRenameValue: (value: string) => void;
startProjectRename: () => void;
cancelProjectRename: () => void;
submitProjectRename: (projectId: string | null) => void;
resetProjectRenameError: () => void;
exportCanvasAssets: () => void | Promise<void>;
};
export function ImageCanvasTopbarView({
projectId,
projectTitle,
projectRenameValue,
isRenamingProject,
isProjectRenameSaving,
projectRenameError,
layers,
assetExportStatus,
isExportingAssets,
setProjectRenameValue,
startProjectRename,
cancelProjectRename,
submitProjectRename,
resetProjectRenameError,
exportCanvasAssets,
}: ImageCanvasTopbarViewProps) {
const hasExportableLayer = layers.some(
(layer) => layer.src.trim().length > 0,
);
return (
<div className="image-canvas-editor__topbar">
<a
className="image-canvas-editor__project-back-button"
href="/project"
aria-label="返回项目页面"
title="返回项目"
>
<ChevronLeft className="h-4 w-4" aria-hidden="true" />
</a>
<div className="image-canvas-editor__title-block">
{isRenamingProject ? (
<form
className="image-canvas-editor__project-title-form"
onSubmit={(event) => {
event.preventDefault();
submitProjectRename(projectId);
}}
>
<PlatformTextField
aria-label="项目名称"
value={projectRenameValue}
autoFocus
disabled={isProjectRenameSaving}
className="image-canvas-editor__project-title-input"
onChange={(event) => {
setProjectRenameValue(event.target.value);
resetProjectRenameError();
}}
onKeyDown={(event) => {
if (event.key === 'Escape') {
event.preventDefault();
cancelProjectRename();
}
}}
/>
<EditorIconButton
type="submit"
label="保存项目名称"
title="保存"
icon={Check}
disabled={isProjectRenameSaving}
/>
<EditorIconButton
label="取消修改项目名称"
title="取消"
icon={X}
disabled={isProjectRenameSaving}
onClick={cancelProjectRename}
/>
{projectRenameError ? (
<span
className="image-canvas-editor__project-title-error"
role="alert"
>
{projectRenameError}
</span>
) : null}
</form>
) : (
<div className="image-canvas-editor__project-title-row">
<button
type="button"
className="image-canvas-editor__project-title-button"
onDoubleClick={startProjectRename}
aria-label={`编辑项目名称${projectTitle}`}
>
<h1>{projectTitle}</h1>
</button>
<EditorIconButton
className="image-canvas-editor__project-rename-button"
label="编辑项目名称"
title="编辑项目名称"
icon={Pencil}
onClick={startProjectRename}
/>
</div>
)}
<span></span>
</div>
<div className="image-canvas-editor__topbar-actions">
<EditorIconButton
label="下载画布素材"
title="下载画布素材"
icon={Download}
disabled={isExportingAssets || !hasExportableLayer}
onClick={() => void exportCanvasAssets()}
/>
{assetExportStatus ? (
<PlatformStatusMessage
tone={assetExportStatus.tone}
surface="platform"
size="xs"
role={assetExportStatus.tone === 'error' ? 'alert' : 'status'}
>
{assetExportStatus.message}
</PlatformStatusMessage>
) : null}
</div>
</div>
);
}