feat(core): ignore empty journal for global useBlockSuitePageMeta hook (#5715)

[TOV-494](https://linear.app/affine-design/issue/TOV-494/空-journal-的隐藏处理)
This commit is contained in:
Cats Juice
2024-02-22 08:09:33 +00:00
parent 0fff5588e6
commit 390fb90a8b
6 changed files with 145 additions and 94 deletions

View File

@@ -1,47 +0,0 @@
/**
* @vitest-environment happy-dom
*/
import 'fake-indexeddb/auto';
import { __unstableSchemas, AffineSchemas } from '@blocksuite/blocks/models';
import { Schema, Workspace } from '@blocksuite/store';
import { renderHook } from '@testing-library/react';
import { initEmptyPage } from '@toeverything/infra/blocksuite';
import { beforeEach, describe, expect, test } from 'vitest';
import { useBlockSuitePageMeta } from '../use-block-suite-page-meta';
import { useBlockSuiteWorkspaceHelper } from '../use-block-suite-workspace-helper';
let blockSuiteWorkspace: Workspace;
const schema = new Schema();
schema.register(AffineSchemas).register(__unstableSchemas);
beforeEach(async () => {
blockSuiteWorkspace = new Workspace({
id: 'test',
schema,
});
initEmptyPage(blockSuiteWorkspace.createPage({ id: 'page0' }));
initEmptyPage(blockSuiteWorkspace.createPage({ id: 'page1' }));
initEmptyPage(blockSuiteWorkspace.createPage({ id: 'page2' }));
});
describe('useBlockSuiteWorkspaceHelper', () => {
test('should create page', () => {
expect(blockSuiteWorkspace.meta.pageMetas.length).toBe(3);
const helperHook = renderHook(() =>
useBlockSuiteWorkspaceHelper(blockSuiteWorkspace)
);
const pageMetaHook = renderHook(() =>
useBlockSuitePageMeta(blockSuiteWorkspace)
);
expect(pageMetaHook.result.current.length).toBe(3);
expect(blockSuiteWorkspace.meta.pageMetas.length).toBe(3);
const page = helperHook.result.current.createPage('page4');
expect(page.id).toBe('page4');
expect(blockSuiteWorkspace.meta.pageMetas.length).toBe(4);
pageMetaHook.rerender();
expect(pageMetaHook.result.current.length).toBe(4);
});
});

View File

@@ -0,0 +1,68 @@
/**
* @vitest-environment happy-dom
*/
import 'fake-indexeddb/auto';
import { configureTestingEnvironment } from '@affine/core/testing';
import { renderHook } from '@testing-library/react';
import type { Workspace } from '@toeverything/infra';
import { initEmptyPage, ServiceProviderContext } from '@toeverything/infra';
import type { PropsWithChildren } from 'react';
import { beforeEach, describe, expect, test, vi } from 'vitest';
import { useBlockSuitePageMeta } from '../use-block-suite-page-meta';
import { useBlockSuiteWorkspaceHelper } from '../use-block-suite-workspace-helper';
const configureTestingWorkspace = async () => {
const { workspace } = await configureTestingEnvironment();
const blockSuiteWorkspace = workspace.blockSuiteWorkspace;
// await initEmptyPage(blockSuiteWorkspace.createPage({ id: 'page-0' }));
initEmptyPage(blockSuiteWorkspace.createPage({ id: 'page1' }));
initEmptyPage(blockSuiteWorkspace.createPage({ id: 'page2' }));
return workspace;
};
beforeEach(async () => {
vi.useFakeTimers({ toFake: ['requestIdleCallback'] });
});
const getWrapper = (workspace: Workspace) =>
function Provider({ children }: PropsWithChildren) {
return (
<ServiceProviderContext.Provider value={workspace.services}>
{children}
</ServiceProviderContext.Provider>
);
};
describe('useBlockSuiteWorkspaceHelper', () => {
test('should create page', async () => {
const workspace = await configureTestingWorkspace();
const blockSuiteWorkspace = workspace.blockSuiteWorkspace;
const Wrapper = getWrapper(workspace);
expect(blockSuiteWorkspace.meta.pageMetas.length).toBe(3);
const helperHook = renderHook(
() => useBlockSuiteWorkspaceHelper(blockSuiteWorkspace),
{
wrapper: Wrapper,
}
);
const pageMetaHook = renderHook(
() => useBlockSuitePageMeta(blockSuiteWorkspace),
{
wrapper: Wrapper,
}
);
await new Promise(resolve => setTimeout(resolve));
expect(pageMetaHook.result.current.length).toBe(3);
expect(blockSuiteWorkspace.meta.pageMetas.length).toBe(3);
const page = helperHook.result.current.createPage('page4');
expect(page.id).toBe('page4');
expect(blockSuiteWorkspace.meta.pageMetas.length).toBe(4);
pageMetaHook.rerender();
expect(pageMetaHook.result.current.length).toBe(4);
});
});

View File

@@ -4,7 +4,7 @@ import { use } from 'foxact/use';
import { useEffect, useMemo, useState } from 'react';
import { WorkspacePropertiesAdapter } from '../modules/workspace/properties';
import { useBlockSuitePageMeta } from './use-block-suite-page-meta';
import { useAllBlockSuitePageMeta } from './use-all-block-suite-page-meta';
function getProxy<T extends object>(obj: T) {
return new Proxy(obj, {});
@@ -14,7 +14,7 @@ const useReactiveAdapter = (adapter: WorkspacePropertiesAdapter) => {
use(adapter.workspace.blockSuiteWorkspace.doc.whenSynced);
const [proxy, setProxy] = useState(adapter);
// fixme: this is a hack to force re-render when default meta changed
useBlockSuitePageMeta(adapter.workspace.blockSuiteWorkspace);
useAllBlockSuitePageMeta(adapter.workspace.blockSuiteWorkspace);
useEffect(() => {
// todo: track which properties are used and then filter by property path change
// using Y.YEvent.path

View File

@@ -0,0 +1,25 @@
import type { PageMeta, Workspace } from '@blocksuite/store';
import type { Atom } from 'jotai';
import { atom, useAtomValue } from 'jotai';
const weakMap = new WeakMap<Workspace, Atom<PageMeta[]>>();
// this hook is extracted from './use-block-suite-page-meta.ts' to avoid circular dependency
export function useAllBlockSuitePageMeta(
blockSuiteWorkspace: Workspace
): PageMeta[] {
if (!weakMap.has(blockSuiteWorkspace)) {
const baseAtom = atom<PageMeta[]>(blockSuiteWorkspace.meta.pageMetas);
weakMap.set(blockSuiteWorkspace, baseAtom);
baseAtom.onMount = set => {
set(blockSuiteWorkspace.meta.pageMetas);
const dispose = blockSuiteWorkspace.meta.pageMetasUpdated.on(() => {
set(blockSuiteWorkspace.meta.pageMetas);
});
return () => {
dispose.dispose();
};
};
}
return useAtomValue(weakMap.get(blockSuiteWorkspace) as Atom<PageMeta[]>);
}

View File

@@ -1,29 +1,26 @@
import type { PageBlockModel } from '@blocksuite/blocks';
import { assertExists } from '@blocksuite/global/utils';
import type { PageMeta, Workspace } from '@blocksuite/store';
import type { Atom } from 'jotai';
import { atom, useAtomValue } from 'jotai';
import { useMemo } from 'react';
const weakMap = new WeakMap<Workspace, Atom<PageMeta[]>>();
import { useAllBlockSuitePageMeta } from './use-all-block-suite-page-meta';
import { useJournalHelper } from './use-journal';
export function useBlockSuitePageMeta(
blockSuiteWorkspace: Workspace
): PageMeta[] {
if (!weakMap.has(blockSuiteWorkspace)) {
const baseAtom = atom<PageMeta[]>(blockSuiteWorkspace.meta.pageMetas);
weakMap.set(blockSuiteWorkspace, baseAtom);
baseAtom.onMount = set => {
set(blockSuiteWorkspace.meta.pageMetas);
const dispose = blockSuiteWorkspace.meta.pageMetasUpdated.on(() => {
set(blockSuiteWorkspace.meta.pageMetas);
});
return () => {
dispose.dispose();
};
};
}
return useAtomValue(weakMap.get(blockSuiteWorkspace) as Atom<PageMeta[]>);
/**
* Get pageMetas excluding journal pages without updatedDate
* If you want to get all pageMetas, use `useAllBlockSuitePageMeta` instead
* @returns
*/
export function useBlockSuitePageMeta(blocksuiteWorkspace: Workspace) {
const pageMetas = useAllBlockSuitePageMeta(blocksuiteWorkspace);
const { isPageJournal } = useJournalHelper(blocksuiteWorkspace);
return useMemo(
() =>
pageMetas.filter(
pageMeta => !isPageJournal(pageMeta.id) || !!pageMeta.updatedDate
),
[isPageJournal, pageMetas]
);
}
export function usePageMetaHelper(blockSuiteWorkspace: Workspace) {