|
|
|
|
@@ -1,27 +1,27 @@
|
|
|
|
|
import { isFrameBlock } from '@blocksuite/affine-block-frame';
|
|
|
|
|
import {
|
|
|
|
|
GroupElementModel,
|
|
|
|
|
MindmapElementModel,
|
|
|
|
|
NoteBlockModel,
|
|
|
|
|
NoteDisplayMode,
|
|
|
|
|
} from '@blocksuite/affine-model';
|
|
|
|
|
import { resetNativeSelection } from '@blocksuite/affine-shared/utils';
|
|
|
|
|
import { DisposableGroup } from '@blocksuite/global/disposable';
|
|
|
|
|
import type { IVec } from '@blocksuite/global/gfx';
|
|
|
|
|
import { Bound } from '@blocksuite/global/gfx';
|
|
|
|
|
import type { PointerEventState } from '@blocksuite/std';
|
|
|
|
|
import {
|
|
|
|
|
BaseTool,
|
|
|
|
|
getTopElements,
|
|
|
|
|
type GfxModel,
|
|
|
|
|
InteractivityIdentifier,
|
|
|
|
|
isGfxGroupCompatibleModel,
|
|
|
|
|
type PointTestOptions,
|
|
|
|
|
} from '@blocksuite/std/gfx';
|
|
|
|
|
import { effect } from '@preact/signals-core';
|
|
|
|
|
|
|
|
|
|
import { calPanDelta } from '../utils/panning-utils.js';
|
|
|
|
|
import { DefaultModeDragType } from './default-tool-ext/ext.js';
|
|
|
|
|
|
|
|
|
|
export enum DefaultModeDragType {
|
|
|
|
|
/** Moving selected contents */
|
|
|
|
|
ContentMoving = 'content-moving',
|
|
|
|
|
/** Native range dragging inside active note block */
|
|
|
|
|
NativeEditing = 'native-editing',
|
|
|
|
|
/** Default void state */
|
|
|
|
|
None = 'none',
|
|
|
|
|
/** Expanding the dragging area, select the content covered inside */
|
|
|
|
|
Selecting = 'selecting',
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export class DefaultTool extends BaseTool {
|
|
|
|
|
static override toolName: string = 'default';
|
|
|
|
|
@@ -44,11 +44,11 @@ export class DefaultTool extends BaseTool {
|
|
|
|
|
|
|
|
|
|
private _disposables: DisposableGroup | null = null;
|
|
|
|
|
|
|
|
|
|
private readonly _panViewport = (delta: IVec) => {
|
|
|
|
|
private _panViewport(delta: IVec) {
|
|
|
|
|
this._accumulateDelta[0] += delta[0];
|
|
|
|
|
this._accumulateDelta[1] += delta[1];
|
|
|
|
|
this.gfx.viewport.applyDeltaCenter(delta[0], delta[1]);
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _selectionRectTransition: null | {
|
|
|
|
|
w: number;
|
|
|
|
|
@@ -117,44 +117,21 @@ export class DefaultTool extends BaseTool {
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { x, y, w, h } = this.controller.draggingArea$.peek();
|
|
|
|
|
const bound = new Bound(x, y, w, h);
|
|
|
|
|
|
|
|
|
|
let elements = gfx.getElementsByBound(bound).filter(el => {
|
|
|
|
|
if (isFrameBlock(el)) {
|
|
|
|
|
return el.childElements.length === 0 || bound.contains(el.elementBound);
|
|
|
|
|
}
|
|
|
|
|
if (el instanceof MindmapElementModel) {
|
|
|
|
|
return bound.contains(el.elementBound);
|
|
|
|
|
}
|
|
|
|
|
if (
|
|
|
|
|
el instanceof NoteBlockModel &&
|
|
|
|
|
el.props.displayMode === NoteDisplayMode.DocOnly
|
|
|
|
|
) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
const elements = this.interactivity?.handleBoxSelection({
|
|
|
|
|
box: this.controller.draggingArea$.peek(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
elements = getTopElements(elements).filter(el => !el.isLocked());
|
|
|
|
|
if (!elements) return;
|
|
|
|
|
|
|
|
|
|
const set = new Set(
|
|
|
|
|
gfx.keyboard.shiftKey$.peek()
|
|
|
|
|
? [...elements, ...gfx.selection.selectedElements]
|
|
|
|
|
: elements
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
this.edgelessSelectionManager.set({
|
|
|
|
|
elements: Array.from(set).map(element => element.id),
|
|
|
|
|
this.selection.set({
|
|
|
|
|
elements: elements.map(el => el.id),
|
|
|
|
|
editing: false,
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
dragType = DefaultModeDragType.None;
|
|
|
|
|
|
|
|
|
|
enableHover = true;
|
|
|
|
|
|
|
|
|
|
dragging = false;
|
|
|
|
|
movementDragging = false;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the end position of the dragging area in the model coordinate
|
|
|
|
|
@@ -174,7 +151,7 @@ export class DefaultTool extends BaseTool {
|
|
|
|
|
return [startX, startY] as IVec;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get edgelessSelectionManager() {
|
|
|
|
|
get selection() {
|
|
|
|
|
return this.gfx.selection;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -183,52 +160,55 @@ export class DefaultTool extends BaseTool {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async _cloneContent() {
|
|
|
|
|
const clonedResult = await this.interactivity?.requestElementsClone({
|
|
|
|
|
const clonedResult = await this.interactivity?.requestElementClone({
|
|
|
|
|
elements: this._toBeMoved,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!clonedResult) return;
|
|
|
|
|
|
|
|
|
|
this._toBeMoved = clonedResult.elements;
|
|
|
|
|
this.edgelessSelectionManager.set({
|
|
|
|
|
this.selection.set({
|
|
|
|
|
elements: this._toBeMoved.map(e => e.id),
|
|
|
|
|
editing: false,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _determineDragType(e: PointerEventState): DefaultModeDragType {
|
|
|
|
|
const { x, y } = e;
|
|
|
|
|
// Is dragging started from current selected rect
|
|
|
|
|
if (this.edgelessSelectionManager.isInSelectedRect(x, y)) {
|
|
|
|
|
if (this.edgelessSelectionManager.selectedElements.length === 1) {
|
|
|
|
|
let selected = this.edgelessSelectionManager.selectedElements[0];
|
|
|
|
|
// double check
|
|
|
|
|
const currentSelected = this._pick(x, y);
|
|
|
|
|
if (
|
|
|
|
|
!isFrameBlock(selected) &&
|
|
|
|
|
!(selected instanceof GroupElementModel) &&
|
|
|
|
|
currentSelected &&
|
|
|
|
|
currentSelected !== selected
|
|
|
|
|
) {
|
|
|
|
|
selected = currentSelected;
|
|
|
|
|
this.edgelessSelectionManager.set({
|
|
|
|
|
elements: [selected.id],
|
|
|
|
|
private _determineDragType(evt: PointerEventState): DefaultModeDragType {
|
|
|
|
|
const { x, y } = this.controller.lastMouseModelPos$.peek();
|
|
|
|
|
if (this.selection.isInSelectedRect(x, y)) {
|
|
|
|
|
if (this.selection.selectedElements.length === 1) {
|
|
|
|
|
const currentHoveredElem = this._getElementInGroup(x, y);
|
|
|
|
|
let curSelected = this.selection.selectedElements[0];
|
|
|
|
|
|
|
|
|
|
// If one of the following condition is true, keep the selection:
|
|
|
|
|
// 1. if group is currently selected
|
|
|
|
|
// 2. if the selected element is descendant of the hovered element
|
|
|
|
|
// 3. not hovering any element or hovering the same element
|
|
|
|
|
//
|
|
|
|
|
// Otherwise, we update the selection to the current hovered element
|
|
|
|
|
const shouldKeepSelection =
|
|
|
|
|
isGfxGroupCompatibleModel(curSelected) ||
|
|
|
|
|
(isGfxGroupCompatibleModel(currentHoveredElem) &&
|
|
|
|
|
currentHoveredElem.hasDescendant(curSelected)) ||
|
|
|
|
|
!currentHoveredElem ||
|
|
|
|
|
currentHoveredElem === curSelected;
|
|
|
|
|
|
|
|
|
|
if (!shouldKeepSelection) {
|
|
|
|
|
curSelected = currentHoveredElem;
|
|
|
|
|
this.selection.set({
|
|
|
|
|
elements: [curSelected.id],
|
|
|
|
|
editing: false,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return this.edgelessSelectionManager.editing
|
|
|
|
|
return this.selection.editing
|
|
|
|
|
? DefaultModeDragType.NativeEditing
|
|
|
|
|
: DefaultModeDragType.ContentMoving;
|
|
|
|
|
} else {
|
|
|
|
|
const selected = this._pick(x, y);
|
|
|
|
|
if (selected) {
|
|
|
|
|
this.edgelessSelectionManager.set({
|
|
|
|
|
elements: [selected.id],
|
|
|
|
|
editing: false,
|
|
|
|
|
});
|
|
|
|
|
const checked = this.interactivity?.handleElementSelection(evt);
|
|
|
|
|
|
|
|
|
|
if (checked) {
|
|
|
|
|
return DefaultModeDragType.ContentMoving;
|
|
|
|
|
} else {
|
|
|
|
|
return DefaultModeDragType.Selecting;
|
|
|
|
|
@@ -236,9 +216,7 @@ export class DefaultTool extends BaseTool {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _pick(x: number, y: number, options?: PointTestOptions) {
|
|
|
|
|
const modelPos = this.gfx.viewport.toModelCoord(x, y);
|
|
|
|
|
|
|
|
|
|
private _getElementInGroup(modelX: number, modelY: number) {
|
|
|
|
|
const tryGetLockedAncestor = (e: GfxModel | null) => {
|
|
|
|
|
if (e?.isLockedByAncestor()) {
|
|
|
|
|
return e.groups.findLast(group => group.isLocked());
|
|
|
|
|
@@ -246,34 +224,7 @@ export class DefaultTool extends BaseTool {
|
|
|
|
|
return e;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const result = this.gfx.getElementInGroup(
|
|
|
|
|
modelPos[0],
|
|
|
|
|
modelPos[1],
|
|
|
|
|
options
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (result instanceof MindmapElementModel) {
|
|
|
|
|
const picked = this.gfx.getElementByPoint(modelPos[0], modelPos[1], {
|
|
|
|
|
...((options ?? {}) as PointTestOptions),
|
|
|
|
|
all: true,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let pickedIdx = picked.length - 1;
|
|
|
|
|
|
|
|
|
|
while (pickedIdx >= 0) {
|
|
|
|
|
const element = picked[pickedIdx];
|
|
|
|
|
if (element === result) {
|
|
|
|
|
pickedIdx -= 1;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return tryGetLockedAncestor(picked[pickedIdx]) ?? null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return tryGetLockedAncestor(result);
|
|
|
|
|
return tryGetLockedAncestor(this.gfx.getElementInGroup(modelX, modelY));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private initializeDragState(
|
|
|
|
|
@@ -305,7 +256,7 @@ export class DefaultTool extends BaseTool {
|
|
|
|
|
if (this.dragType === DefaultModeDragType.ContentMoving) {
|
|
|
|
|
if (this.interactivity) {
|
|
|
|
|
this.doc.captureSync();
|
|
|
|
|
this.interactivity.initializeDrag({
|
|
|
|
|
this.interactivity.handleElementMove({
|
|
|
|
|
movingElements: this._toBeMoved,
|
|
|
|
|
event: event.raw,
|
|
|
|
|
onDragEnd: () => {
|
|
|
|
|
@@ -320,8 +271,8 @@ export class DefaultTool extends BaseTool {
|
|
|
|
|
override click(e: PointerEventState) {
|
|
|
|
|
if (this.doc.readonly) return;
|
|
|
|
|
|
|
|
|
|
if (!this.interactivity?.dispatchOnSelected(e)) {
|
|
|
|
|
this.edgelessSelectionManager.clear();
|
|
|
|
|
if (!this.interactivity?.handleElementSelection(e)) {
|
|
|
|
|
this.selection.clear();
|
|
|
|
|
resetNativeSelection(null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -353,9 +304,9 @@ export class DefaultTool extends BaseTool {
|
|
|
|
|
override dragEnd(e: PointerEventState) {
|
|
|
|
|
this.interactivity?.dispatchEvent('dragend', e);
|
|
|
|
|
|
|
|
|
|
if (this.edgelessSelectionManager.editing || !this.dragging) return;
|
|
|
|
|
if (this.selection.editing || !this.movementDragging) return;
|
|
|
|
|
|
|
|
|
|
this.dragging = false;
|
|
|
|
|
this.movementDragging = false;
|
|
|
|
|
this._toBeMoved = [];
|
|
|
|
|
this._clearSelectingState();
|
|
|
|
|
this.dragType = DefaultModeDragType.None;
|
|
|
|
|
@@ -364,7 +315,7 @@ export class DefaultTool extends BaseTool {
|
|
|
|
|
override dragMove(e: PointerEventState) {
|
|
|
|
|
this.interactivity?.dispatchEvent('dragmove', e);
|
|
|
|
|
|
|
|
|
|
if (!this.dragging) {
|
|
|
|
|
if (!this.movementDragging) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -397,19 +348,14 @@ export class DefaultTool extends BaseTool {
|
|
|
|
|
const { preventDefaultState, handledByView } =
|
|
|
|
|
this.interactivity?.dispatchEvent('dragstart', e) ?? {};
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
this.edgelessSelectionManager.editing ||
|
|
|
|
|
preventDefaultState ||
|
|
|
|
|
handledByView
|
|
|
|
|
)
|
|
|
|
|
return;
|
|
|
|
|
if (this.selection.editing || preventDefaultState || handledByView) return;
|
|
|
|
|
|
|
|
|
|
this.dragging = true;
|
|
|
|
|
this.movementDragging = true;
|
|
|
|
|
|
|
|
|
|
// Determine the drag type based on the current state and event
|
|
|
|
|
let dragType = this._determineDragType(e);
|
|
|
|
|
|
|
|
|
|
const elements = this.edgelessSelectionManager.selectedElements;
|
|
|
|
|
const elements = this.selection.selectedElements;
|
|
|
|
|
if (elements.some(e => e.isLocked())) return;
|
|
|
|
|
|
|
|
|
|
const toBeMoved = new Set(elements);
|
|
|
|
|
@@ -468,8 +414,6 @@ export class DefaultTool extends BaseTool {
|
|
|
|
|
this.interactivity?.dispatchEvent('pointerup', e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override tripleClick() {}
|
|
|
|
|
|
|
|
|
|
override unmounted(): void {}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|