拆分编辑器前端画布视图
抽出素材栏、生成器、舞台工具栏和画布世界视图 补充各拆分视图的聚焦测试 更新 TRACKING.md 记录第三十四阶段验证
This commit is contained in:
318
src/components/image-editor/ImageCanvasPanelDockView.tsx
Normal file
318
src/components/image-editor/ImageCanvasPanelDockView.tsx
Normal file
@@ -0,0 +1,318 @@
|
||||
import {
|
||||
ImagePlus,
|
||||
Layers,
|
||||
Map as MapIcon,
|
||||
Redo2,
|
||||
RotateCcw,
|
||||
Undo2,
|
||||
X,
|
||||
} from 'lucide-react';
|
||||
import type { PointerEvent as ReactPointerEvent } from 'react';
|
||||
|
||||
import { PlatformFloatingMenu, PlatformFloatingMenuItem } from '../common/PlatformFloatingMenu';
|
||||
import { PlatformIconButton } from '../common/PlatformIconButton';
|
||||
import { PlatformInlineOptionButton } from '../common/PlatformInlineOptionButton';
|
||||
import { EditorIconButton } from './ImageCanvasEditorPrimitives';
|
||||
import type { StageMinimapModel } from './ImageCanvasInteractionModel';
|
||||
import {
|
||||
CANVAS_BACKGROUND_OPTIONS,
|
||||
DEFAULT_CANVAS_BACKGROUND_COLOR,
|
||||
formatPercent,
|
||||
} from './ImageCanvasEditorModel';
|
||||
import type { CanvasViewport, SidebarPanel } from './ImageCanvasEditorTypes';
|
||||
|
||||
type ImageCanvasPanelDockViewProps = {
|
||||
viewport: CanvasViewport;
|
||||
canvasBackgroundColor: string;
|
||||
canvasBackgroundHexValue: string;
|
||||
canUndo: boolean;
|
||||
canRedo: boolean;
|
||||
isZoomMenuOpen: boolean;
|
||||
isBackgroundSettingsOpen: boolean;
|
||||
activeSidebarPanel: SidebarPanel | null;
|
||||
isMinimapOpen: boolean;
|
||||
minimapModel: StageMinimapModel | null;
|
||||
onFitLayers: () => void;
|
||||
onUndoCanvasChange: () => void;
|
||||
onRedoCanvasChange: () => void;
|
||||
onUpdateScaleFromCenter: (nextScale: number) => void;
|
||||
onToggleZoomMenu: () => void;
|
||||
onCloseZoomMenu: () => void;
|
||||
onToggleBackgroundSettings: () => void;
|
||||
onApplyCanvasBackgroundColor: (color: string) => void;
|
||||
onCanvasBackgroundHexChange: (value: string) => void;
|
||||
onToggleSidebarPanel: (panel: SidebarPanel) => void;
|
||||
onToggleMinimap: () => void;
|
||||
onMinimapPointerDown: (event: ReactPointerEvent<HTMLButtonElement>) => void;
|
||||
};
|
||||
|
||||
export function ImageCanvasPanelDockView({
|
||||
viewport,
|
||||
canvasBackgroundColor,
|
||||
canvasBackgroundHexValue,
|
||||
canUndo,
|
||||
canRedo,
|
||||
isZoomMenuOpen,
|
||||
isBackgroundSettingsOpen,
|
||||
activeSidebarPanel,
|
||||
isMinimapOpen,
|
||||
minimapModel,
|
||||
onFitLayers,
|
||||
onUndoCanvasChange,
|
||||
onRedoCanvasChange,
|
||||
onUpdateScaleFromCenter,
|
||||
onToggleZoomMenu,
|
||||
onCloseZoomMenu,
|
||||
onToggleBackgroundSettings,
|
||||
onApplyCanvasBackgroundColor,
|
||||
onCanvasBackgroundHexChange,
|
||||
onToggleSidebarPanel,
|
||||
onToggleMinimap,
|
||||
onMinimapPointerDown,
|
||||
}: ImageCanvasPanelDockViewProps) {
|
||||
return (
|
||||
<>
|
||||
<EditorIconButton
|
||||
className="image-canvas-editor__reset-button"
|
||||
label="重置画布视图"
|
||||
title="重置画布视图"
|
||||
icon={RotateCcw}
|
||||
onClick={() => onFitLayers()}
|
||||
/>
|
||||
|
||||
<div
|
||||
className="image-canvas-editor__panel-dock"
|
||||
role="toolbar"
|
||||
aria-label="画布面板入口"
|
||||
onPointerDown={(event) => event.stopPropagation()}
|
||||
>
|
||||
<EditorIconButton
|
||||
label="撤销"
|
||||
title="撤销"
|
||||
icon={Undo2}
|
||||
disabled={!canUndo}
|
||||
onClick={onUndoCanvasChange}
|
||||
/>
|
||||
<EditorIconButton
|
||||
label="重做"
|
||||
title="重做"
|
||||
icon={Redo2}
|
||||
disabled={!canRedo}
|
||||
onClick={onRedoCanvasChange}
|
||||
/>
|
||||
<div className="image-canvas-editor__zoom-menu-wrap">
|
||||
<PlatformInlineOptionButton
|
||||
className="image-canvas-editor__zoom-trigger"
|
||||
aria-label={`当前缩放比例 ${formatPercent(viewport.scale)}`}
|
||||
aria-haspopup="menu"
|
||||
aria-expanded={isZoomMenuOpen}
|
||||
onClick={onToggleZoomMenu}
|
||||
>
|
||||
{formatPercent(viewport.scale)}
|
||||
</PlatformInlineOptionButton>
|
||||
{isZoomMenuOpen ? (
|
||||
<PlatformFloatingMenu label="缩放菜单" placement="top-start">
|
||||
<PlatformFloatingMenuItem
|
||||
className="image-canvas-editor__zoom-menu-item"
|
||||
onClick={() => {
|
||||
onUpdateScaleFromCenter(viewport.scale * 1.16);
|
||||
onCloseZoomMenu();
|
||||
}}
|
||||
>
|
||||
放大
|
||||
</PlatformFloatingMenuItem>
|
||||
<PlatformFloatingMenuItem
|
||||
className="image-canvas-editor__zoom-menu-item"
|
||||
onClick={() => {
|
||||
onUpdateScaleFromCenter(viewport.scale * 0.86);
|
||||
onCloseZoomMenu();
|
||||
}}
|
||||
>
|
||||
缩小
|
||||
</PlatformFloatingMenuItem>
|
||||
<PlatformFloatingMenuItem
|
||||
className="image-canvas-editor__zoom-menu-item"
|
||||
onClick={() => {
|
||||
onFitLayers();
|
||||
onCloseZoomMenu();
|
||||
}}
|
||||
>
|
||||
显示画布所有元素
|
||||
</PlatformFloatingMenuItem>
|
||||
{[0.5, 1, 2].map((scale) => (
|
||||
<PlatformFloatingMenuItem
|
||||
key={scale}
|
||||
className="image-canvas-editor__zoom-menu-item"
|
||||
onClick={() => {
|
||||
onUpdateScaleFromCenter(scale);
|
||||
onCloseZoomMenu();
|
||||
}}
|
||||
>
|
||||
缩放至{Math.round(scale * 100)}%
|
||||
</PlatformFloatingMenuItem>
|
||||
))}
|
||||
</PlatformFloatingMenu>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="image-canvas-editor__background-control">
|
||||
<PlatformIconButton
|
||||
label="画布背景色"
|
||||
title="画布背景色"
|
||||
aria-expanded={isBackgroundSettingsOpen}
|
||||
onClick={onToggleBackgroundSettings}
|
||||
icon={
|
||||
<span
|
||||
className="image-canvas-editor__background-swatch-current"
|
||||
style={{ backgroundColor: canvasBackgroundColor }}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{isBackgroundSettingsOpen ? (
|
||||
<div
|
||||
className="image-canvas-editor__background-panel"
|
||||
role="dialog"
|
||||
aria-label="画布背景设置"
|
||||
>
|
||||
<div className="image-canvas-editor__background-panel-head">
|
||||
<span>画布背景</span>
|
||||
<button
|
||||
type="button"
|
||||
className="image-canvas-editor__background-close"
|
||||
aria-label="关闭画布背景设置"
|
||||
onClick={onToggleBackgroundSettings}
|
||||
>
|
||||
<X className="h-4 w-4" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="image-canvas-editor__background-current-row">
|
||||
<span
|
||||
className="image-canvas-editor__background-current-preview"
|
||||
style={{ backgroundColor: canvasBackgroundColor }}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span>{canvasBackgroundColor}</span>
|
||||
</div>
|
||||
<label className="image-canvas-editor__background-spectrum">
|
||||
<input
|
||||
type="color"
|
||||
aria-label="画布背景色相"
|
||||
value={canvasBackgroundColor}
|
||||
onChange={(event) =>
|
||||
onApplyCanvasBackgroundColor(event.currentTarget.value)
|
||||
}
|
||||
/>
|
||||
<span
|
||||
className="image-canvas-editor__background-spectrum-surface"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span
|
||||
className="image-canvas-editor__background-spectrum-handle"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</label>
|
||||
<label className="image-canvas-editor__background-hue">
|
||||
<input
|
||||
type="color"
|
||||
aria-label="自定义画布背景色"
|
||||
value={canvasBackgroundColor}
|
||||
onChange={(event) =>
|
||||
onApplyCanvasBackgroundColor(event.currentTarget.value)
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<div
|
||||
className="image-canvas-editor__background-presets"
|
||||
aria-label="画布背景预设色"
|
||||
>
|
||||
{CANVAS_BACKGROUND_OPTIONS.map((option) => (
|
||||
<button
|
||||
key={option.value}
|
||||
type="button"
|
||||
className="image-canvas-editor__background-preset"
|
||||
aria-label={option.label}
|
||||
aria-pressed={canvasBackgroundColor === option.value}
|
||||
onClick={() => onApplyCanvasBackgroundColor(option.value)}
|
||||
>
|
||||
<span
|
||||
className="image-canvas-editor__background-swatch"
|
||||
style={{ backgroundColor: option.value }}
|
||||
/>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="image-canvas-editor__background-footer">
|
||||
<label className="image-canvas-editor__background-hex-field">
|
||||
<span>HEX</span>
|
||||
<input
|
||||
aria-label="画布背景十六进制颜色"
|
||||
value={canvasBackgroundHexValue}
|
||||
spellCheck={false}
|
||||
onChange={(event) =>
|
||||
onCanvasBackgroundHexChange(event.currentTarget.value)
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
className="image-canvas-editor__background-reset"
|
||||
onClick={() =>
|
||||
onApplyCanvasBackgroundColor(DEFAULT_CANVAS_BACKGROUND_COLOR)
|
||||
}
|
||||
>
|
||||
<RotateCcw className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
恢复默认
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<EditorIconButton
|
||||
label="打开素材"
|
||||
title="素材"
|
||||
icon={ImagePlus}
|
||||
pressed={activeSidebarPanel === 'assets'}
|
||||
onClick={() => onToggleSidebarPanel('assets')}
|
||||
/>
|
||||
<EditorIconButton
|
||||
label="打开图层"
|
||||
title="图层"
|
||||
icon={Layers}
|
||||
pressed={activeSidebarPanel === 'layers'}
|
||||
onClick={() => onToggleSidebarPanel('layers')}
|
||||
/>
|
||||
<EditorIconButton
|
||||
label="切换小地图"
|
||||
title="小地图"
|
||||
icon={MapIcon}
|
||||
pressed={isMinimapOpen}
|
||||
onClick={onToggleMinimap}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{isMinimapOpen && minimapModel ? (
|
||||
<button
|
||||
type="button"
|
||||
className="image-canvas-editor__minimap"
|
||||
aria-label="画布小地图"
|
||||
title="拖拽移动视图"
|
||||
onPointerDown={onMinimapPointerDown}
|
||||
>
|
||||
<span className="image-canvas-editor__minimap-stage">
|
||||
{minimapModel.layers.map((layer) => (
|
||||
<span
|
||||
key={layer.id}
|
||||
className="image-canvas-editor__minimap-layer"
|
||||
title={layer.title}
|
||||
style={layer.rect}
|
||||
/>
|
||||
))}
|
||||
<span
|
||||
className="image-canvas-editor__minimap-viewport"
|
||||
style={minimapModel.viewport}
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user