拆分图片画布图片信息弹窗
新增图片信息弹窗组件,承接 metadata 详情渲染和 UnifiedModal 接入 修复未登录进入编辑器时项目和素材接口抢跑 401 修复重置画布视图点击事件误传导致适合视图报错 补充图片信息弹窗、鉴权门禁和重置按钮回归测试 更新前端拆分文档和 TRACKING 浏览器回归记录
This commit is contained in:
@@ -397,6 +397,35 @@ describe('ImageCanvasEditorView', () => {
|
||||
}),
|
||||
);
|
||||
|
||||
render(
|
||||
<AuthUiContext.Provider
|
||||
value={createAuthValue({
|
||||
user: {
|
||||
id: 'user-1',
|
||||
publicUserCode: 'U001',
|
||||
displayName: '测试用户',
|
||||
avatarUrl: null,
|
||||
phoneNumberMasked: '138****0000',
|
||||
loginMethod: 'password',
|
||||
bindingStatus: 'active',
|
||||
wechatBound: false,
|
||||
},
|
||||
canAccessProtectedData: true,
|
||||
openLoginModal,
|
||||
})}
|
||||
>
|
||||
<ImageCanvasEditorView />
|
||||
</AuthUiContext.Provider>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(openLoginModal).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('opens the login modal immediately when entering the editor while logged out', async () => {
|
||||
const openLoginModal = vi.fn();
|
||||
|
||||
render(
|
||||
<AuthUiContext.Provider value={createAuthValue({ openLoginModal })}>
|
||||
<ImageCanvasEditorView />
|
||||
@@ -406,6 +435,10 @@ describe('ImageCanvasEditorView', () => {
|
||||
await waitFor(() => {
|
||||
expect(openLoginModal).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
expect(typeof openLoginModal.mock.calls[0]?.[0]).toBe('function');
|
||||
expect(loadEditorAssetLibraryMock).not.toHaveBeenCalled();
|
||||
expect(loadEditorProjectMock).not.toHaveBeenCalled();
|
||||
expect(loadOrCreateRecentEditorProjectMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('renames the current project from the canvas topbar', async () => {
|
||||
@@ -976,13 +1009,14 @@ describe('ImageCanvasEditorView', () => {
|
||||
new File(['image'], '登录后上传.png', { type: 'image/png' }),
|
||||
]);
|
||||
|
||||
expect(openLoginModal).toHaveBeenCalledTimes(1);
|
||||
expect(openLoginModal).toHaveBeenCalled();
|
||||
expect(createEditorAssetMock).not.toHaveBeenCalled();
|
||||
expect(
|
||||
screen.queryByRole('button', { name: '上传失败登录后上传.png' }),
|
||||
).toBeNull();
|
||||
|
||||
const resumeUpload = openLoginModal.mock.calls[0]?.[0];
|
||||
const resumeUpload =
|
||||
openLoginModal.mock.calls[openLoginModal.mock.calls.length - 1]?.[0];
|
||||
expect(typeof resumeUpload).toBe('function');
|
||||
rerender(
|
||||
<AuthUiContext.Provider
|
||||
@@ -2038,6 +2072,14 @@ describe('ImageCanvasEditorView', () => {
|
||||
expect(screen.queryByRole('button', { name: '画布小地图' })).toBeNull();
|
||||
});
|
||||
|
||||
it('resets the canvas view without forwarding the click event to fit layers', () => {
|
||||
render(<ImageCanvasEditorView />);
|
||||
|
||||
expect(() => {
|
||||
fireEvent.click(screen.getByRole('button', { name: '重置画布视图' }));
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('uses normal wheel for vertical canvas scroll and ctrl wheel for zoom', () => {
|
||||
render(<ImageCanvasEditorView />);
|
||||
|
||||
|
||||
@@ -11,10 +11,10 @@ import {
|
||||
|
||||
import { PlatformStatusMessage } from '../common/PlatformStatusMessage';
|
||||
import { PlatformTextField } from '../common/PlatformTextField';
|
||||
import { UnifiedModal } from '../common/UnifiedModal';
|
||||
import { useAuthUi } from '../auth/AuthUiContext';
|
||||
import { EditorIconButton } from './ImageCanvasEditorPrimitives';
|
||||
import { ImageCanvasGenerationComposerView } from './ImageCanvasGenerationComposerView';
|
||||
import { ImageCanvasMetadataModalView } from './ImageCanvasMetadataModalView';
|
||||
import {
|
||||
getCanvasLayersByIds,
|
||||
resolveContextTargetLayerIds,
|
||||
@@ -34,7 +34,6 @@ import {
|
||||
ICON_COMPOSER_HORIZONTAL_CHROME_REM,
|
||||
ICON_COMPOSER_MIN_WIDTH_REM,
|
||||
ICON_DESCRIPTION_CARD_WIDTH_REM,
|
||||
formatLayerImageType,
|
||||
getGenerationFrameAriaLabel,
|
||||
getGenerationFrameLabel,
|
||||
getLayerKindLabel,
|
||||
@@ -83,6 +82,7 @@ export function ImageCanvasEditorView() {
|
||||
const assetListRef = useRef<HTMLDivElement | null>(null);
|
||||
const assetPointerDragRef = useRef<AssetPointerDragState | null>(null);
|
||||
const authUiRef = useRef(authUi);
|
||||
const hasRequestedLoginRef = useRef(false);
|
||||
const layerCounterRef = useRef(0);
|
||||
const layersRef = useRef<CanvasLayer[]>([]);
|
||||
const viewportRef = useRef<CanvasViewport>(DEFAULT_IMAGE_CANVAS_VIEWPORT);
|
||||
@@ -149,6 +149,20 @@ export function ImageCanvasEditorView() {
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!authUi || authUi.user || authUi.canAccessProtectedData) {
|
||||
hasRequestedLoginRef.current = false;
|
||||
return;
|
||||
}
|
||||
if (hasRequestedLoginRef.current) {
|
||||
return;
|
||||
}
|
||||
hasRequestedLoginRef.current = true;
|
||||
authUi.openLoginModal(() => {
|
||||
window.location.reload();
|
||||
});
|
||||
}, [authUi]);
|
||||
const {
|
||||
projectTitle,
|
||||
setProjectTitle,
|
||||
@@ -260,6 +274,7 @@ export function ImageCanvasEditorView() {
|
||||
handleAssetMarqueePointerUp,
|
||||
} = useImageCanvasAssetLibrary({
|
||||
assetListRef,
|
||||
canAccessProtectedData: authUi ? authUi.canAccessProtectedData : true,
|
||||
openEditorLoginModal,
|
||||
onDeleteAssets: removeCanvasLayersLinkedToAssets,
|
||||
});
|
||||
@@ -440,6 +455,7 @@ export function ImageCanvasEditorView() {
|
||||
setters: projectPersistenceSetters,
|
||||
layers,
|
||||
viewport,
|
||||
canAccessProtectedData: authUi ? authUi.canAccessProtectedData : true,
|
||||
openEditorLoginModal,
|
||||
});
|
||||
const {
|
||||
@@ -1323,81 +1339,10 @@ export function ImageCanvasEditorView() {
|
||||
</ImageCanvasStageView>
|
||||
</div>
|
||||
|
||||
<UnifiedModal
|
||||
open={Boolean(metadataLayer)}
|
||||
title={metadataLayer ? `${metadataLayer.title}图片信息` : '图片信息'}
|
||||
size="sm"
|
||||
closeLabel="关闭图片信息"
|
||||
<ImageCanvasMetadataModalView
|
||||
layer={metadataLayer}
|
||||
onClose={() => setMetadataLayer(null)}
|
||||
panelClassName="image-canvas-editor__metadata-dialog"
|
||||
bodyClassName="image-canvas-editor__metadata-body"
|
||||
>
|
||||
{metadataLayer ? (
|
||||
<dl className="image-canvas-editor__metadata-grid">
|
||||
<dt>图片类型</dt>
|
||||
<dd>{formatLayerImageType(metadataLayer)}</dd>
|
||||
<dt>生成输入</dt>
|
||||
<dd className="image-canvas-editor__metadata-inputs">
|
||||
{metadataLayer.generationInputs?.fields.length ||
|
||||
metadataLayer.generationInputs?.references.length ? (
|
||||
<>
|
||||
{metadataLayer.generationInputs.fields.map((field) => (
|
||||
<div
|
||||
key={`${field.title}-${field.value}`}
|
||||
className="image-canvas-editor__metadata-input-field"
|
||||
>
|
||||
<span className="image-canvas-editor__metadata-input-title">
|
||||
{field.title}
|
||||
</span>
|
||||
<span>{field.value}</span>
|
||||
</div>
|
||||
))}
|
||||
{metadataLayer.generationInputs.references.length ? (
|
||||
<div className="image-canvas-editor__metadata-reference-list">
|
||||
{metadataLayer.generationInputs.references.map(
|
||||
(reference) => (
|
||||
<div
|
||||
key={`${reference.title}-${reference.label}-${reference.src}`}
|
||||
className="image-canvas-editor__metadata-reference-card"
|
||||
>
|
||||
<img
|
||||
src={reference.src}
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span className="image-canvas-editor__metadata-reference-copy">
|
||||
<span className="image-canvas-editor__metadata-input-title">
|
||||
{reference.title}
|
||||
</span>
|
||||
<span>{reference.label}</span>
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
</dd>
|
||||
<dt>Model</dt>
|
||||
<dd>{metadataLayer.model ?? '-'}</dd>
|
||||
<dt>Resolution</dt>
|
||||
<dd>
|
||||
{metadataLayer.originalWidth} x {metadataLayer.originalHeight} px
|
||||
</dd>
|
||||
<dt>Provider</dt>
|
||||
<dd>{metadataLayer.provider ?? '-'}</dd>
|
||||
<dt>Task</dt>
|
||||
<dd>{metadataLayer.taskId ?? '-'}</dd>
|
||||
<dt>Object</dt>
|
||||
<dd>
|
||||
{metadataLayer.objectKey ?? metadataLayer.assetObjectId ?? '-'}
|
||||
</dd>
|
||||
</dl>
|
||||
) : null}
|
||||
</UnifiedModal>
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
/* @vitest-environment jsdom */
|
||||
|
||||
import { fireEvent, render, screen, within } from '@testing-library/react';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import type { CanvasLayer } from './ImageCanvasEditorTypes';
|
||||
import { ImageCanvasMetadataModalView } from './ImageCanvasMetadataModalView';
|
||||
|
||||
function createLayer(overrides: Partial<CanvasLayer> = {}): CanvasLayer {
|
||||
return {
|
||||
id: 'layer-1',
|
||||
resourceId: 'resource-1',
|
||||
title: '生成主图',
|
||||
src: 'data:image/png;base64,layer',
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 320,
|
||||
height: 240,
|
||||
originalWidth: 1024,
|
||||
originalHeight: 768,
|
||||
zIndex: 1,
|
||||
sourceType: 'generated',
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe('ImageCanvasMetadataModalView', () => {
|
||||
it('renders generated layer metadata with generation inputs and references', () => {
|
||||
render(
|
||||
<ImageCanvasMetadataModalView
|
||||
layer={createLayer({
|
||||
model: 'gpt-image-2',
|
||||
provider: 'VectorEngine',
|
||||
taskId: 'task-123',
|
||||
objectKey: 'generated/object.png',
|
||||
generationInputs: {
|
||||
fields: [{ title: '生成提示词', value: '清爽游戏按钮' }],
|
||||
references: [
|
||||
{
|
||||
title: '参考图',
|
||||
label: '角色立绘',
|
||||
src: 'data:image/png;base64,reference',
|
||||
},
|
||||
],
|
||||
},
|
||||
})}
|
||||
onClose={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
const dialog = screen.getByRole('dialog', { name: '生成主图图片信息' });
|
||||
|
||||
expect(within(dialog).getByText('生成图片')).toBeTruthy();
|
||||
expect(within(dialog).getByText('生成提示词')).toBeTruthy();
|
||||
expect(within(dialog).getByText('清爽游戏按钮')).toBeTruthy();
|
||||
expect(within(dialog).getByText('参考图')).toBeTruthy();
|
||||
expect(within(dialog).getByText('角色立绘')).toBeTruthy();
|
||||
expect(within(dialog).getByText('gpt-image-2')).toBeTruthy();
|
||||
expect(within(dialog).getByText('1024 x 768 px')).toBeTruthy();
|
||||
expect(within(dialog).getByText('VectorEngine')).toBeTruthy();
|
||||
expect(within(dialog).getByText('task-123')).toBeTruthy();
|
||||
expect(within(dialog).getByText('generated/object.png')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders upload fallback values and closes through the modal shell', () => {
|
||||
const onClose = vi.fn();
|
||||
render(
|
||||
<ImageCanvasMetadataModalView
|
||||
layer={createLayer({
|
||||
title: '上传素材',
|
||||
sourceType: 'uploaded',
|
||||
model: null,
|
||||
provider: null,
|
||||
taskId: null,
|
||||
objectKey: null,
|
||||
assetObjectId: 'asset-object-1',
|
||||
generationInputs: null,
|
||||
})}
|
||||
onClose={onClose}
|
||||
/>,
|
||||
);
|
||||
|
||||
const dialog = screen.getByRole('dialog', { name: '上传素材图片信息' });
|
||||
|
||||
expect(within(dialog).getByText('上传图片')).toBeTruthy();
|
||||
expect(within(dialog).getAllByText('-').length).toBeGreaterThanOrEqual(3);
|
||||
expect(within(dialog).getByText('asset-object-1')).toBeTruthy();
|
||||
|
||||
fireEvent.click(within(dialog).getByRole('button', { name: '关闭图片信息' }));
|
||||
|
||||
expect(onClose).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('does not render a dialog when no layer is selected', () => {
|
||||
render(<ImageCanvasMetadataModalView layer={null} onClose={vi.fn()} />);
|
||||
|
||||
expect(screen.queryByRole('dialog', { name: '图片信息' })).toBeNull();
|
||||
});
|
||||
});
|
||||
83
src/components/image-editor/ImageCanvasMetadataModalView.tsx
Normal file
83
src/components/image-editor/ImageCanvasMetadataModalView.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import { UnifiedModal } from '../common/UnifiedModal';
|
||||
import { formatLayerImageType } from './ImageCanvasGenerationModel';
|
||||
import type { CanvasLayer } from './ImageCanvasEditorTypes';
|
||||
|
||||
type ImageCanvasMetadataModalViewProps = {
|
||||
layer: CanvasLayer | null;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
export function ImageCanvasMetadataModalView({
|
||||
layer,
|
||||
onClose,
|
||||
}: ImageCanvasMetadataModalViewProps) {
|
||||
return (
|
||||
<UnifiedModal
|
||||
open={Boolean(layer)}
|
||||
title={layer ? `${layer.title}图片信息` : '图片信息'}
|
||||
size="sm"
|
||||
closeLabel="关闭图片信息"
|
||||
onClose={onClose}
|
||||
panelClassName="image-canvas-editor__metadata-dialog"
|
||||
bodyClassName="image-canvas-editor__metadata-body"
|
||||
>
|
||||
{layer ? (
|
||||
<dl className="image-canvas-editor__metadata-grid">
|
||||
<dt>图片类型</dt>
|
||||
<dd>{formatLayerImageType(layer)}</dd>
|
||||
<dt>生成输入</dt>
|
||||
<dd className="image-canvas-editor__metadata-inputs">
|
||||
{layer.generationInputs?.fields.length ||
|
||||
layer.generationInputs?.references.length ? (
|
||||
<>
|
||||
{layer.generationInputs.fields.map((field) => (
|
||||
<div
|
||||
key={`${field.title}-${field.value}`}
|
||||
className="image-canvas-editor__metadata-input-field"
|
||||
>
|
||||
<span className="image-canvas-editor__metadata-input-title">
|
||||
{field.title}
|
||||
</span>
|
||||
<span>{field.value}</span>
|
||||
</div>
|
||||
))}
|
||||
{layer.generationInputs.references.length ? (
|
||||
<div className="image-canvas-editor__metadata-reference-list">
|
||||
{layer.generationInputs.references.map((reference) => (
|
||||
<div
|
||||
key={`${reference.title}-${reference.label}-${reference.src}`}
|
||||
className="image-canvas-editor__metadata-reference-card"
|
||||
>
|
||||
<img src={reference.src} alt="" aria-hidden="true" />
|
||||
<span className="image-canvas-editor__metadata-reference-copy">
|
||||
<span className="image-canvas-editor__metadata-input-title">
|
||||
{reference.title}
|
||||
</span>
|
||||
<span>{reference.label}</span>
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
</dd>
|
||||
<dt>Model</dt>
|
||||
<dd>{layer.model ?? '-'}</dd>
|
||||
<dt>Resolution</dt>
|
||||
<dd>
|
||||
{layer.originalWidth} x {layer.originalHeight} px
|
||||
</dd>
|
||||
<dt>Provider</dt>
|
||||
<dd>{layer.provider ?? '-'}</dd>
|
||||
<dt>Task</dt>
|
||||
<dd>{layer.taskId ?? '-'}</dd>
|
||||
<dt>Object</dt>
|
||||
<dd>{layer.objectKey ?? layer.assetObjectId ?? '-'}</dd>
|
||||
</dl>
|
||||
) : null}
|
||||
</UnifiedModal>
|
||||
);
|
||||
}
|
||||
@@ -784,7 +784,7 @@ export function ImageCanvasStageView({
|
||||
label="重置画布视图"
|
||||
title="重置画布视图"
|
||||
icon={RotateCcw}
|
||||
onClick={onFitLayers}
|
||||
onClick={() => onFitLayers()}
|
||||
/>
|
||||
|
||||
<div
|
||||
|
||||
@@ -65,15 +65,18 @@ function createAssetSnapshot(
|
||||
}
|
||||
|
||||
function AssetLibraryHarness({
|
||||
canAccessProtectedData = true,
|
||||
openEditorLoginModal = defaultOpenEditorLoginModal,
|
||||
onDeleteAssets = defaultOnDeleteAssets,
|
||||
}: {
|
||||
canAccessProtectedData?: boolean;
|
||||
openEditorLoginModal?: (postLoginAction?: (() => void) | null) => void;
|
||||
onDeleteAssets?: (assets: EditorAsset[]) => void;
|
||||
}) {
|
||||
const assetListRef = useRef<HTMLDivElement | null>(null);
|
||||
const assetLibrary = useImageCanvasAssetLibrary({
|
||||
assetListRef,
|
||||
canAccessProtectedData,
|
||||
openEditorLoginModal,
|
||||
onDeleteAssets,
|
||||
});
|
||||
@@ -259,6 +262,15 @@ describe('useImageCanvasAssetLibrary', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('does not request the protected asset library before login is available', () => {
|
||||
render(<AssetLibraryHarness canAccessProtectedData={false} />);
|
||||
|
||||
expect(loadEditorAssetLibraryMock).not.toHaveBeenCalled();
|
||||
expect(screen.getByTestId('folders').textContent).toBe(
|
||||
'project:项目素材:false',
|
||||
);
|
||||
});
|
||||
|
||||
it('creates a local folder and replaces it with the persisted folder id', async () => {
|
||||
let resolveCreateFolder: (
|
||||
folder: Awaited<ReturnType<typeof createEditorAssetFolderMock>>,
|
||||
|
||||
@@ -40,10 +40,12 @@ function isEditorAuthError(error: unknown) {
|
||||
|
||||
export function useImageCanvasAssetLibrary({
|
||||
assetListRef,
|
||||
canAccessProtectedData,
|
||||
openEditorLoginModal,
|
||||
onDeleteAssets,
|
||||
}: {
|
||||
assetListRef: RefObject<HTMLDivElement | null>;
|
||||
canAccessProtectedData: boolean;
|
||||
openEditorLoginModal: (postLoginAction?: (() => void) | null) => void;
|
||||
onDeleteAssets?: (assets: EditorAsset[]) => void;
|
||||
}) {
|
||||
@@ -94,6 +96,9 @@ export function useImageCanvasAssetLibrary({
|
||||
selectableAssets.every((asset) => selectedAssetIds.has(asset.id));
|
||||
|
||||
useEffect(() => {
|
||||
if (!canAccessProtectedData) {
|
||||
return undefined;
|
||||
}
|
||||
let cancelled = false;
|
||||
loadEditorAssetLibrary()
|
||||
.then((library) => {
|
||||
@@ -118,7 +123,7 @@ export function useImageCanvasAssetLibrary({
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [openEditorLoginModal]);
|
||||
}, [canAccessProtectedData, openEditorLoginModal]);
|
||||
|
||||
const resolveAssetFolderId = useCallback(
|
||||
(clientX: number, clientY: number) => {
|
||||
|
||||
@@ -43,7 +43,11 @@ function createLayer(id: string): CanvasLayer {
|
||||
};
|
||||
}
|
||||
|
||||
function ProjectPersistenceHarness() {
|
||||
function ProjectPersistenceHarness({
|
||||
canAccessProtectedData = true,
|
||||
}: {
|
||||
canAccessProtectedData?: boolean;
|
||||
}) {
|
||||
const [layers, setLayers] = useState<CanvasLayer[]>([]);
|
||||
const [viewport, setViewport] = useState<CanvasViewport>({
|
||||
x: 0,
|
||||
@@ -79,6 +83,7 @@ function ProjectPersistenceHarness() {
|
||||
},
|
||||
layers,
|
||||
viewport,
|
||||
canAccessProtectedData,
|
||||
openEditorLoginModal: vi.fn(),
|
||||
});
|
||||
|
||||
@@ -169,4 +174,12 @@ describe('useImageCanvasProjectPersistence', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('does not load protected project data before login is available', () => {
|
||||
render(<ProjectPersistenceHarness canAccessProtectedData={false} />);
|
||||
|
||||
expect(loadOrCreateRecentEditorProjectMock).not.toHaveBeenCalled();
|
||||
expect(loadEditorProjectMock).not.toHaveBeenCalled();
|
||||
expect(screen.getByTestId('project-id').textContent).toBe('-');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -46,12 +46,14 @@ export function useImageCanvasProjectPersistence({
|
||||
setters,
|
||||
layers,
|
||||
viewport,
|
||||
canAccessProtectedData,
|
||||
openEditorLoginModal,
|
||||
}: {
|
||||
refs: ImageCanvasProjectPersistenceRefs;
|
||||
setters: ImageCanvasProjectPersistenceSetters;
|
||||
layers: CanvasLayer[];
|
||||
viewport: CanvasViewport;
|
||||
canAccessProtectedData: boolean;
|
||||
openEditorLoginModal: (postLoginAction?: (() => void) | null) => void;
|
||||
}) {
|
||||
const projectIdRef = useRef<string | null>(null);
|
||||
@@ -145,6 +147,10 @@ export function useImageCanvasProjectPersistence({
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!canAccessProtectedData) {
|
||||
setIsProjectReady(false);
|
||||
return undefined;
|
||||
}
|
||||
let cancelled = false;
|
||||
const projectIdFromQuery =
|
||||
typeof window === 'undefined'
|
||||
@@ -202,7 +208,12 @@ export function useImageCanvasProjectPersistence({
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [createProjectResourceForLayer, openEditorLoginModal, setters]);
|
||||
}, [
|
||||
canAccessProtectedData,
|
||||
createProjectResourceForLayer,
|
||||
openEditorLoginModal,
|
||||
setters,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!projectId || !isProjectReady) {
|
||||
|
||||
Reference in New Issue
Block a user