mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-13 16:16:46 +08:00
feat: edgeless dnd preview (#10117)
This commit is contained in:
@@ -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())
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user