mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-07 09:21:24 +08:00
fix(core): properties adapter reactivitiy issue (#5661)
This commit is contained in:
@@ -13,9 +13,9 @@ import { createAffineCloudBlobStorage } from '@affine/workspace-impl';
|
|||||||
import { assertEquals } from '@blocksuite/global/utils';
|
import { assertEquals } from '@blocksuite/global/utils';
|
||||||
import { Workspace } from '@blocksuite/store';
|
import { Workspace } from '@blocksuite/store';
|
||||||
import { revertUpdate } from '@toeverything/y-indexeddb';
|
import { revertUpdate } from '@toeverything/y-indexeddb';
|
||||||
import { useMemo } from 'react';
|
import { useEffect, useMemo } from 'react';
|
||||||
import useSWRImmutable from 'swr/immutable';
|
import useSWRImmutable from 'swr/immutable';
|
||||||
import { applyUpdate } from 'yjs';
|
import { applyUpdate, encodeStateAsUpdate } from 'yjs';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
useMutateQueryResource,
|
useMutateQueryResource,
|
||||||
@@ -105,7 +105,7 @@ const snapshotFetcher = async (
|
|||||||
const workspaceMap = new Map<string, Workspace>();
|
const workspaceMap = new Map<string, Workspace>();
|
||||||
|
|
||||||
// assume the workspace is a cloud workspace since the history feature is only enabled for cloud workspace
|
// assume the workspace is a cloud workspace since the history feature is only enabled for cloud workspace
|
||||||
const getOrCreateWorkspace = (workspaceId: string) => {
|
const getOrCreateShellWorkspace = (workspaceId: string) => {
|
||||||
let workspace = workspaceMap.get(workspaceId);
|
let workspace = workspaceMap.get(workspaceId);
|
||||||
if (!workspace) {
|
if (!workspace) {
|
||||||
const blobStorage = createAffineCloudBlobStorage(workspaceId);
|
const blobStorage = createAffineCloudBlobStorage(workspaceId);
|
||||||
@@ -120,6 +120,7 @@ const getOrCreateWorkspace = (workspaceId: string) => {
|
|||||||
schema: globalBlockSuiteSchema,
|
schema: globalBlockSuiteSchema,
|
||||||
});
|
});
|
||||||
workspaceMap.set(workspaceId, workspace);
|
workspaceMap.set(workspaceId, workspace);
|
||||||
|
workspace.doc.emit('sync', []);
|
||||||
}
|
}
|
||||||
return workspace;
|
return workspace;
|
||||||
};
|
};
|
||||||
@@ -143,17 +144,17 @@ export const usePageHistory = (
|
|||||||
|
|
||||||
// workspace id + page id + timestamp + snapshot -> Page (to be used for rendering in blocksuite editor)
|
// workspace id + page id + timestamp + snapshot -> Page (to be used for rendering in blocksuite editor)
|
||||||
export const useSnapshotPage = (
|
export const useSnapshotPage = (
|
||||||
workspaceId: string,
|
workspace: Workspace,
|
||||||
pageDocId: string,
|
pageDocId: string,
|
||||||
ts?: string
|
ts?: string
|
||||||
) => {
|
) => {
|
||||||
const snapshot = usePageHistory(workspaceId, pageDocId, ts);
|
const snapshot = usePageHistory(workspace.id, pageDocId, ts);
|
||||||
const page = useMemo(() => {
|
const page = useMemo(() => {
|
||||||
if (!ts) {
|
if (!ts) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const pageId = pageDocId + '-' + ts;
|
const pageId = pageDocId + '-' + ts;
|
||||||
const historyShellWorkspace = getOrCreateWorkspace(workspaceId);
|
const historyShellWorkspace = getOrCreateShellWorkspace(workspace.id);
|
||||||
let page = historyShellWorkspace.getPage(pageId);
|
let page = historyShellWorkspace.getPage(pageId);
|
||||||
if (!page && snapshot) {
|
if (!page && snapshot) {
|
||||||
page = historyShellWorkspace.createPage({
|
page = historyShellWorkspace.createPage({
|
||||||
@@ -169,7 +170,15 @@ export const useSnapshotPage = (
|
|||||||
.catch(console.error); // must load before applyUpdate
|
.catch(console.error); // must load before applyUpdate
|
||||||
}
|
}
|
||||||
return page ?? undefined;
|
return page ?? undefined;
|
||||||
}, [pageDocId, snapshot, ts, workspaceId]);
|
}, [pageDocId, snapshot, ts, workspace]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const historyShellWorkspace = getOrCreateShellWorkspace(workspace.id);
|
||||||
|
// apply the rootdoc's update to the current workspace
|
||||||
|
// this makes sure the page reference links are not deleted ones in the preview
|
||||||
|
const update = encodeStateAsUpdate(workspace.doc);
|
||||||
|
applyUpdate(historyShellWorkspace.doc, update);
|
||||||
|
}, [workspace]);
|
||||||
|
|
||||||
return page;
|
return page;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { ConfirmModal, Modal } from '@affine/component/ui/modal';
|
|||||||
import { openSettingModalAtom, type PageMode } from '@affine/core/atoms';
|
import { openSettingModalAtom, type PageMode } from '@affine/core/atoms';
|
||||||
import { useIsWorkspaceOwner } from '@affine/core/hooks/affine/use-is-workspace-owner';
|
import { useIsWorkspaceOwner } from '@affine/core/hooks/affine/use-is-workspace-owner';
|
||||||
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
|
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
|
||||||
|
import { useBlockSuiteWorkspacePageTitle } from '@affine/core/hooks/use-block-suite-workspace-page-title';
|
||||||
import { useUserSubscription } from '@affine/core/hooks/use-subscription';
|
import { useUserSubscription } from '@affine/core/hooks/use-subscription';
|
||||||
import { waitForCurrentWorkspaceAtom } from '@affine/core/modules/workspace';
|
import { waitForCurrentWorkspaceAtom } from '@affine/core/modules/workspace';
|
||||||
import { timestampToLocalTime } from '@affine/core/utils';
|
import { timestampToLocalTime } from '@affine/core/utils';
|
||||||
@@ -418,7 +419,7 @@ const PageHistoryManager = ({
|
|||||||
return workspace.getPage(pageId)?.spaceDoc.guid ?? pageId;
|
return workspace.getPage(pageId)?.spaceDoc.guid ?? pageId;
|
||||||
}, [pageId, workspace]);
|
}, [pageId, workspace]);
|
||||||
|
|
||||||
const snapshotPage = useSnapshotPage(workspaceId, pageDocId, activeVersion);
|
const snapshotPage = useSnapshotPage(workspace, pageDocId, activeVersion);
|
||||||
|
|
||||||
const t = useAFFiNEI18N();
|
const t = useAFFiNEI18N();
|
||||||
|
|
||||||
@@ -440,10 +441,7 @@ const PageHistoryManager = ({
|
|||||||
const defaultPreviewPageMode = useAtomValue(currentModeAtom);
|
const defaultPreviewPageMode = useAtomValue(currentModeAtom);
|
||||||
const [mode, setMode] = useState<PageMode>(defaultPreviewPageMode);
|
const [mode, setMode] = useState<PageMode>(defaultPreviewPageMode);
|
||||||
|
|
||||||
const title = useMemo(
|
const title = useBlockSuiteWorkspacePageTitle(workspace, pageId);
|
||||||
() => workspace.getPage(pageId)?.meta.title || t['Untitled'](),
|
|
||||||
[pageId, t, workspace]
|
|
||||||
);
|
|
||||||
|
|
||||||
const [showRestoreConfirmModal, setShowRestoreConfirmModal] = useState(false);
|
const [showRestoreConfirmModal, setShowRestoreConfirmModal] = useState(false);
|
||||||
|
|
||||||
|
|||||||
+2
@@ -51,6 +51,8 @@ beforeEach(async () => {
|
|||||||
|
|
||||||
blockSuiteWorkspace = workspace.blockSuiteWorkspace;
|
blockSuiteWorkspace = workspace.blockSuiteWorkspace;
|
||||||
|
|
||||||
|
blockSuiteWorkspace.doc.emit('sync', []);
|
||||||
|
|
||||||
const initPage = async (page: Page) => {
|
const initPage = async (page: Page) => {
|
||||||
await page.waitForLoaded();
|
await page.waitForLoaded();
|
||||||
expect(page).not.toBeNull();
|
expect(page).not.toBeNull();
|
||||||
|
|||||||
@@ -1,20 +1,25 @@
|
|||||||
import type { Workspace } from '@blocksuite/store';
|
import type { Workspace } from '@blocksuite/store';
|
||||||
import { useAtomValue } from 'jotai';
|
import { useAtomValue } from 'jotai';
|
||||||
import { useEffect, useMemo, useReducer } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import type { WorkspacePropertiesAdapter } from '../modules/workspace/properties';
|
||||||
import {
|
import {
|
||||||
currentWorkspacePropertiesAdapterAtom,
|
currentWorkspacePropertiesAdapterAtom,
|
||||||
WorkspacePropertiesAdapter,
|
workspaceAdapterAtomFamily,
|
||||||
} from '../modules/workspace/properties';
|
} from '../modules/workspace/properties';
|
||||||
|
|
||||||
|
function getProxy<T extends object>(obj: T) {
|
||||||
|
return new Proxy(obj, {});
|
||||||
|
}
|
||||||
|
|
||||||
const useReactiveAdapter = (adapter: WorkspacePropertiesAdapter) => {
|
const useReactiveAdapter = (adapter: WorkspacePropertiesAdapter) => {
|
||||||
const [, forceUpdate] = useReducer(c => c + 1, 0);
|
const [proxy, setProxy] = useState(adapter);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// todo: track which properties are used and then filter by property path change
|
// todo: track which properties are used and then filter by property path change
|
||||||
// using Y.YEvent.path
|
// using Y.YEvent.path
|
||||||
function observe() {
|
function observe() {
|
||||||
forceUpdate();
|
setProxy(getProxy(adapter));
|
||||||
}
|
}
|
||||||
adapter.properties.observeDeep(observe);
|
adapter.properties.observeDeep(observe);
|
||||||
return () => {
|
return () => {
|
||||||
@@ -22,7 +27,7 @@ const useReactiveAdapter = (adapter: WorkspacePropertiesAdapter) => {
|
|||||||
};
|
};
|
||||||
}, [adapter]);
|
}, [adapter]);
|
||||||
|
|
||||||
return adapter;
|
return proxy;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function useCurrentWorkspacePropertiesAdapter() {
|
export function useCurrentWorkspacePropertiesAdapter() {
|
||||||
@@ -31,9 +36,6 @@ export function useCurrentWorkspacePropertiesAdapter() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useWorkspacePropertiesAdapter(workspace: Workspace) {
|
export function useWorkspacePropertiesAdapter(workspace: Workspace) {
|
||||||
const adapter = useMemo(
|
const adapter = useAtomValue(workspaceAdapterAtomFamily(workspace));
|
||||||
() => new WorkspacePropertiesAdapter(workspace),
|
|
||||||
[workspace]
|
|
||||||
);
|
|
||||||
return useReactiveAdapter(adapter);
|
return useReactiveAdapter(adapter);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,21 @@
|
|||||||
import type { Workspace } from '@affine/workspace/workspace';
|
import type { Workspace as BlockSuiteWorkspace } from '@blocksuite/store';
|
||||||
import { atomWithObservable } from 'jotai/utils';
|
import { atom } from 'jotai';
|
||||||
import { filter, map, of } from 'rxjs';
|
import { atomFamily } from 'jotai/utils';
|
||||||
|
|
||||||
import { currentWorkspaceAtom } from '../atoms';
|
import { waitForCurrentWorkspaceAtom } from '../atoms';
|
||||||
import { WorkspacePropertiesAdapter } from './adapter';
|
import { WorkspacePropertiesAdapter } from './adapter';
|
||||||
|
|
||||||
export const currentWorkspacePropertiesAdapterAtom =
|
// todo: remove the inner atom when workspace is closed by using workspaceAdapterAtomFamily.remove
|
||||||
atomWithObservable<WorkspacePropertiesAdapter>(get => {
|
export const workspaceAdapterAtomFamily = atomFamily(
|
||||||
return of(get(currentWorkspaceAtom)).pipe(
|
(workspace: BlockSuiteWorkspace) => {
|
||||||
filter((workspace): workspace is Workspace => !!workspace),
|
return atom(async () => {
|
||||||
map(workspace => {
|
await workspace.doc.whenLoaded;
|
||||||
return new WorkspacePropertiesAdapter(workspace.blockSuiteWorkspace);
|
return new WorkspacePropertiesAdapter(workspace);
|
||||||
})
|
});
|
||||||
);
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
|
export const currentWorkspacePropertiesAdapterAtom = atom(async get => {
|
||||||
|
const workspace = await get(waitForCurrentWorkspaceAtom);
|
||||||
|
return get(workspaceAdapterAtomFamily(workspace.blockSuiteWorkspace));
|
||||||
|
});
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ export const loader: LoaderFunction = async ({ params }) => {
|
|||||||
assertDownloadResponse(response);
|
assertDownloadResponse(response);
|
||||||
const { arrayBuffer } = response;
|
const { arrayBuffer } = response;
|
||||||
applyUpdate(workspace.doc, new Uint8Array(arrayBuffer));
|
applyUpdate(workspace.doc, new Uint8Array(arrayBuffer));
|
||||||
|
workspace.doc.emit('sync', []);
|
||||||
}
|
}
|
||||||
const page = workspace.getPage(pageId);
|
const page = workspace.getPage(pageId);
|
||||||
assertExists(page, 'cannot find page');
|
assertExists(page, 'cannot find page');
|
||||||
|
|||||||
Reference in New Issue
Block a user