Pull onPointerDown, onDoubleClick, onPointerMove into instance methods (#876)
* Pull onPointerDown, onDoubleClick, onPointerMove into instance methods * Use bound instance methods
This commit is contained in:
parent
c89584832d
commit
92ba401da8
@ -571,7 +571,255 @@ export class App extends React.Component<any, AppState> {
|
|||||||
left: event.clientX,
|
left: event.clientX,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
onPointerDown={event => {
|
onPointerDown={this.handleCanvasPointerDown}
|
||||||
|
onDoubleClick={this.handleCanvasDoubleClick}
|
||||||
|
onPointerMove={this.handleCanvasPointerMove}
|
||||||
|
onPointerUp={this.removePointer}
|
||||||
|
onPointerCancel={this.removePointer}
|
||||||
|
onDrop={event => {
|
||||||
|
const file = event.dataTransfer.files[0];
|
||||||
|
if (
|
||||||
|
file?.type === "application/json" ||
|
||||||
|
file?.name.endsWith(".excalidraw")
|
||||||
|
) {
|
||||||
|
loadFromBlob(file)
|
||||||
|
.then(({ elements, appState }) =>
|
||||||
|
this.syncActionResult({ elements, appState }),
|
||||||
|
)
|
||||||
|
.catch(error => console.error(error));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("labels.drawingCanvas")}
|
||||||
|
</canvas>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleCanvasDoubleClick = (
|
||||||
|
event: React.MouseEvent<HTMLCanvasElement>,
|
||||||
|
) => {
|
||||||
|
resetCursor();
|
||||||
|
|
||||||
|
const { x, y } = viewportCoordsToSceneCoords(
|
||||||
|
event,
|
||||||
|
this.state,
|
||||||
|
this.canvas,
|
||||||
|
);
|
||||||
|
|
||||||
|
const elementAtPosition = getElementAtPosition(
|
||||||
|
elements,
|
||||||
|
this.state,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
this.state.zoom,
|
||||||
|
);
|
||||||
|
|
||||||
|
const element =
|
||||||
|
elementAtPosition && isTextElement(elementAtPosition)
|
||||||
|
? elementAtPosition
|
||||||
|
: newTextElement(
|
||||||
|
newElement(
|
||||||
|
"text",
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
this.state.currentItemStrokeColor,
|
||||||
|
this.state.currentItemBackgroundColor,
|
||||||
|
this.state.currentItemFillStyle,
|
||||||
|
this.state.currentItemStrokeWidth,
|
||||||
|
this.state.currentItemRoughness,
|
||||||
|
this.state.currentItemOpacity,
|
||||||
|
),
|
||||||
|
"", // default text
|
||||||
|
this.state.currentItemFont, // default font
|
||||||
|
);
|
||||||
|
|
||||||
|
this.setState({ editingElement: element });
|
||||||
|
|
||||||
|
let textX = event.clientX;
|
||||||
|
let textY = event.clientY;
|
||||||
|
|
||||||
|
if (elementAtPosition && isTextElement(elementAtPosition)) {
|
||||||
|
elements = elements.filter(
|
||||||
|
element => element.id !== elementAtPosition.id,
|
||||||
|
);
|
||||||
|
this.setState({});
|
||||||
|
|
||||||
|
const centerElementX = elementAtPosition.x + elementAtPosition.width / 2;
|
||||||
|
const centerElementY = elementAtPosition.y + elementAtPosition.height / 2;
|
||||||
|
|
||||||
|
const {
|
||||||
|
x: centerElementXInViewport,
|
||||||
|
y: centerElementYInViewport,
|
||||||
|
} = sceneCoordsToViewportCoords(
|
||||||
|
{ sceneX: centerElementX, sceneY: centerElementY },
|
||||||
|
this.state,
|
||||||
|
this.canvas,
|
||||||
|
);
|
||||||
|
|
||||||
|
textX = centerElementXInViewport;
|
||||||
|
textY = centerElementYInViewport;
|
||||||
|
|
||||||
|
// x and y will change after calling newTextElement function
|
||||||
|
element.x = centerElementX;
|
||||||
|
element.y = centerElementY;
|
||||||
|
} else if (!event.altKey) {
|
||||||
|
const snappedToCenterPosition = this.getTextWysiwygSnappedToCenterPosition(
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (snappedToCenterPosition) {
|
||||||
|
element.x = snappedToCenterPosition.elementCenterX;
|
||||||
|
element.y = snappedToCenterPosition.elementCenterY;
|
||||||
|
textX = snappedToCenterPosition.wysiwygX;
|
||||||
|
textY = snappedToCenterPosition.wysiwygY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetSelection = () => {
|
||||||
|
this.setState({
|
||||||
|
draggingElement: null,
|
||||||
|
editingElement: null,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
textWysiwyg({
|
||||||
|
initText: element.text,
|
||||||
|
x: textX,
|
||||||
|
y: textY,
|
||||||
|
strokeColor: element.strokeColor,
|
||||||
|
font: element.font,
|
||||||
|
opacity: this.state.currentItemOpacity,
|
||||||
|
zoom: this.state.zoom,
|
||||||
|
onSubmit: text => {
|
||||||
|
if (text) {
|
||||||
|
elements = [
|
||||||
|
...elements,
|
||||||
|
{
|
||||||
|
// we need to recreate the element to update dimensions &
|
||||||
|
// position
|
||||||
|
...newTextElement(element, text, element.font),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
this.setState(prevState => ({
|
||||||
|
selectedElementIds: {
|
||||||
|
...prevState.selectedElementIds,
|
||||||
|
[element.id]: true,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
history.resumeRecording();
|
||||||
|
resetSelection();
|
||||||
|
},
|
||||||
|
onCancel: () => {
|
||||||
|
resetSelection();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private handleCanvasPointerMove = (
|
||||||
|
event: React.PointerEvent<HTMLCanvasElement>,
|
||||||
|
) => {
|
||||||
|
gesture.pointers = gesture.pointers.map(pointer =>
|
||||||
|
pointer.id === event.pointerId
|
||||||
|
? {
|
||||||
|
id: event.pointerId,
|
||||||
|
x: event.clientX,
|
||||||
|
y: event.clientY,
|
||||||
|
}
|
||||||
|
: pointer,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (gesture.pointers.length === 2) {
|
||||||
|
const center = getCenter(gesture.pointers);
|
||||||
|
const deltaX = center.x - gesture.lastCenter!.x;
|
||||||
|
const deltaY = center.y - gesture.lastCenter!.y;
|
||||||
|
gesture.lastCenter = center;
|
||||||
|
|
||||||
|
const distance = getDistance(gesture.pointers);
|
||||||
|
const scaleFactor = distance / gesture.initialDistance!;
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
scrollX: normalizeScroll(this.state.scrollX + deltaX / this.state.zoom),
|
||||||
|
scrollY: normalizeScroll(this.state.scrollY + deltaY / this.state.zoom),
|
||||||
|
zoom: getNormalizedZoom(gesture.initialScale! * scaleFactor),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
gesture.lastCenter = gesture.initialDistance = gesture.initialScale = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isHoldingSpace || isPanning || isDraggingScrollBar) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const {
|
||||||
|
isOverHorizontalScrollBar,
|
||||||
|
isOverVerticalScrollBar,
|
||||||
|
} = isOverScrollBars(currentScrollBars, event.clientX, event.clientY);
|
||||||
|
const isOverScrollBar =
|
||||||
|
isOverVerticalScrollBar || isOverHorizontalScrollBar;
|
||||||
|
if (!this.state.draggingElement && !this.state.multiElement) {
|
||||||
|
if (isOverScrollBar) {
|
||||||
|
resetCursor();
|
||||||
|
} else {
|
||||||
|
setCursorForShape(this.state.elementType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { x, y } = viewportCoordsToSceneCoords(
|
||||||
|
event,
|
||||||
|
this.state,
|
||||||
|
this.canvas,
|
||||||
|
);
|
||||||
|
if (this.state.multiElement) {
|
||||||
|
const { multiElement } = this.state;
|
||||||
|
const originX = multiElement.x;
|
||||||
|
const originY = multiElement.y;
|
||||||
|
const points = multiElement.points;
|
||||||
|
const pnt = points[points.length - 1];
|
||||||
|
pnt[0] = x - originX;
|
||||||
|
pnt[1] = y - originY;
|
||||||
|
invalidateShapeForElement(multiElement);
|
||||||
|
this.setState({});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasDeselectedButton = Boolean(event.buttons);
|
||||||
|
if (hasDeselectedButton || this.state.elementType !== "selection") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedElements = getSelectedElements(elements, this.state);
|
||||||
|
if (selectedElements.length === 1 && !isOverScrollBar) {
|
||||||
|
const resizeElement = getElementWithResizeHandler(
|
||||||
|
elements,
|
||||||
|
this.state,
|
||||||
|
{ x, y },
|
||||||
|
this.state.zoom,
|
||||||
|
event.pointerType,
|
||||||
|
);
|
||||||
|
if (resizeElement && resizeElement.resizeHandle) {
|
||||||
|
document.documentElement.style.cursor = getCursorForResizingElement(
|
||||||
|
resizeElement,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const hitElement = getElementAtPosition(
|
||||||
|
elements,
|
||||||
|
this.state,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
this.state.zoom,
|
||||||
|
);
|
||||||
|
document.documentElement.style.cursor =
|
||||||
|
hitElement && !isOverScrollBar ? "move" : "";
|
||||||
|
};
|
||||||
|
|
||||||
|
private handleCanvasPointerDown = (
|
||||||
|
event: React.PointerEvent<HTMLCanvasElement>,
|
||||||
|
) => {
|
||||||
if (lastPointerUp !== null) {
|
if (lastPointerUp !== null) {
|
||||||
// Unfortunately, sometimes we don't get a pointerup after a pointerdown,
|
// Unfortunately, sometimes we don't get a pointerup after a pointerdown,
|
||||||
// this can happen when a contextual menu or alert is triggered. In order to avoid
|
// this can happen when a contextual menu or alert is triggered. In order to avoid
|
||||||
@ -664,11 +912,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
const {
|
const {
|
||||||
isOverHorizontalScrollBar,
|
isOverHorizontalScrollBar,
|
||||||
isOverVerticalScrollBar,
|
isOverVerticalScrollBar,
|
||||||
} = isOverScrollBars(
|
} = isOverScrollBars(currentScrollBars, event.clientX, event.clientY);
|
||||||
currentScrollBars,
|
|
||||||
event.clientX,
|
|
||||||
event.clientY,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { x, y } = viewportCoordsToSceneCoords(
|
const { x, y } = viewportCoordsToSceneCoords(
|
||||||
event,
|
event,
|
||||||
@ -695,9 +939,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
const x = event.clientX;
|
const x = event.clientX;
|
||||||
const dx = x - lastX;
|
const dx = x - lastX;
|
||||||
this.setState({
|
this.setState({
|
||||||
scrollX: normalizeScroll(
|
scrollX: normalizeScroll(this.state.scrollX - dx / this.state.zoom),
|
||||||
this.state.scrollX - dx / this.state.zoom,
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
lastX = x;
|
lastX = x;
|
||||||
return;
|
return;
|
||||||
@ -707,9 +949,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
const y = event.clientY;
|
const y = event.clientY;
|
||||||
const dy = y - lastY;
|
const dy = y - lastY;
|
||||||
this.setState({
|
this.setState({
|
||||||
scrollY: normalizeScroll(
|
scrollY: normalizeScroll(this.state.scrollY - dy / this.state.zoom),
|
||||||
this.state.scrollY - dy / this.state.zoom,
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
lastY = y;
|
lastY = y;
|
||||||
}
|
}
|
||||||
@ -746,11 +986,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (isTextElement(element)) {
|
if (isTextElement(element)) {
|
||||||
element = newTextElement(
|
element = newTextElement(element, "", this.state.currentItemFont);
|
||||||
element,
|
|
||||||
"",
|
|
||||||
this.state.currentItemFont,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResizeTestType = ReturnType<typeof resizeTest>;
|
type ResizeTestType = ReturnType<typeof resizeTest>;
|
||||||
@ -768,15 +1004,10 @@ export class App extends React.Component<any, AppState> {
|
|||||||
event.pointerType,
|
event.pointerType,
|
||||||
);
|
);
|
||||||
|
|
||||||
const selectedElements = getSelectedElements(
|
const selectedElements = getSelectedElements(elements, this.state);
|
||||||
elements,
|
|
||||||
this.state,
|
|
||||||
);
|
|
||||||
if (selectedElements.length === 1 && resizeElement) {
|
if (selectedElements.length === 1 && resizeElement) {
|
||||||
this.setState({
|
this.setState({
|
||||||
resizingElement: resizeElement
|
resizingElement: resizeElement ? resizeElement.element : null,
|
||||||
? resizeElement.element
|
|
||||||
: null,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
resizeHandle = resizeElement.resizeHandle;
|
resizeHandle = resizeElement.resizeHandle;
|
||||||
@ -794,9 +1025,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
);
|
);
|
||||||
// clear selection if shift is not clicked
|
// clear selection if shift is not clicked
|
||||||
if (
|
if (
|
||||||
!(
|
!(hitElement && this.state.selectedElementIds[hitElement.id]) &&
|
||||||
hitElement && this.state.selectedElementIds[hitElement.id]
|
|
||||||
) &&
|
|
||||||
!event.shiftKey
|
!event.shiftKey
|
||||||
) {
|
) {
|
||||||
this.setState({ selectedElementIds: {} });
|
this.setState({ selectedElementIds: {} });
|
||||||
@ -886,11 +1115,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
elements = [
|
elements = [
|
||||||
...elements,
|
...elements,
|
||||||
{
|
{
|
||||||
...newTextElement(
|
...newTextElement(element, text, this.state.currentItemFont),
|
||||||
element,
|
|
||||||
text,
|
|
||||||
this.state.currentItemFont,
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -1039,9 +1264,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
const x = event.clientX;
|
const x = event.clientX;
|
||||||
const dx = x - lastX;
|
const dx = x - lastX;
|
||||||
this.setState({
|
this.setState({
|
||||||
scrollX: normalizeScroll(
|
scrollX: normalizeScroll(this.state.scrollX - dx / this.state.zoom),
|
||||||
this.state.scrollX - dx / this.state.zoom,
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
lastX = x;
|
lastX = x;
|
||||||
return;
|
return;
|
||||||
@ -1051,9 +1274,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
const y = event.clientY;
|
const y = event.clientY;
|
||||||
const dy = y - lastY;
|
const dy = y - lastY;
|
||||||
this.setState({
|
this.setState({
|
||||||
scrollY: normalizeScroll(
|
scrollY: normalizeScroll(this.state.scrollY - dy / this.state.zoom),
|
||||||
this.state.scrollY - dy / this.state.zoom,
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
lastY = y;
|
lastY = y;
|
||||||
return;
|
return;
|
||||||
@ -1081,10 +1302,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
if (isResizingElements && this.state.resizingElement) {
|
if (isResizingElements && this.state.resizingElement) {
|
||||||
this.setState({ isResizing: true });
|
this.setState({ isResizing: true });
|
||||||
const el = this.state.resizingElement;
|
const el = this.state.resizingElement;
|
||||||
const selectedElements = getSelectedElements(
|
const selectedElements = getSelectedElements(elements, this.state);
|
||||||
elements,
|
|
||||||
this.state,
|
|
||||||
);
|
|
||||||
if (selectedElements.length === 1) {
|
if (selectedElements.length === 1) {
|
||||||
const { x, y } = viewportCoordsToSceneCoords(
|
const { x, y } = viewportCoordsToSceneCoords(
|
||||||
event,
|
event,
|
||||||
@ -1094,8 +1312,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
const deltaX = x - lastX;
|
const deltaX = x - lastX;
|
||||||
const deltaY = y - lastY;
|
const deltaY = y - lastY;
|
||||||
const element = selectedElements[0];
|
const element = selectedElements[0];
|
||||||
const isLinear =
|
const isLinear = element.type === "line" || element.type === "arrow";
|
||||||
element.type === "line" || element.type === "arrow";
|
|
||||||
switch (resizeHandle) {
|
switch (resizeHandle) {
|
||||||
case "nw":
|
case "nw":
|
||||||
if (isLinear && element.points.length === 2) {
|
if (isLinear && element.points.length === 2) {
|
||||||
@ -1225,9 +1442,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
if (element.points.length > 0) {
|
if (element.points.length > 0) {
|
||||||
const len = element.points.length;
|
const len = element.points.length;
|
||||||
|
|
||||||
const points = [...element.points].sort(
|
const points = [...element.points].sort((a, b) => a[1] - b[1]);
|
||||||
(a, b) => a[1] - b[1],
|
|
||||||
);
|
|
||||||
|
|
||||||
for (let i = 1; i < points.length; ++i) {
|
for (let i = 1; i < points.length; ++i) {
|
||||||
const pnt = points[i];
|
const pnt = points[i];
|
||||||
@ -1242,9 +1457,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
|
|
||||||
if (element.points.length > 0) {
|
if (element.points.length > 0) {
|
||||||
const len = element.points.length;
|
const len = element.points.length;
|
||||||
const points = [...element.points].sort(
|
const points = [...element.points].sort((a, b) => a[0] - b[0]);
|
||||||
(a, b) => a[0] - b[0],
|
|
||||||
);
|
|
||||||
|
|
||||||
for (let i = 0; i < points.length; ++i) {
|
for (let i = 0; i < points.length; ++i) {
|
||||||
const pnt = points[i];
|
const pnt = points[i];
|
||||||
@ -1257,9 +1470,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
element.height += deltaY;
|
element.height += deltaY;
|
||||||
if (element.points.length > 0) {
|
if (element.points.length > 0) {
|
||||||
const len = element.points.length;
|
const len = element.points.length;
|
||||||
const points = [...element.points].sort(
|
const points = [...element.points].sort((a, b) => a[1] - b[1]);
|
||||||
(a, b) => a[1] - b[1],
|
|
||||||
);
|
|
||||||
|
|
||||||
for (let i = 1; i < points.length; ++i) {
|
for (let i = 1; i < points.length; ++i) {
|
||||||
const pnt = points[i];
|
const pnt = points[i];
|
||||||
@ -1272,9 +1483,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
element.width += deltaX;
|
element.width += deltaX;
|
||||||
if (element.points.length > 0) {
|
if (element.points.length > 0) {
|
||||||
const len = element.points.length;
|
const len = element.points.length;
|
||||||
const points = [...element.points].sort(
|
const points = [...element.points].sort((a, b) => a[0] - b[0]);
|
||||||
(a, b) => a[0] - b[0],
|
|
||||||
);
|
|
||||||
|
|
||||||
for (let i = 1; i < points.length; ++i) {
|
for (let i = 1; i < points.length; ++i) {
|
||||||
const pnt = points[i];
|
const pnt = points[i];
|
||||||
@ -1286,16 +1495,14 @@ export class App extends React.Component<any, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (resizeHandle) {
|
if (resizeHandle) {
|
||||||
resizeHandle = normalizeResizeHandle(
|
resizeHandle = normalizeResizeHandle(element, resizeHandle);
|
||||||
element,
|
|
||||||
resizeHandle,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
normalizeDimensions(element);
|
normalizeDimensions(element);
|
||||||
|
|
||||||
document.documentElement.style.cursor = getCursorForResizingElement(
|
document.documentElement.style.cursor = getCursorForResizingElement({
|
||||||
{ element, resizeHandle },
|
element,
|
||||||
);
|
resizeHandle,
|
||||||
|
});
|
||||||
el.x = element.x;
|
el.x = element.x;
|
||||||
el.y = element.y;
|
el.y = element.y;
|
||||||
invalidateShapeForElement(el);
|
invalidateShapeForElement(el);
|
||||||
@ -1307,17 +1514,11 @@ export class App extends React.Component<any, AppState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (hitElement && this.state.selectedElementIds[hitElement.id]) {
|
||||||
hitElement &&
|
|
||||||
this.state.selectedElementIds[hitElement.id]
|
|
||||||
) {
|
|
||||||
// Marking that click was used for dragging to check
|
// Marking that click was used for dragging to check
|
||||||
// if elements should be deselected on pointerup
|
// if elements should be deselected on pointerup
|
||||||
draggingOccurred = true;
|
draggingOccurred = true;
|
||||||
const selectedElements = getSelectedElements(
|
const selectedElements = getSelectedElements(elements, this.state);
|
||||||
elements,
|
|
||||||
this.state,
|
|
||||||
);
|
|
||||||
if (selectedElements.length > 0) {
|
if (selectedElements.length > 0) {
|
||||||
const { x, y } = viewportCoordsToSceneCoords(
|
const { x, y } = viewportCoordsToSceneCoords(
|
||||||
event,
|
event,
|
||||||
@ -1353,8 +1554,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
let height = distance(originY, y);
|
let height = distance(originY, y);
|
||||||
|
|
||||||
const isLinear =
|
const isLinear =
|
||||||
this.state.elementType === "line" ||
|
this.state.elementType === "line" || this.state.elementType === "arrow";
|
||||||
this.state.elementType === "arrow";
|
|
||||||
|
|
||||||
if (isLinear) {
|
if (isLinear) {
|
||||||
draggingOccurred = true;
|
draggingOccurred = true;
|
||||||
@ -1400,10 +1600,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
invalidateShapeForElement(draggingElement);
|
invalidateShapeForElement(draggingElement);
|
||||||
|
|
||||||
if (this.state.elementType === "selection") {
|
if (this.state.elementType === "selection") {
|
||||||
if (
|
if (!event.shiftKey && isSomeElementSelected(elements, this.state)) {
|
||||||
!event.shiftKey &&
|
|
||||||
isSomeElementSelected(elements, this.state)
|
|
||||||
) {
|
|
||||||
this.setState({ selectedElementIds: {} });
|
this.setState({ selectedElementIds: {} });
|
||||||
}
|
}
|
||||||
const elementsWithinSelection = getElementsWithinSelection(
|
const elementsWithinSelection = getElementsWithinSelection(
|
||||||
@ -1414,10 +1611,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
selectedElementIds: {
|
selectedElementIds: {
|
||||||
...prevState.selectedElementIds,
|
...prevState.selectedElementIds,
|
||||||
...Object.fromEntries(
|
...Object.fromEntries(
|
||||||
elementsWithinSelection.map(element => [
|
elementsWithinSelection.map(element => [element.id, true]),
|
||||||
element.id,
|
|
||||||
true,
|
|
||||||
]),
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
@ -1508,13 +1702,8 @@ export class App extends React.Component<any, AppState> {
|
|||||||
this.setState({});
|
this.setState({});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (resizingElement && isInvisiblySmallElement(resizingElement)) {
|
||||||
resizingElement &&
|
elements = elements.filter(el => el.id !== resizingElement.id);
|
||||||
isInvisiblySmallElement(resizingElement)
|
|
||||||
) {
|
|
||||||
elements = elements.filter(
|
|
||||||
el => el.id !== resizingElement.id,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If click occurred on already selected element
|
// If click occurred on already selected element
|
||||||
@ -1525,11 +1714,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
// If click occurred and elements were dragged or some element
|
// If click occurred and elements were dragged or some element
|
||||||
// was added to selection (on pointerdown phase) we need to keep
|
// was added to selection (on pointerdown phase) we need to keep
|
||||||
// selection unchanged
|
// selection unchanged
|
||||||
if (
|
if (hitElement && !draggingOccurred && !elementIsAddedToSelection) {
|
||||||
hitElement &&
|
|
||||||
!draggingOccurred &&
|
|
||||||
!elementIsAddedToSelection
|
|
||||||
) {
|
|
||||||
if (event.shiftKey) {
|
if (event.shiftKey) {
|
||||||
this.setState(prevState => ({
|
this.setState(prevState => ({
|
||||||
selectedElementIds: {
|
selectedElementIds: {
|
||||||
@ -1583,260 +1768,8 @@ export class App extends React.Component<any, AppState> {
|
|||||||
|
|
||||||
window.addEventListener("pointermove", onPointerMove);
|
window.addEventListener("pointermove", onPointerMove);
|
||||||
window.addEventListener("pointerup", onPointerUp);
|
window.addEventListener("pointerup", onPointerUp);
|
||||||
}}
|
|
||||||
onDoubleClick={event => {
|
|
||||||
resetCursor();
|
|
||||||
|
|
||||||
const { x, y } = viewportCoordsToSceneCoords(
|
|
||||||
event,
|
|
||||||
this.state,
|
|
||||||
this.canvas,
|
|
||||||
);
|
|
||||||
|
|
||||||
const elementAtPosition = getElementAtPosition(
|
|
||||||
elements,
|
|
||||||
this.state,
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
this.state.zoom,
|
|
||||||
);
|
|
||||||
|
|
||||||
const element =
|
|
||||||
elementAtPosition && isTextElement(elementAtPosition)
|
|
||||||
? elementAtPosition
|
|
||||||
: newTextElement(
|
|
||||||
newElement(
|
|
||||||
"text",
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
this.state.currentItemStrokeColor,
|
|
||||||
this.state.currentItemBackgroundColor,
|
|
||||||
this.state.currentItemFillStyle,
|
|
||||||
this.state.currentItemStrokeWidth,
|
|
||||||
this.state.currentItemRoughness,
|
|
||||||
this.state.currentItemOpacity,
|
|
||||||
),
|
|
||||||
"", // default text
|
|
||||||
this.state.currentItemFont, // default font
|
|
||||||
);
|
|
||||||
|
|
||||||
this.setState({ editingElement: element });
|
|
||||||
|
|
||||||
let textX = event.clientX;
|
|
||||||
let textY = event.clientY;
|
|
||||||
|
|
||||||
if (elementAtPosition && isTextElement(elementAtPosition)) {
|
|
||||||
elements = elements.filter(
|
|
||||||
element => element.id !== elementAtPosition.id,
|
|
||||||
);
|
|
||||||
this.setState({});
|
|
||||||
|
|
||||||
const centerElementX =
|
|
||||||
elementAtPosition.x + elementAtPosition.width / 2;
|
|
||||||
const centerElementY =
|
|
||||||
elementAtPosition.y + elementAtPosition.height / 2;
|
|
||||||
|
|
||||||
const {
|
|
||||||
x: centerElementXInViewport,
|
|
||||||
y: centerElementYInViewport,
|
|
||||||
} = sceneCoordsToViewportCoords(
|
|
||||||
{ sceneX: centerElementX, sceneY: centerElementY },
|
|
||||||
this.state,
|
|
||||||
this.canvas,
|
|
||||||
);
|
|
||||||
|
|
||||||
textX = centerElementXInViewport;
|
|
||||||
textY = centerElementYInViewport;
|
|
||||||
|
|
||||||
// x and y will change after calling newTextElement function
|
|
||||||
element.x = centerElementX;
|
|
||||||
element.y = centerElementY;
|
|
||||||
} else if (!event.altKey) {
|
|
||||||
const snappedToCenterPosition = this.getTextWysiwygSnappedToCenterPosition(
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (snappedToCenterPosition) {
|
|
||||||
element.x = snappedToCenterPosition.elementCenterX;
|
|
||||||
element.y = snappedToCenterPosition.elementCenterY;
|
|
||||||
textX = snappedToCenterPosition.wysiwygX;
|
|
||||||
textY = snappedToCenterPosition.wysiwygY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const resetSelection = () => {
|
|
||||||
this.setState({
|
|
||||||
draggingElement: null,
|
|
||||||
editingElement: null,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
textWysiwyg({
|
|
||||||
initText: element.text,
|
|
||||||
x: textX,
|
|
||||||
y: textY,
|
|
||||||
strokeColor: element.strokeColor,
|
|
||||||
font: element.font,
|
|
||||||
opacity: this.state.currentItemOpacity,
|
|
||||||
zoom: this.state.zoom,
|
|
||||||
onSubmit: text => {
|
|
||||||
if (text) {
|
|
||||||
elements = [
|
|
||||||
...elements,
|
|
||||||
{
|
|
||||||
// we need to recreate the element to update dimensions &
|
|
||||||
// position
|
|
||||||
...newTextElement(element, text, element.font),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
this.setState(prevState => ({
|
|
||||||
selectedElementIds: {
|
|
||||||
...prevState.selectedElementIds,
|
|
||||||
[element.id]: true,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
history.resumeRecording();
|
|
||||||
resetSelection();
|
|
||||||
},
|
|
||||||
onCancel: () => {
|
|
||||||
resetSelection();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
onPointerMove={event => {
|
|
||||||
gesture.pointers = gesture.pointers.map(pointer =>
|
|
||||||
pointer.id === event.pointerId
|
|
||||||
? {
|
|
||||||
id: event.pointerId,
|
|
||||||
x: event.clientX,
|
|
||||||
y: event.clientY,
|
|
||||||
}
|
|
||||||
: pointer,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (gesture.pointers.length === 2) {
|
|
||||||
const center = getCenter(gesture.pointers);
|
|
||||||
const deltaX = center.x - gesture.lastCenter!.x;
|
|
||||||
const deltaY = center.y - gesture.lastCenter!.y;
|
|
||||||
gesture.lastCenter = center;
|
|
||||||
|
|
||||||
const distance = getDistance(gesture.pointers);
|
|
||||||
const scaleFactor = distance / gesture.initialDistance!;
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
scrollX: normalizeScroll(
|
|
||||||
this.state.scrollX + deltaX / this.state.zoom,
|
|
||||||
),
|
|
||||||
scrollY: normalizeScroll(
|
|
||||||
this.state.scrollY + deltaY / this.state.zoom,
|
|
||||||
),
|
|
||||||
zoom: getNormalizedZoom(gesture.initialScale! * scaleFactor),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
gesture.lastCenter = gesture.initialDistance = gesture.initialScale = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isHoldingSpace || isPanning || isDraggingScrollBar) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const {
|
|
||||||
isOverHorizontalScrollBar,
|
|
||||||
isOverVerticalScrollBar,
|
|
||||||
} = isOverScrollBars(
|
|
||||||
currentScrollBars,
|
|
||||||
event.clientX,
|
|
||||||
event.clientY,
|
|
||||||
);
|
|
||||||
const isOverScrollBar =
|
|
||||||
isOverVerticalScrollBar || isOverHorizontalScrollBar;
|
|
||||||
if (!this.state.draggingElement && !this.state.multiElement) {
|
|
||||||
if (isOverScrollBar) {
|
|
||||||
resetCursor();
|
|
||||||
} else {
|
|
||||||
setCursorForShape(this.state.elementType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const { x, y } = viewportCoordsToSceneCoords(
|
|
||||||
event,
|
|
||||||
this.state,
|
|
||||||
this.canvas,
|
|
||||||
);
|
|
||||||
if (this.state.multiElement) {
|
|
||||||
const { multiElement } = this.state;
|
|
||||||
const originX = multiElement.x;
|
|
||||||
const originY = multiElement.y;
|
|
||||||
const points = multiElement.points;
|
|
||||||
const pnt = points[points.length - 1];
|
|
||||||
pnt[0] = x - originX;
|
|
||||||
pnt[1] = y - originY;
|
|
||||||
invalidateShapeForElement(multiElement);
|
|
||||||
this.setState({});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasDeselectedButton = Boolean(event.buttons);
|
|
||||||
if (
|
|
||||||
hasDeselectedButton ||
|
|
||||||
this.state.elementType !== "selection"
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectedElements = getSelectedElements(
|
|
||||||
elements,
|
|
||||||
this.state,
|
|
||||||
);
|
|
||||||
if (selectedElements.length === 1 && !isOverScrollBar) {
|
|
||||||
const resizeElement = getElementWithResizeHandler(
|
|
||||||
elements,
|
|
||||||
this.state,
|
|
||||||
{ x, y },
|
|
||||||
this.state.zoom,
|
|
||||||
event.pointerType,
|
|
||||||
);
|
|
||||||
if (resizeElement && resizeElement.resizeHandle) {
|
|
||||||
document.documentElement.style.cursor = getCursorForResizingElement(
|
|
||||||
resizeElement,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const hitElement = getElementAtPosition(
|
|
||||||
elements,
|
|
||||||
this.state,
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
this.state.zoom,
|
|
||||||
);
|
|
||||||
document.documentElement.style.cursor =
|
|
||||||
hitElement && !isOverScrollBar ? "move" : "";
|
|
||||||
}}
|
|
||||||
onPointerUp={this.removePointer}
|
|
||||||
onPointerCancel={this.removePointer}
|
|
||||||
onDrop={event => {
|
|
||||||
const file = event.dataTransfer.files[0];
|
|
||||||
if (
|
|
||||||
file?.type === "application/json" ||
|
|
||||||
file?.name.endsWith(".excalidraw")
|
|
||||||
) {
|
|
||||||
loadFromBlob(file)
|
|
||||||
.then(({ elements, appState }) =>
|
|
||||||
this.syncActionResult({ elements, appState }),
|
|
||||||
)
|
|
||||||
.catch(error => console.error(error));
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("labels.drawingCanvas")}
|
|
||||||
</canvas>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleWheel = (event: WheelEvent) => {
|
private handleWheel = (event: WheelEvent) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const { deltaX, deltaY } = event;
|
const { deltaX, deltaY } = event;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user