Resize handler detection should not be active when moving multip… (#767)
* Fix bug. * Implement `getSelectedElements`. * Explicit condition. * Respect variable naming. * Keep state consistent. * Use `isSomeElementSelected` abstraction. * Missing ones.
This commit is contained in:
parent
ad72946131
commit
6ebd41734f
@ -1,5 +1,5 @@
|
|||||||
import { Action } from "./types";
|
import { Action } from "./types";
|
||||||
import { deleteSelectedElements } from "../scene";
|
import { deleteSelectedElements, isSomeElementSelected } from "../scene";
|
||||||
import { KEYS } from "../keys";
|
import { KEYS } from "../keys";
|
||||||
|
|
||||||
export const actionDeleteSelected: Action = {
|
export const actionDeleteSelected: Action = {
|
||||||
@ -12,6 +12,6 @@ export const actionDeleteSelected: Action = {
|
|||||||
},
|
},
|
||||||
contextItemLabel: "labels.delete",
|
contextItemLabel: "labels.delete",
|
||||||
contextMenuOrder: 3,
|
contextMenuOrder: 3,
|
||||||
commitToHistory: (_, elements) => elements.some(el => el.isSelected),
|
commitToHistory: (_, elements) => isSomeElementSelected(elements),
|
||||||
keyTest: event => event.key === KEYS.BACKSPACE || event.key === KEYS.DELETE,
|
keyTest: event => event.key === KEYS.BACKSPACE || event.key === KEYS.DELETE,
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Action } from "./types";
|
import { Action } from "./types";
|
||||||
import { ExcalidrawElement, ExcalidrawTextElement } from "../element/types";
|
import { ExcalidrawElement, ExcalidrawTextElement } from "../element/types";
|
||||||
import { getCommonAttributeOfSelectedElements } from "../scene";
|
import {
|
||||||
|
getCommonAttributeOfSelectedElements,
|
||||||
|
isSomeElementSelected,
|
||||||
|
} from "../scene";
|
||||||
import { ButtonSelect } from "../components/ButtonSelect";
|
import { ButtonSelect } from "../components/ButtonSelect";
|
||||||
import { isTextElement, redrawTextBoundingBox } from "../element";
|
import { isTextElement, redrawTextBoundingBox } from "../element";
|
||||||
import { ColorPicker } from "../components/ColorPicker";
|
import { ColorPicker } from "../components/ColorPicker";
|
||||||
@ -28,7 +31,7 @@ const getFormValue = function<T>(
|
|||||||
): T | null {
|
): T | null {
|
||||||
return (
|
return (
|
||||||
(editingElement && getAttribute(editingElement)) ??
|
(editingElement && getAttribute(editingElement)) ??
|
||||||
(elements.some(element => element.isSelected)
|
(isSomeElementSelected(elements)
|
||||||
? getCommonAttributeOfSelectedElements(elements, getAttribute)
|
? getCommonAttributeOfSelectedElements(elements, getAttribute)
|
||||||
: defaultValue) ??
|
: defaultValue) ??
|
||||||
null
|
null
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { ExcalidrawElement } from "./element/types";
|
import { ExcalidrawElement } from "./element/types";
|
||||||
|
import { getSelectedElements } from "./scene";
|
||||||
|
|
||||||
let CLIPBOARD = "";
|
let CLIPBOARD = "";
|
||||||
let PREFER_APP_CLIPBOARD = false;
|
let PREFER_APP_CLIPBOARD = false;
|
||||||
@ -19,9 +20,7 @@ export async function copyToAppClipboard(
|
|||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
) {
|
) {
|
||||||
CLIPBOARD = JSON.stringify(
|
CLIPBOARD = JSON.stringify(
|
||||||
elements
|
getSelectedElements(elements).map(({ shape, ...el }) => el),
|
||||||
.filter(element => element.isSelected)
|
|
||||||
.map(({ shape, ...el }) => el),
|
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
// when copying to in-app clipboard, clear system clipboard so that if
|
// when copying to in-app clipboard, clear system clipboard so that if
|
||||||
|
@ -16,6 +16,7 @@ import { t } from "../i18n";
|
|||||||
import { KEYS } from "../keys";
|
import { KEYS } from "../keys";
|
||||||
|
|
||||||
import { probablySupportsClipboardBlob } from "../clipboard";
|
import { probablySupportsClipboardBlob } from "../clipboard";
|
||||||
|
import { getSelectedElements, isSomeElementSelected } from "../scene";
|
||||||
|
|
||||||
const scales = [1, 2, 3];
|
const scales = [1, 2, 3];
|
||||||
const defaultScale = scales.includes(devicePixelRatio) ? devicePixelRatio : 1;
|
const defaultScale = scales.includes(devicePixelRatio) ? devicePixelRatio : 1;
|
||||||
@ -46,7 +47,7 @@ function ExportModal({
|
|||||||
onExportToBackend: ExportCB;
|
onExportToBackend: ExportCB;
|
||||||
onCloseRequest: () => void;
|
onCloseRequest: () => void;
|
||||||
}) {
|
}) {
|
||||||
const someElementIsSelected = elements.some(element => element.isSelected);
|
const someElementIsSelected = isSomeElementSelected(elements);
|
||||||
const [scale, setScale] = useState(defaultScale);
|
const [scale, setScale] = useState(defaultScale);
|
||||||
const [exportSelected, setExportSelected] = useState(someElementIsSelected);
|
const [exportSelected, setExportSelected] = useState(someElementIsSelected);
|
||||||
const previewRef = useRef<HTMLDivElement>(null);
|
const previewRef = useRef<HTMLDivElement>(null);
|
||||||
@ -56,7 +57,7 @@ function ExportModal({
|
|||||||
const onlySelectedInput = useRef<HTMLInputElement>(null);
|
const onlySelectedInput = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
const exportedElements = exportSelected
|
const exportedElements = exportSelected
|
||||||
? elements.filter(element => element.isSelected)
|
? getSelectedElements(elements)
|
||||||
: elements;
|
: elements;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { ExcalidrawElement } from "../element/types";
|
import { ExcalidrawElement } from "../element/types";
|
||||||
|
import { getSelectedElements } from "../scene";
|
||||||
|
|
||||||
import "./HintViewer.css";
|
import "./HintViewer.css";
|
||||||
|
|
||||||
@ -20,7 +21,7 @@ const getHints = ({ elementType, multiMode, isResizing, elements }: Hint) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isResizing) {
|
if (isResizing) {
|
||||||
const selectedElements = elements.filter(el => el.isSelected);
|
const selectedElements = getSelectedElements(elements);
|
||||||
if (
|
if (
|
||||||
selectedElements.length === 1 &&
|
selectedElements.length === 1 &&
|
||||||
(selectedElements[0].type === "arrow" ||
|
(selectedElements[0].type === "arrow" ||
|
||||||
|
@ -36,6 +36,8 @@ import {
|
|||||||
loadFromBlob,
|
loadFromBlob,
|
||||||
getZoomOrigin,
|
getZoomOrigin,
|
||||||
getNormalizedZoom,
|
getNormalizedZoom,
|
||||||
|
getSelectedElements,
|
||||||
|
isSomeElementSelected,
|
||||||
} from "./scene";
|
} from "./scene";
|
||||||
|
|
||||||
import { renderScene } from "./renderer";
|
import { renderScene } from "./renderer";
|
||||||
@ -275,7 +277,7 @@ const LayerUI = React.memo(
|
|||||||
const { elementType, editingElement } = appState;
|
const { elementType, editingElement } = appState;
|
||||||
const targetElements = editingElement
|
const targetElements = editingElement
|
||||||
? [editingElement]
|
? [editingElement]
|
||||||
: elements.filter(el => el.isSelected);
|
: getSelectedElements(elements);
|
||||||
if (!targetElements.length && elementType === "selection") {
|
if (!targetElements.length && elementType === "selection") {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -1046,11 +1048,15 @@ export class App extends React.Component<any, AppState> {
|
|||||||
{ x, y },
|
{ x, y },
|
||||||
this.state.zoom,
|
this.state.zoom,
|
||||||
);
|
);
|
||||||
this.setState({
|
|
||||||
resizingElement: resizeElement ? resizeElement.element : null,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (resizeElement) {
|
const selectedElements = getSelectedElements(elements);
|
||||||
|
if (selectedElements.length === 1 && resizeElement) {
|
||||||
|
this.setState({
|
||||||
|
resizingElement: resizeElement
|
||||||
|
? resizeElement.element
|
||||||
|
: null,
|
||||||
|
});
|
||||||
|
|
||||||
resizeHandle = resizeElement.resizeHandle;
|
resizeHandle = resizeElement.resizeHandle;
|
||||||
document.documentElement.style.cursor = getCursorForResizingElement(
|
document.documentElement.style.cursor = getCursorForResizingElement(
|
||||||
resizeElement,
|
resizeElement,
|
||||||
@ -1087,13 +1093,11 @@ export class App extends React.Component<any, AppState> {
|
|||||||
...element,
|
...element,
|
||||||
isSelected: false,
|
isSelected: false,
|
||||||
})),
|
})),
|
||||||
...elements
|
...getSelectedElements(elements).map(element => {
|
||||||
.filter(element => element.isSelected)
|
const newElement = duplicateElement(element);
|
||||||
.map(element => {
|
newElement.isSelected = true;
|
||||||
const newElement = duplicateElement(element);
|
return newElement;
|
||||||
newElement.isSelected = true;
|
}),
|
||||||
return newElement;
|
|
||||||
}),
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1328,7 +1332,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 = elements.filter(el => el.isSelected);
|
const selectedElements = getSelectedElements(elements);
|
||||||
if (selectedElements.length === 1) {
|
if (selectedElements.length === 1) {
|
||||||
const { x, y } = viewportCoordsToSceneCoords(
|
const { x, y } = viewportCoordsToSceneCoords(
|
||||||
e,
|
e,
|
||||||
@ -1555,8 +1559,8 @@ export class App extends React.Component<any, AppState> {
|
|||||||
// Marking that click was used for dragging to check
|
// Marking that click was used for dragging to check
|
||||||
// if elements should be deselected on mouseup
|
// if elements should be deselected on mouseup
|
||||||
draggingOccurred = true;
|
draggingOccurred = true;
|
||||||
const selectedElements = elements.filter(el => el.isSelected);
|
const selectedElements = getSelectedElements(elements);
|
||||||
if (selectedElements.length) {
|
if (selectedElements.length > 0) {
|
||||||
const { x, y } = viewportCoordsToSceneCoords(
|
const { x, y } = viewportCoordsToSceneCoords(
|
||||||
e,
|
e,
|
||||||
this.state,
|
this.state,
|
||||||
@ -1638,7 +1642,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
draggingElement.shape = null;
|
draggingElement.shape = null;
|
||||||
|
|
||||||
if (this.state.elementType === "selection") {
|
if (this.state.elementType === "selection") {
|
||||||
if (!e.shiftKey && elements.some(el => el.isSelected)) {
|
if (!e.shiftKey && isSomeElementSelected(elements)) {
|
||||||
elements = clearSelection(elements);
|
elements = clearSelection(elements);
|
||||||
}
|
}
|
||||||
const elementsWithinSelection = getElementsWithinSelection(
|
const elementsWithinSelection = getElementsWithinSelection(
|
||||||
@ -1772,7 +1776,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
elementType !== "selection" ||
|
elementType !== "selection" ||
|
||||||
elements.some(el => el.isSelected)
|
isSomeElementSelected(elements)
|
||||||
) {
|
) {
|
||||||
history.resumeRecording();
|
history.resumeRecording();
|
||||||
}
|
}
|
||||||
@ -1941,9 +1945,8 @@ export class App extends React.Component<any, AppState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedElements = elements.filter(e => e.isSelected)
|
const selectedElements = getSelectedElements(elements);
|
||||||
.length;
|
if (selectedElements.length === 1) {
|
||||||
if (selectedElements === 1) {
|
|
||||||
const resizeElement = getElementWithResizeHandler(
|
const resizeElement = getElementWithResizeHandler(
|
||||||
elements,
|
elements,
|
||||||
{ x, y },
|
{ x, y },
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
SCROLLBAR_WIDTH,
|
SCROLLBAR_WIDTH,
|
||||||
} from "../scene/scrollbars";
|
} from "../scene/scrollbars";
|
||||||
import { getZoomTranslation } from "../scene/zoom";
|
import { getZoomTranslation } from "../scene/zoom";
|
||||||
|
import { getSelectedElements } from "../scene/selection";
|
||||||
|
|
||||||
import { renderElement, renderElementToSvg } from "./renderElement";
|
import { renderElement, renderElementToSvg } from "./renderElement";
|
||||||
|
|
||||||
@ -135,7 +136,7 @@ export function renderScene(
|
|||||||
|
|
||||||
// Pain selected elements
|
// Pain selected elements
|
||||||
if (renderSelection) {
|
if (renderSelection) {
|
||||||
const selectedElements = elements.filter(element => element.isSelected);
|
const selectedElements = getSelectedElements(elements);
|
||||||
const dashledLinePadding = 4 / sceneState.zoom;
|
const dashledLinePadding = 4 / sceneState.zoom;
|
||||||
|
|
||||||
context.save();
|
context.save();
|
||||||
|
@ -3,9 +3,10 @@ export {
|
|||||||
clearSelection,
|
clearSelection,
|
||||||
getSelectedIndices,
|
getSelectedIndices,
|
||||||
deleteSelectedElements,
|
deleteSelectedElements,
|
||||||
someElementIsSelected,
|
isSomeElementSelected,
|
||||||
getElementsWithinSelection,
|
getElementsWithinSelection,
|
||||||
getCommonAttributeOfSelectedElements,
|
getCommonAttributeOfSelectedElements,
|
||||||
|
getSelectedElements,
|
||||||
} from "./selection";
|
} from "./selection";
|
||||||
export {
|
export {
|
||||||
exportCanvas,
|
exportCanvas,
|
||||||
|
@ -55,8 +55,11 @@ export function getSelectedIndices(elements: readonly ExcalidrawElement[]) {
|
|||||||
return selectedIndices;
|
return selectedIndices;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const someElementIsSelected = (elements: readonly ExcalidrawElement[]) =>
|
export function isSomeElementSelected(
|
||||||
elements.some(element => element.isSelected);
|
elements: readonly ExcalidrawElement[],
|
||||||
|
): boolean {
|
||||||
|
return elements.some(element => element.isSelected);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns common attribute (picked by `getAttribute` callback) of selected
|
* Returns common attribute (picked by `getAttribute` callback) of selected
|
||||||
@ -68,10 +71,14 @@ export function getCommonAttributeOfSelectedElements<T>(
|
|||||||
): T | null {
|
): T | null {
|
||||||
const attributes = Array.from(
|
const attributes = Array.from(
|
||||||
new Set(
|
new Set(
|
||||||
elements
|
getSelectedElements(elements).map(element => getAttribute(element)),
|
||||||
.filter(element => element.isSelected)
|
|
||||||
.map(element => getAttribute(element)),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return attributes.length === 1 ? attributes[0] : null;
|
return attributes.length === 1 ? attributes[0] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getSelectedElements(
|
||||||
|
elements: readonly ExcalidrawElement[],
|
||||||
|
): readonly ExcalidrawElement[] {
|
||||||
|
return elements.filter(element => element.isSelected);
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user