From 879aac4f6b6c891419215729d317ce963fdc8429 Mon Sep 17 00:00:00 2001 From: Peng Xiao Date: Fri, 25 Oct 2024 12:43:28 +0800 Subject: [PATCH] feat(blocks): allow peek view to be closed by the caller --- .../affine/reference-link/index.tsx | 6 +- .../specs/custom/spec-patchers.tsx | 27 ++- .../modules/peek-view/entities/peek-view.ts | 174 +++++++++++------- .../view/doc-preview/doc-peek-view.tsx | 2 +- .../peek-view/view/peek-view-manager.tsx | 8 +- 5 files changed, 135 insertions(+), 82 deletions(-) diff --git a/packages/frontend/core/src/components/affine/reference-link/index.tsx b/packages/frontend/core/src/components/affine/reference-link/index.tsx index 38bf78dd851cb..0eee0237fc33b 100644 --- a/packages/frontend/core/src/components/affine/reference-link/index.tsx +++ b/packages/frontend/core/src/components/affine/reference-link/index.tsx @@ -83,7 +83,11 @@ export function AffinePageReference({ if (e.shiftKey && ref.current) { e.preventDefault(); e.stopPropagation(); - peekView.open(ref.current).catch(console.error); + peekView + .open({ + element: ref.current, + }) + .catch(console.error); } if (isInPeekView) { diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/spec-patchers.tsx b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/spec-patchers.tsx index 6bdce4f58843a..c0de4b417eed0 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/spec-patchers.tsx +++ b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/spec-patchers.tsx @@ -11,7 +11,6 @@ import type { EditorService } from '@affine/core/modules/editor'; import { EditorSettingService } from '@affine/core/modules/editor-settting'; import { resolveLinkToDoc } from '@affine/core/modules/navigation'; import type { PeekViewService } from '@affine/core/modules/peek-view'; -import type { ActivePeekView } from '@affine/core/modules/peek-view/entities/peek-view'; import { CreationQuickSearchSession, DocsQuickSearchSession, @@ -35,6 +34,8 @@ import type { AffineReference, DocMode, DocModeProvider, + PeekOptions, + PeekViewService as BSPeekViewService, QuickSearchResult, RootService, } from '@blocksuite/affine/blocks'; @@ -243,11 +244,27 @@ export function patchEmbedLinkedDocBlockConfig(framework: FrameworkProvider) { export function patchPeekViewService(service: PeekViewService) { return PeekViewExtension({ - peek: (target: ActivePeekView['target'], template?: TemplateResult) => { - logger.debug('center peek', target, template); - return service.peekView.open(target, template); + peek: ( + element: { + target: HTMLElement; + docId: string; + blockIds?: string[]; + template?: TemplateResult; + }, + options?: PeekOptions + ) => { + logger.debug('center peek', element); + const { template, target, ...props } = element; + return service.peekView.open( + { + element: target, + ...props, + }, + template, + options?.abortSignal + ); }, - }); + } satisfies BSPeekViewService); } export function patchDocModeService( diff --git a/packages/frontend/core/src/modules/peek-view/entities/peek-view.ts b/packages/frontend/core/src/modules/peek-view/entities/peek-view.ts index 6556b6c6a6766..0f17db7d5b31b 100644 --- a/packages/frontend/core/src/modules/peek-view/entities/peek-view.ts +++ b/packages/frontend/core/src/modules/peek-view/entities/peek-view.ts @@ -17,12 +17,17 @@ import { firstValueFrom, map, race } from 'rxjs'; import { resolveLinkToDoc } from '../../navigation'; import type { WorkbenchService } from '../../workbench'; -export type PeekViewTarget = +export type PeekViewElement = | HTMLElement | BlockComponent | AffineReference - | HTMLAnchorElement - | { docId: string; blockIds?: string[] }; + | HTMLAnchorElement; + +export interface PeekViewTarget { + element?: PeekViewElement; + docId?: string; + blockIds?: string[]; +} export interface DocPeekViewInfo { type: 'doc'; @@ -101,79 +106,83 @@ function resolvePeekInfoFromPeekTarget( }; } - if (peekTarget instanceof AffineReference) { - const referenceInfo = peekTarget.referenceInfo; - if (referenceInfo) { - const { pageId: docId } = referenceInfo; - const info: DocPeekViewInfo = { - type: 'doc', - docId, - }; - Object.assign(info, referenceInfo.params); - return info; - } - } else if ('model' in peekTarget) { - const blockModel = peekTarget.model; - if (isEmbedLinkedDocModel(blockModel)) { - const info: DocPeekViewInfo = { - type: 'doc', - docId: blockModel.pageId, - }; - Object.assign(info, blockModel.params); - return info; - } else if (isEmbedSyncedDocModel(blockModel)) { - return { - type: 'doc', - docId: blockModel.pageId, - }; - } else if (isSurfaceRefModel(blockModel)) { - const refModel = (peekTarget as SurfaceRefBlockComponent).referenceModel; - // refModel can be null if the reference is invalid - if (refModel) { - const docId = - 'doc' in refModel ? refModel.doc.id : refModel.surface.doc.id; - return { + const element = peekTarget.element; + + if (element) { + if (element instanceof AffineReference) { + const referenceInfo = element.referenceInfo; + if (referenceInfo) { + const { pageId: docId } = referenceInfo; + const info: DocPeekViewInfo = { type: 'doc', docId, - mode: 'edgeless', - xywh: refModel.xywh, }; + Object.assign(info, referenceInfo.params); + return info; } - } else if (isImageBlockModel(blockModel)) { - return { - type: 'image', - docId: blockModel.doc.id, - blockIds: [blockModel.id], - }; - } else if (isAIChatBlockModel(blockModel)) { - return { - type: 'ai-chat-block', - docId: blockModel.doc.id, - model: blockModel, - host: peekTarget.host, - }; - } - } else if (peekTarget instanceof HTMLAnchorElement) { - const maybeDoc = resolveLinkToDoc(peekTarget.href); - if (maybeDoc) { - const info: DocPeekViewInfo = { - type: 'doc', - docId: maybeDoc.docId, - }; - - if (maybeDoc.mode) { - info.mode = maybeDoc.mode; - } - if (maybeDoc.blockIds?.length) { - info.blockIds = maybeDoc.blockIds; - } - if (maybeDoc.elementIds?.length) { - info.elementIds = maybeDoc.elementIds; + } else if ('model' in element) { + const blockModel = element.model; + if (isEmbedLinkedDocModel(blockModel)) { + const info: DocPeekViewInfo = { + type: 'doc', + docId: blockModel.pageId, + }; + Object.assign(info, blockModel.params); + return info; + } else if (isEmbedSyncedDocModel(blockModel)) { + return { + type: 'doc', + docId: blockModel.pageId, + }; + } else if (isSurfaceRefModel(blockModel)) { + const refModel = (element as SurfaceRefBlockComponent).referenceModel; + // refModel can be null if the reference is invalid + if (refModel) { + const docId = + 'doc' in refModel ? refModel.doc.id : refModel.surface.doc.id; + return { + type: 'doc', + docId, + mode: 'edgeless', + xywh: refModel.xywh, + }; + } + } else if (isImageBlockModel(blockModel)) { + return { + type: 'image', + docId: blockModel.doc.id, + blockIds: [blockModel.id], + }; + } else if (isAIChatBlockModel(blockModel)) { + return { + type: 'ai-chat-block', + docId: blockModel.doc.id, + model: blockModel, + host: element.host, + }; } + } else if (element instanceof HTMLAnchorElement) { + const maybeDoc = resolveLinkToDoc(element.href); + if (maybeDoc) { + const info: DocPeekViewInfo = { + type: 'doc', + docId: maybeDoc.docId, + }; - return info; + if (maybeDoc.mode) { + info.mode = maybeDoc.mode; + } + if (maybeDoc.blockIds?.length) { + info.blockIds = maybeDoc.blockIds; + } + if (maybeDoc.elementIds?.length) { + info.elementIds = maybeDoc.elementIds; + } + + return info; + } } - } else if ('docId' in peekTarget) { + } else if ('docId' in peekTarget && peekTarget.docId) { return { type: 'doc', docId: peekTarget.docId, @@ -208,7 +217,8 @@ export class PeekViewEntity extends Entity { // return true if the peek view will be handled open = async ( target: ActivePeekView['target'], - template?: TemplateResult + template?: TemplateResult, + abortSignal?: AbortSignal ) => { const resolvedInfo = resolvePeekInfoFromPeekTarget(target, template); if (!resolvedInfo) { @@ -220,7 +230,11 @@ export class PeekViewEntity extends Entity { // if there is an active peek view and it is a doc peek view, we will navigate it first if (active?.info.type === 'doc' && this.show$.value?.value) { // TODO(@pengx17): scroll to the viewing position? - this.workbenchService.workbench.openDoc(active.info.docId); + this.workbenchService.workbench.openDoc({ + docId: active.info.docId, + blockIds: active.info.blockIds, + elementIds: active.info.elementIds, + }); } this._active$.next({ target, info: resolvedInfo }); @@ -231,6 +245,24 @@ export class PeekViewEntity extends Entity { ? 'zoom' : 'fade', }); + + if (abortSignal) { + const abortListener = () => { + if (this.active$.value?.target === target) { + this.close(); + } + }; + + abortSignal.addEventListener('abort', abortListener); + + const showSubscription = this.show$.subscribe(v => { + if (!v && !abortSignal.aborted) { + abortSignal.removeEventListener('abort', abortListener); + showSubscription.unsubscribe(); + } + }); + } + return firstValueFrom(race(this._active$, this.show$).pipe(map(() => {}))); }; diff --git a/packages/frontend/core/src/modules/peek-view/view/doc-preview/doc-peek-view.tsx b/packages/frontend/core/src/modules/peek-view/view/doc-preview/doc-peek-view.tsx index 801d6360dbe75..1c921370a7b48 100644 --- a/packages/frontend/core/src/modules/peek-view/view/doc-preview/doc-peek-view.tsx +++ b/packages/frontend/core/src/modules/peek-view/view/doc-preview/doc-peek-view.tsx @@ -96,10 +96,10 @@ function DocPeekPreviewEditor({ if (!refNodeSlots) return; // doc change event inside peek view should be handled by peek view disposableGroup.add( + // todo(@pengx17): seems not working refNodeSlots.docLinkClicked.on(options => { peekView .open({ - type: 'doc', docId: options.pageId, ...options.params, }) diff --git a/packages/frontend/core/src/modules/peek-view/view/peek-view-manager.tsx b/packages/frontend/core/src/modules/peek-view/view/peek-view-manager.tsx index fd47d82a7c02d..5ee69feb7eaf4 100644 --- a/packages/frontend/core/src/modules/peek-view/view/peek-view-manager.tsx +++ b/packages/frontend/core/src/modules/peek-view/view/peek-view-manager.tsx @@ -86,8 +86,8 @@ const getRendererProps = ( children: preview, controls, target: - activePeekView?.target instanceof HTMLElement - ? activePeekView.target + activePeekView?.target.element instanceof HTMLElement + ? activePeekView.target.element : undefined, mode: getMode(activePeekView.info), dialogFrame: activePeekView.info.type !== 'image', @@ -108,8 +108,8 @@ export const PeekViewManagerModal = () => { useEffect(() => { const subscription = peekViewEntity.show$.subscribe(() => { - if (activePeekView?.target instanceof BlockComponent) { - activePeekView.target.requestUpdate(); + if (activePeekView?.target.element instanceof BlockComponent) { + activePeekView.target.element.requestUpdate(); } });