mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-12 15:46:29 +08:00
fix: avoid unnecessary rerendering of selected-rect (#12583)
### Changed - Note scale issue - Overlay should call refresh when `clear` is called - Optimize edgeless-selected-rect to avoid unecessary rerendering <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Edgeless note blocks now respect both minimum and maximum size limits when resizing. - **Improvements** - Enhanced performance and responsiveness of resize and rotate handles in selection overlays by caching allowed handles and optimizing cursor management. - Cursor styles for resize and rotate handles are now set more reliably and efficiently through declarative styling. - **Bug Fixes** - Ensured overlay clearing now properly refreshes the renderer for more consistent visual updates. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -400,7 +400,7 @@ export const EdgelessNoteInteraction =
|
||||
|
||||
onResizeMove(context): void {
|
||||
const { originalBound, newBound, lockRatio, constraint } = context;
|
||||
const { minWidth, minHeight } = constraint;
|
||||
const { minWidth, minHeight, maxHeight, maxWidth } = constraint;
|
||||
|
||||
let scale = initialScale;
|
||||
let edgelessProp = { ...model.props.edgeless };
|
||||
@@ -411,8 +411,8 @@ export const EdgelessNoteInteraction =
|
||||
edgelessProp.scale = scale;
|
||||
}
|
||||
|
||||
newBound.w = clamp(newBound.w, minWidth, Number.MAX_SAFE_INTEGER);
|
||||
newBound.h = clamp(newBound.h, minHeight, Number.MAX_SAFE_INTEGER);
|
||||
newBound.w = clamp(newBound.w, minWidth * scale, maxWidth);
|
||||
newBound.h = clamp(newBound.h, minHeight * scale, maxHeight);
|
||||
|
||||
if (newBound.h > minHeight * scale) {
|
||||
edgelessProp.collapse = true;
|
||||
|
||||
@@ -35,7 +35,9 @@ export abstract class Overlay extends Extension {
|
||||
]);
|
||||
}
|
||||
|
||||
clear() {}
|
||||
clear() {
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
dispose() {}
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ import { state } from 'lit/decorators.js';
|
||||
import { ifDefined } from 'lit/directives/if-defined.js';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
import { type Subscription } from 'rxjs';
|
||||
|
||||
import { RenderResizeHandles } from './resize-handles.js';
|
||||
import { generateCursorUrl, getRotatedResizeCursor } from './utils.js';
|
||||
@@ -359,21 +358,6 @@ export class EdgelessSelectedRectWidget extends WidgetComponent<RootBlockModel>
|
||||
}
|
||||
`;
|
||||
|
||||
private readonly _initSelectedSlot = () => {
|
||||
this._propDisposables.forEach(disposable => disposable.unsubscribe());
|
||||
this._propDisposables = [];
|
||||
|
||||
this.selection.selectedElements.forEach(element => {
|
||||
if ('flavour' in element) {
|
||||
this._propDisposables.push(
|
||||
element.propsUpdated.subscribe(() => {
|
||||
this._updateOnElementChange(element.id);
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
private readonly _dragEndCleanup = () => {
|
||||
this._isWidthLimit = false;
|
||||
this._isHeightLimit = false;
|
||||
@@ -386,16 +370,15 @@ export class EdgelessSelectedRectWidget extends WidgetComponent<RootBlockModel>
|
||||
this.frameOverlay.clear();
|
||||
};
|
||||
|
||||
private _propDisposables: Subscription[] = [];
|
||||
|
||||
private readonly _updateCursor = (options?: {
|
||||
type: 'resize' | 'rotate';
|
||||
angle: number;
|
||||
handle: ResizeHandle;
|
||||
pure?: boolean;
|
||||
}) => {
|
||||
if (!options) {
|
||||
!this._isResizing && (this.gfx.cursor$.value = 'default');
|
||||
return;
|
||||
return 'default';
|
||||
}
|
||||
|
||||
const { type, angle, handle } = options;
|
||||
@@ -410,7 +393,11 @@ export class EdgelessSelectedRectWidget extends WidgetComponent<RootBlockModel>
|
||||
});
|
||||
}
|
||||
|
||||
this.gfx.cursor$.value = cursor;
|
||||
if (options.pure !== true) {
|
||||
this.gfx.cursor$.value = cursor;
|
||||
}
|
||||
|
||||
return cursor;
|
||||
};
|
||||
|
||||
private readonly _updateOnElementChange = (
|
||||
@@ -423,8 +410,30 @@ export class EdgelessSelectedRectWidget extends WidgetComponent<RootBlockModel>
|
||||
}
|
||||
};
|
||||
|
||||
private readonly _updateHandles = () => {
|
||||
const interaction = this._interaction;
|
||||
const { store, selection } = this;
|
||||
const elements = selection.selectedElements;
|
||||
|
||||
if (interaction && !selection.editing && !store.readonly) {
|
||||
const resizeHandles = interaction.getResizeHandlers({
|
||||
elements,
|
||||
});
|
||||
const { rotatable } = interaction.getRotateConfig({
|
||||
elements,
|
||||
});
|
||||
|
||||
this._allowedHandles = {
|
||||
rotatable,
|
||||
resizeHandles,
|
||||
};
|
||||
} else {
|
||||
this._allowedHandles = null;
|
||||
}
|
||||
};
|
||||
|
||||
private readonly _updateOnSelectionChange = () => {
|
||||
this._initSelectedSlot();
|
||||
this._updateHandles();
|
||||
this._updateSelectedRect();
|
||||
// Reset the cursor
|
||||
this._updateCursor();
|
||||
@@ -554,10 +563,6 @@ export class EdgelessSelectedRectWidget extends WidgetComponent<RootBlockModel>
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
_disposables.add(() => {
|
||||
this._propDisposables.forEach(disposable => disposable.unsubscribe());
|
||||
});
|
||||
}
|
||||
|
||||
private get _interaction() {
|
||||
@@ -569,7 +574,7 @@ export class EdgelessSelectedRectWidget extends WidgetComponent<RootBlockModel>
|
||||
}
|
||||
|
||||
private _renderHandles() {
|
||||
const { selection, gfx, block, store } = this;
|
||||
const { selection, gfx, block } = this;
|
||||
const elements = selection.selectedElements;
|
||||
|
||||
if (selection.inoperable) {
|
||||
@@ -579,23 +584,16 @@ export class EdgelessSelectedRectWidget extends WidgetComponent<RootBlockModel>
|
||||
const handles = [];
|
||||
|
||||
if (
|
||||
this._allowedHandles &&
|
||||
this._interaction &&
|
||||
!selection.editing &&
|
||||
!store.readonly &&
|
||||
!elements.some(element => element.isLocked())
|
||||
) {
|
||||
const interaction = this._interaction;
|
||||
const resizeHandlers = interaction.getResizeHandlers({
|
||||
elements,
|
||||
});
|
||||
const { rotatable } = interaction.getRotateConfig({
|
||||
elements,
|
||||
});
|
||||
|
||||
handles.push(
|
||||
RenderResizeHandles(
|
||||
resizeHandlers,
|
||||
rotatable,
|
||||
this._allowedHandles.resizeHandles,
|
||||
this._allowedHandles.rotatable,
|
||||
(e: PointerEvent, handle: ResizeHandle) => {
|
||||
const isRotate = (e.target as HTMLElement).classList.contains(
|
||||
'rotate'
|
||||
@@ -603,7 +601,7 @@ export class EdgelessSelectedRectWidget extends WidgetComponent<RootBlockModel>
|
||||
|
||||
if (isRotate) {
|
||||
interaction.handleElementRotate({
|
||||
elements: this.selection.selectedElements,
|
||||
elements,
|
||||
event: e,
|
||||
onRotateStart: () => {
|
||||
this._mode = 'rotate';
|
||||
@@ -622,7 +620,7 @@ export class EdgelessSelectedRectWidget extends WidgetComponent<RootBlockModel>
|
||||
});
|
||||
} else {
|
||||
interaction.handleElementResize({
|
||||
elements: this.selection.selectedElements,
|
||||
elements,
|
||||
handle,
|
||||
event: e,
|
||||
onResizeStart: () => {
|
||||
@@ -632,12 +630,19 @@ export class EdgelessSelectedRectWidget extends WidgetComponent<RootBlockModel>
|
||||
if (lockRatio) {
|
||||
this._scaleDirection = handle;
|
||||
this._scalePercent = `${Math.round(scaleX * 100)}%`;
|
||||
this._mode = 'scale';
|
||||
}
|
||||
|
||||
if (exceed) {
|
||||
this._isWidthLimit = exceed.w;
|
||||
this._isHeightLimit = exceed.h;
|
||||
}
|
||||
|
||||
this._updateCursor({
|
||||
type: 'resize',
|
||||
angle: elements.length > 1 ? 0 : (elements[0]?.rotate ?? 0),
|
||||
handle,
|
||||
});
|
||||
},
|
||||
onResizeEnd: () => {
|
||||
this._mode = 'resize';
|
||||
@@ -647,14 +652,11 @@ export class EdgelessSelectedRectWidget extends WidgetComponent<RootBlockModel>
|
||||
}
|
||||
},
|
||||
option => {
|
||||
if (option) {
|
||||
this._updateCursor({
|
||||
...option,
|
||||
angle: elements.length > 1 ? 0 : (elements[0]?.rotate ?? 0),
|
||||
});
|
||||
} else {
|
||||
this._updateCursor();
|
||||
}
|
||||
return this._updateCursor({
|
||||
...option,
|
||||
angle: elements.length > 1 ? 0 : (elements[0]?.rotate ?? 0),
|
||||
pure: true,
|
||||
});
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -784,6 +786,12 @@ export class EdgelessSelectedRectWidget extends WidgetComponent<RootBlockModel>
|
||||
`;
|
||||
}
|
||||
|
||||
@state()
|
||||
private accessor _allowedHandles: {
|
||||
rotatable: boolean;
|
||||
resizeHandles: ResizeHandle[];
|
||||
} | null = null;
|
||||
|
||||
@state()
|
||||
private accessor _isHeightLimit = false;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { ResizeHandle } from '@blocksuite/std/gfx';
|
||||
import { html, nothing } from 'lit';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
|
||||
export enum HandleDirection {
|
||||
Bottom = 'bottom',
|
||||
@@ -17,36 +18,28 @@ function ResizeHandleRenderer(
|
||||
handle: ResizeHandle,
|
||||
rotatable: boolean,
|
||||
onPointerDown?: (e: PointerEvent, direction: ResizeHandle) => void,
|
||||
updateCursor?: (options?: {
|
||||
getCursor?: (options: {
|
||||
type: 'resize' | 'rotate';
|
||||
handle: ResizeHandle;
|
||||
}) => void
|
||||
}) => string
|
||||
) {
|
||||
const handlerPointerDown = (e: PointerEvent) => {
|
||||
e.stopPropagation();
|
||||
onPointerDown && onPointerDown(e, handle);
|
||||
};
|
||||
|
||||
const pointerEnter = (type: 'resize' | 'rotate') => (e: PointerEvent) => {
|
||||
e.stopPropagation();
|
||||
if (e.buttons === 1 || !updateCursor) return;
|
||||
|
||||
updateCursor({ type, handle });
|
||||
};
|
||||
|
||||
const pointerLeave = (e: PointerEvent) => {
|
||||
e.stopPropagation();
|
||||
if (e.buttons === 1 || !updateCursor) return;
|
||||
|
||||
updateCursor();
|
||||
};
|
||||
|
||||
const rotationTpl =
|
||||
handle.length > 6 && rotatable
|
||||
? html`<div
|
||||
class="rotate"
|
||||
@pointerover=${pointerEnter('rotate')}
|
||||
@pointerout=${pointerLeave}
|
||||
style=${styleMap({
|
||||
cursor: getCursor
|
||||
? getCursor({
|
||||
type: 'rotate',
|
||||
handle,
|
||||
})
|
||||
: 'default',
|
||||
})}
|
||||
></div>`
|
||||
: nothing;
|
||||
|
||||
@@ -58,8 +51,14 @@ function ResizeHandleRenderer(
|
||||
${rotationTpl}
|
||||
<div
|
||||
class="resize transparent-handle"
|
||||
@pointerover=${pointerEnter('resize')}
|
||||
@pointerout=${pointerLeave}
|
||||
style=${styleMap({
|
||||
cursor: getCursor
|
||||
? getCursor({
|
||||
type: 'resize',
|
||||
handle,
|
||||
})
|
||||
: 'default',
|
||||
})}
|
||||
></div>
|
||||
</div>`;
|
||||
}
|
||||
@@ -79,17 +78,17 @@ export function RenderResizeHandles(
|
||||
resizeHandles: ResizeHandle[],
|
||||
rotatable: boolean,
|
||||
onPointerDown: (e: PointerEvent, direction: ResizeHandle) => void,
|
||||
updateCursor?: (options?: {
|
||||
getCursor?: (options: {
|
||||
type: 'resize' | 'rotate';
|
||||
handle: ResizeHandle;
|
||||
}) => void
|
||||
}) => string
|
||||
) {
|
||||
return html`
|
||||
${repeat(
|
||||
resizeHandles,
|
||||
handle => handle,
|
||||
handle =>
|
||||
ResizeHandleRenderer(handle, rotatable, onPointerDown, updateCursor)
|
||||
ResizeHandleRenderer(handle, rotatable, onPointerDown, getCursor)
|
||||
)}
|
||||
`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user