@ -44,8 +44,8 @@ import { AppState } from "./types";
import { ExcalidrawElement } from "./element/types" ;
import {
isInputLike ,
isWritableElement ,
isInputLike ,
debounce ,
capitalizeString ,
distance ,
@ -154,37 +154,267 @@ export function viewportCoordsToSceneCoords(
return { x , y } ;
}
function pickAppStatePropertiesForHistory (
appState : AppState ,
) : Partial < AppState > {
return {
exportBackground : appState.exportBackground ,
currentItemStrokeColor : appState.currentItemStrokeColor ,
currentItemBackgroundColor : appState.currentItemBackgroundColor ,
currentItemFillStyle : appState.currentItemFillStyle ,
currentItemStrokeWidth : appState.currentItemStrokeWidth ,
currentItemRoughness : appState.currentItemRoughness ,
currentItemOpacity : appState.currentItemOpacity ,
currentItemFont : appState.currentItemFont ,
viewBackgroundColor : appState.viewBackgroundColor ,
name : appState.name ,
} ;
}
let cursorX = 0 ;
let cursorY = 0 ;
let isHoldingSpace : boolean = false ;
let isPanning : boolean = false ;
let isHoldingMouseButton : boolean = false ;
interface LayerUIProps {
actionManager : ActionManager ;
appState : AppState ;
canvas : HTMLCanvasElement | null ;
setAppState : any ;
elements : readonly ExcalidrawElement [ ] ;
setElements : ( elements : readonly ExcalidrawElement [ ] ) = > void ;
}
const LayerUI = React . memo (
( {
actionManager ,
appState ,
setAppState ,
canvas ,
elements ,
setElements ,
} : LayerUIProps ) = > {
function renderCanvasActions() {
return (
< Stack.Col gap = { 4 } >
< Stack.Row justifyContent = { "space-between" } >
{ actionManager . renderAction ( "loadScene" ) }
{ actionManager . renderAction ( "saveScene" ) }
< ExportDialog
elements = { elements }
appState = { appState }
actionManager = { actionManager }
onExportToPng = { ( exportedElements , scale ) = > {
if ( canvas ) {
exportCanvas ( "png" , exportedElements , canvas , {
exportBackground : appState.exportBackground ,
name : appState.name ,
viewBackgroundColor : appState.viewBackgroundColor ,
scale ,
} ) ;
}
} }
onExportToSvg = { ( exportedElements , scale ) = > {
if ( canvas ) {
exportCanvas ( "svg" , exportedElements , canvas , {
exportBackground : appState.exportBackground ,
name : appState.name ,
viewBackgroundColor : appState.viewBackgroundColor ,
scale ,
} ) ;
}
} }
onExportToClipboard = { ( exportedElements , scale ) = > {
if ( canvas ) {
exportCanvas ( "clipboard" , exportedElements , canvas , {
exportBackground : appState.exportBackground ,
name : appState.name ,
viewBackgroundColor : appState.viewBackgroundColor ,
scale ,
} ) ;
}
} }
onExportToBackend = { exportedElements = > {
if ( canvas ) {
exportCanvas (
"backend" ,
exportedElements . map ( element = > ( {
. . . element ,
isSelected : false ,
} ) ) ,
canvas ,
appState ,
) ;
}
} }
/ >
{ actionManager . renderAction ( "clearCanvas" ) }
< / Stack.Row >
{ actionManager . renderAction ( "changeViewBackgroundColor" ) }
< / Stack.Col >
) ;
}
function renderSelectedShapeActions (
elements : readonly ExcalidrawElement [ ] ,
) {
const { elementType , editingElement } = appState ;
const targetElements = editingElement
? [ editingElement ]
: elements . filter ( el = > el . isSelected ) ;
if ( ! targetElements . length && elementType === "selection" ) {
return null ;
}
return (
< Island padding = { 4 } >
< div className = "panelColumn" >
{ actionManager . renderAction ( "changeStrokeColor" ) }
{ ( hasBackground ( elementType ) ||
targetElements . some ( element = > hasBackground ( element . type ) ) ) && (
< >
{ actionManager . renderAction ( "changeBackgroundColor" ) }
{ actionManager . renderAction ( "changeFillStyle" ) }
< / >
) }
{ ( hasStroke ( elementType ) ||
targetElements . some ( element = > hasStroke ( element . type ) ) ) && (
< >
{ actionManager . renderAction ( "changeStrokeWidth" ) }
{ actionManager . renderAction ( "changeSloppiness" ) }
< / >
) }
{ ( hasText ( elementType ) ||
targetElements . some ( element = > hasText ( element . type ) ) ) && (
< >
{ actionManager . renderAction ( "changeFontSize" ) }
{ actionManager . renderAction ( "changeFontFamily" ) }
< / >
) }
{ actionManager . renderAction ( "changeOpacity" ) }
{ actionManager . renderAction ( "deleteSelectedElements" ) }
< / div >
< / Island >
) ;
}
function renderShapesSwitcher() {
return (
< >
{ SHAPES . map ( ( { value , icon } , index ) = > {
const label = t ( ` toolBar. ${ value } ` ) ;
return (
< ToolButton
key = { value }
type = "radio"
icon = { icon }
checked = { appState . elementType === value }
name = "editor-current-shape"
title = { ` ${ capitalizeString ( label ) } — ${
capitalizeString ( value ) [ 0 ]
} , $ { index + 1 } ` }
keyBindingLabel = { ` ${ index + 1 } ` }
aria - label = { capitalizeString ( label ) }
aria - keyshortcuts = { ` ${ label [ 0 ] } ${ index + 1 } ` }
onChange = { ( ) = > {
setAppState ( { elementType : value , multiElement : null } ) ;
setElements ( clearSelection ( elements ) ) ;
document . documentElement . style . cursor =
value === "text" ? CURSOR_TYPE.TEXT : CURSOR_TYPE.CROSSHAIR ;
setAppState ( { } ) ;
} }
> < / ToolButton >
) ;
} ) }
< / >
) ;
}
return (
< FixedSideContainer side = "top" >
< div className = "App-menu App-menu_top" >
< Stack.Col gap = { 4 } align = "end" >
< section
className = "App-right-menu"
aria - labelledby = "canvas-actions-title"
>
< h2 className = "visually-hidden" id = "canvas-actions-title" >
{ t ( "headings.canvasActions" ) }
< / h2 >
< Island padding = { 4 } > { renderCanvasActions ( ) } < / Island >
< / section >
< section
className = "App-right-menu"
aria - labelledby = "selected-shape-title"
>
< h2 className = "visually-hidden" id = "selected-shape-title" >
{ t ( "headings.selectedShapeActions" ) }
< / h2 >
{ renderSelectedShapeActions ( elements ) }
< / section >
< / Stack.Col >
< section aria - labelledby = "shapes-title" >
< Stack.Col gap = { 4 } align = "start" >
< Stack.Row gap = { 1 } >
< Island padding = { 1 } >
< h2 className = "visually-hidden" id = "shapes-title" >
{ t ( "headings.shapes" ) }
< / h2 >
< Stack.Row gap = { 1 } > { renderShapesSwitcher ( ) } < / Stack.Row >
< / Island >
< LockIcon
checked = { appState . elementLocked }
onChange = { ( ) = > {
setAppState ( {
elementLocked : ! appState . elementLocked ,
elementType : appState.elementLocked
? "selection"
: appState . elementType ,
} ) ;
} }
title = { t ( "toolBar.lock" ) }
/ >
< / Stack.Row >
< / Stack.Col >
< / section >
< div / >
< / div >
< / FixedSideContainer >
) ;
} ,
( prev , next ) = > {
const getNecessaryObj = ( appState : AppState ) : Partial < AppState > = > {
const {
draggingElement ,
resizingElement ,
multiElement ,
editingElement ,
isResizing ,
cursorX ,
cursorY ,
. . . ret
} = appState ;
return ret ;
} ;
const prevAppState = getNecessaryObj ( prev . appState ) ;
const nextAppState = getNecessaryObj ( next . appState ) ;
const keys = Object . keys ( prevAppState ) as ( keyof Partial < AppState > ) [ ] ;
return (
prev . elements === next . elements &&
keys . every ( k = > prevAppState [ k ] === nextAppState [ k ] )
) ;
} ,
) ;
export class App extends React . Component < any , AppState > {
canvas : HTMLCanvasElement | null = null ;
rc : RoughCanvas | null = null ;
actionManager : ActionManager = new ActionManager ( ) ;
actionManager : ActionManager ;
canvasOnlyActions : Array < Action > ;
constructor ( props : any ) {
super ( props ) ;
this . actionManager = new ActionManager (
this . syncActionResult ,
( ) = > {
history . resumeRecording ( ) ;
} ,
( ) = > this . state ,
( ) = > elements ,
) ;
this . actionManager . registerAction ( actionFinalize ) ;
this . actionManager . registerAction ( actionDeleteSelected ) ;
this . actionManager . registerAction ( actionSendToBack ) ;
@ -233,6 +463,7 @@ export class App extends React.Component<any, AppState> {
}
copyToAppClipboard ( elements ) ;
elements = deleteSelectedElements ( elements ) ;
history . resumeRecording ( ) ;
this . setState ( { } ) ;
e . preventDefault ( ) ;
} ;
@ -279,6 +510,7 @@ export class App extends React.Component<any, AppState> {
element . isSelected = true ;
elements = [ . . . clearSelection ( elements ) , element ] ;
history . resumeRecording ( ) ;
this . setState ( { } ) ;
}
e . preventDefault ( ) ;
@ -291,17 +523,6 @@ export class App extends React.Component<any, AppState> {
this . saveDebounced . flush ( ) ;
} ;
public shouldComponentUpdate ( props : any , nextState : AppState ) {
if ( ! history . isRecording ( ) ) {
// temporary hack to fix #592
// eslint-disable-next-line react/no-direct-mutation-state
this . state = nextState ;
this . componentDidUpdate ( ) ;
return false ;
}
return true ;
}
private async loadScene ( id : string | null , k : string | undefined ) {
let data ;
let selectedId ;
@ -321,6 +542,7 @@ export class App extends React.Component<any, AppState> {
}
if ( data . appState ) {
history . resumeRecording ( ) ;
this . setState ( { . . . data . appState , selectedId } ) ;
} else {
this . setState ( { } ) ;
@ -395,11 +617,7 @@ export class App extends React.Component<any, AppState> {
return ;
}
const actionResult = this . actionManager . handleKeyDown (
event ,
elements ,
this . state ,
) ;
const actionResult = this . actionManager . handleKeyDown ( event ) ;
if ( actionResult ) {
this . syncActionResult ( actionResult ) ;
@ -452,9 +670,10 @@ export class App extends React.Component<any, AppState> {
event . preventDefault ( ) ;
if (
this . state . resizingElement ||
this . state . multiElement ||
this . state . editingElement
this . state . resizingElement ||
this . state . editingElement ||
this . state . draggingElement
) {
return ;
}
@ -509,212 +728,14 @@ export class App extends React.Component<any, AppState> {
}
} ;
private renderSelectedShapeActions ( elements : readonly ExcalidrawElement [ ] ) {
const { elementType , editingElement } = this . state ;
const targetElements = editingElement
? [ editingElement ]
: elements . filter ( el = > el . isSelected ) ;
if ( ! targetElements . length && elementType === "selection" ) {
return null ;
}
return (
< Island padding = { 4 } >
< div className = "panelColumn" >
{ this . actionManager . renderAction (
"changeStrokeColor" ,
elements ,
this . state ,
this . syncActionResult ,
) }
{ ( hasBackground ( elementType ) ||
targetElements . some ( element = > hasBackground ( element . type ) ) ) && (
< >
{ this . actionManager . renderAction (
"changeBackgroundColor" ,
elements ,
this . state ,
this . syncActionResult ,
) }
{ this . actionManager . renderAction (
"changeFillStyle" ,
elements ,
this . state ,
this . syncActionResult ,
) }
< / >
) }
{ ( hasStroke ( elementType ) ||
targetElements . some ( element = > hasStroke ( element . type ) ) ) && (
< >
{ this . actionManager . renderAction (
"changeStrokeWidth" ,
elements ,
this . state ,
this . syncActionResult ,
) }
{ this . actionManager . renderAction (
"changeSloppiness" ,
elements ,
this . state ,
this . syncActionResult ,
) }
< / >
) }
{ ( hasText ( elementType ) ||
targetElements . some ( element = > hasText ( element . type ) ) ) && (
< >
{ this . actionManager . renderAction (
"changeFontSize" ,
elements ,
this . state ,
this . syncActionResult ,
) }
{ this . actionManager . renderAction (
"changeFontFamily" ,
elements ,
this . state ,
this . syncActionResult ,
) }
< / >
) }
{ this . actionManager . renderAction (
"changeOpacity" ,
elements ,
this . state ,
this . syncActionResult ,
) }
{ this . actionManager . renderAction (
"deleteSelectedElements" ,
elements ,
this . state ,
this . syncActionResult ,
) }
< / div >
< / Island >
) ;
}
setAppState = ( obj : any ) = > {
this . setState ( obj ) ;
} ;
private renderShapesSwitcher() {
return (
< >
{ SHAPES . map ( ( { value , icon } , index ) = > {
const label = t ( ` toolBar. ${ value } ` ) ;
return (
< ToolButton
key = { value }
type = "radio"
icon = { icon }
checked = { this . state . elementType === value }
name = "editor-current-shape"
title = { ` ${ capitalizeString ( label ) } — ${
capitalizeString ( value ) [ 0 ]
} , $ { index + 1 } ` }
keyBindingLabel = { ` ${ index + 1 } ` }
aria - label = { capitalizeString ( label ) }
aria - keyshortcuts = { ` ${ label [ 0 ] } ${ index + 1 } ` }
onChange = { ( ) = > {
this . setState ( { elementType : value , multiElement : null } ) ;
elements = clearSelection ( elements ) ;
document . documentElement . style . cursor =
value === "text" ? CURSOR_TYPE.TEXT : CURSOR_TYPE.CROSSHAIR ;
setElements = ( elements_ : readonly ExcalidrawElement [ ] ) = > {
elements = elements_ ;
this . setState ( { } ) ;
} }
> < / ToolButton >
) ;
} ) }
< / >
) ;
}
private renderCanvasActions() {
return (
< Stack.Col gap = { 4 } >
< Stack.Row justifyContent = { "space-between" } >
{ this . actionManager . renderAction (
"loadScene" ,
elements ,
this . state ,
this . syncActionResult ,
) }
{ this . actionManager . renderAction (
"saveScene" ,
elements ,
this . state ,
this . syncActionResult ,
) }
< ExportDialog
elements = { elements }
appState = { this . state }
actionManager = { this . actionManager }
syncActionResult = { this . syncActionResult }
onExportToPng = { ( exportedElements , scale ) = > {
if ( this . canvas ) {
exportCanvas ( "png" , exportedElements , this . canvas , {
exportBackground : this.state.exportBackground ,
name : this.state.name ,
viewBackgroundColor : this.state.viewBackgroundColor ,
scale ,
} ) ;
}
} }
onExportToSvg = { ( exportedElements , scale ) = > {
if ( this . canvas ) {
exportCanvas ( "svg" , exportedElements , this . canvas , {
exportBackground : this.state.exportBackground ,
name : this.state.name ,
viewBackgroundColor : this.state.viewBackgroundColor ,
scale ,
} ) ;
}
} }
onExportToClipboard = { ( exportedElements , scale ) = > {
if ( this . canvas ) {
exportCanvas ( "clipboard" , exportedElements , this . canvas , {
exportBackground : this.state.exportBackground ,
name : this.state.name ,
viewBackgroundColor : this.state.viewBackgroundColor ,
scale ,
} ) ;
}
} }
onExportToBackend = { exportedElements = > {
if ( this . canvas ) {
exportCanvas (
"backend" ,
exportedElements . map ( element = > ( {
. . . element ,
isSelected : false ,
} ) ) ,
this . canvas ,
this . state ,
) ;
}
} }
/ >
{ this . actionManager . renderAction (
"clearCanvas" ,
elements ,
this . state ,
this . syncActionResult ,
) }
< / Stack.Row >
{ this . actionManager . renderAction (
"changeViewBackgroundColor" ,
elements ,
this . state ,
this . syncActionResult ,
) }
< / Stack.Col >
) ;
}
} ;
public render() {
const canvasWidth = window . innerWidth - CANVAS_WINDOW_OFFSET_LEFT ;
@ -722,55 +743,14 @@ export class App extends React.Component<any, AppState> {
return (
< div className = "container" >
< FixedSideContainer side = "top" >
< div className = "App-menu App-menu_top" >
< Stack.Col gap = { 4 } align = "end" >
< section
className = "App-right-menu"
aria - labelledby = "canvas-actions-title"
>
< h2 className = "visually-hidden" id = "canvas-actions-title" >
{ t ( "headings.canvasActions" ) }
< / h2 >
< Island padding = { 4 } > { this . renderCanvasActions ( ) } < / Island >
< / section >
< section
className = "App-right-menu"
aria - labelledby = "selected-shape-title"
>
< h2 className = "visually-hidden" id = "selected-shape-title" >
{ t ( "headings.selectedShapeActions" ) }
< / h2 >
{ this . renderSelectedShapeActions ( elements ) }
< / section >
< / Stack.Col >
< section aria - labelledby = "shapes-title" >
< Stack.Col gap = { 4 } align = "start" >
< Stack.Row gap = { 1 } >
< Island padding = { 1 } >
< h2 className = "visually-hidden" id = "shapes-title" >
{ t ( "headings.shapes" ) }
< / h2 >
< Stack.Row gap = { 1 } > { this . renderShapesSwitcher ( ) } < / Stack.Row >
< / Island >
< LockIcon
checked = { this . state . elementLocked }
onChange = { ( ) = > {
this . setState ( {
elementLocked : ! this . state . elementLocked ,
elementType : this.state.elementLocked
? "selection"
: this . state . elementType ,
} ) ;
} }
title = { t ( "toolBar.lock" ) }
< LayerUI
canvas = { this . canvas }
appState = { this . state }
setAppState = { this . setAppState }
actionManager = { this . actionManager }
elements = { elements }
setElements = { this . setElements }
/ >
< / Stack.Row >
< / Stack.Col >
< / section >
< div / >
< / div >
< / FixedSideContainer >
< main >
< canvas
id = "canvas"
@ -822,11 +802,8 @@ export class App extends React.Component<any, AppState> {
label : t ( "labels.paste" ) ,
action : ( ) = > this . pasteFromClipboard ( ) ,
} ,
. . . this . actionManager . getContextMenuItems (
elements ,
this . state ,
this . syncActionResult ,
action = > this . canvasOnlyActions . includes ( action ) ,
. . . this . actionManager . getContextMenuItems ( action = >
this . canvasOnlyActions . includes ( action ) ,
) ,
] ,
top : e.clientY ,
@ -852,9 +829,6 @@ export class App extends React.Component<any, AppState> {
action : ( ) = > this . pasteFromClipboard ( ) ,
} ,
. . . this . actionManager . getContextMenuItems (
elements ,
this . state ,
this . syncActionResult ,
action = > ! this . canvasOnlyActions . includes ( action ) ,
) ,
] ,
@ -889,8 +863,7 @@ export class App extends React.Component<any, AppState> {
const deltaY = lastY - e . clientY ;
lastX = e . clientX ;
lastY = e . clientY ;
// We don't want to save history when panning around
history . skipRecording ( ) ;
this . setState ( {
scrollX : this.state.scrollX - deltaX ,
scrollY : this.state.scrollY - deltaY ,
@ -1004,6 +977,7 @@ export class App extends React.Component<any, AppState> {
// state of the box
if ( ! hitElement . isSelected ) {
hitElement . isSelected = true ;
elements = elements . slice ( ) ;
elementIsAddedToSelection = true ;
}
@ -1074,6 +1048,7 @@ export class App extends React.Component<any, AppState> {
} ,
] ;
}
history . resumeRecording ( ) ;
resetSelection ( ) ;
} ,
onCancel : ( ) = > {
@ -1104,6 +1079,11 @@ export class App extends React.Component<any, AppState> {
draggingElement : element ,
} ) ;
}
} else if ( element . type === "selection" ) {
this . setState ( {
selectionElement : element ,
draggingElement : element ,
} ) ;
} else {
elements = [ . . . elements , element ] ;
this . setState ( { multiElement : null , draggingElement : element } ) ;
@ -1138,7 +1118,6 @@ export class App extends React.Component<any, AppState> {
mouseY : number ,
perfect : boolean ,
) = > {
// TODO: Implement perfect sizing for origin
if ( perfect ) {
const absPx = p1 [ 0 ] + element . x ;
const absPy = p1 [ 1 ] + element . y ;
@ -1195,8 +1174,6 @@ export class App extends React.Component<any, AppState> {
if ( isOverHorizontalScrollBar ) {
const x = e . clientX - CANVAS_WINDOW_OFFSET_LEFT ;
const dx = x - lastX ;
// We don't want to save history when scrolling
history . skipRecording ( ) ;
this . setState ( { scrollX : this.state.scrollX - dx } ) ;
lastX = x ;
return ;
@ -1205,8 +1182,6 @@ export class App extends React.Component<any, AppState> {
if ( isOverVerticalScrollBar ) {
const y = e . clientY - CANVAS_WINDOW_OFFSET_TOP ;
const dy = y - lastY ;
// We don't want to save history when scrolling
history . skipRecording ( ) ;
this . setState ( { scrollY : this.state.scrollY - dy } ) ;
lastY = y ;
return ;
@ -1444,8 +1419,6 @@ export class App extends React.Component<any, AppState> {
lastX = x ;
lastY = y ;
// We don't want to save history when resizing an element
history . skipRecording ( ) ;
this . setState ( { } ) ;
return ;
}
@ -1465,8 +1438,6 @@ export class App extends React.Component<any, AppState> {
} ) ;
lastX = x ;
lastY = y ;
// We don't want to save history when dragging an element to initially size it
history . skipRecording ( ) ;
this . setState ( { } ) ;
return ;
}
@ -1532,7 +1503,7 @@ export class App extends React.Component<any, AppState> {
draggingElement . shape = null ;
if ( this . state . elementType === "selection" ) {
if ( ! e . shiftKey ) {
if ( ! e . shiftKey && elements . some ( el = > el . isSelected ) ) {
elements = clearSelection ( elements ) ;
}
const elementsWithinSelection = getElementsWithinSelection (
@ -1543,13 +1514,10 @@ export class App extends React.Component<any, AppState> {
element . isSelected = true ;
} ) ;
}
// We don't want to save history when moving an element
history . skipRecording ( ) ;
this . setState ( { } ) ;
} ;
const onMouseUp = ( e : MouseEvent ) = > {
this . setState ( { isResizing : false } ) ;
const {
draggingElement ,
resizingElement ,
@ -1558,6 +1526,12 @@ export class App extends React.Component<any, AppState> {
elementLocked ,
} = this . state ;
this . setState ( {
isResizing : false ,
resizingElement : null ,
selectionElement : null ,
} ) ;
resizeArrowFn = null ;
lastMouseUp = null ;
isHoldingMouseButton = false ;
@ -1567,6 +1541,7 @@ export class App extends React.Component<any, AppState> {
if ( elementType === "arrow" || elementType === "line" ) {
if ( draggingElement ! . points . length > 1 ) {
history . resumeRecording ( ) ;
this . setState ( { } ) ;
}
if ( ! draggingOccurred && draggingElement && ! multiElement ) {
const { x , y } = viewportCoordsToSceneCoords ( e , this . state ) ;
@ -1603,6 +1578,11 @@ export class App extends React.Component<any, AppState> {
this . setState ( { } ) ;
}
if ( resizingElement ) {
history . resumeRecording ( ) ;
this . setState ( { } ) ;
}
if (
resizingElement &&
isInvisiblySmallElement ( resizingElement )
@ -1640,15 +1620,19 @@ export class App extends React.Component<any, AppState> {
return ;
}
if ( elementType === "selection" ) {
elements = elements . slice ( 0 , - 1 ) ;
} else if ( ! elementLocked ) {
if ( ! elementLocked ) {
draggingElement . isSelected = true ;
}
if (
elementType !== "selection" ||
elements . some ( el = > el . isSelected )
) {
history . resumeRecording ( ) ;
}
if ( ! elementLocked ) {
resetCursor ( ) ;
this . setState ( {
draggingElement : null ,
elementType : "selection" ,
@ -1664,16 +1648,6 @@ export class App extends React.Component<any, AppState> {
window . addEventListener ( "mousemove" , onMouseMove ) ;
window . addEventListener ( "mouseup" , onMouseUp ) ;
if (
! this . state . multiElement ||
( this . state . multiElement &&
this . state . multiElement . points . length < 2 )
) {
// We don't want to save history on mouseDown, only on mouseUp when it's fully configured
history . skipRecording ( ) ;
this . setState ( { } ) ;
}
} }
onDoubleClick = { e = > {
const { x , y } = viewportCoordsToSceneCoords ( e , this . state ) ;
@ -1765,6 +1739,7 @@ export class App extends React.Component<any, AppState> {
} ,
] ;
}
history . resumeRecording ( ) ;
resetSelection ( ) ;
} ,
onCancel : ( ) = > {
@ -1883,8 +1858,7 @@ export class App extends React.Component<any, AppState> {
private handleWheel = ( e : WheelEvent ) = > {
e . preventDefault ( ) ;
const { deltaX , deltaY } = e ;
// We don't want to save history when panning around
history . skipRecording ( ) ;
this . setState ( {
scrollX : this.state.scrollX - deltaX ,
scrollY : this.state.scrollY - deltaY ,
@ -1918,6 +1892,7 @@ export class App extends React.Component<any, AppState> {
return duplicate ;
} ) ,
] ;
history . resumeRecording ( ) ;
this . setState ( { } ) ;
} ;
@ -1960,6 +1935,7 @@ export class App extends React.Component<any, AppState> {
componentDidUpdate() {
const atLeastOneVisibleElement = renderScene (
elements ,
this . state . selectionElement ,
this . rc ! ,
this . canvas ! ,
{
@ -1974,14 +1950,8 @@ export class App extends React.Component<any, AppState> {
}
this . saveDebounced ( ) ;
if ( history . isRecording ( ) ) {
history . pushEntry (
history . generateCurrentEntry (
pickAppStatePropertiesForHistory ( this . state ) ,
elements ,
) ,
) ;
} else {
history . resumeRecording ( ) ;
history . pushEntry ( this . state , elements ) ;
history . skipRecording ( ) ;
}
}
}