Files
AFFiNE-Mirror/blocksuite/affine/widget-drag-handle/src/helpers/preview-helper.ts
2024-12-29 06:51:48 +00:00

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) {}
}