diff --git a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/template.css.ts b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/template.css.ts new file mode 100644 index 000000000..87713f6d1 --- /dev/null +++ b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/template.css.ts @@ -0,0 +1,19 @@ +import { style } from '@vanilla-extract/css'; + +export const menuItem = style({ + display: 'flex', + alignItems: 'center', + gap: 8, +}); +export const menuItemIcon = style({ + fontSize: 24, + lineHeight: 0, +}); +export const menuItemText = style({ + fontSize: 14, + width: 0, + flex: 1, + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + overflow: 'hidden', +}); diff --git a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/template.tsx b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/template.tsx new file mode 100644 index 000000000..de9450303 --- /dev/null +++ b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/template.tsx @@ -0,0 +1,110 @@ +import { Button, Menu, MenuItem } from '@affine/component'; +import { type DocRecord, DocsService } from '@affine/core/modules/doc'; +import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta'; +import { FeatureFlagService } from '@affine/core/modules/feature-flag'; +import { TemplateDocService } from '@affine/core/modules/template-doc'; +import { useLiveData, useService, useServices } from '@toeverything/infra'; +import { useCallback, useState } from 'react'; + +import * as styles from './template.css'; + +export const TemplateDocSetting = () => { + const { featureFlagService, templateDocService } = useServices({ + FeatureFlagService, + TemplateDocService, + }); + const setting = templateDocService.setting; + + const enabled = useLiveData(featureFlagService.flags.enable_template_doc.$); + const loading = useLiveData(setting.loading$); + const pageTemplateDocId = useLiveData(setting.pageTemplateDocId$); + const journalTemplateDocId = useLiveData(setting.journalTemplateDocId$); + + const updatePageTemplate = useCallback( + (id?: string) => { + setting.updatePageTemplateDocId(id); + }, + [setting] + ); + + const updateJournalTemplate = useCallback( + (id?: string) => { + setting.updateJournalTemplateDocId(id); + }, + [setting] + ); + + if (!enabled) return null; + if (loading) return null; + + return ( +
+ Normal Page: + +
+ Journal: + +
+ ); +}; + +interface TemplateSelectorProps { + current?: string; + onChange?: (id?: string) => void; +} +const TemplateSelector = ({ current, onChange }: TemplateSelectorProps) => { + const docsService = useService(DocsService); + const doc = useLiveData(current ? docsService.list.doc$(current) : null); + const isInTrash = useLiveData(doc?.trash$); + + return ( + }> + + + ); +}; + +const List = ({ onChange }: { onChange?: (id?: string) => void }) => { + const list = useService(TemplateDocService).list; + const [docs] = useState(list.getTemplateDocs()); + + const handleClick = useCallback( + (id: string) => { + onChange?.(id); + }, + [onChange] + ); + + return docs.map(doc => { + return ; + }); +}; + +interface DocItemProps { + doc: DocRecord; + onClick?: (id: string) => void; +} +const DocItem = ({ doc, onClick }: DocItemProps) => { + const docDisplayService = useService(DocDisplayMetaService); + const Icon = useLiveData(docDisplayService.icon$(doc.id)); + const title = useLiveData(docDisplayService.title$(doc.id)); + + const handleClick = useCallback(() => { + onClick?.(doc.id); + }, [doc.id, onClick]); + + return ( + +
  • + + {title} +
  • +
    + ); +}; diff --git a/packages/frontend/core/src/modules/db/index.ts b/packages/frontend/core/src/modules/db/index.ts index 7fc1c5ceb..203fff902 100644 --- a/packages/frontend/core/src/modules/db/index.ts +++ b/packages/frontend/core/src/modules/db/index.ts @@ -1,5 +1,6 @@ import type { Framework } from '@toeverything/infra'; +import { WorkspaceServerService } from '../cloud'; import { WorkspaceScope, WorkspaceService } from '../workspace'; import { WorkspaceDB } from './entities/db'; import { WorkspaceDBTable } from './entities/table'; @@ -11,7 +12,7 @@ export { WorkspaceDBService } from './services/db'; export function configureWorkspaceDBModule(framework: Framework) { framework .scope(WorkspaceScope) - .service(WorkspaceDBService, [WorkspaceService]) + .service(WorkspaceDBService, [WorkspaceService, WorkspaceServerService]) .entity(WorkspaceDB) .entity(WorkspaceDBTable, [WorkspaceService]); } diff --git a/packages/frontend/core/src/modules/db/schema/schema.ts b/packages/frontend/core/src/modules/db/schema/schema.ts index 3d17513d9..746dd75bb 100644 --- a/packages/frontend/core/src/modules/db/schema/schema.ts +++ b/packages/frontend/core/src/modules/db/schema/schema.ts @@ -48,6 +48,10 @@ export const AFFiNE_WORKSPACE_USERDATA_DB_SCHEMA = { key: f.string().primaryKey(), index: f.string(), }, + settings: { + key: f.string().primaryKey(), + value: f.json(), + }, } as const satisfies DBSchemaBuilder; export type AFFiNEWorkspaceUserdataDbSchema = typeof AFFiNE_WORKSPACE_USERDATA_DB_SCHEMA; diff --git a/packages/frontend/core/src/modules/db/services/db.ts b/packages/frontend/core/src/modules/db/services/db.ts index 41138c5d8..ed9d8dd8d 100644 --- a/packages/frontend/core/src/modules/db/services/db.ts +++ b/packages/frontend/core/src/modules/db/services/db.ts @@ -1,11 +1,13 @@ import { createORMClient, + LiveData, ObjectPool, Service, YjsDBAdapter, } from '@toeverything/infra'; import { Doc as YDoc } from 'yjs'; +import { AuthService, type WorkspaceServerService } from '../../cloud'; import type { WorkspaceService } from '../../workspace'; import { WorkspaceDB, type WorkspaceDBWithTables } from '../entities/db'; import { @@ -31,7 +33,10 @@ export class WorkspaceDBService extends Service { }, }); - constructor(private readonly workspaceService: WorkspaceService) { + constructor( + private readonly workspaceService: WorkspaceService, + private readonly workspaceServerService: WorkspaceServerService + ) { super(); this.db = this.framework.createEntity( WorkspaceDB, @@ -95,6 +100,25 @@ export class WorkspaceDBService extends Service { return newDB as WorkspaceDBWithTables; } + authService = this.workspaceServerService.server?.scope.get(AuthService); + public get userdataDB$() { + // if is local workspace or no account, use __local__ userdata + // sometimes we may have cloud workspace but no account for a short time, we also use __local__ userdata + if ( + this.workspaceService.workspace.meta.flavour === 'local' || + !this.authService + ) { + return new LiveData(this.userdataDB('__local__')); + } else { + return this.authService.session.account$.map(account => { + if (!account) { + return this.userdataDB('__local__'); + } + return this.userdataDB(account.id); + }); + } + } + static isDBDocId(docId: string) { return docId.startsWith('db$') || docId.startsWith('userdata$'); } diff --git a/packages/frontend/core/src/modules/favorite/index.ts b/packages/frontend/core/src/modules/favorite/index.ts index b5eae5315..2bd08bfb0 100644 --- a/packages/frontend/core/src/modules/favorite/index.ts +++ b/packages/frontend/core/src/modules/favorite/index.ts @@ -1,6 +1,5 @@ import { type Framework } from '@toeverything/infra'; -import { WorkspaceServerService } from '../cloud'; import { WorkspaceDBService } from '../db'; import { WorkspaceScope, WorkspaceService } from '../workspace'; import { FavoriteList } from './entities/favorite-list'; @@ -28,11 +27,7 @@ export function configureFavoriteModule(framework: Framework) { .scope(WorkspaceScope) .service(FavoriteService) .entity(FavoriteList, [FavoriteStore]) - .store(FavoriteStore, [ - WorkspaceDBService, - WorkspaceService, - WorkspaceServerService, - ]) + .store(FavoriteStore, [WorkspaceDBService]) .service(MigrationFavoriteItemsAdapter, [WorkspaceService]) .service(CompatibleFavoriteItemsAdapter, [FavoriteService]); } diff --git a/packages/frontend/core/src/modules/favorite/stores/favorite.ts b/packages/frontend/core/src/modules/favorite/stores/favorite.ts index c4ca68453..1d79a76a8 100644 --- a/packages/frontend/core/src/modules/favorite/stores/favorite.ts +++ b/packages/frontend/core/src/modules/favorite/stores/favorite.ts @@ -1,9 +1,7 @@ import { LiveData, Store } from '@toeverything/infra'; import { map } from 'rxjs'; -import { AuthService, type WorkspaceServerService } from '../../cloud'; import type { WorkspaceDBService } from '../../db'; -import type { WorkspaceService } from '../../workspace'; import type { FavoriteSupportTypeUnion } from '../constant'; import { isFavoriteSupportType } from '../constant'; @@ -14,41 +12,18 @@ export interface FavoriteRecord { } export class FavoriteStore extends Store { - authService = this.workspaceServerService.server?.scope.get(AuthService); - constructor( - private readonly workspaceDBService: WorkspaceDBService, - private readonly workspaceService: WorkspaceService, - private readonly workspaceServerService: WorkspaceServerService - ) { + constructor(private readonly workspaceDBService: WorkspaceDBService) { super(); } - private get userdataDB$() { - // if is local workspace or no account, use __local__ userdata - // sometimes we may have cloud workspace but no account for a short time, we also use __local__ userdata - if ( - this.workspaceService.workspace.meta.flavour === 'local' || - !this.authService - ) { - return new LiveData(this.workspaceDBService.userdataDB('__local__')); - } else { - return this.authService.session.account$.map(account => { - if (!account) { - return this.workspaceDBService.userdataDB('__local__'); - } - return this.workspaceDBService.userdataDB(account.id); - }); - } - } - watchIsLoading() { - return this.userdataDB$ + return this.workspaceDBService.userdataDB$ .map(db => LiveData.from(db.favorite.isLoading$, false)) .flat(); } watchFavorites() { - return this.userdataDB$ + return this.workspaceDBService.userdataDB$ .map(db => LiveData.from(db.favorite.find$(), [])) .flat() .map(raw => { @@ -63,7 +38,7 @@ export class FavoriteStore extends Store { id: string, index: string ): FavoriteRecord { - const db = this.userdataDB$.value; + const db = this.workspaceDBService.userdataDB$.value; const raw = db.favorite.create({ key: this.encodeKey(type, id), index, @@ -72,17 +47,17 @@ export class FavoriteStore extends Store { } reorderFavorite(type: FavoriteSupportTypeUnion, id: string, index: string) { - const db = this.userdataDB$.value; + const db = this.workspaceDBService.userdataDB$.value; db.favorite.update(this.encodeKey(type, id), { index }); } removeFavorite(type: FavoriteSupportTypeUnion, id: string) { - const db = this.userdataDB$.value; + const db = this.workspaceDBService.userdataDB$.value; db.favorite.delete(this.encodeKey(type, id)); } watchFavorite(type: FavoriteSupportTypeUnion, id: string) { - const db = this.userdataDB$.value; + const db = this.workspaceDBService.userdataDB$.value; return LiveData.from( db.favorite .get$(this.encodeKey(type, id)) diff --git a/packages/frontend/core/src/modules/template-doc/entities/setting.ts b/packages/frontend/core/src/modules/template-doc/entities/setting.ts index 746a6e49b..7fbcb0ea1 100644 --- a/packages/frontend/core/src/modules/template-doc/entities/setting.ts +++ b/packages/frontend/core/src/modules/template-doc/entities/setting.ts @@ -1,12 +1,22 @@ import { Entity } from '@toeverything/infra'; -export type TemplateDocSettings = { - templateId?: string; - journalTemplateId?: string; -}; +import type { TemplateDocSettingStore } from '../store/setting'; export class TemplateDocSetting extends Entity { - constructor() { + constructor(private readonly store: TemplateDocSettingStore) { super(); } + + loading$ = this.store.watchIsLoading(); + setting$ = this.store.watchSetting(); + pageTemplateDocId$ = this.store.watchSettingKey('pageTemplateId'); + journalTemplateDocId$ = this.store.watchSettingKey('journalTemplateId'); + + updatePageTemplateDocId(id?: string) { + this.store.updateSetting('pageTemplateId', id); + } + + updateJournalTemplateDocId(id?: string) { + this.store.updateSetting('journalTemplateId', id); + } } diff --git a/packages/frontend/core/src/modules/template-doc/index.ts b/packages/frontend/core/src/modules/template-doc/index.ts index f05ca5e6a..4d4a42192 100644 --- a/packages/frontend/core/src/modules/template-doc/index.ts +++ b/packages/frontend/core/src/modules/template-doc/index.ts @@ -7,6 +7,7 @@ import { TemplateDocList } from './entities/list'; import { TemplateDocSetting } from './entities/setting'; import { TemplateDocService } from './services/template-doc'; import { TemplateDocListStore } from './store/list'; +import { TemplateDocSettingStore } from './store/setting'; export { TemplateDocService }; export * from './view/template-list-menu'; @@ -17,5 +18,6 @@ export const configureTemplateDocModule = (framework: Framework) => { .service(TemplateDocService) .store(TemplateDocListStore, [WorkspaceDBService]) .entity(TemplateDocList, [TemplateDocListStore, DocsService]) - .entity(TemplateDocSetting); + .store(TemplateDocSettingStore, [WorkspaceDBService]) + .entity(TemplateDocSetting, [TemplateDocSettingStore]); }; diff --git a/packages/frontend/core/src/modules/template-doc/store/setting.ts b/packages/frontend/core/src/modules/template-doc/store/setting.ts new file mode 100644 index 000000000..b56f2149f --- /dev/null +++ b/packages/frontend/core/src/modules/template-doc/store/setting.ts @@ -0,0 +1,47 @@ +import { LiveData, Store } from '@toeverything/infra'; + +import type { WorkspaceDBService } from '../../db'; +import type { TemplateDocSettings } from '../type'; + +export class TemplateDocSettingStore extends Store { + private readonly key = 'templateDoc'; + + constructor(private readonly dbService: WorkspaceDBService) { + super(); + } + + watchIsLoading() { + return this.dbService.userdataDB$ + .map(db => LiveData.from(db.settings.isLoading$, false)) + .flat(); + } + + watchSetting() { + return this.dbService.userdataDB$ + .map(db => LiveData.from(db.settings.find$({ key: this.key }), [])) + .flat() + .map(raw => raw?.[0]?.value as TemplateDocSettings); + } + + watchSettingKey(key: T) { + return this.dbService.userdataDB$ + .map(db => LiveData.from(db.settings.find$({ key: this.key }), [])) + .flat() + .map(raw => { + const value = raw?.[0]?.value as TemplateDocSettings; + if (!value) return undefined; + return value[key]; + }); + } + + updateSetting( + key: T, + value: TemplateDocSettings[T] + ) { + const db = this.dbService.userdataDB$.value; + const prev = db.settings.find({ key: this.key })[0]?.value ?? {}; + db.settings.update(this.key, { + value: { ...prev, [key]: value }, + }); + } +} diff --git a/packages/frontend/core/src/modules/template-doc/type.ts b/packages/frontend/core/src/modules/template-doc/type.ts new file mode 100644 index 000000000..d7a288a25 --- /dev/null +++ b/packages/frontend/core/src/modules/template-doc/type.ts @@ -0,0 +1,4 @@ +export type TemplateDocSettings = { + pageTemplateId?: string; + journalTemplateId?: string; +};