@ -100,12 +100,27 @@ function resetCursor() {
document . documentElement . style . cursor = "" ;
document . documentElement . style . cursor = "" ;
}
}
function setCursorForShape ( shape : string ) {
if ( shape === "selection" ) {
resetCursor ( ) ;
} else {
document . documentElement . style . cursor =
shape === "text" ? CURSOR_TYPE.TEXT : CURSOR_TYPE.CROSSHAIR ;
}
}
const ELEMENT_SHIFT_TRANSLATE_AMOUNT = 5 ;
const ELEMENT_SHIFT_TRANSLATE_AMOUNT = 5 ;
const ELEMENT_TRANSLATE_AMOUNT = 1 ;
const ELEMENT_TRANSLATE_AMOUNT = 1 ;
const TEXT_TO_CENTER_SNAP_THRESHOLD = 30 ;
const TEXT_TO_CENTER_SNAP_THRESHOLD = 30 ;
const CURSOR_TYPE = {
const CURSOR_TYPE = {
TEXT : "text" ,
TEXT : "text" ,
CROSSHAIR : "crosshair" ,
CROSSHAIR : "crosshair" ,
GRABBING : "grabbing" ,
} ;
const MOUSE_BUTTON = {
MAIN : 0 ,
WHEEL : 1 ,
SECONDARY : 2 ,
} ;
} ;
let lastCanvasWidth = - 1 ;
let lastCanvasWidth = - 1 ;
@ -141,6 +156,9 @@ function pickAppStatePropertiesForHistory(
let cursorX = 0 ;
let cursorX = 0 ;
let cursorY = 0 ;
let cursorY = 0 ;
let isHoldingSpace : boolean = false ;
let isPanning : boolean = false ;
let isHoldingMouseButton : boolean = false ;
export class App extends React . Component < any , AppState > {
export class App extends React . Component < any , AppState > {
canvas : HTMLCanvasElement | null = null ;
canvas : HTMLCanvasElement | null = null ;
@ -225,6 +243,7 @@ export class App extends React.Component<any, AppState> {
} ;
} ;
private onUnload = ( ) = > {
private onUnload = ( ) = > {
isHoldingSpace = false ;
this . saveDebounced ( ) ;
this . saveDebounced ( ) ;
this . saveDebounced . flush ( ) ;
this . saveDebounced . flush ( ) ;
} ;
} ;
@ -269,9 +288,11 @@ export class App extends React.Component<any, AppState> {
document . addEventListener ( "cut" , this . onCut ) ;
document . addEventListener ( "cut" , this . onCut ) ;
document . addEventListener ( "keydown" , this . onKeyDown , false ) ;
document . addEventListener ( "keydown" , this . onKeyDown , false ) ;
document . addEventListener ( "keyup" , this . onKeyUp , { passive : true } ) ;
document . addEventListener ( "mousemove" , this . updateCurrentCursorPosition ) ;
document . addEventListener ( "mousemove" , this . updateCurrentCursorPosition ) ;
window . addEventListener ( "resize" , this . onResize , false ) ;
window . addEventListener ( "resize" , this . onResize , false ) ;
window . addEventListener ( "unload" , this . onUnload , false ) ;
window . addEventListener ( "unload" , this . onUnload , false ) ;
window . addEventListener ( "blur" , this . onUnload , false ) ;
const searchParams = new URLSearchParams ( window . location . search ) ;
const searchParams = new URLSearchParams ( window . location . search ) ;
const id = searchParams . get ( "id" ) ;
const id = searchParams . get ( "id" ) ;
@ -292,6 +313,7 @@ export class App extends React.Component<any, AppState> {
) ;
) ;
window . removeEventListener ( "resize" , this . onResize , false ) ;
window . removeEventListener ( "resize" , this . onResize , false ) ;
window . removeEventListener ( "unload" , this . onUnload , false ) ;
window . removeEventListener ( "unload" , this . onUnload , false ) ;
window . removeEventListener ( "blur" , this . onUnload , false ) ;
}
}
public state : AppState = getDefaultAppState ( ) ;
public state : AppState = getDefaultAppState ( ) ;
@ -356,11 +378,11 @@ export class App extends React.Component<any, AppState> {
! event . metaKey &&
! event . metaKey &&
this . state . draggingElement === null
this . state . draggingElement === null
) {
) {
if ( shape === "text" ) {
if ( ! isHoldingSpace ) {
document . documentElement . style . cursor = CURSOR_TYPE . TEXT ;
document . documentElement . style . cursor =
} else {
shape === "text" ? CURSOR_TYPE.TEXT : CURSOR_TYPE.CROSSHAIR ;
document . documentElement . style . cursor = CURSOR_TYPE . CROSSHAIR ;
}
}
elements = clearSelection ( elements ) ;
this . setState ( { elementType : shape } ) ;
this . setState ( { elementType : shape } ) ;
} else if ( event [ KEYS . META ] && event . code === "KeyZ" ) {
} else if ( event [ KEYS . META ] && event . code === "KeyZ" ) {
event . preventDefault ( ) ;
event . preventDefault ( ) ;
@ -380,6 +402,25 @@ export class App extends React.Component<any, AppState> {
this . setState ( data . appState ) ;
this . setState ( data . appState ) ;
}
}
}
}
} else if ( event . key === KEYS . SPACE && ! isHoldingMouseButton ) {
isHoldingSpace = true ;
document . documentElement . style . cursor = CURSOR_TYPE . GRABBING ;
}
} ;
private onKeyUp = ( event : KeyboardEvent ) = > {
if ( event . key === KEYS . SPACE ) {
if ( this . state . elementType === "selection" ) {
resetCursor ( ) ;
} else {
elements = clearSelection ( elements ) ;
document . documentElement . style . cursor =
this . state . elementType === "text"
? CURSOR_TYPE . TEXT
: CURSOR_TYPE . CROSSHAIR ;
this . setState ( { } ) ;
}
isHoldingSpace = false ;
}
}
} ;
} ;
@ -783,11 +824,19 @@ export class App extends React.Component<any, AppState> {
lastMouseUp ( e ) ;
lastMouseUp ( e ) ;
}
}
// pan canvas on wheel button drag
if ( isPanning ) return ;
if ( e . button === 1 ) {
// pan canvas on wheel button drag or space+drag
if (
! isHoldingMouseButton &&
( e . button === MOUSE_BUTTON . WHEEL ||
( e . button === MOUSE_BUTTON . MAIN && isHoldingSpace ) )
) {
isHoldingMouseButton = true ;
isPanning = true ;
document . documentElement . style . cursor = CURSOR_TYPE . GRABBING ;
let { clientX : lastX , clientY : lastY } = e ;
let { clientX : lastX , clientY : lastY } = e ;
const onMouseMove = ( e : MouseEvent ) = > {
const onMouseMove = ( e : MouseEvent ) = > {
document . documentElement . style . cursor = ` grabbing ` ;
let deltaX = lastX - e . clientX ;
let deltaX = lastX - e . clientX ;
let deltaY = lastY - e . clientY ;
let deltaY = lastY - e . clientY ;
lastX = e . clientX ;
lastX = e . clientX ;
@ -799,22 +848,28 @@ export class App extends React.Component<any, AppState> {
scrollY : state.scrollY - deltaY ,
scrollY : state.scrollY - deltaY ,
} ) ) ;
} ) ) ;
} ;
} ;
const onMouseUp = ( lastMouseUp = ( e : MouseEvent ) = > {
const teard ow n = ( lastMouseUp = ( ) = > {
lastMouseUp = null ;
lastMouseUp = null ;
resetCursor ( ) ;
isPanning = false ;
isHoldingMouseButton = false ;
if ( ! isHoldingSpace ) {
setCursorForShape ( this . state . elementType ) ;
}
history . resumeRecording ( ) ;
history . resumeRecording ( ) ;
window . removeEventListener ( "mousemove" , onMouseMove ) ;
window . removeEventListener ( "mousemove" , onMouseMove ) ;
window . removeEventListener ( "mouseup" , onMouseUp ) ;
window . removeEventListener ( "mouseup" , teardown ) ;
window . removeEventListener ( "blur" , teardown ) ;
} ) ;
} ) ;
window . addEventListener ( "blur" , teardown ) ;
window . addEventListener ( "mousemove" , onMouseMove , {
window . addEventListener ( "mousemove" , onMouseMove , {
passive : true ,
passive : true ,
} ) ;
} ) ;
window . addEventListener ( "mouseup" , onMouseUp ) ;
window . addEventListener ( "mouseup" , teard ow n) ;
return ;
return ;
}
}
// only handle left mouse button
// only handle left mouse button
if ( e . button !== 0 ) return ;
if ( e . button !== MOUSE_BUTTON . MAIN ) return ;
// fixes mousemove causing selection of UI texts #32
// fixes mousemove causing selection of UI texts #32
e . preventDefault ( ) ;
e . preventDefault ( ) ;
// Preventing the event above disables default behavior
// Preventing the event above disables default behavior
@ -1208,6 +1263,7 @@ export class App extends React.Component<any, AppState> {
} = this . state ;
} = this . state ;
lastMouseUp = null ;
lastMouseUp = null ;
isHoldingMouseButton = false ;
window . removeEventListener ( "mousemove" , onMouseMove ) ;
window . removeEventListener ( "mousemove" , onMouseMove ) ;
window . removeEventListener ( "mouseup" , onMouseUp ) ;
window . removeEventListener ( "mouseup" , onMouseUp ) ;
@ -1393,6 +1449,7 @@ export class App extends React.Component<any, AppState> {
} ) ;
} ) ;
} }
} }
onMouseMove = { e = > {
onMouseMove = { e = > {
if ( isHoldingSpace || isPanning ) return ;
const hasDeselectedButton = Boolean ( e . buttons ) ;
const hasDeselectedButton = Boolean ( e . buttons ) ;
if (
if (
hasDeselectedButton ||
hasDeselectedButton ||