refactor(core): refactor atom to use di (#5831)

To support multiple instances, this PR removes some atoms and implements them using the new DI system.

removed atom

- `pageSettingsAtom`
- `currentPageIdAtom`
- `currentModeAtom`
This commit is contained in:
EYHN
2024-02-27 03:50:53 +00:00
parent 0dabb08217
commit ad9b0303c4
60 changed files with 602 additions and 626 deletions

View File

@@ -1,4 +1,3 @@
export * from './initialization';
export {
migratePages as forceUpgradePages,
migrateGuidCompatibility,

View File

@@ -1,114 +0,0 @@
import type {
JobMiddleware,
Page,
PageMeta,
PageSnapshot,
Workspace,
WorkspaceInfoSnapshot,
} from '@blocksuite/store';
import { Job } from '@blocksuite/store';
import type { createStore, WritableAtom } from 'jotai/vanilla';
import { Map as YMap } from 'yjs';
import { getLatestVersions } from '../migration/blocksuite';
import { replaceIdMiddleware } from './middleware';
export function initEmptyPage(page: Page, title?: string) {
page.load(() => {
const pageBlockId = page.addBlock('affine:page', {
title: new page.Text(title ?? ''),
});
page.addBlock('affine:surface', {}, pageBlockId);
const noteBlockId = page.addBlock('affine:note', {}, pageBlockId);
page.addBlock('affine:paragraph', {}, noteBlockId);
});
}
/**
* FIXME: Use exported json data to instead of building data.
*/
export async function buildShowcaseWorkspace(
workspace: Workspace,
{
store,
atoms,
}: {
atoms: {
pageMode: WritableAtom<
undefined,
[pageId: string, mode: 'page' | 'edgeless'],
void
>;
};
store: ReturnType<typeof createStore>;
}
) {
const { onboarding } = await import('@affine/templates');
const info = onboarding['info.json'] as WorkspaceInfoSnapshot;
const migrationMiddleware: JobMiddleware = ({ slots, workspace }) => {
slots.afterImport.on(payload => {
if (payload.type === 'page') {
workspace.schema.upgradePage(
info?.pageVersion ?? 0,
{},
payload.page.spaceDoc
);
}
});
};
const job = new Job({
workspace,
middlewares: [replaceIdMiddleware, migrationMiddleware],
});
job.snapshotToWorkspaceInfo(info);
// for now all onboarding assets are considered served via CDN
// hack assets so that every blob exists
// @ts-expect-error - rethinking API
job._assetsManager.writeToBlob = async () => {};
const pageSnapshots: PageSnapshot[] = Object.entries(onboarding)
.filter(([key]) => {
return key.endsWith('snapshot.json');
})
.map(([_, value]) => value as unknown as PageSnapshot);
await Promise.all(
pageSnapshots.map(snapshot => {
return job.snapshotToPage(snapshot);
})
);
const newVersions = getLatestVersions(workspace.schema);
workspace.doc
.getMap('meta')
.set('blockVersions', new YMap(Object.entries(newVersions)));
// todo: find better way to do the following
// perhaps put them into middleware?
{
// the "AFFiNE - not just a note-taking app" page should be set to edgeless mode
const edgelessPage1 = (workspace.meta.pages as PageMeta[])?.find(
p => p.title === 'AFFiNE - not just a note-taking app'
)?.id;
if (edgelessPage1) {
store.set(atoms.pageMode, edgelessPage1, 'edgeless');
}
// should jump to "AFFiNE - not just a note-taking app" by default
const defaultPage = (workspace.meta.pages as PageMeta[])?.find(p =>
p.title.startsWith('AFFiNE - not just a note-taking app')
)?.id;
if (defaultPage) {
workspace.setPageMeta(defaultPage, {
jumpOnce: true,
});
}
}
}

View File

@@ -337,7 +337,7 @@ class ServiceCollectionEditor {
* ```
*/
addImpl = <
Arg1 extends ServiceIdentifier<any>,
Arg1 extends ServiceIdentifier<any> | (new (...args: any) => any),
Arg2 extends Type<Trait> | ServiceFactory<Trait> | Trait,
Trait = ServiceIdentifierType<Arg1>,
Deps extends Arg2 extends Type<Trait>

View File

@@ -3,6 +3,7 @@ export * from './atom';
export * from './blocksuite';
export * from './command';
export * from './di';
export * from './initialization';
export * from './livedata';
export * from './page';
export * from './storage';

View File

@@ -0,0 +1,117 @@
import type { WorkspaceFlavour } from '@affine/env/workspace';
import type {
JobMiddleware,
Page,
PageSnapshot,
WorkspaceInfoSnapshot,
} from '@blocksuite/store';
import { Job } from '@blocksuite/store';
import { Map as YMap } from 'yjs';
import { getLatestVersions } from '../blocksuite/migration/blocksuite';
import { PageRecordList } from '../page';
import type { WorkspaceManager } from '../workspace';
import { replaceIdMiddleware } from './middleware';
export function initEmptyPage(page: Page, title?: string) {
page.load(() => {
const pageBlockId = page.addBlock('affine:page', {
title: new page.Text(title ?? ''),
});
page.addBlock('affine:surface', {}, pageBlockId);
const noteBlockId = page.addBlock('affine:note', {}, pageBlockId);
page.addBlock('affine:paragraph', {}, noteBlockId);
});
}
/**
* FIXME: Use exported json data to instead of building data.
*/
export async function buildShowcaseWorkspace(
workspaceManager: WorkspaceManager,
flavour: WorkspaceFlavour,
workspaceName: string
) {
const meta = await workspaceManager.createWorkspace(
flavour,
async blockSuiteWorkspace => {
blockSuiteWorkspace.meta.setName(workspaceName);
const { onboarding } = await import('@affine/templates');
const info = onboarding['info.json'] as WorkspaceInfoSnapshot;
const migrationMiddleware: JobMiddleware = ({ slots, workspace }) => {
slots.afterImport.on(payload => {
if (payload.type === 'page') {
workspace.schema.upgradePage(
info?.pageVersion ?? 0,
{},
payload.page.spaceDoc
);
}
});
};
const job = new Job({
workspace: blockSuiteWorkspace,
middlewares: [replaceIdMiddleware, migrationMiddleware],
});
job.snapshotToWorkspaceInfo(info);
// for now all onboarding assets are considered served via CDN
// hack assets so that every blob exists
// @ts-expect-error - rethinking API
job._assetsManager.writeToBlob = async () => {};
const pageSnapshots: PageSnapshot[] = Object.entries(onboarding)
.filter(([key]) => {
return key.endsWith('snapshot.json');
})
.map(([_, value]) => value as unknown as PageSnapshot);
await Promise.all(
pageSnapshots.map(snapshot => {
return job.snapshotToPage(snapshot);
})
);
const newVersions = getLatestVersions(blockSuiteWorkspace.schema);
blockSuiteWorkspace.doc
.getMap('meta')
.set('blockVersions', new YMap(Object.entries(newVersions)));
}
);
const { workspace, release } = workspaceManager.open(meta);
await workspace.engine.sync.waitForLoadedRootDoc();
const pageRecordList = workspace.services.get(PageRecordList);
// todo: find better way to do the following
// perhaps put them into middleware?
{
// the "AFFiNE - not just a note-taking app" page should be set to edgeless mode
const edgelessPage1 = pageRecordList.records.value.find(
p => p.title.value === 'AFFiNE - not just a note-taking app'
);
if (edgelessPage1) {
edgelessPage1.setMode('edgeless');
}
// should jump to "AFFiNE - not just a note-taking app" by default
const defaultPage = pageRecordList.records.value.find(p =>
p.title.value.startsWith('AFFiNE - not just a note-taking app')
);
if (defaultPage) {
defaultPage.setMeta({
jumpOnce: true,
});
}
}
release();
return meta;
}

View File

@@ -3,13 +3,29 @@ import { useSyncExternalStore } from 'react';
import type { LiveData } from './index';
function noopSubscribe() {
return () => {};
}
function noopGetSnapshot() {
return null;
}
/**
* subscribe LiveData and return the value.
*/
export function useLiveData<T>(liveData: LiveData<T>): T {
export function useLiveData<Input extends LiveData<any> | null | undefined>(
liveData: Input
): NonNullable<Input> extends LiveData<infer T>
? Input extends undefined
? T | undefined
: Input extends null
? T | null
: T
: never {
return useSyncExternalStore(
liveData.reactSubscribe,
liveData.reactGetSnapshot
liveData ? liveData.reactSubscribe : noopSubscribe,
liveData ? liveData.reactGetSnapshot : noopGetSnapshot
);
}

View File

@@ -1,31 +0,0 @@
import { WorkspaceFlavour } from '@affine/env/workspace';
import { describe, expect, test } from 'vitest';
import { configureInfraServices, configureTestingInfraServices } from '../..';
import { ServiceCollection } from '../../di';
import { WorkspaceManager } from '../../workspace';
import { PageListService } from '..';
describe('Page System', () => {
test('basic', async () => {
const services = new ServiceCollection();
configureInfraServices(services);
configureTestingInfraServices(services);
const provider = services.provider();
const workspaceManager = provider.get(WorkspaceManager);
const { workspace } = workspaceManager.open(
await workspaceManager.createWorkspace(WorkspaceFlavour.LOCAL)
);
const pageListService = workspace.services.get(PageListService);
expect(pageListService.pages.value.length).toBe(0);
workspace.blockSuiteWorkspace.createPage({
id: 'page0',
});
expect(pageListService.pages.value.length).toBe(1);
});
});

View File

@@ -1,21 +1,23 @@
import type { Page as BlockSuitePage, PageMeta } from '@blocksuite/store';
import type { Page as BlockSuitePage } from '@blocksuite/store';
import { createIdentifier, type ServiceCollection } from '../di';
import type { PageRecord } from './record';
import { PageScope } from './service-scope';
export const BlockSuitePageContext = createIdentifier<BlockSuitePage>(
'BlockSuitePageContext'
);
export const PageMetaContext = createIdentifier<PageMeta>('PageMetaContext');
export const PageRecordContext =
createIdentifier<PageRecord>('PageRecordContext');
export function configurePageContext(
services: ServiceCollection,
blockSuitePage: BlockSuitePage,
pageMeta: PageMeta
pageRecord: PageRecord
) {
services
.scope(PageScope)
.addImpl(PageMetaContext, pageMeta)
.addImpl(PageRecordContext, pageRecord)
.addImpl(BlockSuitePageContext, blockSuitePage);
}

View File

@@ -1,25 +1,26 @@
export * from './context';
export * from './list';
export * from './manager';
export * from './page';
export * from './record';
export * from './record-list';
export * from './service-scope';
import { type ServiceCollection, ServiceProvider } from '../di';
import { CleanupService } from '../lifecycle';
import { Workspace, WorkspaceScope } from '../workspace';
import { BlockSuitePageContext, PageMetaContext } from './context';
import { PageListService } from './list';
import { Workspace, WorkspaceLocalState, WorkspaceScope } from '../workspace';
import { BlockSuitePageContext, PageRecordContext } from './context';
import { PageManager } from './manager';
import { Page } from './page';
import { PageRecordList } from './record-list';
import { PageScope } from './service-scope';
export function configurePageServices(services: ServiceCollection) {
services
.scope(WorkspaceScope)
.add(PageListService, [Workspace])
.add(PageManager, [Workspace, PageListService, ServiceProvider]);
.add(PageManager, [Workspace, PageRecordList, ServiceProvider])
.add(PageRecordList, [Workspace, WorkspaceLocalState]);
services
.scope(PageScope)
.add(CleanupService)
.add(Page, [PageMetaContext, BlockSuitePageContext, ServiceProvider]);
.add(Page, [PageRecordContext, BlockSuitePageContext, ServiceProvider]);
}

View File

@@ -1,10 +1,8 @@
import type { PageMeta } from '@blocksuite/store';
import type { ServiceProvider } from '../di';
import { ObjectPool } from '../utils/object-pool';
import type { Workspace } from '../workspace';
import type { PageRecordList } from '.';
import { configurePageContext } from './context';
import type { PageListService } from './list';
import { Page } from './page';
import { PageScope } from './service-scope';
@@ -13,28 +11,21 @@ export class PageManager {
constructor(
private readonly workspace: Workspace,
private readonly pageList: PageListService,
private readonly pageRecordList: PageRecordList,
private readonly serviceProvider: ServiceProvider
) {}
openByPageId(pageId: string) {
const pageMeta = this.pageList.getPageMetaById(pageId);
if (!pageMeta) {
throw new Error('Page not found');
open(pageId: string) {
const pageRecord = this.pageRecordList.record(pageId).value;
if (!pageRecord) {
throw new Error('Page record not found');
}
return this.open(pageMeta);
}
open(pageMeta: PageMeta) {
const blockSuitePage = this.workspace.blockSuiteWorkspace.getPage(
pageMeta.id
);
const blockSuitePage = this.workspace.blockSuiteWorkspace.getPage(pageId);
if (!blockSuitePage) {
throw new Error('Page not found');
}
const exists = this.pool.get(pageMeta.id);
const exists = this.pool.get(pageId);
if (exists) {
return { page: exists.obj, release: exists.release };
}
@@ -43,7 +34,7 @@ export class PageManager {
// avoid to modify the original service collection
.clone();
configurePageContext(serviceCollection, blockSuitePage, pageMeta);
configurePageContext(serviceCollection, blockSuitePage, pageRecord);
const provider = serviceCollection.provider(
PageScope,
@@ -52,7 +43,7 @@ export class PageManager {
const page = provider.get(Page);
const { obj, release } = this.pool.put(pageMeta.id, page);
const { obj, release } = this.pool.put(pageId, page);
return { page: obj, release };
}

View File

@@ -1,15 +1,28 @@
import type { Page as BlockSuitePage } from '@blocksuite/store';
import { type PageMeta } from '@blocksuite/store';
import type { ServiceProvider } from '@toeverything/infra/di';
export class Page {
get id() {
return this.meta.id;
}
import type { PageMode, PageRecord } from './record';
export class Page {
constructor(
public readonly meta: PageMeta,
public readonly record: PageRecord,
public readonly blockSuitePage: BlockSuitePage,
public readonly services: ServiceProvider
) {}
get id() {
return this.record.id;
}
readonly mete = this.record.meta;
readonly mode = this.record.mode;
readonly title = this.record.title;
setMode(mode: PageMode) {
this.record.setMode(mode);
}
toggleMode() {
this.record.toggleMode();
}
}

View File

@@ -1,24 +1,35 @@
import type { PageMeta } from '@blocksuite/store';
import { Observable } from 'rxjs';
import { LiveData } from '../livedata';
import { SyncEngineStep, type Workspace } from '../workspace';
import {
SyncEngineStep,
type Workspace,
type WorkspaceLocalState,
} from '../workspace';
import { PageRecord } from './record';
export class PageListService {
constructor(private readonly workspace: Workspace) {}
export class PageRecordList {
constructor(
private readonly workspace: Workspace,
private readonly localState: WorkspaceLocalState
) {}
public readonly pages = LiveData.from<PageMeta[]>(
public readonly records = LiveData.from<PageRecord[]>(
new Observable(subscriber => {
subscriber.next(
Array.from(this.workspace.blockSuiteWorkspace.meta.pageMetas)
);
const emit = () => {
subscriber.next(
this.workspace.blockSuiteWorkspace.meta.pageMetas.map(
v => new PageRecord(v.id, this.workspace, this.localState)
)
);
};
emit();
const dispose =
this.workspace.blockSuiteWorkspace.meta.pageMetasUpdated.on(() => {
subscriber.next(
Array.from(this.workspace.blockSuiteWorkspace.meta.pageMetas)
);
}).dispose;
this.workspace.blockSuiteWorkspace.meta.pageMetasUpdated.on(
emit
).dispose;
return () => {
dispose();
};
@@ -44,7 +55,7 @@ export class PageListService {
false
);
public getPageMetaById(id: string) {
return this.pages.value.find(page => page.id === id);
public record(id: string) {
return this.records.map(record => record.find(record => record.id === id));
}
}

View File

@@ -0,0 +1,65 @@
import type { PageMeta } from '@blocksuite/store';
import { Observable } from 'rxjs';
import { LiveData } from '../livedata';
import type { Workspace, WorkspaceLocalState } from '../workspace';
export type PageMode = 'edgeless' | 'page';
export class PageRecord {
constructor(
public readonly id: string,
private readonly workspace: Workspace,
private readonly localState: WorkspaceLocalState
) {}
meta = LiveData.from<PageMeta>(
new Observable(subscriber => {
const emit = () => {
const meta = this.workspace.blockSuiteWorkspace.meta.pageMetas.find(
page => page.id === this.id
);
if (meta === undefined) {
return;
}
subscriber.next(meta);
};
emit();
const dispose =
this.workspace.blockSuiteWorkspace.meta.pageMetasUpdated.on(
emit
).dispose;
return () => {
dispose();
};
}),
{
id: this.id,
title: '',
tags: [],
createDate: 0,
}
);
setMeta(meta: Partial<PageMeta>): void {
this.workspace.blockSuiteWorkspace.setPageMeta(this.id, meta);
}
mode: LiveData<PageMode> = LiveData.from(
this.localState.watch<PageMode>(`page:${this.id}:mode`),
'page'
).map(mode => (mode === 'edgeless' ? 'edgeless' : 'page'));
setMode(mode: PageMode) {
this.localState.set(`page:${this.id}:mode`, mode);
}
toggleMode() {
this.setMode(this.mode.value === 'edgeless' ? 'page' : 'edgeless');
return this.mode.value;
}
title = this.meta.map(meta => meta.title);
}

View File

@@ -6,13 +6,14 @@ export * from './list';
export * from './manager';
export * from './metadata';
export * from './service-scope';
export * from './storage';
export * from './testing';
export * from './upgrade';
export * from './workspace';
import { type ServiceCollection, ServiceProvider } from '../di';
import { CleanupService } from '../lifecycle';
import { GlobalCache, GlobalState } from '../storage';
import { GlobalCache, GlobalState, MemoryMemento } from '../storage';
import {
BlockSuiteWorkspaceContext,
RootYDocContext,
@@ -33,6 +34,7 @@ import { WorkspaceFactory } from './factory';
import { WorkspaceListProvider, WorkspaceListService } from './list';
import { WorkspaceManager } from './manager';
import { WorkspaceScope } from './service-scope';
import { WorkspaceLocalState } from './storage';
import {
TestingLocalWorkspaceFactory,
TestingLocalWorkspaceListProvider,
@@ -83,5 +85,7 @@ export function configureTestingWorkspaceServices(services: ServiceCollection) {
)
.override(WorkspaceFactory('local'), TestingLocalWorkspaceFactory, [
GlobalState,
]);
])
.scope(WorkspaceScope)
.override(WorkspaceLocalState, MemoryMemento);
}

View File

@@ -0,0 +1,8 @@
import { createIdentifier } from '../di';
import type { Memento } from '../storage';
export interface WorkspaceLocalState extends Memento {}
export const WorkspaceLocalState = createIdentifier<WorkspaceLocalState>(
'WorkspaceLocalState'
);

View File

@@ -10,6 +10,8 @@ import { type WorkspaceMetadata } from './metadata';
import type { WorkspaceUpgradeController } from './upgrade';
import { type WorkspaceUpgradeStatus } from './upgrade';
export type { Workspace as BlockSuiteWorkspace } from '@blocksuite/store';
const logger = new DebugLogger('affine:workspace');
export type WorkspaceStatus = {

View File

@@ -1,37 +0,0 @@
/**
* @vitest-environment happy-dom
*/
import 'fake-indexeddb/auto';
import { createStore } from 'jotai';
import { describe, expect, test } from 'vitest';
import {
pageSettingFamily,
pageSettingsAtom,
recentPageIdsBaseAtom,
} from '../index';
describe('page mode atom', () => {
test('basic', () => {
const store = createStore();
const page0SettingAtom = pageSettingFamily('page0');
store.set(page0SettingAtom, {
mode: 'page',
});
expect(store.get(pageSettingsAtom)).toEqual({
page0: {
mode: 'page',
},
});
expect(store.get(recentPageIdsBaseAtom)).toEqual(['page0']);
const page1SettingAtom = pageSettingFamily('page1');
store.set(page1SettingAtom, {
mode: 'edgeless',
});
expect(store.get(recentPageIdsBaseAtom)).toEqual(['page1', 'page0']);
});
});

View File

@@ -1,7 +1,5 @@
import type { PrimitiveAtom } from 'jotai';
import { atom } from 'jotai';
import { atomFamily, atomWithStorage } from 'jotai/utils';
import type { AtomFamily } from 'jotai/vanilla/utils/atomFamily';
import { atomWithStorage } from 'jotai/utils';
import type { AuthProps } from '../components/affine/auth';
import type { CreateWorkspaceMode } from '../components/affine/create-workspace-modal';
@@ -45,66 +43,11 @@ export const authAtom = atom<AuthAtom>({
export const openDisableCloudAlertModalAtom = atom(false);
export type PageMode = 'page' | 'edgeless';
type PageLocalSetting = {
mode: PageMode;
};
const pageSettingsBaseAtom = atomWithStorage(
'pageSettings',
{} as Record<string, PageLocalSetting>
);
// readonly atom by design
export const pageSettingsAtom = atom(get => get(pageSettingsBaseAtom));
export const recentPageIdsBaseAtom = atomWithStorage<string[]>(
'recentPageSettings',
[]
);
const defaultPageSetting = {
mode: 'page',
} satisfies PageLocalSetting;
export const pageSettingFamily: AtomFamily<
string,
PrimitiveAtom<PageLocalSetting>
> = atomFamily((pageId: string) =>
atom(
get =>
get(pageSettingsBaseAtom)[pageId] ?? {
...defaultPageSetting,
},
(get, set, patch) => {
// fixme: this does not work when page reload,
// since atomWithStorage is async
set(recentPageIdsBaseAtom, ids => {
// pick 3 recent page ids
return [...new Set([pageId, ...ids]).values()].slice(0, 3);
});
const prevSetting = {
...defaultPageSetting,
...get(pageSettingsBaseAtom)[pageId],
};
set(pageSettingsBaseAtom, settings => ({
...settings,
[pageId]: {
...prevSetting,
...(typeof patch === 'function' ? patch(prevSetting) : patch),
},
}));
}
)
);
export const setPageModeAtom = atom(
void 0,
(_, set, pageId: string, mode: PageMode) => {
set(pageSettingFamily(pageId), { mode });
}
);
export type PageModeOption = 'all' | 'page' | 'edgeless';
export const allPageModeSelectAtom = atom<PageModeOption>('all');

View File

@@ -1,13 +0,0 @@
import { atom } from 'jotai/vanilla';
import { pageSettingFamily } from './index';
export const currentPageIdAtom = atom<string | null>(null);
export const currentModeAtom = atom<'page' | 'edgeless'>(get => {
const pageId = get(currentPageIdAtom);
if (!pageId) {
return 'page';
}
return get(pageSettingFamily(pageId)).mode;
});

View File

@@ -2,42 +2,36 @@ import { DebugLogger } from '@affine/debug';
import { DEFAULT_WORKSPACE_NAME } from '@affine/env/constant';
import { WorkspaceFlavour } from '@affine/env/workspace';
import type { WorkspaceManager } from '@toeverything/infra';
import { getCurrentStore } from '@toeverything/infra/atom';
import {
buildShowcaseWorkspace,
initEmptyPage,
} from '@toeverything/infra/blocksuite';
import { buildShowcaseWorkspace, initEmptyPage } from '@toeverything/infra';
import { setPageModeAtom } from '../atoms';
const logger = new DebugLogger('affine:first-app-data');
const logger = new DebugLogger('createFirstAppData');
export async function createFirstAppData(workspaceManager: WorkspaceManager) {
if (localStorage.getItem('is-first-open') !== null) {
return;
}
localStorage.setItem('is-first-open', 'false');
const workspaceMetadata = await workspaceManager.createWorkspace(
WorkspaceFlavour.LOCAL,
async workspace => {
workspace.meta.setName(DEFAULT_WORKSPACE_NAME);
if (runtimeConfig.enablePreloading) {
await buildShowcaseWorkspace(workspace, {
store: getCurrentStore(),
atoms: {
pageMode: setPageModeAtom,
},
});
} else {
if (runtimeConfig.enablePreloading) {
const workspaceMetadata = await buildShowcaseWorkspace(
workspaceManager,
WorkspaceFlavour.LOCAL,
DEFAULT_WORKSPACE_NAME
);
logger.info('create first workspace', workspaceMetadata);
return workspaceMetadata;
} else {
const workspaceMetadata = await workspaceManager.createWorkspace(
WorkspaceFlavour.LOCAL,
async workspace => {
workspace.meta.setName(DEFAULT_WORKSPACE_NAME);
const page = workspace.createPage();
workspace.setPageMeta(page.id, {
jumpOnce: true,
});
initEmptyPage(page);
}
logger.debug('create first workspace');
}
);
console.info('create first workspace', workspaceMetadata);
return workspaceMetadata;
);
logger.info('create first workspace', workspaceMetadata);
return workspaceMetadata;
}
}

View File

@@ -1,11 +1,9 @@
import { WorkspaceListService } from '@toeverything/infra';
import { useService } from '@toeverything/infra/di';
import { Page, WorkspaceListService } from '@toeverything/infra';
import { useService, useServiceOptional } from '@toeverything/infra/di';
import { useLiveData } from '@toeverything/infra/livedata';
import { useAtomValue } from 'jotai/react';
import { useEffect } from 'react';
import { useLocation, useParams } from 'react-router-dom';
import { currentPageIdAtom } from '../../../../atoms/mode';
import { CurrentWorkspaceService } from '../../../../modules/workspace/current-workspace';
export interface DumpInfoProps {
@@ -18,7 +16,7 @@ export const DumpInfo = (_props: DumpInfoProps) => {
const currentWorkspace = useLiveData(
useService(CurrentWorkspaceService).currentWorkspace
);
const currentPageId = useAtomValue(currentPageIdAtom);
const currentPage = useServiceOptional(Page);
const path = location.pathname;
const query = useParams();
useEffect(() => {
@@ -26,9 +24,9 @@ export const DumpInfo = (_props: DumpInfoProps) => {
path,
query,
currentWorkspaceId: currentWorkspace?.id,
currentPageId,
currentPageId: currentPage?.id,
workspaceList,
});
}, [path, query, currentWorkspace, currentPageId, workspaceList]);
}, [path, query, currentWorkspace, workspaceList, currentPage?.id]);
return null;
};

View File

@@ -4,11 +4,7 @@ import {
type ConfirmModalProps,
Modal,
} from '@affine/component/ui/modal';
import {
authAtom,
openDisableCloudAlertModalAtom,
setPageModeAtom,
} from '@affine/core/atoms';
import { authAtom, openDisableCloudAlertModalAtom } from '@affine/core/atoms';
import { useCurrentLoginStatus } from '@affine/core/hooks/affine/use-current-login-status';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { DebugLogger } from '@affine/debug';
@@ -17,11 +13,7 @@ import { WorkspaceFlavour } from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { _addLocalWorkspace } from '@affine/workspace-impl';
import { WorkspaceManager } from '@toeverything/infra';
import { getCurrentStore } from '@toeverything/infra/atom';
import {
buildShowcaseWorkspace,
initEmptyPage,
} from '@toeverything/infra/blocksuite';
import { buildShowcaseWorkspace, initEmptyPage } from '@toeverything/infra';
import { useService } from '@toeverything/infra/di';
import { useSetAtom } from 'jotai';
import type { KeyboardEvent } from 'react';
@@ -234,28 +226,27 @@ export const CreateWorkspaceModal = ({
async (name: string, workspaceFlavour: WorkspaceFlavour) => {
// this will be the last step for web for now
// fix me later
const { id } = await workspaceManager.createWorkspace(
workspaceFlavour,
async workspace => {
workspace.meta.setName(name);
if (runtimeConfig.enablePreloading) {
await buildShowcaseWorkspace(workspace, {
store: getCurrentStore(),
atoms: {
pageMode: setPageModeAtom,
},
});
} else {
if (runtimeConfig.enablePreloading) {
const { id } = await buildShowcaseWorkspace(
workspaceManager,
workspaceFlavour,
name
);
onCreate(id);
} else {
const { id } = await workspaceManager.createWorkspace(
workspaceFlavour,
async workspace => {
workspace.meta.setName(name);
const page = workspace.createPage();
workspace.setPageMeta(page.id, {
jumpOnce: true,
});
initEmptyPage(page);
}
logger.debug('create first workspace');
}
);
onCreate(id);
);
onCreate(id);
}
},
[onCreate, workspaceManager]
);

View File

@@ -2,7 +2,7 @@ import { Loading, Scrollable } from '@affine/component';
import { EditorLoading } from '@affine/component/page-detail-skeleton';
import { Button, IconButton } from '@affine/component/ui/button';
import { ConfirmModal, Modal } from '@affine/component/ui/modal';
import { openSettingModalAtom, type PageMode } from '@affine/core/atoms';
import { openSettingModalAtom } from '@affine/core/atoms';
import { useIsWorkspaceOwner } from '@affine/core/hooks/affine/use-is-workspace-owner';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { useBlockSuiteWorkspacePageTitle } from '@affine/core/hooks/use-block-suite-workspace-page-title';
@@ -10,15 +10,13 @@ import { useWorkspaceQuota } from '@affine/core/hooks/use-workspace-quota';
import { Trans } from '@affine/i18n';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { CloseIcon, ToggleCollapseIcon } from '@blocksuite/icons';
import {
type Page,
type Workspace as BlockSuiteWorkspace,
} from '@blocksuite/store';
import type { Page as BlockSuitePage } from '@blocksuite/store';
import { type Workspace as BlockSuiteWorkspace } from '@blocksuite/store';
import * as Collapsible from '@radix-ui/react-collapsible';
import type { DialogContentProps } from '@radix-ui/react-dialog';
import { Workspace } from '@toeverything/infra';
import { Page, type PageMode, Workspace } from '@toeverything/infra';
import { useService } from '@toeverything/infra/di';
import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai';
import { atom, useAtom, useSetAtom } from 'jotai';
import {
Fragment,
type PropsWithChildren,
@@ -30,7 +28,6 @@ import {
} from 'react';
import { encodeStateAsUpdate } from 'yjs';
import { currentModeAtom } from '../../../atoms/mode';
import { pageHistoryModalAtom } from '../../../atoms/page-history';
import { timestampToLocalTime } from '../../../utils';
import { BlockSuiteEditor } from '../../blocksuite/block-suite-editor';
@@ -93,7 +90,7 @@ const ModalContainer = ({
interface HistoryEditorPreviewProps {
ts?: string;
snapshotPage?: Page;
snapshotPage?: BlockSuitePage;
mode: PageMode;
onModeChange: (mode: PageMode) => void;
title: string;
@@ -450,8 +447,8 @@ const PageHistoryManager = ({
[activeVersion, onClose, onRestore, snapshotPage]
);
const defaultPreviewPageMode = useAtomValue(currentModeAtom);
const [mode, setMode] = useState<PageMode>(defaultPreviewPageMode);
const page = useService(Page);
const [mode, setMode] = useState<PageMode>(page.mode.value);
const title = useBlockSuiteWorkspacePageTitle(workspace, pageId);

View File

@@ -4,9 +4,9 @@ import { ExportMenuItems } from '@affine/core/components/page-list';
import { WorkspaceFlavour } from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { LinkIcon } from '@blocksuite/icons';
import { useAtomValue } from 'jotai';
import { Page, useLiveData } from '@toeverything/infra';
import { useService } from '@toeverything/infra/di';
import { currentModeAtom } from '../../../../atoms/mode';
import { useExportPage } from '../../../../hooks/affine/use-export-page';
import * as styles from './index.css';
import type { ShareMenuProps } from './share-menu';
@@ -17,6 +17,7 @@ export const ShareExport = ({
currentPage,
}: ShareMenuProps) => {
const t = useAFFiNEI18N();
const page = useService(Page);
const workspaceId = workspace.id;
const pageId = currentPage.id;
const { sharingUrl, onClickCopyLink } = useSharingUrl({
@@ -25,7 +26,7 @@ export const ShareExport = ({
urlType: 'workspace',
});
const exportHandler = useExportPage(currentPage);
const currentMode = useAtomValue(currentModeAtom);
const currentMode = useLiveData(page.mode);
return (
<>

View File

@@ -8,14 +8,14 @@ import {
import { PublicLinkDisableModal } from '@affine/component/disable-public-link';
import { Button } from '@affine/component/ui/button';
import { Menu, MenuItem, MenuTrigger } from '@affine/component/ui/menu';
import type { PageMode } from '@affine/core/atoms';
import { currentModeAtom } from '@affine/core/atoms/mode';
import { useIsSharedPage } from '@affine/core/hooks/affine/use-is-shared-page';
import { useServerBaseUrl } from '@affine/core/hooks/affine/use-server-config';
import { WorkspaceFlavour } from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { ArrowRightSmallIcon } from '@blocksuite/icons';
import { useAtomValue } from 'jotai';
import { useService } from '@toeverything/infra';
import { useLiveData } from '@toeverything/infra';
import { Page, type PageMode } from '@toeverything/infra';
import { useMemo, useState } from 'react';
import { useCallback } from 'react';
@@ -56,6 +56,7 @@ export const AffineSharePage = (props: ShareMenuProps) => {
currentPage,
} = props;
const pageId = currentPage.id;
const page = useService(Page);
const [showDisable, setShowDisable] = useState(false);
const {
isSharedPage,
@@ -64,14 +65,15 @@ export const AffineSharePage = (props: ShareMenuProps) => {
currentShareMode,
disableShare,
} = useIsSharedPage(workspaceId, currentPage.id);
const currentPageMode = useAtomValue(currentModeAtom);
const currentPageMode = useLiveData(page.mode);
const defaultMode = useMemo(() => {
if (isSharedPage) {
// if it's a shared page, use the share mode
return currentShareMode;
}
// default to current page mode
// default to page mode
return currentPageMode;
}, [currentPageMode, currentShareMode, isSharedPage]);
const [mode, setMode] = useState<PageMode>(defaultMode);

View File

@@ -1,4 +1,3 @@
import type { PageMode } from '@affine/core/atoms';
import type { BlockElement } from '@blocksuite/lit';
import type {
AffineEditorContainer,
@@ -6,6 +5,7 @@ import type {
EdgelessEditor,
} from '@blocksuite/presets';
import { type Page, Slot } from '@blocksuite/store';
import type { PageMode } from '@toeverything/infra';
import clsx from 'clsx';
import type React from 'react';
import {

View File

@@ -6,7 +6,6 @@ import {
MenuSeparator,
} from '@affine/component/ui/menu';
import { openHistoryTipsModalAtom } from '@affine/core/atoms';
import { currentModeAtom } from '@affine/core/atoms/mode';
import { PageHistoryModal } from '@affine/core/components/affine/page-history-modal';
import { Export, MoveToTrash } from '@affine/core/components/page-list';
import { useBlockSuiteMetaHelper } from '@affine/core/hooks/affine/use-block-suite-meta-helper';
@@ -26,8 +25,8 @@ import {
ImportIcon,
PageIcon,
} from '@blocksuite/icons';
import { useService, Workspace } from '@toeverything/infra';
import { useAtomValue, useSetAtom } from 'jotai';
import { Page, useLiveData, useService, Workspace } from '@toeverything/infra';
import { useSetAtom } from 'jotai';
import { useCallback, useState } from 'react';
import { HeaderDropDownButton } from '../../../pure/header-drop-down-button';
@@ -55,12 +54,12 @@ export const PageHeaderMenuButton = ({
const pageMeta = useBlockSuitePageMeta(blockSuiteWorkspace).find(
meta => meta.id === pageId
);
const currentMode = useAtomValue(currentModeAtom);
const page = useService(Page);
const currentMode = useLiveData(page.mode);
const { favorite, toggleFavorite } = useFavorite(pageId);
const { togglePageMode, duplicate } =
useBlockSuiteMetaHelper(blockSuiteWorkspace);
const { duplicate } = useBlockSuiteMetaHelper(blockSuiteWorkspace);
const { importFile } = usePageHelper(blockSuiteWorkspace);
const { setTrashModal } = useTrashModalHelper(blockSuiteWorkspace);
@@ -86,13 +85,13 @@ export const PageHeaderMenuButton = ({
}, [pageId, pageMeta, setTrashModal]);
const handleSwitchMode = useCallback(() => {
togglePageMode(pageId);
page.toggleMode();
toast(
currentMode === 'page'
? t['com.affine.toastMessage.edgelessMode']()
: t['com.affine.toastMessage.pageMode']()
);
}, [currentMode, pageId, t, togglePageMode]);
}, [currentMode, page, t]);
const menuItemStyle = {
padding: '4px 12px',
transition: 'all 0.3s',

View File

@@ -1,13 +1,15 @@
import { Tooltip } from '@affine/component/ui/tooltip';
import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { useAtomValue } from 'jotai';
import {
Page,
type PageMode,
useLiveData,
useService,
} from '@toeverything/infra';
import type { CSSProperties } from 'react';
import { useCallback, useEffect } from 'react';
import type { PageMode } from '../../../atoms';
import { currentModeAtom } from '../../../atoms/mode';
import { useBlockSuiteMetaHelper } from '../../../hooks/affine/use-block-suite-meta-helper';
import type { BlockSuiteWorkspace } from '../../../shared';
import { toast } from '../../../utils';
import { StyledEditorModeSwitch, StyledKeyboardItem } from './style';
@@ -44,10 +46,9 @@ export const EditorModeSwitch = ({
meta => meta.id === pageId
);
const trash = pageMeta?.trash ?? false;
const page = useService(Page);
const { togglePageMode, switchToEdgelessMode, switchToPageMode } =
useBlockSuiteMetaHelper(blockSuiteWorkspace);
const currentMode = useAtomValue(currentModeAtom);
const currentMode = useLiveData(page.mode);
useEffect(() => {
if (trash || isPublic) {
@@ -56,7 +57,7 @@ export const EditorModeSwitch = ({
const keydown = (e: KeyboardEvent) => {
if (e.code === 'KeyS' && e.altKey) {
e.preventDefault();
togglePageMode(pageId);
page.toggleMode();
toast(
currentMode === 'page'
? t['com.affine.toastMessage.edgelessMode']()
@@ -67,23 +68,23 @@ export const EditorModeSwitch = ({
document.addEventListener('keydown', keydown, { capture: true });
return () =>
document.removeEventListener('keydown', keydown, { capture: true });
}, [currentMode, isPublic, pageId, t, togglePageMode, trash]);
}, [currentMode, isPublic, page, pageId, t, trash]);
const onSwitchToPageMode = useCallback(() => {
if (currentMode === 'page' || isPublic) {
return;
}
switchToPageMode(pageId);
page.setMode('page');
toast(t['com.affine.toastMessage.pageMode']());
}, [currentMode, isPublic, pageId, switchToPageMode, t]);
}, [currentMode, isPublic, page, t]);
const onSwitchToEdgelessMode = useCallback(() => {
if (currentMode === 'edgeless' || isPublic) {
return;
}
switchToEdgelessMode(pageId);
page.setMode('edgeless');
toast(t['com.affine.toastMessage.edgelessMode']());
}, [currentMode, isPublic, pageId, switchToEdgelessMode, t]);
}, [currentMode, isPublic, page, t]);
const shouldHide = useCallback(
(mode: PageMode) =>

View File

@@ -3,36 +3,35 @@ import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { usePageMetaHelper } from '@affine/core/hooks/use-block-suite-page-meta';
import { useBlockSuiteWorkspaceHelper } from '@affine/core/hooks/use-block-suite-workspace-helper';
import { WorkspaceSubPath } from '@affine/core/shared';
import { initEmptyPage } from '@toeverything/infra/blocksuite';
import { useAtomValue, useSetAtom } from 'jotai';
import { useService } from '@toeverything/infra';
import { PageRecordList } from '@toeverything/infra';
import { initEmptyPage } from '@toeverything/infra';
import { useCallback, useMemo } from 'react';
import { pageSettingsAtom, setPageModeAtom } from '../../../atoms';
import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
import type { BlockSuiteWorkspace } from '../../../shared';
export const usePageHelper = (blockSuiteWorkspace: BlockSuiteWorkspace) => {
const { openPage, jumpToSubPath } = useNavigateHelper();
const { createPage } = useBlockSuiteWorkspaceHelper(blockSuiteWorkspace);
const pageSettings = useAtomValue(pageSettingsAtom);
const { setPageMeta } = usePageMetaHelper(blockSuiteWorkspace);
const pageRecordList = useService(PageRecordList);
const isPreferredEdgeless = useCallback(
(pageId: string) => pageSettings[pageId]?.mode === 'edgeless',
[pageSettings]
(pageId: string) =>
pageRecordList.record(pageId).value?.mode.value === 'edgeless',
[pageRecordList]
);
const setPageMode = useSetAtom(setPageModeAtom);
const createPageAndOpen = useCallback(
(mode?: 'page' | 'edgeless') => {
const page = createPage();
initEmptyPage(page);
setPageMode(page.id, mode || 'page');
pageRecordList.record(page.id).value?.setMode(mode || 'page');
openPage(blockSuiteWorkspace.id, page.id);
return page;
},
[blockSuiteWorkspace.id, createPage, openPage, setPageMode]
[blockSuiteWorkspace.id, createPage, openPage, pageRecordList]
);
const createEdgelessAndOpen = useCallback(() => {
@@ -89,17 +88,17 @@ export const usePageHelper = (blockSuiteWorkspace: BlockSuiteWorkspace) => {
return useMemo(() => {
return {
isPreferredEdgeless,
createPage: createPageAndOpen,
createEdgeless: createEdgelessAndOpen,
importFile: importFileAndOpen,
isPreferredEdgeless: isPreferredEdgeless,
createLinkedPage: createLinkedPageAndOpen,
};
}, [
isPreferredEdgeless,
createEdgelessAndOpen,
createLinkedPageAndOpen,
createPageAndOpen,
importFileAndOpen,
isPreferredEdgeless,
]);
};

View File

@@ -1,7 +1,7 @@
import type { PageMode } from '@affine/core/atoms';
import { useCurrentLoginStatus } from '@affine/core/hooks/affine/use-current-login-status';
import type { PageMode } from '@toeverything/infra';
import { useState } from 'react';
import { useCurrentLoginStatus } from '../../../hooks/affine/use-current-login-status';
import { AuthenticatedItem } from './authenticated-item';
import { PresentButton } from './present';
import * as styles from './styles.css';

View File

@@ -4,15 +4,20 @@ import { useActiveBlocksuiteEditor } from '@affine/core/hooks/use-block-suite-ed
import { useBlockSuiteWorkspacePage } from '@affine/core/hooks/use-block-suite-workspace-page';
import { assertExists, DisposableGroup } from '@blocksuite/global/utils';
import type { AffineEditorContainer } from '@blocksuite/presets';
import type { Page, Workspace } from '@blocksuite/store';
import type { Workspace } from '@blocksuite/store';
import type { Page as BlockSuitePage } from '@blocksuite/store';
import {
Page,
type PageMode,
useLiveData,
useService,
} from '@toeverything/infra';
import { fontStyleOptions } from '@toeverything/infra/atom';
import clsx from 'clsx';
import { useAtomValue } from 'jotai';
import type { CSSProperties } from 'react';
import { memo, Suspense, useCallback, useMemo } from 'react';
import { useLocation } from 'react-router-dom';
import { type PageMode, pageSettingFamily } from '../atoms';
import { useAppSettingHelper } from '../hooks/affine/use-app-setting-helper';
import { BlockSuiteEditor as Editor } from './blocksuite/block-suite-editor';
import * as styles from './page-detail-editor.css';
@@ -23,7 +28,7 @@ declare global {
}
export type OnLoadEditor = (
page: Page,
page: BlockSuitePage,
editor: AffineEditorContainer
) => () => void;
@@ -41,23 +46,19 @@ function useRouterHash() {
const PageDetailEditorMain = memo(function PageDetailEditorMain({
page,
pageId,
onLoad,
isPublic,
publishMode,
}: PageDetailEditorProps & { page: Page }) {
const pageSettingAtom = pageSettingFamily(pageId);
const pageSetting = useAtomValue(pageSettingAtom);
}: PageDetailEditorProps & { page: BlockSuitePage }) {
const currentMode = useLiveData(useService(Page).mode);
const mode = useMemo(() => {
const currentMode = pageSetting.mode;
const shareMode = publishMode || currentMode;
if (isPublic) {
return shareMode;
}
return currentMode;
}, [isPublic, publishMode, pageSetting.mode]);
}, [isPublic, publishMode, currentMode]);
const { appSettings } = useAppSettingHelper();

View File

@@ -1,4 +1,3 @@
import { currentPageIdAtom } from '@affine/core/atoms/mode';
import { useCollectionManager } from '@affine/core/components/page-list';
import {
useBlockSuitePageMeta,
@@ -16,19 +15,24 @@ import {
TodayIcon,
ViewLayersIcon,
} from '@blocksuite/icons';
import { type PageMeta } from '@blocksuite/store';
import { useService, Workspace } from '@toeverything/infra';
import { getCurrentStore } from '@toeverything/infra/atom';
import type { PageMeta } from '@blocksuite/store';
import {
Page,
PageRecordList,
useLiveData,
Workspace,
} from '@toeverything/infra';
import {
type AffineCommand,
AffineCommandRegistry,
type CommandCategory,
PreconditionStrategy,
} from '@toeverything/infra/command';
import { useService, useServiceOptional } from '@toeverything/infra/di';
import { atom, useAtomValue } from 'jotai';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { pageSettingsAtom, recentPageIdsBaseAtom } from '../../../atoms';
import { recentPageIdsBaseAtom } from '../../../atoms';
import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
import { usePageHelper } from '../../blocksuite/block-suite-page-list/utils';
import { filterSortAndGroupCommands } from './filter-commands';
@@ -96,7 +100,6 @@ const useRecentPages = () => {
export const pageToCommand = (
category: CommandCategory,
page: PageMeta,
store: ReturnType<typeof getCurrentStore>,
navigationHelper: ReturnType<typeof useNavigateHelper>,
getPageTitle: ReturnType<typeof useGetBlockSuiteWorkspacePageTitle>,
isPageJournal: (pageId: string) => boolean,
@@ -105,7 +108,8 @@ export const pageToCommand = (
subTitle?: string,
blockId?: string
): CMDKCommand => {
const pageMode = store.get(pageSettingsAtom)?.[page.id]?.mode;
const pageMode = workspace.services.get(PageRecordList).record(page.id).value
?.mode.value;
const title = getPageTitle(page.id) || t['Untitled']();
const commandLabel = {
@@ -147,7 +151,6 @@ export const pageToCommand = (
export const usePageCommands = () => {
const recentPages = useRecentPages();
const pages = useWorkspacePages();
const store = getCurrentStore();
const workspace = useService(Workspace);
const pageHelper = usePageHelper(workspace.blockSuiteWorkspace);
const pageMetaHelper = usePageMetaHelper(workspace.blockSuiteWorkspace);
@@ -185,7 +188,6 @@ export const usePageCommands = () => {
return pageToCommand(
'affine:recent',
page,
store,
navigationHelper,
getPageTitle,
isPageJournal,
@@ -207,9 +209,7 @@ export const usePageCommands = () => {
});
results = pages.map(page => {
const pageMode = store.get(pageSettingsAtom)?.[page.id]?.mode;
const category =
pageMode === 'edgeless' ? 'affine:edgeless' : 'affine:pages';
const category = 'affine:pages';
const subTitle = resultValues.find(
result => result.space === page.id
@@ -220,7 +220,6 @@ export const usePageCommands = () => {
const command = pageToCommand(
category,
page,
store,
navigationHelper,
getPageTitle,
isPageJournal,
@@ -288,7 +287,6 @@ export const usePageCommands = () => {
searchTime,
query,
recentPages,
store,
navigationHelper,
getPageTitle,
isPageJournal,
@@ -367,11 +365,9 @@ export const useCollectionsCommands = () => {
export const useCMDKCommandGroups = () => {
const pageCommands = usePageCommands();
const collectionCommands = useCollectionsCommands();
const currentPageId = useAtomValue(currentPageIdAtom);
const pageSettings = useAtomValue(pageSettingsAtom);
const currentPageMode = currentPageId
? pageSettings[currentPageId]?.mode
: undefined;
const currentPage = useServiceOptional(Page);
const currentPageMode = useLiveData(currentPage?.mode);
const affineCommands = useMemo(() => {
return getAllCommand({
pageMode: currentPageMode,

View File

@@ -1,13 +1,12 @@
import { Tooltip } from '@affine/component/ui/tooltip';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { CloseIcon, NewIcon } from '@blocksuite/icons';
import { useLiveData, useServiceOptional } from '@toeverything/infra';
import { Page } from '@toeverything/infra';
import { useSetAtom } from 'jotai/react';
import { useAtomValue } from 'jotai/react';
import { useCallback, useState } from 'react';
import { useParams } from 'react-router-dom';
import { openSettingModalAtom } from '../../../atoms';
import { currentModeAtom } from '../../../atoms/mode';
import type { SettingProps } from '../../affine/setting-modal';
import { ContactIcon, HelpIcon, KeyboardIcon } from './icons';
import {
@@ -29,7 +28,9 @@ type IslandItemNames = 'whatNew' | 'contact' | 'shortcuts';
const showList = environment.isDesktop ? DESKTOP_SHOW_LIST : DEFAULT_SHOW_LIST;
export const HelpIsland = () => {
const mode = useAtomValue(currentModeAtom);
const page = useServiceOptional(Page);
const pageId = page?.id;
const mode = useLiveData(page?.mode);
const setOpenSettingModalAtom = useSetAtom(openSettingModalAtom);
const [spread, setShowSpread] = useState(false);
const t = useAFFiNEI18N();
@@ -53,8 +54,6 @@ export const HelpIsland = () => {
[openSettingModal]
);
const { pageId } = useParams();
return (
<StyledIsland
spread={spread}

View File

@@ -5,11 +5,10 @@ import { EdgelessIcon, PageIcon } from '@blocksuite/icons';
import type { PageMeta, Workspace } from '@blocksuite/store';
import { useDraggable } from '@dnd-kit/core';
import * as Collapsible from '@radix-ui/react-collapsible';
import { useAtomValue } from 'jotai/index';
import { PageRecordList, useLiveData, useService } from '@toeverything/infra';
import React, { useCallback, useMemo } from 'react';
import { useParams } from 'react-router-dom';
import { pageSettingFamily } from '../../../../atoms';
import { getDragItemId } from '../../../../hooks/affine/use-sidebar-drag';
import { useNavigateHelper } from '../../../../hooks/use-navigate-helper';
import { DragMenuItemOverlay } from '../components/drag-menu-item-overlay';
@@ -37,12 +36,13 @@ export const Page = ({
const pageId = page.id;
const active = params.pageId === pageId;
const setting = useAtomValue(pageSettingFamily(pageId));
const pageRecord = useLiveData(useService(PageRecordList).record(pageId));
const pageMode = useLiveData(pageRecord?.mode);
const dragItemId = getDragItemId('collectionPage', pageId);
const icon = useMemo(() => {
return setting?.mode === 'edgeless' ? <EdgelessIcon /> : <PageIcon />;
}, [setting?.mode]);
return pageMode === 'edgeless' ? <EdgelessIcon /> : <PageIcon />;
}, [pageMode]);
const { jumpToPage } = useNavigateHelper();
const clickPage = useCallback(() => {

View File

@@ -4,11 +4,10 @@ import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { EdgelessIcon, PageIcon } from '@blocksuite/icons';
import { type PageMeta, type Workspace } from '@blocksuite/store';
import * as Collapsible from '@radix-ui/react-collapsible';
import { useAtomValue } from 'jotai/react';
import { PageRecordList, useLiveData, useService } from '@toeverything/infra';
import { useMemo, useState } from 'react';
import { useParams } from 'react-router-dom';
import { pageSettingFamily } from '../../../../atoms';
import * as styles from '../favorite/styles.css';
import { PostfixItem } from './postfix-item';
export interface ReferencePageProps {
@@ -28,10 +27,11 @@ export const ReferencePage = ({
const params = useParams();
const active = params.pageId === pageId;
const setting = useAtomValue(pageSettingFamily(pageId));
const pageRecord = useLiveData(useService(PageRecordList).record(pageId));
const pageMode = useLiveData(pageRecord?.mode);
const icon = useMemo(() => {
return setting?.mode === 'edgeless' ? <EdgelessIcon /> : <PageIcon />;
}, [setting?.mode]);
return pageMode === 'edgeless' ? <EdgelessIcon /> : <PageIcon />;
}, [pageMode]);
const references = useBlockSuitePageReferences(workspace, pageId);
const referencesToShow = useMemo(() => {

View File

@@ -4,11 +4,12 @@ import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { EdgelessIcon, PageIcon } from '@blocksuite/icons';
import { useDraggable } from '@dnd-kit/core';
import * as Collapsible from '@radix-ui/react-collapsible';
import { useAtomValue } from 'jotai/index';
import { useService } from '@toeverything/infra';
import { useLiveData } from '@toeverything/infra';
import { PageRecordList } from '@toeverything/infra';
import { useMemo, useState } from 'react';
import { useParams } from 'react-router-dom';
import { pageSettingFamily } from '../../../../atoms';
import { getDragItemId } from '../../../../hooks/affine/use-sidebar-drag';
import { DragMenuItemOverlay } from '../components/drag-menu-item-overlay';
import { PostfixItem } from '../components/postfix-item';
@@ -28,11 +29,12 @@ export const FavouritePage = ({
const params = useParams();
const active = params.pageId === pageId;
const dragItemId = getDragItemId('favouritePage', pageId);
const pageRecord = useLiveData(useService(PageRecordList).record(pageId));
const pageMode = useLiveData(pageRecord?.mode);
const setting = useAtomValue(pageSettingFamily(pageId));
const icon = useMemo(() => {
return setting?.mode === 'edgeless' ? <EdgelessIcon /> : <PageIcon />;
}, [setting?.mode]);
return pageMode === 'edgeless' ? <EdgelessIcon /> : <PageIcon />;
}, [pageMode]);
const references = useBlockSuitePageReferences(workspace, pageId);
const referencesToShow = useMemo(() => {

View File

@@ -1,5 +1,4 @@
import { MenuItem } from '@affine/component/app-sidebar';
import { currentPageIdAtom } from '@affine/core/atoms/mode';
import {
useJournalInfoHelper,
useJournalRouteHelper,
@@ -7,7 +6,7 @@ import {
import type { BlockSuiteWorkspace } from '@affine/core/shared';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { TodayIcon, TomorrowIcon, YesterdayIcon } from '@blocksuite/icons';
import { useAtomValue } from 'jotai';
import { Page, useServiceOptional } from '@toeverything/infra';
import { useParams } from 'react-router-dom';
interface AppSidebarJournalButtonProps {
@@ -18,11 +17,11 @@ export const AppSidebarJournalButton = ({
workspace,
}: AppSidebarJournalButtonProps) => {
const t = useAFFiNEI18N();
const currentPageId = useAtomValue(currentPageIdAtom);
const currentPage = useServiceOptional(Page);
const { openToday } = useJournalRouteHelper(workspace);
const { journalDate, isJournal } = useJournalInfoHelper(
workspace,
currentPageId
currentPage?.id
);
const params = useParams();
const isJournalActive = isJournal && !!params.pageId;

View File

@@ -1,17 +1,11 @@
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import {
useBlockSuitePageMeta,
usePageMetaHelper,
} from '@affine/core/hooks/use-block-suite-page-meta';
import { usePageMetaHelper } from '@affine/core/hooks/use-block-suite-page-meta';
import { useBlockSuiteWorkspaceHelper } from '@affine/core/hooks/use-block-suite-workspace-helper';
import { CollectionService } from '@affine/core/modules/collection';
import { useService } from '@toeverything/infra';
import { useAtomValue, useSetAtom } from 'jotai';
import { PageRecordList, useService } from '@toeverything/infra';
import { useCallback } from 'react';
import { applyUpdate, encodeStateAsUpdate } from 'yjs';
import { setPageModeAtom } from '../../atoms';
import { currentModeAtom } from '../../atoms/mode';
import type { BlockSuiteWorkspace } from '../../shared';
import { useNavigateHelper } from '../use-navigate-helper';
import { useReferenceLinkHelper } from './use-reference-link-helper';
@@ -22,31 +16,10 @@ export function useBlockSuiteMetaHelper(
const { setPageMeta, getPageMeta, setPageReadonly, setPageTitle } =
usePageMetaHelper(blockSuiteWorkspace);
const { addReferenceLink } = useReferenceLinkHelper(blockSuiteWorkspace);
const metas = useBlockSuitePageMeta(blockSuiteWorkspace);
const setPageMode = useSetAtom(setPageModeAtom);
const currentMode = useAtomValue(currentModeAtom);
const { createPage } = useBlockSuiteWorkspaceHelper(blockSuiteWorkspace);
const { openPage } = useNavigateHelper();
const collectionService = useService(CollectionService);
const switchToPageMode = useCallback(
(pageId: string) => {
setPageMode(pageId, 'page');
},
[setPageMode]
);
const switchToEdgelessMode = useCallback(
(pageId: string) => {
setPageMode(pageId, 'edgeless');
},
[setPageMode]
);
const togglePageMode = useCallback(
(pageId: string) => {
setPageMode(pageId, currentMode === 'edgeless' ? 'page' : 'edgeless');
},
[currentMode, setPageMode]
);
const pageRecordList = useService(PageRecordList);
const addToFavorite = useCallback(
(pageId: string) => {
@@ -77,28 +50,21 @@ export function useBlockSuiteMetaHelper(
// TODO-Doma
// "Remove" may cause ambiguity here. Consider renaming as "moveToTrash".
const removeToTrash = useCallback(
(pageId: string, isRoot = true) => {
const parentMeta = metas.find(m => m.subpageIds?.includes(pageId));
const { subpageIds = [] } = getPageMeta(pageId) ?? {};
subpageIds.forEach(id => {
removeToTrash(id, false);
});
(pageId: string) => {
setPageMeta(pageId, {
trash: true,
trashDate: Date.now(),
trashRelate: isRoot ? parentMeta?.id : undefined,
trashRelate: undefined,
});
setPageReadonly(pageId, true);
collectionService.deletePagesFromCollections([pageId]);
},
[collectionService, getPageMeta, metas, setPageMeta, setPageReadonly]
[collectionService, setPageMeta, setPageReadonly]
);
const restoreFromTrash = useCallback(
(pageId: string) => {
const { subpageIds = [], trashRelate } = getPageMeta(pageId) ?? {};
const { trashRelate } = getPageMeta(pageId) ?? {};
if (trashRelate) {
addReferenceLink(trashRelate, pageId);
@@ -110,9 +76,6 @@ export function useBlockSuiteMetaHelper(
trashRelate: undefined,
});
setPageReadonly(pageId, false);
subpageIds.forEach(id => {
restoreFromTrash(id);
});
},
[addReferenceLink, getPageMeta, setPageMeta, setPageReadonly]
);
@@ -150,6 +113,7 @@ export function useBlockSuiteMetaHelper(
const duplicate = useAsyncCallback(
async (pageId: string, openPageAfterDuplication: boolean = true) => {
const currentPageMode = pageRecordList.record(pageId).value?.mode.value;
const currentPageMeta = getPageMeta(pageId);
const newPage = createPage();
const currentPage = blockSuiteWorkspace.getPage(pageId);
@@ -174,28 +138,24 @@ export function useBlockSuiteMetaHelper(
const newPageTitle =
currentPageMeta.title.replace(lastDigitRegex, '') + `(${newNumber})`;
setPageMode(newPage.id, currentMode);
pageRecordList
.record(newPage.id)
.value?.setMode(currentPageMode || 'page');
setPageTitle(newPage.id, newPageTitle);
openPageAfterDuplication && openPage(blockSuiteWorkspace.id, newPage.id);
},
[
blockSuiteWorkspace,
createPage,
currentMode,
getPageMeta,
openPage,
pageRecordList,
setPageMeta,
setPageMode,
setPageTitle,
]
);
return {
switchToPageMode,
switchToEdgelessMode,
togglePageMode,
publicPage,
cancelPublicPage,

View File

@@ -7,11 +7,10 @@ import {
revokePublicPageMutation,
} from '@affine/graphql';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import type { Workspace } from '@toeverything/infra/workspace';
import type { PageMode, Workspace } from '@toeverything/infra';
import { useSetAtom } from 'jotai';
import { useCallback, useMemo } from 'react';
import type { PageMode } from '../../atoms';
import { useMutation } from '../use-mutation';
import { useQuery } from '../use-query';

View File

@@ -4,7 +4,7 @@ import { WorkspaceFlavour } from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { assertExists } from '@blocksuite/global/utils';
import { EdgelessIcon, HistoryIcon, PageIcon } from '@blocksuite/icons';
import { Workspace } from '@toeverything/infra';
import { Page, useLiveData, Workspace } from '@toeverything/infra';
import {
PreconditionStrategy,
registerAffineCommand,
@@ -18,10 +18,10 @@ import { useBlockSuiteMetaHelper } from './use-block-suite-meta-helper';
import { useExportPage } from './use-export-page';
import { useTrashModalHelper } from './use-trash-modal-helper';
export function useRegisterBlocksuiteEditorCommands(
pageId: string,
mode: 'page' | 'edgeless'
) {
export function useRegisterBlocksuiteEditorCommands() {
const page = useService(Page);
const pageId = page.id;
const mode = useLiveData(page.mode);
const t = useAFFiNEI18N();
const workspace = useService(Workspace);
const blockSuiteWorkspace = workspace.blockSuiteWorkspace;
@@ -42,7 +42,7 @@ export function useRegisterBlocksuiteEditorCommands(
}));
}, [pageId, setPageHistoryModalState]);
const { togglePageMode, toggleFavorite, restoreFromTrash, duplicate } =
const { toggleFavorite, restoreFromTrash, duplicate } =
useBlockSuiteMetaHelper(blockSuiteWorkspace);
const exportHandler = useExportPage(currentPage);
const { setTrashModal } = useTrashModalHelper(blockSuiteWorkspace);
@@ -116,7 +116,7 @@ export function useRegisterBlocksuiteEditorCommands(
: t['com.affine.pageMode.page']()
}`,
run() {
togglePageMode(pageId);
page.toggleMode();
toast(
mode === 'page'
? t['com.affine.toastMessage.edgelessMode']()
@@ -245,10 +245,10 @@ export function useRegisterBlocksuiteEditorCommands(
restoreFromTrash,
t,
toggleFavorite,
togglePageMode,
trash,
isCloudWorkspace,
openHistoryModal,
duplicate,
page,
]);
}

View File

@@ -1,15 +0,0 @@
import { useBlockSuiteWorkspacePage } from '@affine/core/hooks/use-block-suite-workspace-page';
import { Workspace } from '@toeverything/infra';
import { useService } from '@toeverything/infra/di';
import { useAtomValue } from 'jotai';
import { currentPageIdAtom } from '../../atoms/mode';
export const useCurrentPage = () => {
const currentPageId = useAtomValue(currentPageIdAtom);
const currentWorkspace = useService(Workspace);
return useBlockSuiteWorkspacePage(
currentWorkspace.blockSuiteWorkspace,
currentPageId
);
};

View File

@@ -1,4 +1,3 @@
import type { PageMeta } from '@blocksuite/store';
import { noop } from 'lodash-es';
import { useEffect } from 'react';
@@ -16,6 +15,6 @@ export function useDocumentTitle(newTitle?: string | null) {
}, [newTitle]);
}
export function usePageDocumentTitle(pageMeta?: PageMeta) {
useDocumentTitle(pageMeta?.title ? `${pageMeta.title} · AFFiNE` : null);
export function usePageDocumentTitle(pageTitle?: string) {
useDocumentTitle(pageTitle ? `${pageTitle} · AFFiNE` : null);
}

View File

@@ -1,4 +1,4 @@
import { initEmptyPage } from '@toeverything/infra/blocksuite';
import { initEmptyPage } from '@toeverything/infra';
import dayjs from 'dayjs';
import { useCallback, useMemo } from 'react';

View File

@@ -1,8 +1,8 @@
import type { GlobalCache } from '@toeverything/infra';
import type { GlobalCache, GlobalState, Memento } from '@toeverything/infra';
import { Observable } from 'rxjs';
export class LocalStorageGlobalCache implements GlobalCache {
prefix = 'cache:';
export class LocalStorageMemento implements Memento {
constructor(private readonly prefix: string) {}
get<T>(key: string): T | null {
const json = localStorage.getItem(this.prefix + key);
@@ -30,3 +30,21 @@ export class LocalStorageGlobalCache implements GlobalCache {
channel.close();
}
}
export class LocalStorageGlobalCache
extends LocalStorageMemento
implements GlobalCache
{
constructor() {
super('global-cache:');
}
}
export class LocalStorageGlobalState
extends LocalStorageMemento
implements GlobalState
{
constructor() {
super('global-state:');
}
}

View File

@@ -1,12 +1,16 @@
import {
GlobalCache,
GlobalState,
type ServiceCollection,
Workspace,
WorkspaceScope,
} from '@toeverything/infra';
import { CollectionService } from './collection';
import { LocalStorageGlobalCache } from './infra-web/storage';
import {
LocalStorageGlobalCache,
LocalStorageGlobalState,
} from './infra-web/storage';
import { CurrentPageService } from './page';
import {
CurrentWorkspaceService,
@@ -25,5 +29,7 @@ export function configureBusinessServices(services: ServiceCollection) {
}
export function configureWebInfraServices(services: ServiceCollection) {
services.addImpl(GlobalCache, LocalStorageGlobalCache);
services
.addImpl(GlobalCache, LocalStorageGlobalCache)
.addImpl(GlobalState, LocalStorageGlobalState);
}

View File

@@ -16,8 +16,10 @@ import {
LocalSyncStorage,
Page,
PageManager,
type PageMode,
ReadonlyMappingSyncStorage,
RemoteBlobStorage,
useLiveData,
useService,
useServiceOptional,
WorkspaceIdContext,
@@ -34,7 +36,6 @@ import {
useRouteError,
} from 'react-router-dom';
import type { PageMode } from '../../atoms';
import { AppContainer } from '../../components/affine/app-container';
import { PageDetailEditor } from '../../components/page-detail-editor';
import { SharePageNotFoundError } from '../../components/share-page-not-found-error';
@@ -159,9 +160,7 @@ export const Component = () => {
workspace.engine.sync
.waitForSynced()
.then(() => {
const { page } = workspace.services
.get(PageManager)
.openByPageId(pageId);
const { page } = workspace.services.get(PageManager).open(pageId);
workspace.blockSuiteWorkspace.awarenessStore.setReadonly(
page.blockSuitePage,
@@ -186,9 +185,11 @@ export const Component = () => {
]);
const page = useServiceOptional(Page);
const pageTitle = useLiveData(page?.title);
usePageDocumentTitle(page?.meta);
usePageDocumentTitle(pageTitle);
const loginStatus = useCurrentLoginStatus();
if (!page) {
return;
}

View File

@@ -1,8 +1,8 @@
import { EditorModeSwitch } from '@affine/core/components/blocksuite/block-suite-mode-switch';
import ShareHeaderRightItem from '@affine/core/components/cloud/share-header-right-item';
import type { Workspace as BlockSuiteWorkspace } from '@blocksuite/store';
import type { PageMode } from '@toeverything/infra';
import type { PageMode } from '../../atoms';
import { BlocksuiteHeaderTitle } from '../../components/blocksuite/block-suite-header/title/index';
import * as styles from './share-header.css';

View File

@@ -17,14 +17,14 @@ import type { Page as BlockSuitePage } from '@blocksuite/store';
import {
globalBlockSuiteSchema,
Page,
PageListService,
PageManager,
PageRecordList,
useLiveData,
useServiceOptional,
} from '@toeverything/infra';
import { appSettingAtom, Workspace } from '@toeverything/infra';
import { useService } from '@toeverything/infra';
import { useAtom, useAtomValue, useSetAtom, useStore } from 'jotai';
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
import {
memo,
type ReactElement,
@@ -36,8 +36,7 @@ import {
import { useParams } from 'react-router-dom';
import type { Map as YMap } from 'yjs';
import { pageSettingFamily, setPageModeAtom } from '../../../atoms';
import { currentModeAtom, currentPageIdAtom } from '../../../atoms/mode';
import { recentPageIdsBaseAtom } from '../../../atoms';
import { AffineErrorBoundary } from '../../../components/affine/affine-error-boundary';
import { HubIsland } from '../../../components/affine/hub-island';
import { GlobalPageHistoryModal } from '../../../components/affine/page-history-modal';
@@ -116,6 +115,7 @@ const DetailPageLayout = ({
const DetailPageImpl = memo(function DetailPageImpl() {
const page = useService(Page);
const pageRecordList = useService(PageRecordList);
const currentPageId = page.id;
const { openPage, jumpToSubPath } = useNavigateHelper();
const currentWorkspace = useService(Workspace);
@@ -129,17 +129,16 @@ const DetailPageImpl = memo(function DetailPageImpl() {
const collectionService = useService(CollectionService);
const { setTemporaryFilter } = useCollectionManager(collectionService);
const mode = useAtomValue(currentModeAtom);
const setPageMode = useSetAtom(setPageModeAtom);
useRegisterBlocksuiteEditorCommands(currentPageId, mode);
usePageDocumentTitle(pageMeta);
const rootStore = useStore();
const mode = useLiveData(page.mode);
useRegisterBlocksuiteEditorCommands();
const title = useLiveData(page.title);
usePageDocumentTitle(title);
const onLoad = useCallback(
(page: BlockSuitePage, editor: AffineEditorContainer) => {
(bsPage: BlockSuitePage, editor: AffineEditorContainer) => {
try {
// todo(joooye34): improve the following migration code
const surfaceBlock = page.getBlockByFlavour('affine:surface')[0];
const surfaceBlock = bsPage.getBlockByFlavour('affine:surface')[0];
// hotfix for old page
if (
surfaceBlock &&
@@ -152,7 +151,7 @@ const DetailPageImpl = memo(function DetailPageImpl() {
{
'affine:surface': 3,
},
page.spaceDoc
bsPage.spaceDoc
);
}
} catch {}
@@ -177,17 +176,17 @@ const DetailPageImpl = memo(function DetailPageImpl() {
'affine:page'
) as PageService;
pageService.getPageMode = (pageId: string) =>
rootStore.get(pageSettingFamily(pageId)).mode;
pageRecordList.record(pageId).value?.mode.value ?? 'page';
pageService.getPageUpdatedAt = (pageId: string) => {
const linkedPage = page.workspace.getPage(pageId);
const linkedPage = pageRecordList.record(pageId).value;
if (!linkedPage) return new Date();
const updatedDate = linkedPage.meta.updatedDate;
const createDate = linkedPage.meta.createDate;
const updatedDate = linkedPage.meta.value.updatedDate;
const createDate = linkedPage.meta.value.createDate;
return updatedDate ? new Date(updatedDate) : new Date(createDate);
};
setPageMode(currentPageId, mode);
page.setMode(mode);
// fixme: it seems pageLinkClicked is not triggered sometimes?
const dispose = editor.slots.pageLinkClicked.on(({ pageId }) => {
return openPage(blockSuiteWorkspace.id, pageId);
@@ -202,15 +201,14 @@ const DetailPageImpl = memo(function DetailPageImpl() {
};
},
[
blockSuiteWorkspace.id,
currentPageId,
currentWorkspace.id,
jumpToSubPath,
page,
mode,
pageRecordList,
openPage,
setPageMode,
blockSuiteWorkspace.id,
jumpToSubPath,
currentWorkspace.id,
setTemporaryFilter,
rootStore,
]
);
@@ -269,31 +267,31 @@ const DetailPageImpl = memo(function DetailPageImpl() {
});
export const DetailPage = ({ pageId }: { pageId: string }): ReactElement => {
const pageListService = useService(PageListService);
const pageRecordList = useService(PageRecordList);
const pageListReady = useLiveData(pageListService.isReady);
const pageListReady = useLiveData(pageRecordList.isReady);
const pageMetas = useLiveData(pageListService.pages);
const pageRecords = useLiveData(pageRecordList.records);
const pageMeta = useMemo(
() => pageMetas.find(page => page.id === pageId),
[pageMetas, pageId]
const pageRecord = useMemo(
() => pageRecords.find(page => page.id === pageId),
[pageRecords, pageId]
);
const pageManager = useService(PageManager);
const currentPageService = useService(CurrentPageService);
useEffect(() => {
if (!pageMeta) {
if (!pageRecord) {
return;
}
const { page, release } = pageManager.open(pageMeta);
const { page, release } = pageManager.open(pageRecord.id);
currentPageService.openPage(page);
return () => {
currentPageService.closePage();
release();
};
}, [currentPageService, pageManager, pageMeta]);
}, [currentPageService, pageManager, pageRecord]);
const page = useServiceOptional(Page);
@@ -304,6 +302,14 @@ export const DetailPage = ({ pageId }: { pageId: string }): ReactElement => {
currentWorkspace.setPriorityRule(id => id.endsWith(pageId));
}, [pageId, currentWorkspace]);
const jumpOnce = useLiveData(pageRecord?.meta.map(meta => meta.jumpOnce));
useEffect(() => {
if (jumpOnce) {
pageRecord?.setMeta({ jumpOnce: false });
}
}, [jumpOnce, pageRecord]);
// if sync engine has been synced and the page is null, show 404 page.
if (pageListReady && !page) {
return <PageNotFound />;
@@ -313,27 +319,26 @@ export const DetailPage = ({ pageId }: { pageId: string }): ReactElement => {
return <PageDetailSkeleton key="current-page-is-null" />;
}
if (page.meta.jumpOnce) {
currentWorkspace.blockSuiteWorkspace.setPageMeta(page.id, {
jumpOnce: false,
});
}
return <DetailPageImpl />;
};
export const Component = () => {
performanceRenderLogger.info('DetailPage');
const setCurrentPageId = useSetAtom(currentPageIdAtom);
const params = useParams();
const setRecentPageIds = useSetAtom(recentPageIdsBaseAtom);
useEffect(() => {
if (params.pageId) {
localStorage.setItem('last_page_id', params.pageId);
setCurrentPageId(params.pageId);
const pageId = params.pageId;
localStorage.setItem('last_page_id', pageId);
setRecentPageIds(ids => {
// pick 3 recent page ids
return [...new Set([pageId, ...ids]).values()].slice(0, 3);
});
}
}, [params, setCurrentPageId]);
}, [params, setRecentPageIds]);
const pageId = params.pageId;

View File

@@ -37,7 +37,7 @@ export async function configureTestingEnvironment() {
await workspace.engine.sync.waitForSynced();
const { page } = workspace.services.get(PageManager).openByPageId('page0');
const { page } = workspace.services.get(PageManager).open('page0');
rootServices.get(CurrentWorkspaceService).openWorkspace(workspace);
workspace.services.get(CurrentPageService).openPage(page);

View File

@@ -1,5 +1,12 @@
import { WorkspaceFactory, WorkspaceListProvider } from '@toeverything/infra';
import type { ServiceCollection } from '@toeverything/infra/di';
import type { ServiceCollection } from '@toeverything/infra';
import {
GlobalState,
Workspace,
WorkspaceFactory,
WorkspaceListProvider,
WorkspaceLocalState,
WorkspaceScope,
} from '@toeverything/infra';
import { CloudWorkspaceFactory, CloudWorkspaceListProvider } from './cloud';
import {
@@ -7,6 +14,7 @@ import {
LocalWorkspaceFactory,
LocalWorkspaceListProvider,
} from './local';
import { WorkspaceLocalStateImpl } from './local-state';
export * from './cloud';
export * from './local';
@@ -16,7 +24,12 @@ export function configureWorkspaceImplServices(services: ServiceCollection) {
.addImpl(WorkspaceListProvider('affine-cloud'), CloudWorkspaceListProvider)
.addImpl(WorkspaceFactory('affine-cloud'), CloudWorkspaceFactory)
.addImpl(WorkspaceListProvider('local'), LocalWorkspaceListProvider)
.addImpl(WorkspaceFactory('local'), LocalWorkspaceFactory);
.addImpl(WorkspaceFactory('local'), LocalWorkspaceFactory)
.scope(WorkspaceScope)
.addImpl(WorkspaceLocalState, WorkspaceLocalStateImpl, [
Workspace,
GlobalState,
]);
}
/**

View File

@@ -0,0 +1,31 @@
import type {
GlobalState,
Workspace,
WorkspaceLocalState,
} from '@toeverything/infra';
export class WorkspaceLocalStateImpl implements WorkspaceLocalState {
constructor(
private readonly workspace: Workspace,
private readonly globalState: GlobalState
) {}
get<T>(key: string): T | null {
return this.globalState.get<T>(
`workspace-state:${this.workspace.id}:${key}`
);
}
watch<T>(key: string) {
return this.globalState.watch<T>(
`workspace-state:${this.workspace.id}:${key}`
);
}
set<T>(key: string, value: T | null): void {
return this.globalState.set<T>(
`workspace-state:${this.workspace.id}:${key}`,
value
);
}
}