mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-18 14:56:59 +08:00
119 lines
3.7 KiB
TypeScript
119 lines
3.7 KiB
TypeScript
import { SpecProvider } from '@blocksuite/affine-shared/utils';
|
|
import {
|
|
type BlockComponent,
|
|
BlockStdScope,
|
|
type DndEventState,
|
|
} from '@blocksuite/block-std';
|
|
import { Point } from '@blocksuite/global/utils';
|
|
import { BlockViewType, type Query } from '@blocksuite/store';
|
|
|
|
import { DragPreview } from '../components/drag-preview.js';
|
|
import type { AffineDragHandleWidget } from '../drag-handle.js';
|
|
|
|
export class PreviewHelper {
|
|
private readonly _calculatePreviewOffset = (
|
|
blocks: BlockComponent[],
|
|
state: DndEventState
|
|
) => {
|
|
const { top, left } = blocks[0].getBoundingClientRect();
|
|
const previewOffset = new Point(state.raw.x - left, state.raw.y - top);
|
|
return previewOffset;
|
|
};
|
|
|
|
private readonly _calculateQuery = (selectedIds: string[]): Query => {
|
|
const ids: Array<{ id: string; viewType: BlockViewType }> = selectedIds.map(
|
|
id => ({
|
|
id,
|
|
viewType: BlockViewType.Display,
|
|
})
|
|
);
|
|
|
|
// The ancestors of the selected blocks should be rendered as Bypass
|
|
selectedIds.forEach(block => {
|
|
let parent: string | null = block;
|
|
do {
|
|
if (!selectedIds.includes(parent)) {
|
|
ids.push({ viewType: BlockViewType.Bypass, id: parent });
|
|
}
|
|
parent = this.widget.doc.blockCollection.crud.getParent(parent);
|
|
} while (parent && !ids.map(({ id }) => id).includes(parent));
|
|
});
|
|
|
|
// The children of the selected blocks should be rendered as Display
|
|
const addChildren = (id: string) => {
|
|
const children = this.widget.doc.getBlock(id)?.model.children ?? [];
|
|
children.forEach(child => {
|
|
ids.push({ viewType: BlockViewType.Display, id: child.id });
|
|
addChildren(child.id);
|
|
});
|
|
};
|
|
selectedIds.forEach(addChildren);
|
|
|
|
return {
|
|
match: ids,
|
|
mode: 'strict',
|
|
};
|
|
};
|
|
|
|
createDragPreview = (
|
|
blocks: BlockComponent[],
|
|
state: DndEventState,
|
|
dragPreviewEl?: HTMLElement,
|
|
dragPreviewOffset?: Point
|
|
): DragPreview => {
|
|
if (this.widget.dragPreview) {
|
|
this.widget.dragPreview.remove();
|
|
}
|
|
|
|
let dragPreview: DragPreview;
|
|
if (dragPreviewEl) {
|
|
dragPreview = new DragPreview(dragPreviewOffset);
|
|
dragPreview.append(dragPreviewEl);
|
|
} else {
|
|
let width = 0;
|
|
blocks.forEach(element => {
|
|
width = Math.max(width, element.getBoundingClientRect().width);
|
|
});
|
|
|
|
const selectedIds = blocks.map(block => block.model.id);
|
|
|
|
const query = this._calculateQuery(selectedIds);
|
|
|
|
const doc = this.widget.doc.blockCollection.getDoc({ query });
|
|
|
|
const previewSpec = SpecProvider.getInstance().getSpec('page:preview');
|
|
const previewStd = new BlockStdScope({
|
|
doc,
|
|
extensions: previewSpec.value,
|
|
});
|
|
const previewTemplate = previewStd.render();
|
|
|
|
const offset = this._calculatePreviewOffset(blocks, state);
|
|
const posX = state.raw.x - offset.x;
|
|
const posY = state.raw.y - offset.y;
|
|
const altKey = state.raw.altKey;
|
|
|
|
dragPreview = new DragPreview(offset);
|
|
dragPreview.template = previewTemplate;
|
|
dragPreview.onRemove = () => {
|
|
this.widget.doc.blockCollection.clearQuery(query);
|
|
};
|
|
dragPreview.style.width = `${width / this.widget.scaleInNote.peek()}px`;
|
|
dragPreview.style.transform = `translate(${posX}px, ${posY}px) scale(${this.widget.scaleInNote.peek()})`;
|
|
|
|
dragPreview.style.opacity = altKey ? '1' : '0.5';
|
|
}
|
|
this.widget.rootComponent.append(dragPreview);
|
|
return dragPreview;
|
|
};
|
|
|
|
removeDragPreview = () => {
|
|
if (this.widget.dragPreview) {
|
|
this.widget.dragPreview.remove();
|
|
this.widget.dragPreview = null;
|
|
}
|
|
};
|
|
|
|
constructor(readonly widget: AffineDragHandleWidget) {}
|
|
}
|