Skip to content

Commit 06133e2

Browse files
committed
feat(core): template-doc settings for user-worksapce scope
1 parent 3508787 commit 06133e2

File tree

12 files changed

+239
-46
lines changed

12 files changed

+239
-46
lines changed

packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/index.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { ArrowRightSmallIcon } from '@blocksuite/icons/rc';
1212
import { FrameworkScope } from '@toeverything/infra';
1313
import { useCallback } from 'react';
1414

15+
import { TemplateDocSetting } from '../template';
1516
import { DeleteLeaveWorkspace } from './delete-leave-workspace';
1617
import { EnableCloudPanel } from './enable-cloud';
1718
import { DesktopExportPanel } from './export';
@@ -60,6 +61,7 @@ export const WorkspaceSettingDetail = ({
6061
})}
6162
subtitle={t['com.affine.settings.workspace.description']()}
6263
/>
64+
<TemplateDocSetting />
6365
<SettingWrapper title={t['Info']()}>
6466
<SettingRow
6567
name={t['Workspace Profile']()}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { style } from '@vanilla-extract/css';
2+
3+
export const menuItem = style({
4+
display: 'flex',
5+
alignItems: 'center',
6+
gap: 8,
7+
});
8+
export const menuItemIcon = style({
9+
fontSize: 24,
10+
lineHeight: 0,
11+
});
12+
export const menuItemText = style({
13+
fontSize: 14,
14+
width: 0,
15+
flex: 1,
16+
textOverflow: 'ellipsis',
17+
whiteSpace: 'nowrap',
18+
overflow: 'hidden',
19+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { Button, Menu, MenuItem } from '@affine/component';
2+
import { type DocRecord, DocsService } from '@affine/core/modules/doc';
3+
import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta';
4+
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
5+
import { TemplateDocService } from '@affine/core/modules/template-doc';
6+
import { useLiveData, useService, useServices } from '@toeverything/infra';
7+
import { useCallback, useState } from 'react';
8+
9+
import * as styles from './template.css';
10+
11+
export const TemplateDocSetting = () => {
12+
const { featureFlagService, templateDocService } = useServices({
13+
FeatureFlagService,
14+
TemplateDocService,
15+
});
16+
const setting = templateDocService.setting;
17+
18+
const enabled = useLiveData(featureFlagService.flags.enable_template_doc.$);
19+
const loading = useLiveData(setting.loading$);
20+
const pageTemplateDocId = useLiveData(setting.pageTemplateDocId$);
21+
const journalTemplateDocId = useLiveData(setting.journalTemplateDocId$);
22+
23+
const updatePageTemplate = useCallback(
24+
(id?: string) => {
25+
setting.updatePageTemplateDocId(id);
26+
},
27+
[setting]
28+
);
29+
30+
const updateJournalTemplate = useCallback(
31+
(id?: string) => {
32+
setting.updateJournalTemplateDocId(id);
33+
},
34+
[setting]
35+
);
36+
37+
if (!enabled) return null;
38+
if (loading) return null;
39+
40+
return (
41+
<div>
42+
Normal Page:
43+
<TemplateSelector
44+
current={pageTemplateDocId}
45+
onChange={updatePageTemplate}
46+
/>
47+
<br />
48+
Journal:
49+
<TemplateSelector
50+
current={journalTemplateDocId}
51+
onChange={updateJournalTemplate}
52+
/>
53+
</div>
54+
);
55+
};
56+
57+
interface TemplateSelectorProps {
58+
current?: string;
59+
onChange?: (id?: string) => void;
60+
}
61+
const TemplateSelector = ({ current, onChange }: TemplateSelectorProps) => {
62+
const docsService = useService(DocsService);
63+
const doc = useLiveData(current ? docsService.list.doc$(current) : null);
64+
const isInTrash = useLiveData(doc?.trash$);
65+
66+
return (
67+
<Menu items={<List onChange={onChange} />}>
68+
<Button>{isInTrash ? 'Doc is removed' : (doc?.id ?? 'Unset')}</Button>
69+
</Menu>
70+
);
71+
};
72+
73+
const List = ({ onChange }: { onChange?: (id?: string) => void }) => {
74+
const list = useService(TemplateDocService).list;
75+
const [docs] = useState(list.getTemplateDocs());
76+
77+
const handleClick = useCallback(
78+
(id: string) => {
79+
onChange?.(id);
80+
},
81+
[onChange]
82+
);
83+
84+
return docs.map(doc => {
85+
return <DocItem key={doc.id} doc={doc} onClick={handleClick} />;
86+
});
87+
};
88+
89+
interface DocItemProps {
90+
doc: DocRecord;
91+
onClick?: (id: string) => void;
92+
}
93+
const DocItem = ({ doc, onClick }: DocItemProps) => {
94+
const docDisplayService = useService(DocDisplayMetaService);
95+
const Icon = useLiveData(docDisplayService.icon$(doc.id));
96+
const title = useLiveData(docDisplayService.title$(doc.id));
97+
98+
const handleClick = useCallback(() => {
99+
onClick?.(doc.id);
100+
}, [doc.id, onClick]);
101+
102+
return (
103+
<MenuItem onClick={handleClick}>
104+
<li className={styles.menuItem}>
105+
<Icon className={styles.menuItemIcon} />
106+
<span className={styles.menuItemText}>{title}</span>
107+
</li>
108+
</MenuItem>
109+
);
110+
};

packages/frontend/core/src/modules/db/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { Framework } from '@toeverything/infra';
22

3+
import { WorkspaceServerService } from '../cloud';
34
import { WorkspaceScope, WorkspaceService } from '../workspace';
45
import { WorkspaceDB } from './entities/db';
56
import { WorkspaceDBTable } from './entities/table';
@@ -11,7 +12,7 @@ export { WorkspaceDBService } from './services/db';
1112
export function configureWorkspaceDBModule(framework: Framework) {
1213
framework
1314
.scope(WorkspaceScope)
14-
.service(WorkspaceDBService, [WorkspaceService])
15+
.service(WorkspaceDBService, [WorkspaceService, WorkspaceServerService])
1516
.entity(WorkspaceDB)
1617
.entity(WorkspaceDBTable, [WorkspaceService]);
1718
}

packages/frontend/core/src/modules/db/schema/schema.ts

+4
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ export const AFFiNE_WORKSPACE_USERDATA_DB_SCHEMA = {
4848
key: f.string().primaryKey(),
4949
index: f.string(),
5050
},
51+
settings: {
52+
key: f.string().primaryKey(),
53+
value: f.json(),
54+
},
5155
} as const satisfies DBSchemaBuilder;
5256
export type AFFiNEWorkspaceUserdataDbSchema =
5357
typeof AFFiNE_WORKSPACE_USERDATA_DB_SCHEMA;

packages/frontend/core/src/modules/db/services/db.ts

+25-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import {
22
createORMClient,
3+
LiveData,
34
ObjectPool,
45
Service,
56
YjsDBAdapter,
67
} from '@toeverything/infra';
78
import { Doc as YDoc } from 'yjs';
89

10+
import { AuthService, type WorkspaceServerService } from '../../cloud';
911
import type { WorkspaceService } from '../../workspace';
1012
import { WorkspaceDB, type WorkspaceDBWithTables } from '../entities/db';
1113
import {
@@ -31,7 +33,10 @@ export class WorkspaceDBService extends Service {
3133
},
3234
});
3335

34-
constructor(private readonly workspaceService: WorkspaceService) {
36+
constructor(
37+
private readonly workspaceService: WorkspaceService,
38+
private readonly workspaceServerService: WorkspaceServerService
39+
) {
3540
super();
3641
this.db = this.framework.createEntity(
3742
WorkspaceDB<AFFiNEWorkspaceDbSchema>,
@@ -95,6 +100,25 @@ export class WorkspaceDBService extends Service {
95100
return newDB as WorkspaceDBWithTables<AFFiNEWorkspaceUserdataDbSchema>;
96101
}
97102

103+
authService = this.workspaceServerService.server?.scope.get(AuthService);
104+
public get userdataDB$() {
105+
// if is local workspace or no account, use __local__ userdata
106+
// sometimes we may have cloud workspace but no account for a short time, we also use __local__ userdata
107+
if (
108+
this.workspaceService.workspace.meta.flavour === 'local' ||
109+
!this.authService
110+
) {
111+
return new LiveData(this.userdataDB('__local__'));
112+
} else {
113+
return this.authService.session.account$.map(account => {
114+
if (!account) {
115+
return this.userdataDB('__local__');
116+
}
117+
return this.userdataDB(account.id);
118+
});
119+
}
120+
}
121+
98122
static isDBDocId(docId: string) {
99123
return docId.startsWith('db$') || docId.startsWith('userdata$');
100124
}

packages/frontend/core/src/modules/favorite/index.ts

+1-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { type Framework } from '@toeverything/infra';
22

3-
import { WorkspaceServerService } from '../cloud';
43
import { WorkspaceDBService } from '../db';
54
import { WorkspaceScope, WorkspaceService } from '../workspace';
65
import { FavoriteList } from './entities/favorite-list';
@@ -28,11 +27,7 @@ export function configureFavoriteModule(framework: Framework) {
2827
.scope(WorkspaceScope)
2928
.service(FavoriteService)
3029
.entity(FavoriteList, [FavoriteStore])
31-
.store(FavoriteStore, [
32-
WorkspaceDBService,
33-
WorkspaceService,
34-
WorkspaceServerService,
35-
])
30+
.store(FavoriteStore, [WorkspaceDBService])
3631
.service(MigrationFavoriteItemsAdapter, [WorkspaceService])
3732
.service(CompatibleFavoriteItemsAdapter, [FavoriteService]);
3833
}

packages/frontend/core/src/modules/favorite/stores/favorite.ts

+7-32
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import { LiveData, Store } from '@toeverything/infra';
22
import { map } from 'rxjs';
33

4-
import { AuthService, type WorkspaceServerService } from '../../cloud';
54
import type { WorkspaceDBService } from '../../db';
6-
import type { WorkspaceService } from '../../workspace';
75
import type { FavoriteSupportTypeUnion } from '../constant';
86
import { isFavoriteSupportType } from '../constant';
97

@@ -14,41 +12,18 @@ export interface FavoriteRecord {
1412
}
1513

1614
export class FavoriteStore extends Store {
17-
authService = this.workspaceServerService.server?.scope.get(AuthService);
18-
constructor(
19-
private readonly workspaceDBService: WorkspaceDBService,
20-
private readonly workspaceService: WorkspaceService,
21-
private readonly workspaceServerService: WorkspaceServerService
22-
) {
15+
constructor(private readonly workspaceDBService: WorkspaceDBService) {
2316
super();
2417
}
2518

26-
private get userdataDB$() {
27-
// if is local workspace or no account, use __local__ userdata
28-
// sometimes we may have cloud workspace but no account for a short time, we also use __local__ userdata
29-
if (
30-
this.workspaceService.workspace.meta.flavour === 'local' ||
31-
!this.authService
32-
) {
33-
return new LiveData(this.workspaceDBService.userdataDB('__local__'));
34-
} else {
35-
return this.authService.session.account$.map(account => {
36-
if (!account) {
37-
return this.workspaceDBService.userdataDB('__local__');
38-
}
39-
return this.workspaceDBService.userdataDB(account.id);
40-
});
41-
}
42-
}
43-
4419
watchIsLoading() {
45-
return this.userdataDB$
20+
return this.workspaceDBService.userdataDB$
4621
.map(db => LiveData.from(db.favorite.isLoading$, false))
4722
.flat();
4823
}
4924

5025
watchFavorites() {
51-
return this.userdataDB$
26+
return this.workspaceDBService.userdataDB$
5227
.map(db => LiveData.from(db.favorite.find$(), []))
5328
.flat()
5429
.map(raw => {
@@ -63,7 +38,7 @@ export class FavoriteStore extends Store {
6338
id: string,
6439
index: string
6540
): FavoriteRecord {
66-
const db = this.userdataDB$.value;
41+
const db = this.workspaceDBService.userdataDB$.value;
6742
const raw = db.favorite.create({
6843
key: this.encodeKey(type, id),
6944
index,
@@ -72,17 +47,17 @@ export class FavoriteStore extends Store {
7247
}
7348

7449
reorderFavorite(type: FavoriteSupportTypeUnion, id: string, index: string) {
75-
const db = this.userdataDB$.value;
50+
const db = this.workspaceDBService.userdataDB$.value;
7651
db.favorite.update(this.encodeKey(type, id), { index });
7752
}
7853

7954
removeFavorite(type: FavoriteSupportTypeUnion, id: string) {
80-
const db = this.userdataDB$.value;
55+
const db = this.workspaceDBService.userdataDB$.value;
8156
db.favorite.delete(this.encodeKey(type, id));
8257
}
8358

8459
watchFavorite(type: FavoriteSupportTypeUnion, id: string) {
85-
const db = this.userdataDB$.value;
60+
const db = this.workspaceDBService.userdataDB$.value;
8661
return LiveData.from<FavoriteRecord | undefined>(
8762
db.favorite
8863
.get$(this.encodeKey(type, id))
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,22 @@
11
import { Entity } from '@toeverything/infra';
22

3-
export type TemplateDocSettings = {
4-
templateId?: string;
5-
journalTemplateId?: string;
6-
};
3+
import type { TemplateDocSettingStore } from '../store/setting';
74

85
export class TemplateDocSetting extends Entity {
9-
constructor() {
6+
constructor(private readonly store: TemplateDocSettingStore) {
107
super();
118
}
9+
10+
loading$ = this.store.watchIsLoading();
11+
setting$ = this.store.watchSetting();
12+
pageTemplateDocId$ = this.store.watchSettingKey('pageTemplateId');
13+
journalTemplateDocId$ = this.store.watchSettingKey('journalTemplateId');
14+
15+
updatePageTemplateDocId(id?: string) {
16+
this.store.updateSetting('pageTemplateId', id);
17+
}
18+
19+
updateJournalTemplateDocId(id?: string) {
20+
this.store.updateSetting('journalTemplateId', id);
21+
}
1222
}

packages/frontend/core/src/modules/template-doc/index.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { TemplateDocList } from './entities/list';
77
import { TemplateDocSetting } from './entities/setting';
88
import { TemplateDocService } from './services/template-doc';
99
import { TemplateDocListStore } from './store/list';
10+
import { TemplateDocSettingStore } from './store/setting';
1011

1112
export { TemplateDocService };
1213
export * from './view/template-list-menu';
@@ -17,5 +18,6 @@ export const configureTemplateDocModule = (framework: Framework) => {
1718
.service(TemplateDocService)
1819
.store(TemplateDocListStore, [WorkspaceDBService])
1920
.entity(TemplateDocList, [TemplateDocListStore, DocsService])
20-
.entity(TemplateDocSetting);
21+
.store(TemplateDocSettingStore, [WorkspaceDBService])
22+
.entity(TemplateDocSetting, [TemplateDocSettingStore]);
2123
};

0 commit comments

Comments
 (0)