From 6ebd41734fc391a4bf7eb47f4a031f5cacf37275 Mon Sep 17 00:00:00 2001 From: Enzo Ferey Date: Sun, 16 Feb 2020 22:54:50 +0100 Subject: [PATCH] =?UTF-8?q?Resize=20handler=20detection=20should=20not=20b?= =?UTF-8?q?e=20active=20when=20moving=20multip=E2=80=A6=20(#767)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix bug. * Implement `getSelectedElements`. * Explicit condition. * Respect variable naming. * Keep state consistent. * Use `isSomeElementSelected` abstraction. * Missing ones. --- src/actions/actionDeleteSelected.tsx | 4 +-- src/actions/actionProperties.tsx | 7 +++-- src/clipboard.ts | 5 ++-- src/components/ExportDialog.tsx | 5 ++-- src/components/HintViewer.tsx | 3 +- src/index.tsx | 43 +++++++++++++++------------- src/renderer/renderScene.ts | 3 +- src/scene/index.ts | 3 +- src/scene/selection.ts | 17 +++++++---- 9 files changed, 53 insertions(+), 37 deletions(-) diff --git a/src/actions/actionDeleteSelected.tsx b/src/actions/actionDeleteSelected.tsx index a2c67dd3..ce5e1d67 100644 --- a/src/actions/actionDeleteSelected.tsx +++ b/src/actions/actionDeleteSelected.tsx @@ -1,5 +1,5 @@ import { Action } from "./types"; -import { deleteSelectedElements } from "../scene"; +import { deleteSelectedElements, isSomeElementSelected } from "../scene"; import { KEYS } from "../keys"; export const actionDeleteSelected: Action = { @@ -12,6 +12,6 @@ export const actionDeleteSelected: Action = { }, contextItemLabel: "labels.delete", contextMenuOrder: 3, - commitToHistory: (_, elements) => elements.some(el => el.isSelected), + commitToHistory: (_, elements) => isSomeElementSelected(elements), keyTest: event => event.key === KEYS.BACKSPACE || event.key === KEYS.DELETE, }; diff --git a/src/actions/actionProperties.tsx b/src/actions/actionProperties.tsx index 4c80906a..4324eab5 100644 --- a/src/actions/actionProperties.tsx +++ b/src/actions/actionProperties.tsx @@ -1,7 +1,10 @@ import React from "react"; import { Action } from "./types"; import { ExcalidrawElement, ExcalidrawTextElement } from "../element/types"; -import { getCommonAttributeOfSelectedElements } from "../scene"; +import { + getCommonAttributeOfSelectedElements, + isSomeElementSelected, +} from "../scene"; import { ButtonSelect } from "../components/ButtonSelect"; import { isTextElement, redrawTextBoundingBox } from "../element"; import { ColorPicker } from "../components/ColorPicker"; @@ -28,7 +31,7 @@ const getFormValue = function( ): T | null { return ( (editingElement && getAttribute(editingElement)) ?? - (elements.some(element => element.isSelected) + (isSomeElementSelected(elements) ? getCommonAttributeOfSelectedElements(elements, getAttribute) : defaultValue) ?? null diff --git a/src/clipboard.ts b/src/clipboard.ts index f0917557..e5047c7b 100644 --- a/src/clipboard.ts +++ b/src/clipboard.ts @@ -1,4 +1,5 @@ import { ExcalidrawElement } from "./element/types"; +import { getSelectedElements } from "./scene"; let CLIPBOARD = ""; let PREFER_APP_CLIPBOARD = false; @@ -19,9 +20,7 @@ export async function copyToAppClipboard( elements: readonly ExcalidrawElement[], ) { CLIPBOARD = JSON.stringify( - elements - .filter(element => element.isSelected) - .map(({ shape, ...el }) => el), + getSelectedElements(elements).map(({ shape, ...el }) => el), ); try { // when copying to in-app clipboard, clear system clipboard so that if diff --git a/src/components/ExportDialog.tsx b/src/components/ExportDialog.tsx index 1f284956..1e504d26 100644 --- a/src/components/ExportDialog.tsx +++ b/src/components/ExportDialog.tsx @@ -16,6 +16,7 @@ import { t } from "../i18n"; import { KEYS } from "../keys"; import { probablySupportsClipboardBlob } from "../clipboard"; +import { getSelectedElements, isSomeElementSelected } from "../scene"; const scales = [1, 2, 3]; const defaultScale = scales.includes(devicePixelRatio) ? devicePixelRatio : 1; @@ -46,7 +47,7 @@ function ExportModal({ onExportToBackend: ExportCB; onCloseRequest: () => void; }) { - const someElementIsSelected = elements.some(element => element.isSelected); + const someElementIsSelected = isSomeElementSelected(elements); const [scale, setScale] = useState(defaultScale); const [exportSelected, setExportSelected] = useState(someElementIsSelected); const previewRef = useRef(null); @@ -56,7 +57,7 @@ function ExportModal({ const onlySelectedInput = useRef(null); const exportedElements = exportSelected - ? elements.filter(element => element.isSelected) + ? getSelectedElements(elements) : elements; useEffect(() => { diff --git a/src/components/HintViewer.tsx b/src/components/HintViewer.tsx index ad25562d..85b8454d 100644 --- a/src/components/HintViewer.tsx +++ b/src/components/HintViewer.tsx @@ -1,6 +1,7 @@ import React from "react"; import { t } from "../i18n"; import { ExcalidrawElement } from "../element/types"; +import { getSelectedElements } from "../scene"; import "./HintViewer.css"; @@ -20,7 +21,7 @@ const getHints = ({ elementType, multiMode, isResizing, elements }: Hint) => { } if (isResizing) { - const selectedElements = elements.filter(el => el.isSelected); + const selectedElements = getSelectedElements(elements); if ( selectedElements.length === 1 && (selectedElements[0].type === "arrow" || diff --git a/src/index.tsx b/src/index.tsx index 6ffedf4f..7cffae20 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -36,6 +36,8 @@ import { loadFromBlob, getZoomOrigin, getNormalizedZoom, + getSelectedElements, + isSomeElementSelected, } from "./scene"; import { renderScene } from "./renderer"; @@ -275,7 +277,7 @@ const LayerUI = React.memo( const { elementType, editingElement } = appState; const targetElements = editingElement ? [editingElement] - : elements.filter(el => el.isSelected); + : getSelectedElements(elements); if (!targetElements.length && elementType === "selection") { return null; } @@ -1046,11 +1048,15 @@ export class App extends React.Component { { x, y }, 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; document.documentElement.style.cursor = getCursorForResizingElement( resizeElement, @@ -1087,13 +1093,11 @@ export class App extends React.Component { ...element, isSelected: false, })), - ...elements - .filter(element => element.isSelected) - .map(element => { - const newElement = duplicateElement(element); - newElement.isSelected = true; - return newElement; - }), + ...getSelectedElements(elements).map(element => { + const newElement = duplicateElement(element); + newElement.isSelected = true; + return newElement; + }), ]; } } @@ -1328,7 +1332,7 @@ export class App extends React.Component { if (isResizingElements && this.state.resizingElement) { this.setState({ isResizing: true }); const el = this.state.resizingElement; - const selectedElements = elements.filter(el => el.isSelected); + const selectedElements = getSelectedElements(elements); if (selectedElements.length === 1) { const { x, y } = viewportCoordsToSceneCoords( e, @@ -1555,8 +1559,8 @@ export class App extends React.Component { // Marking that click was used for dragging to check // if elements should be deselected on mouseup draggingOccurred = true; - const selectedElements = elements.filter(el => el.isSelected); - if (selectedElements.length) { + const selectedElements = getSelectedElements(elements); + if (selectedElements.length > 0) { const { x, y } = viewportCoordsToSceneCoords( e, this.state, @@ -1638,7 +1642,7 @@ export class App extends React.Component { draggingElement.shape = null; if (this.state.elementType === "selection") { - if (!e.shiftKey && elements.some(el => el.isSelected)) { + if (!e.shiftKey && isSomeElementSelected(elements)) { elements = clearSelection(elements); } const elementsWithinSelection = getElementsWithinSelection( @@ -1772,7 +1776,7 @@ export class App extends React.Component { if ( elementType !== "selection" || - elements.some(el => el.isSelected) + isSomeElementSelected(elements) ) { history.resumeRecording(); } @@ -1941,9 +1945,8 @@ export class App extends React.Component { return; } - const selectedElements = elements.filter(e => e.isSelected) - .length; - if (selectedElements === 1) { + const selectedElements = getSelectedElements(elements); + if (selectedElements.length === 1) { const resizeElement = getElementWithResizeHandler( elements, { x, y }, diff --git a/src/renderer/renderScene.ts b/src/renderer/renderScene.ts index 43f880d6..d35db621 100644 --- a/src/renderer/renderScene.ts +++ b/src/renderer/renderScene.ts @@ -12,6 +12,7 @@ import { SCROLLBAR_WIDTH, } from "../scene/scrollbars"; import { getZoomTranslation } from "../scene/zoom"; +import { getSelectedElements } from "../scene/selection"; import { renderElement, renderElementToSvg } from "./renderElement"; @@ -135,7 +136,7 @@ export function renderScene( // Pain selected elements if (renderSelection) { - const selectedElements = elements.filter(element => element.isSelected); + const selectedElements = getSelectedElements(elements); const dashledLinePadding = 4 / sceneState.zoom; context.save(); diff --git a/src/scene/index.ts b/src/scene/index.ts index af9795dd..c19f7831 100644 --- a/src/scene/index.ts +++ b/src/scene/index.ts @@ -3,9 +3,10 @@ export { clearSelection, getSelectedIndices, deleteSelectedElements, - someElementIsSelected, + isSomeElementSelected, getElementsWithinSelection, getCommonAttributeOfSelectedElements, + getSelectedElements, } from "./selection"; export { exportCanvas, diff --git a/src/scene/selection.ts b/src/scene/selection.ts index 3ad3c38d..569aa9e2 100644 --- a/src/scene/selection.ts +++ b/src/scene/selection.ts @@ -55,8 +55,11 @@ export function getSelectedIndices(elements: readonly ExcalidrawElement[]) { return selectedIndices; } -export const someElementIsSelected = (elements: readonly ExcalidrawElement[]) => - elements.some(element => element.isSelected); +export function isSomeElementSelected( + elements: readonly ExcalidrawElement[], +): boolean { + return elements.some(element => element.isSelected); +} /** * Returns common attribute (picked by `getAttribute` callback) of selected @@ -68,10 +71,14 @@ export function getCommonAttributeOfSelectedElements( ): T | null { const attributes = Array.from( new Set( - elements - .filter(element => element.isSelected) - .map(element => getAttribute(element)), + getSelectedElements(elements).map(element => getAttribute(element)), ), ); return attributes.length === 1 ? attributes[0] : null; } + +export function getSelectedElements( + elements: readonly ExcalidrawElement[], +): readonly ExcalidrawElement[] { + return elements.filter(element => element.isSelected); +}