@ -40,212 +40,233 @@ interface LayerUIProps {
onLockToggle : ( ) = > void ;
}
export const LayerUI = React . memo (
( {
actionManager ,
appState ,
setAppState ,
canvas ,
elements ,
onRoomCreate ,
onUsernameChange ,
onRoomDestroy ,
onLockToggle ,
} : LayerUIProps ) = > {
const isMobile = useIsMobile ( ) ;
const LayerUI = ( {
actionManager ,
appState ,
setAppState ,
canvas ,
elements ,
onRoomCreate ,
onUsernameChange ,
onRoomDestroy ,
onLockToggle ,
} : LayerUIProps ) = > {
const isMobile = useIsMobile ( ) ;
function renderExportDialog() {
const createExporter = ( type : ExportType ) : ExportCB = > (
exportedElements ,
scale ,
) = > {
if ( canvas ) {
exportCanvas ( type , exportedElements , appState , canvas , {
exportBackground : appState.exportBackground ,
name : appState.name ,
viewBackgroundColor : appState.viewBackgroundColor ,
scale ,
} ) ;
}
} ;
return (
< ExportDialog
elements = { elements }
appState = { appState }
actionManager = { actionManager }
onExportToPng = { createExporter ( "png" ) }
onExportToSvg = { createExporter ( "svg" ) }
onExportToClipboard = { createExporter ( "clipboard" ) }
onExportToBackend = { ( exportedElements ) = > {
if ( canvas ) {
exportCanvas (
"backend" ,
exportedElements ,
{
. . . appState ,
selectedElementIds : { } ,
} ,
canvas ,
appState ,
) ;
}
} }
/ >
) ;
}
return isMobile ? (
< MobileMenu
appState = { appState }
const renderExportDialog = ( ) = > {
const createExporter = ( type : ExportType ) : ExportCB = > (
exportedElements ,
scale ,
) = > {
if ( canvas ) {
exportCanvas ( type , exportedElements , appState , canvas , {
exportBackground : appState.exportBackground ,
name : appState.name ,
viewBackgroundColor : appState.viewBackgroundColor ,
scale ,
} ) ;
}
} ;
return (
< ExportDialog
elements = { elements }
appState = { appState }
actionManager = { actionManager }
exportButton = { renderExportDialog ( ) }
setAppState = { setAppState }
onUsernameChange = { onUsernameChange }
onRoomCreate = { onRoomCreate }
onRoomDestroy = { onRoomDestroy }
onLockToggle = { onLockToggle }
onExportToPng = { createExporter ( "png" ) }
onExportToSvg = { createExporter ( "svg" ) }
onExportToClipboard = { createExporter ( "clipboard" ) }
onExportToBackend = { ( exportedElements ) = > {
if ( canvas ) {
exportCanvas (
"backend" ,
exportedElements ,
{
. . . appState ,
selectedElementIds : { } ,
} ,
canvas ,
appState ,
) ;
}
} }
/ >
) : (
< >
{ appState . isLoading && < LoadingMessage / > }
{ appState . errorMessage && (
< ErrorDialog
message = { appState . errorMessage }
onClose = { ( ) = > setAppState ( { errorMessage : null } ) }
/ >
) }
{ appState . showShortcutsDialog && (
< ShortcutsDialog
onClose = { ( ) = > setAppState ( { showShortcutsDialog : null } ) }
/ >
) }
< FixedSideContainer side = "top" >
< HintViewer appState = { appState } elements = { elements } / >
< div className = "App-menu App-menu_top" >
< Stack.Col gap = { 4 } >
< Section heading = "canvasActions" >
{ / * t h e z I n d e x e n s u r e s t h i s m e n u h a s h i g h e r s t a c k i n g o r d e r ,
see https : //github.com/excalidraw/excalidraw/pull/1445 */}
< Island padding = { 4 } style = { { zIndex : 1 } } >
< Stack.Col gap = { 4 } >
< Stack.Row gap = { 1 } justifyContent = { "space-between" } >
{ actionManager . renderAction ( "loadScene" ) }
{ actionManager . renderAction ( "saveScene" ) }
{ renderExportDialog ( ) }
{ actionManager . renderAction ( "clearCanvas" ) }
< RoomDialog
isCollaborating = { appState . isCollaborating }
collaboratorCount = { appState . collaborators . size }
username = { appState . username }
onUsernameChange = { onUsernameChange }
onRoomCreate = { onRoomCreate }
onRoomDestroy = { onRoomDestroy }
) ;
} ;
const renderCanvasActions = ( ) = > (
< Section heading = "canvasActions" >
{ / * t h e z I n d e x e n s u r e s t h i s m e n u h a s h i g h e r s t a c k i n g o r d e r ,
see https : //github.com/excalidraw/excalidraw/pull/1445 */}
< Island padding = { 4 } style = { { zIndex : 1 } } >
< Stack.Col gap = { 4 } >
< Stack.Row gap = { 1 } justifyContent = "space-between" >
{ actionManager . renderAction ( "loadScene" ) }
{ actionManager . renderAction ( "saveScene" ) }
{ renderExportDialog ( ) }
{ actionManager . renderAction ( "clearCanvas" ) }
< RoomDialog
isCollaborating = { appState . isCollaborating }
collaboratorCount = { appState . collaborators . size }
username = { appState . username }
onUsernameChange = { onUsernameChange }
onRoomCreate = { onRoomCreate }
onRoomDestroy = { onRoomDestroy }
/ >
< / Stack.Row >
{ actionManager . renderAction ( "changeViewBackgroundColor" ) }
< / Stack.Col >
< / Island >
< / Section >
) ;
const renderSelectedShapeActions = ( ) = > (
< Section heading = "selectedShapeActions" >
< Island className = { CLASSES . SHAPE_ACTIONS_MENU } padding = { 4 } >
< SelectedShapeActions
appState = { appState }
elements = { elements }
renderAction = { actionManager . renderAction }
elementType = { appState . elementType }
/ >
< / Island >
< / Section >
) ;
const renderFixedSideContainer = ( ) = > {
const shouldRenderSelectedShapeActions = showSelectedShapeActions (
appState ,
elements ,
) ;
return (
< FixedSideContainer side = "top" >
< HintViewer appState = { appState } elements = { elements } / >
< div className = "App-menu App-menu_top" >
< Stack.Col gap = { 4 } >
{ renderCanvasActions ( ) }
{ shouldRenderSelectedShapeActions && renderSelectedShapeActions ( ) }
< / Stack.Col >
< Section heading = "shapes" >
{ ( heading ) = > (
< Stack.Col gap = { 4 } align = "start" >
< Stack.Row gap = { 1 } >
< Island padding = { 1 } >
{ heading }
< Stack.Row gap = { 1 } >
< ShapesSwitcher
elementType = { appState . elementType }
setAppState = { setAppState }
/ >
< / Stack.Row >
{ actionManager . renderAction ( "changeViewBackgroundColor" ) }
< / Stack.Col >
< / Island >
< / Section >
{ showSelectedShapeActions ( appState , elements ) && (
< Section heading = "selectedShapeActions" >
< Island className = { CLASSES . SHAPE_ACTIONS_MENU } padding = { 4 } >
< SelectedShapeActions
appState = { appState }
elements = { elements }
renderAction = { actionManager . renderAction }
elementType = { appState . elementType }
/ >
< / Island >
< / Section >
) }
< / Stack.Col >
< Section heading = "shapes" >
{ ( heading ) = > (
< Stack.Col gap = { 4 } align = "start" >
< Stack.Row gap = { 1 } >
< Island padding = { 1 } >
{ heading }
< Stack.Row gap = { 1 } >
< ShapesSwitcher
elementType = { appState . elementType }
setAppState = { setAppState }
/ >
< / Stack.Row >
< / Island >
< LockIcon
checked = { appState . elementLocked }
onChange = { onLockToggle }
title = { t ( "toolBar.lock" ) }
/ >
< / Stack.Row >
< / Stack.Col >
) }
< / Section >
< div / >
< / div >
< div className = "App-menu App-menu_bottom" >
< Stack.Col gap = { 2 } >
< Section heading = "canvasActions" >
< Island padding = { 1 } >
< ZoomActions
renderAction = { actionManager . renderAction }
zoom = { appState . zoom }
< LockIcon
checked = { appState . elementLocked }
onChange = { onLockToggle }
title = { t ( "toolBar.lock" ) }
/ >
< / Island >
< / Section >
< / Stack.Col >
< / div >
< / FixedSideContainer >
< aside >
< GitHubCorner / >
< / aside >
< footer role = "contentinfo" >
< LanguageList
onChange = { ( lng ) = > {
setLanguage ( lng ) ;
setAppState ( { } ) ;
} }
languages = { languages }
floating
/ >
{ actionManager . renderAction ( "toggleShortcuts" ) }
{ appState . scrolledOutside && (
< button
className = "scroll-back-to-content"
onClick = { ( ) = > {
setAppState ( { . . . calculateScrollCenter ( elements ) } ) ;
} }
>
{ t ( "buttons.scrollBackToContent" ) }
< / button >
) }
< / footer >
< / >
< / Stack.Row >
< / Stack.Col >
) }
< / Section >
< div / >
< / div >
< div className = "App-menu App-menu_bottom" >
< Stack.Col gap = { 2 } >
< Section heading = "canvasActions" >
< Island padding = { 1 } >
< ZoomActions
renderAction = { actionManager . renderAction }
zoom = { appState . zoom }
/ >
< / Island >
< / Section >
< / Stack.Col >
< / 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 > ) [ ] ;
const renderFooter = ( ) = > (
< footer role = "contentinfo" >
< LanguageList
onChange = { ( lng ) = > {
setLanguage ( lng ) ;
setAppState ( { } ) ;
} }
languages = { languages }
floating
/ >
{ actionManager . renderAction ( "toggleShortcuts" ) }
{ appState . scrolledOutside && (
< button
className = "scroll-back-to-content"
onClick = { ( ) = > {
setAppState ( { . . . calculateScrollCenter ( elements ) } ) ;
} }
>
{ t ( "buttons.scrollBackToContent" ) }
< / button >
) }
< / footer >
) ;
return (
prev . elements === next . elements &&
keys . every ( ( key ) = > prevAppState [ key ] === nextAppState [ key ] )
) ;
} ,
) ;
return isMobile ? (
< MobileMenu
appState = { appState }
elements = { elements }
actionManager = { actionManager }
exportButton = { renderExportDialog ( ) }
setAppState = { setAppState }
onUsernameChange = { onUsernameChange }
onRoomCreate = { onRoomCreate }
onRoomDestroy = { onRoomDestroy }
onLockToggle = { onLockToggle }
/ >
) : (
< >
{ appState . isLoading && < LoadingMessage / > }
{ appState . errorMessage && (
< ErrorDialog
message = { appState . errorMessage }
onClose = { ( ) = > setAppState ( { errorMessage : null } ) }
/ >
) }
{ appState . showShortcutsDialog && (
< ShortcutsDialog
onClose = { ( ) = > setAppState ( { showShortcutsDialog : null } ) }
/ >
) }
{ renderFixedSideContainer ( ) }
< aside >
< GitHubCorner / >
< / aside >
{ renderFooter ( ) }
< / >
) ;
} ;
const areEqual = ( prev : LayerUIProps , next : LayerUIProps ) = > {
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 ( ( key ) = > prevAppState [ key ] === nextAppState [ key ] )
) ;
} ;
export default React . memo ( LayerUI , areEqual ) ;