Files
Genarrative/src/components/image-editor/useCanvasGenerationDialogs.ts
T
JenkenB ea29887d9c 完善快速编辑参考图与生成占位
- 快速编辑支持最多8张额外参考图,并将原图自动作为最后一张参考图提交

- 快速编辑尺寸和模型沿用原图配置,按禁用态参数胶囊展示

- 快速编辑提交前同步改写原图引用为实际图序号

- 快速编辑生成改为创建独立画布生成占位,并支持键盘删除生成中对象

- 对齐生成类面板参考图图标、规范类固定参数和相关文档测试
2026-06-18 22:50:42 +08:00

268 lines
8.4 KiB
TypeScript

import { useCallback, useMemo, useRef, useState } from 'react';
import { isCanvasGenerationDialog } from './ImageCanvasGenerationModel';
import type {
CanvasGenerationDialogState,
GenerateDialogState,
} from './ImageCanvasEditorTypes';
type CanvasGenerationDialogUpdater = (
dialog: CanvasGenerationDialogState,
) => CanvasGenerationDialogState | null;
export function useCanvasGenerationDialogs({
onActivate,
}: {
onActivate?: () => void;
} = {}) {
const generationDialogCounterRef = useRef(0);
const generateDialogRef = useRef<GenerateDialogState | null>(null);
const inactiveGenerateDialogsRef = useRef<CanvasGenerationDialogState[]>([]);
const [generateDialog, setGenerateDialog] =
useState<GenerateDialogState | null>(null);
const [inactiveGenerateDialogs, setInactiveGenerateDialogs] = useState<
CanvasGenerationDialogState[]
>([]);
generateDialogRef.current = generateDialog;
inactiveGenerateDialogsRef.current = inactiveGenerateDialogs;
const activeCanvasGenerationDialog = isCanvasGenerationDialog(generateDialog)
? generateDialog
: null;
const canvasGenerationDialogs = useMemo(
() =>
activeCanvasGenerationDialog
? [...inactiveGenerateDialogs, activeCanvasGenerationDialog]
: inactiveGenerateDialogs,
[activeCanvasGenerationDialog, inactiveGenerateDialogs],
);
const createGenerationDialogId = useCallback(() => {
generationDialogCounterRef.current += 1;
return `generation-dialog-${generationDialogCounterRef.current}`;
}, []);
const archiveActiveCanvasGenerationDialog = useCallback(() => {
const currentDialog = generateDialogRef.current;
if (!isCanvasGenerationDialog(currentDialog)) {
return;
}
setInactiveGenerateDialogs((currentDialogs) =>
currentDialogs.some((dialog) => dialog.id === currentDialog.id)
? currentDialogs
: [
...currentDialogs,
{
...currentDialog,
composerOpen: false,
},
],
);
}, []);
const openCanvasGenerationDialog = useCallback(
(dialog: Omit<CanvasGenerationDialogState, 'id'>) => {
const currentDialog = generateDialogRef.current;
if (isCanvasGenerationDialog(currentDialog)) {
inactiveGenerateDialogsRef.current =
inactiveGenerateDialogsRef.current.some(
(existingDialog) => existingDialog.id === currentDialog.id,
)
? inactiveGenerateDialogsRef.current
: [
...inactiveGenerateDialogsRef.current,
{
...currentDialog,
composerOpen: false,
},
];
}
archiveActiveCanvasGenerationDialog();
const id = createGenerationDialogId();
const nextDialog = {
...dialog,
id,
};
generateDialogRef.current = nextDialog;
setGenerateDialog(nextDialog);
return id;
},
[archiveActiveCanvasGenerationDialog, createGenerationDialogId],
);
const updateCanvasGenerationDialogById = useCallback(
(dialogId: string, updater: CanvasGenerationDialogUpdater) => {
const currentDialogRef = generateDialogRef.current;
if (
isCanvasGenerationDialog(currentDialogRef) &&
currentDialogRef.id === dialogId
) {
generateDialogRef.current = updater(currentDialogRef);
}
inactiveGenerateDialogsRef.current =
inactiveGenerateDialogsRef.current.flatMap((dialog) => {
if (dialog.id !== dialogId) {
return [dialog];
}
const nextDialog = updater(dialog);
return nextDialog ? [nextDialog] : [];
});
setGenerateDialog((currentDialog) =>
isCanvasGenerationDialog(currentDialog) &&
currentDialog.id === dialogId
? updater(currentDialog)
: currentDialog,
);
setInactiveGenerateDialogs((currentDialogs) =>
currentDialogs.flatMap((dialog) => {
if (dialog.id !== dialogId) {
return [dialog];
}
const nextDialog = updater(dialog);
return nextDialog ? [nextDialog] : [];
}),
);
},
[],
);
const removeCanvasGenerationDialogById = useCallback(
(dialogId: string) => {
updateCanvasGenerationDialogById(dialogId, () => null);
},
[updateCanvasGenerationDialogById],
);
const hasCanvasGenerationDialogById = useCallback((dialogId: string) => {
const currentDialog = generateDialogRef.current;
return [
...(isCanvasGenerationDialog(currentDialog) ? [currentDialog] : []),
...inactiveGenerateDialogsRef.current,
].some((dialog) => dialog.id === dialogId);
}, []);
const activateCanvasGenerationDialog = useCallback(
(targetDialog: CanvasGenerationDialogState) => {
setInactiveGenerateDialogs((currentDialogs) => {
const nextDialogs = currentDialogs.filter(
(dialog) => dialog.id !== targetDialog.id,
);
const currentDialog = generateDialogRef.current;
if (
isCanvasGenerationDialog(currentDialog) &&
currentDialog.id !== targetDialog.id
) {
nextDialogs.push({
...currentDialog,
composerOpen: false,
});
}
return nextDialogs;
});
setGenerateDialog({
...targetDialog,
composerOpen: true,
});
onActivate?.();
},
[onActivate],
);
const restoreCanvasGenerationDialogs = useCallback(
(dialogs: CanvasGenerationDialogState[]) => {
const nextCounter = dialogs.reduce((maxCounter, dialog) => {
const match = /^generation-dialog-(\d+)$/u.exec(dialog.id);
const numericId = match ? Number.parseInt(match[1] ?? '0', 10) : 0;
return Math.max(maxCounter, Number.isFinite(numericId) ? numericId : 0);
}, 0);
generationDialogCounterRef.current = Math.max(
generationDialogCounterRef.current,
nextCounter,
);
const activeDialog =
[...dialogs].reverse().find((dialog) => dialog.composerOpen !== false) ??
dialogs[dialogs.length - 1] ??
null;
setGenerateDialog(
activeDialog
? {
...activeDialog,
composerOpen: activeDialog.composerOpen !== false,
}
: null,
);
setInactiveGenerateDialogs(
dialogs
.filter((dialog) => dialog.id !== activeDialog?.id)
.map((dialog) => ({
...dialog,
composerOpen: false,
})),
);
},
[],
);
const removeCanvasGenerationDialogsByLayerId = useCallback(
(targetLayerId: string) => {
const keepDialog = (dialog: CanvasGenerationDialogState) =>
dialog.sourceLayerId !== targetLayerId &&
dialog.generatedLayerId !== targetLayerId;
setGenerateDialog((currentDialog) =>
isCanvasGenerationDialog(currentDialog) && !keepDialog(currentDialog)
? null
: currentDialog,
);
setInactiveGenerateDialogs((currentDialogs) =>
currentDialogs.filter(keepDialog),
);
},
[],
);
const getGeneratingDialogPlaceholder = useCallback(
(dialog: GenerateDialogState) => {
const currentDialog = generateDialogRef.current;
if (dialog.id) {
const latestDialog = [
...(isCanvasGenerationDialog(currentDialog) ? [currentDialog] : []),
...inactiveGenerateDialogsRef.current,
].find((candidateDialog) => candidateDialog.id === dialog.id);
if (latestDialog?.status === 'generating') {
return latestDialog.placeholder ?? dialog.placeholder;
}
}
if (
currentDialog?.mode === dialog.mode &&
(!dialog.id || currentDialog.id === dialog.id) &&
currentDialog.status === 'generating'
) {
return currentDialog.placeholder ?? dialog.placeholder;
}
return dialog.placeholder;
},
[],
);
return {
generateDialog,
setGenerateDialog,
generateDialogRef,
inactiveGenerateDialogs,
setInactiveGenerateDialogs,
inactiveGenerateDialogsRef,
activeCanvasGenerationDialog,
canvasGenerationDialogs,
archiveActiveCanvasGenerationDialog,
openCanvasGenerationDialog,
updateCanvasGenerationDialogById,
removeCanvasGenerationDialogById,
hasCanvasGenerationDialogById,
activateCanvasGenerationDialog,
restoreCanvasGenerationDialogs,
removeCanvasGenerationDialogsByLayerId,
getGeneratingDialogPlaceholder,
};
}