mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-24 22:09:08 +08:00
163 lines
4.5 KiB
TypeScript
163 lines
4.5 KiB
TypeScript
import {
|
|
DocModeExtension,
|
|
DocModeProvider,
|
|
EditorSettingExtension,
|
|
EditorSettingProvider,
|
|
} from '@blocksuite/affine-shared/services';
|
|
import { SpecProvider } from '@blocksuite/affine-shared/utils';
|
|
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 => {
|
|
const ids: Array<{ id: string; viewType: BlockViewType }> = selectedIds.map(
|
|
id => ({
|
|
id,
|
|
viewType: '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: 'bypass', id: parent });
|
|
}
|
|
parent = this.widget.doc.getParent(parent)?.id ?? null;
|
|
} 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: 'display', id: child.id });
|
|
addChildren(child.id);
|
|
});
|
|
};
|
|
selectedIds.forEach(addChildren);
|
|
|
|
return {
|
|
match: ids,
|
|
mode: 'strict',
|
|
};
|
|
};
|
|
|
|
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(
|
|
mode === 'edgeless' ? 'edgeless:preview' : 'page:preview'
|
|
);
|
|
const settingSignal = signal({ ...editorSetting });
|
|
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,
|
|
edgelessDisableScheduleUpdate: true,
|
|
};
|
|
|
|
const previewStd = new BlockStdScope({
|
|
store,
|
|
extensions: previewSpec.value,
|
|
});
|
|
|
|
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);
|
|
};
|
|
|
|
constructor(readonly widget: AffineDragHandleWidget) {}
|
|
}
|