mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-18 10:36:22 +08:00
refactor: workspace loading logic (#1966)
This commit is contained in:
@@ -5,7 +5,10 @@ import 'fake-indexeddb/auto';
|
||||
|
||||
import assert from 'node:assert';
|
||||
|
||||
import { jotaiWorkspacesAtom } from '@affine/workspace/atom';
|
||||
import {
|
||||
rootCurrentWorkspaceIdAtom,
|
||||
rootWorkspacesMetadataAtom,
|
||||
} from '@affine/workspace/atom';
|
||||
import type { LocalWorkspace } from '@affine/workspace/type';
|
||||
import { WorkspaceFlavour } from '@affine/workspace/type';
|
||||
import type { PageBlockModel } from '@blocksuite/blocks';
|
||||
@@ -38,11 +41,7 @@ import {
|
||||
useRecentlyViewed,
|
||||
useSyncRecentViewsWithRouter,
|
||||
} from '../use-recent-views';
|
||||
import {
|
||||
REDIRECT_TIMEOUT,
|
||||
useSyncRouterWithCurrentWorkspaceAndPage,
|
||||
} from '../use-sync-router-with-current-workspace-and-page';
|
||||
import { useWorkspaces, useWorkspacesHelper } from '../use-workspaces';
|
||||
import { useAppHelper, useWorkspaces } from '../use-workspaces';
|
||||
|
||||
vi.mock(
|
||||
'../../components/blocksuite/header/editor-mode-switch/CustomLottie',
|
||||
@@ -167,23 +166,24 @@ describe('usePageMetas', async () => {
|
||||
describe('useWorkspacesHelper', () => {
|
||||
test('basic', async () => {
|
||||
const { ProviderWrapper, store } = await getJotaiContext();
|
||||
const workspaceHelperHook = renderHook(() => useWorkspacesHelper(), {
|
||||
const workspaceHelperHook = renderHook(() => useAppHelper(), {
|
||||
wrapper: ProviderWrapper,
|
||||
});
|
||||
const id = await workspaceHelperHook.result.current.createLocalWorkspace(
|
||||
'test'
|
||||
);
|
||||
const workspaces = await store.get(workspacesAtom);
|
||||
expect(workspaces.length).toBe(1);
|
||||
expect(workspaces[0].id).toBe(id);
|
||||
expect(workspaces.length).toBe(2);
|
||||
expect(workspaces[1].id).toBe(id);
|
||||
const workspacesHook = renderHook(() => useWorkspaces(), {
|
||||
wrapper: ProviderWrapper,
|
||||
});
|
||||
store.set(rootCurrentWorkspaceIdAtom, workspacesHook.result.current[1].id);
|
||||
await store.get(currentWorkspaceAtom);
|
||||
const currentWorkspaceHook = renderHook(() => useCurrentWorkspace(), {
|
||||
wrapper: ProviderWrapper,
|
||||
});
|
||||
currentWorkspaceHook.result.current[1](workspacesHook.result.current[0].id);
|
||||
currentWorkspaceHook.result.current[1](workspacesHook.result.current[1].id);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -198,115 +198,35 @@ describe('useWorkspaces', () => {
|
||||
|
||||
test('mutation', async () => {
|
||||
const { ProviderWrapper, store } = await getJotaiContext();
|
||||
const { result } = renderHook(() => useWorkspacesHelper(), {
|
||||
const { result } = renderHook(() => useAppHelper(), {
|
||||
wrapper: ProviderWrapper,
|
||||
});
|
||||
{
|
||||
const workspaces = await store.get(workspacesAtom);
|
||||
expect(workspaces.length).toEqual(1);
|
||||
}
|
||||
await result.current.createLocalWorkspace('test');
|
||||
const workspaces = await store.get(workspacesAtom);
|
||||
console.log(workspaces);
|
||||
expect(workspaces.length).toEqual(1);
|
||||
{
|
||||
const workspaces = await store.get(workspacesAtom);
|
||||
expect(workspaces.length).toEqual(2);
|
||||
}
|
||||
const { result: result2 } = renderHook(() => useWorkspaces(), {
|
||||
wrapper: ProviderWrapper,
|
||||
});
|
||||
expect(result2.current.length).toEqual(1);
|
||||
const firstWorkspace = result2.current[0];
|
||||
expect(result2.current.length).toEqual(2);
|
||||
const firstWorkspace = result2.current[1];
|
||||
expect(firstWorkspace.flavour).toBe('local');
|
||||
assert(firstWorkspace.flavour === WorkspaceFlavour.LOCAL);
|
||||
expect(firstWorkspace.blockSuiteWorkspace.meta.name).toBe('test');
|
||||
});
|
||||
});
|
||||
|
||||
describe('useSyncRouterWithCurrentWorkspaceAndPage', () => {
|
||||
test('from "/"', async () => {
|
||||
const { ProviderWrapper, store } = await getJotaiContext();
|
||||
const mutationHook = renderHook(() => useWorkspacesHelper(), {
|
||||
wrapper: ProviderWrapper,
|
||||
});
|
||||
const id = await mutationHook.result.current.createLocalWorkspace('test0');
|
||||
await store.get(currentWorkspaceAtom);
|
||||
mutationHook.rerender();
|
||||
mutationHook.result.current.createWorkspacePage(id, 'page0');
|
||||
const routerHook = renderHook(() => useRouter());
|
||||
await routerHook.result.current.push('/');
|
||||
routerHook.rerender();
|
||||
expect(routerHook.result.current.asPath).toBe('/');
|
||||
renderHook(
|
||||
({ router }) => useSyncRouterWithCurrentWorkspaceAndPage(router),
|
||||
{
|
||||
wrapper: ProviderWrapper,
|
||||
initialProps: {
|
||||
router: routerHook.result.current,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
expect(routerHook.result.current.asPath).toBe(`/workspace/${id}/page0`);
|
||||
});
|
||||
|
||||
test('from empty workspace', async () => {
|
||||
const { ProviderWrapper, store } = await getJotaiContext();
|
||||
const mutationHook = renderHook(() => useWorkspacesHelper(), {
|
||||
wrapper: ProviderWrapper,
|
||||
});
|
||||
const id = await mutationHook.result.current.createLocalWorkspace('test0');
|
||||
const workspaces = await store.get(workspacesAtom);
|
||||
expect(workspaces.length).toEqual(1);
|
||||
mutationHook.rerender();
|
||||
const routerHook = renderHook(() => useRouter());
|
||||
await routerHook.result.current.push(`/workspace/${id}/not_exist`);
|
||||
routerHook.rerender();
|
||||
expect(routerHook.result.current.asPath).toBe(`/workspace/${id}/not_exist`);
|
||||
renderHook(
|
||||
({ router }) => useSyncRouterWithCurrentWorkspaceAndPage(router),
|
||||
{
|
||||
wrapper: ProviderWrapper,
|
||||
initialProps: {
|
||||
router: routerHook.result.current,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, REDIRECT_TIMEOUT + 50));
|
||||
|
||||
expect(routerHook.result.current.asPath).toBe(`/workspace/${id}/all`);
|
||||
});
|
||||
|
||||
test('from incorrect "/workspace/[workspaceId]/[pageId]"', async () => {
|
||||
const { ProviderWrapper, store } = await getJotaiContext();
|
||||
const mutationHook = renderHook(() => useWorkspacesHelper(), {
|
||||
wrapper: ProviderWrapper,
|
||||
});
|
||||
const id = await mutationHook.result.current.createLocalWorkspace('test0');
|
||||
const workspaces = await store.get(workspacesAtom);
|
||||
expect(workspaces.length).toEqual(1);
|
||||
mutationHook.rerender();
|
||||
mutationHook.result.current.createWorkspacePage(id, 'page0');
|
||||
const routerHook = renderHook(() => useRouter());
|
||||
await routerHook.result.current.push(`/workspace/${id}/not_exist`);
|
||||
routerHook.rerender();
|
||||
expect(routerHook.result.current.asPath).toBe(`/workspace/${id}/not_exist`);
|
||||
renderHook(
|
||||
({ router }) => useSyncRouterWithCurrentWorkspaceAndPage(router),
|
||||
{
|
||||
wrapper: ProviderWrapper,
|
||||
initialProps: {
|
||||
router: routerHook.result.current,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, REDIRECT_TIMEOUT + 50));
|
||||
|
||||
expect(routerHook.result.current.asPath).toBe(`/workspace/${id}/page0`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useRecentlyViewed', () => {
|
||||
test('basic', async () => {
|
||||
const { ProviderWrapper, store } = await getJotaiContext();
|
||||
const workspaceId = blockSuiteWorkspace.id;
|
||||
const pageId = 'page0';
|
||||
store.set(jotaiWorkspacesAtom, [
|
||||
store.set(rootWorkspacesMetadataAtom, [
|
||||
{
|
||||
id: workspaceId,
|
||||
flavour: WorkspaceFlavour.LOCAL,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { jotaiStore, jotaiWorkspacesAtom } from '@affine/workspace/atom';
|
||||
import { rootStore, rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
||||
import type { AffineWorkspace } from '@affine/workspace/type';
|
||||
import { useCallback } from 'react';
|
||||
import useSWR from 'swr';
|
||||
@@ -16,7 +16,7 @@ export function useToggleWorkspacePublish(workspace: AffineWorkspace) {
|
||||
});
|
||||
await mutate(QueryKey.getWorkspaces);
|
||||
// fixme: remove force update
|
||||
jotaiStore.set(jotaiWorkspacesAtom, ws => [...ws]);
|
||||
rootStore.set(rootWorkspacesMetadataAtom, ws => [...ws]);
|
||||
},
|
||||
[mutate, workspace.id]
|
||||
);
|
||||
|
||||
@@ -1,43 +1,12 @@
|
||||
import type { Page } from '@blocksuite/store';
|
||||
import { atom, useAtom, useAtomValue } from 'jotai';
|
||||
|
||||
import { currentPageIdAtom } from '../../atoms';
|
||||
import { currentWorkspaceAtom } from './use-current-workspace';
|
||||
|
||||
export const currentPageAtom = atom<Promise<Page | null>>(async get => {
|
||||
const id = get(currentPageIdAtom);
|
||||
const workspace = await get(currentWorkspaceAtom);
|
||||
if (!workspace || !id) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
const page = workspace.blockSuiteWorkspace.getPage(id);
|
||||
if (page) {
|
||||
return page;
|
||||
} else {
|
||||
return new Promise(resolve => {
|
||||
const dispose = workspace.blockSuiteWorkspace.slots.pageAdded.on(
|
||||
pageId => {
|
||||
if (pageId === id) {
|
||||
resolve(page);
|
||||
dispose.dispose();
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export function useCurrentPage(): Page | null {
|
||||
return useAtomValue(currentPageAtom);
|
||||
}
|
||||
import { rootCurrentPageIdAtom } from '@affine/workspace/atom';
|
||||
import { useAtom } from 'jotai';
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @deprecated Use `rootCurrentPageIdAtom` directly instead.
|
||||
*/
|
||||
export function useCurrentPageId(): [
|
||||
string | null,
|
||||
(newId: string | null) => void
|
||||
] {
|
||||
return useAtom(currentPageIdAtom);
|
||||
return useAtom(rootCurrentPageIdAtom);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,15 @@
|
||||
import { atomWithSyncStorage } from '@affine/jotai';
|
||||
import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai';
|
||||
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import {
|
||||
currentPageIdAtom,
|
||||
currentWorkspaceIdAtom,
|
||||
workspacesAtom,
|
||||
} from '../../atoms';
|
||||
import { currentPageIdAtom, currentWorkspaceIdAtom } from '../../atoms';
|
||||
import { rootCurrentWorkspaceAtom } from '../../atoms/root';
|
||||
import type { AllWorkspace } from '../../shared';
|
||||
|
||||
export const currentWorkspaceAtom = atom<Promise<AllWorkspace | null>>(
|
||||
async get => {
|
||||
const id = get(currentWorkspaceIdAtom);
|
||||
const workspaces = await get(workspacesAtom);
|
||||
return workspaces.find(workspace => workspace.id === id) ?? null;
|
||||
}
|
||||
);
|
||||
/**
|
||||
* @deprecated use `rootCurrentWorkspaceAtom` instead
|
||||
*/
|
||||
export const currentWorkspaceAtom = rootCurrentWorkspaceAtom;
|
||||
|
||||
export const lastWorkspaceIdAtom = atomWithSyncStorage<string | null>(
|
||||
'last_workspace_id',
|
||||
@@ -26,7 +20,7 @@ export function useCurrentWorkspace(): [
|
||||
AllWorkspace | null,
|
||||
(id: string | null) => void
|
||||
] {
|
||||
const currentWorkspace = useAtomValue(currentWorkspaceAtom);
|
||||
const currentWorkspace = useAtomValue(rootCurrentWorkspaceAtom);
|
||||
const [, setId] = useAtom(currentWorkspaceIdAtom);
|
||||
const [, setPageId] = useAtom(currentPageIdAtom);
|
||||
const setLast = useSetAtom(lastWorkspaceIdAtom);
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import { jotaiStore, jotaiWorkspacesAtom } from '@affine/workspace/atom';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { WorkspacePlugins } from '../plugins';
|
||||
|
||||
export function useCreateFirstWorkspace() {
|
||||
// may not need use effect at all, right?
|
||||
useEffect(() => {
|
||||
return jotaiStore.sub(jotaiWorkspacesAtom, () => {
|
||||
const workspaces = jotaiStore.get(jotaiWorkspacesAtom);
|
||||
|
||||
if (workspaces.length === 0) {
|
||||
createFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a first workspace, only just once for a browser
|
||||
*/
|
||||
async function createFirst() {
|
||||
const Plugins = Object.values(WorkspacePlugins).sort(
|
||||
(a, b) => a.loadPriority - b.loadPriority
|
||||
);
|
||||
|
||||
for (const Plugin of Plugins) {
|
||||
await Plugin.Events['app:first-init']?.();
|
||||
}
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import { rootCurrentPageIdAtom } from '@affine/workspace/atom';
|
||||
import { useAtom, useAtomValue } from 'jotai';
|
||||
import type { NextRouter } from 'next/router';
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
import { rootCurrentWorkspaceAtom } from '../atoms/root';
|
||||
export const HALT_PROBLEM_TIMEOUT = 1000;
|
||||
export function useRouterAndWorkspaceWithPageIdDefense(router: NextRouter) {
|
||||
const currentWorkspace = useAtomValue(rootCurrentWorkspaceAtom);
|
||||
const [currentPageId, setCurrentPageId] = useAtom(rootCurrentPageIdAtom);
|
||||
const fallbackModeRef = useRef(false);
|
||||
useEffect(() => {
|
||||
if (!router.isReady) {
|
||||
return;
|
||||
}
|
||||
const { workspaceId, pageId } = router.query;
|
||||
if (typeof pageId !== 'string') {
|
||||
console.warn('pageId is not a string', pageId);
|
||||
return;
|
||||
}
|
||||
if (typeof workspaceId !== 'string') {
|
||||
console.warn('workspaceId is not a string', workspaceId);
|
||||
return;
|
||||
}
|
||||
if (currentWorkspace?.id !== workspaceId) {
|
||||
console.warn('workspaceId is not currentWorkspace', workspaceId);
|
||||
return;
|
||||
}
|
||||
if (currentPageId !== pageId && !fallbackModeRef.current) {
|
||||
console.log('set current page id', pageId);
|
||||
setCurrentPageId(pageId);
|
||||
void router.push({
|
||||
pathname: '/workspace/[workspaceId]/[pageId]',
|
||||
query: {
|
||||
...router.query,
|
||||
workspaceId,
|
||||
pageId,
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [currentPageId, currentWorkspace.id, router, setCurrentPageId]);
|
||||
useEffect(() => {
|
||||
if (fallbackModeRef.current) {
|
||||
return;
|
||||
}
|
||||
const id = setTimeout(() => {
|
||||
if (currentPageId) {
|
||||
const page =
|
||||
currentWorkspace.blockSuiteWorkspace.getPage(currentPageId);
|
||||
if (!page) {
|
||||
const firstOne =
|
||||
currentWorkspace.blockSuiteWorkspace.meta.pageMetas.at(0);
|
||||
if (firstOne) {
|
||||
console.warn(
|
||||
'cannot find page',
|
||||
currentPageId,
|
||||
'so redirect to',
|
||||
firstOne.id
|
||||
);
|
||||
setCurrentPageId(firstOne.id);
|
||||
void router.push({
|
||||
pathname: '/workspace/[workspaceId]/[pageId]',
|
||||
query: {
|
||||
...router.query,
|
||||
workspaceId: currentWorkspace.id,
|
||||
pageId: firstOne.id,
|
||||
},
|
||||
});
|
||||
fallbackModeRef.current = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}, HALT_PROBLEM_TIMEOUT);
|
||||
return () => {
|
||||
clearTimeout(id);
|
||||
};
|
||||
}, [
|
||||
currentPageId,
|
||||
currentWorkspace.blockSuiteWorkspace,
|
||||
currentWorkspace.id,
|
||||
router,
|
||||
setCurrentPageId,
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import {
|
||||
rootCurrentPageIdAtom,
|
||||
rootCurrentWorkspaceIdAtom,
|
||||
rootWorkspacesMetadataAtom,
|
||||
} from '@affine/workspace/atom';
|
||||
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
|
||||
import type { NextRouter } from 'next/router';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export function useRouterWithWorkspaceIdDefense(router: NextRouter) {
|
||||
const metadata = useAtomValue(rootWorkspacesMetadataAtom);
|
||||
const [currentWorkspaceId, setCurrentWorkspaceId] = useAtom(
|
||||
rootCurrentWorkspaceIdAtom
|
||||
);
|
||||
const setCurrentPageId = useSetAtom(rootCurrentPageIdAtom);
|
||||
useEffect(() => {
|
||||
if (!router.isReady) {
|
||||
return;
|
||||
}
|
||||
if (!currentWorkspaceId) {
|
||||
return;
|
||||
}
|
||||
const exist = metadata.find(m => m.id === currentWorkspaceId);
|
||||
if (!exist) {
|
||||
// clean up
|
||||
setCurrentWorkspaceId(null);
|
||||
setCurrentPageId(null);
|
||||
const firstOne = metadata.at(0);
|
||||
if (!firstOne) {
|
||||
throw new Error('no workspace');
|
||||
}
|
||||
void router.push({
|
||||
pathname: '/workspace/[workspaceId]/all',
|
||||
query: {
|
||||
...router.query,
|
||||
workspaceId: firstOne.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [
|
||||
currentWorkspaceId,
|
||||
metadata,
|
||||
router,
|
||||
router.isReady,
|
||||
setCurrentPageId,
|
||||
setCurrentWorkspaceId,
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { rootCurrentPageIdAtom } from '@affine/workspace/atom';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import type { NextRouter } from 'next/router';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export function useSyncRouterWithCurrentPageId(router: NextRouter) {
|
||||
const setCurrentPageId = useSetAtom(rootCurrentPageIdAtom);
|
||||
useEffect(() => {
|
||||
if (!router.isReady) {
|
||||
return;
|
||||
}
|
||||
const pageId = router.query.pageId;
|
||||
if (typeof pageId === 'string') {
|
||||
console.log('set page id', pageId);
|
||||
setCurrentPageId(pageId);
|
||||
}
|
||||
}, [router.isReady, router.query.pageId, setCurrentPageId]);
|
||||
}
|
||||
@@ -1,215 +0,0 @@
|
||||
import { jotaiStore } from '@affine/workspace/atom';
|
||||
import { WorkspaceFlavour } from '@affine/workspace/type';
|
||||
import type { NextRouter } from 'next/router';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { currentPageIdAtom } from '../atoms';
|
||||
import type { AllWorkspace } from '../shared';
|
||||
import { WorkspaceSubPath } from '../shared';
|
||||
import { useCurrentPageId } from './current/use-current-page-id';
|
||||
import { useCurrentWorkspace } from './current/use-current-workspace';
|
||||
import { RouteLogic, useRouterHelper } from './use-router-helper';
|
||||
import { useWorkspaces } from './use-workspaces';
|
||||
|
||||
export function findSuitablePageId(
|
||||
workspace: AllWorkspace,
|
||||
targetId: string
|
||||
): string | null {
|
||||
switch (workspace.flavour) {
|
||||
case WorkspaceFlavour.AFFINE: {
|
||||
return (
|
||||
workspace.blockSuiteWorkspace.meta.pageMetas.find(
|
||||
page => page.id === targetId
|
||||
)?.id ??
|
||||
workspace.blockSuiteWorkspace.meta.pageMetas.at(0)?.id ??
|
||||
null
|
||||
);
|
||||
}
|
||||
case WorkspaceFlavour.LOCAL: {
|
||||
return (
|
||||
workspace.blockSuiteWorkspace.meta.pageMetas.find(
|
||||
page => page.id === targetId
|
||||
)?.id ??
|
||||
workspace.blockSuiteWorkspace.meta.pageMetas.at(0)?.id ??
|
||||
null
|
||||
);
|
||||
}
|
||||
case WorkspaceFlavour.PUBLIC: {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const REDIRECT_TIMEOUT = 1000;
|
||||
export function useSyncRouterWithCurrentWorkspaceAndPage(router: NextRouter) {
|
||||
const [currentWorkspace, setCurrentWorkspaceId] = useCurrentWorkspace();
|
||||
const [currentPageId, setCurrentPageId] = useCurrentPageId();
|
||||
const workspaces = useWorkspaces();
|
||||
const { jumpToSubPath } = useRouterHelper(router);
|
||||
useEffect(() => {
|
||||
const listener: Parameters<typeof router.events.on>[1] = (url: string) => {
|
||||
if (url.startsWith('/')) {
|
||||
const path = url.split('/');
|
||||
if (path.length === 4 && path[1] === 'workspace') {
|
||||
if (
|
||||
path[3] === 'all' ||
|
||||
path[3] === 'setting' ||
|
||||
path[3] === 'trash' ||
|
||||
path[3] === 'favorite' ||
|
||||
path[3] === 'shared'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
setCurrentWorkspaceId(path[2]);
|
||||
if (currentWorkspace && 'blockSuiteWorkspace' in currentWorkspace) {
|
||||
if (currentWorkspace.blockSuiteWorkspace.getPage(path[3])) {
|
||||
setCurrentPageId(path[3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
router.events.on('routeChangeStart', listener);
|
||||
return () => {
|
||||
router.events.off('routeChangeStart', listener);
|
||||
};
|
||||
}, [currentWorkspace, router, setCurrentPageId, setCurrentWorkspaceId]);
|
||||
useEffect(() => {
|
||||
if (!router.isReady) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
router.pathname === '/workspace/[workspaceId]/[pageId]' ||
|
||||
router.pathname === '/'
|
||||
) {
|
||||
const targetPageId = router.query.pageId;
|
||||
const targetWorkspaceId = router.query.workspaceId;
|
||||
if (currentWorkspace && currentPageId) {
|
||||
if (
|
||||
currentWorkspace.id === targetWorkspaceId &&
|
||||
currentPageId === targetPageId
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (
|
||||
typeof targetPageId !== 'string' ||
|
||||
typeof targetWorkspaceId !== 'string'
|
||||
) {
|
||||
if (router.asPath === '/') {
|
||||
const first = workspaces.at(0);
|
||||
if (first && 'blockSuiteWorkspace' in first) {
|
||||
const targetWorkspaceId = first.id;
|
||||
const targetPageId =
|
||||
first.blockSuiteWorkspace.meta.pageMetas.at(0)?.id;
|
||||
if (targetPageId) {
|
||||
setCurrentWorkspaceId(targetWorkspaceId);
|
||||
setCurrentPageId(targetPageId);
|
||||
router.push(`/workspace/${targetWorkspaceId}/${targetPageId}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!currentWorkspace) {
|
||||
const targetWorkspace = workspaces.find(
|
||||
workspace => workspace.id === targetPageId
|
||||
);
|
||||
if (targetWorkspace) {
|
||||
setCurrentWorkspaceId(targetWorkspace.id);
|
||||
router.push({
|
||||
query: {
|
||||
...router.query,
|
||||
workspaceId: targetWorkspace.id,
|
||||
},
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
const first = workspaces.at(0);
|
||||
if (first) {
|
||||
setCurrentWorkspaceId(first.id);
|
||||
router.push({
|
||||
query: {
|
||||
...router.query,
|
||||
workspaceId: first.id,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!currentPageId && currentWorkspace) {
|
||||
const targetId = findSuitablePageId(currentWorkspace, targetPageId);
|
||||
if (targetId) {
|
||||
setCurrentPageId(targetId);
|
||||
router.push({
|
||||
query: {
|
||||
...router.query,
|
||||
workspaceId: currentWorkspace.id,
|
||||
pageId: targetId,
|
||||
},
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
const dispose =
|
||||
currentWorkspace.blockSuiteWorkspace.slots.pageAdded.on(
|
||||
pageId => {
|
||||
if (pageId === targetPageId) {
|
||||
dispose.dispose();
|
||||
setCurrentPageId(pageId);
|
||||
router.push({
|
||||
query: {
|
||||
...router.query,
|
||||
workspaceId: currentWorkspace.id,
|
||||
pageId: targetPageId,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
const clearId = setTimeout(() => {
|
||||
const pageId = jotaiStore.get(currentPageIdAtom);
|
||||
if (pageId === null) {
|
||||
const id =
|
||||
currentWorkspace.blockSuiteWorkspace.meta.pageMetas.at(0)?.id;
|
||||
if (id) {
|
||||
router.push({
|
||||
query: {
|
||||
...router.query,
|
||||
workspaceId: currentWorkspace.id,
|
||||
pageId: id,
|
||||
},
|
||||
});
|
||||
setCurrentPageId(id);
|
||||
dispose.dispose();
|
||||
return;
|
||||
}
|
||||
}
|
||||
jumpToSubPath(
|
||||
currentWorkspace.blockSuiteWorkspace.id,
|
||||
WorkspaceSubPath.ALL,
|
||||
RouteLogic.REPLACE
|
||||
);
|
||||
dispose.dispose();
|
||||
}, REDIRECT_TIMEOUT);
|
||||
return () => {
|
||||
clearTimeout(clearId);
|
||||
dispose.dispose();
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [
|
||||
currentPageId,
|
||||
currentWorkspace,
|
||||
router.query.workspaceId,
|
||||
router.query.pageId,
|
||||
setCurrentPageId,
|
||||
setCurrentWorkspaceId,
|
||||
workspaces,
|
||||
router,
|
||||
jumpToSubPath,
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import {
|
||||
rootCurrentWorkspaceIdAtom,
|
||||
rootWorkspacesMetadataAtom,
|
||||
} from '@affine/workspace/atom';
|
||||
import { useAtom, useAtomValue } from 'jotai';
|
||||
import type { NextRouter } from 'next/router';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export function useSyncRouterWithCurrentWorkspaceId(router: NextRouter) {
|
||||
const [currentWorkspaceId, setCurrentWorkspaceId] = useAtom(
|
||||
rootCurrentWorkspaceIdAtom
|
||||
);
|
||||
const metadata = useAtomValue(rootWorkspacesMetadataAtom);
|
||||
useEffect(() => {
|
||||
if (!router.isReady) {
|
||||
return;
|
||||
}
|
||||
const workspaceId = router.query.workspaceId;
|
||||
if (typeof workspaceId !== 'string') {
|
||||
return;
|
||||
}
|
||||
if (currentWorkspaceId) {
|
||||
return;
|
||||
}
|
||||
const targetWorkspace = metadata.find(
|
||||
workspace => workspace.id === workspaceId
|
||||
);
|
||||
if (targetWorkspace) {
|
||||
console.log('set workspace id', workspaceId);
|
||||
setCurrentWorkspaceId(targetWorkspace.id);
|
||||
void router.push({
|
||||
pathname: '/workspace/[workspaceId]/all',
|
||||
query: {
|
||||
workspaceId: targetWorkspace.id,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
const targetWorkspace = metadata.at(0);
|
||||
if (targetWorkspace) {
|
||||
console.log('set workspace id', workspaceId);
|
||||
setCurrentWorkspaceId(targetWorkspace.id);
|
||||
void router.push({
|
||||
pathname: '/workspace/[workspaceId]/all',
|
||||
query: {
|
||||
workspaceId: targetWorkspace.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [currentWorkspaceId, metadata, router, setCurrentWorkspaceId]);
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import type { NextRouter } from 'next/router';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { useCurrentWorkspace } from './current/use-current-workspace';
|
||||
import { useWorkspaces } from './use-workspaces';
|
||||
|
||||
export function useSyncRouterWithCurrentWorkspace(router: NextRouter) {
|
||||
const [currentWorkspace, setCurrentWorkspaceId] = useCurrentWorkspace();
|
||||
const workspaces = useWorkspaces();
|
||||
useEffect(() => {
|
||||
const listener: Parameters<typeof router.events.on>[1] = (url: string) => {
|
||||
if (url.startsWith('/')) {
|
||||
const path = url.split('/');
|
||||
if (path.length === 4 && path[1] === 'workspace') {
|
||||
setCurrentWorkspaceId(path[2]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
router.events.on('routeChangeStart', listener);
|
||||
return () => {
|
||||
router.events.off('routeChangeStart', listener);
|
||||
};
|
||||
}, [currentWorkspace, router, setCurrentWorkspaceId]);
|
||||
useEffect(() => {
|
||||
if (!router.isReady) {
|
||||
return;
|
||||
}
|
||||
const workspaceId = router.query.workspaceId;
|
||||
if (typeof workspaceId !== 'string') {
|
||||
return;
|
||||
}
|
||||
if (!currentWorkspace) {
|
||||
const targetWorkspace = workspaces.find(
|
||||
workspace => workspace.id === workspaceId
|
||||
);
|
||||
if (targetWorkspace) {
|
||||
setCurrentWorkspaceId(targetWorkspace.id);
|
||||
} else {
|
||||
const targetWorkspace = workspaces.at(0);
|
||||
if (targetWorkspace) {
|
||||
setCurrentWorkspaceId(targetWorkspace.id);
|
||||
router.push({
|
||||
pathname: '/workspace/[workspaceId]/all',
|
||||
query: {
|
||||
workspaceId: targetWorkspace.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [currentWorkspace, router, setCurrentWorkspaceId, workspaces]);
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
import { jotaiWorkspacesAtom } from '@affine/workspace/atom';
|
||||
import {
|
||||
rootCurrentWorkspaceIdAtom,
|
||||
rootWorkspacesMetadataAtom,
|
||||
} from '@affine/workspace/atom';
|
||||
import type { WorkspaceFlavour } from '@affine/workspace/type';
|
||||
import type { WorkspaceRegistry } from '@affine/workspace/type';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { WorkspacePlugins } from '../plugins';
|
||||
import { useRouterHelper } from './use-router-helper';
|
||||
|
||||
/**
|
||||
* Transform workspace from one flavour to another
|
||||
@@ -14,9 +15,8 @@ import { useRouterHelper } from './use-router-helper';
|
||||
* The logic here is to delete the old workspace and create a new one.
|
||||
*/
|
||||
export function useTransformWorkspace() {
|
||||
const set = useSetAtom(jotaiWorkspacesAtom);
|
||||
const router = useRouter();
|
||||
const helper = useRouterHelper(router);
|
||||
const setCurrentWorkspaceId = useSetAtom(rootCurrentWorkspaceIdAtom);
|
||||
const set = useSetAtom(rootWorkspacesMetadataAtom);
|
||||
return useCallback(
|
||||
async <From extends WorkspaceFlavour, To extends WorkspaceFlavour>(
|
||||
from: From,
|
||||
@@ -35,9 +35,9 @@ export function useTransformWorkspace() {
|
||||
});
|
||||
return [...workspaces];
|
||||
});
|
||||
await helper.jumpToWorkspace(newId);
|
||||
setCurrentWorkspaceId(newId);
|
||||
return newId;
|
||||
},
|
||||
[helper, set]
|
||||
[set, setCurrentWorkspaceId]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { DebugLogger } from '@affine/debug';
|
||||
import { jotaiWorkspacesAtom } from '@affine/workspace/atom';
|
||||
import { rootWorkspacesMetadataAtom } from '@affine/workspace/atom';
|
||||
import type { LocalWorkspace } from '@affine/workspace/type';
|
||||
import { WorkspaceFlavour } from '@affine/workspace/type';
|
||||
import { createEmptyBlockSuiteWorkspace } from '@affine/workspace/utils';
|
||||
@@ -18,10 +18,13 @@ export function useWorkspaces(): AllWorkspace[] {
|
||||
|
||||
const logger = new DebugLogger('use-workspaces');
|
||||
|
||||
export function useWorkspacesHelper() {
|
||||
/**
|
||||
* This hook has the permission to all workspaces. Be careful when using it.
|
||||
*/
|
||||
export function useAppHelper() {
|
||||
const workspaces = useWorkspaces();
|
||||
const jotaiWorkspaces = useAtomValue(jotaiWorkspacesAtom);
|
||||
const set = useSetAtom(jotaiWorkspacesAtom);
|
||||
const jotaiWorkspaces = useAtomValue(rootWorkspacesMetadataAtom);
|
||||
const set = useSetAtom(rootWorkspacesMetadataAtom);
|
||||
return {
|
||||
createWorkspacePage: useCallback(
|
||||
(workspaceId: string, pageId: string) => {
|
||||
|
||||
Reference in New Issue
Block a user