完善画布生成面板交互
补齐普通生图参考图来源菜单和画布选择流程 接入UI设计图与视频生成面板的提交链路 让生成引用上传目标支持多种生成面板 统一图片信息弹窗断言并补充相关测试 修复图标按钮浮层锚点ref与视频生成类型契约
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { ImageIcon } from 'lucide-react';
|
||||
import {
|
||||
type CSSProperties,
|
||||
type Dispatch,
|
||||
@@ -11,6 +12,9 @@ import {
|
||||
PlatformFloatingMenu,
|
||||
PlatformFloatingMenuItem,
|
||||
} from '../common/PlatformFloatingMenu';
|
||||
import { PlatformActionButton } from '../common/PlatformActionButton';
|
||||
import { PlatformIconButton } from '../common/PlatformIconButton';
|
||||
import { PlatformTextField } from '../common/PlatformTextField';
|
||||
import { ImageCanvasBasicGenerationComposerView } from './ImageCanvasBasicGenerationComposerView';
|
||||
import { ImageCanvasCharacterAnimationPanelView } from './ImageCanvasCharacterAnimationPanelView';
|
||||
import { ImageCanvasCharacterGenerationComposerView } from './ImageCanvasCharacterGenerationComposerView';
|
||||
@@ -18,7 +22,10 @@ import { ImageCanvasEditGenerationModalView } from './ImageCanvasEditGenerationM
|
||||
import { ImageCanvasIconSpritesheetComposerView } from './ImageCanvasIconSpritesheetComposerView';
|
||||
import { ImageCanvasQuickEditPanelView } from './ImageCanvasQuickEditPanelView';
|
||||
import { ImageCanvasSpecGenerationPanelView } from './ImageCanvasSpecGenerationPanelView';
|
||||
import { SPEC_TYPE_LABEL } from './ImageCanvasGenerationModel';
|
||||
import {
|
||||
SPEC_TYPE_LABEL,
|
||||
calculateEditorVideoPrice,
|
||||
} from './ImageCanvasGenerationModel';
|
||||
import type {
|
||||
CharacterAnimationPanelState,
|
||||
CanvasLayer,
|
||||
@@ -33,14 +40,19 @@ 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;
|
||||
@@ -58,12 +70,16 @@ type ImageCanvasGenerationComposerViewProps = {
|
||||
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;
|
||||
@@ -116,18 +132,153 @@ function renderEditorPortal(node: ReactNode) {
|
||||
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 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">
|
||||
<span className="image-canvas-editor__generation-ratio">
|
||||
16:9 · {resolution} · {durationSeconds}秒
|
||||
</span>
|
||||
<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,
|
||||
@@ -143,12 +294,16 @@ export function ImageCanvasGenerationComposerView({
|
||||
setGenerateDialog,
|
||||
setQuickEditPanel,
|
||||
setCharacterAnimationPanel,
|
||||
setIsGenerationReferenceMenuOpen,
|
||||
setIsCharacterSpecMenuOpen,
|
||||
setIsCharacterReferenceMenuOpen,
|
||||
setIsIconSpecMenuOpen,
|
||||
setIsUiDesignSpecMenuOpen,
|
||||
setIsPickingGenerationReferenceFromCanvas,
|
||||
setIsPickingCharacterSpecFromCanvas,
|
||||
setIsPickingCharacterReferenceFromCanvas,
|
||||
setIsPickingIconSpecFromCanvas,
|
||||
setIsPickingUiDesignSpecFromCanvas,
|
||||
onOpenSpecDialog,
|
||||
onRequestUpload,
|
||||
onSubmitImageGeneration,
|
||||
@@ -192,7 +347,18 @@ export function ImageCanvasGenerationComposerView({
|
||||
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}
|
||||
/>
|
||||
@@ -204,12 +370,58 @@ export function ImageCanvasGenerationComposerView({
|
||||
<ImageCanvasSpecGenerationPanelView
|
||||
dialog={generateDialog}
|
||||
style={generationComposerStyle}
|
||||
isGenerationReferenceMenuOpen={isGenerationReferenceMenuOpen}
|
||||
generationReferenceButtonRef={generationReferenceButtonRef}
|
||||
setIsGenerationReferenceMenuOpen={setIsGenerationReferenceMenuOpen}
|
||||
setIsPickingGenerationReferenceFromCanvas={
|
||||
setIsPickingGenerationReferenceFromCanvas
|
||||
}
|
||||
renderEditorPortal={renderEditorPortal}
|
||||
buildPortalMenuStyle={buildPortalMenuStyle}
|
||||
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}
|
||||
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}
|
||||
@@ -268,11 +480,21 @@ export function ImageCanvasGenerationComposerView({
|
||||
请选择画布中的图片作为常规参考图,按 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' &&
|
||||
|
||||
Reference in New Issue
Block a user