|
@@ -454,7 +454,6 @@ import {
|
|
import { Emitter } from "../emitter";
|
|
import { Emitter } from "../emitter";
|
|
import { ElementCanvasButtons } from "../components/ElementCanvasButtons";
|
|
import { ElementCanvasButtons } from "../components/ElementCanvasButtons";
|
|
import { Store, CaptureUpdateAction } from "../store";
|
|
import { Store, CaptureUpdateAction } from "../store";
|
|
-import { AnimatedTrail } from "../animated-trail";
|
|
|
|
import { LaserTrails } from "../laser-trails";
|
|
import { LaserTrails } from "../laser-trails";
|
|
import { withBatchedUpdates, withBatchedUpdatesThrottled } from "../reactUtils";
|
|
import { withBatchedUpdates, withBatchedUpdatesThrottled } from "../reactUtils";
|
|
import { textWysiwyg } from "../wysiwyg/textWysiwyg";
|
|
import { textWysiwyg } from "../wysiwyg/textWysiwyg";
|
|
@@ -464,6 +463,8 @@ import { isMaybeMermaidDefinition } from "../mermaid";
|
|
|
|
|
|
import { LassoTrail } from "../lasso";
|
|
import { LassoTrail } from "../lasso";
|
|
|
|
|
|
|
|
+import { EraserTrail } from "../eraser";
|
|
|
|
+
|
|
import { activeConfirmDialogAtom } from "./ActiveConfirmDialog";
|
|
import { activeConfirmDialogAtom } from "./ActiveConfirmDialog";
|
|
import BraveMeasureTextError from "./BraveMeasureTextError";
|
|
import BraveMeasureTextError from "./BraveMeasureTextError";
|
|
import { ContextMenu, CONTEXT_MENU_SEPARATOR } from "./ContextMenu";
|
|
import { ContextMenu, CONTEXT_MENU_SEPARATOR } from "./ContextMenu";
|
|
@@ -675,26 +676,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
animationFrameHandler = new AnimationFrameHandler();
|
|
animationFrameHandler = new AnimationFrameHandler();
|
|
|
|
|
|
laserTrails = new LaserTrails(this.animationFrameHandler, this);
|
|
laserTrails = new LaserTrails(this.animationFrameHandler, this);
|
|
- eraserTrail = new AnimatedTrail(this.animationFrameHandler, this, {
|
|
|
|
- streamline: 0.2,
|
|
|
|
- size: 5,
|
|
|
|
- keepHead: true,
|
|
|
|
- sizeMapping: (c) => {
|
|
|
|
- const DECAY_TIME = 200;
|
|
|
|
- const DECAY_LENGTH = 10;
|
|
|
|
- const t = Math.max(0, 1 - (performance.now() - c.pressure) / DECAY_TIME);
|
|
|
|
- const l =
|
|
|
|
- (DECAY_LENGTH -
|
|
|
|
- Math.min(DECAY_LENGTH, c.totalLength - c.currentIndex)) /
|
|
|
|
- DECAY_LENGTH;
|
|
|
|
-
|
|
|
|
- return Math.min(easeOut(l), easeOut(t));
|
|
|
|
- },
|
|
|
|
- fill: () =>
|
|
|
|
- this.state.theme === THEME.LIGHT
|
|
|
|
- ? "rgba(0, 0, 0, 0.2)"
|
|
|
|
- : "rgba(255, 255, 255, 0.2)",
|
|
|
|
- });
|
|
|
|
|
|
+ eraserTrail = new EraserTrail(this.animationFrameHandler, this);
|
|
lassoTrail = new LassoTrail(this.animationFrameHandler, this);
|
|
lassoTrail = new LassoTrail(this.animationFrameHandler, this);
|
|
|
|
|
|
onChangeEmitter = new Emitter<
|
|
onChangeEmitter = new Emitter<
|
|
@@ -1676,8 +1658,8 @@ class App extends React.Component<AppProps, AppState> {
|
|
<SVGLayer
|
|
<SVGLayer
|
|
trails={[
|
|
trails={[
|
|
this.laserTrails,
|
|
this.laserTrails,
|
|
- this.eraserTrail,
|
|
|
|
this.lassoTrail,
|
|
this.lassoTrail,
|
|
|
|
+ this.eraserTrail,
|
|
]}
|
|
]}
|
|
/>
|
|
/>
|
|
{selectedElements.length === 1 &&
|
|
{selectedElements.length === 1 &&
|
|
@@ -5163,7 +5145,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
return elements;
|
|
return elements;
|
|
}
|
|
}
|
|
|
|
|
|
- private getElementHitThreshold() {
|
|
|
|
|
|
+ getElementHitThreshold() {
|
|
return DEFAULT_COLLISION_THRESHOLD / this.state.zoom.value;
|
|
return DEFAULT_COLLISION_THRESHOLD / this.state.zoom.value;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -6219,101 +6201,16 @@ class App extends React.Component<AppProps, AppState> {
|
|
|
|
|
|
private handleEraser = (
|
|
private handleEraser = (
|
|
event: PointerEvent,
|
|
event: PointerEvent,
|
|
- pointerDownState: PointerDownState,
|
|
|
|
scenePointer: { x: number; y: number },
|
|
scenePointer: { x: number; y: number },
|
|
) => {
|
|
) => {
|
|
- this.eraserTrail.addPointToPath(scenePointer.x, scenePointer.y);
|
|
|
|
-
|
|
|
|
- let didChange = false;
|
|
|
|
-
|
|
|
|
- const processedGroups = new Set<ExcalidrawElement["id"]>();
|
|
|
|
- const nonDeletedElements = this.scene.getNonDeletedElements();
|
|
|
|
-
|
|
|
|
- const processElements = (elements: ExcalidrawElement[]) => {
|
|
|
|
- for (const element of elements) {
|
|
|
|
- if (element.locked) {
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (event.altKey) {
|
|
|
|
- if (this.elementsPendingErasure.delete(element.id)) {
|
|
|
|
- didChange = true;
|
|
|
|
- }
|
|
|
|
- } else if (!this.elementsPendingErasure.has(element.id)) {
|
|
|
|
- didChange = true;
|
|
|
|
- this.elementsPendingErasure.add(element.id);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // (un)erase groups atomically
|
|
|
|
- if (didChange && element.groupIds?.length) {
|
|
|
|
- const shallowestGroupId = element.groupIds.at(-1)!;
|
|
|
|
- if (!processedGroups.has(shallowestGroupId)) {
|
|
|
|
- processedGroups.add(shallowestGroupId);
|
|
|
|
- const elems = getElementsInGroup(
|
|
|
|
- nonDeletedElements,
|
|
|
|
- shallowestGroupId,
|
|
|
|
- );
|
|
|
|
- for (const elem of elems) {
|
|
|
|
- if (event.altKey) {
|
|
|
|
- this.elementsPendingErasure.delete(elem.id);
|
|
|
|
- } else {
|
|
|
|
- this.elementsPendingErasure.add(elem.id);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- const distance = pointDistance(
|
|
|
|
- pointFrom(pointerDownState.lastCoords.x, pointerDownState.lastCoords.y),
|
|
|
|
- pointFrom(scenePointer.x, scenePointer.y),
|
|
|
|
|
|
+ const elementsToErase = this.eraserTrail.addPointToPath(
|
|
|
|
+ scenePointer.x,
|
|
|
|
+ scenePointer.y,
|
|
|
|
+ event.altKey,
|
|
);
|
|
);
|
|
- const threshold = this.getElementHitThreshold();
|
|
|
|
- const p = { ...pointerDownState.lastCoords };
|
|
|
|
- let samplingInterval = 0;
|
|
|
|
- while (samplingInterval <= distance) {
|
|
|
|
- const hitElements = this.getElementsAtPosition(p.x, p.y);
|
|
|
|
- processElements(hitElements);
|
|
|
|
-
|
|
|
|
- // Exit since we reached current point
|
|
|
|
- if (samplingInterval === distance) {
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // Calculate next point in the line at a distance of sampling interval
|
|
|
|
- samplingInterval = Math.min(samplingInterval + threshold, distance);
|
|
|
|
-
|
|
|
|
- const distanceRatio = samplingInterval / distance;
|
|
|
|
- const nextX = (1 - distanceRatio) * p.x + distanceRatio * scenePointer.x;
|
|
|
|
- const nextY = (1 - distanceRatio) * p.y + distanceRatio * scenePointer.y;
|
|
|
|
- p.x = nextX;
|
|
|
|
- p.y = nextY;
|
|
|
|
- }
|
|
|
|
|
|
|
|
- pointerDownState.lastCoords.x = scenePointer.x;
|
|
|
|
- pointerDownState.lastCoords.y = scenePointer.y;
|
|
|
|
-
|
|
|
|
- if (didChange) {
|
|
|
|
- for (const element of this.scene.getNonDeletedElements()) {
|
|
|
|
- if (
|
|
|
|
- isBoundToContainer(element) &&
|
|
|
|
- (this.elementsPendingErasure.has(element.id) ||
|
|
|
|
- this.elementsPendingErasure.has(element.containerId))
|
|
|
|
- ) {
|
|
|
|
- if (event.altKey) {
|
|
|
|
- this.elementsPendingErasure.delete(element.id);
|
|
|
|
- this.elementsPendingErasure.delete(element.containerId);
|
|
|
|
- } else {
|
|
|
|
- this.elementsPendingErasure.add(element.id);
|
|
|
|
- this.elementsPendingErasure.add(element.containerId);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- this.elementsPendingErasure = new Set(this.elementsPendingErasure);
|
|
|
|
- this.triggerRender();
|
|
|
|
- }
|
|
|
|
|
|
+ this.elementsPendingErasure = new Set(elementsToErase);
|
|
|
|
+ this.triggerRender();
|
|
};
|
|
};
|
|
|
|
|
|
// set touch moving for mobile context menu
|
|
// set touch moving for mobile context menu
|
|
@@ -8159,7 +8056,7 @@ class App extends React.Component<AppProps, AppState> {
|
|
}
|
|
}
|
|
|
|
|
|
if (isEraserActive(this.state)) {
|
|
if (isEraserActive(this.state)) {
|
|
- this.handleEraser(event, pointerDownState, pointerCoords);
|
|
|
|
|
|
+ this.handleEraser(event, pointerCoords);
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|