Skip to content

Commit

Permalink
feat: rewrite public UI component rendering using tunnels (excalidraw…
Browse files Browse the repository at this point in the history
…#6117)

* feat: rewrite public UI component rendering using tunnels

* factor out into components

* comments

* fix variable naming

* fix not hiding welcomeScreen

* factor out AppFooter and memoize components

* remove `UIOptions.welcomeScreen` and render only from host app

* factor out tunnels into own file

* update changelog. Keep `UIOptions.welcomeScreen` as deprecated

* update changelog

* lint

---------

Co-authored-by: Aakansha Doshi <[email protected]>
  • Loading branch information
dwelle and ad1992 authored Jan 31, 2023
1 parent 3a141ca commit e6de1fe
Show file tree
Hide file tree
Showing 22 changed files with 417 additions and 372 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"roughjs": "4.5.2",
"sass": "1.51.0",
"socket.io-client": "2.3.1",
"tunnel-rat": "0.1.0",
"typescript": "4.9.4",
"workbox-background-sync": "^6.5.4",
"workbox-broadcast-update": "^6.5.4",
Expand Down
2 changes: 0 additions & 2 deletions src/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,6 @@ class App extends React.Component<AppProps, AppState> {
})
}
langCode={getLanguage().code}
isCollaborating={this.props.isCollaborating}
renderTopRightUI={renderTopRightUI}
renderCustomStats={renderCustomStats}
renderCustomSidebar={this.props.renderSidebar}
Expand All @@ -605,7 +604,6 @@ class App extends React.Component<AppProps, AppState> {
onImageAction={this.onImageAction}
renderWelcomeScreen={
!this.state.isLoading &&
this.props.UIOptions.welcomeScreen &&
this.state.showWelcomeScreen &&
this.state.activeTool.type === "selection" &&
!this.scene.getElementsIncludingDeleted().length
Expand Down
119 changes: 51 additions & 68 deletions src/components/LayerUI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -45,14 +38,19 @@ 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";
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;
Expand All @@ -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"];
Expand All @@ -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,
Expand All @@ -93,7 +116,6 @@ const LayerUI = ({
onPenModeToggle,
onInsertElements,
showExitZenModeBtn,
isCollaborating,
renderTopRightUI,
renderCustomStats,
renderCustomSidebar,
Expand All @@ -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;
Expand Down Expand Up @@ -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}
{/* wrapping to Fragment stops React from occasionally complaining
about identical Keys */}
<>{renderMenu()}</>
<mainMenuTunnel.Out />
{renderWelcomeScreen && <welcomeScreenMenuHintTunnel.Out />}
</div>
);

Expand Down Expand Up @@ -264,7 +239,6 @@ const LayerUI = ({

return (
<FixedSideContainer side="top">
{WelcomeScreenComponents.Center}
<div className="App-menu App-menu_top">
<Stack.Col
gap={6}
Expand All @@ -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}
Expand Down Expand Up @@ -380,7 +356,16 @@ const LayerUI = ({

return (
<>
{restChildren}
{/* ------------------------- tunneled UI ---------------------------- */}
{/* make sure we render host app components first so that we can detect
them first on initial render to optimize layout shift */}
{children}
{/* render component fallbacks. Can be rendered anywhere as they'll be
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
Expand Down Expand Up @@ -427,8 +412,6 @@ const LayerUI = ({
renderCustomStats={renderCustomStats}
renderSidebars={renderSidebars}
device={device}
renderMenu={renderMenu}
welcomeScreenCenter={WelcomeScreenComponents.Center}
/>
)}

Expand All @@ -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
Expand Down
22 changes: 9 additions & 13 deletions src/components/MobileMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
import React from "react";
import {
AppState,
Device,
ExcalidrawProps,
UIWelcomeScreenComponents,
} from "../types";
import { AppState, Device, ExcalidrawProps } from "../types";
import { ActionManager } from "../actions/manager";
import { t } from "../i18n";
import Stack from "./Stack";
Expand All @@ -24,6 +19,7 @@ import { Stats } from "./Stats";
import { actionToggleStats } from "../actions";
import { HandButton } from "./HandButton";
import { isHandToolActive } from "../appState";
import { mainMenuTunnel, welcomeScreenCenterTunnel } from "./tunnels";

type MobileMenuProps = {
appState: AppState;
Expand All @@ -45,8 +41,6 @@ type MobileMenuProps = {
renderCustomStats?: ExcalidrawProps["renderCustomStats"];
renderSidebars: () => JSX.Element | null;
device: Device;
renderMenu: () => React.ReactNode;
welcomeScreenCenter: UIWelcomeScreenComponents["Center"];
};

export const MobileMenu = ({
Expand All @@ -63,13 +57,11 @@ export const MobileMenu = ({
renderCustomStats,
renderSidebars,
device,
renderMenu,
welcomeScreenCenter,
}: MobileMenuProps) => {
const renderToolbar = () => {
return (
<FixedSideContainer side="top" className="App-top-bar">
{welcomeScreenCenter}
<welcomeScreenCenterTunnel.Out />
<Section heading="shapes">
{(heading: React.ReactNode) => (
<Stack.Col gap={4} align="center">
Expand Down Expand Up @@ -135,12 +127,16 @@ export const MobileMenu = ({

const renderAppToolbar = () => {
if (appState.viewModeEnabled) {
return <div className="App-toolbar-content">{renderMenu()}</div>;
return (
<div className="App-toolbar-content">
<mainMenuTunnel.Out />
</div>
);
}

return (
<div className="App-toolbar-content">
{renderMenu()}
<mainMenuTunnel.Out />
{actionManager.renderAction("toggleEditMenu")}
{actionManager.renderAction("undo")}
{actionManager.renderAction("redo")}
Expand Down
17 changes: 6 additions & 11 deletions src/components/footer/Footer.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import clsx from "clsx";
import { actionShortcuts } from "../../actions";
import { ActionManager } from "../../actions/manager";
import {
AppState,
UIChildrenComponents,
UIWelcomeScreenComponents,
} from "../../types";
import { AppState } from "../../types";
import {
ExitZenModeAction,
FinalizeAction,
Expand All @@ -16,19 +12,18 @@ import { useDevice } from "../App";
import { HelpButton } from "../HelpButton";
import { Section } from "../Section";
import Stack from "../Stack";
import { footerCenterTunnel, welcomeScreenHelpHintTunnel } from "../tunnels";

const Footer = ({
appState,
actionManager,
showExitZenModeBtn,
footerCenter,
welcomeScreenHelp,
renderWelcomeScreen,
}: {
appState: AppState;
actionManager: ActionManager;
showExitZenModeBtn: boolean;
footerCenter: UIChildrenComponents["FooterCenter"];
welcomeScreenHelp: UIWelcomeScreenComponents["HelpHint"];
renderWelcomeScreen: boolean;
}) => {
const device = useDevice();
const showFinalize =
Expand Down Expand Up @@ -73,14 +68,14 @@ const Footer = ({
</Section>
</Stack.Col>
</div>
{footerCenter}
<footerCenterTunnel.Out />
<div
className={clsx("layer-ui__wrapper__footer-right zen-mode-transition", {
"transition-right disable-pointerEvents": appState.zenModeEnabled,
})}
>
<div style={{ position: "relative" }}>
{welcomeScreenHelp}
{renderWelcomeScreen && <welcomeScreenHelpHintTunnel.Out />}
<HelpButton
onClick={() => actionManager.executeAction(actionShortcuts)}
/>
Expand Down
19 changes: 11 additions & 8 deletions src/components/footer/FooterCenter.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import clsx from "clsx";
import { useExcalidrawAppState } from "../App";
import { footerCenterTunnel } from "../tunnels";
import "./FooterCenter.scss";

const FooterCenter = ({ children }: { children?: React.ReactNode }) => {
const appState = useExcalidrawAppState();
return (
<div
className={clsx("footer-center zen-mode-transition", {
"layer-ui__wrapper__footer-left--transition-bottom":
appState.zenModeEnabled,
})}
>
{children}
</div>
<footerCenterTunnel.In>
<div
className={clsx("footer-center zen-mode-transition", {
"layer-ui__wrapper__footer-left--transition-bottom":
appState.zenModeEnabled,
})}
>
{children}
</div>
</footerCenterTunnel.In>
);
};

Expand Down
Loading

0 comments on commit e6de1fe

Please sign in to comment.