8dad52f040
新增画布Agent会话契约、领域规则、SpacetimeDB元数据表和生成绑定。 新增api-server画布Agent会话CRUD、OSS消息文档读写、SSE消息流和生成工具编排。 新增前端右侧Agent对话面板、会话状态hook、SSE客户端、BFF客户端和相关测试。 调整画布侧栏、任务栏、小地图默认状态和面板互斥交互。 补齐画布Agent、OSS消息存储、SSE收口、后端契约和项目记忆文档。
175 lines
5.4 KiB
TypeScript
175 lines
5.4 KiB
TypeScript
import { useCallback, useState } from 'react';
|
|
|
|
import { ApiClientError } from '../../services/apiClient';
|
|
import { renameEditorProject } from '../../services/image-editor/editorProjectClient';
|
|
import {
|
|
DEFAULT_CANVAS_BACKGROUND_COLOR,
|
|
normalizeCanvasBackgroundHex,
|
|
} from './ImageCanvasEditorModel';
|
|
import type { CanvasTool, SidebarPanel } from './ImageCanvasEditorTypes';
|
|
|
|
type UseImageCanvasEditorChromeOptions = {
|
|
openEditorLoginModal: (postLoginAction?: (() => void) | null) => void;
|
|
};
|
|
|
|
function isEditorAuthError(error: unknown) {
|
|
return (
|
|
error instanceof ApiClientError &&
|
|
(error.status === 401 || error.status === 403)
|
|
);
|
|
}
|
|
|
|
export function useImageCanvasEditorChrome({
|
|
openEditorLoginModal,
|
|
}: UseImageCanvasEditorChromeOptions) {
|
|
const [projectTitle, setProjectTitle] = useState('未命名画布');
|
|
const [projectRenameValue, setProjectRenameValue] = useState('未命名画布');
|
|
const [isRenamingProject, setIsRenamingProject] = useState(false);
|
|
const [isProjectRenameSaving, setIsProjectRenameSaving] = useState(false);
|
|
const [projectRenameError, setProjectRenameError] = useState<string | null>(
|
|
null,
|
|
);
|
|
const [activeSidebarPanel, setActiveSidebarPanel] =
|
|
useState<SidebarPanel | null>(null);
|
|
const [activeTool, setActiveTool] = useState<CanvasTool>('select');
|
|
const [isZoomMenuOpen, setIsZoomMenuOpen] = useState(false);
|
|
const [isBackgroundSettingsOpen, setIsBackgroundSettingsOpen] =
|
|
useState(false);
|
|
const [isMinimapOpen, setIsMinimapOpen] = useState(false);
|
|
const [canvasBackgroundColor, setCanvasBackgroundColor] = useState(
|
|
DEFAULT_CANVAS_BACKGROUND_COLOR,
|
|
);
|
|
const [canvasBackgroundHexValue, setCanvasBackgroundHexValue] = useState(
|
|
DEFAULT_CANVAS_BACKGROUND_COLOR,
|
|
);
|
|
|
|
const applyCanvasBackgroundColor = useCallback((color: string) => {
|
|
const normalizedColor = normalizeCanvasBackgroundHex(color);
|
|
if (!normalizedColor) {
|
|
return false;
|
|
}
|
|
setCanvasBackgroundColor(normalizedColor);
|
|
setCanvasBackgroundHexValue(normalizedColor);
|
|
return true;
|
|
}, []);
|
|
|
|
const handleCanvasBackgroundHexChange = useCallback((nextValue: string) => {
|
|
setCanvasBackgroundHexValue(nextValue);
|
|
const normalizedColor = normalizeCanvasBackgroundHex(nextValue);
|
|
if (normalizedColor) {
|
|
setCanvasBackgroundColor(normalizedColor);
|
|
setCanvasBackgroundHexValue(normalizedColor);
|
|
}
|
|
}, []);
|
|
|
|
const startProjectRename = useCallback(() => {
|
|
setProjectRenameValue(projectTitle);
|
|
setProjectRenameError(null);
|
|
setIsRenamingProject(true);
|
|
}, [projectTitle]);
|
|
|
|
const cancelProjectRename = useCallback(() => {
|
|
setProjectRenameValue(projectTitle);
|
|
setProjectRenameError(null);
|
|
setIsRenamingProject(false);
|
|
}, [projectTitle]);
|
|
|
|
const submitProjectRename = useCallback(
|
|
(projectId: string | null) => {
|
|
const nextTitle = projectRenameValue.trim();
|
|
if (!nextTitle) {
|
|
setProjectRenameError('项目名称不能为空');
|
|
return;
|
|
}
|
|
if (!projectId || nextTitle === projectTitle) {
|
|
setProjectRenameValue(projectTitle);
|
|
setProjectRenameError(null);
|
|
setIsRenamingProject(false);
|
|
return;
|
|
}
|
|
setIsProjectRenameSaving(true);
|
|
setProjectRenameError(null);
|
|
renameEditorProject(projectId, nextTitle)
|
|
.then((project: Awaited<ReturnType<typeof renameEditorProject>>) => {
|
|
const savedTitle = project.title?.trim() || nextTitle;
|
|
setProjectTitle(savedTitle);
|
|
setProjectRenameValue(savedTitle);
|
|
setIsRenamingProject(false);
|
|
})
|
|
.catch((error: unknown) => {
|
|
if (isEditorAuthError(error)) {
|
|
openEditorLoginModal();
|
|
}
|
|
setProjectRenameError(
|
|
error instanceof Error ? error.message : '重命名项目失败',
|
|
);
|
|
})
|
|
.finally(() => setIsProjectRenameSaving(false));
|
|
},
|
|
[openEditorLoginModal, projectRenameValue, projectTitle],
|
|
);
|
|
|
|
const resetProjectRenameError = useCallback(() => {
|
|
setProjectRenameError(null);
|
|
}, []);
|
|
|
|
const closeEditorChromePanels = useCallback(() => {
|
|
setActiveSidebarPanel(null);
|
|
setIsZoomMenuOpen(false);
|
|
setIsBackgroundSettingsOpen(false);
|
|
}, []);
|
|
|
|
const toggleSidebarPanel = useCallback((panel: SidebarPanel) => {
|
|
setActiveSidebarPanel((currentPanel) =>
|
|
currentPanel === panel ? null : panel,
|
|
);
|
|
}, []);
|
|
|
|
const toggleZoomMenu = useCallback(() => {
|
|
setIsZoomMenuOpen((open) => !open);
|
|
}, []);
|
|
|
|
const closeZoomMenu = useCallback(() => {
|
|
setIsZoomMenuOpen(false);
|
|
}, []);
|
|
|
|
const toggleBackgroundSettings = useCallback(() => {
|
|
setIsBackgroundSettingsOpen((isOpen) => !isOpen);
|
|
}, []);
|
|
|
|
const toggleMinimap = useCallback(() => {
|
|
setIsMinimapOpen((open) => !open);
|
|
}, []);
|
|
|
|
return {
|
|
projectTitle,
|
|
setProjectTitle,
|
|
projectRenameValue,
|
|
setProjectRenameValue,
|
|
isRenamingProject,
|
|
isProjectRenameSaving,
|
|
projectRenameError,
|
|
activeSidebarPanel,
|
|
setActiveSidebarPanel,
|
|
activeTool,
|
|
setActiveTool,
|
|
isZoomMenuOpen,
|
|
isBackgroundSettingsOpen,
|
|
isMinimapOpen,
|
|
canvasBackgroundColor,
|
|
canvasBackgroundHexValue,
|
|
startProjectRename,
|
|
cancelProjectRename,
|
|
submitProjectRename,
|
|
resetProjectRenameError,
|
|
applyCanvasBackgroundColor,
|
|
handleCanvasBackgroundHexChange,
|
|
closeEditorChromePanels,
|
|
toggleSidebarPanel,
|
|
toggleZoomMenu,
|
|
closeZoomMenu,
|
|
toggleBackgroundSettings,
|
|
toggleMinimap,
|
|
};
|
|
}
|