重构: replace canvas projectId prop drilling with zustand
This commit is contained in:
+2
-1
@@ -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",
|
||||
|
||||
@@ -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(
|
||||
<EditorAgentConversationPanelView
|
||||
projectId="project-1"
|
||||
open
|
||||
onToggleOpen={vi.fn()}
|
||||
client={client}
|
||||
@@ -196,7 +200,6 @@ describe('EditorAgentConversationPanelView', () => {
|
||||
|
||||
render(
|
||||
<EditorAgentConversationPanelView
|
||||
projectId="project-1"
|
||||
open
|
||||
onToggleOpen={vi.fn()}
|
||||
client={client}
|
||||
@@ -292,7 +295,6 @@ describe('EditorAgentConversationPanelView', () => {
|
||||
|
||||
const { rerender } = render(
|
||||
<EditorAgentConversationPanelView
|
||||
projectId="project-1"
|
||||
open
|
||||
onToggleOpen={vi.fn()}
|
||||
client={client}
|
||||
@@ -316,7 +318,6 @@ describe('EditorAgentConversationPanelView', () => {
|
||||
|
||||
rerender(
|
||||
<EditorAgentConversationPanelView
|
||||
projectId="project-1"
|
||||
open={false}
|
||||
onToggleOpen={vi.fn()}
|
||||
client={client}
|
||||
@@ -326,7 +327,6 @@ describe('EditorAgentConversationPanelView', () => {
|
||||
|
||||
rerender(
|
||||
<EditorAgentConversationPanelView
|
||||
projectId="project-1"
|
||||
open
|
||||
onToggleOpen={vi.fn()}
|
||||
client={client}
|
||||
@@ -349,7 +349,6 @@ describe('EditorAgentConversationPanelView', () => {
|
||||
render(
|
||||
<div onWheel={parentWheel}>
|
||||
<EditorAgentConversationPanelView
|
||||
projectId="project-1"
|
||||
open
|
||||
onToggleOpen={vi.fn()}
|
||||
client={client}
|
||||
@@ -387,7 +386,6 @@ describe('EditorAgentConversationPanelView', () => {
|
||||
const client = createClient();
|
||||
render(
|
||||
<EditorAgentConversationPanelView
|
||||
projectId="project-1"
|
||||
open
|
||||
onToggleOpen={vi.fn()}
|
||||
client={client}
|
||||
|
||||
@@ -45,7 +45,6 @@ type EditorAgentAttachmentOption = {
|
||||
};
|
||||
|
||||
type EditorAgentConversationPanelViewProps = {
|
||||
projectId?: string | null;
|
||||
open: boolean;
|
||||
onToggleOpen: () => 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,
|
||||
|
||||
@@ -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<HTMLDivElement>(),
|
||||
specToolWrapRef: createRef<HTMLSpanElement>(),
|
||||
musicToolWrapRef: createRef<HTMLSpanElement>(),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -41,7 +41,6 @@ import { ImageCanvasUiAssetExtractionOverlayView } from './ImageCanvasUiAssetExt
|
||||
import { ImageCanvasWorldView } from './ImageCanvasWorldView';
|
||||
|
||||
export type ImageCanvasStageViewProps = {
|
||||
projectId?: string | null;
|
||||
canvasViewportRef: RefObject<HTMLDivElement | null>;
|
||||
specToolWrapRef: RefObject<HTMLSpanElement | null>;
|
||||
musicToolWrapRef: RefObject<HTMLSpanElement | null>;
|
||||
@@ -192,7 +191,6 @@ export type ImageCanvasStageViewProps = {
|
||||
};
|
||||
|
||||
export function ImageCanvasStageView({
|
||||
projectId,
|
||||
canvasViewportRef,
|
||||
specToolWrapRef,
|
||||
musicToolWrapRef,
|
||||
@@ -463,7 +461,6 @@ export function ImageCanvasStageView({
|
||||
/>
|
||||
|
||||
<ImageCanvasTaskSidebarView
|
||||
projectId={projectId}
|
||||
refreshKey={taskListRefreshKey}
|
||||
open={isTaskSidebarOpen}
|
||||
onToggleOpen={onToggleTaskSidebar}
|
||||
@@ -472,7 +469,6 @@ export function ImageCanvasStageView({
|
||||
|
||||
{isAgentConversationEnabled ? (
|
||||
<EditorAgentConversationPanelView
|
||||
projectId={projectId}
|
||||
open={isAgentConversationOpen}
|
||||
onToggleOpen={onToggleAgentConversation}
|
||||
layers={layers}
|
||||
|
||||
@@ -6,6 +6,7 @@ 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 { useImageCanvasContextStore } from './useImageCanvasContextStore.ts';
|
||||
|
||||
vi.mock('../../services/external-generation', () => ({
|
||||
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(
|
||||
<div onWheel={parentWheel}>
|
||||
<ImageCanvasTaskSidebarView
|
||||
projectId="project-1"
|
||||
open
|
||||
onToggleOpen={vi.fn()}
|
||||
onFocusExternalTask={focusExternalTask}
|
||||
@@ -230,7 +231,6 @@ describe('ImageCanvasTaskSidebarView', () => {
|
||||
|
||||
render(
|
||||
<ImageCanvasTaskSidebarView
|
||||
projectId="project-1"
|
||||
open
|
||||
onToggleOpen={vi.fn()}
|
||||
onFocusExternalTask={vi.fn()}
|
||||
@@ -287,7 +287,6 @@ describe('ImageCanvasTaskSidebarView', () => {
|
||||
|
||||
render(
|
||||
<ImageCanvasTaskSidebarView
|
||||
projectId="project-1"
|
||||
open
|
||||
onToggleOpen={vi.fn()}
|
||||
onFocusExternalTask={vi.fn()}
|
||||
@@ -325,7 +324,6 @@ describe('ImageCanvasTaskSidebarView', () => {
|
||||
|
||||
const { rerender } = render(
|
||||
<ImageCanvasTaskSidebarView
|
||||
projectId="project-1"
|
||||
refreshKey={0}
|
||||
open
|
||||
onToggleOpen={vi.fn()}
|
||||
@@ -347,7 +345,6 @@ describe('ImageCanvasTaskSidebarView', () => {
|
||||
];
|
||||
rerender(
|
||||
<ImageCanvasTaskSidebarView
|
||||
projectId="project-1"
|
||||
refreshKey={1}
|
||||
open
|
||||
onToggleOpen={vi.fn()}
|
||||
@@ -388,7 +385,6 @@ describe('ImageCanvasTaskSidebarView', () => {
|
||||
|
||||
render(
|
||||
<ImageCanvasTaskSidebarView
|
||||
projectId="project-1"
|
||||
open
|
||||
onToggleOpen={vi.fn()}
|
||||
onFocusExternalTask={vi.fn()}
|
||||
|
||||
@@ -19,6 +19,7 @@ import type { ExternalGenerationTaskRecord } from '../../../packages/shared/src/
|
||||
import { listExternalGenerationTasks } from '../../services/external-generation';
|
||||
import { EditorIconButton } from './ImageCanvasEditorPrimitives';
|
||||
import type { CanvasTaskStatus } from './ImageCanvasEditorTypes';
|
||||
import { useImageCanvasContextStore } from './useImageCanvasContextStore.ts';
|
||||
|
||||
const COMPLETED_TASK_LIST_LIMIT = 20;
|
||||
const COMPLETED_TASK_LIST_MAX_LIMIT = 100;
|
||||
@@ -42,7 +43,6 @@ type TaskSidebarItem = {
|
||||
};
|
||||
|
||||
type ImageCanvasTaskSidebarViewProps = {
|
||||
projectId?: string | null;
|
||||
refreshKey?: number;
|
||||
open: boolean;
|
||||
onToggleOpen: () => 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<TaskSidebarTab>('active');
|
||||
const [now, setNow] = useState(() => Date.now());
|
||||
|
||||
@@ -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> = {}): CanvasLayer {
|
||||
const id = overrides.id ?? 'layer-a';
|
||||
@@ -30,8 +31,8 @@ function renderTopbar(
|
||||
Parameters<typeof ImageCanvasTopbarView>[0]
|
||||
> = {},
|
||||
) {
|
||||
useImageCanvasContextStore.getState().setProjectId('project-a');
|
||||
const props: Parameters<typeof ImageCanvasTopbarView>[0] = {
|
||||
projectId: 'project-a',
|
||||
projectTitle: '默认项目',
|
||||
projectRenameValue: '默认项目',
|
||||
isRenamingProject: false,
|
||||
@@ -144,7 +145,6 @@ describe('ImageCanvasTopbarView', () => {
|
||||
const exportCanvasAssets = vi.fn();
|
||||
const { rerender } = render(
|
||||
<ImageCanvasTopbarView
|
||||
projectId="project-a"
|
||||
projectTitle="默认项目"
|
||||
projectRenameValue="默认项目"
|
||||
isRenamingProject={false}
|
||||
@@ -175,7 +175,6 @@ describe('ImageCanvasTopbarView', () => {
|
||||
|
||||
rerender(
|
||||
<ImageCanvasTopbarView
|
||||
projectId="project-a"
|
||||
projectTitle="默认项目"
|
||||
projectRenameValue="默认项目"
|
||||
isRenamingProject={false}
|
||||
|
||||
@@ -12,9 +12,9 @@ import { PlatformTextField } from '../common/PlatformTextField';
|
||||
import { EditorIconButton } from './ImageCanvasEditorPrimitives';
|
||||
import type { CanvasLayer } from './ImageCanvasEditorTypes';
|
||||
import type { AssetExportStatus } from './useImageCanvasAssetExportWorkflow';
|
||||
import { useImageCanvasContextStore } from './useImageCanvasContextStore.ts';
|
||||
|
||||
export type ImageCanvasTopbarViewProps = {
|
||||
projectId: string | null;
|
||||
projectTitle: string;
|
||||
projectRenameValue: string;
|
||||
isRenamingProject: boolean;
|
||||
@@ -57,7 +57,6 @@ function buildCanvasUserCode(
|
||||
}
|
||||
|
||||
export function ImageCanvasTopbarView({
|
||||
projectId,
|
||||
projectTitle,
|
||||
projectRenameValue,
|
||||
isRenamingProject,
|
||||
@@ -79,6 +78,7 @@ export function ImageCanvasTopbarView({
|
||||
onOpenWallet,
|
||||
onOpenAccount,
|
||||
}: ImageCanvasTopbarViewProps) {
|
||||
const projectId = useImageCanvasContextStore((state) => state.projectId);
|
||||
const hasExportableLayer = layers.some(
|
||||
(layer) => layer.src.trim().length > 0,
|
||||
);
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { create } from 'zustand';
|
||||
|
||||
type ImageCanvasContextState = {
|
||||
projectId: string | null;
|
||||
setProjectId: (projectId?: string | null) => void;
|
||||
};
|
||||
|
||||
export const useImageCanvasContextStore =
|
||||
create<ImageCanvasContextState>((set) => ({
|
||||
projectId: null,
|
||||
setProjectId: (projectId) => {
|
||||
set({ projectId: projectId?.trim() || null });
|
||||
},
|
||||
}));
|
||||
Reference in New Issue
Block a user