feat: edgeless dnd preview (#10117)

This commit is contained in:
doouding
2025-02-12 12:37:07 +00:00
parent f89fcf82f8
commit 270d1754a3
5 changed files with 145 additions and 51 deletions
@@ -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(),
};
}
@@ -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);
};
@@ -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<string, { xywh: SerializedXYWH }>
).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;
}
@@ -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<string, { xywh: SerializedXYWH }>
).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();
@@ -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())
);
}