mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 04:18:54 +00:00
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:
@@ -1,4 +1,3 @@
|
||||
export * from './initialization';
|
||||
export {
|
||||
migratePages as forceUpgradePages,
|
||||
migrateGuidCompatibility,
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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';
|
||||
|
||||
117
packages/common/infra/src/initialization/index.ts
Normal file
117
packages/common/infra/src/initialization/index.ts
Normal 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;
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
65
packages/common/infra/src/page/record.ts
Normal file
65
packages/common/infra/src/page/record.ts
Normal 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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
8
packages/common/infra/src/workspace/storage.ts
Normal file
8
packages/common/infra/src/workspace/storage.ts
Normal 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'
|
||||
);
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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']);
|
||||
});
|
||||
});
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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]
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
@@ -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,
|
||||
]);
|
||||
};
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { initEmptyPage } from '@toeverything/infra/blocksuite';
|
||||
import { initEmptyPage } from '@toeverything/infra';
|
||||
import dayjs from 'dayjs';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
|
||||
@@ -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:');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
31
packages/frontend/workspace-impl/src/local-state.ts
Normal file
31
packages/frontend/workspace-impl/src/local-state.ts
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user