import { CanvasChromeButton } from '@genarrative/image-canvas-react'; import { OverflowActions } from '@genarrative/shared/components'; import { Crop, Download, Grid2X2, ImageOff, Loader2, PersonStanding, Scissors, Sparkles, WandSparkles, } from 'lucide-react'; import { Children, type CSSProperties, Fragment, isValidElement, type ReactNode, } from 'react'; import { EditorIconButton } from './ImageCanvasEditorPrimitives'; import type { CanvasLayer } from './ImageCanvasEditorTypes'; import { canOpenRedrawPanel, isQuickEditSupportedLayer, } from './ImageCanvasGenerationModel'; /** 选中工具条上的动作标识,供宿主声明自己实际可用的动作集合。 */ export type ImageCanvasSelectedToolbarAction = | 'quick-edit' | 'redraw' | 'crop-expand' | 'remove-background' | 'perfect-pixel' | 'split-icon-spritesheet' | 'extract-ui-design' | 'character-animation' | 'download'; type ImageCanvasSelectedLayerToolbarViewProps = { maxVisibleActions?: number; /** * 宿主显式声明的可用动作集合。 * * 传 `null`(默认)时完全沿用美术画布内置判定;传入集合时以集合为准, * 让没有对应后端链路的画布宿主不必把「点了没反应」的按钮渲染出来。 */ supportedActions?: ReadonlySet | null; /** 宿主在工具条上追加的常规动作,渲染在下载按钮之前。 */ extraActions?: ReactNode; /** 工具栏末组动作,紧随下载按钮,与下载共用一个分组。 */ endActions?: ReactNode; /** 提供文字时显示带文字的导出入口;缺省保持网页画布的图标按钮。 */ downloadLabel?: string; selectedLayer: CanvasLayer | null; selectedToolbarStyle: CSSProperties | null; onOpenQuickEditPanel: (layer: CanvasLayer) => void; onOpenRedrawPanel: (layer: CanvasLayer) => void; onOpenCropExpandPanel: (layer: CanvasLayer) => void; onRemoveBackground: (layer: CanvasLayer) => void; onPerfectPixel: (layer: CanvasLayer) => void; isPerfectPixelProcessing: boolean; isPerfectPixelPendingConfirmation: boolean; isSplittingIconSpritesheet: boolean; isPersistingAssetKind: boolean; onSplitIconSpritesheet: (layer: CanvasLayer) => void; onExtractUiDesignAssets: (layer: CanvasLayer) => void; onOpenCharacterAnimationPanel: (layer: CanvasLayer) => void; onDownloadLayer: (layer: CanvasLayer) => void; }; /** 条件动作通常放在 Fragment 中;空 Fragment 不能产生一个空分组。 */ function hasToolbarActions(actions: ReactNode): boolean { return Children.toArray(actions).some((action) => isValidElement<{ children?: ReactNode }>(action) && action.type === Fragment ? hasToolbarActions(action.props.children) : action !== '', ); } export function ImageCanvasSelectedLayerToolbarView({ maxVisibleActions, supportedActions = null, extraActions, endActions, downloadLabel, selectedLayer, selectedToolbarStyle, onOpenQuickEditPanel, onOpenRedrawPanel, onOpenCropExpandPanel, onRemoveBackground, onPerfectPixel, isPerfectPixelProcessing, isPerfectPixelPendingConfirmation, isSplittingIconSpritesheet, isPersistingAssetKind, onSplitIconSpritesheet, onExtractUiDesignAssets, onOpenCharacterAnimationPanel, onDownloadLayer, }: ImageCanvasSelectedLayerToolbarViewProps) { if (!selectedLayer || !selectedToolbarStyle) { return null; } const isActionSupported = ( action: ImageCanvasSelectedToolbarAction, builtInAvailability: boolean, ) => (supportedActions ? supportedActions.has(action) : builtInAvailability); const canRedraw = isActionSupported( 'redraw', canOpenRedrawPanel(selectedLayer), ); const showDownload = isActionSupported('download', true); const downloadAction = showDownload ? ( downloadLabel ? ( } onClick={() => onDownloadLayer(selectedLayer)} > {downloadLabel} ) : ( onDownloadLayer(selectedLayer)} /> ) ) : null; const hasExtraActions = hasToolbarActions(extraActions); const hasEndActions = showDownload || hasToolbarActions(endActions); const divider = (