将生成器对话框作为画布布局项序列化和恢复 生成成功后保留生成器快照并锚定到成品图层 图片类生成结果同步写入账号素材库 补充生成器持久化测试和浏览器回归相关文档
635 lines
24 KiB
TypeScript
635 lines
24 KiB
TypeScript
import { ChevronDown, ImageIcon } from 'lucide-react';
|
||
import {
|
||
type CSSProperties,
|
||
type Dispatch,
|
||
type ReactNode,
|
||
type RefObject,
|
||
type SetStateAction,
|
||
} from 'react';
|
||
import { createPortal } from 'react-dom';
|
||
|
||
import {
|
||
PlatformFloatingMenu,
|
||
PlatformFloatingMenuItem,
|
||
} from '../common/PlatformFloatingMenu';
|
||
import { PlatformActionButton } from '../common/PlatformActionButton';
|
||
import { PlatformIconButton } from '../common/PlatformIconButton';
|
||
import { PlatformInlineOptionButton } from '../common/PlatformInlineOptionButton';
|
||
import { PlatformTextField } from '../common/PlatformTextField';
|
||
import { ImageCanvasBasicGenerationComposerView } from './ImageCanvasBasicGenerationComposerView';
|
||
import { ImageCanvasCharacterAnimationPanelView } from './ImageCanvasCharacterAnimationPanelView';
|
||
import { ImageCanvasCharacterGenerationComposerView } from './ImageCanvasCharacterGenerationComposerView';
|
||
import { ImageCanvasEditGenerationModalView } from './ImageCanvasEditGenerationModalView';
|
||
import { ImageCanvasIconSpritesheetComposerView } from './ImageCanvasIconSpritesheetComposerView';
|
||
import { ImageCanvasQuickEditPanelView } from './ImageCanvasQuickEditPanelView';
|
||
import { ImageCanvasSpecGenerationPanelView } from './ImageCanvasSpecGenerationPanelView';
|
||
import {
|
||
EDITOR_VIDEO_MODEL_OPTIONS,
|
||
SPEC_TYPE_LABEL,
|
||
calculateEditorVideoPrice,
|
||
} from './ImageCanvasGenerationModel';
|
||
import type {
|
||
CharacterAnimationPanelState,
|
||
CanvasLayer,
|
||
GenerateDialogState,
|
||
QuickEditPanelState,
|
||
SpecFormValues,
|
||
SpecGenerationType,
|
||
UploadTarget,
|
||
} from './ImageCanvasEditorTypes';
|
||
|
||
type ImageCanvasGenerationComposerViewProps = {
|
||
specToolWrapRef: RefObject<HTMLSpanElement | null>;
|
||
characterSpecButtonRef: RefObject<HTMLButtonElement | null>;
|
||
characterReferenceButtonRef: RefObject<HTMLButtonElement | null>;
|
||
generationReferenceButtonRef: RefObject<HTMLButtonElement | null>;
|
||
iconSpecButtonRef: RefObject<HTMLButtonElement | null>;
|
||
isSpecMenuOpen: boolean;
|
||
isGenerationReferenceMenuOpen: boolean;
|
||
isCharacterSpecMenuOpen: boolean;
|
||
isCharacterReferenceMenuOpen: boolean;
|
||
isIconSpecMenuOpen: boolean;
|
||
isUiDesignSpecMenuOpen: boolean;
|
||
isPickingGenerationReferenceFromCanvas: boolean;
|
||
isPickingCharacterSpecFromCanvas: boolean;
|
||
isPickingCharacterReferenceFromCanvas: boolean;
|
||
isPickingIconSpecFromCanvas: boolean;
|
||
isPickingUiDesignSpecFromCanvas: boolean;
|
||
generateDialog: GenerateDialogState | null;
|
||
generationComposerStyle: CSSProperties | null;
|
||
iconComposerStyle: CSSProperties | null;
|
||
quickEditPanel: QuickEditPanelState | null;
|
||
quickEditSourceLayer: CanvasLayer | null;
|
||
quickEditPanelStyle: CSSProperties | null;
|
||
quickEditSizeOptions: string[];
|
||
quickEditModelOptions: Array<{ label: string; value: string }>;
|
||
characterAnimationPanel: CharacterAnimationPanelState | null;
|
||
characterAnimationSourceLayer: CanvasLayer | null;
|
||
characterAnimationPanelStyle: CSSProperties | null;
|
||
characterAnimationPrice: number;
|
||
setGenerateDialog: Dispatch<SetStateAction<GenerateDialogState | null>>;
|
||
setQuickEditPanel: Dispatch<SetStateAction<QuickEditPanelState | null>>;
|
||
setCharacterAnimationPanel: Dispatch<
|
||
SetStateAction<CharacterAnimationPanelState | null>
|
||
>;
|
||
setIsGenerationReferenceMenuOpen: Dispatch<SetStateAction<boolean>>;
|
||
setIsCharacterSpecMenuOpen: Dispatch<SetStateAction<boolean>>;
|
||
setIsCharacterReferenceMenuOpen: Dispatch<SetStateAction<boolean>>;
|
||
setIsIconSpecMenuOpen: Dispatch<SetStateAction<boolean>>;
|
||
setIsUiDesignSpecMenuOpen: Dispatch<SetStateAction<boolean>>;
|
||
setIsPickingGenerationReferenceFromCanvas: Dispatch<SetStateAction<boolean>>;
|
||
setIsPickingCharacterSpecFromCanvas: Dispatch<SetStateAction<boolean>>;
|
||
setIsPickingCharacterReferenceFromCanvas: Dispatch<SetStateAction<boolean>>;
|
||
setIsPickingIconSpecFromCanvas: Dispatch<SetStateAction<boolean>>;
|
||
setIsPickingUiDesignSpecFromCanvas: Dispatch<SetStateAction<boolean>>;
|
||
onOpenSpecDialog: (specType: SpecGenerationType) => void;
|
||
onRequestUpload: (target: UploadTarget) => void;
|
||
onSubmitImageGeneration: (dialog: GenerateDialogState) => void;
|
||
onSubmitIconSpritesheetGeneration: (dialog: GenerateDialogState) => void;
|
||
onSubmitQuickEdit: () => void;
|
||
onSubmitCharacterAnimation: () => void;
|
||
onCloseGenerateComposer: () => void;
|
||
onUpdateSpecFormValue: (key: keyof SpecFormValues, value: string) => void;
|
||
onUpdateIconDescription: (index: number, value: string) => void;
|
||
onAddIconDescription: () => void;
|
||
onUpdateCharacterAnimationDuration: (frameCountValue: string) => void;
|
||
onRememberImageModel: (model: string) => void;
|
||
};
|
||
|
||
function buildPortalMenuStyle(
|
||
anchor: HTMLElement | null,
|
||
placement: 'above' | 'below',
|
||
): CSSProperties {
|
||
const rect = anchor?.getBoundingClientRect();
|
||
if (!rect) {
|
||
return {
|
||
position: 'fixed',
|
||
left: 0,
|
||
top: 0,
|
||
right: 'auto',
|
||
bottom: 'auto',
|
||
zIndex: 70,
|
||
};
|
||
}
|
||
|
||
return {
|
||
position: 'fixed',
|
||
left: Math.round(rect.left),
|
||
top:
|
||
placement === 'above'
|
||
? Math.round(rect.top)
|
||
: Math.round(rect.bottom + 8),
|
||
right: 'auto',
|
||
bottom: 'auto',
|
||
zIndex: 70,
|
||
transform:
|
||
placement === 'above' ? 'translateY(calc(-100% - 0.45rem))' : undefined,
|
||
};
|
||
}
|
||
|
||
function renderEditorPortal(node: ReactNode) {
|
||
if (typeof document === 'undefined') {
|
||
return node;
|
||
}
|
||
return createPortal(node, document.body);
|
||
}
|
||
|
||
function ImageCanvasVideoGenerationComposerView({
|
||
dialog,
|
||
style,
|
||
generationReferenceButtonRef,
|
||
isGenerationReferenceMenuOpen,
|
||
setGenerateDialog,
|
||
setIsGenerationReferenceMenuOpen,
|
||
setIsPickingGenerationReferenceFromCanvas,
|
||
onRequestUpload,
|
||
onSubmit,
|
||
}: {
|
||
dialog: GenerateDialogState;
|
||
style: CSSProperties;
|
||
generationReferenceButtonRef: RefObject<HTMLButtonElement | null>;
|
||
isGenerationReferenceMenuOpen: boolean;
|
||
setGenerateDialog: Dispatch<SetStateAction<GenerateDialogState | null>>;
|
||
setIsGenerationReferenceMenuOpen: Dispatch<SetStateAction<boolean>>;
|
||
setIsPickingGenerationReferenceFromCanvas: Dispatch<SetStateAction<boolean>>;
|
||
onRequestUpload: (target: UploadTarget) => void;
|
||
onSubmit: (dialog: GenerateDialogState) => void;
|
||
}) {
|
||
const resolution = dialog.videoResolution ?? '480p';
|
||
const durationSeconds = dialog.videoDurationSeconds ?? 4;
|
||
const currentModel =
|
||
EDITOR_VIDEO_MODEL_OPTIONS.find((item) => item.value === dialog.videoModel)
|
||
?? EDITOR_VIDEO_MODEL_OPTIONS[0];
|
||
const price = calculateEditorVideoPrice(resolution, durationSeconds);
|
||
return (
|
||
<>
|
||
<form
|
||
className="image-canvas-editor__generation-composer image-canvas-editor__generation-composer--image"
|
||
style={style}
|
||
role="dialog"
|
||
aria-label="生成视频"
|
||
onPointerDown={(event) => event.stopPropagation()}
|
||
onSubmit={(event) => {
|
||
event.preventDefault();
|
||
if (dialog.status !== 'generating') {
|
||
onSubmit(dialog);
|
||
}
|
||
}}
|
||
>
|
||
<PlatformIconButton
|
||
ref={generationReferenceButtonRef}
|
||
variant="surfaceFloating"
|
||
className="image-canvas-editor__generation-ref"
|
||
label="添加视频参考图"
|
||
disabled={dialog.status === 'generating'}
|
||
onClick={() => setIsGenerationReferenceMenuOpen((open) => !open)}
|
||
icon={<ImageIcon className="h-4 w-4" />}
|
||
>
|
||
<span>参考图</span>
|
||
</PlatformIconButton>
|
||
<PlatformTextField
|
||
variant="textarea"
|
||
aria-label="视频描述"
|
||
value={dialog.prompt}
|
||
disabled={dialog.status === 'generating'}
|
||
placeholder="描述视频画面"
|
||
size="sm"
|
||
density="compact"
|
||
className="image-canvas-editor__generation-prompt"
|
||
onChange={(event) =>
|
||
setGenerateDialog((currentDialog) =>
|
||
currentDialog
|
||
? {
|
||
...currentDialog,
|
||
prompt: event.target.value,
|
||
status:
|
||
currentDialog.status === 'failed'
|
||
? 'idle'
|
||
: currentDialog.status,
|
||
errorMessage:
|
||
currentDialog.status === 'failed'
|
||
? undefined
|
||
: currentDialog.errorMessage,
|
||
}
|
||
: currentDialog,
|
||
)
|
||
}
|
||
/>
|
||
<div className="image-canvas-editor__generation-composer-footer">
|
||
<PlatformInlineOptionButton
|
||
className="image-canvas-editor__generation-ratio"
|
||
aria-label={`视频参数 16:9 · ${durationSeconds}秒 · ${resolution}`}
|
||
disabled={dialog.status === 'generating'}
|
||
trailingIcon={<ChevronDown className="h-3 w-3" />}
|
||
onClick={() => {
|
||
setGenerateDialog((currentDialog) =>
|
||
currentDialog?.mode === 'video'
|
||
? {
|
||
...currentDialog,
|
||
videoDurationSeconds:
|
||
currentDialog.videoDurationSeconds === 5 ? 4 : 5,
|
||
status:
|
||
currentDialog.status === 'failed'
|
||
? 'idle'
|
||
: currentDialog.status,
|
||
errorMessage:
|
||
currentDialog.status === 'failed'
|
||
? undefined
|
||
: currentDialog.errorMessage,
|
||
}
|
||
: currentDialog,
|
||
);
|
||
}}
|
||
>
|
||
16:9 · {resolution} · {durationSeconds}秒
|
||
</PlatformInlineOptionButton>
|
||
<PlatformInlineOptionButton
|
||
className="image-canvas-editor__generation-model"
|
||
aria-label={`模型 ${currentModel.label}`}
|
||
disabled={dialog.status === 'generating'}
|
||
trailingIcon={<ChevronDown className="h-3 w-3" />}
|
||
onClick={() => {
|
||
setGenerateDialog((currentDialog) => {
|
||
if (currentDialog?.mode !== 'video') {
|
||
return currentDialog;
|
||
}
|
||
const currentIndex = EDITOR_VIDEO_MODEL_OPTIONS.findIndex(
|
||
(item) => item.value === currentDialog.videoModel,
|
||
);
|
||
const nextModel =
|
||
EDITOR_VIDEO_MODEL_OPTIONS[
|
||
(Math.max(currentIndex, 0) + 1) %
|
||
EDITOR_VIDEO_MODEL_OPTIONS.length
|
||
] ?? EDITOR_VIDEO_MODEL_OPTIONS[0];
|
||
if (!nextModel) {
|
||
return currentDialog;
|
||
}
|
||
return {
|
||
...currentDialog,
|
||
videoModel: nextModel.value,
|
||
status:
|
||
currentDialog.status === 'failed'
|
||
? 'idle'
|
||
: currentDialog.status,
|
||
errorMessage:
|
||
currentDialog.status === 'failed'
|
||
? undefined
|
||
: currentDialog.errorMessage,
|
||
};
|
||
});
|
||
}}
|
||
>
|
||
{currentModel.label}
|
||
</PlatformInlineOptionButton>
|
||
<PlatformInlineOptionButton
|
||
className="image-canvas-editor__generation-ratio"
|
||
aria-label={`清晰度 ${resolution}`}
|
||
disabled={dialog.status === 'generating'}
|
||
trailingIcon={<ChevronDown className="h-3 w-3" />}
|
||
onClick={() => {
|
||
setGenerateDialog((currentDialog) =>
|
||
currentDialog?.mode === 'video'
|
||
? {
|
||
...currentDialog,
|
||
videoResolution:
|
||
currentDialog.videoResolution === '720p'
|
||
? '480p'
|
||
: '720p',
|
||
status:
|
||
currentDialog.status === 'failed'
|
||
? 'idle'
|
||
: currentDialog.status,
|
||
errorMessage:
|
||
currentDialog.status === 'failed'
|
||
? undefined
|
||
: currentDialog.errorMessage,
|
||
}
|
||
: currentDialog,
|
||
);
|
||
}}
|
||
>
|
||
{resolution}
|
||
</PlatformInlineOptionButton>
|
||
<PlatformActionButton
|
||
type="submit"
|
||
tone="secondary"
|
||
size="xs"
|
||
shape="pill"
|
||
className="image-canvas-editor__generation-submit"
|
||
disabled={dialog.status === 'generating'}
|
||
aria-label="生成视频"
|
||
>
|
||
{dialog.status === 'generating' ? '生成中' : `${price}`}
|
||
</PlatformActionButton>
|
||
</div>
|
||
</form>
|
||
{isGenerationReferenceMenuOpen
|
||
? renderEditorPortal(
|
||
<PlatformFloatingMenu
|
||
className="image-canvas-editor__spec-menu image-canvas-editor__portal-menu"
|
||
label="参考图来源"
|
||
placement="top-start"
|
||
style={buildPortalMenuStyle(
|
||
generationReferenceButtonRef.current,
|
||
'above',
|
||
)}
|
||
>
|
||
<PlatformFloatingMenuItem
|
||
onClick={() => {
|
||
setIsGenerationReferenceMenuOpen(false);
|
||
setIsPickingGenerationReferenceFromCanvas(true);
|
||
}}
|
||
>
|
||
从画布中选择
|
||
</PlatformFloatingMenuItem>
|
||
<PlatformFloatingMenuItem
|
||
onClick={() => {
|
||
setIsGenerationReferenceMenuOpen(false);
|
||
setIsPickingGenerationReferenceFromCanvas(false);
|
||
onRequestUpload('generation-reference');
|
||
}}
|
||
>
|
||
上传图片
|
||
</PlatformFloatingMenuItem>
|
||
</PlatformFloatingMenu>,
|
||
)
|
||
: null}
|
||
</>
|
||
);
|
||
}
|
||
|
||
export function ImageCanvasGenerationComposerView({
|
||
specToolWrapRef,
|
||
characterSpecButtonRef,
|
||
characterReferenceButtonRef,
|
||
generationReferenceButtonRef,
|
||
iconSpecButtonRef,
|
||
isSpecMenuOpen,
|
||
isGenerationReferenceMenuOpen,
|
||
isCharacterSpecMenuOpen,
|
||
isCharacterReferenceMenuOpen,
|
||
isIconSpecMenuOpen,
|
||
isUiDesignSpecMenuOpen,
|
||
isPickingGenerationReferenceFromCanvas,
|
||
isPickingCharacterSpecFromCanvas,
|
||
isPickingCharacterReferenceFromCanvas,
|
||
isPickingIconSpecFromCanvas,
|
||
isPickingUiDesignSpecFromCanvas,
|
||
generateDialog,
|
||
generationComposerStyle,
|
||
iconComposerStyle,
|
||
quickEditPanel,
|
||
quickEditSourceLayer,
|
||
quickEditPanelStyle,
|
||
quickEditSizeOptions,
|
||
quickEditModelOptions,
|
||
characterAnimationPanel,
|
||
characterAnimationSourceLayer,
|
||
characterAnimationPanelStyle,
|
||
characterAnimationPrice,
|
||
setGenerateDialog,
|
||
setQuickEditPanel,
|
||
setCharacterAnimationPanel,
|
||
setIsGenerationReferenceMenuOpen,
|
||
setIsCharacterSpecMenuOpen,
|
||
setIsCharacterReferenceMenuOpen,
|
||
setIsIconSpecMenuOpen,
|
||
setIsUiDesignSpecMenuOpen,
|
||
setIsPickingGenerationReferenceFromCanvas,
|
||
setIsPickingCharacterSpecFromCanvas,
|
||
setIsPickingCharacterReferenceFromCanvas,
|
||
setIsPickingIconSpecFromCanvas,
|
||
setIsPickingUiDesignSpecFromCanvas,
|
||
onOpenSpecDialog,
|
||
onRequestUpload,
|
||
onSubmitImageGeneration,
|
||
onSubmitIconSpritesheetGeneration,
|
||
onSubmitQuickEdit,
|
||
onSubmitCharacterAnimation,
|
||
onCloseGenerateComposer,
|
||
onUpdateSpecFormValue,
|
||
onUpdateIconDescription,
|
||
onAddIconDescription,
|
||
onUpdateCharacterAnimationDuration,
|
||
onRememberImageModel,
|
||
}: ImageCanvasGenerationComposerViewProps) {
|
||
return (
|
||
<>
|
||
{isSpecMenuOpen
|
||
? renderEditorPortal(
|
||
<PlatformFloatingMenu
|
||
className="image-canvas-editor__spec-menu image-canvas-editor__portal-menu"
|
||
label="生成规范类型"
|
||
placement="top-start"
|
||
style={buildPortalMenuStyle(specToolWrapRef.current, 'above')}
|
||
>
|
||
{(['character', 'ui', 'custom'] as const).map((specType) => (
|
||
<PlatformFloatingMenuItem
|
||
key={specType}
|
||
className="image-canvas-editor__spec-menu-item"
|
||
onClick={() => onOpenSpecDialog(specType)}
|
||
>
|
||
{SPEC_TYPE_LABEL[specType]}
|
||
</PlatformFloatingMenuItem>
|
||
))}
|
||
</PlatformFloatingMenu>,
|
||
)
|
||
: null}
|
||
|
||
{generateDialog?.mode === 'generate' &&
|
||
generateDialog.composerOpen !== false &&
|
||
generationComposerStyle ? (
|
||
<ImageCanvasBasicGenerationComposerView
|
||
dialog={generateDialog}
|
||
style={generationComposerStyle}
|
||
setGenerateDialog={setGenerateDialog}
|
||
generationReferenceButtonRef={generationReferenceButtonRef}
|
||
isGenerationReferenceMenuOpen={isGenerationReferenceMenuOpen}
|
||
setIsGenerationReferenceMenuOpen={setIsGenerationReferenceMenuOpen}
|
||
setIsPickingGenerationReferenceFromCanvas={
|
||
setIsPickingGenerationReferenceFromCanvas
|
||
}
|
||
renderEditorPortal={renderEditorPortal}
|
||
buildPortalMenuStyle={buildPortalMenuStyle}
|
||
onRequestUpload={onRequestUpload}
|
||
onToggleReferenceMenu={() =>
|
||
setIsGenerationReferenceMenuOpen((open) => !open)
|
||
}
|
||
onSubmit={onSubmitImageGeneration}
|
||
onClose={onCloseGenerateComposer}
|
||
/>
|
||
) : null}
|
||
|
||
{generateDialog?.mode === 'spec' &&
|
||
generateDialog.composerOpen !== false &&
|
||
generationComposerStyle ? (
|
||
<ImageCanvasSpecGenerationPanelView
|
||
dialog={generateDialog}
|
||
style={generationComposerStyle}
|
||
isGenerationReferenceMenuOpen={isGenerationReferenceMenuOpen}
|
||
generationReferenceButtonRef={generationReferenceButtonRef}
|
||
setIsGenerationReferenceMenuOpen={setIsGenerationReferenceMenuOpen}
|
||
setIsPickingGenerationReferenceFromCanvas={
|
||
setIsPickingGenerationReferenceFromCanvas
|
||
}
|
||
renderEditorPortal={renderEditorPortal}
|
||
buildPortalMenuStyle={buildPortalMenuStyle}
|
||
setGenerateDialog={setGenerateDialog}
|
||
onOpenSpecDialog={onOpenSpecDialog}
|
||
onUpdateSpecFormValue={onUpdateSpecFormValue}
|
||
onRequestUpload={onRequestUpload}
|
||
onSubmit={onSubmitImageGeneration}
|
||
/>
|
||
) : null}
|
||
|
||
{generateDialog?.mode === 'ui-design' &&
|
||
generateDialog.composerOpen !== false &&
|
||
generationComposerStyle ? (
|
||
<ImageCanvasSpecGenerationPanelView
|
||
dialog={generateDialog}
|
||
style={generationComposerStyle}
|
||
isGenerationReferenceMenuOpen={isUiDesignSpecMenuOpen}
|
||
generationReferenceButtonRef={generationReferenceButtonRef}
|
||
setIsGenerationReferenceMenuOpen={setIsUiDesignSpecMenuOpen}
|
||
setIsPickingGenerationReferenceFromCanvas={
|
||
setIsPickingUiDesignSpecFromCanvas
|
||
}
|
||
renderEditorPortal={renderEditorPortal}
|
||
buildPortalMenuStyle={buildPortalMenuStyle}
|
||
setGenerateDialog={setGenerateDialog}
|
||
onOpenSpecDialog={onOpenSpecDialog}
|
||
onUpdateSpecFormValue={onUpdateSpecFormValue}
|
||
onRequestUpload={onRequestUpload}
|
||
onSubmit={onSubmitImageGeneration}
|
||
/>
|
||
) : null}
|
||
|
||
{generateDialog?.mode === 'video' &&
|
||
generateDialog.composerOpen !== false &&
|
||
generationComposerStyle ? (
|
||
<ImageCanvasVideoGenerationComposerView
|
||
dialog={generateDialog}
|
||
style={generationComposerStyle}
|
||
generationReferenceButtonRef={generationReferenceButtonRef}
|
||
isGenerationReferenceMenuOpen={isGenerationReferenceMenuOpen}
|
||
setGenerateDialog={setGenerateDialog}
|
||
setIsGenerationReferenceMenuOpen={setIsGenerationReferenceMenuOpen}
|
||
setIsPickingGenerationReferenceFromCanvas={
|
||
setIsPickingGenerationReferenceFromCanvas
|
||
}
|
||
onRequestUpload={onRequestUpload}
|
||
onSubmit={onSubmitImageGeneration}
|
||
/>
|
||
) : null}
|
||
|
||
{generateDialog?.mode === 'character' && generationComposerStyle ? (
|
||
<ImageCanvasCharacterGenerationComposerView
|
||
dialog={generateDialog}
|
||
style={generationComposerStyle}
|
||
characterSpecButtonRef={characterSpecButtonRef}
|
||
characterReferenceButtonRef={characterReferenceButtonRef}
|
||
isCharacterSpecMenuOpen={isCharacterSpecMenuOpen}
|
||
isCharacterReferenceMenuOpen={isCharacterReferenceMenuOpen}
|
||
setGenerateDialog={setGenerateDialog}
|
||
setIsCharacterSpecMenuOpen={setIsCharacterSpecMenuOpen}
|
||
setIsCharacterReferenceMenuOpen={setIsCharacterReferenceMenuOpen}
|
||
setIsPickingCharacterSpecFromCanvas={
|
||
setIsPickingCharacterSpecFromCanvas
|
||
}
|
||
setIsPickingCharacterReferenceFromCanvas={
|
||
setIsPickingCharacterReferenceFromCanvas
|
||
}
|
||
renderEditorPortal={renderEditorPortal}
|
||
buildPortalMenuStyle={buildPortalMenuStyle}
|
||
onOpenSpecDialog={onOpenSpecDialog}
|
||
onRequestUpload={onRequestUpload}
|
||
onRememberImageModel={onRememberImageModel}
|
||
onSubmit={onSubmitImageGeneration}
|
||
/>
|
||
) : null}
|
||
|
||
{generateDialog?.mode === 'icon' &&
|
||
generateDialog.composerOpen !== false &&
|
||
iconComposerStyle ? (
|
||
<ImageCanvasIconSpritesheetComposerView
|
||
dialog={generateDialog}
|
||
style={iconComposerStyle}
|
||
iconSpecButtonRef={iconSpecButtonRef}
|
||
isIconSpecMenuOpen={isIconSpecMenuOpen}
|
||
setGenerateDialog={setGenerateDialog}
|
||
setIsIconSpecMenuOpen={setIsIconSpecMenuOpen}
|
||
setIsPickingIconSpecFromCanvas={setIsPickingIconSpecFromCanvas}
|
||
renderEditorPortal={renderEditorPortal}
|
||
buildPortalMenuStyle={buildPortalMenuStyle}
|
||
onOpenSpecDialog={onOpenSpecDialog}
|
||
onRequestUpload={onRequestUpload}
|
||
onUpdateIconDescription={onUpdateIconDescription}
|
||
onAddIconDescription={onAddIconDescription}
|
||
onRememberImageModel={onRememberImageModel}
|
||
onSubmit={onSubmitIconSpritesheetGeneration}
|
||
/>
|
||
) : null}
|
||
|
||
{isPickingCharacterSpecFromCanvas ? (
|
||
<div className="image-canvas-editor__canvas-pick-hint">
|
||
请选择画布中的图片作为角色形象规范,按 Esc 退出
|
||
</div>
|
||
) : null}
|
||
{isPickingCharacterReferenceFromCanvas ? (
|
||
<div className="image-canvas-editor__canvas-pick-hint">
|
||
请选择画布中的图片作为常规参考图,按 Esc 退出
|
||
</div>
|
||
) : null}
|
||
{isPickingGenerationReferenceFromCanvas ? (
|
||
<div className="image-canvas-editor__canvas-pick-hint">
|
||
请选择画布中的图片作为参考图,按 Esc 退出
|
||
</div>
|
||
) : null}
|
||
{isPickingIconSpecFromCanvas ? (
|
||
<div className="image-canvas-editor__canvas-pick-hint">
|
||
请选择画布中的图标素材规范,按 Esc 退出
|
||
</div>
|
||
) : null}
|
||
{isPickingUiDesignSpecFromCanvas ? (
|
||
<div className="image-canvas-editor__canvas-pick-hint">
|
||
请选择画布中的图标素材规范,按 Esc 退出
|
||
</div>
|
||
) : null}
|
||
|
||
{quickEditPanel &&
|
||
quickEditPanel.status !== 'generating' &&
|
||
quickEditSourceLayer &&
|
||
quickEditPanelStyle ? (
|
||
<ImageCanvasQuickEditPanelView
|
||
panel={quickEditPanel}
|
||
sourceLayer={quickEditSourceLayer}
|
||
style={quickEditPanelStyle}
|
||
sizeOptions={quickEditSizeOptions}
|
||
modelOptions={quickEditModelOptions}
|
||
setQuickEditPanel={setQuickEditPanel}
|
||
onSubmit={onSubmitQuickEdit}
|
||
/>
|
||
) : null}
|
||
|
||
{characterAnimationPanel &&
|
||
characterAnimationSourceLayer &&
|
||
characterAnimationPanelStyle ? (
|
||
<ImageCanvasCharacterAnimationPanelView
|
||
panel={characterAnimationPanel}
|
||
style={characterAnimationPanelStyle}
|
||
price={characterAnimationPrice}
|
||
setCharacterAnimationPanel={setCharacterAnimationPanel}
|
||
onUpdateDuration={onUpdateCharacterAnimationDuration}
|
||
onSubmit={onSubmitCharacterAnimation}
|
||
/>
|
||
) : null}
|
||
|
||
<ImageCanvasEditGenerationModalView
|
||
dialog={generateDialog}
|
||
setGenerateDialog={setGenerateDialog}
|
||
onSubmit={onSubmitImageGeneration}
|
||
/>
|
||
</>
|
||
);
|
||
}
|