diff --git a/packages/common/graphql/src/graphql/get-doc-summary.gql b/packages/common/graphql/src/graphql/get-doc-summary.gql new file mode 100644 index 000000000..ecafddab7 --- /dev/null +++ b/packages/common/graphql/src/graphql/get-doc-summary.gql @@ -0,0 +1,7 @@ +query getDocSummary($workspaceId: String!, $docId: String!) { + workspace(id: $workspaceId) { + doc(docId: $docId) { + summary + } + } +} \ No newline at end of file diff --git a/packages/common/graphql/src/graphql/index.ts b/packages/common/graphql/src/graphql/index.ts index 7baed6904..325def15d 100644 --- a/packages/common/graphql/src/graphql/index.ts +++ b/packages/common/graphql/src/graphql/index.ts @@ -1454,6 +1454,18 @@ export const getDocDefaultRoleQuery = { }`, }; +export const getDocSummaryQuery = { + id: 'getDocSummaryQuery' as const, + op: 'getDocSummary', + query: `query getDocSummary($workspaceId: String!, $docId: String!) { + workspace(id: $workspaceId) { + doc(docId: $docId) { + summary + } + } +}`, +}; + export const getInviteInfoQuery = { id: 'getInviteInfoQuery' as const, op: 'getInviteInfo', diff --git a/packages/common/graphql/src/schema.ts b/packages/common/graphql/src/schema.ts index c37e0d79c..198d30279 100644 --- a/packages/common/graphql/src/schema.ts +++ b/packages/common/graphql/src/schema.ts @@ -5077,6 +5077,19 @@ export type GetDocDefaultRoleQuery = { }; }; +export type GetDocSummaryQueryVariables = Exact<{ + workspaceId: Scalars['String']['input']; + docId: Scalars['String']['input']; +}>; + +export type GetDocSummaryQuery = { + __typename?: 'Query'; + workspace: { + __typename?: 'WorkspaceType'; + doc: { __typename?: 'DocType'; summary: string | null }; + }; +}; + export type GetInviteInfoQueryVariables = Exact<{ inviteId: Scalars['String']['input']; }>; @@ -6432,6 +6445,11 @@ export type Queries = variables: GetDocDefaultRoleQueryVariables; response: GetDocDefaultRoleQuery; } + | { + name: 'getDocSummaryQuery'; + variables: GetDocSummaryQueryVariables; + response: GetDocSummaryQuery; + } | { name: 'getInviteInfoQuery'; variables: GetInviteInfoQueryVariables; diff --git a/packages/common/nbstore/src/frontend/doc.ts b/packages/common/nbstore/src/frontend/doc.ts index 1f2f1e559..84fb21729 100644 --- a/packages/common/nbstore/src/frontend/doc.ts +++ b/packages/common/nbstore/src/frontend/doc.ts @@ -249,7 +249,7 @@ export class DocFrontend { while (true) { throwIfAborted(signal); - const docId = await this.status.jobDocQueue.asyncPop(signal); + const docId = await this.status.jobDocQueue.asyncPop(undefined, signal); const jobs = this.status.jobMap.get(docId); this.status.jobMap.delete(docId); diff --git a/packages/common/nbstore/src/sync/doc/peer.ts b/packages/common/nbstore/src/sync/doc/peer.ts index 3acead8bd..c94f6162f 100644 --- a/packages/common/nbstore/src/sync/doc/peer.ts +++ b/packages/common/nbstore/src/sync/doc/peer.ts @@ -712,7 +712,7 @@ export class DocSyncPeer { while (true) { throwIfAborted(signal); - const docId = await this.status.jobDocQueue.asyncPop(signal); + const docId = await this.status.jobDocQueue.asyncPop(undefined, signal); while (true) { // batch process jobs for the same doc diff --git a/packages/common/nbstore/src/sync/indexer/index.ts b/packages/common/nbstore/src/sync/indexer/index.ts index 1940c1dd0..4d686766c 100644 --- a/packages/common/nbstore/src/sync/indexer/index.ts +++ b/packages/common/nbstore/src/sync/indexer/index.ts @@ -36,6 +36,8 @@ import { crawlingDocData } from './crawler'; export type IndexerPreferOptions = 'local' | 'remote'; export interface IndexerSyncState { + paused: boolean; + batterySaveMode: boolean; /** * Number of documents currently in the indexing queue */ @@ -167,6 +169,14 @@ export class IndexerSyncImpl implements IndexerSync { this.status.disableBatterySaveMode(); } + pauseSync() { + this.status.pauseSync(); + } + + resumeSync() { + this.status.resumeSync(); + } + start() { if (this.abort) { this.abort.abort(MANUALLY_STOP); @@ -324,6 +334,7 @@ export class IndexerSyncImpl implements IndexerSync { const docId = await this.status.acceptJob(signal); if (docId === this.rootDocId) { + console.log('[indexer] start indexing root doc', docId); // #region crawl root doc for (const [docId, { title }] of this.status.docsInRootDoc) { const existingDoc = this.status.docsInIndexer.get(docId); @@ -401,6 +412,7 @@ export class IndexerSyncImpl implements IndexerSync { // doc is deleted, just skip continue; } + console.log('[indexer] start indexing doc', docId); const docYDoc = new YDoc({ guid: docId }); applyUpdate(docYDoc, docBin.bin); @@ -454,6 +466,8 @@ export class IndexerSyncImpl implements IndexerSync { // #endregion } + console.log('[indexer] complete job', docId); + this.status.completeJob(); } } finally { @@ -619,10 +633,11 @@ class IndexerSyncStatus { currentJob: string | null = null; errorMessage: string | null = null; statusUpdatedSubject$ = new Subject(); - batterySaveMode: { + paused: { promise: Promise; resolve: () => void; } | null = null; + batterySaveMode: boolean = false; state$ = new Observable(subscribe => { const next = () => { @@ -632,6 +647,8 @@ class IndexerSyncStatus { total: 0, errorMessage: this.errorMessage, completed: true, + batterySaveMode: this.batterySaveMode, + paused: this.paused !== null, }); } else { subscribe.next({ @@ -639,6 +656,8 @@ class IndexerSyncStatus { total: this.docsInRootDoc.size + 1, errorMessage: this.errorMessage, completed: this.rootDocReady && this.jobs.length() === 0, + batterySaveMode: this.batterySaveMode, + paused: this.paused !== null, }); } }; @@ -697,10 +716,14 @@ class IndexerSyncStatus { } async acceptJob(abort?: AbortSignal) { - if (this.batterySaveMode) { - await this.batterySaveMode.promise; + if (this.paused) { + await this.paused.promise; } - const job = await this.jobs.asyncPop(abort); + const job = await this.jobs.asyncPop( + // if battery save mode is enabled, only accept jobs with priority > 1; otherwise accept all jobs + this.batterySaveMode ? 1 : undefined, + abort + ); this.currentJob = job; this.statusUpdatedSubject$.next(job); return job; @@ -728,12 +751,33 @@ class IndexerSyncStatus { if (this.batterySaveMode) { return; } - this.batterySaveMode = Promise.withResolvers(); + this.batterySaveMode = true; + this.statusUpdatedSubject$.next(true); } disableBatterySaveMode() { - this.batterySaveMode?.resolve(); - this.batterySaveMode = null; + if (!this.batterySaveMode) { + return; + } + this.batterySaveMode = false; + this.statusUpdatedSubject$.next(true); + } + + pauseSync() { + if (this.paused) { + return; + } + this.paused = Promise.withResolvers(); + this.statusUpdatedSubject$.next(true); + } + + resumeSync() { + if (!this.paused) { + return; + } + this.paused.resolve(); + this.paused = null; + this.statusUpdatedSubject$.next(true); } reset() { @@ -745,6 +789,8 @@ class IndexerSyncStatus { this.rootDoc = new YDoc(); this.rootDocReady = false; this.currentJob = null; + this.batterySaveMode = false; + this.paused = null; this.statusUpdatedSubject$.next(true); } } diff --git a/packages/common/nbstore/src/utils/async-priority-queue.ts b/packages/common/nbstore/src/utils/async-priority-queue.ts index 14ed54c99..f57277e5e 100644 --- a/packages/common/nbstore/src/utils/async-priority-queue.ts +++ b/packages/common/nbstore/src/utils/async-priority-queue.ts @@ -4,8 +4,11 @@ export class AsyncPriorityQueue extends PriorityQueue { private _resolveUpdate: (() => void) | null = null; private _waitForUpdate: Promise | null = null; - async asyncPop(abort?: AbortSignal): Promise { - const update = this.pop(); + async asyncPop( + minimumPriority?: number, + abort?: AbortSignal + ): Promise { + const update = this.pop(minimumPriority); if (update) { return update; } else { @@ -27,7 +30,7 @@ export class AsyncPriorityQueue extends PriorityQueue { }), ]); - return this.asyncPop(abort); + return this.asyncPop(minimumPriority, abort); } } diff --git a/packages/common/nbstore/src/utils/priority-queue.ts b/packages/common/nbstore/src/utils/priority-queue.ts index f27ae7321..a9b133a3a 100644 --- a/packages/common/nbstore/src/utils/priority-queue.ts +++ b/packages/common/nbstore/src/utils/priority-queue.ts @@ -24,13 +24,20 @@ export class PriorityQueue { this.priorityMap.set(id, priority); } - pop() { + pop(minimumPriority?: number) { const node = this.tree.max(); if (!node) { return null; } + if ( + minimumPriority !== undefined && + node.getValue().priority < minimumPriority + ) { + return null; + } + this.tree.removeNode(node); const { id } = node.getValue(); diff --git a/packages/common/nbstore/src/worker/client.ts b/packages/common/nbstore/src/worker/client.ts index aaae07e49..ed034ae7f 100644 --- a/packages/common/nbstore/src/worker/client.ts +++ b/packages/common/nbstore/src/worker/client.ts @@ -87,17 +87,18 @@ export class StoreManagerClient { }); } - enableBatterySaveMode() { + pause() { this.connections.forEach(connection => { - connection.store.enableBatterySaveMode().catch(err => { - console.error('error enabling battery save mode', err); + connection.store.pauseSync().catch(err => { + console.error('error pausing', err); }); }); } - disableBatterySaveMode() { + + resume() { this.connections.forEach(connection => { - connection.store.disableBatterySaveMode().catch(err => { - console.error('error disabling battery save mode', err); + connection.store.resumeSync().catch(err => { + console.error('error resuming', err); }); }); } @@ -135,6 +136,13 @@ export class StoreClient { disableBatterySaveMode(): Promise { return this.client.call('sync.disableBatterySaveMode'); } + + pauseSync() { + return this.client.call('sync.pauseSync'); + } + resumeSync() { + return this.client.call('sync.resumeSync'); + } } class WorkerDocStorage implements DocStorage { diff --git a/packages/common/nbstore/src/worker/consumer.ts b/packages/common/nbstore/src/worker/consumer.ts index 600bdc531..f16939fac 100644 --- a/packages/common/nbstore/src/worker/consumer.ts +++ b/packages/common/nbstore/src/worker/consumer.ts @@ -135,34 +135,44 @@ class StoreConsumer { } private readonly ENABLE_BATTERY_SAVE_MODE_DELAY = 1000; - private enableBatterySaveModeTimeout: NodeJS.Timeout | null = null; - private enabledBatterySaveMode = false; + private syncPauseTimeout: NodeJS.Timeout | null = null; + private syncPaused = false; - enableBatterySaveMode() { - if (this.enableBatterySaveModeTimeout || this.enabledBatterySaveMode) { + private pauseSync() { + if (this.syncPauseTimeout || this.syncPaused) { return; } - this.enableBatterySaveModeTimeout = setTimeout(() => { - if (!this.enabledBatterySaveMode) { - this.indexerSync.enableBatterySaveMode(); - this.enabledBatterySaveMode = true; - console.log('[BatterySaveMode] enabled'); + this.syncPauseTimeout = setTimeout(() => { + if (!this.syncPaused) { + this.indexerSync.pauseSync(); + this.syncPaused = true; + console.log('[IndexerSync] paused'); } }, this.ENABLE_BATTERY_SAVE_MODE_DELAY); } - disableBatterySaveMode() { - if (this.enableBatterySaveModeTimeout) { - clearTimeout(this.enableBatterySaveModeTimeout); - this.enableBatterySaveModeTimeout = null; + private resumeSync() { + if (this.syncPauseTimeout) { + clearTimeout(this.syncPauseTimeout); + this.syncPauseTimeout = null; } - if (this.enabledBatterySaveMode) { - this.indexerSync.disableBatterySaveMode(); - this.enabledBatterySaveMode = false; - console.log('[BatterySaveMode] disabled'); + if (this.syncPaused) { + this.indexerSync.resumeSync(); + this.syncPaused = false; + console.log('[IndexerSync] resumed'); } } + private enableBatterySaveMode() { + console.log('[IndexerSync] enable battery save mode'); + this.indexerSync.enableBatterySaveMode(); + } + + private disableBatterySaveMode() { + console.log('[IndexerSync] disable battery save mode'); + this.indexerSync.disableBatterySaveMode(); + } + private registerHandlers(consumer: OpConsumer) { const collectJobs = new Map< string, @@ -316,6 +326,8 @@ class StoreConsumer { this.indexerSync.aggregate$(table, query, field, options), 'sync.enableBatterySaveMode': () => this.enableBatterySaveMode(), 'sync.disableBatterySaveMode': () => this.disableBatterySaveMode(), + 'sync.pauseSync': () => this.pauseSync(), + 'sync.resumeSync': () => this.resumeSync(), }); } } diff --git a/packages/common/nbstore/src/worker/ops.ts b/packages/common/nbstore/src/worker/ops.ts index 21e7cb706..17e813ce5 100644 --- a/packages/common/nbstore/src/worker/ops.ts +++ b/packages/common/nbstore/src/worker/ops.ts @@ -143,6 +143,8 @@ interface GroupedWorkerOps { sync: { enableBatterySaveMode: [void, void]; disableBatterySaveMode: [void, void]; + pauseSync: [void, void]; + resumeSync: [void, void]; }; } diff --git a/packages/frontend/apps/electron-renderer/src/app/effects/store-manager.ts b/packages/frontend/apps/electron-renderer/src/app/effects/store-manager.ts index fbfb682a9..944f9d1c9 100644 --- a/packages/frontend/apps/electron-renderer/src/app/effects/store-manager.ts +++ b/packages/frontend/apps/electron-renderer/src/app/effects/store-manager.ts @@ -47,13 +47,13 @@ export function setupStoreManager(framework: Framework) { storeManagerClient.dispose(); }); window.addEventListener('focus', () => { - storeManagerClient.disableBatterySaveMode(); + storeManagerClient.resume(); }); window.addEventListener('click', () => { - storeManagerClient.disableBatterySaveMode(); + storeManagerClient.resume(); }); window.addEventListener('blur', () => { - storeManagerClient.enableBatterySaveMode(); + storeManagerClient.pause(); }); framework.impl(NbstoreProvider, { diff --git a/packages/frontend/apps/web/src/app.tsx b/packages/frontend/apps/web/src/app.tsx index 74dbe3fe6..72d18ebb3 100644 --- a/packages/frontend/apps/web/src/app.tsx +++ b/packages/frontend/apps/web/src/app.tsx @@ -42,13 +42,13 @@ window.addEventListener('beforeunload', () => { storeManagerClient.dispose(); }); window.addEventListener('focus', () => { - storeManagerClient.disableBatterySaveMode(); + storeManagerClient.resume(); }); window.addEventListener('click', () => { - storeManagerClient.disableBatterySaveMode(); + storeManagerClient.resume(); }); window.addEventListener('blur', () => { - storeManagerClient.enableBatterySaveMode(); + storeManagerClient.pause(); }); const future = { diff --git a/packages/frontend/core/src/blocksuite/block-suite-editor/bi-directional-link-panel.tsx b/packages/frontend/core/src/blocksuite/block-suite-editor/bi-directional-link-panel.tsx index 91af95942..645de22d2 100644 --- a/packages/frontend/core/src/blocksuite/block-suite-editor/bi-directional-link-panel.tsx +++ b/packages/frontend/core/src/blocksuite/block-suite-editor/bi-directional-link-panel.tsx @@ -1,4 +1,10 @@ -import { Button, Divider, useLitPortalFactory } from '@affine/component'; +import { + Button, + Divider, + observeIntersection, + Skeleton, + useLitPortalFactory, +} from '@affine/component'; import { getViewManager } from '@affine/core/blocksuite/manager/view'; import { patchReferenceRenderer, @@ -30,11 +36,14 @@ import { useLiveData, useServices, } from '@toeverything/infra'; +import { debounce } from 'lodash-es'; import { Fragment, type ReactNode, useCallback, + useEffect, useMemo, + useRef, useState, } from 'react'; @@ -177,14 +186,22 @@ const usePreviewExtensions = () => { return [extensions, portals] as const; }; -const useBacklinkGroups: () => BacklinkGroups[] = () => { +export const BacklinkGroups = () => { + const [extensions, portals] = usePreviewExtensions(); + const { workspaceService, docService } = useServices({ + WorkspaceService, + DocService, + }); + const { docLinksService } = useServices({ DocLinksService, }); const backlinkGroups = useLiveData( - LiveData.computed(get => { - const links = get(docLinksService.backlinks.backlinks$); + docLinksService.backlinks.backlinks$.map(links => { + if (links === undefined) { + return undefined; + } // group by docId const groupedLinks = links.reduce( @@ -203,17 +220,10 @@ const useBacklinkGroups: () => BacklinkGroups[] = () => { }) ); - return backlinkGroups; -}; + useEffect(() => { + docLinksService.backlinks.revalidateFromCloud(); + }, [docLinksService]); -export const BacklinkGroups = () => { - const [extensions, portals] = usePreviewExtensions(); - const { workspaceService, docService } = useServices({ - WorkspaceService, - DocService, - }); - - const backlinkGroups = useBacklinkGroups(); const textRendererOptions = useMemo(() => { const docLinkBaseURLMiddleware: TransformerMiddleware = ({ adapterConfigs, @@ -233,36 +243,87 @@ export const BacklinkGroups = () => { return ( <> - {backlinkGroups.map(linkGroup => ( - { - track.doc.biDirectionalLinksPanel.backlinkTitle.navigate(); - }} + {backlinkGroups === undefined ? ( + + ) : ( + backlinkGroups.map(linkGroup => ( + { + track.doc.biDirectionalLinksPanel.backlinkTitle.navigate(); + }} + /> + } + length={linkGroup.links.length} + docId={docService.doc.id} + linkDocId={linkGroup.docId} + > + - } - length={linkGroup.links.length} - docId={docService.doc.id} - linkDocId={linkGroup.docId} - > - - + + )) + )} + {portals.map(p => ( + {p.portal} ))} - <> - {portals.map(p => ( - {p.portal} - ))} - ); }; +const BacklinkLinks = () => { + const t = useI18n(); + + const [visibility, setVisibility] = useState(false); + const containerRef = useRef(null); + + useEffect(() => { + const container = containerRef.current; + if (!container) return; + + return observeIntersection( + container, + debounce( + entry => { + setVisibility(entry.isIntersecting); + }, + 500, + { + trailing: true, + } + ) + ); + }, []); + + const { docLinksService } = useServices({ + DocLinksService, + }); + + const backlinks = useLiveData(docLinksService.backlinks.backlinks$); + + useEffect(() => { + if (visibility) { + docLinksService.backlinks.revalidateFromCloud(); + } + }, [docLinksService, visibility]); + + const backlinkCount = backlinks?.length; + + return ( +
+
+ {t['com.affine.page-properties.backlinks']()}{' '} + {backlinkCount !== undefined ? `· ${backlinkCount}` : ''} +
+ +
+ ); +}; + export const LinkPreview = ({ linkGroup, textRendererOptions, @@ -361,18 +422,13 @@ export const BiDirectionalLinkPanel = () => { show ? docLinksService.links.links$ : new LiveData([] as Link[]) ); - const backlinkGroups = useBacklinkGroups(); - - const backlinkCount = useMemo(() => { - return backlinkGroups.reduce((acc, link) => acc + link.links.length, 0); - }, [backlinkGroups]); - const handleClickShow = useCallback(() => { setShow(!show); track.doc.biDirectionalLinksPanel.$.toggle({ type: show ? 'collapse' : 'expand', }); }, [show, setShow]); + return (
{!show && } @@ -390,12 +446,7 @@ export const BiDirectionalLinkPanel = () => { <> -
-
- {t['com.affine.page-properties.backlinks']()} · {backlinkCount} -
- -
+
{t['com.affine.page-properties.outgoing-links']()} ·{' '} diff --git a/packages/frontend/core/src/components/page-list/page-content-preview.tsx b/packages/frontend/core/src/components/page-list/page-content-preview.tsx index 6f761841c..278d7fe3f 100644 --- a/packages/frontend/core/src/components/page-list/page-content-preview.tsx +++ b/packages/frontend/core/src/components/page-list/page-content-preview.tsx @@ -1,7 +1,6 @@ -import { DocsSearchService } from '@affine/core/modules/docs-search'; -import { WorkspaceService } from '@affine/core/modules/workspace'; +import { DocSummaryService } from '@affine/core/modules/doc-summary'; import { LiveData, useLiveData, useService } from '@toeverything/infra'; -import { type ReactNode, useEffect, useMemo } from 'react'; +import { type ReactNode, useMemo } from 'react'; interface PagePreviewProps { pageId: string; @@ -14,8 +13,7 @@ const PagePreviewInner = ({ emptyFallback, fallback, }: PagePreviewProps) => { - const docSummary = useService(DocsSearchService); - const workspaceService = useService(WorkspaceService); + const docSummary = useService(DocSummaryService); const summary = useLiveData( useMemo( () => LiveData.from(docSummary.watchDocSummary(pageId), null), @@ -23,16 +21,6 @@ const PagePreviewInner = ({ ) ); - useEffect(() => { - const undo = docSummary.indexer.addPriority(pageId, 100); - return undo; - }, [docSummary, pageId]); - - useEffect(() => { - const undo = workspaceService.workspace.engine.doc.addPriority(pageId, 10); - return undo; - }, [workspaceService, pageId]); - const res = summary === null ? fallback : summary === '' ? emptyFallback : summary; return res; diff --git a/packages/frontend/core/src/components/workspace-selector/workspace-card/index.tsx b/packages/frontend/core/src/components/workspace-selector/workspace-card/index.tsx index e58bcaf63..5a43a1c57 100644 --- a/packages/frontend/core/src/components/workspace-selector/workspace-card/index.tsx +++ b/packages/frontend/core/src/components/workspace-selector/workspace-card/index.tsx @@ -312,8 +312,6 @@ export const WorkspaceCard = forwardRef< onClickOpenSettings?.(workspaceMetadata); }, [onClickOpenSettings, workspaceMetadata]); - console.log(information); - return (
{ + if (collapsed) { + return; + } const abortController = new AbortController(); + const undoSync = workspaceService.workspace.engine.doc.addPriority( + docId, + 10 + ); + const undoIndexer = docsSearchService.indexer.addPriority(docId, 10); docsSearchService.indexer - .waitForDocCompletedWithPriority(docId, 100, abortController.signal) + .waitForDocCompleted(docId, abortController.signal) .then(() => { setReferencesLoading(false); }) @@ -132,9 +143,11 @@ export const NavigationPanelDocNode = ({ } }); return () => { + undoSync(); + undoIndexer(); abortController.abort(MANUALLY_STOP); }; - }, [docId, docsSearchService]); + }, [docId, docsSearchService, workspaceService, collapsed]); const dndData = useMemo(() => { return { diff --git a/packages/frontend/core/src/desktop/dialogs/doc-info/info-modal.tsx b/packages/frontend/core/src/desktop/dialogs/doc-info/info-modal.tsx index cf53aab88..2e06335ea 100644 --- a/packages/frontend/core/src/desktop/dialogs/doc-info/info-modal.tsx +++ b/packages/frontend/core/src/desktop/dialogs/doc-info/info-modal.tsx @@ -14,14 +14,14 @@ import type { DatabaseRow, DatabaseValueCell, } from '@affine/core/modules/doc-info/types'; -import { DocsSearchService } from '@affine/core/modules/docs-search'; +import { DocLinksService } from '@affine/core/modules/doc-link'; import { GuardService } from '@affine/core/modules/permissions'; import { WorkspacePropertyService } from '@affine/core/modules/workspace-property'; import { useI18n } from '@affine/i18n'; import track from '@affine/track'; import { PlusIcon } from '@blocksuite/icons/rc'; -import { LiveData, useLiveData, useServices } from '@toeverything/infra'; -import { useCallback, useMemo, useState } from 'react'; +import { useLiveData, useServices } from '@toeverything/infra'; +import { useCallback, useEffect, useState } from 'react'; import * as styles from './info-modal.css'; import { LinksRow } from './links-row'; @@ -34,11 +34,11 @@ export const InfoTable = ({ onClose: () => void; }) => { const t = useI18n(); - const { docsSearchService, workspacePropertyService, guardService } = + const { workspacePropertyService, guardService, docLinksService } = useServices({ - DocsSearchService, WorkspacePropertyService, GuardService, + DocLinksService, }); const canEditPropertyInfo = useLiveData( guardService.can$('Workspace_Properties_Update') @@ -46,30 +46,9 @@ export const InfoTable = ({ const canEditProperty = useLiveData(guardService.can$('Doc_Update', docId)); const [newPropertyId, setNewPropertyId] = useState(null); const properties = useLiveData(workspacePropertyService.sortedProperties$); - const links = useLiveData( - useMemo( - () => LiveData.from(docsSearchService.watchRefsFrom(docId), null), - [docId, docsSearchService] - ) - ); + const links = useLiveData(docLinksService.links.links$); - const backlinks = useLiveData( - useMemo(() => { - return LiveData.from(docsSearchService.watchRefsTo(docId), []).map( - links => { - const visitedDoc = new Set(); - // for each doc, we only show the first block - return links.filter(link => { - if (visitedDoc.has(link.docId)) { - return false; - } - visitedDoc.add(link.docId); - return true; - }); - } - ); - }, [docId, docsSearchService]) - ); + const backlinks = useLiveData(docLinksService.backlinks.backlinks$); const onBacklinkPropertyChange = useCallback( (_row: DatabaseRow, cell: DatabaseValueCell, _value: unknown) => { @@ -111,6 +90,10 @@ export const InfoTable = ({ [] ); + useEffect(() => { + docLinksService.backlinks.revalidateFromCloud(); + }, [docLinksService.backlinks]); + return ( <> { - const { docsSearchService, workspacePropertyService } = useServices({ - DocsSearchService, + const { workspacePropertyService, docLinksService } = useServices({ WorkspacePropertyService, + DocLinksService, }); const t = useI18n(); const canEditPropertyInfo = useGuard('Workspace_Properties_Update'); const canEditProperty = useGuard('Doc_Update', docId); - const links = useLiveData( - useMemo( - () => LiveData.from(docsSearchService.watchRefsFrom(docId), null), - [docId, docsSearchService] - ) - ); - const backlinks = useLiveData( - useMemo(() => { - return LiveData.from(docsSearchService.watchRefsTo(docId), []).map( - links => { - const visitedDoc = new Set(); - // for each doc, we only show the first block - return links.filter(link => { - if (visitedDoc.has(link.docId)) { - return false; - } - visitedDoc.add(link.docId); - return true; - }); - } - ); - }, [docId, docsSearchService]) - ); + const links = useLiveData(docLinksService.links.links$); + const backlinks = useLiveData(docLinksService.backlinks.backlinks$); const [newPropertyId, setNewPropertyId] = useState(null); @@ -69,6 +48,10 @@ export const DocInfoSheet = ({ setNewPropertyId(property.id); }, []); + useEffect(() => { + docLinksService.backlinks.revalidateFromCloud(); + }, [docLinksService.backlinks]); + const properties = useLiveData(workspacePropertyService.sortedProperties$); return ( diff --git a/packages/frontend/core/src/mobile/components/navigation/nodes/doc/index.tsx b/packages/frontend/core/src/mobile/components/navigation/nodes/doc/index.tsx index 8766c3f55..b3d968ab3 100644 --- a/packages/frontend/core/src/mobile/components/navigation/nodes/doc/index.tsx +++ b/packages/frontend/core/src/mobile/components/navigation/nodes/doc/index.tsx @@ -8,6 +8,7 @@ import { DocsSearchService } from '@affine/core/modules/docs-search'; import { FeatureFlagService } from '@affine/core/modules/feature-flag'; import { GlobalContextService } from '@affine/core/modules/global-context'; import { NavigationPanelService } from '@affine/core/modules/navigation-panel'; +import { WorkspaceService } from '@affine/core/modules/workspace'; import { useI18n } from '@affine/i18n'; import { LiveData, @@ -44,8 +45,10 @@ export const NavigationPanelDocNode = ({ globalContextService, docDisplayMetaService, featureFlagService, + workspaceService, } = useServices({ DocsSearchService, + WorkspaceService, DocsService, GlobalContextService, DocDisplayMetaService, @@ -95,9 +98,17 @@ export const NavigationPanelDocNode = ({ const [referencesLoading, setReferencesLoading] = useState(true); useLayoutEffect(() => { + if (collapsed) { + return; + } const abortController = new AbortController(); + const undoSync = workspaceService.workspace.engine.doc.addPriority( + docId, + 10 + ); + const undoIndexer = docsSearchService.indexer.addPriority(docId, 10); docsSearchService.indexer - .waitForDocCompletedWithPriority(docId, 100, abortController.signal) + .waitForDocCompleted(docId, abortController.signal) .then(() => { setReferencesLoading(false); }) @@ -107,9 +118,11 @@ export const NavigationPanelDocNode = ({ } }); return () => { + undoSync(); + undoIndexer(); abortController.abort(MANUALLY_STOP); }; - }, [docId, docsSearchService]); + }, [docId, docsSearchService, workspaceService, collapsed]); const workspaceDialogService = useService(WorkspaceDialogService); const option = useMemo( diff --git a/packages/frontend/core/src/modules/doc-link/entities/doc-backlinks.ts b/packages/frontend/core/src/modules/doc-link/entities/doc-backlinks.ts index 6fcf0ec33..e9d38888d 100644 --- a/packages/frontend/core/src/modules/doc-link/entities/doc-backlinks.ts +++ b/packages/frontend/core/src/modules/doc-link/entities/doc-backlinks.ts @@ -1,7 +1,20 @@ -import { Entity, LiveData } from '@toeverything/infra'; +import { + catchErrorInto, + effect, + Entity, + exhaustMapWithTrailing, + fromPromise, + LiveData, + onComplete, + onStart, + smartRetry, +} from '@toeverything/infra'; +import { tap } from 'rxjs'; -import type { DocService } from '../../doc'; +import type { DocService, DocsService } from '../../doc'; import type { DocsSearchService } from '../../docs-search'; +import type { FeatureFlagService } from '../../feature-flag'; +import type { WorkspaceService } from '../../workspace'; export interface Backlink { docId: string; @@ -17,13 +30,128 @@ export interface Backlink { export class DocBacklinks extends Entity { constructor( private readonly docsSearchService: DocsSearchService, - private readonly docService: DocService + private readonly docService: DocService, + private readonly docsService: DocsService, + private readonly featureFlagService: FeatureFlagService, + private readonly workspaceService: WorkspaceService ) { super(); } - backlinks$ = LiveData.from( - this.docsSearchService.watchRefsTo(this.docService.doc.id), - [] + backlinks$ = new LiveData(undefined); + + isLoading$ = new LiveData(false); + error$ = new LiveData(undefined); + + revalidateFromCloud = effect( + exhaustMapWithTrailing(() => + fromPromise(async () => { + const searchFromCloud = + this.featureFlagService.flags.enable_battery_save_mode && + this.workspaceService.workspace.flavour !== 'local'; + const { buckets } = await this.docsSearchService.indexer.aggregate( + 'block', + { + type: 'boolean', + occur: 'must', + queries: [ + { + type: 'match', + field: 'refDocId', + match: this.docService.doc.id, + }, + ], + }, + 'docId', + { + hits: { + fields: [ + 'docId', + 'blockId', + 'parentBlockId', + 'parentFlavour', + 'additional', + 'markdownPreview', + ], + pagination: { + limit: 5, // the max number of backlinks to show for each doc + }, + }, + pagination: { + limit: 100, + }, + prefer: searchFromCloud ? 'remote' : 'local', + } + ); + return buckets.flatMap(bucket => { + const title = + this.docsService.list.doc$(bucket.key).value?.title$.value ?? ''; + + if (bucket.key === this.docService.doc.id) { + // Ignore if it is a link to the current document. + return []; + } + + return bucket.hits.nodes.map(node => { + const blockId = node.fields.blockId ?? ''; + const markdownPreview = node.fields.markdownPreview ?? ''; + const additional = + typeof node.fields.additional === 'string' + ? node.fields.additional + : node.fields.additional[0]; + + const additionalData: { + displayMode?: string; + noteBlockId?: string; + } = JSON.parse(additional || '{}'); + + const displayMode = additionalData.displayMode ?? ''; + const noteBlockId = additionalData.noteBlockId ?? ''; + const parentBlockId = + typeof node.fields.parentBlockId === 'string' + ? node.fields.parentBlockId + : node.fields.parentBlockId[0]; + const parentFlavour = + typeof node.fields.parentFlavour === 'string' + ? node.fields.parentFlavour + : node.fields.parentFlavour[0]; + + return { + docId: bucket.key, + blockId: typeof blockId === 'string' ? blockId : blockId[0], + title: title, + markdownPreview: + typeof markdownPreview === 'string' + ? markdownPreview + : markdownPreview[0], + displayMode: + typeof displayMode === 'string' ? displayMode : displayMode[0], + noteBlockId: + typeof noteBlockId === 'string' ? noteBlockId : noteBlockId[0], + parentBlockId: + typeof parentBlockId === 'string' + ? parentBlockId + : parentBlockId[0], + parentFlavour: + typeof parentFlavour === 'string' + ? parentFlavour + : parentFlavour[0], + }; + }); + }); + }).pipe( + smartRetry(), + tap(backlinks => { + this.backlinks$.value = backlinks; + }), + catchErrorInto(this.error$), + onStart(() => { + this.isLoading$.value = true; + }), + onComplete(() => { + this.isLoading$.value = false; + }) + ) + ) ); } diff --git a/packages/frontend/core/src/modules/doc-link/index.ts b/packages/frontend/core/src/modules/doc-link/index.ts index c29edc022..320c6bbfc 100644 --- a/packages/frontend/core/src/modules/doc-link/index.ts +++ b/packages/frontend/core/src/modules/doc-link/index.ts @@ -2,8 +2,10 @@ import { type Framework } from '@toeverything/infra'; import { DocScope } from '../doc/scopes/doc'; import { DocService } from '../doc/services/doc'; +import { DocsService } from '../doc/services/docs'; import { DocsSearchService } from '../docs-search'; -import { WorkspaceScope } from '../workspace'; +import { FeatureFlagService } from '../feature-flag'; +import { WorkspaceScope, WorkspaceService } from '../workspace'; import { DocBacklinks } from './entities/doc-backlinks'; import { DocLinks } from './entities/doc-links'; import { DocLinksService } from './services/doc-links'; @@ -17,6 +19,12 @@ export function configureDocLinksModule(framework: Framework) { .scope(WorkspaceScope) .scope(DocScope) .service(DocLinksService) - .entity(DocBacklinks, [DocsSearchService, DocService]) + .entity(DocBacklinks, [ + DocsSearchService, + DocService, + DocsService, + FeatureFlagService, + WorkspaceService, + ]) .entity(DocLinks, [DocsSearchService, DocService]); } diff --git a/packages/frontend/core/src/modules/doc-summary/index.ts b/packages/frontend/core/src/modules/doc-summary/index.ts new file mode 100644 index 000000000..fd9550ccb --- /dev/null +++ b/packages/frontend/core/src/modules/doc-summary/index.ts @@ -0,0 +1,25 @@ +import type { Framework } from '@toeverything/infra'; + +import { WorkspaceServerService } from '../cloud'; +import { FeatureFlagService } from '../feature-flag'; +import { CacheStorage } from '../storage'; +import { WorkspaceScope, WorkspaceService } from '../workspace'; +import { DocSummaryService } from './services/doc-summary'; +import { DocSummaryStore } from './stores/doc-summary'; + +export { DocSummaryService }; + +export function configureDocSummaryModule(framework: Framework) { + framework + .scope(WorkspaceScope) + .service(DocSummaryService, [ + WorkspaceService, + DocSummaryStore, + FeatureFlagService, + ]) + .store(DocSummaryStore, [ + WorkspaceService, + WorkspaceServerService, + CacheStorage, + ]); +} diff --git a/packages/frontend/core/src/modules/doc-summary/services/doc-summary.ts b/packages/frontend/core/src/modules/doc-summary/services/doc-summary.ts new file mode 100644 index 000000000..67cfa0b51 --- /dev/null +++ b/packages/frontend/core/src/modules/doc-summary/services/doc-summary.ts @@ -0,0 +1,109 @@ +import { effect, fromPromise, Service, smartRetry } from '@toeverything/infra'; +import { catchError, EMPTY, Observable, type Subscription, tap } from 'rxjs'; + +import type { FeatureFlagService } from '../../feature-flag'; +import type { WorkspaceService } from '../../workspace'; +import type { DocSummaryStore } from '../stores/doc-summary'; + +export class DocSummaryService extends Service { + constructor( + private readonly workspaceService: WorkspaceService, + private readonly store: DocSummaryStore, + private readonly featureFlagService: FeatureFlagService + ) { + super(); + } + + private readonly docSummaryCache = new Map< + string, + Observable + >(); + + watchDocSummary(docId: string) { + const cached$ = this.docSummaryCache.get(docId); + if (!cached$) { + const ob$ = new Observable(subscribe => { + if ( + this.workspaceService.workspace.flavour === 'local' || + this.featureFlagService.flags.enable_battery_save_mode.value === false + ) { + // use local indexer + const sub = this.store + .watchDocSummaryFromIndexer(docId) + .subscribe(subscribe); + return () => sub.unsubscribe(); + } + // use cache, and revalidate from cloud + const sub = this.store.watchDocSummaryCache(docId).subscribe(subscribe); + this.revalidateDocSummaryFromCloud(docId); + return () => sub.unsubscribe(); + }); + this.docSummaryCache.set(docId, ob$); + return ob$; + } + return cached$; + } + + private readonly revalidateDocSummaryFromCloud = effect( + (source$: Observable) => { + // make a lifo queue + const queue: string[] = []; + + let currentTask: Subscription | undefined; + + const processTask = () => { + if (currentTask) { + return; + } + const docId = queue.pop(); + if (!docId) { + return; + } + currentTask = fromPromise(this.store.getDocSummaryFromCloud(docId)) + .pipe( + smartRetry(), + tap(summary => { + if (summary) { + this.store.setDocSummaryCache(docId, summary).catch(error => { + console.error(error); + // ignore error + }); + } + }), + catchError(error => { + console.error(error); + // ignore error + return EMPTY; + }) + ) + .subscribe({ + complete() { + currentTask = undefined; + processTask(); + }, + }); + }; + + return new Observable(subscriber => { + const sub = source$.subscribe({ + next: value => { + queue.push(value); + processTask(); + }, + complete: () => { + subscriber.complete(); + }, + }); + return () => { + sub.unsubscribe(); + currentTask?.unsubscribe(); + }; + }); + } + ); + + override dispose() { + this.revalidateDocSummaryFromCloud.unsubscribe(); + super.dispose(); + } +} diff --git a/packages/frontend/core/src/modules/doc-summary/stores/doc-summary.ts b/packages/frontend/core/src/modules/doc-summary/stores/doc-summary.ts new file mode 100644 index 000000000..371b801d8 --- /dev/null +++ b/packages/frontend/core/src/modules/doc-summary/stores/doc-summary.ts @@ -0,0 +1,87 @@ +import { getDocSummaryQuery } from '@affine/graphql'; +import { Store } from '@toeverything/infra'; +import { map, Observable } from 'rxjs'; + +import type { WorkspaceServerService } from '../../cloud'; +import type { CacheStorage } from '../../storage'; +import type { WorkspaceService } from '../../workspace'; + +export class DocSummaryStore extends Store { + get indexer() { + return this.workspaceService.workspace.engine.indexer; + } + + private readonly gql = this.workspaceServerService.server?.gql; + + constructor( + private readonly workspaceService: WorkspaceService, + private readonly workspaceServerService: WorkspaceServerService, + private readonly cacheStorage: CacheStorage + ) { + super(); + } + + async getDocSummaryFromCloud(docId: string) { + return this.gql?.({ + query: getDocSummaryQuery, + variables: { + workspaceId: this.workspaceService.workspace.id, + docId, + }, + }).then(res => res.workspace.doc.summary ?? ''); + } + + watchDocSummaryFromIndexer(docId: string) { + return new Observable(subscribe => { + const undoIndexer = this.indexer.addPriority(docId, 10); + const undoSync = this.workspaceService.workspace.engine.doc.addPriority( + docId, + 10 + ); + const sub = this.indexer + .search$( + 'doc', + { + type: 'match', + field: 'docId', + match: docId, + }, + { + fields: ['summary'], + pagination: { + limit: 1, + }, + } + ) + .pipe( + map(({ nodes }) => { + const node = nodes.at(0); + return ( + (typeof node?.fields.summary === 'string' + ? node?.fields.summary + : node?.fields.summary[0]) ?? '' + ); + }) + ) + .subscribe(subscribe); + return () => { + undoIndexer(); + undoSync(); + sub.unsubscribe(); + }; + }); + } + + async setDocSummaryCache(docId: string, summary: string) { + return this.cacheStorage.set( + `doc-summary:${this.workspaceService.workspace.id}:${docId}`, + summary + ); + } + + watchDocSummaryCache(docId: string) { + return this.cacheStorage.watch( + `doc-summary:${this.workspaceService.workspace.id}:${docId}` + ); + } +} diff --git a/packages/frontend/core/src/modules/doc/entities/doc.ts b/packages/frontend/core/src/modules/doc/entities/doc.ts index daff5fd98..98ca9d5c3 100644 --- a/packages/frontend/core/src/modules/doc/entities/doc.ts +++ b/packages/frontend/core/src/modules/doc/entities/doc.ts @@ -34,6 +34,14 @@ export class Doc extends Entity { this.yDoc.off('afterTransaction', handleTransactionThrottled); handleTransactionThrottled.cancel(); }); + + this.disposables.push( + this.workspaceService.workspace.engine.doc.addPriority(this.id, 100) + ); + + this.disposables.push( + this.workspaceService.workspace.engine.indexer.addPriority(this.id, 100) + ); } /** diff --git a/packages/frontend/core/src/modules/doc/services/doc.ts b/packages/frontend/core/src/modules/doc/services/doc.ts index abdd10bc1..7bf42488c 100644 --- a/packages/frontend/core/src/modules/doc/services/doc.ts +++ b/packages/frontend/core/src/modules/doc/services/doc.ts @@ -4,4 +4,9 @@ import { Doc } from '../entities/doc'; export class DocService extends Service { public readonly doc = this.framework.createEntity(Doc); + + override dispose() { + this.doc.dispose(); + super.dispose(); + } } diff --git a/packages/frontend/core/src/modules/docs-search/services/docs-search.ts b/packages/frontend/core/src/modules/docs-search/services/docs-search.ts index ef99800a6..90cc08116 100644 --- a/packages/frontend/core/src/modules/docs-search/services/docs-search.ts +++ b/packages/frontend/core/src/modules/docs-search/services/docs-search.ts @@ -228,110 +228,6 @@ export class DocsSearchService extends Service { ); } - watchRefsTo(docId: string) { - return this.indexer - .aggregate$( - 'block', - { - type: 'boolean', - occur: 'must', - queries: [ - { - type: 'match', - field: 'refDocId', - match: docId, - }, - ], - }, - 'docId', - { - hits: { - fields: [ - 'docId', - 'blockId', - 'parentBlockId', - 'parentFlavour', - 'additional', - 'markdownPreview', - ], - pagination: { - limit: 5, // the max number of backlinks to show for each doc - }, - }, - pagination: { - limit: 100, - }, - } - ) - .pipe( - switchMap(({ buckets }) => { - return fromPromise(async () => { - return buckets.flatMap(bucket => { - const title = - this.docsService.list.doc$(bucket.key).value?.title$.value ?? - ''; - - if (bucket.key === docId) { - // Ignore if it is a link to the current document. - return []; - } - - return bucket.hits.nodes.map(node => { - const blockId = node.fields.blockId ?? ''; - const markdownPreview = node.fields.markdownPreview ?? ''; - const additional = - typeof node.fields.additional === 'string' - ? node.fields.additional - : node.fields.additional[0]; - - const additionalData: { - displayMode?: string; - noteBlockId?: string; - } = JSON.parse(additional || '{}'); - - const displayMode = additionalData.displayMode ?? ''; - const noteBlockId = additionalData.noteBlockId ?? ''; - const parentBlockId = - typeof node.fields.parentBlockId === 'string' - ? node.fields.parentBlockId - : node.fields.parentBlockId[0]; - const parentFlavour = - typeof node.fields.parentFlavour === 'string' - ? node.fields.parentFlavour - : node.fields.parentFlavour[0]; - - return { - docId: bucket.key, - blockId: typeof blockId === 'string' ? blockId : blockId[0], - title: title, - markdownPreview: - typeof markdownPreview === 'string' - ? markdownPreview - : markdownPreview[0], - displayMode: - typeof displayMode === 'string' - ? displayMode - : displayMode[0], - noteBlockId: - typeof noteBlockId === 'string' - ? noteBlockId - : noteBlockId[0], - parentBlockId: - typeof parentBlockId === 'string' - ? parentBlockId - : parentBlockId[0], - parentFlavour: - typeof parentFlavour === 'string' - ? parentFlavour - : parentFlavour[0], - }; - }); - }); - }); - }) - ); - } - watchDatabasesTo(docId: string) { const DatabaseAdditionalSchema = z.object({ databaseName: z.string().optional(), diff --git a/packages/frontend/core/src/modules/feature-flag/constant.ts b/packages/frontend/core/src/modules/feature-flag/constant.ts index 19e720c29..7b5543462 100644 --- a/packages/frontend/core/src/modules/feature-flag/constant.ts +++ b/packages/frontend/core/src/modules/feature-flag/constant.ts @@ -281,6 +281,13 @@ export const AFFINE_FLAGS = { configurable: true, defaultState: true, }, + enable_battery_save_mode: { + category: 'affine', + displayName: 'Enable Battery Save Mode (Require Restart)', + description: 'Enable battery save mode', + configurable: isCanaryBuild, + defaultState: false, + }, } satisfies { [key in string]: FlagInfo }; // oxlint-disable-next-line no-redeclare diff --git a/packages/frontend/core/src/modules/index.ts b/packages/frontend/core/src/modules/index.ts index fd1acfa14..f4d87570a 100644 --- a/packages/frontend/core/src/modules/index.ts +++ b/packages/frontend/core/src/modules/index.ts @@ -23,6 +23,7 @@ import { configureDocModule } from './doc'; import { configureDocDisplayMetaModule } from './doc-display-meta'; import { configureDocInfoModule } from './doc-info'; import { configureDocLinksModule } from './doc-link'; +import { configureDocSummaryModule } from './doc-summary'; import { configureDocsSearchModule } from './docs-search'; import { configureEditorModule } from './editor'; import { configureEditorSettingModule } from './editor-setting'; @@ -124,4 +125,5 @@ export function configureCommonModules(framework: Framework) { configureCollectionRulesModule(framework); configureIndexerEmbeddingModule(framework); configureCommentModule(framework); + configureDocSummaryModule(framework); } diff --git a/packages/frontend/core/src/modules/workspace-engine/impls/cloud.ts b/packages/frontend/core/src/modules/workspace-engine/impls/cloud.ts index b288bf625..500638678 100644 --- a/packages/frontend/core/src/modules/workspace-engine/impls/cloud.ts +++ b/packages/frontend/core/src/modules/workspace-engine/impls/cloud.ts @@ -337,8 +337,6 @@ class CloudWorkspaceFlavourProvider implements WorkspaceFlavourProvider { const isEmpty = isEmptyUpdate(localData) && isEmptyUpdate(cloudData); - console.log('isEmpty', isEmpty, localData, cloudData); - docStorage.connection.disconnect(); const info = await this.getWorkspaceInfo(id, signal); diff --git a/packages/frontend/core/src/modules/workspace/entities/engine.ts b/packages/frontend/core/src/modules/workspace/entities/engine.ts index 575ccdbc4..4d2560b14 100644 --- a/packages/frontend/core/src/modules/workspace/entities/engine.ts +++ b/packages/frontend/core/src/modules/workspace/entities/engine.ts @@ -4,6 +4,7 @@ import type { } from '@affine/nbstore/worker/client'; import { Entity } from '@toeverything/infra'; +import type { FeatureFlagService } from '../../feature-flag'; import type { NbstoreService } from '../../storage'; import { WorkspaceEngineBeforeStart } from '../events'; import type { WorkspaceService } from '../services/workspace'; @@ -17,7 +18,8 @@ export class WorkspaceEngine extends Entity<{ constructor( private readonly workspaceService: WorkspaceService, - private readonly nbstoreService: NbstoreService + private readonly nbstoreService: NbstoreService, + private readonly featureFlagService: FeatureFlagService ) { super(); } @@ -61,6 +63,14 @@ export class WorkspaceEngine extends Entity<{ `workspace:${this.workspaceService.workspace.flavour}:${this.workspaceService.workspace.id}`, this.props.engineWorkerInitOptions ); + if ( + this.featureFlagService.flags.enable_battery_save_mode.value && + this.workspaceService.workspace.flavour !== 'local' + ) { + store.enableBatterySaveMode().catch(err => { + console.error('error enabling battery save mode', err); + }); + } this.client = store; this.disposables.push(dispose); this.eventBus.emit(WorkspaceEngineBeforeStart, this); @@ -68,6 +78,7 @@ export class WorkspaceEngine extends Entity<{ const rootDoc = this.workspaceService.workspace.docCollection.doc; // priority load root doc this.doc.addPriority(rootDoc.guid, 100); + this.indexer.addPriority(rootDoc.guid, 100); this.doc.start(); this.disposables.push(() => this.doc.stop()); diff --git a/packages/frontend/core/src/modules/workspace/entities/profile.ts b/packages/frontend/core/src/modules/workspace/entities/profile.ts index ecf609927..b0237ae7f 100644 --- a/packages/frontend/core/src/modules/workspace/entities/profile.ts +++ b/packages/frontend/core/src/modules/workspace/entities/profile.ts @@ -62,7 +62,6 @@ export class WorkspaceProfile extends Entity<{ metadata: WorkspaceMetadata }> { } private setProfile(info: WorkspaceProfileInfo) { - console.log('setProfile', info, isEqual(this.profile$.value, info)); if (isEqual(this.profile$.value, info)) { return; } diff --git a/packages/frontend/core/src/modules/workspace/index.ts b/packages/frontend/core/src/modules/workspace/index.ts index bd27ba94e..50beca56c 100644 --- a/packages/frontend/core/src/modules/workspace/index.ts +++ b/packages/frontend/core/src/modules/workspace/index.ts @@ -74,7 +74,11 @@ export function configureWorkspaceModule(framework: Framework) { .service(WorkspaceService) .entity(Workspace, [WorkspaceScope, FeatureFlagService]) .service(WorkspaceEngineService, [WorkspaceScope]) - .entity(WorkspaceEngine, [WorkspaceService, NbstoreService]) + .entity(WorkspaceEngine, [ + WorkspaceService, + NbstoreService, + FeatureFlagService, + ]) .impl(WorkspaceLocalState, WorkspaceLocalStateImpl, [ WorkspaceService, GlobalState,