mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-19 16:35:15 +08:00
9742e9735e
#### PR Dependency Tree * **PR #14591** 👈 This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * New canvas renderer debug metrics and controls for runtime inspection. * Mindmap/group reordering now normalizes group targets, improving reorder consistency. * **Bug Fixes** * Fixed connector behavior for empty/degenerate paths. * More aggressive viewport invalidation so structural changes display correctly. * Improved z-index synchronization during transforms and layer updates. * **Performance** * Retained DOM caching for brushes, shapes, and connectors to reduce DOM churn. * Targeted canvas refreshes, pooling, and reuse to lower redraw and memory overhead. * **Tests** * Added canvas renderer performance benchmarks and curve edge-case unit tests. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
83 lines
2.1 KiB
TypeScript
83 lines
2.1 KiB
TypeScript
import type { DomRenderer } from '@blocksuite/affine-block-surface';
|
|
import type {
|
|
BrushElementModel,
|
|
HighlighterElementModel,
|
|
} from '@blocksuite/affine-model';
|
|
|
|
const SVG_NS = 'http://www.w3.org/2000/svg';
|
|
|
|
type BrushLikeModel = BrushElementModel | HighlighterElementModel;
|
|
|
|
type RetainedBrushDom = {
|
|
path: SVGPathElement;
|
|
svg: SVGSVGElement;
|
|
};
|
|
|
|
const retainedBrushDom = new WeakMap<HTMLElement, RetainedBrushDom>();
|
|
|
|
function clearBrushLikeDom(domElement: HTMLElement) {
|
|
retainedBrushDom.delete(domElement);
|
|
domElement.replaceChildren();
|
|
}
|
|
|
|
function getRetainedBrushDom(domElement: HTMLElement) {
|
|
const existing = retainedBrushDom.get(domElement);
|
|
|
|
if (existing) {
|
|
return existing;
|
|
}
|
|
|
|
const svg = document.createElementNS(SVG_NS, 'svg');
|
|
svg.style.position = 'absolute';
|
|
svg.style.left = '0';
|
|
svg.style.top = '0';
|
|
svg.style.overflow = 'visible';
|
|
svg.style.pointerEvents = 'none';
|
|
|
|
const path = document.createElementNS(SVG_NS, 'path');
|
|
path.setAttribute('stroke', 'none');
|
|
svg.append(path);
|
|
|
|
const retained = { svg, path };
|
|
retainedBrushDom.set(domElement, retained);
|
|
domElement.replaceChildren(svg);
|
|
|
|
return retained;
|
|
}
|
|
|
|
export function renderBrushLikeDom({
|
|
color,
|
|
domElement,
|
|
model,
|
|
renderer,
|
|
}: {
|
|
color: string;
|
|
domElement: HTMLElement;
|
|
model: BrushLikeModel;
|
|
renderer: DomRenderer;
|
|
}) {
|
|
const { zoom } = renderer.viewport;
|
|
const [, , w, h] = model.deserializedXYWH;
|
|
|
|
if (w <= 0 || h <= 0 || !model.commands) {
|
|
clearBrushLikeDom(domElement);
|
|
return;
|
|
}
|
|
|
|
const { path, svg } = getRetainedBrushDom(domElement);
|
|
|
|
svg.style.width = `${w * zoom}px`;
|
|
svg.style.height = `${h * zoom}px`;
|
|
svg.style.transform = model.rotate === 0 ? '' : `rotate(${model.rotate}deg)`;
|
|
svg.style.transformOrigin = model.rotate === 0 ? '' : 'center';
|
|
svg.setAttribute('viewBox', `0 0 ${w} ${h}`);
|
|
|
|
path.setAttribute('d', model.commands);
|
|
path.setAttribute('fill', color);
|
|
|
|
domElement.style.width = `${w * zoom}px`;
|
|
domElement.style.height = `${h * zoom}px`;
|
|
domElement.style.overflow = 'visible';
|
|
domElement.style.pointerEvents = 'none';
|
|
}
|