@ -8,15 +8,8 @@ import { NonDeletedExcalidrawElement } from "../element/types";
import { Language , t } from "../i18n" ;
import { calculateScrollCenter } from "../scene" ;
import { ExportType } from "../scene/types" ;
import {
AppProps ,
AppState ,
ExcalidrawProps ,
BinaryFiles ,
UIChildrenComponents ,
UIWelcomeScreenComponents ,
} from "../types" ;
import { isShallowEqual , muteFSAbortError , getReactChildren } from "../utils" ;
import { AppProps , AppState , ExcalidrawProps , BinaryFiles } from "../types" ;
import { isShallowEqual , muteFSAbortError } from "../utils" ;
import { SelectedShapeActions , ShapesSwitcher } from "./Actions" ;
import { ErrorDialog } from "./ErrorDialog" ;
import { ExportCB , ImageExportDialog } from "./ImageExportDialog" ;
@ -45,7 +38,6 @@ import { useDevice } from "../components/App";
import { Stats } from "./Stats" ;
import { actionToggleStats } from "../actions/actionToggleStats" ;
import Footer from "./footer/Footer" ;
import WelcomeScreen from "./welcome-screen/WelcomeScreen" ;
import { hostSidebarCountersAtom } from "./Sidebar/Sidebar" ;
import { jotaiScope } from "../jotai" ;
import { useAtom } from "jotai" ;
@ -53,6 +45,12 @@ import MainMenu from "./main-menu/MainMenu";
import { ActiveConfirmDialog } from "./ActiveConfirmDialog" ;
import { HandButton } from "./HandButton" ;
import { isHandToolActive } from "../appState" ;
import {
mainMenuTunnel ,
welcomeScreenMenuHintTunnel ,
welcomeScreenToolbarHintTunnel ,
welcomeScreenCenterTunnel ,
} from "./tunnels" ;
interface LayerUIProps {
actionManager : ActionManager ;
@ -67,7 +65,6 @@ interface LayerUIProps {
onInsertElements : ( elements : readonly NonDeletedExcalidrawElement [ ] ) = > void ;
showExitZenModeBtn : boolean ;
langCode : Language [ "code" ] ;
isCollaborating : boolean ;
renderTopRightUI? : ExcalidrawProps [ "renderTopRightUI" ] ;
renderCustomStats? : ExcalidrawProps [ "renderCustomStats" ] ;
renderCustomSidebar? : ExcalidrawProps [ "renderSidebar" ] ;
@ -81,6 +78,32 @@ interface LayerUIProps {
children? : React.ReactNode ;
}
const DefaultMainMenu : React.FC < {
UIOptions : AppProps [ "UIOptions" ] ;
} > = ( { UIOptions } ) = > {
return (
< MainMenu __fallback >
< MainMenu.DefaultItems.LoadScene / >
< MainMenu.DefaultItems.SaveToActiveFile / >
{ /* FIXME we should to test for this inside the item itself */ }
{ UIOptions . canvasActions . export && < MainMenu.DefaultItems.Export / > }
{ /* FIXME we should to test for this inside the item itself */ }
{ UIOptions . canvasActions . saveAsImage && (
< MainMenu.DefaultItems.SaveAsImage / >
) }
< MainMenu.DefaultItems.Help / >
< MainMenu.DefaultItems.ClearCanvas / >
< MainMenu.Separator / >
< MainMenu.Group title = "Excalidraw links" >
< MainMenu.DefaultItems.Socials / >
< / MainMenu.Group >
< MainMenu.Separator / >
< MainMenu.DefaultItems.ToggleTheme / >
< MainMenu.DefaultItems.ChangeCanvasBackground / >
< / MainMenu >
) ;
} ;
const LayerUI = ( {
actionManager ,
appState ,
@ -93,7 +116,6 @@ const LayerUI = ({
onPenModeToggle ,
onInsertElements ,
showExitZenModeBtn ,
isCollaborating ,
renderTopRightUI ,
renderCustomStats ,
renderCustomSidebar ,
@ -108,28 +130,6 @@ const LayerUI = ({
} : LayerUIProps ) = > {
const device = useDevice ( ) ;
const [ childrenComponents , restChildren ] =
getReactChildren < UIChildrenComponents > ( children , {
Menu : true ,
FooterCenter : true ,
WelcomeScreen : true ,
} ) ;
const [ WelcomeScreenComponents ] = getReactChildren < UIWelcomeScreenComponents > (
renderWelcomeScreen
? (
childrenComponents ? . WelcomeScreen ? ? (
< WelcomeScreen >
< WelcomeScreen.Center / >
< WelcomeScreen.Hints.MenuHint / >
< WelcomeScreen.Hints.ToolbarHint / >
< WelcomeScreen.Hints.HelpHint / >
< / WelcomeScreen >
)
) ? . props ? . children
: null ,
) ;
const renderJSONExportDialog = ( ) = > {
if ( ! UIOptions . canvasActions . export ) {
return null ;
@ -197,37 +197,12 @@ const LayerUI = ({
) ;
} ;
const renderMenu = ( ) = > {
return (
childrenComponents . Menu || (
< MainMenu >
< MainMenu.DefaultItems.LoadScene / >
< MainMenu.DefaultItems.SaveToActiveFile / >
{ /* FIXME we should to test for this inside the item itself */ }
{ UIOptions . canvasActions . export && < MainMenu.DefaultItems.Export / > }
{ /* FIXME we should to test for this inside the item itself */ }
{ UIOptions . canvasActions . saveAsImage && (
< MainMenu.DefaultItems.SaveAsImage / >
) }
< MainMenu.DefaultItems.Help / >
< MainMenu.DefaultItems.ClearCanvas / >
< MainMenu.Separator / >
< MainMenu.Group title = "Excalidraw links" >
< MainMenu.DefaultItems.Socials / >
< / MainMenu.Group >
< MainMenu.Separator / >
< MainMenu.DefaultItems.ToggleTheme / >
< MainMenu.DefaultItems.ChangeCanvasBackground / >
< / MainMenu >
)
) ;
} ;
const renderCanvasActions = ( ) = > (
< div style = { { position : "relative" } } >
{ WelcomeScreenComponents . MenuHint }
{ / * w r a p p i n g t o F r a g m e n t s t o p s R e a c t f r o m o c c a s i o n a l l y c o m p l a i n i n g
about identical Keys * / }
< > { renderMenu ( ) } < / >
< mainMenuTunnel.Out / >
{ renderWelcomeScreen && < welcomeScreenMenuHintTunnel.Out / > }
< / div >
) ;
@ -264,7 +239,6 @@ const LayerUI = ({
return (
< FixedSideContainer side = "top" >
{ WelcomeScreenComponents . Center }
< div className = "App-menu App-menu_top" >
< Stack.Col
gap = { 6 }
@ -279,7 +253,9 @@ const LayerUI = ({
< Section heading = "shapes" className = "shapes-section" >
{ ( heading : React.ReactNode ) = > (
< div style = { { position : "relative" } } >
{ WelcomeScreenComponents . ToolbarHint }
{ renderWelcomeScreen && (
< welcomeScreenToolbarHintTunnel.Out / >
) }
< Stack.Col gap = { 4 } align = "start" >
< Stack.Row
gap = { 1 }
@ -380,7 +356,16 @@ const LayerUI = ({
return (
< >
{ restChildren }
{ /* ------------------------- tunneled UI ---------------------------- */ }
{ / * m a k e s u r e w e r e n d e r h o s t a p p c o m p o n e n t s f i r s t s o t h a t w e c a n d e t e c t
them first on initial render to optimize layout shift * / }
{ children }
{ / * r e n d e r c o m p o n e n t f a l l b a c k s . C a n b e r e n d e r e d a n y w h e r e a s t h e y ' l l b e
tunneled away . We only render tunneled components that actually
have defaults when host do not render anything . * / }
< DefaultMainMenu UIOptions = { UIOptions } / >
{ /* ------------------------------------------------------------------ */ }
{ appState . isLoading && < LoadingMessage delay = { 250 } / > }
{ appState . errorMessage && (
< ErrorDialog
@ -427,8 +412,6 @@ const LayerUI = ({
renderCustomStats = { renderCustomStats }
renderSidebars = { renderSidebars }
device = { device }
renderMenu = { renderMenu }
welcomeScreenCenter = { WelcomeScreenComponents . Center }
/ >
) }
@ -451,13 +434,13 @@ const LayerUI = ({
: { }
}
>
{ renderWelcomeScreen && < welcomeScreenCenterTunnel.Out / > }
{ renderFixedSideContainer ( ) }
< Footer
appState = { appState }
actionManager = { actionManager }
showExitZenModeBtn = { showExitZenModeBtn }
footerCenter = { childrenComponents . FooterCenter }
welcomeScreenHelp = { WelcomeScreenComponents . HelpHint }
renderWelcomeScreen = { renderWelcomeScreen }
/ >
{ appState . showStats && (
< Stats