From a22a816fe649f5c85ad74d35ad1dddc4506a38fd Mon Sep 17 00:00:00 2001 From: Oliver Date: Sat, 18 May 2024 11:39:33 +0200 Subject: [PATCH] Greatly improve the loading speed and UX of the annotation sidebar --- .../ribbon/react/containers/ribbon/logic.ts | 34 ++- .../AnnotationsSidebarContainer.tsx | 2 +- .../containers/AnnotationsSidebarInPage.tsx | 39 +++- .../annotations-sidebar/containers/logic.ts | 207 ++++++++++-------- .../annotations-sidebar/containers/types.ts | 4 +- 5 files changed, 174 insertions(+), 112 deletions(-) diff --git a/src/in-page-ui/ribbon/react/containers/ribbon/logic.ts b/src/in-page-ui/ribbon/react/containers/ribbon/logic.ts index 3eda67cce2..ce66df404f 100644 --- a/src/in-page-ui/ribbon/react/containers/ribbon/logic.ts +++ b/src/in-page-ui/ribbon/react/containers/ribbon/logic.ts @@ -279,10 +279,12 @@ export class RibbonContainerLogic extends UILogic< hydrateStateFromDB: EventHandler<'hydrateStateFromDB'> = async ({ event: { url }, }) => { + // Stage 1: All relevant metadata let lists = [] let interActionTimestamps = [] this.emitMutation({ + fullPageUrl: { $set: url }, bookmark: { isBookmarked: { $set: false }, lastBookmarkTimestamp: { $set: null }, @@ -313,13 +315,18 @@ export class RibbonContainerLogic extends UILogic< ) // this section is there because sometimes when switching pages in web apps, the cache is still the old one when trying to see if the page has annotations - annotationsByURL.map((annotation) => { return interActionTimestamps.push( Math.floor(annotation.createdWhen / 1000), ) }) + // Set data for lists and annotations + this.emitMutation({ + lists: { pageListIds: { $set: lists } }, + annotations: { $set: annotationsByURL.length }, + }) + const bookmark = await this.dependencies.bookmarks.findBookmark(url) const isBookmarked = @@ -329,24 +336,31 @@ export class RibbonContainerLogic extends UILogic< interActionTimestamps.push(bookmark.time) } const saveTime = Math.min.apply(Math, interActionTimestamps) + this.emitMutation({ + bookmark: { + isBookmarked: { $set: !!isBookmarked }, + lastBookmarkTimestamp: { $set: saveTime }, + }, + }) + + // Enable Ribbon after everything set + this.emitMutation({ + isRibbonEnabled: { + $set: await this.dependencies.getSidebarEnabled(), + }, + }) const activityStatus = await this.dependencies.syncSettings.activityIndicator.get( 'feedHasActivity', ) + + // set general settings that are not important for first load this.emitMutation({ - fullPageUrl: { $set: url }, pausing: { isPaused: { $set: true, }, }, - bookmark: { - isBookmarked: { $set: !!isBookmarked }, - lastBookmarkTimestamp: { $set: saveTime }, - }, - isRibbonEnabled: { - $set: await this.dependencies.getSidebarEnabled(), - }, tooltip: { isTooltipEnabled: { $set: await this.dependencies.tooltip.getState(), @@ -357,8 +371,6 @@ export class RibbonContainerLogic extends UILogic< $set: await this.dependencies.highlights.getState(), }, }, - lists: { pageListIds: { $set: lists } }, - annotations: { $set: annotationsByURL.length }, hasFeedActivity: { $set: activityStatus }, }) } diff --git a/src/sidebar/annotations-sidebar/containers/AnnotationsSidebarContainer.tsx b/src/sidebar/annotations-sidebar/containers/AnnotationsSidebarContainer.tsx index 122f938c60..e4416e517f 100644 --- a/src/sidebar/annotations-sidebar/containers/AnnotationsSidebarContainer.tsx +++ b/src/sidebar/annotations-sidebar/containers/AnnotationsSidebarContainer.tsx @@ -1287,7 +1287,7 @@ export class AnnotationsSidebarContainer< reply.reference.id === replyReference.id, )?.userReference?.id === - this.state.currentUserReference?.id, + this.state.currentUserId, comment: this.state.replyEditStates[ replyReference.id diff --git a/src/sidebar/annotations-sidebar/containers/AnnotationsSidebarInPage.tsx b/src/sidebar/annotations-sidebar/containers/AnnotationsSidebarInPage.tsx index b2a3a0a044..4f56984806 100644 --- a/src/sidebar/annotations-sidebar/containers/AnnotationsSidebarInPage.tsx +++ b/src/sidebar/annotations-sidebar/containers/AnnotationsSidebarInPage.tsx @@ -229,8 +229,13 @@ export class AnnotationsSidebarInPage extends AnnotationsSidebarContainer< private handleExternalAction = async (event: SidebarActionOptions) => { // instantl load page summaries bc they are not dependent on initlogicpromise + await Promise.all([this.initLogicPromise]) + await this.processEvent('setSidebarVisible', null) if (event.action === 'show_page_summary') { + await this.processEvent('setActiveSidebarTab', { + tab: 'summary', + }) await this.processEvent('askAIviaInPageInteractions', { textToProcess: event.highlightedText, prompt: event.prompt, @@ -270,7 +275,6 @@ export class AnnotationsSidebarInPage extends AnnotationsSidebarContainer< await this.processEvent('setActiveSidebarTab', { tab: 'annotations', }) - await sleepPromise(100) } this.processEvent('createYoutubeTimestampWithScreenshot', { @@ -311,18 +315,24 @@ export class AnnotationsSidebarInPage extends AnnotationsSidebarContainer< } // Don't handle any external action that depend on cache until init logic has completed - await Promise.all([ - this.initLogicPromise, - this.props.inPageUI.cacheLoadPromise, - ]) + await Promise.all([this.props.inPageUI.cacheLoadPromise]) if (event.action === 'selected_list_mode_from_web_ui') { + if (this.state.activeTab !== 'spaces') { + await this.processEvent('setActiveSidebarTab', { + tab: 'spaces', + }) + } await this.processEvent('setSelectedListFromWebUI', { sharedListId: event.sharedListId, manuallyPullLocalListData: event.manuallyPullLocalListData, }) } else if (event.action === 'show_annotation') { + if (this.state.activeTab !== 'annotations') { + await this.processEvent('setActiveSidebarTab', { + tab: 'annotations', + }) + } await this.activateAnnotation(event.annotationCacheId, 'show') - await sleepPromise(500) if ( this.state.selectedListId && this.state.activeTab === 'spaces' @@ -336,6 +346,11 @@ export class AnnotationsSidebarInPage extends AnnotationsSidebarContainer< }) } } else if (event.action === 'edit_annotation') { + if (this.state.activeTab !== 'annotations') { + await this.processEvent('setActiveSidebarTab', { + tab: 'annotations', + }) + } await this.processEvent('setAnnotationEditMode', { instanceLocation: this.state.selectedListId && @@ -359,6 +374,11 @@ export class AnnotationsSidebarInPage extends AnnotationsSidebarContainer< 'edit_spaces', ) } else if (event.action === 'set_sharing_access') { + // if (this.state.activeTab !== 'annotations') { + // await this.processEvent('setActiveSidebarTab', { + // tab: 'annotations', + // }) + // } await this.processEvent('receiveSharingAccessChange', { sharingAccess: event.annotationSharingAccess, }) @@ -370,15 +390,18 @@ export class AnnotationsSidebarInPage extends AnnotationsSidebarContainer< }) } else if (event.action === 'cite_page') { await this.processEvent('setActiveSidebarTab', { tab: 'spaces' }) - await sleepPromise(300) await this.processEvent('openPageCitationMenu', null) } else if (event.action === 'share_page_link') { await this.processEvent('setActiveSidebarTab', { tab: 'spaces' }) - await sleepPromise(300) await this.processEvent('openPageLinkShareMenu', null) } else if (event.action === 'check_sidebar_status') { return true } else if (event.action === 'set_focus_mode') { + if (this.state.activeTab !== 'spaces') { + await this.processEvent('setActiveSidebarTab', { + tab: 'spaces', + }) + } const unifiedListId: UnifiedList['unifiedId'] = this.props.annotationsCache.getListByLocalId( event.listId, ).unifiedId diff --git a/src/sidebar/annotations-sidebar/containers/logic.ts b/src/sidebar/annotations-sidebar/containers/logic.ts index 0e53b7bcee..f39748fc94 100644 --- a/src/sidebar/annotations-sidebar/containers/logic.ts +++ b/src/sidebar/annotations-sidebar/containers/logic.ts @@ -115,6 +115,7 @@ import { CLOUDFLARE_WORKER_URLS } from '@worldbrain/memex-common/lib/content-sha import { DEF_HIGHLIGHT_CSS_CLASS } from '@worldbrain/memex-common/lib/in-page-ui/highlighting/constants' import type { HighlightColor } from '@worldbrain/memex-common/lib/common-ui/components/highlightColorPicker/types' import { convertLinksInAIResponse } from '@worldbrain/memex-common/lib/ai-chat' +import { UserReference } from '@worldbrain/memex-common/lib/web-interface/types/users' const md = new MarkdownIt() export type SidebarContainerOptions = SidebarContainerDependencies & { @@ -273,7 +274,7 @@ export class SidebarContainerLogic extends UILogic< aiQueryEditorState: null, users: {}, - currentUserReference: null, + currentUserId: null, pillVisibility: 'unhover', videoDetails: null, summaryModeActiveTab: 'Answer', @@ -879,69 +880,117 @@ export class SidebarContainerLogic extends UILogic< runtimeAPI, } = this.options - const signupDate = new Date( - await (await this.options.authBG.getCurrentUser())?.creationTime, - ).getTime() + // All the things necessary for the sidebar to function + await loadInitial(this, async () => { + this.showState = initialState ?? 'hidden' + this.emitMutation({ + showState: { $set: initialState ?? 'hidden' }, + loadState: { $set: 'running' }, + }) - this.emitMutation({ - signupDate: { $set: signupDate }, - isTrial: { - $set: await enforceTrialPeriod30Days( - this.options.browserAPIs, - signupDate, - ), - }, - }) + if (fullPageUrl == null) { + return + } + this.fullPageUrl = fullPageUrl - const userReference = await this.options.getCurrentUser() - this.emitMutation({ - currentUserReference: { $set: userReference ?? null }, - }) + const currentUser = await this.options.authBG.getCurrentUser() + if (currentUser) { + this.emitMutation({ + currentUserId: { $set: currentUser.id ?? null }, + }) + const signupDate = new Date(currentUser?.creationTime).getTime() + this.emitMutation({ + signupDate: { $set: signupDate }, + isTrial: { + $set: await enforceTrialPeriod30Days( + this.options.browserAPIs, + signupDate, + ), + }, + }) + } - this.sidebar = document - .getElementById('memex-sidebar-container') - ?.shadowRoot.getElementById('annotationSidebarContainer') - this.readingViewState = - (await browser.storage.local.get('@Sidebar-reading_view')) ?? true - // this.readingViewStorageListener(true) + // add trigger for signing up if the current user is not available - const openAIKey = await this.syncSettings.openAI?.get('apiKey') - const hasAPIKey = openAIKey && openAIKey?.trim().startsWith('sk-') + const openAIKey = await this.syncSettings.openAI?.get('apiKey') + const hasAPIKey = openAIKey && openAIKey?.trim().startsWith('sk-') - const selectedModel = await this.syncSettings.openAI.get( - 'selectedModel', - ) - - this.emitMutation({ - hasKey: { $set: hasAPIKey }, - AImodel: { $set: selectedModel ?? 'claude-3-haiku' }, - }) + const selectedModel = await this.syncSettings.openAI.get( + 'selectedModel', + ) - await loadInitial(this, async () => { - this.showState = initialState ?? 'hidden' this.emitMutation({ - showState: { $set: initialState ?? 'hidden' }, - loadState: { $set: 'running' }, + hasKey: { $set: hasAPIKey }, + AImodel: { $set: selectedModel ?? 'claude-3-haiku' }, }) + this.sidebar = document + .getElementById('memex-sidebar-container') + ?.shadowRoot.getElementById('annotationSidebarContainer') + this.readingViewState = + (await browser.storage.local.get('@Sidebar-reading_view')) ?? + true + if (initialState === 'visible') { this.readingViewStorageListener(true) } - if (fullPageUrl == null) { - return + if (isUrlPDFViewerUrl(window.location.href, { runtimeAPI })) { + const width = SIDEBAR_WIDTH_STORAGE_KEY + + this.emitMutation({ + showState: { $set: 'visible' }, + sidebarWidth: { $set: width }, + }) + + setTimeout(async () => { + await storageAPI.local.set({ + '@Sidebar-reading_view': true, + }) + }, 1000) } - this.fullPageUrl = fullPageUrl - if (shouldHydrateCacheOnInit) { - await this.hydrateAnnotationsCache(this.fullPageUrl, { - renderHighlights: true, + const pageAlreadySaved = + (await this.options.pageIndexingBG.getPageMetadata({ + normalizedPageUrl: normalizeUrl(this.fullPageUrl), + })) != null + + this.emitMutation({ + pageAlreadySaved: { $set: pageAlreadySaved }, + }) + + const highlightColors = await this.fetchHighlightColors() + + this.emitMutation({ highlightColors: { $set: highlightColors } }) + + // Load the setting for the auto-adding of notes to spaces + const isAutoAddEnabled = await this.syncSettings.extension.get( + 'shouldAutoAddSpaces', + ) + + if (isAutoAddEnabled == null) { + this.emitMutation({ + isAutoAddEnabled: { $set: true }, + }) + return this.syncSettings.extension.set( + 'shouldAutoAddSpaces', + true, + ) + } else { + this.emitMutation({ + isAutoAddEnabled: { $set: isAutoAddEnabled }, }) } - this.syncCachePageListsState(this.fullPageUrl) - await this.setPageActivityState(this.fullPageUrl) }) + // after sidebar is ready load the annotations cache + + if (shouldHydrateCacheOnInit) { + await this.hydrateAnnotationsCache(this.fullPageUrl, { + renderHighlights: true, + }) + } + this.syncCachePageListsState(this.fullPageUrl) this.setupRemoteEventListeners() annotationsCache.events.addListener( 'newAnnotationsState', @@ -958,50 +1007,6 @@ export class SidebarContainerLogic extends UILogic< // Set initial state, based on what's in the cache (assuming it already has been hydrated) this.cacheAnnotationsSubscription(annotationsCache.annotations) this.cacheListsSubscription(annotationsCache.lists) - - if (isUrlPDFViewerUrl(window.location.href, { runtimeAPI })) { - const width = SIDEBAR_WIDTH_STORAGE_KEY - - this.emitMutation({ - showState: { $set: 'visible' }, - sidebarWidth: { $set: width }, - }) - - setTimeout(async () => { - await storageAPI.local.set({ - '@Sidebar-reading_view': true, - }) - }, 1000) - } - - const pageAlreadySaved = - (await this.options.pageIndexingBG.getPageMetadata({ - normalizedPageUrl: normalizeUrl(this.fullPageUrl), - })) != null - - this.emitMutation({ - pageAlreadySaved: { $set: pageAlreadySaved }, - }) - - const highlightColors = await this.fetchHighlightColors() - - this.emitMutation({ highlightColors: { $set: highlightColors } }) - - // Load the setting for the auto-adding of notes to spaces - const isAutoAddEnabled = await this.syncSettings.extension.get( - 'shouldAutoAddSpaces', - ) - - if (isAutoAddEnabled == null) { - this.emitMutation({ - isAutoAddEnabled: { $set: true }, - }) - return this.syncSettings.extension.set('shouldAutoAddSpaces', true) - } else { - this.emitMutation({ - isAutoAddEnabled: { $set: isAutoAddEnabled }, - }) - } } cleanup = () => { @@ -2871,6 +2876,11 @@ export class SidebarContainerLogic extends UILogic< name: event.spaceName, }) + const userReference = { + id: previousState.currentUserId, + type: 'user-reference', + } as UserReference + this.options.annotationsCache.addList({ name: event.spaceName, collabKey, @@ -2879,7 +2889,7 @@ export class SidebarContainerLogic extends UILogic< hasRemoteAnnotationsToLoad: false, type: 'user-list', unifiedAnnotationIds: [], - creator: previousState.currentUserReference ?? undefined, + creator: userReference ?? undefined, parentLocalId: null, isPrivate: true, }) @@ -2905,6 +2915,11 @@ export class SidebarContainerLogic extends UILogic< name: event.spaceName, }) + const userReference = { + id: previousState.currentUserId, + type: 'user-reference', + } as UserReference + this.options.annotationsCache.addList({ name: event.spaceName, collabKey, @@ -2913,7 +2928,7 @@ export class SidebarContainerLogic extends UILogic< hasRemoteAnnotationsToLoad: false, type: 'user-list', unifiedAnnotationIds: [], - creator: previousState.currentUserReference ?? undefined, + creator: userReference ?? undefined, parentLocalId: null, isPrivate: true, }) @@ -3768,6 +3783,16 @@ export class SidebarContainerLogic extends UILogic< }) } + setSidebarVisible: EventHandler<'setSidebarVisible'> = async ({ + event, + previousState, + }) => { + this.showState = 'visible' + this.emitMutation({ + showState: { $set: 'visible' }, + }) + } + setActiveSidebarTabEvents(activeTab) { this.options.summarizeBG.setActiveSidebarTab({ activeTab: activeTab }) } diff --git a/src/sidebar/annotations-sidebar/containers/types.ts b/src/sidebar/annotations-sidebar/containers/types.ts index 123506e440..62b8bf8e38 100644 --- a/src/sidebar/annotations-sidebar/containers/types.ts +++ b/src/sidebar/annotations-sidebar/containers/types.ts @@ -205,7 +205,7 @@ export interface SidebarContainerState extends AnnotationConversationsState { prompt: string /** TODO: Properly set up logic to use this state instead of querying for user each time. */ - currentUserReference: UserReference | null + currentUserId: string | null users: { [userId: string]: { name: string @@ -589,6 +589,8 @@ interface SidebarEvents { rerenderHighlights?: boolean } + setSidebarVisible: null + bookmarkPage: null openSpacePickerInRibbon: null