修复画布任务列表服务端任务显示
任务列表只使用服务端返回的生成任务。 保留生成任务水合后的开始和结束时间。 同步任务列表与画布阶段视图测试。
This commit is contained in:
@@ -569,6 +569,8 @@ describe('ImageCanvasEditorModel', () => {
|
||||
composerOpen: false,
|
||||
generatedLayerId: 'layer-generated',
|
||||
imageModel: 'gpt-image-2',
|
||||
generationStartedAt: 1_771_400_000_000,
|
||||
generationFinishedAt: 1_771_400_004_000,
|
||||
placeholder: {
|
||||
x: 100,
|
||||
y: 120,
|
||||
@@ -624,6 +626,8 @@ describe('ImageCanvasEditorModel', () => {
|
||||
status: 'generating',
|
||||
generatedLayerId: 'layer-generated',
|
||||
imageModel: 'gpt-image-2',
|
||||
generationStartedAt: 1_771_400_000_000,
|
||||
generationFinishedAt: 1_771_400_004_000,
|
||||
placeholder: {
|
||||
x: 100,
|
||||
y: 120,
|
||||
|
||||
@@ -592,6 +592,8 @@ export function hydrateCanvasGenerationDialog(
|
||||
aspectRatio: stringOrUndefined(snapshot.aspectRatio),
|
||||
imageSize: stringOrUndefined(snapshot.imageSize),
|
||||
errorMessage: stringOrUndefined(snapshot.errorMessage),
|
||||
generationStartedAt: numberOrUndefined(snapshot.generationStartedAt),
|
||||
generationFinishedAt: numberOrUndefined(snapshot.generationFinishedAt),
|
||||
placeholder: hydrateGenerationPlaceholder(snapshot.placeholder),
|
||||
};
|
||||
}
|
||||
@@ -823,6 +825,12 @@ export function numberFromSnapshot(value: unknown, fallback: number) {
|
||||
return typeof value === 'number' && Number.isFinite(value) ? value : fallback;
|
||||
}
|
||||
|
||||
function numberOrUndefined(value: unknown) {
|
||||
return typeof value === 'number' && Number.isFinite(value)
|
||||
? value
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function audioDurationOrNull(value: unknown) {
|
||||
if (value && typeof value === 'object' && 'durationSeconds' in value) {
|
||||
return audioDurationOrNull(
|
||||
|
||||
@@ -133,7 +133,6 @@ function createStageProps(): ImageCanvasStageViewProps {
|
||||
hoveredLayerId: null,
|
||||
canvasMarquee: null,
|
||||
canvasGenerationDialogs: [],
|
||||
backgroundRemovalTasks: [],
|
||||
isTaskSidebarOpen: false,
|
||||
generateDialog: null,
|
||||
cropExpandPanel: null,
|
||||
@@ -176,8 +175,6 @@ function createStageProps(): ImageCanvasStageViewProps {
|
||||
onUpdateLayerAssetKind: vi.fn(),
|
||||
onGenerationFramePointerDown: vi.fn(),
|
||||
onActivateGenerationDialog: vi.fn(),
|
||||
onFocusGenerationDialog: vi.fn(),
|
||||
onFocusTaskLayer: vi.fn(),
|
||||
onFocusExternalTask: vi.fn(),
|
||||
onToggleTaskSidebar: vi.fn(),
|
||||
onCropExpandHandlePointerDown: vi.fn(),
|
||||
|
||||
@@ -1678,7 +1678,6 @@ export function ImageCanvasEditorView() {
|
||||
hoveredLayerId,
|
||||
canvasMarquee,
|
||||
canvasGenerationDialogs,
|
||||
backgroundRemovalTasks: generationSurface.backgroundRemovalTasks,
|
||||
isTaskSidebarOpen: generationSurface.isTaskSidebarOpen,
|
||||
generateDialog,
|
||||
cropExpandPanel: generationSurface.cropExpandPanel,
|
||||
@@ -1726,8 +1725,6 @@ export function ImageCanvasEditorView() {
|
||||
onUpdateLayerAssetKind: updateLayerAssetKind,
|
||||
onGenerationFramePointerDown: handleGenerationFramePointerDown,
|
||||
onActivateGenerationDialog: activateCanvasGenerationDialog,
|
||||
onFocusGenerationDialog: focusCanvasGenerationDialog,
|
||||
onFocusTaskLayer: focusCanvasLayerById,
|
||||
onFocusExternalTask: focusExternalGenerationTask,
|
||||
onToggleTaskSidebar: generationSurface.toggleTaskSidebar,
|
||||
onCropExpandHandlePointerDown: generationSurface.startCropExpandFrameResize,
|
||||
|
||||
@@ -17,7 +17,6 @@ import type {
|
||||
CanvasClipboard,
|
||||
CanvasContextMenuState,
|
||||
CanvasGenerationDialogState,
|
||||
CanvasTaskListItem,
|
||||
CanvasLayer,
|
||||
CanvasMarqueeState,
|
||||
CanvasTool,
|
||||
@@ -58,7 +57,6 @@ export type ImageCanvasStageViewProps = {
|
||||
hoveredLayerId: string | null;
|
||||
canvasMarquee: CanvasMarqueeState | null;
|
||||
canvasGenerationDialogs: CanvasGenerationDialogState[];
|
||||
backgroundRemovalTasks: CanvasTaskListItem[];
|
||||
isTaskSidebarOpen: boolean;
|
||||
generateDialog: GenerateDialogState | null;
|
||||
cropExpandPanel: CropExpandPanelState | null;
|
||||
@@ -117,8 +115,6 @@ export type ImageCanvasStageViewProps = {
|
||||
dialog: CanvasGenerationDialogState,
|
||||
) => void;
|
||||
onActivateGenerationDialog: (dialog: CanvasGenerationDialogState) => void;
|
||||
onFocusGenerationDialog: (dialog: CanvasGenerationDialogState) => void;
|
||||
onFocusTaskLayer: (layerId: string) => void;
|
||||
onFocusExternalTask: (task: ExternalGenerationTaskRecord) => void;
|
||||
onToggleTaskSidebar: () => void;
|
||||
onCropExpandHandlePointerDown: (
|
||||
@@ -199,7 +195,6 @@ export function ImageCanvasStageView({
|
||||
hoveredLayerId,
|
||||
canvasMarquee,
|
||||
canvasGenerationDialogs,
|
||||
backgroundRemovalTasks,
|
||||
isTaskSidebarOpen,
|
||||
generateDialog,
|
||||
cropExpandPanel,
|
||||
@@ -243,8 +238,6 @@ export function ImageCanvasStageView({
|
||||
onUpdateLayerAssetKind,
|
||||
onGenerationFramePointerDown,
|
||||
onActivateGenerationDialog,
|
||||
onFocusGenerationDialog,
|
||||
onFocusTaskLayer,
|
||||
onFocusExternalTask,
|
||||
onToggleTaskSidebar,
|
||||
onCropExpandHandlePointerDown,
|
||||
@@ -445,12 +438,8 @@ export function ImageCanvasStageView({
|
||||
|
||||
<ImageCanvasTaskSidebarView
|
||||
projectId={projectId}
|
||||
generationDialogs={canvasGenerationDialogs}
|
||||
backgroundRemovalTasks={backgroundRemovalTasks}
|
||||
open={isTaskSidebarOpen}
|
||||
onToggleOpen={onToggleTaskSidebar}
|
||||
onFocusGenerationDialog={onFocusGenerationDialog}
|
||||
onFocusLayer={onFocusTaskLayer}
|
||||
onFocusExternalTask={onFocusExternalTask}
|
||||
/>
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import type { ExternalGenerationTaskRecord } from '../../../packages/shared/src/contracts/externalGeneration';
|
||||
import { listExternalGenerationTasks } from '../../services/external-generation';
|
||||
import { ImageCanvasTaskSidebarView } from './ImageCanvasTaskSidebarView';
|
||||
import type { CanvasGenerationDialogState } from './ImageCanvasEditorTypes';
|
||||
|
||||
vi.mock('../../services/external-generation', () => ({
|
||||
listExternalGenerationTasks: vi.fn().mockResolvedValue({
|
||||
@@ -35,27 +34,6 @@ beforeEach(() => {
|
||||
});
|
||||
});
|
||||
|
||||
function createDialog(
|
||||
overrides: Partial<CanvasGenerationDialogState>,
|
||||
): CanvasGenerationDialogState {
|
||||
return {
|
||||
id: overrides.id ?? 'dialog-1',
|
||||
mode: overrides.mode ?? 'generate',
|
||||
prompt: overrides.prompt ?? '',
|
||||
status: overrides.status ?? 'idle',
|
||||
composerOpen: false,
|
||||
placeholder: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 320,
|
||||
height: 320,
|
||||
originalWidth: 1024,
|
||||
originalHeight: 1024,
|
||||
},
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function createExternalTask(
|
||||
overrides: Partial<ExternalGenerationTaskRecord> = {},
|
||||
): ExternalGenerationTaskRecord {
|
||||
@@ -85,40 +63,53 @@ function createExternalTask(
|
||||
|
||||
describe('ImageCanvasTaskSidebarView', () => {
|
||||
it('groups tasks by tabs and keeps running tasks before queued tasks', async () => {
|
||||
const focusGenerationDialog = vi.fn();
|
||||
const focusExternalTask = vi.fn();
|
||||
const parentWheel = vi.fn();
|
||||
const completedAt = new Date(Date.now() - 1000).toISOString();
|
||||
listExternalGenerationTasksMock.mockImplementation(
|
||||
(options: Parameters<typeof listExternalGenerationTasks>[0] = {}) =>
|
||||
Promise.resolve({
|
||||
overview: {
|
||||
pendingCount: 1,
|
||||
runningCount: 1,
|
||||
unacknowledgedTerminalCount: 1,
|
||||
updatedAtMicros: 1,
|
||||
},
|
||||
tasks: options.statuses?.includes('running')
|
||||
? [
|
||||
createExternalTask({
|
||||
jobId: 'queued-task',
|
||||
requestLabel: '图标素材',
|
||||
status: 'queued',
|
||||
}),
|
||||
createExternalTask({
|
||||
jobId: 'running-task',
|
||||
requestLabel: '角色图片',
|
||||
status: 'running',
|
||||
startedAt: new Date(Date.now() - 3000).toISOString(),
|
||||
}),
|
||||
]
|
||||
: [
|
||||
createExternalTask({
|
||||
jobId: 'done-task',
|
||||
requestLabel: '视频生成',
|
||||
requestPrompt: '生成一段开场动画',
|
||||
status: 'completed',
|
||||
startedAt: new Date(Date.now() - 4000).toISOString(),
|
||||
completedAt,
|
||||
updatedAt: completedAt,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
render(
|
||||
<div onWheel={parentWheel}>
|
||||
<ImageCanvasTaskSidebarView
|
||||
projectId="project-1"
|
||||
generationDialogs={[
|
||||
createDialog({
|
||||
id: 'queued-dialog',
|
||||
mode: 'icon',
|
||||
status: 'idle',
|
||||
}),
|
||||
createDialog({
|
||||
id: 'running-dialog',
|
||||
mode: 'character',
|
||||
status: 'generating',
|
||||
generationStartedAt: Date.now() - 3000,
|
||||
}),
|
||||
createDialog({
|
||||
id: 'done-dialog',
|
||||
mode: 'video',
|
||||
prompt: '生成一段开场动画',
|
||||
status: 'idle',
|
||||
generatedLayerId: 'layer-video',
|
||||
generationStartedAt: Date.now() - 4000,
|
||||
generationFinishedAt: Date.now() - 1000,
|
||||
}),
|
||||
]}
|
||||
backgroundRemovalTasks={[]}
|
||||
open
|
||||
onToggleOpen={vi.fn()}
|
||||
onFocusGenerationDialog={focusGenerationDialog}
|
||||
onFocusLayer={vi.fn()}
|
||||
onFocusExternalTask={vi.fn()}
|
||||
onFocusExternalTask={focusExternalTask}
|
||||
/>
|
||||
</div>,
|
||||
);
|
||||
@@ -149,11 +140,18 @@ describe('ImageCanvasTaskSidebarView', () => {
|
||||
.filter((text) => text.includes('角色图片') || text.includes('图标素材'));
|
||||
expect(activeTitles[0]).toContain('角色图片');
|
||||
expect(activeTitles[1]).toContain('图标素材');
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen
|
||||
.getByRole('tab', { name: /排队\/生成中/u })
|
||||
.getAttribute('aria-selected'),
|
||||
).toBe('true'),
|
||||
);
|
||||
|
||||
const completedTab = screen.getByRole('tab', { name: /已完成/u });
|
||||
fireEvent.click(completedTab);
|
||||
expect(screen.queryByText('视频生成')).not.toBeNull();
|
||||
expect(screen.queryByText(/生成一段开场动画/u)).not.toBeNull();
|
||||
expect(await screen.findByText('视频生成')).toBeTruthy();
|
||||
expect(screen.getByText(/生成一段开场动画/u)).toBeTruthy();
|
||||
expect(screen.queryByText(/完成于/u)).not.toBeNull();
|
||||
expect(screen.queryByText(/生成完成/u)).toBeNull();
|
||||
|
||||
@@ -161,8 +159,8 @@ describe('ImageCanvasTaskSidebarView', () => {
|
||||
expect(parentWheel).not.toHaveBeenCalled();
|
||||
|
||||
fireEvent.click(screen.getByText('视频生成'));
|
||||
expect(focusGenerationDialog).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ id: 'done-dialog' }),
|
||||
expect(focusExternalTask).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ jobId: 'done-task' }),
|
||||
);
|
||||
expect(completedTab.getAttribute('aria-selected')).toBe('true');
|
||||
});
|
||||
@@ -216,12 +214,8 @@ describe('ImageCanvasTaskSidebarView', () => {
|
||||
render(
|
||||
<ImageCanvasTaskSidebarView
|
||||
projectId="project-1"
|
||||
generationDialogs={[]}
|
||||
backgroundRemovalTasks={[]}
|
||||
open
|
||||
onToggleOpen={vi.fn()}
|
||||
onFocusGenerationDialog={vi.fn()}
|
||||
onFocusLayer={vi.fn()}
|
||||
onFocusExternalTask={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
@@ -231,9 +225,16 @@ describe('ImageCanvasTaskSidebarView', () => {
|
||||
).toBeTruthy();
|
||||
expect(screen.getByText(/已用时 1分/u)).toBeTruthy();
|
||||
expect(screen.queryByText(/不该显示的外部项目任务/u)).toBeNull();
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen
|
||||
.getByRole('tab', { name: /排队\/生成中/u })
|
||||
.getAttribute('aria-selected'),
|
||||
).toBe('true'),
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole('tab', { name: /已完成/u }));
|
||||
expect(screen.getByText(/已完成提示词/u)).toBeTruthy();
|
||||
expect(await screen.findByText(/已完成提示词/u)).toBeTruthy();
|
||||
expect(screen.getByText(/完成于/u)).toBeTruthy();
|
||||
expect(screen.queryByText(/另一个项目的已完成任务/u)).toBeNull();
|
||||
});
|
||||
@@ -266,12 +267,8 @@ describe('ImageCanvasTaskSidebarView', () => {
|
||||
render(
|
||||
<ImageCanvasTaskSidebarView
|
||||
projectId="project-1"
|
||||
generationDialogs={[]}
|
||||
backgroundRemovalTasks={[]}
|
||||
open
|
||||
onToggleOpen={vi.fn()}
|
||||
onFocusGenerationDialog={vi.fn()}
|
||||
onFocusLayer={vi.fn()}
|
||||
onFocusExternalTask={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
@@ -17,11 +17,7 @@ import {
|
||||
import type { ExternalGenerationTaskRecord } from '../../../packages/shared/src/contracts/externalGeneration';
|
||||
import { listExternalGenerationTasks } from '../../services/external-generation';
|
||||
import { EditorIconButton } from './ImageCanvasEditorPrimitives';
|
||||
import type {
|
||||
CanvasGenerationDialogState,
|
||||
CanvasTaskListItem,
|
||||
CanvasTaskStatus,
|
||||
} from './ImageCanvasEditorTypes';
|
||||
import type { CanvasTaskStatus } from './ImageCanvasEditorTypes';
|
||||
|
||||
const COMPLETED_TASK_LIST_LIMIT = 20;
|
||||
const ACTIVE_TASK_LIST_LIMIT = 100;
|
||||
@@ -44,12 +40,8 @@ type TaskSidebarItem = {
|
||||
|
||||
type ImageCanvasTaskSidebarViewProps = {
|
||||
projectId?: string | null;
|
||||
generationDialogs: CanvasGenerationDialogState[];
|
||||
backgroundRemovalTasks: CanvasTaskListItem[];
|
||||
open: boolean;
|
||||
onToggleOpen: () => void;
|
||||
onFocusGenerationDialog: (dialog: CanvasGenerationDialogState) => void;
|
||||
onFocusLayer: (layerId: string) => void;
|
||||
onFocusExternalTask: (task: ExternalGenerationTaskRecord) => void;
|
||||
};
|
||||
|
||||
@@ -85,58 +77,11 @@ function formatTaskTimeMs(timestampMs?: number) {
|
||||
)}:${pad(date.getMinutes())}`;
|
||||
}
|
||||
|
||||
function generationDialogTitle(dialog: CanvasGenerationDialogState) {
|
||||
switch (dialog.mode) {
|
||||
case 'quick-edit':
|
||||
return '局部修改';
|
||||
case 'character-animation':
|
||||
return '角色动画';
|
||||
case 'video':
|
||||
return '视频生成';
|
||||
case 'audio-sound-effect':
|
||||
return '音效生成';
|
||||
case 'audio-background-music':
|
||||
return '背景音乐';
|
||||
case 'icon':
|
||||
return '图标素材';
|
||||
case 'ui-design':
|
||||
return 'UI素材提取';
|
||||
case 'publication':
|
||||
return '宣发素材';
|
||||
case 'character':
|
||||
return '角色图片';
|
||||
case 'spec':
|
||||
return '图片规范';
|
||||
default:
|
||||
return '图片生成';
|
||||
}
|
||||
}
|
||||
|
||||
function generationDialogDetail(dialog: CanvasGenerationDialogState) {
|
||||
if (dialog.status === 'generating') {
|
||||
return '生成中';
|
||||
}
|
||||
if (dialog.status === 'failed') {
|
||||
return dialog.errorMessage || '生成失败';
|
||||
}
|
||||
return dialog.generatedLayerId ? '生成完成' : '待提交';
|
||||
}
|
||||
|
||||
function normalizeTaskPrompt(value?: string | null) {
|
||||
const prompt = value?.trim();
|
||||
return prompt || null;
|
||||
}
|
||||
|
||||
function generationDialogTaskDetail(
|
||||
dialog: CanvasGenerationDialogState,
|
||||
status: CanvasTaskStatus,
|
||||
) {
|
||||
if (status === 'done') {
|
||||
return normalizeTaskPrompt(dialog.prompt) ?? generationDialogTitle(dialog);
|
||||
}
|
||||
return generationDialogDetail(dialog);
|
||||
}
|
||||
|
||||
function externalTaskDetail(
|
||||
task: ExternalGenerationTaskRecord,
|
||||
status: CanvasTaskStatus,
|
||||
@@ -183,7 +128,7 @@ function canvasTaskStatusFromExternalTask(
|
||||
|
||||
function statusLabel(status: CanvasTaskStatus) {
|
||||
if (status === 'pending') {
|
||||
return '待提交';
|
||||
return '排队中';
|
||||
}
|
||||
if (status === 'running') {
|
||||
return '进行中';
|
||||
@@ -267,12 +212,8 @@ function mergeExternalTaskRefresh(
|
||||
|
||||
export function ImageCanvasTaskSidebarView({
|
||||
projectId,
|
||||
generationDialogs,
|
||||
backgroundRemovalTasks,
|
||||
open,
|
||||
onToggleOpen,
|
||||
onFocusGenerationDialog,
|
||||
onFocusLayer,
|
||||
onFocusExternalTask,
|
||||
}: ImageCanvasTaskSidebarViewProps) {
|
||||
const [activeTab, setActiveTab] = useState<TaskSidebarTab>('active');
|
||||
@@ -280,7 +221,6 @@ export function ImageCanvasTaskSidebarView({
|
||||
const [externalTasks, setExternalTasks] = useState<
|
||||
ExternalGenerationTaskRecord[]
|
||||
>([]);
|
||||
const observedStartedAtRef = useRef(new Map<string, number>());
|
||||
const previousActiveItemCountRef = useRef(0);
|
||||
const filterVisibleExternalTasks = useCallback(
|
||||
(tasks: ExternalGenerationTaskRecord[]) =>
|
||||
@@ -395,13 +335,16 @@ export function ImageCanvasTaskSidebarView({
|
||||
window.clearTimeout(timerId);
|
||||
}
|
||||
};
|
||||
}, [activeExternalTaskIds, activeExternalTaskKey, filterVisibleExternalTasks]);
|
||||
}, [
|
||||
activeExternalTaskIds,
|
||||
activeExternalTaskKey,
|
||||
filterVisibleExternalTasks,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const hasRunningTask =
|
||||
generationDialogs.some((dialog) => dialog.status === 'generating') ||
|
||||
backgroundRemovalTasks.some((task) => task.status === 'running') ||
|
||||
externalTasks.some((task) => task.status === 'running');
|
||||
const hasRunningTask = externalTasks.some(
|
||||
(task) => task.status === 'running',
|
||||
);
|
||||
if (!hasRunningTask) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -409,66 +352,9 @@ export function ImageCanvasTaskSidebarView({
|
||||
setNow(Date.now());
|
||||
}, 1000);
|
||||
return () => window.clearInterval(timerId);
|
||||
}, [backgroundRemovalTasks, externalTasks, generationDialogs]);
|
||||
}, [externalTasks]);
|
||||
|
||||
const items = useMemo<TaskSidebarItem[]>(() => {
|
||||
const resolveStartedAt = (itemId: string, startedAt?: number) => {
|
||||
if (typeof startedAt === 'number' && Number.isFinite(startedAt)) {
|
||||
return startedAt;
|
||||
}
|
||||
const observedStartedAt = observedStartedAtRef.current.get(itemId);
|
||||
if (observedStartedAt) {
|
||||
return observedStartedAt;
|
||||
}
|
||||
observedStartedAtRef.current.set(itemId, now);
|
||||
return now;
|
||||
};
|
||||
const generationItems: TaskSidebarItem[] = generationDialogs.map(
|
||||
(dialog) => {
|
||||
const status =
|
||||
dialog.status === 'generating'
|
||||
? 'running'
|
||||
: dialog.status === 'failed'
|
||||
? 'failed'
|
||||
: dialog.generatedLayerId
|
||||
? 'done'
|
||||
: 'pending';
|
||||
const id = `generation-${dialog.id}`;
|
||||
const startedAt =
|
||||
status === 'running'
|
||||
? resolveStartedAt(id, dialog.generationStartedAt)
|
||||
: dialog.generationStartedAt;
|
||||
const finishedAt = dialog.generationFinishedAt;
|
||||
return {
|
||||
id,
|
||||
title: generationDialogTitle(dialog),
|
||||
detail: generationDialogTaskDetail(dialog, status),
|
||||
status,
|
||||
startedAt,
|
||||
finishedAt,
|
||||
elapsedMs:
|
||||
status === 'running' && startedAt
|
||||
? now - startedAt
|
||||
: startedAt && finishedAt
|
||||
? finishedAt - startedAt
|
||||
: undefined,
|
||||
errorMessage: dialog.errorMessage,
|
||||
onClick: () => onFocusGenerationDialog(dialog),
|
||||
sortTime: finishedAt ?? startedAt ?? 0,
|
||||
};
|
||||
},
|
||||
);
|
||||
const backgroundItems: TaskSidebarItem[] = backgroundRemovalTasks.map(
|
||||
(task) => ({
|
||||
...task,
|
||||
elapsedMs:
|
||||
task.status === 'running' ? now - task.startedAt : task.elapsedMs,
|
||||
onClick: task.focusLayerId
|
||||
? () => onFocusLayer(task.focusLayerId!)
|
||||
: undefined,
|
||||
sortTime: task.finishedAt ?? task.startedAt,
|
||||
}),
|
||||
);
|
||||
const externalVisibleTasks = mergeExternalTasks(
|
||||
externalTasks.filter(
|
||||
(task) => task.status === 'queued' || task.status === 'running',
|
||||
@@ -515,16 +401,8 @@ export function ImageCanvasTaskSidebarView({
|
||||
0,
|
||||
};
|
||||
});
|
||||
return [...backgroundItems, ...generationItems, ...externalItems];
|
||||
}, [
|
||||
backgroundRemovalTasks,
|
||||
externalTasks,
|
||||
generationDialogs,
|
||||
now,
|
||||
onFocusExternalTask,
|
||||
onFocusGenerationDialog,
|
||||
onFocusLayer,
|
||||
]);
|
||||
return externalItems;
|
||||
}, [externalTasks, now, onFocusExternalTask]);
|
||||
|
||||
const activeItems = useMemo(
|
||||
() =>
|
||||
@@ -597,7 +475,10 @@ export function ImageCanvasTaskSidebarView({
|
||||
<strong>任务列表</strong>
|
||||
<span>{items.length}</span>
|
||||
</div>
|
||||
<div className="image-canvas-editor__task-sidebar-tabs" role="tablist">
|
||||
<div
|
||||
className="image-canvas-editor__task-sidebar-tabs"
|
||||
role="tablist"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
|
||||
Reference in New Issue
Block a user