From 270d1754a3939c8bc6dff72ebf69c7156c82c25c Mon Sep 17 00:00:00 2001 From: doouding Date: Wed, 12 Feb 2025 12:37:07 +0000 Subject: [PATCH] feat: edgeless dnd preview (#10117) --- .../block-note/src/note-edgeless-block.ts | 2 +- .../src/helpers/preview-helper.ts | 99 +++++++++++++++++-- .../affine/widget-drag-handle/src/utils.ts | 37 ++++++- .../src/watchers/drag-event-watcher.ts | 52 +++------- .../edgeless/edgeless-root-preview-block.ts | 6 +- 5 files changed, 145 insertions(+), 51 deletions(-) diff --git a/blocksuite/affine/block-note/src/note-edgeless-block.ts b/blocksuite/affine/block-note/src/note-edgeless-block.ts index 47d8b41b6..1f86633aa 100644 --- a/blocksuite/affine/block-note/src/note-edgeless-block.ts +++ b/blocksuite/affine/block-note/src/note-edgeless-block.ts @@ -184,7 +184,7 @@ export class EdgelessNoteBlockComponent extends toGfxBlockComponent( x: bound.x, y: bound.y, w: width, - h: collapse ? height : 'inherit', + h: collapse ? height : 'unset', zIndex: this.toZIndex(), }; } diff --git a/blocksuite/affine/widget-drag-handle/src/helpers/preview-helper.ts b/blocksuite/affine/widget-drag-handle/src/helpers/preview-helper.ts index 50c30b33b..5c776f815 100644 --- a/blocksuite/affine/widget-drag-handle/src/helpers/preview-helper.ts +++ b/blocksuite/affine/widget-drag-handle/src/helpers/preview-helper.ts @@ -5,11 +5,17 @@ import { EditorSettingProvider, } from '@blocksuite/affine-shared/services'; import { SpecProvider } from '@blocksuite/affine-shared/utils'; -import { BlockStdScope } from '@blocksuite/block-std'; -import { type BlockViewType, type Query } from '@blocksuite/store'; +import { BlockStdScope, LifeCycleWatcher } from '@blocksuite/block-std'; +import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx'; +import { + type BlockViewType, + type Query, + type SliceSnapshot, +} from '@blocksuite/store'; import { signal } from '@preact/signals-core'; import type { AffineDragHandleWidget } from '../drag-handle.js'; +import { getSnapshotRect } from '../utils.js'; export class PreviewHelper { private readonly _calculateQuery = (selectedIds: string[]): Query => { @@ -47,19 +53,56 @@ export class PreviewHelper { }; }; - renderDragPreview = (blockIds: string[], container: HTMLElement): void => { + getPreviewStd = ( + blockIds: string[], + snapshot: SliceSnapshot, + mode: 'edgeless' | 'page' + ) => { const widget = this.widget; const std = widget.std; + const sourceGfx = std.get(GfxControllerIdentifier); const docModeService = std.get(DocModeProvider); const editorSetting = std.get(EditorSettingProvider).peek(); const query = this._calculateQuery(blockIds as string[]); const store = widget.doc.doc.getStore({ query }); - const previewSpec = SpecProvider.getInstance().getSpec('page:preview'); + const previewSpec = SpecProvider.getInstance().getSpec( + mode === 'edgeless' ? 'edgeless:preview' : 'page:preview' + ); const settingSignal = signal({ ...editorSetting }); - previewSpec.extend([ + const extensions = [ DocModeExtension(docModeService), EditorSettingExtension(settingSignal), - ]); + ]; + + if (mode === 'edgeless') { + if (!blockIds.includes(sourceGfx.surface!.id)) { + blockIds.push(sourceGfx.surface!.id); + } + class PreviewViewportInitializer extends LifeCycleWatcher { + static override key = 'preview-viewport-initializer'; + + override mounted(): void { + const rect = getSnapshotRect(snapshot); + if (!rect) { + return; + } + + this.std.view.viewUpdated.on(payload => { + if (payload.view.model.flavour === 'affine:page') { + const gfx = this.std.get(GfxControllerIdentifier); + + queueMicrotask(() => { + gfx.viewport.setViewportByBound(rect); + }); + } + }); + } + } + + extensions.push(PreviewViewportInitializer); + } + + previewSpec.extend(extensions); settingSignal.value = { ...settingSignal.value, @@ -70,10 +113,48 @@ export class PreviewHelper { store, extensions: previewSpec.value, }); - const previewTemplate = previewStd.render(); - const noteBlock = this.widget.host.querySelector('affine-note'); - container.style.width = `${noteBlock?.offsetWidth ?? noteBlock?.clientWidth ?? 500}px`; + let width: number = 500; + let height; + + if (mode === 'page') { + const noteBlock = this.widget.host.querySelector('affine-note'); + width = noteBlock?.offsetWidth ?? noteBlock?.clientWidth ?? 500; + } else { + const rect = getSnapshotRect(snapshot); + if (rect) { + width = rect.w; + height = rect.h; + } else { + height = 500; + } + } + + return { + previewStd, + width, + height, + }; + }; + + renderDragPreview = (options: { + blockIds: string[]; + snapshot: SliceSnapshot; + container: HTMLElement; + mode: 'page' | 'edgeless'; + }): void => { + const { blockIds, snapshot, container, mode } = options; + const { previewStd, width, height } = this.getPreviewStd( + blockIds, + snapshot, + mode + ); + const previewTemplate = previewStd.render(); + + container.style.width = `${width}px`; + if (height) { + container.style.height = `${height}px`; + } container.append(previewTemplate); }; diff --git a/blocksuite/affine/widget-drag-handle/src/utils.ts b/blocksuite/affine/widget-drag-handle/src/utils.ts index 4b3a45c08..92fac620f 100644 --- a/blocksuite/affine/widget-drag-handle/src/utils.ts +++ b/blocksuite/affine/widget-drag-handle/src/utils.ts @@ -18,7 +18,12 @@ import { matchModels, } from '@blocksuite/affine-shared/utils'; import type { BlockComponent, EditorHost } from '@blocksuite/block-std'; -import { Point, Rect } from '@blocksuite/global/utils'; +import { + Bound, + Point, + Rect, + type SerializedXYWH, +} from '@blocksuite/global/utils'; import type { BaseSelection, BlockModel, @@ -277,3 +282,33 @@ function getHoveringNote(point: Point) { ) || null ); } + +export function getSnapshotRect(snapshot: SliceSnapshot): Bound | null { + let bound: Bound | null = null; + + const getBound = (block: BlockSnapshot) => { + if (block.flavour === 'affine:surface') { + if (block.props.elements) { + Object.values( + block.props.elements as Record + ).forEach(elem => { + if (elem.xywh) { + bound = bound + ? bound.unite(Bound.deserialize(elem.xywh)) + : Bound.deserialize(elem.xywh); + } + }); + } + + block.children.forEach(getBound); + } else if (block.props.xywh) { + bound = bound + ? bound.unite(Bound.deserialize(block.props.xywh as SerializedXYWH)) + : Bound.deserialize(block.props.xywh as SerializedXYWH); + } + }; + + snapshot.content.forEach(getBound); + + return bound; +} diff --git a/blocksuite/affine/widget-drag-handle/src/watchers/drag-event-watcher.ts b/blocksuite/affine/widget-drag-handle/src/watchers/drag-event-watcher.ts index 72ca8b2c7..0771feda7 100644 --- a/blocksuite/affine/widget-drag-handle/src/watchers/drag-event-watcher.ts +++ b/blocksuite/affine/widget-drag-handle/src/watchers/drag-event-watcher.ts @@ -70,6 +70,7 @@ import { containBlock, extractIdsFromSnapshot, getParentNoteBlock, + getSnapshotRect, includeTextSelection, isOutOfNoteBlock, } from '../utils.js'; @@ -878,38 +879,6 @@ export class DragEventWatcher { return idRemap; }; - private readonly _getSnapshotRect = ( - snapshot: SliceSnapshot - ): Bound | null => { - let bound: Bound | null = null; - - const getBound = (block: BlockSnapshot) => { - if (block.flavour === 'affine:surface') { - if (block.props.elements) { - Object.values( - block.props.elements as Record - ).forEach(elem => { - if (elem.xywh) { - bound = bound - ? bound.unite(Bound.deserialize(elem.xywh)) - : Bound.deserialize(elem.xywh); - } - }); - } - - block.children.forEach(getBound); - } else if (block.props.xywh) { - bound = bound - ? bound.unite(Bound.deserialize(block.props.xywh as SerializedXYWH)) - : Bound.deserialize(block.props.xywh as SerializedXYWH); - } - }; - - snapshot.content.forEach(getBound); - - return bound; - }; - /** * Rewrite the xywh of the snapshot to make the top left corner of the snapshot align with the point * @param snapshot @@ -921,7 +890,7 @@ export class DragEventWatcher { point: Point, ignoreOriginalPos: boolean = false ) => { - const rect = this._getSnapshotRect(snapshot); + const rect = getSnapshotRect(snapshot); if (!rect) return; const { x: modelX, y: modelY } = point; @@ -1204,14 +1173,21 @@ export class DragEventWatcher { this._cleanup(); }, setDragPreview: ({ source, container }) => { - if (!source.data?.bsEntity?.modelIds.length) { + if ( + !source.data?.bsEntity?.modelIds.length || + !source.data.bsEntity.snapshot + ) { return; } - this.previewHelper.renderDragPreview( - source.data?.bsEntity?.modelIds, - container - ); + const { snapshot } = source.data.bsEntity; + + this.previewHelper.renderDragPreview({ + blockIds: source.data?.bsEntity?.modelIds, + snapshot, + container, + mode: this.mode ?? 'page', + }); }, setDragData: () => { const { fromMode, snapshot } = this._getDraggedSnapshot(); diff --git a/blocksuite/blocks/src/root-block/edgeless/edgeless-root-preview-block.ts b/blocksuite/blocks/src/root-block/edgeless/edgeless-root-preview-block.ts index f8a43ad6f..6c2db0772 100644 --- a/blocksuite/blocks/src/root-block/edgeless/edgeless-root-preview-block.ts +++ b/blocksuite/blocks/src/root-block/edgeless/edgeless-root-preview-block.ts @@ -110,7 +110,7 @@ export class EdgelessRootPreviewBlockComponent this.std .get(FontLoaderService) .ready.then(() => { - this.surface.refresh(); + this.surface?.refresh(); }) .catch(console.error); } @@ -173,7 +173,9 @@ export class EdgelessRootPreviewBlockComponent private _initSlotEffects() { this.disposables.add( - this.std.get(ThemeProvider).theme$.subscribe(() => this.surface.refresh()) + this.std + .get(ThemeProvider) + .theme$.subscribe(() => this.surface?.refresh()) ); }