319 lines
11 KiB
TypeScript
319 lines
11 KiB
TypeScript
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}
|
|
</>
|
|
);
|
|
}
|