diff --git a/package.json b/package.json index 87c2d3280..0136a88a0 100644 --- a/package.json +++ b/package.json @@ -150,7 +150,8 @@ "react-native-safe-area-context": "^5.8.0", "react-native-webview": "^13.16.1", "three": "^0.184.0", - "vite": "^6.2.0" + "vite": "^6.2.0", + "zustand": "^5.0.14" }, "devDependencies": { "@colbymchenry/codegraph": "^0.8.0", diff --git a/src/components/image-editor/EditorAgentConversationPanelView.test.tsx b/src/components/image-editor/EditorAgentConversationPanelView.test.tsx index c6b7dc8b1..25108f430 100644 --- a/src/components/image-editor/EditorAgentConversationPanelView.test.tsx +++ b/src/components/image-editor/EditorAgentConversationPanelView.test.tsx @@ -8,9 +8,10 @@ import { waitFor, within, } from '@testing-library/react'; -import { describe, expect, it, vi } from 'vitest'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; import { EditorAgentConversationPanelView } from './EditorAgentConversationPanelView'; +import { useImageCanvasContextStore } from './useImageCanvasContextStore.ts'; import type { EditorAgentConversationClient } from './useEditorAgentConversation'; function createClient(): EditorAgentConversationClient { @@ -85,12 +86,15 @@ function createClient(): EditorAgentConversationClient { } describe('EditorAgentConversationPanelView', () => { + beforeEach(() => { + useImageCanvasContextStore.getState().setProjectId('project-1'); + }); + it('manages conversations, attachments and message sending inside the panel', async () => { const client = createClient(); render( { render( { const { rerender } = render( { rerender( { rerender( { render(
{ const client = createClient(); render( void; layers?: CanvasLayer[]; @@ -404,7 +403,6 @@ function AttachmentPickerModal({ } export function EditorAgentConversationPanelView({ - projectId, open, onToggleOpen, layers = [], @@ -418,6 +416,9 @@ export function EditorAgentConversationPanelView({ setHasConversationMounted(true); } }, [open]); + const projectId = useImageCanvasContextStore( + (state) => state.projectId, + ); const effectiveProjectId = hasConversationMounted ? projectId : null; const { conversations, diff --git a/src/components/image-editor/ImageCanvasEditorShellView.test.tsx b/src/components/image-editor/ImageCanvasEditorShellView.test.tsx index dc5fa045a..bbfb494b6 100644 --- a/src/components/image-editor/ImageCanvasEditorShellView.test.tsx +++ b/src/components/image-editor/ImageCanvasEditorShellView.test.tsx @@ -93,7 +93,6 @@ function createSidebarProps(): ImageCanvasSidebarViewProps { function createTopbarProps(): ImageCanvasTopbarViewProps { return { - projectId: 'project-1', projectTitle: '默认项目', projectRenameValue: '默认项目', isRenamingProject: false, @@ -119,7 +118,6 @@ function createTopbarProps(): ImageCanvasTopbarViewProps { function createStageProps(): ImageCanvasStageViewProps { return { - projectId: 'project-1', canvasViewportRef: createRef(), specToolWrapRef: createRef(), musicToolWrapRef: createRef(), diff --git a/src/components/image-editor/ImageCanvasEditorView.tsx b/src/components/image-editor/ImageCanvasEditorView.tsx index 504334bf5..b15077d26 100644 --- a/src/components/image-editor/ImageCanvasEditorView.tsx +++ b/src/components/image-editor/ImageCanvasEditorView.tsx @@ -81,6 +81,7 @@ import { useImageCanvasEditorChrome } from './useImageCanvasEditorChrome'; import { useImageCanvasGenerationSurface } from './useImageCanvasGenerationSurface'; import { useImageCanvasKeyboardShortcuts } from './useImageCanvasKeyboardShortcuts'; import { useImageCanvasLayerCommands } from './useImageCanvasLayerCommands'; +import { useImageCanvasContextStore } from './useImageCanvasContextStore.ts'; import { useImageCanvasProjectPersistence } from './useImageCanvasProjectPersistence'; import { useImageCanvasStageController } from './useImageCanvasStageController'; import { useImageCanvasStageInteractions } from './useImageCanvasStageInteractions'; @@ -1015,6 +1016,12 @@ export function ImageCanvasEditorView({ openEditorLoginModal, onProjectAccessLost, }); + const setEditorProjectContextId = useImageCanvasContextStore( + (state) => state.setProjectId, + ); + useEffect(() => { + setEditorProjectContextId(projectId); + }, [projectId, setEditorProjectContextId]); const applyGeneratedProjectSnapshot = useCallback( (project: EditorProjectSnapshot) => { applyProjectSnapshot(project); @@ -1890,7 +1897,6 @@ export function ImageCanvasEditorView({ getCanvasPointFromClient, }; const topbarProps = { - projectId, projectTitle, projectRenameValue, isRenamingProject, @@ -1919,7 +1925,6 @@ export function ImageCanvasEditorView({ }, }; const stageProps = { - projectId, canvasViewportRef, specToolWrapRef, musicToolWrapRef, diff --git a/src/components/image-editor/ImageCanvasStageView.tsx b/src/components/image-editor/ImageCanvasStageView.tsx index 68ba896b0..3867fb717 100644 --- a/src/components/image-editor/ImageCanvasStageView.tsx +++ b/src/components/image-editor/ImageCanvasStageView.tsx @@ -41,7 +41,6 @@ import { ImageCanvasUiAssetExtractionOverlayView } from './ImageCanvasUiAssetExt import { ImageCanvasWorldView } from './ImageCanvasWorldView'; export type ImageCanvasStageViewProps = { - projectId?: string | null; canvasViewportRef: RefObject; specToolWrapRef: RefObject; musicToolWrapRef: RefObject; @@ -192,7 +191,6 @@ export type ImageCanvasStageViewProps = { }; export function ImageCanvasStageView({ - projectId, canvasViewportRef, specToolWrapRef, musicToolWrapRef, @@ -463,7 +461,6 @@ export function ImageCanvasStageView({ /> ({ listExternalGenerationTasks: vi.fn().mockResolvedValue({ @@ -22,6 +23,7 @@ vi.mock('../../services/external-generation', () => ({ const listExternalGenerationTasksMock = vi.mocked(listExternalGenerationTasks); beforeEach(() => { + useImageCanvasContextStore.getState().setProjectId('project-1'); listExternalGenerationTasksMock.mockClear(); listExternalGenerationTasksMock.mockResolvedValue({ overview: { @@ -115,7 +117,6 @@ describe('ImageCanvasTaskSidebarView', () => { render(
{ render( { render( { const { rerender } = render( { ]; rerender( { render( void; @@ -252,12 +252,12 @@ function trimStoredExternalTasks( } export function ImageCanvasTaskSidebarView({ - projectId, refreshKey = 0, open, onToggleOpen, onFocusExternalTask, }: ImageCanvasTaskSidebarViewProps) { + const projectId = useImageCanvasContextStore((state) => state.projectId); const normalizedProjectId = projectId?.trim() ?? ''; const [activeTab, setActiveTab] = useState('active'); const [now, setNow] = useState(() => Date.now()); diff --git a/src/components/image-editor/ImageCanvasTopbarView.test.tsx b/src/components/image-editor/ImageCanvasTopbarView.test.tsx index b9f2c584b..ba0c69689 100644 --- a/src/components/image-editor/ImageCanvasTopbarView.test.tsx +++ b/src/components/image-editor/ImageCanvasTopbarView.test.tsx @@ -5,6 +5,7 @@ import { describe, expect, it, vi } from 'vitest'; import type { CanvasLayer } from './ImageCanvasEditorTypes'; import { ImageCanvasTopbarView } from './ImageCanvasTopbarView'; +import { useImageCanvasContextStore } from './useImageCanvasContextStore.ts'; function createLayer(overrides: Partial = {}): CanvasLayer { const id = overrides.id ?? 'layer-a'; @@ -30,8 +31,8 @@ function renderTopbar( Parameters[0] > = {}, ) { + useImageCanvasContextStore.getState().setProjectId('project-a'); const props: Parameters[0] = { - projectId: 'project-a', projectTitle: '默认项目', projectRenameValue: '默认项目', isRenamingProject: false, @@ -144,7 +145,6 @@ describe('ImageCanvasTopbarView', () => { const exportCanvasAssets = vi.fn(); const { rerender } = render( { rerender( state.projectId); const hasExportableLayer = layers.some( (layer) => layer.src.trim().length > 0, ); diff --git a/src/components/image-editor/useImageCanvasContextStore.ts b/src/components/image-editor/useImageCanvasContextStore.ts new file mode 100644 index 000000000..0869656f5 --- /dev/null +++ b/src/components/image-editor/useImageCanvasContextStore.ts @@ -0,0 +1,14 @@ +import { create } from 'zustand'; + +type ImageCanvasContextState = { + projectId: string | null; + setProjectId: (projectId?: string | null) => void; +}; + +export const useImageCanvasContextStore = + create((set) => ({ + projectId: null, + setProjectId: (projectId) => { + set({ projectId: projectId?.trim() || null }); + }, + }));