mirror of
				https://github.com/excalidraw/excalidraw.git
				synced 2025-10-30 18:34:22 +01:00 
			
		
		
		
	Add NonDeleted<ExcalidrawElement> (#1068)
* add NonDeleted * make test:all script run tests without prompt * rename helper * replace with helper * make element contructors return nonDeleted elements * cache filtered elements where appliacable for better perf * rename manager element getter * remove unnecessary assertion * fix test * make element types in resizeElement into nonDeleted Co-authored-by: dwelle <luzar.david@gmail.com>
This commit is contained in:
		| @@ -110,6 +110,7 @@ | |||||||
|     "prettier": "prettier \"**/*.{css,scss,json,md,html,yml}\" --ignore-path=.eslintignore", |     "prettier": "prettier \"**/*.{css,scss,json,md,html,yml}\" --ignore-path=.eslintignore", | ||||||
|     "start": "react-scripts start", |     "start": "react-scripts start", | ||||||
|     "test": "npm run test:app", |     "test": "npm run test:app", | ||||||
|  |     "test:all": "npm run test:typecheck && npm run test:code && npm run test:other && npm run test:app -- --watchAll=false", | ||||||
|     "test:update": "npm run test:app -- --updateSnapshot --watchAll=false", |     "test:update": "npm run test:app -- --updateSnapshot --watchAll=false", | ||||||
|     "test:app": "react-scripts test --env=jsdom --passWithNoTests", |     "test:app": "react-scripts test --env=jsdom --passWithNoTests", | ||||||
|     "test:code": "eslint --max-warnings=0 --ignore-path .gitignore --ext .js,.ts,.tsx .", |     "test:code": "eslint --max-warnings=0 --ignore-path .gitignore --ext .js,.ts,.tsx .", | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ import React from "react"; | |||||||
| import { trash } from "../components/icons"; | import { trash } from "../components/icons"; | ||||||
| import { t } from "../i18n"; | import { t } from "../i18n"; | ||||||
| import { register } from "./register"; | import { register } from "./register"; | ||||||
|  | import { getNonDeletedElements } from "../element"; | ||||||
|  |  | ||||||
| export const actionDeleteSelected = register({ | export const actionDeleteSelected = register({ | ||||||
|   name: "deleteSelectedElements", |   name: "deleteSelectedElements", | ||||||
| @@ -20,7 +21,10 @@ export const actionDeleteSelected = register({ | |||||||
|         elementType: "selection", |         elementType: "selection", | ||||||
|         multiElement: null, |         multiElement: null, | ||||||
|       }, |       }, | ||||||
|       commitToHistory: isSomeElementSelected(elements, appState), |       commitToHistory: isSomeElementSelected( | ||||||
|  |         getNonDeletedElements(elements), | ||||||
|  |         appState, | ||||||
|  |       ), | ||||||
|     }; |     }; | ||||||
|   }, |   }, | ||||||
|   contextItemLabel: "labels.delete", |   contextItemLabel: "labels.delete", | ||||||
| @@ -33,7 +37,7 @@ export const actionDeleteSelected = register({ | |||||||
|       title={t("labels.delete")} |       title={t("labels.delete")} | ||||||
|       aria-label={t("labels.delete")} |       aria-label={t("labels.delete")} | ||||||
|       onClick={() => updateData(null)} |       onClick={() => updateData(null)} | ||||||
|       visible={isSomeElementSelected(elements, appState)} |       visible={isSomeElementSelected(getNonDeletedElements(elements), appState)} | ||||||
|     /> |     /> | ||||||
|   ), |   ), | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ import React from "react"; | |||||||
| import { KEYS } from "../keys"; | import { KEYS } from "../keys"; | ||||||
| import { register } from "./register"; | import { register } from "./register"; | ||||||
| import { ExcalidrawElement } from "../element/types"; | import { ExcalidrawElement } from "../element/types"; | ||||||
| import { duplicateElement } from "../element"; | import { duplicateElement, getNonDeletedElements } from "../element"; | ||||||
| import { isSomeElementSelected } from "../scene"; | import { isSomeElementSelected } from "../scene"; | ||||||
| import { ToolButton } from "../components/ToolButton"; | import { ToolButton } from "../components/ToolButton"; | ||||||
| import { clone } from "../components/icons"; | import { clone } from "../components/icons"; | ||||||
| @@ -43,7 +43,7 @@ export const actionDuplicateSelection = register({ | |||||||
|       )}`} |       )}`} | ||||||
|       aria-label={t("labels.duplicateSelection")} |       aria-label={t("labels.duplicateSelection")} | ||||||
|       onClick={() => updateData(null)} |       onClick={() => updateData(null)} | ||||||
|       visible={isSomeElementSelected(elements, appState)} |       visible={isSomeElementSelected(getNonDeletedElements(elements), appState)} | ||||||
|     /> |     /> | ||||||
|   ), |   ), | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ import React from "react"; | |||||||
| import { menu, palette } from "../components/icons"; | import { menu, palette } from "../components/icons"; | ||||||
| import { ToolButton } from "../components/ToolButton"; | import { ToolButton } from "../components/ToolButton"; | ||||||
| import { t } from "../i18n"; | import { t } from "../i18n"; | ||||||
| import { showSelectedShapeActions } from "../element"; | import { showSelectedShapeActions, getNonDeletedElements } from "../element"; | ||||||
| import { register } from "./register"; | import { register } from "./register"; | ||||||
| import { allowFullScreen, exitFullScreen, isFullScreen } from "../utils"; | import { allowFullScreen, exitFullScreen, isFullScreen } from "../utils"; | ||||||
| import { KEYS } from "../keys"; | import { KEYS } from "../keys"; | ||||||
| @@ -39,7 +39,10 @@ export const actionToggleEditMenu = register({ | |||||||
|   }), |   }), | ||||||
|   PanelComponent: ({ elements, appState, updateData }) => ( |   PanelComponent: ({ elements, appState, updateData }) => ( | ||||||
|     <ToolButton |     <ToolButton | ||||||
|       visible={showSelectedShapeActions(appState, elements)} |       visible={showSelectedShapeActions( | ||||||
|  |         appState, | ||||||
|  |         getNonDeletedElements(elements), | ||||||
|  |       )} | ||||||
|       type="button" |       type="button" | ||||||
|       icon={palette} |       icon={palette} | ||||||
|       aria-label={t("buttons.edit")} |       aria-label={t("buttons.edit")} | ||||||
|   | |||||||
| @@ -5,7 +5,11 @@ import { | |||||||
|   isSomeElementSelected, |   isSomeElementSelected, | ||||||
| } from "../scene"; | } from "../scene"; | ||||||
| import { ButtonSelect } from "../components/ButtonSelect"; | import { ButtonSelect } from "../components/ButtonSelect"; | ||||||
| import { isTextElement, redrawTextBoundingBox } from "../element"; | import { | ||||||
|  |   isTextElement, | ||||||
|  |   redrawTextBoundingBox, | ||||||
|  |   getNonDeletedElements, | ||||||
|  | } from "../element"; | ||||||
| import { ColorPicker } from "../components/ColorPicker"; | import { ColorPicker } from "../components/ColorPicker"; | ||||||
| import { AppState } from "../../src/types"; | import { AppState } from "../../src/types"; | ||||||
| import { t } from "../i18n"; | import { t } from "../i18n"; | ||||||
| @@ -33,10 +37,15 @@ const getFormValue = function <T>( | |||||||
|   defaultValue?: T, |   defaultValue?: T, | ||||||
| ): T | null { | ): T | null { | ||||||
|   const editingElement = appState.editingElement; |   const editingElement = appState.editingElement; | ||||||
|  |   const nonDeletedElements = getNonDeletedElements(elements); | ||||||
|   return ( |   return ( | ||||||
|     (editingElement && getAttribute(editingElement)) ?? |     (editingElement && getAttribute(editingElement)) ?? | ||||||
|     (isSomeElementSelected(elements, appState) |     (isSomeElementSelected(nonDeletedElements, appState) | ||||||
|       ? getCommonAttributeOfSelectedElements(elements, appState, getAttribute) |       ? getCommonAttributeOfSelectedElements( | ||||||
|  |           nonDeletedElements, | ||||||
|  |           appState, | ||||||
|  |           getAttribute, | ||||||
|  |         ) | ||||||
|       : defaultValue) ?? |       : defaultValue) ?? | ||||||
|     null |     null | ||||||
|   ); |   ); | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ import { | |||||||
| import { ExcalidrawElement } from "../element/types"; | import { ExcalidrawElement } from "../element/types"; | ||||||
| import { AppState } from "../types"; | import { AppState } from "../types"; | ||||||
| import { t } from "../i18n"; | import { t } from "../i18n"; | ||||||
|  | import { globalSceneState } from "../scene"; | ||||||
|  |  | ||||||
| export class ActionManager implements ActionsManagerInterface { | export class ActionManager implements ActionsManagerInterface { | ||||||
|   actions = {} as ActionsManagerInterface["actions"]; |   actions = {} as ActionsManagerInterface["actions"]; | ||||||
| @@ -17,16 +18,18 @@ export class ActionManager implements ActionsManagerInterface { | |||||||
|  |  | ||||||
|   getAppState: () => AppState; |   getAppState: () => AppState; | ||||||
|  |  | ||||||
|   getElements: () => readonly ExcalidrawElement[]; |   getElementsIncludingDeleted: () => readonly ExcalidrawElement[]; | ||||||
|  |  | ||||||
|   constructor( |   constructor( | ||||||
|     updater: UpdaterFn, |     updater: UpdaterFn, | ||||||
|     getAppState: () => AppState, |     getAppState: () => AppState, | ||||||
|     getElements: () => readonly ExcalidrawElement[], |     getElementsIncludingDeleted: () => ReturnType< | ||||||
|  |       typeof globalSceneState["getElementsIncludingDeleted"] | ||||||
|  |     >, | ||||||
|   ) { |   ) { | ||||||
|     this.updater = updater; |     this.updater = updater; | ||||||
|     this.getAppState = getAppState; |     this.getAppState = getAppState; | ||||||
|     this.getElements = getElements; |     this.getElementsIncludingDeleted = getElementsIncludingDeleted; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   registerAction(action: Action) { |   registerAction(action: Action) { | ||||||
| @@ -43,7 +46,11 @@ export class ActionManager implements ActionsManagerInterface { | |||||||
|       .filter( |       .filter( | ||||||
|         (action) => |         (action) => | ||||||
|           action.keyTest && |           action.keyTest && | ||||||
|           action.keyTest(event, this.getAppState(), this.getElements()), |           action.keyTest( | ||||||
|  |             event, | ||||||
|  |             this.getAppState(), | ||||||
|  |             this.getElementsIncludingDeleted(), | ||||||
|  |           ), | ||||||
|       ); |       ); | ||||||
|  |  | ||||||
|     if (data.length === 0) { |     if (data.length === 0) { | ||||||
| @@ -51,12 +58,24 @@ export class ActionManager implements ActionsManagerInterface { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     event.preventDefault(); |     event.preventDefault(); | ||||||
|     this.updater(data[0].perform(this.getElements(), this.getAppState(), null)); |     this.updater( | ||||||
|  |       data[0].perform( | ||||||
|  |         this.getElementsIncludingDeleted(), | ||||||
|  |         this.getAppState(), | ||||||
|  |         null, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|     return true; |     return true; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   executeAction(action: Action) { |   executeAction(action: Action) { | ||||||
|     this.updater(action.perform(this.getElements(), this.getAppState(), null)); |     this.updater( | ||||||
|  |       action.perform( | ||||||
|  |         this.getElementsIncludingDeleted(), | ||||||
|  |         this.getAppState(), | ||||||
|  |         null, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   getContextMenuItems(actionFilter: ActionFilterFn = (action) => action) { |   getContextMenuItems(actionFilter: ActionFilterFn = (action) => action) { | ||||||
| @@ -72,7 +91,11 @@ export class ActionManager implements ActionsManagerInterface { | |||||||
|         label: action.contextItemLabel ? t(action.contextItemLabel) : "", |         label: action.contextItemLabel ? t(action.contextItemLabel) : "", | ||||||
|         action: () => { |         action: () => { | ||||||
|           this.updater( |           this.updater( | ||||||
|             action.perform(this.getElements(), this.getAppState(), null), |             action.perform( | ||||||
|  |               this.getElementsIncludingDeleted(), | ||||||
|  |               this.getAppState(), | ||||||
|  |               null, | ||||||
|  |             ), | ||||||
|           ); |           ); | ||||||
|         }, |         }, | ||||||
|       })); |       })); | ||||||
| @@ -84,13 +107,17 @@ export class ActionManager implements ActionsManagerInterface { | |||||||
|       const PanelComponent = action.PanelComponent!; |       const PanelComponent = action.PanelComponent!; | ||||||
|       const updateData = (formState?: any) => { |       const updateData = (formState?: any) => { | ||||||
|         this.updater( |         this.updater( | ||||||
|           action.perform(this.getElements(), this.getAppState(), formState), |           action.perform( | ||||||
|  |             this.getElementsIncludingDeleted(), | ||||||
|  |             this.getAppState(), | ||||||
|  |             formState, | ||||||
|  |           ), | ||||||
|         ); |         ); | ||||||
|       }; |       }; | ||||||
|  |  | ||||||
|       return ( |       return ( | ||||||
|         <PanelComponent |         <PanelComponent | ||||||
|           elements={this.getElements()} |           elements={this.getElementsIncludingDeleted()} | ||||||
|           appState={this.getAppState()} |           appState={this.getAppState()} | ||||||
|           updateData={updateData} |           updateData={updateData} | ||||||
|         /> |         /> | ||||||
|   | |||||||
| @@ -1,4 +1,7 @@ | |||||||
| import { ExcalidrawElement } from "./element/types"; | import { | ||||||
|  |   ExcalidrawElement, | ||||||
|  |   NonDeletedExcalidrawElement, | ||||||
|  | } from "./element/types"; | ||||||
| import { getSelectedElements } from "./scene"; | import { getSelectedElements } from "./scene"; | ||||||
| import { AppState } from "./types"; | import { AppState } from "./types"; | ||||||
| import { SVG_EXPORT_TAG } from "./scene/export"; | import { SVG_EXPORT_TAG } from "./scene/export"; | ||||||
| @@ -19,7 +22,7 @@ export const probablySupportsClipboardBlob = | |||||||
|   "toBlob" in HTMLCanvasElement.prototype; |   "toBlob" in HTMLCanvasElement.prototype; | ||||||
|  |  | ||||||
| export async function copyToAppClipboard( | export async function copyToAppClipboard( | ||||||
|   elements: readonly ExcalidrawElement[], |   elements: readonly NonDeletedExcalidrawElement[], | ||||||
|   appState: AppState, |   appState: AppState, | ||||||
| ) { | ) { | ||||||
|   CLIPBOARD = JSON.stringify(getSelectedElements(elements, appState)); |   CLIPBOARD = JSON.stringify(getSelectedElements(elements, appState)); | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ import { ToolButton } from "./ToolButton"; | |||||||
| import { capitalizeString, setCursorForShape } from "../utils"; | import { capitalizeString, setCursorForShape } from "../utils"; | ||||||
| import Stack from "./Stack"; | import Stack from "./Stack"; | ||||||
| import useIsMobile from "../is-mobile"; | import useIsMobile from "../is-mobile"; | ||||||
|  | import { getNonDeletedElements } from "../element"; | ||||||
|  |  | ||||||
| export function SelectedShapeActions({ | export function SelectedShapeActions({ | ||||||
|   appState, |   appState, | ||||||
| @@ -21,7 +22,10 @@ export function SelectedShapeActions({ | |||||||
|   renderAction: ActionManager["renderAction"]; |   renderAction: ActionManager["renderAction"]; | ||||||
|   elementType: ExcalidrawElement["type"]; |   elementType: ExcalidrawElement["type"]; | ||||||
| }) { | }) { | ||||||
|   const targetElements = getTargetElement(elements, appState); |   const targetElements = getTargetElement( | ||||||
|  |     getNonDeletedElements(elements), | ||||||
|  |     appState, | ||||||
|  |   ); | ||||||
|   const isEditing = Boolean(appState.editingElement); |   const isEditing = Boolean(appState.editingElement); | ||||||
|   const isMobile = useIsMobile(); |   const isMobile = useIsMobile(); | ||||||
|  |  | ||||||
| @@ -82,13 +86,9 @@ export function SelectedShapeActions({ | |||||||
| export function ShapesSwitcher({ | export function ShapesSwitcher({ | ||||||
|   elementType, |   elementType, | ||||||
|   setAppState, |   setAppState, | ||||||
|   setElements, |  | ||||||
|   elements, |  | ||||||
| }: { | }: { | ||||||
|   elementType: ExcalidrawElement["type"]; |   elementType: ExcalidrawElement["type"]; | ||||||
|   setAppState: any; |   setAppState: any; | ||||||
|   setElements: any; |  | ||||||
|   elements: readonly ExcalidrawElement[]; |  | ||||||
| }) { | }) { | ||||||
|   return ( |   return ( | ||||||
|     <> |     <> | ||||||
|   | |||||||
| @@ -20,7 +20,6 @@ import { | |||||||
|   getElementMap, |   getElementMap, | ||||||
|   getDrawingVersion, |   getDrawingVersion, | ||||||
|   getSyncableElements, |   getSyncableElements, | ||||||
|   hasNonDeletedElements, |  | ||||||
|   newLinearElement, |   newLinearElement, | ||||||
|   ResizeArrowFnType, |   ResizeArrowFnType, | ||||||
|   resizeElements, |   resizeElements, | ||||||
| @@ -185,7 +184,7 @@ export class App extends React.Component<any, AppState> { | |||||||
|     this.actionManager = new ActionManager( |     this.actionManager = new ActionManager( | ||||||
|       this.syncActionResult, |       this.syncActionResult, | ||||||
|       () => this.state, |       () => this.state, | ||||||
|       () => globalSceneState.getAllElements(), |       () => globalSceneState.getElementsIncludingDeleted(), | ||||||
|     ); |     ); | ||||||
|     this.actionManager.registerAll(actions); |     this.actionManager.registerAll(actions); | ||||||
|  |  | ||||||
| @@ -209,10 +208,7 @@ export class App extends React.Component<any, AppState> { | |||||||
|           appState={this.state} |           appState={this.state} | ||||||
|           setAppState={this.setAppState} |           setAppState={this.setAppState} | ||||||
|           actionManager={this.actionManager} |           actionManager={this.actionManager} | ||||||
|           elements={globalSceneState.getAllElements().filter((element) => { |           elements={globalSceneState.getElements()} | ||||||
|             return !element.isDeleted; |  | ||||||
|           })} |  | ||||||
|           setElements={this.setElements} |  | ||||||
|           onRoomCreate={this.openPortal} |           onRoomCreate={this.openPortal} | ||||||
|           onRoomDestroy={this.closePortal} |           onRoomDestroy={this.closePortal} | ||||||
|           onLockToggle={this.toggleLock} |           onLockToggle={this.toggleLock} | ||||||
| @@ -310,7 +306,7 @@ export class App extends React.Component<any, AppState> { | |||||||
|     try { |     try { | ||||||
|       await Promise.race([ |       await Promise.race([ | ||||||
|         document.fonts?.ready?.then(() => { |         document.fonts?.ready?.then(() => { | ||||||
|           globalSceneState.getAllElements().forEach((element) => { |           globalSceneState.getElementsIncludingDeleted().forEach((element) => { | ||||||
|             if (isTextElement(element)) { |             if (isTextElement(element)) { | ||||||
|               invalidateShapeForElement(element); |               invalidateShapeForElement(element); | ||||||
|             } |             } | ||||||
| @@ -431,7 +427,7 @@ export class App extends React.Component<any, AppState> { | |||||||
|   } |   } | ||||||
|   private onResize = withBatchedUpdates(() => { |   private onResize = withBatchedUpdates(() => { | ||||||
|     globalSceneState |     globalSceneState | ||||||
|       .getAllElements() |       .getElementsIncludingDeleted() | ||||||
|       .forEach((element) => invalidateShapeForElement(element)); |       .forEach((element) => invalidateShapeForElement(element)); | ||||||
|     this.setState({}); |     this.setState({}); | ||||||
|   }); |   }); | ||||||
| @@ -439,7 +435,7 @@ export class App extends React.Component<any, AppState> { | |||||||
|   private beforeUnload = withBatchedUpdates((event: BeforeUnloadEvent) => { |   private beforeUnload = withBatchedUpdates((event: BeforeUnloadEvent) => { | ||||||
|     if ( |     if ( | ||||||
|       this.state.isCollaborating && |       this.state.isCollaborating && | ||||||
|       hasNonDeletedElements(globalSceneState.getAllElements()) |       globalSceneState.getElements().length > 0 | ||||||
|     ) { |     ) { | ||||||
|       event.preventDefault(); |       event.preventDefault(); | ||||||
|       // NOTE: modern browsers no longer allow showing a custom message here |       // NOTE: modern browsers no longer allow showing a custom message here | ||||||
| @@ -484,8 +480,9 @@ export class App extends React.Component<any, AppState> { | |||||||
|       ); |       ); | ||||||
|       cursorButton[socketID] = user.button; |       cursorButton[socketID] = user.button; | ||||||
|     }); |     }); | ||||||
|  |     const elements = globalSceneState.getElements(); | ||||||
|     const { atLeastOneVisibleElement, scrollBars } = renderScene( |     const { atLeastOneVisibleElement, scrollBars } = renderScene( | ||||||
|       globalSceneState.getAllElements().filter((element) => { |       elements.filter((element) => { | ||||||
|         // don't render text element that's being currently edited (it's |         // don't render text element that's being currently edited (it's | ||||||
|         //  rendered on remote only) |         //  rendered on remote only) | ||||||
|         return ( |         return ( | ||||||
| @@ -517,22 +514,20 @@ export class App extends React.Component<any, AppState> { | |||||||
|     if (scrollBars) { |     if (scrollBars) { | ||||||
|       currentScrollBars = scrollBars; |       currentScrollBars = scrollBars; | ||||||
|     } |     } | ||||||
|     const scrolledOutside = |     const scrolledOutside = !atLeastOneVisibleElement && elements.length > 0; | ||||||
|       !atLeastOneVisibleElement && |  | ||||||
|       hasNonDeletedElements(globalSceneState.getAllElements()); |  | ||||||
|     if (this.state.scrolledOutside !== scrolledOutside) { |     if (this.state.scrolledOutside !== scrolledOutside) { | ||||||
|       this.setState({ scrolledOutside: scrolledOutside }); |       this.setState({ scrolledOutside: scrolledOutside }); | ||||||
|     } |     } | ||||||
|     this.saveDebounced(); |     this.saveDebounced(); | ||||||
|  |  | ||||||
|     if ( |     if ( | ||||||
|       getDrawingVersion(globalSceneState.getAllElements()) > |       getDrawingVersion(globalSceneState.getElementsIncludingDeleted()) > | ||||||
|       this.lastBroadcastedOrReceivedSceneVersion |       this.lastBroadcastedOrReceivedSceneVersion | ||||||
|     ) { |     ) { | ||||||
|       this.broadcastScene("SCENE_UPDATE"); |       this.broadcastScene("SCENE_UPDATE"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     history.record(this.state, globalSceneState.getAllElements()); |     history.record(this.state, globalSceneState.getElementsIncludingDeleted()); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   // Copy/paste |   // Copy/paste | ||||||
| @@ -543,7 +538,7 @@ export class App extends React.Component<any, AppState> { | |||||||
|     } |     } | ||||||
|     this.copyAll(); |     this.copyAll(); | ||||||
|     const { elements: nextElements, appState } = deleteSelectedElements( |     const { elements: nextElements, appState } = deleteSelectedElements( | ||||||
|       globalSceneState.getAllElements(), |       globalSceneState.getElementsIncludingDeleted(), | ||||||
|       this.state, |       this.state, | ||||||
|     ); |     ); | ||||||
|     globalSceneState.replaceAllElements(nextElements); |     globalSceneState.replaceAllElements(nextElements); | ||||||
| @@ -561,19 +556,16 @@ export class App extends React.Component<any, AppState> { | |||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   private copyAll = () => { |   private copyAll = () => { | ||||||
|     copyToAppClipboard(globalSceneState.getAllElements(), this.state); |     copyToAppClipboard(globalSceneState.getElements(), this.state); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   private copyToClipboardAsPng = () => { |   private copyToClipboardAsPng = () => { | ||||||
|     const selectedElements = getSelectedElements( |     const elements = globalSceneState.getElements(); | ||||||
|       globalSceneState.getAllElements(), |  | ||||||
|       this.state, |     const selectedElements = getSelectedElements(elements, this.state); | ||||||
|     ); |  | ||||||
|     exportCanvas( |     exportCanvas( | ||||||
|       "clipboard", |       "clipboard", | ||||||
|       selectedElements.length |       selectedElements.length ? selectedElements : elements, | ||||||
|         ? selectedElements |  | ||||||
|         : globalSceneState.getAllElements(), |  | ||||||
|       this.state, |       this.state, | ||||||
|       this.canvas!, |       this.canvas!, | ||||||
|       this.state, |       this.state, | ||||||
| @@ -582,14 +574,14 @@ export class App extends React.Component<any, AppState> { | |||||||
|  |  | ||||||
|   private copyToClipboardAsSvg = () => { |   private copyToClipboardAsSvg = () => { | ||||||
|     const selectedElements = getSelectedElements( |     const selectedElements = getSelectedElements( | ||||||
|       globalSceneState.getAllElements(), |       globalSceneState.getElements(), | ||||||
|       this.state, |       this.state, | ||||||
|     ); |     ); | ||||||
|     exportCanvas( |     exportCanvas( | ||||||
|       "clipboard-svg", |       "clipboard-svg", | ||||||
|       selectedElements.length |       selectedElements.length | ||||||
|         ? selectedElements |         ? selectedElements | ||||||
|         : globalSceneState.getAllElements(), |         : globalSceneState.getElements(), | ||||||
|       this.state, |       this.state, | ||||||
|       this.canvas!, |       this.canvas!, | ||||||
|       this.state, |       this.state, | ||||||
| @@ -669,7 +661,7 @@ export class App extends React.Component<any, AppState> { | |||||||
|     ); |     ); | ||||||
|  |  | ||||||
|     globalSceneState.replaceAllElements([ |     globalSceneState.replaceAllElements([ | ||||||
|       ...globalSceneState.getAllElements(), |       ...globalSceneState.getElementsIncludingDeleted(), | ||||||
|       ...newElements, |       ...newElements, | ||||||
|     ]); |     ]); | ||||||
|     history.resumeRecording(); |     history.resumeRecording(); | ||||||
| @@ -703,7 +695,7 @@ export class App extends React.Component<any, AppState> { | |||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     globalSceneState.replaceAllElements([ |     globalSceneState.replaceAllElements([ | ||||||
|       ...globalSceneState.getAllElements(), |       ...globalSceneState.getElementsIncludingDeleted(), | ||||||
|       element, |       element, | ||||||
|     ]); |     ]); | ||||||
|     this.setState({ selectedElementIds: { [element.id]: true } }); |     this.setState({ selectedElementIds: { [element.id]: true } }); | ||||||
| @@ -789,15 +781,15 @@ export class App extends React.Component<any, AppState> { | |||||||
|         // elements with more staler versions than ours, ignore them |         // elements with more staler versions than ours, ignore them | ||||||
|         // and keep ours. |         // and keep ours. | ||||||
|         if ( |         if ( | ||||||
|           globalSceneState.getAllElements() == null || |           globalSceneState.getElementsIncludingDeleted() == null || | ||||||
|           globalSceneState.getAllElements().length === 0 |           globalSceneState.getElementsIncludingDeleted().length === 0 | ||||||
|         ) { |         ) { | ||||||
|           globalSceneState.replaceAllElements(remoteElements); |           globalSceneState.replaceAllElements(remoteElements); | ||||||
|         } else { |         } else { | ||||||
|           // create a map of ids so we don't have to iterate |           // create a map of ids so we don't have to iterate | ||||||
|           // over the array more than once. |           // over the array more than once. | ||||||
|           const localElementMap = getElementMap( |           const localElementMap = getElementMap( | ||||||
|             globalSceneState.getAllElements(), |             globalSceneState.getElementsIncludingDeleted(), | ||||||
|           ); |           ); | ||||||
|  |  | ||||||
|           // Reconcile |           // Reconcile | ||||||
| @@ -982,12 +974,14 @@ export class App extends React.Component<any, AppState> { | |||||||
|     const data: SocketUpdateDataSource[typeof sceneType] = { |     const data: SocketUpdateDataSource[typeof sceneType] = { | ||||||
|       type: sceneType, |       type: sceneType, | ||||||
|       payload: { |       payload: { | ||||||
|         elements: getSyncableElements(globalSceneState.getAllElements()), |         elements: getSyncableElements( | ||||||
|  |           globalSceneState.getElementsIncludingDeleted(), | ||||||
|  |         ), | ||||||
|       }, |       }, | ||||||
|     }; |     }; | ||||||
|     this.lastBroadcastedOrReceivedSceneVersion = Math.max( |     this.lastBroadcastedOrReceivedSceneVersion = Math.max( | ||||||
|       this.lastBroadcastedOrReceivedSceneVersion, |       this.lastBroadcastedOrReceivedSceneVersion, | ||||||
|       getDrawingVersion(globalSceneState.getAllElements()), |       getDrawingVersion(globalSceneState.getElementsIncludingDeleted()), | ||||||
|     ); |     ); | ||||||
|     return this._broadcastSocketData( |     return this._broadcastSocketData( | ||||||
|       data as typeof data & { _brand: "socketUpdateData" }, |       data as typeof data & { _brand: "socketUpdateData" }, | ||||||
| @@ -1063,7 +1057,7 @@ export class App extends React.Component<any, AppState> { | |||||||
|         ? ELEMENT_SHIFT_TRANSLATE_AMOUNT |         ? ELEMENT_SHIFT_TRANSLATE_AMOUNT | ||||||
|         : ELEMENT_TRANSLATE_AMOUNT; |         : ELEMENT_TRANSLATE_AMOUNT; | ||||||
|       globalSceneState.replaceAllElements( |       globalSceneState.replaceAllElements( | ||||||
|         globalSceneState.getAllElements().map((el) => { |         globalSceneState.getElementsIncludingDeleted().map((el) => { | ||||||
|           if (this.state.selectedElementIds[el.id]) { |           if (this.state.selectedElementIds[el.id]) { | ||||||
|             const update: { x?: number; y?: number } = {}; |             const update: { x?: number; y?: number } = {}; | ||||||
|             if (event.key === KEYS.ARROW_LEFT) { |             if (event.key === KEYS.ARROW_LEFT) { | ||||||
| @@ -1083,7 +1077,7 @@ export class App extends React.Component<any, AppState> { | |||||||
|       event.preventDefault(); |       event.preventDefault(); | ||||||
|     } else if (event.key === KEYS.ENTER) { |     } else if (event.key === KEYS.ENTER) { | ||||||
|       const selectedElements = getSelectedElements( |       const selectedElements = getSelectedElements( | ||||||
|         globalSceneState.getAllElements(), |         globalSceneState.getElements(), | ||||||
|         this.state, |         this.state, | ||||||
|       ); |       ); | ||||||
|  |  | ||||||
| @@ -1188,7 +1182,7 @@ export class App extends React.Component<any, AppState> { | |||||||
|  |  | ||||||
|     const deleteElement = () => { |     const deleteElement = () => { | ||||||
|       globalSceneState.replaceAllElements([ |       globalSceneState.replaceAllElements([ | ||||||
|         ...globalSceneState.getAllElements().map((_element) => { |         ...globalSceneState.getElementsIncludingDeleted().map((_element) => { | ||||||
|           if (_element.id === element.id) { |           if (_element.id === element.id) { | ||||||
|             return newElementWith(_element, { isDeleted: true }); |             return newElementWith(_element, { isDeleted: true }); | ||||||
|           } |           } | ||||||
| @@ -1199,7 +1193,7 @@ export class App extends React.Component<any, AppState> { | |||||||
|  |  | ||||||
|     const updateElement = (text: string) => { |     const updateElement = (text: string) => { | ||||||
|       globalSceneState.replaceAllElements([ |       globalSceneState.replaceAllElements([ | ||||||
|         ...globalSceneState.getAllElements().map((_element) => { |         ...globalSceneState.getElementsIncludingDeleted().map((_element) => { | ||||||
|           if (_element.id === element.id) { |           if (_element.id === element.id) { | ||||||
|             return newTextElement({ |             return newTextElement({ | ||||||
|               ...(_element as ExcalidrawTextElement), |               ...(_element as ExcalidrawTextElement), | ||||||
| @@ -1271,7 +1265,7 @@ export class App extends React.Component<any, AppState> { | |||||||
|     centerIfPossible?: boolean; |     centerIfPossible?: boolean; | ||||||
|   }) => { |   }) => { | ||||||
|     const elementAtPosition = getElementAtPosition( |     const elementAtPosition = getElementAtPosition( | ||||||
|       globalSceneState.getAllElements(), |       globalSceneState.getElements(), | ||||||
|       this.state, |       this.state, | ||||||
|       x, |       x, | ||||||
|       y, |       y, | ||||||
| @@ -1326,7 +1320,7 @@ export class App extends React.Component<any, AppState> { | |||||||
|       }); |       }); | ||||||
|     } else { |     } else { | ||||||
|       globalSceneState.replaceAllElements([ |       globalSceneState.replaceAllElements([ | ||||||
|         ...globalSceneState.getAllElements(), |         ...globalSceneState.getElementsIncludingDeleted(), | ||||||
|         element, |         element, | ||||||
|       ]); |       ]); | ||||||
|  |  | ||||||
| @@ -1503,13 +1497,12 @@ export class App extends React.Component<any, AppState> { | |||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const selectedElements = getSelectedElements( |     const elements = globalSceneState.getElements(); | ||||||
|       globalSceneState.getAllElements(), |  | ||||||
|       this.state, |     const selectedElements = getSelectedElements(elements, this.state); | ||||||
|     ); |  | ||||||
|     if (selectedElements.length === 1 && !isOverScrollBar) { |     if (selectedElements.length === 1 && !isOverScrollBar) { | ||||||
|       const elementWithResizeHandler = getElementWithResizeHandler( |       const elementWithResizeHandler = getElementWithResizeHandler( | ||||||
|         globalSceneState.getAllElements(), |         elements, | ||||||
|         this.state, |         this.state, | ||||||
|         { x, y }, |         { x, y }, | ||||||
|         this.state.zoom, |         this.state.zoom, | ||||||
| @@ -1538,7 +1531,7 @@ export class App extends React.Component<any, AppState> { | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     const hitElement = getElementAtPosition( |     const hitElement = getElementAtPosition( | ||||||
|       globalSceneState.getAllElements(), |       elements, | ||||||
|       this.state, |       this.state, | ||||||
|       x, |       x, | ||||||
|       y, |       y, | ||||||
| @@ -1737,13 +1730,11 @@ export class App extends React.Component<any, AppState> { | |||||||
|     let hitElement: ExcalidrawElement | null = null; |     let hitElement: ExcalidrawElement | null = null; | ||||||
|     let hitElementWasAddedToSelection = false; |     let hitElementWasAddedToSelection = false; | ||||||
|     if (this.state.elementType === "selection") { |     if (this.state.elementType === "selection") { | ||||||
|       const selectedElements = getSelectedElements( |       const elements = globalSceneState.getElements(); | ||||||
|         globalSceneState.getAllElements(), |       const selectedElements = getSelectedElements(elements, this.state); | ||||||
|         this.state, |  | ||||||
|       ); |  | ||||||
|       if (selectedElements.length === 1) { |       if (selectedElements.length === 1) { | ||||||
|         const elementWithResizeHandler = getElementWithResizeHandler( |         const elementWithResizeHandler = getElementWithResizeHandler( | ||||||
|           globalSceneState.getAllElements(), |           elements, | ||||||
|           this.state, |           this.state, | ||||||
|           { x, y }, |           { x, y }, | ||||||
|           this.state.zoom, |           this.state.zoom, | ||||||
| @@ -1781,7 +1772,7 @@ export class App extends React.Component<any, AppState> { | |||||||
|       } |       } | ||||||
|       if (!isResizingElements) { |       if (!isResizingElements) { | ||||||
|         hitElement = getElementAtPosition( |         hitElement = getElementAtPosition( | ||||||
|           globalSceneState.getAllElements(), |           elements, | ||||||
|           this.state, |           this.state, | ||||||
|           x, |           x, | ||||||
|           y, |           y, | ||||||
| @@ -1809,7 +1800,7 @@ export class App extends React.Component<any, AppState> { | |||||||
|               }, |               }, | ||||||
|             })); |             })); | ||||||
|             globalSceneState.replaceAllElements( |             globalSceneState.replaceAllElements( | ||||||
|               globalSceneState.getAllElements(), |               globalSceneState.getElementsIncludingDeleted(), | ||||||
|             ); |             ); | ||||||
|             hitElementWasAddedToSelection = true; |             hitElementWasAddedToSelection = true; | ||||||
|           } |           } | ||||||
| @@ -1820,7 +1811,7 @@ export class App extends React.Component<any, AppState> { | |||||||
|             // put the duplicates where the selected elements used to be. |             // put the duplicates where the selected elements used to be. | ||||||
|             const nextElements = []; |             const nextElements = []; | ||||||
|             const elementsToAppend = []; |             const elementsToAppend = []; | ||||||
|             for (const element of globalSceneState.getAllElements()) { |             for (const element of globalSceneState.getElementsIncludingDeleted()) { | ||||||
|               if ( |               if ( | ||||||
|                 this.state.selectedElementIds[element.id] || |                 this.state.selectedElementIds[element.id] || | ||||||
|                 (element.id === hitElement.id && hitElementWasAddedToSelection) |                 (element.id === hitElement.id && hitElementWasAddedToSelection) | ||||||
| @@ -1930,7 +1921,7 @@ export class App extends React.Component<any, AppState> { | |||||||
|           points: [...element.points, [0, 0]], |           points: [...element.points, [0, 0]], | ||||||
|         }); |         }); | ||||||
|         globalSceneState.replaceAllElements([ |         globalSceneState.replaceAllElements([ | ||||||
|           ...globalSceneState.getAllElements(), |           ...globalSceneState.getElementsIncludingDeleted(), | ||||||
|           element, |           element, | ||||||
|         ]); |         ]); | ||||||
|         this.setState({ |         this.setState({ | ||||||
| @@ -1958,7 +1949,7 @@ export class App extends React.Component<any, AppState> { | |||||||
|         }); |         }); | ||||||
|       } else { |       } else { | ||||||
|         globalSceneState.replaceAllElements([ |         globalSceneState.replaceAllElements([ | ||||||
|           ...globalSceneState.getAllElements(), |           ...globalSceneState.getElementsIncludingDeleted(), | ||||||
|           element, |           element, | ||||||
|         ]); |         ]); | ||||||
|         this.setState({ |         this.setState({ | ||||||
| @@ -2047,7 +2038,7 @@ export class App extends React.Component<any, AppState> { | |||||||
|         // if elements should be deselected on pointerup |         // if elements should be deselected on pointerup | ||||||
|         draggingOccurred = true; |         draggingOccurred = true; | ||||||
|         const selectedElements = getSelectedElements( |         const selectedElements = getSelectedElements( | ||||||
|           globalSceneState.getAllElements(), |           globalSceneState.getElements(), | ||||||
|           this.state, |           this.state, | ||||||
|         ); |         ); | ||||||
|         if (selectedElements.length > 0) { |         if (selectedElements.length > 0) { | ||||||
| @@ -2123,14 +2114,12 @@ export class App extends React.Component<any, AppState> { | |||||||
|       } |       } | ||||||
|  |  | ||||||
|       if (this.state.elementType === "selection") { |       if (this.state.elementType === "selection") { | ||||||
|         if ( |         const elements = globalSceneState.getElements(); | ||||||
|           !event.shiftKey && |         if (!event.shiftKey && isSomeElementSelected(elements, this.state)) { | ||||||
|           isSomeElementSelected(globalSceneState.getAllElements(), this.state) |  | ||||||
|         ) { |  | ||||||
|           this.setState({ selectedElementIds: {} }); |           this.setState({ selectedElementIds: {} }); | ||||||
|         } |         } | ||||||
|         const elementsWithinSelection = getElementsWithinSelection( |         const elementsWithinSelection = getElementsWithinSelection( | ||||||
|           globalSceneState.getAllElements(), |           elements, | ||||||
|           draggingElement, |           draggingElement, | ||||||
|         ); |         ); | ||||||
|         this.setState((prevState) => ({ |         this.setState((prevState) => ({ | ||||||
| @@ -2223,7 +2212,7 @@ export class App extends React.Component<any, AppState> { | |||||||
|       ) { |       ) { | ||||||
|         // remove invisible element which was added in onPointerDown |         // remove invisible element which was added in onPointerDown | ||||||
|         globalSceneState.replaceAllElements( |         globalSceneState.replaceAllElements( | ||||||
|           globalSceneState.getAllElements().slice(0, -1), |           globalSceneState.getElementsIncludingDeleted().slice(0, -1), | ||||||
|         ); |         ); | ||||||
|         this.setState({ |         this.setState({ | ||||||
|           draggingElement: null, |           draggingElement: null, | ||||||
| @@ -2240,7 +2229,7 @@ export class App extends React.Component<any, AppState> { | |||||||
|       if (resizingElement && isInvisiblySmallElement(resizingElement)) { |       if (resizingElement && isInvisiblySmallElement(resizingElement)) { | ||||||
|         globalSceneState.replaceAllElements( |         globalSceneState.replaceAllElements( | ||||||
|           globalSceneState |           globalSceneState | ||||||
|             .getAllElements() |             .getElementsIncludingDeleted() | ||||||
|             .filter((el) => el.id !== resizingElement.id), |             .filter((el) => el.id !== resizingElement.id), | ||||||
|         ); |         ); | ||||||
|       } |       } | ||||||
| @@ -2285,7 +2274,7 @@ export class App extends React.Component<any, AppState> { | |||||||
|  |  | ||||||
|       if ( |       if ( | ||||||
|         elementType !== "selection" || |         elementType !== "selection" || | ||||||
|         isSomeElementSelected(globalSceneState.getAllElements(), this.state) |         isSomeElementSelected(globalSceneState.getElements(), this.state) | ||||||
|       ) { |       ) { | ||||||
|         history.resumeRecording(); |         history.resumeRecording(); | ||||||
|       } |       } | ||||||
| @@ -2366,8 +2355,9 @@ export class App extends React.Component<any, AppState> { | |||||||
|       window.devicePixelRatio, |       window.devicePixelRatio, | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|  |     const elements = globalSceneState.getElements(); | ||||||
|     const element = getElementAtPosition( |     const element = getElementAtPosition( | ||||||
|       globalSceneState.getAllElements(), |       elements, | ||||||
|       this.state, |       this.state, | ||||||
|       x, |       x, | ||||||
|       y, |       y, | ||||||
| @@ -2381,12 +2371,12 @@ export class App extends React.Component<any, AppState> { | |||||||
|             action: () => this.pasteFromClipboard(null), |             action: () => this.pasteFromClipboard(null), | ||||||
|           }, |           }, | ||||||
|           probablySupportsClipboardBlob && |           probablySupportsClipboardBlob && | ||||||
|             hasNonDeletedElements(globalSceneState.getAllElements()) && { |             elements.length > 0 && { | ||||||
|               label: t("labels.copyAsPng"), |               label: t("labels.copyAsPng"), | ||||||
|               action: this.copyToClipboardAsPng, |               action: this.copyToClipboardAsPng, | ||||||
|             }, |             }, | ||||||
|           probablySupportsClipboardWriteText && |           probablySupportsClipboardWriteText && | ||||||
|             hasNonDeletedElements(globalSceneState.getAllElements()) && { |             elements.length > 0 && { | ||||||
|               label: t("labels.copyAsSvg"), |               label: t("labels.copyAsSvg"), | ||||||
|               action: this.copyToClipboardAsSvg, |               action: this.copyToClipboardAsSvg, | ||||||
|             }, |             }, | ||||||
| @@ -2468,7 +2458,7 @@ export class App extends React.Component<any, AppState> { | |||||||
|     scale: number, |     scale: number, | ||||||
|   ) { |   ) { | ||||||
|     const elementClickedInside = getElementContainingPosition( |     const elementClickedInside = getElementContainingPosition( | ||||||
|       globalSceneState.getAllElements(), |       globalSceneState.getElementsIncludingDeleted(), | ||||||
|       x, |       x, | ||||||
|       y, |       y, | ||||||
|     ); |     ); | ||||||
| @@ -2522,7 +2512,10 @@ export class App extends React.Component<any, AppState> { | |||||||
|   }, 300); |   }, 300); | ||||||
|  |  | ||||||
|   private saveDebounced = debounce(() => { |   private saveDebounced = debounce(() => { | ||||||
|     saveToLocalStorage(globalSceneState.getAllElements(), this.state); |     saveToLocalStorage( | ||||||
|  |       globalSceneState.getElementsIncludingDeleted(), | ||||||
|  |       this.state, | ||||||
|  |     ); | ||||||
|   }, 300); |   }, 300); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -2548,7 +2541,7 @@ if (process.env.NODE_ENV === "test" || process.env.NODE_ENV === "development") { | |||||||
|   Object.defineProperties(window.h, { |   Object.defineProperties(window.h, { | ||||||
|     elements: { |     elements: { | ||||||
|       get() { |       get() { | ||||||
|         return globalSceneState.getAllElements(); |         return globalSceneState.getElementsIncludingDeleted(); | ||||||
|       }, |       }, | ||||||
|       set(elements: ExcalidrawElement[]) { |       set(elements: ExcalidrawElement[]) { | ||||||
|         return globalSceneState.replaceAllElements(elements); |         return globalSceneState.replaceAllElements(elements); | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ import React, { useState, useEffect, useRef } from "react"; | |||||||
|  |  | ||||||
| import { ToolButton } from "./ToolButton"; | import { ToolButton } from "./ToolButton"; | ||||||
| import { clipboard, exportFile, link } from "./icons"; | import { clipboard, exportFile, link } from "./icons"; | ||||||
| import { ExcalidrawElement } from "../element/types"; | import { NonDeletedExcalidrawElement } from "../element/types"; | ||||||
| import { AppState } from "../types"; | import { AppState } from "../types"; | ||||||
| import { exportToCanvas } from "../scene/export"; | import { exportToCanvas } from "../scene/export"; | ||||||
| import { ActionsManagerInterface } from "../actions/types"; | import { ActionsManagerInterface } from "../actions/types"; | ||||||
| @@ -20,7 +20,7 @@ const scales = [1, 2, 3]; | |||||||
| const defaultScale = scales.includes(devicePixelRatio) ? devicePixelRatio : 1; | const defaultScale = scales.includes(devicePixelRatio) ? devicePixelRatio : 1; | ||||||
|  |  | ||||||
| export type ExportCB = ( | export type ExportCB = ( | ||||||
|   elements: readonly ExcalidrawElement[], |   elements: readonly NonDeletedExcalidrawElement[], | ||||||
|   scale?: number, |   scale?: number, | ||||||
| ) => void; | ) => void; | ||||||
|  |  | ||||||
| @@ -35,7 +35,7 @@ function ExportModal({ | |||||||
|   onExportToBackend, |   onExportToBackend, | ||||||
| }: { | }: { | ||||||
|   appState: AppState; |   appState: AppState; | ||||||
|   elements: readonly ExcalidrawElement[]; |   elements: readonly NonDeletedExcalidrawElement[]; | ||||||
|   exportPadding?: number; |   exportPadding?: number; | ||||||
|   actionManager: ActionsManagerInterface; |   actionManager: ActionsManagerInterface; | ||||||
|   onExportToPng: ExportCB; |   onExportToPng: ExportCB; | ||||||
| @@ -166,7 +166,7 @@ export function ExportDialog({ | |||||||
|   onExportToBackend, |   onExportToBackend, | ||||||
| }: { | }: { | ||||||
|   appState: AppState; |   appState: AppState; | ||||||
|   elements: readonly ExcalidrawElement[]; |   elements: readonly NonDeletedExcalidrawElement[]; | ||||||
|   exportPadding?: number; |   exportPadding?: number; | ||||||
|   actionManager: ActionsManagerInterface; |   actionManager: ActionsManagerInterface; | ||||||
|   onExportToPng: ExportCB; |   onExportToPng: ExportCB; | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| import React from "react"; | import React from "react"; | ||||||
| import { t } from "../i18n"; | import { t } from "../i18n"; | ||||||
| import { ExcalidrawElement } from "../element/types"; | import { NonDeletedExcalidrawElement } from "../element/types"; | ||||||
| import { getSelectedElements } from "../scene"; | import { getSelectedElements } from "../scene"; | ||||||
|  |  | ||||||
| import "./HintViewer.scss"; | import "./HintViewer.scss"; | ||||||
| @@ -9,7 +9,7 @@ import { isLinearElement } from "../element/typeChecks"; | |||||||
|  |  | ||||||
| interface Hint { | interface Hint { | ||||||
|   appState: AppState; |   appState: AppState; | ||||||
|   elements: readonly ExcalidrawElement[]; |   elements: readonly NonDeletedExcalidrawElement[]; | ||||||
| } | } | ||||||
|  |  | ||||||
| const getHints = ({ appState, elements }: Hint) => { | const getHints = ({ appState, elements }: Hint) => { | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ import { calculateScrollCenter } from "../scene"; | |||||||
| import { exportCanvas } from "../data"; | import { exportCanvas } from "../data"; | ||||||
|  |  | ||||||
| import { AppState } from "../types"; | import { AppState } from "../types"; | ||||||
| import { ExcalidrawElement } from "../element/types"; | import { NonDeletedExcalidrawElement } from "../element/types"; | ||||||
|  |  | ||||||
| import { ActionManager } from "../actions/manager"; | import { ActionManager } from "../actions/manager"; | ||||||
| import { Island } from "./Island"; | import { Island } from "./Island"; | ||||||
| @@ -31,8 +31,7 @@ interface LayerUIProps { | |||||||
|   appState: AppState; |   appState: AppState; | ||||||
|   canvas: HTMLCanvasElement | null; |   canvas: HTMLCanvasElement | null; | ||||||
|   setAppState: any; |   setAppState: any; | ||||||
|   elements: readonly ExcalidrawElement[]; |   elements: readonly NonDeletedExcalidrawElement[]; | ||||||
|   setElements: (elements: readonly ExcalidrawElement[]) => void; |  | ||||||
|   onRoomCreate: () => void; |   onRoomCreate: () => void; | ||||||
|   onRoomDestroy: () => void; |   onRoomDestroy: () => void; | ||||||
|   onLockToggle: () => void; |   onLockToggle: () => void; | ||||||
| @@ -45,7 +44,6 @@ export const LayerUI = React.memo( | |||||||
|     setAppState, |     setAppState, | ||||||
|     canvas, |     canvas, | ||||||
|     elements, |     elements, | ||||||
|     setElements, |  | ||||||
|     onRoomCreate, |     onRoomCreate, | ||||||
|     onRoomDestroy, |     onRoomDestroy, | ||||||
|     onLockToggle, |     onLockToggle, | ||||||
| @@ -96,7 +94,6 @@ export const LayerUI = React.memo( | |||||||
|       <MobileMenu |       <MobileMenu | ||||||
|         appState={appState} |         appState={appState} | ||||||
|         elements={elements} |         elements={elements} | ||||||
|         setElements={setElements} |  | ||||||
|         actionManager={actionManager} |         actionManager={actionManager} | ||||||
|         exportButton={renderExportDialog()} |         exportButton={renderExportDialog()} | ||||||
|         setAppState={setAppState} |         setAppState={setAppState} | ||||||
| @@ -170,8 +167,6 @@ export const LayerUI = React.memo( | |||||||
|                         <ShapesSwitcher |                         <ShapesSwitcher | ||||||
|                           elementType={appState.elementType} |                           elementType={appState.elementType} | ||||||
|                           setAppState={setAppState} |                           setAppState={setAppState} | ||||||
|                           setElements={setElements} |  | ||||||
|                           elements={elements} |  | ||||||
|                         /> |                         /> | ||||||
|                       </Stack.Row> |                       </Stack.Row> | ||||||
|                     </Island> |                     </Island> | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ import { t, setLanguage } from "../i18n"; | |||||||
| import Stack from "./Stack"; | import Stack from "./Stack"; | ||||||
| import { LanguageList } from "./LanguageList"; | import { LanguageList } from "./LanguageList"; | ||||||
| import { showSelectedShapeActions } from "../element"; | import { showSelectedShapeActions } from "../element"; | ||||||
| import { ExcalidrawElement } from "../element/types"; | import { NonDeletedExcalidrawElement } from "../element/types"; | ||||||
| import { FixedSideContainer } from "./FixedSideContainer"; | import { FixedSideContainer } from "./FixedSideContainer"; | ||||||
| import { Island } from "./Island"; | import { Island } from "./Island"; | ||||||
| import { HintViewer } from "./HintViewer"; | import { HintViewer } from "./HintViewer"; | ||||||
| @@ -22,8 +22,7 @@ type MobileMenuProps = { | |||||||
|   actionManager: ActionManager; |   actionManager: ActionManager; | ||||||
|   exportButton: React.ReactNode; |   exportButton: React.ReactNode; | ||||||
|   setAppState: any; |   setAppState: any; | ||||||
|   elements: readonly ExcalidrawElement[]; |   elements: readonly NonDeletedExcalidrawElement[]; | ||||||
|   setElements: any; |  | ||||||
|   onRoomCreate: () => void; |   onRoomCreate: () => void; | ||||||
|   onRoomDestroy: () => void; |   onRoomDestroy: () => void; | ||||||
|   onLockToggle: () => void; |   onLockToggle: () => void; | ||||||
| @@ -32,7 +31,6 @@ type MobileMenuProps = { | |||||||
| export function MobileMenu({ | export function MobileMenu({ | ||||||
|   appState, |   appState, | ||||||
|   elements, |   elements, | ||||||
|   setElements, |  | ||||||
|   actionManager, |   actionManager, | ||||||
|   exportButton, |   exportButton, | ||||||
|   setAppState, |   setAppState, | ||||||
| @@ -54,8 +52,6 @@ export function MobileMenu({ | |||||||
|                     <ShapesSwitcher |                     <ShapesSwitcher | ||||||
|                       elementType={appState.elementType} |                       elementType={appState.elementType} | ||||||
|                       setAppState={setAppState} |                       setAppState={setAppState} | ||||||
|                       setElements={setElements} |  | ||||||
|                       elements={elements} |  | ||||||
|                     /> |                     /> | ||||||
|                   </Stack.Row> |                   </Stack.Row> | ||||||
|                 </Island> |                 </Island> | ||||||
|   | |||||||
| @@ -1,4 +1,7 @@ | |||||||
| import { ExcalidrawElement } from "../element/types"; | import { | ||||||
|  |   ExcalidrawElement, | ||||||
|  |   NonDeletedExcalidrawElement, | ||||||
|  | } from "../element/types"; | ||||||
|  |  | ||||||
| import { getDefaultAppState } from "../appState"; | import { getDefaultAppState } from "../appState"; | ||||||
|  |  | ||||||
| @@ -16,7 +19,6 @@ import { serializeAsJSON } from "./json"; | |||||||
| import { ExportType } from "../scene/types"; | import { ExportType } from "../scene/types"; | ||||||
| import { restore } from "./restore"; | import { restore } from "./restore"; | ||||||
| import { restoreFromLocalStorage } from "./localStorage"; | import { restoreFromLocalStorage } from "./localStorage"; | ||||||
| import { hasNonDeletedElements } from "../element"; |  | ||||||
|  |  | ||||||
| export { loadFromBlob } from "./blob"; | export { loadFromBlob } from "./blob"; | ||||||
| export { saveAsJSON, loadFromJSON } from "./json"; | export { saveAsJSON, loadFromJSON } from "./json"; | ||||||
| @@ -283,7 +285,7 @@ export async function importFromBackend( | |||||||
|  |  | ||||||
| export async function exportCanvas( | export async function exportCanvas( | ||||||
|   type: ExportType, |   type: ExportType, | ||||||
|   elements: readonly ExcalidrawElement[], |   elements: readonly NonDeletedExcalidrawElement[], | ||||||
|   appState: AppState, |   appState: AppState, | ||||||
|   canvas: HTMLCanvasElement, |   canvas: HTMLCanvasElement, | ||||||
|   { |   { | ||||||
| @@ -300,7 +302,7 @@ export async function exportCanvas( | |||||||
|     scale?: number; |     scale?: number; | ||||||
|   }, |   }, | ||||||
| ) { | ) { | ||||||
|   if (!hasNonDeletedElements(elements)) { |   if (elements.length === 0) { | ||||||
|     return window.alert(t("alerts.cannotExportEmptyCanvas")); |     return window.alert(t("alerts.cannotExportEmptyCanvas")); | ||||||
|   } |   } | ||||||
|   if (type === "svg" || type === "clipboard-svg") { |   if (type === "svg" || type === "clipboard-svg") { | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| import { distanceBetweenPointAndSegment } from "../math"; | import { distanceBetweenPointAndSegment } from "../math"; | ||||||
|  |  | ||||||
| import { ExcalidrawElement } from "./types"; | import { NonDeletedExcalidrawElement } from "./types"; | ||||||
|  |  | ||||||
| import { getDiamondPoints, getElementAbsoluteCoords } from "./bounds"; | import { getDiamondPoints, getElementAbsoluteCoords } from "./bounds"; | ||||||
| import { Point } from "../types"; | import { Point } from "../types"; | ||||||
| @@ -11,7 +11,7 @@ import { isLinearElement } from "./typeChecks"; | |||||||
| import { rotate } from "../math"; | import { rotate } from "../math"; | ||||||
|  |  | ||||||
| function isElementDraggableFromInside( | function isElementDraggableFromInside( | ||||||
|   element: ExcalidrawElement, |   element: NonDeletedExcalidrawElement, | ||||||
|   appState: AppState, |   appState: AppState, | ||||||
| ): boolean { | ): boolean { | ||||||
|   return ( |   return ( | ||||||
| @@ -21,7 +21,7 @@ function isElementDraggableFromInside( | |||||||
| } | } | ||||||
|  |  | ||||||
| export function hitTest( | export function hitTest( | ||||||
|   element: ExcalidrawElement, |   element: NonDeletedExcalidrawElement, | ||||||
|   appState: AppState, |   appState: AppState, | ||||||
|   x: number, |   x: number, | ||||||
|   y: number, |   y: number, | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| import { ExcalidrawElement } from "./types"; | import { ExcalidrawElement, NonDeletedExcalidrawElement } from "./types"; | ||||||
| import { isInvisiblySmallElement } from "./sizeHelpers"; | import { isInvisiblySmallElement } from "./sizeHelpers"; | ||||||
|  |  | ||||||
| export { | export { | ||||||
| @@ -63,6 +63,9 @@ export function getDrawingVersion(elements: readonly ExcalidrawElement[]) { | |||||||
|   return elements.reduce((acc, el) => acc + el.version, 0); |   return elements.reduce((acc, el) => acc + el.version, 0); | ||||||
| } | } | ||||||
|  |  | ||||||
| export function hasNonDeletedElements(elements: readonly ExcalidrawElement[]) { | export function getNonDeletedElements(elements: readonly ExcalidrawElement[]) { | ||||||
|   return elements.some((element) => !element.isDeleted); |   return ( | ||||||
|  |     elements.filter((element) => !element.isDeleted) as | ||||||
|  |     readonly NonDeletedExcalidrawElement[] | ||||||
|  |   ); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ import { | |||||||
|   ExcalidrawTextElement, |   ExcalidrawTextElement, | ||||||
|   ExcalidrawLinearElement, |   ExcalidrawLinearElement, | ||||||
|   ExcalidrawGenericElement, |   ExcalidrawGenericElement, | ||||||
|  |   NonDeleted, | ||||||
| } from "../element/types"; | } from "../element/types"; | ||||||
| import { measureText } from "../utils"; | import { measureText } from "../utils"; | ||||||
| import { randomInteger, randomId } from "../random"; | import { randomInteger, randomId } from "../random"; | ||||||
| @@ -56,7 +57,7 @@ function _newElementBase<T extends ExcalidrawElement>( | |||||||
|     seed: rest.seed ?? randomInteger(), |     seed: rest.seed ?? randomInteger(), | ||||||
|     version: rest.version || 1, |     version: rest.version || 1, | ||||||
|     versionNonce: rest.versionNonce ?? 0, |     versionNonce: rest.versionNonce ?? 0, | ||||||
|     isDeleted: rest.isDeleted ?? false, |     isDeleted: false as false, | ||||||
|   }; |   }; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -64,7 +65,7 @@ export function newElement( | |||||||
|   opts: { |   opts: { | ||||||
|     type: ExcalidrawGenericElement["type"]; |     type: ExcalidrawGenericElement["type"]; | ||||||
|   } & ElementConstructorOpts, |   } & ElementConstructorOpts, | ||||||
| ): ExcalidrawGenericElement { | ): NonDeleted<ExcalidrawGenericElement> { | ||||||
|   return _newElementBase<ExcalidrawGenericElement>(opts.type, opts); |   return _newElementBase<ExcalidrawGenericElement>(opts.type, opts); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -73,13 +74,12 @@ export function newTextElement( | |||||||
|     text: string; |     text: string; | ||||||
|     font: string; |     font: string; | ||||||
|   } & ElementConstructorOpts, |   } & ElementConstructorOpts, | ||||||
| ): ExcalidrawTextElement { | ): NonDeleted<ExcalidrawTextElement> { | ||||||
|   const { text, font } = opts; |   const { text, font } = opts; | ||||||
|   const metrics = measureText(text, font); |   const metrics = measureText(text, font); | ||||||
|   const textElement = newElementWith( |   const textElement = newElementWith( | ||||||
|     { |     { | ||||||
|       ..._newElementBase<ExcalidrawTextElement>("text", opts), |       ..._newElementBase<ExcalidrawTextElement>("text", opts), | ||||||
|       isDeleted: false, |  | ||||||
|       text: text, |       text: text, | ||||||
|       font: font, |       font: font, | ||||||
|       // Center the text |       // Center the text | ||||||
| @@ -100,7 +100,7 @@ export function newLinearElement( | |||||||
|     type: ExcalidrawLinearElement["type"]; |     type: ExcalidrawLinearElement["type"]; | ||||||
|     lastCommittedPoint?: ExcalidrawLinearElement["lastCommittedPoint"]; |     lastCommittedPoint?: ExcalidrawLinearElement["lastCommittedPoint"]; | ||||||
|   } & ElementConstructorOpts, |   } & ElementConstructorOpts, | ||||||
| ): ExcalidrawLinearElement { | ): NonDeleted<ExcalidrawLinearElement> { | ||||||
|   return { |   return { | ||||||
|     ..._newElementBase<ExcalidrawLinearElement>(opts.type, opts), |     ..._newElementBase<ExcalidrawLinearElement>(opts.type, opts), | ||||||
|     points: [], |     points: [], | ||||||
|   | |||||||
| @@ -3,7 +3,11 @@ import { SHIFT_LOCKING_ANGLE } from "../constants"; | |||||||
| import { getSelectedElements, globalSceneState } from "../scene"; | import { getSelectedElements, globalSceneState } from "../scene"; | ||||||
| import { rescalePoints } from "../points"; | import { rescalePoints } from "../points"; | ||||||
| import { rotate, adjustXYWithRotation } from "../math"; | import { rotate, adjustXYWithRotation } from "../math"; | ||||||
| import { ExcalidrawElement, ExcalidrawLinearElement } from "./types"; | import { | ||||||
|  |   ExcalidrawLinearElement, | ||||||
|  |   NonDeletedExcalidrawElement, | ||||||
|  |   NonDeleted, | ||||||
|  | } from "./types"; | ||||||
| import { getElementAbsoluteCoords, getCommonBounds } from "./bounds"; | import { getElementAbsoluteCoords, getCommonBounds } from "./bounds"; | ||||||
| import { isLinearElement } from "./typeChecks"; | import { isLinearElement } from "./typeChecks"; | ||||||
| import { mutateElement } from "./mutateElement"; | import { mutateElement } from "./mutateElement"; | ||||||
| @@ -17,7 +21,7 @@ import { | |||||||
| type ResizeTestType = ReturnType<typeof resizeTest>; | type ResizeTestType = ReturnType<typeof resizeTest>; | ||||||
|  |  | ||||||
| export type ResizeArrowFnType = ( | export type ResizeArrowFnType = ( | ||||||
|   element: ExcalidrawLinearElement, |   element: NonDeleted<ExcalidrawLinearElement>, | ||||||
|   pointIndex: number, |   pointIndex: number, | ||||||
|   deltaX: number, |   deltaX: number, | ||||||
|   deltaY: number, |   deltaY: number, | ||||||
| @@ -27,13 +31,13 @@ export type ResizeArrowFnType = ( | |||||||
| ) => void; | ) => void; | ||||||
|  |  | ||||||
| const arrowResizeOrigin: ResizeArrowFnType = ( | const arrowResizeOrigin: ResizeArrowFnType = ( | ||||||
|   element: ExcalidrawLinearElement, |   element, | ||||||
|   pointIndex: number, |   pointIndex, | ||||||
|   deltaX: number, |   deltaX, | ||||||
|   deltaY: number, |   deltaY, | ||||||
|   pointerX: number, |   pointerX, | ||||||
|   pointerY: number, |   pointerY, | ||||||
|   perfect: boolean, |   perfect, | ||||||
| ) => { | ) => { | ||||||
|   const [px, py] = element.points[pointIndex]; |   const [px, py] = element.points[pointIndex]; | ||||||
|   let x = element.x + deltaX; |   let x = element.x + deltaX; | ||||||
| @@ -63,13 +67,13 @@ const arrowResizeOrigin: ResizeArrowFnType = ( | |||||||
| }; | }; | ||||||
|  |  | ||||||
| const arrowResizeEnd: ResizeArrowFnType = ( | const arrowResizeEnd: ResizeArrowFnType = ( | ||||||
|   element: ExcalidrawLinearElement, |   element, | ||||||
|   pointIndex: number, |   pointIndex, | ||||||
|   deltaX: number, |   deltaX, | ||||||
|   deltaY: number, |   deltaY, | ||||||
|   pointerX: number, |   pointerX, | ||||||
|   pointerY: number, |   pointerY, | ||||||
|   perfect: boolean, |   perfect, | ||||||
| ) => { | ) => { | ||||||
|   const [px, py] = element.points[pointIndex]; |   const [px, py] = element.points[pointIndex]; | ||||||
|   if (perfect) { |   if (perfect) { | ||||||
| @@ -110,7 +114,7 @@ export function resizeElements( | |||||||
|     isRotating: resizeHandle === "rotation", |     isRotating: resizeHandle === "rotation", | ||||||
|   }); |   }); | ||||||
|   const selectedElements = getSelectedElements( |   const selectedElements = getSelectedElements( | ||||||
|     globalSceneState.getAllElements(), |     globalSceneState.getElements(), | ||||||
|     appState, |     appState, | ||||||
|   ); |   ); | ||||||
|   if (selectedElements.length === 1) { |   if (selectedElements.length === 1) { | ||||||
| @@ -451,7 +455,7 @@ export function resizeElements( | |||||||
| } | } | ||||||
|  |  | ||||||
| export function canResizeMutlipleElements( | export function canResizeMutlipleElements( | ||||||
|   elements: readonly ExcalidrawElement[], |   elements: readonly NonDeletedExcalidrawElement[], | ||||||
| ) { | ) { | ||||||
|   return elements.every((element) => |   return elements.every((element) => | ||||||
|     ["rectangle", "diamond", "ellipse"].includes(element.type), |     ["rectangle", "diamond", "ellipse"].includes(element.type), | ||||||
|   | |||||||
| @@ -1,4 +1,8 @@ | |||||||
| import { ExcalidrawElement, PointerType } from "./types"; | import { | ||||||
|  |   ExcalidrawElement, | ||||||
|  |   PointerType, | ||||||
|  |   NonDeletedExcalidrawElement, | ||||||
|  | } from "./types"; | ||||||
|  |  | ||||||
| import { | import { | ||||||
|   OMIT_SIDES_FOR_MULTIPLE_ELEMENTS, |   OMIT_SIDES_FOR_MULTIPLE_ELEMENTS, | ||||||
| @@ -24,7 +28,7 @@ function isInHandlerRect( | |||||||
| } | } | ||||||
|  |  | ||||||
| export function resizeTest( | export function resizeTest( | ||||||
|   element: ExcalidrawElement, |   element: NonDeletedExcalidrawElement, | ||||||
|   appState: AppState, |   appState: AppState, | ||||||
|   x: number, |   x: number, | ||||||
|   y: number, |   y: number, | ||||||
| @@ -66,7 +70,7 @@ export function resizeTest( | |||||||
| } | } | ||||||
|  |  | ||||||
| export function getElementWithResizeHandler( | export function getElementWithResizeHandler( | ||||||
|   elements: readonly ExcalidrawElement[], |   elements: readonly NonDeletedExcalidrawElement[], | ||||||
|   appState: AppState, |   appState: AppState, | ||||||
|   { x, y }: { x: number; y: number }, |   { x, y }: { x: number; y: number }, | ||||||
|   zoom: number, |   zoom: number, | ||||||
| @@ -78,7 +82,7 @@ export function getElementWithResizeHandler( | |||||||
|     } |     } | ||||||
|     const resizeHandle = resizeTest(element, appState, x, y, zoom, pointerType); |     const resizeHandle = resizeTest(element, appState, x, y, zoom, pointerType); | ||||||
|     return resizeHandle ? { element, resizeHandle } : null; |     return resizeHandle ? { element, resizeHandle } : null; | ||||||
|   }, null as { element: ExcalidrawElement; resizeHandle: ReturnType<typeof resizeTest> } | null); |   }, null as { element: NonDeletedExcalidrawElement; resizeHandle: ReturnType<typeof resizeTest> } | null); | ||||||
| } | } | ||||||
|  |  | ||||||
| export function getResizeHandlerFromCoords( | export function getResizeHandlerFromCoords( | ||||||
|   | |||||||
| @@ -1,10 +1,10 @@ | |||||||
| import { AppState } from "../types"; | import { AppState } from "../types"; | ||||||
| import { ExcalidrawElement } from "./types"; | import { NonDeletedExcalidrawElement } from "./types"; | ||||||
| import { getSelectedElements } from "../scene"; | import { getSelectedElements } from "../scene"; | ||||||
|  |  | ||||||
| export const showSelectedShapeActions = ( | export const showSelectedShapeActions = ( | ||||||
|   appState: AppState, |   appState: AppState, | ||||||
|   elements: readonly ExcalidrawElement[], |   elements: readonly NonDeletedExcalidrawElement[], | ||||||
| ) => | ) => | ||||||
|   Boolean( |   Boolean( | ||||||
|     appState.editingElement || |     appState.editingElement || | ||||||
|   | |||||||
| @@ -33,6 +33,12 @@ export type ExcalidrawElement = | |||||||
|   | ExcalidrawTextElement |   | ExcalidrawTextElement | ||||||
|   | ExcalidrawLinearElement; |   | ExcalidrawLinearElement; | ||||||
|  |  | ||||||
|  | export type NonDeleted<TElement extends ExcalidrawElement> = TElement & { | ||||||
|  |   isDeleted: false; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export type NonDeletedExcalidrawElement = NonDeleted<ExcalidrawElement>; | ||||||
|  |  | ||||||
| export type ExcalidrawTextElement = _ExcalidrawElementBase & | export type ExcalidrawTextElement = _ExcalidrawElementBase & | ||||||
|   Readonly<{ |   Readonly<{ | ||||||
|     type: "text"; |     type: "text"; | ||||||
|   | |||||||
| @@ -1,4 +1,8 @@ | |||||||
| import { ExcalidrawElement, ExcalidrawTextElement } from "../element/types"; | import { | ||||||
|  |   ExcalidrawElement, | ||||||
|  |   ExcalidrawTextElement, | ||||||
|  |   NonDeletedExcalidrawElement, | ||||||
|  | } from "../element/types"; | ||||||
| import { isTextElement } from "../element/typeChecks"; | import { isTextElement } from "../element/typeChecks"; | ||||||
| import { | import { | ||||||
|   getDiamondPoints, |   getDiamondPoints, | ||||||
| @@ -24,7 +28,7 @@ export interface ExcalidrawElementWithCanvas { | |||||||
| } | } | ||||||
|  |  | ||||||
| function generateElementCanvas( | function generateElementCanvas( | ||||||
|   element: ExcalidrawElement, |   element: NonDeletedExcalidrawElement, | ||||||
|   zoom: number, |   zoom: number, | ||||||
| ): ExcalidrawElementWithCanvas { | ): ExcalidrawElementWithCanvas { | ||||||
|   const canvas = document.createElement("canvas"); |   const canvas = document.createElement("canvas"); | ||||||
| @@ -72,7 +76,7 @@ function generateElementCanvas( | |||||||
| } | } | ||||||
|  |  | ||||||
| function drawElementOnCanvas( | function drawElementOnCanvas( | ||||||
|   element: ExcalidrawElement, |   element: NonDeletedExcalidrawElement, | ||||||
|   rc: RoughCanvas, |   rc: RoughCanvas, | ||||||
|   context: CanvasRenderingContext2D, |   context: CanvasRenderingContext2D, | ||||||
| ) { | ) { | ||||||
| @@ -133,7 +137,7 @@ export function invalidateShapeForElement(element: ExcalidrawElement) { | |||||||
| } | } | ||||||
|  |  | ||||||
| function generateElement( | function generateElement( | ||||||
|   element: ExcalidrawElement, |   element: NonDeletedExcalidrawElement, | ||||||
|   generator: RoughGenerator, |   generator: RoughGenerator, | ||||||
|   sceneState?: SceneState, |   sceneState?: SceneState, | ||||||
| ) { | ) { | ||||||
| @@ -285,7 +289,7 @@ function drawElementFromCanvas( | |||||||
| } | } | ||||||
|  |  | ||||||
| export function renderElement( | export function renderElement( | ||||||
|   element: ExcalidrawElement, |   element: NonDeletedExcalidrawElement, | ||||||
|   rc: RoughCanvas, |   rc: RoughCanvas, | ||||||
|   context: CanvasRenderingContext2D, |   context: CanvasRenderingContext2D, | ||||||
|   renderOptimizations: boolean, |   renderOptimizations: boolean, | ||||||
| @@ -342,7 +346,7 @@ export function renderElement( | |||||||
| } | } | ||||||
|  |  | ||||||
| export function renderElementToSvg( | export function renderElementToSvg( | ||||||
|   element: ExcalidrawElement, |   element: NonDeletedExcalidrawElement, | ||||||
|   rsvg: RoughSVG, |   rsvg: RoughSVG, | ||||||
|   svgRoot: SVGElement, |   svgRoot: SVGElement, | ||||||
|   offsetX?: number, |   offsetX?: number, | ||||||
|   | |||||||
| @@ -2,7 +2,10 @@ import { RoughCanvas } from "roughjs/bin/canvas"; | |||||||
| import { RoughSVG } from "roughjs/bin/svg"; | import { RoughSVG } from "roughjs/bin/svg"; | ||||||
|  |  | ||||||
| import { FlooredNumber, AppState } from "../types"; | import { FlooredNumber, AppState } from "../types"; | ||||||
| import { ExcalidrawElement } from "../element/types"; | import { | ||||||
|  |   ExcalidrawElement, | ||||||
|  |   NonDeletedExcalidrawElement, | ||||||
|  | } from "../element/types"; | ||||||
| import { | import { | ||||||
|   getElementAbsoluteCoords, |   getElementAbsoluteCoords, | ||||||
|   OMIT_SIDES_FOR_MULTIPLE_ELEMENTS, |   OMIT_SIDES_FOR_MULTIPLE_ELEMENTS, | ||||||
| @@ -74,9 +77,9 @@ function strokeCircle( | |||||||
| } | } | ||||||
|  |  | ||||||
| export function renderScene( | export function renderScene( | ||||||
|   allElements: readonly ExcalidrawElement[], |   elements: readonly NonDeletedExcalidrawElement[], | ||||||
|   appState: AppState, |   appState: AppState, | ||||||
|   selectionElement: ExcalidrawElement | null, |   selectionElement: NonDeletedExcalidrawElement | null, | ||||||
|   scale: number, |   scale: number, | ||||||
|   rc: RoughCanvas, |   rc: RoughCanvas, | ||||||
|   canvas: HTMLCanvasElement, |   canvas: HTMLCanvasElement, | ||||||
| @@ -99,8 +102,6 @@ export function renderScene( | |||||||
|     return { atLeastOneVisibleElement: false }; |     return { atLeastOneVisibleElement: false }; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   const elements = allElements.filter((element) => !element.isDeleted); |  | ||||||
|  |  | ||||||
|   const context = canvas.getContext("2d")!; |   const context = canvas.getContext("2d")!; | ||||||
|   context.scale(scale, scale); |   context.scale(scale, scale); | ||||||
|  |  | ||||||
| @@ -493,7 +494,7 @@ function isVisibleElement( | |||||||
|  |  | ||||||
| // This should be only called for exporting purposes | // This should be only called for exporting purposes | ||||||
| export function renderSceneToSvg( | export function renderSceneToSvg( | ||||||
|   elements: readonly ExcalidrawElement[], |   elements: readonly NonDeletedExcalidrawElement[], | ||||||
|   rsvg: RoughSVG, |   rsvg: RoughSVG, | ||||||
|   svgRoot: SVGElement, |   svgRoot: SVGElement, | ||||||
|   { |   { | ||||||
|   | |||||||
| @@ -1,4 +1,7 @@ | |||||||
| import { ExcalidrawElement } from "../element/types"; | import { | ||||||
|  |   ExcalidrawElement, | ||||||
|  |   NonDeletedExcalidrawElement, | ||||||
|  | } from "../element/types"; | ||||||
|  |  | ||||||
| import { getElementAbsoluteCoords, hitTest } from "../element"; | import { getElementAbsoluteCoords, hitTest } from "../element"; | ||||||
| import { AppState } from "../types"; | import { AppState } from "../types"; | ||||||
| @@ -16,7 +19,7 @@ export const hasStroke = (type: string) => | |||||||
| export const hasText = (type: string) => type === "text"; | export const hasText = (type: string) => type === "text"; | ||||||
|  |  | ||||||
| export function getElementAtPosition( | export function getElementAtPosition( | ||||||
|   elements: readonly ExcalidrawElement[], |   elements: readonly NonDeletedExcalidrawElement[], | ||||||
|   appState: AppState, |   appState: AppState, | ||||||
|   x: number, |   x: number, | ||||||
|   y: number, |   y: number, | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| import rough from "roughjs/bin/rough"; | import rough from "roughjs/bin/rough"; | ||||||
| import { ExcalidrawElement } from "../element/types"; | import { NonDeletedExcalidrawElement } from "../element/types"; | ||||||
| import { getCommonBounds } from "../element/bounds"; | import { getCommonBounds } from "../element/bounds"; | ||||||
| import { renderScene, renderSceneToSvg } from "../renderer/renderScene"; | import { renderScene, renderSceneToSvg } from "../renderer/renderScene"; | ||||||
| import { distance, SVG_NS } from "../utils"; | import { distance, SVG_NS } from "../utils"; | ||||||
| @@ -9,7 +9,7 @@ import { AppState } from "../types"; | |||||||
| export const SVG_EXPORT_TAG = `<!-- svg-source:excalidraw -->`; | export const SVG_EXPORT_TAG = `<!-- svg-source:excalidraw -->`; | ||||||
|  |  | ||||||
| export function exportToCanvas( | export function exportToCanvas( | ||||||
|   elements: readonly ExcalidrawElement[], |   elements: readonly NonDeletedExcalidrawElement[], | ||||||
|   appState: AppState, |   appState: AppState, | ||||||
|   { |   { | ||||||
|     exportBackground, |     exportBackground, | ||||||
| @@ -66,7 +66,7 @@ export function exportToCanvas( | |||||||
| } | } | ||||||
|  |  | ||||||
| export function exportToSvg( | export function exportToSvg( | ||||||
|   elements: readonly ExcalidrawElement[], |   elements: readonly NonDeletedExcalidrawElement[], | ||||||
|   { |   { | ||||||
|     exportBackground, |     exportBackground, | ||||||
|     exportPadding = 10, |     exportPadding = 10, | ||||||
|   | |||||||
| @@ -1,4 +1,8 @@ | |||||||
| import { ExcalidrawElement } from "../element/types"; | import { | ||||||
|  |   ExcalidrawElement, | ||||||
|  |   NonDeletedExcalidrawElement, | ||||||
|  | } from "../element/types"; | ||||||
|  | import { getNonDeletedElements } from "../element"; | ||||||
|  |  | ||||||
| export interface SceneStateCallback { | export interface SceneStateCallback { | ||||||
|   (): void; |   (): void; | ||||||
| @@ -8,15 +12,19 @@ export interface SceneStateCallbackRemover { | |||||||
|   (): void; |   (): void; | ||||||
| } | } | ||||||
|  |  | ||||||
| class SceneState { | class GlobalScene { | ||||||
|   private callbacks: Set<SceneStateCallback> = new Set(); |   private callbacks: Set<SceneStateCallback> = new Set(); | ||||||
|  |  | ||||||
|   constructor(private _elements: readonly ExcalidrawElement[] = []) {} |   constructor(private _elements: readonly ExcalidrawElement[] = []) {} | ||||||
|  |  | ||||||
|   getAllElements() { |   getElementsIncludingDeleted() { | ||||||
|     return this._elements; |     return this._elements; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   getElements(): readonly NonDeletedExcalidrawElement[] { | ||||||
|  |     return getNonDeletedElements(this._elements); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   replaceAllElements(nextElements: readonly ExcalidrawElement[]) { |   replaceAllElements(nextElements: readonly ExcalidrawElement[]) { | ||||||
|     this._elements = nextElements; |     this._elements = nextElements; | ||||||
|     this.informMutation(); |     this.informMutation(); | ||||||
| @@ -44,4 +52,4 @@ class SceneState { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| export const globalSceneState = new SceneState(); | export const globalSceneState = new GlobalScene(); | ||||||
|   | |||||||
| @@ -1,11 +1,14 @@ | |||||||
| import { ExcalidrawElement } from "../element/types"; | import { | ||||||
|  |   ExcalidrawElement, | ||||||
|  |   NonDeletedExcalidrawElement, | ||||||
|  | } from "../element/types"; | ||||||
| import { getElementAbsoluteCoords, getElementBounds } from "../element"; | import { getElementAbsoluteCoords, getElementBounds } from "../element"; | ||||||
| import { AppState } from "../types"; | import { AppState } from "../types"; | ||||||
| import { newElementWith } from "../element/mutateElement"; | import { newElementWith } from "../element/mutateElement"; | ||||||
|  |  | ||||||
| export function getElementsWithinSelection( | export function getElementsWithinSelection( | ||||||
|   elements: readonly ExcalidrawElement[], |   elements: readonly NonDeletedExcalidrawElement[], | ||||||
|   selection: ExcalidrawElement, |   selection: NonDeletedExcalidrawElement, | ||||||
| ) { | ) { | ||||||
|   const [ |   const [ | ||||||
|     selectionX1, |     selectionX1, | ||||||
| @@ -47,7 +50,7 @@ export function deleteSelectedElements( | |||||||
| } | } | ||||||
|  |  | ||||||
| export function isSomeElementSelected( | export function isSomeElementSelected( | ||||||
|   elements: readonly ExcalidrawElement[], |   elements: readonly NonDeletedExcalidrawElement[], | ||||||
|   appState: AppState, |   appState: AppState, | ||||||
| ): boolean { | ): boolean { | ||||||
|   return elements.some((element) => appState.selectedElementIds[element.id]); |   return elements.some((element) => appState.selectedElementIds[element.id]); | ||||||
| @@ -58,7 +61,7 @@ export function isSomeElementSelected( | |||||||
|  *  elements. If elements don't share the same value, returns `null`. |  *  elements. If elements don't share the same value, returns `null`. | ||||||
|  */ |  */ | ||||||
| export function getCommonAttributeOfSelectedElements<T>( | export function getCommonAttributeOfSelectedElements<T>( | ||||||
|   elements: readonly ExcalidrawElement[], |   elements: readonly NonDeletedExcalidrawElement[], | ||||||
|   appState: AppState, |   appState: AppState, | ||||||
|   getAttribute: (element: ExcalidrawElement) => T, |   getAttribute: (element: ExcalidrawElement) => T, | ||||||
| ): T | null { | ): T | null { | ||||||
| @@ -73,14 +76,14 @@ export function getCommonAttributeOfSelectedElements<T>( | |||||||
| } | } | ||||||
|  |  | ||||||
| export function getSelectedElements( | export function getSelectedElements( | ||||||
|   elements: readonly ExcalidrawElement[], |   elements: readonly NonDeletedExcalidrawElement[], | ||||||
|   appState: AppState, |   appState: AppState, | ||||||
| ): readonly ExcalidrawElement[] { | ) { | ||||||
|   return elements.filter((element) => appState.selectedElementIds[element.id]); |   return elements.filter((element) => appState.selectedElementIds[element.id]); | ||||||
| } | } | ||||||
|  |  | ||||||
| export function getTargetElement( | export function getTargetElement( | ||||||
|   elements: readonly ExcalidrawElement[], |   elements: readonly NonDeletedExcalidrawElement[], | ||||||
|   appState: AppState, |   appState: AppState, | ||||||
| ) { | ) { | ||||||
|   return appState.editingElement |   return appState.editingElement | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ import { | |||||||
|   actionBringToFront, |   actionBringToFront, | ||||||
|   actionSendToBack, |   actionSendToBack, | ||||||
| } from "../actions"; | } from "../actions"; | ||||||
|  | import { ExcalidrawElement } from "../element/types"; | ||||||
|  |  | ||||||
| // Unmount ReactDOM from root | // Unmount ReactDOM from root | ||||||
| ReactDOM.unmountComponentAtNode(document.getElementById("root")!); | ReactDOM.unmountComponentAtNode(document.getElementById("root")!); | ||||||
| @@ -27,7 +28,7 @@ function populateElements( | |||||||
|   const selectedElementIds: any = {}; |   const selectedElementIds: any = {}; | ||||||
|  |  | ||||||
|   h.elements = elements.map(({ id, isDeleted = false, isSelected = false }) => { |   h.elements = elements.map(({ id, isDeleted = false, isSelected = false }) => { | ||||||
|     const element: Mutable<ReturnType<typeof newElement>> = newElement({ |     const element: Mutable<ExcalidrawElement> = newElement({ | ||||||
|       type: "rectangle", |       type: "rectangle", | ||||||
|       x: 100, |       x: 100, | ||||||
|       y: 100, |       y: 100, | ||||||
|   | |||||||
							
								
								
									
										13
									
								
								src/types.ts
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								src/types.ts
									
									
									
									
									
								
							| @@ -1,7 +1,8 @@ | |||||||
| import { | import { | ||||||
|   ExcalidrawElement, |  | ||||||
|   PointerType, |   PointerType, | ||||||
|   ExcalidrawLinearElement, |   ExcalidrawLinearElement, | ||||||
|  |   NonDeletedExcalidrawElement, | ||||||
|  |   NonDeleted, | ||||||
| } from "./element/types"; | } from "./element/types"; | ||||||
| import { SHAPES } from "./shapes"; | import { SHAPES } from "./shapes"; | ||||||
| import { Point as RoughPoint } from "roughjs/bin/geometry"; | import { Point as RoughPoint } from "roughjs/bin/geometry"; | ||||||
| @@ -12,13 +13,13 @@ export type Point = Readonly<RoughPoint>; | |||||||
| export type AppState = { | export type AppState = { | ||||||
|   isLoading: boolean; |   isLoading: boolean; | ||||||
|   errorMessage: string | null; |   errorMessage: string | null; | ||||||
|   draggingElement: ExcalidrawElement | null; |   draggingElement: NonDeletedExcalidrawElement | null; | ||||||
|   resizingElement: ExcalidrawElement | null; |   resizingElement: NonDeletedExcalidrawElement | null; | ||||||
|   multiElement: ExcalidrawLinearElement | null; |   multiElement: NonDeleted<ExcalidrawLinearElement> | null; | ||||||
|   selectionElement: ExcalidrawElement | null; |   selectionElement: NonDeletedExcalidrawElement | null; | ||||||
|   // element being edited, but not necessarily added to elements array yet |   // element being edited, but not necessarily added to elements array yet | ||||||
|   //  (e.g. text element when typing into the input) |   //  (e.g. text element when typing into the input) | ||||||
|   editingElement: ExcalidrawElement | null; |   editingElement: NonDeletedExcalidrawElement | null; | ||||||
|   elementType: typeof SHAPES[number]["value"]; |   elementType: typeof SHAPES[number]["value"]; | ||||||
|   elementLocked: boolean; |   elementLocked: boolean; | ||||||
|   exportBackground: boolean; |   exportBackground: boolean; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Pete Hunt
					Pete Hunt