2021-01-05 20:06:14 +02:00
|
|
|
import clsx from "clsx";
|
2022-10-18 06:59:14 +02:00
|
|
|
import React from "react";
|
2021-01-05 20:06:14 +02:00
|
|
|
import { ActionManager } from "../actions/manager";
|
2022-06-21 20:03:23 +05:00
|
|
|
import { CLASSES, LIBRARY_SIDEBAR_WIDTH } from "../constants";
|
2020-03-07 10:20:38 -05:00
|
|
|
import { exportCanvas } from "../data";
|
2021-02-03 20:43:16 +01:00
|
|
|
import { isTextElement, showSelectedShapeActions } from "../element";
|
2020-07-27 15:29:19 +03:00
|
|
|
import { NonDeletedExcalidrawElement } from "../element/types";
|
2021-01-04 02:21:52 +05:30
|
|
|
import { Language, t } from "../i18n";
|
2022-10-18 06:59:14 +02:00
|
|
|
import { calculateScrollCenter } from "../scene";
|
2020-03-07 10:20:38 -05:00
|
|
|
import { ExportType } from "../scene/types";
|
2023-01-31 13:53:20 +01:00
|
|
|
import { AppProps, AppState, ExcalidrawProps, BinaryFiles } from "../types";
|
|
|
|
import { isShallowEqual, muteFSAbortError } from "../utils";
|
2022-08-22 16:09:24 +05:30
|
|
|
import { SelectedShapeActions, ShapesSwitcher } from "./Actions";
|
2020-04-03 12:50:51 +01:00
|
|
|
import { ErrorDialog } from "./ErrorDialog";
|
2021-05-25 21:37:14 +02:00
|
|
|
import { ExportCB, ImageExportDialog } from "./ImageExportDialog";
|
2021-01-05 20:06:14 +02:00
|
|
|
import { FixedSideContainer } from "./FixedSideContainer";
|
|
|
|
import { HintViewer } from "./HintViewer";
|
|
|
|
import { Island } from "./Island";
|
|
|
|
import { LoadingMessage } from "./LoadingMessage";
|
2021-06-11 23:13:05 +02:00
|
|
|
import { LockButton } from "./LockButton";
|
2021-01-05 20:06:14 +02:00
|
|
|
import { MobileMenu } from "./MobileMenu";
|
2020-12-27 18:26:30 +02:00
|
|
|
import { PasteChartDialog } from "./PasteChartDialog";
|
2021-01-05 20:06:14 +02:00
|
|
|
import { Section } from "./Section";
|
2021-01-17 17:46:23 +01:00
|
|
|
import { HelpDialog } from "./HelpDialog";
|
2021-01-05 20:06:14 +02:00
|
|
|
import Stack from "./Stack";
|
|
|
|
import { UserList } from "./UserList";
|
2022-10-18 06:59:14 +02:00
|
|
|
import Library from "../data/library";
|
2021-05-25 21:37:14 +02:00
|
|
|
import { JSONExportDialog } from "./JSONExportDialog";
|
2021-06-11 23:13:05 +02:00
|
|
|
import { LibraryButton } from "./LibraryButton";
|
2021-07-15 09:54:26 -04:00
|
|
|
import { isImageFileHandle } from "../data/blob";
|
2021-11-17 23:53:43 +05:30
|
|
|
import { LibraryMenu } from "./LibraryMenu";
|
2020-03-07 10:20:38 -05:00
|
|
|
|
2021-12-15 15:31:44 +01:00
|
|
|
import "./LayerUI.scss";
|
|
|
|
import "./Toolbar.scss";
|
2022-02-02 14:31:38 +01:00
|
|
|
import { PenModeButton } from "./PenModeButton";
|
2022-03-28 14:46:40 +02:00
|
|
|
import { trackEvent } from "../analytics";
|
2023-01-05 22:04:23 +05:30
|
|
|
import { useDevice } from "../components/App";
|
2022-06-21 20:03:23 +05:00
|
|
|
import { Stats } from "./Stats";
|
|
|
|
import { actionToggleStats } from "../actions/actionToggleStats";
|
2022-12-21 14:29:06 +05:30
|
|
|
import Footer from "./footer/Footer";
|
2022-10-18 06:59:14 +02:00
|
|
|
import { hostSidebarCountersAtom } from "./Sidebar/Sidebar";
|
2022-10-17 12:25:24 +02:00
|
|
|
import { jotaiScope } from "../jotai";
|
|
|
|
import { useAtom } from "jotai";
|
2023-01-12 20:40:09 +05:30
|
|
|
import MainMenu from "./main-menu/MainMenu";
|
2023-01-23 16:54:35 +01:00
|
|
|
import { ActiveConfirmDialog } from "./ActiveConfirmDialog";
|
2023-01-23 16:12:28 +01:00
|
|
|
import { HandButton } from "./HandButton";
|
|
|
|
import { isHandToolActive } from "../appState";
|
2023-01-31 13:53:20 +01:00
|
|
|
import {
|
|
|
|
mainMenuTunnel,
|
|
|
|
welcomeScreenMenuHintTunnel,
|
|
|
|
welcomeScreenToolbarHintTunnel,
|
|
|
|
welcomeScreenCenterTunnel,
|
|
|
|
} from "./tunnels";
|
2021-12-15 15:31:44 +01:00
|
|
|
|
2020-03-07 10:20:38 -05:00
|
|
|
interface LayerUIProps {
|
|
|
|
actionManager: ActionManager;
|
|
|
|
appState: AppState;
|
2021-10-21 22:05:48 +02:00
|
|
|
files: BinaryFiles;
|
2020-03-07 10:20:38 -05:00
|
|
|
canvas: HTMLCanvasElement | null;
|
2020-10-16 11:53:40 +02:00
|
|
|
setAppState: React.Component<any, AppState>["setState"];
|
2020-04-08 09:49:52 -07:00
|
|
|
elements: readonly NonDeletedExcalidrawElement[];
|
2020-03-24 19:51:49 +09:00
|
|
|
onLockToggle: () => void;
|
2023-01-23 16:12:28 +01:00
|
|
|
onHandToolToggle: () => void;
|
2022-02-02 14:31:38 +01:00
|
|
|
onPenModeToggle: () => void;
|
2020-12-27 18:26:30 +02:00
|
|
|
onInsertElements: (elements: readonly NonDeletedExcalidrawElement[]) => void;
|
2021-02-06 21:22:28 +05:30
|
|
|
showExitZenModeBtn: boolean;
|
2021-01-04 02:21:52 +05:30
|
|
|
langCode: Language["code"];
|
2022-06-21 20:03:23 +05:00
|
|
|
renderTopRightUI?: ExcalidrawProps["renderTopRightUI"];
|
|
|
|
renderCustomStats?: ExcalidrawProps["renderCustomStats"];
|
2022-10-17 12:25:24 +02:00
|
|
|
renderCustomSidebar?: ExcalidrawProps["renderSidebar"];
|
2021-03-13 12:35:35 +01:00
|
|
|
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
|
2021-04-04 15:57:14 +05:30
|
|
|
UIOptions: AppProps["UIOptions"];
|
2021-04-13 01:29:25 +05:30
|
|
|
focusContainer: () => void;
|
2021-04-21 23:38:24 +05:30
|
|
|
library: Library;
|
|
|
|
id: string;
|
2021-10-21 22:05:48 +02:00
|
|
|
onImageAction: (data: { insertOnCanvasDirectly: boolean }) => void;
|
2022-11-01 17:29:58 +01:00
|
|
|
renderWelcomeScreen: boolean;
|
2022-12-21 14:29:06 +05:30
|
|
|
children?: React.ReactNode;
|
2020-03-07 10:20:38 -05:00
|
|
|
}
|
2022-12-21 14:29:06 +05:30
|
|
|
|
2023-01-31 13:53:20 +01:00
|
|
|
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>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2020-04-18 01:54:19 +05:30
|
|
|
const LayerUI = ({
|
|
|
|
actionManager,
|
|
|
|
appState,
|
2021-10-21 22:05:48 +02:00
|
|
|
files,
|
2020-04-18 01:54:19 +05:30
|
|
|
setAppState,
|
|
|
|
elements,
|
2022-08-20 22:49:44 +05:30
|
|
|
canvas,
|
2020-04-18 01:54:19 +05:30
|
|
|
onLockToggle,
|
2023-01-23 16:12:28 +01:00
|
|
|
onHandToolToggle,
|
2022-02-02 14:31:38 +01:00
|
|
|
onPenModeToggle,
|
2020-12-27 18:26:30 +02:00
|
|
|
onInsertElements,
|
2021-02-06 21:22:28 +05:30
|
|
|
showExitZenModeBtn,
|
2021-05-13 21:02:59 +05:30
|
|
|
renderTopRightUI,
|
2022-06-21 20:03:23 +05:00
|
|
|
renderCustomStats,
|
2022-10-17 12:25:24 +02:00
|
|
|
renderCustomSidebar,
|
2021-03-13 12:35:35 +01:00
|
|
|
libraryReturnUrl,
|
2021-04-04 15:57:14 +05:30
|
|
|
UIOptions,
|
2021-04-13 01:29:25 +05:30
|
|
|
focusContainer,
|
2021-04-21 23:38:24 +05:30
|
|
|
library,
|
|
|
|
id,
|
2021-10-21 22:05:48 +02:00
|
|
|
onImageAction,
|
2022-11-01 17:29:58 +01:00
|
|
|
renderWelcomeScreen,
|
2022-12-21 14:29:06 +05:30
|
|
|
children,
|
2020-04-18 01:54:19 +05:30
|
|
|
}: LayerUIProps) => {
|
2022-06-21 20:03:23 +05:00
|
|
|
const device = useDevice();
|
2020-03-07 10:20:38 -05:00
|
|
|
|
2021-05-25 21:37:14 +02:00
|
|
|
const renderJSONExportDialog = () => {
|
|
|
|
if (!UIOptions.canvasActions.export) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<JSONExportDialog
|
|
|
|
elements={elements}
|
|
|
|
appState={appState}
|
2021-10-21 22:05:48 +02:00
|
|
|
files={files}
|
2021-05-25 21:37:14 +02:00
|
|
|
actionManager={actionManager}
|
2021-05-29 02:56:25 +05:30
|
|
|
exportOpts={UIOptions.canvasActions.export}
|
2021-05-30 00:37:38 +05:30
|
|
|
canvas={canvas}
|
2023-01-05 22:04:23 +05:30
|
|
|
setAppState={setAppState}
|
2021-05-25 21:37:14 +02:00
|
|
|
/>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
const renderImageExportDialog = () => {
|
2021-05-29 19:41:50 +05:30
|
|
|
if (!UIOptions.canvasActions.saveAsImage) {
|
2021-04-04 15:57:14 +05:30
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2021-11-01 15:24:05 +02:00
|
|
|
const createExporter =
|
|
|
|
(type: ExportType): ExportCB =>
|
|
|
|
async (exportedElements) => {
|
2022-03-28 14:46:40 +02:00
|
|
|
trackEvent("export", type, "ui");
|
2021-11-01 15:24:05 +02:00
|
|
|
const fileHandle = await exportCanvas(
|
|
|
|
type,
|
|
|
|
exportedElements,
|
|
|
|
appState,
|
|
|
|
files,
|
|
|
|
{
|
|
|
|
exportBackground: appState.exportBackground,
|
|
|
|
name: appState.name,
|
|
|
|
viewBackgroundColor: appState.viewBackgroundColor,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
.catch(muteFSAbortError)
|
|
|
|
.catch((error) => {
|
|
|
|
console.error(error);
|
|
|
|
setAppState({ errorMessage: error.message });
|
|
|
|
});
|
|
|
|
|
|
|
|
if (
|
|
|
|
appState.exportEmbedScene &&
|
|
|
|
fileHandle &&
|
|
|
|
isImageFileHandle(fileHandle)
|
|
|
|
) {
|
|
|
|
setAppState({ fileHandle });
|
|
|
|
}
|
|
|
|
};
|
2020-12-20 19:44:04 +05:30
|
|
|
|
2020-04-18 01:54:19 +05:30
|
|
|
return (
|
2021-05-25 21:37:14 +02:00
|
|
|
<ImageExportDialog
|
2020-03-07 10:20:38 -05:00
|
|
|
elements={elements}
|
2020-04-18 01:54:19 +05:30
|
|
|
appState={appState}
|
2022-11-01 17:29:58 +01:00
|
|
|
setAppState={setAppState}
|
2021-10-21 22:05:48 +02:00
|
|
|
files={files}
|
2020-03-07 10:20:38 -05:00
|
|
|
actionManager={actionManager}
|
2020-04-18 01:54:19 +05:30
|
|
|
onExportToPng={createExporter("png")}
|
|
|
|
onExportToSvg={createExporter("svg")}
|
|
|
|
onExportToClipboard={createExporter("clipboard")}
|
2020-03-07 10:20:38 -05:00
|
|
|
/>
|
2020-04-18 01:54:19 +05:30
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2022-11-01 17:29:58 +01:00
|
|
|
const renderCanvasActions = () => (
|
|
|
|
<div style={{ position: "relative" }}>
|
2023-01-12 15:49:28 +01:00
|
|
|
{/* wrapping to Fragment stops React from occasionally complaining
|
|
|
|
about identical Keys */}
|
2023-01-31 13:53:20 +01:00
|
|
|
<mainMenuTunnel.Out />
|
|
|
|
{renderWelcomeScreen && <welcomeScreenMenuHintTunnel.Out />}
|
2022-11-01 17:29:58 +01:00
|
|
|
</div>
|
2020-04-18 01:54:19 +05:30
|
|
|
);
|
|
|
|
|
|
|
|
const renderSelectedShapeActions = () => (
|
2020-04-25 18:43:02 +05:30
|
|
|
<Section
|
|
|
|
heading="selectedShapeActions"
|
2022-11-01 17:29:58 +01:00
|
|
|
className={clsx("selected-shape-actions zen-mode-transition", {
|
2022-07-14 16:13:10 +05:30
|
|
|
"transition-left": appState.zenModeEnabled,
|
2020-10-19 17:14:28 +03:00
|
|
|
})}
|
2020-04-25 18:43:02 +05:30
|
|
|
>
|
2021-02-14 18:18:34 +05:30
|
|
|
<Island
|
|
|
|
className={CLASSES.SHAPE_ACTIONS_MENU}
|
|
|
|
padding={2}
|
|
|
|
style={{
|
2022-11-01 17:29:58 +01:00
|
|
|
// we want to make sure this doesn't overflow so subtracting the
|
|
|
|
// approximate height of hamburgerMenu + footer
|
|
|
|
maxHeight: `${appState.height - 166}px`,
|
2021-02-14 18:18:34 +05:30
|
|
|
}}
|
|
|
|
>
|
2020-04-18 01:54:19 +05:30
|
|
|
<SelectedShapeActions
|
|
|
|
appState={appState}
|
|
|
|
elements={elements}
|
|
|
|
renderAction={actionManager.renderAction}
|
|
|
|
/>
|
|
|
|
</Island>
|
|
|
|
</Section>
|
|
|
|
);
|
|
|
|
|
|
|
|
const renderFixedSideContainer = () => {
|
|
|
|
const shouldRenderSelectedShapeActions = showSelectedShapeActions(
|
|
|
|
appState,
|
|
|
|
elements,
|
|
|
|
);
|
2020-07-20 00:12:56 +03:00
|
|
|
|
2020-04-18 01:54:19 +05:30
|
|
|
return (
|
|
|
|
<FixedSideContainer side="top">
|
|
|
|
<div className="App-menu App-menu_top">
|
2020-04-29 22:49:36 +02:00
|
|
|
<Stack.Col
|
2022-11-01 17:29:58 +01:00
|
|
|
gap={6}
|
2022-11-01 22:25:12 +01:00
|
|
|
className={clsx("App-menu_top__left", {
|
2022-07-14 16:13:10 +05:30
|
|
|
"disable-pointerEvents": appState.zenModeEnabled,
|
|
|
|
})}
|
2020-04-29 22:49:36 +02:00
|
|
|
>
|
2022-11-01 17:29:58 +01:00
|
|
|
{renderCanvasActions()}
|
2020-04-18 01:54:19 +05:30
|
|
|
{shouldRenderSelectedShapeActions && renderSelectedShapeActions()}
|
|
|
|
</Stack.Col>
|
2022-07-14 16:13:10 +05:30
|
|
|
{!appState.viewModeEnabled && (
|
2022-11-01 17:29:58 +01:00
|
|
|
<Section heading="shapes" className="shapes-section">
|
2022-07-22 11:20:36 +05:30
|
|
|
{(heading: React.ReactNode) => (
|
2022-11-01 17:29:58 +01:00
|
|
|
<div style={{ position: "relative" }}>
|
2023-01-31 13:53:20 +01:00
|
|
|
{renderWelcomeScreen && (
|
|
|
|
<welcomeScreenToolbarHintTunnel.Out />
|
|
|
|
)}
|
2022-11-01 17:29:58 +01:00
|
|
|
<Stack.Col gap={4} align="start">
|
|
|
|
<Stack.Row
|
|
|
|
gap={1}
|
|
|
|
className={clsx("App-toolbar-container", {
|
2022-07-14 16:13:10 +05:30
|
|
|
"zen-mode": appState.zenModeEnabled,
|
2021-12-15 15:31:44 +01:00
|
|
|
})}
|
2021-02-02 02:26:42 +05:30
|
|
|
>
|
2022-11-01 17:29:58 +01:00
|
|
|
<Island
|
|
|
|
padding={1}
|
|
|
|
className={clsx("App-toolbar", {
|
|
|
|
"zen-mode": appState.zenModeEnabled,
|
|
|
|
})}
|
|
|
|
>
|
|
|
|
<HintViewer
|
2022-03-15 20:56:39 +05:30
|
|
|
appState={appState}
|
2022-11-01 17:29:58 +01:00
|
|
|
elements={elements}
|
|
|
|
isMobile={device.isMobile}
|
|
|
|
device={device}
|
2021-02-02 02:26:42 +05:30
|
|
|
/>
|
2022-11-01 17:29:58 +01:00
|
|
|
{heading}
|
|
|
|
<Stack.Row gap={1}>
|
|
|
|
<PenModeButton
|
|
|
|
zenModeEnabled={appState.zenModeEnabled}
|
|
|
|
checked={appState.penMode}
|
|
|
|
onChange={onPenModeToggle}
|
|
|
|
title={t("toolBar.penMode")}
|
|
|
|
penDetected={appState.penDetected}
|
|
|
|
/>
|
|
|
|
<LockButton
|
|
|
|
checked={appState.activeTool.locked}
|
2023-01-23 16:12:28 +01:00
|
|
|
onChange={onLockToggle}
|
2022-11-01 17:29:58 +01:00
|
|
|
title={t("toolBar.lock")}
|
|
|
|
/>
|
2023-01-23 16:12:28 +01:00
|
|
|
|
2022-11-01 17:29:58 +01:00
|
|
|
<div className="App-toolbar__divider"></div>
|
|
|
|
|
2023-01-23 16:12:28 +01:00
|
|
|
<HandButton
|
|
|
|
checked={isHandToolActive(appState)}
|
|
|
|
onChange={() => onHandToolToggle()}
|
|
|
|
title={t("toolBar.hand")}
|
|
|
|
isMobile
|
|
|
|
/>
|
|
|
|
|
2022-11-01 17:29:58 +01:00
|
|
|
<ShapesSwitcher
|
|
|
|
appState={appState}
|
|
|
|
canvas={canvas}
|
|
|
|
activeTool={appState.activeTool}
|
|
|
|
setAppState={setAppState}
|
|
|
|
onImageAction={({ pointerType }) => {
|
|
|
|
onImageAction({
|
|
|
|
insertOnCanvasDirectly: pointerType !== "mouse",
|
|
|
|
});
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
</Stack.Row>
|
|
|
|
</Island>
|
|
|
|
</Stack.Row>
|
|
|
|
</Stack.Col>
|
|
|
|
</div>
|
2021-02-02 02:26:42 +05:30
|
|
|
)}
|
|
|
|
</Section>
|
|
|
|
)}
|
2021-05-06 21:00:17 +02:00
|
|
|
<div
|
|
|
|
className={clsx(
|
|
|
|
"layer-ui__wrapper__top-right zen-mode-transition",
|
|
|
|
{
|
2022-07-14 16:13:10 +05:30
|
|
|
"transition-right": appState.zenModeEnabled,
|
2021-05-06 21:00:17 +02:00
|
|
|
},
|
|
|
|
)}
|
2020-06-19 11:36:49 +01:00
|
|
|
>
|
2023-01-05 22:04:23 +05:30
|
|
|
<UserList collaborators={appState.collaborators} />
|
2022-06-21 20:03:23 +05:00
|
|
|
{renderTopRightUI?.(device.isMobile, appState)}
|
2022-11-01 22:25:12 +01:00
|
|
|
{!appState.viewModeEnabled && (
|
|
|
|
<LibraryButton appState={appState} setAppState={setAppState} />
|
|
|
|
)}
|
2021-05-06 21:00:17 +02:00
|
|
|
</div>
|
2020-04-18 01:54:19 +05:30
|
|
|
</div>
|
|
|
|
</FixedSideContainer>
|
2020-03-07 10:20:38 -05:00
|
|
|
);
|
2020-04-18 01:54:19 +05:30
|
|
|
};
|
2020-03-07 10:20:38 -05:00
|
|
|
|
2022-10-18 06:59:14 +02:00
|
|
|
const renderSidebars = () => {
|
|
|
|
return appState.openSidebar === "customSidebar" ? (
|
|
|
|
renderCustomSidebar?.() || null
|
|
|
|
) : appState.openSidebar === "library" ? (
|
|
|
|
<LibraryMenu
|
|
|
|
appState={appState}
|
|
|
|
onInsertElements={onInsertElements}
|
|
|
|
libraryReturnUrl={libraryReturnUrl}
|
|
|
|
focusContainer={focusContainer}
|
|
|
|
library={library}
|
|
|
|
id={id}
|
|
|
|
/>
|
|
|
|
) : null;
|
|
|
|
};
|
|
|
|
|
2022-10-17 12:25:24 +02:00
|
|
|
const [hostSidebarCounters] = useAtom(hostSidebarCountersAtom, jotaiScope);
|
|
|
|
|
2022-08-26 11:46:34 +05:30
|
|
|
return (
|
2020-12-27 18:26:30 +02:00
|
|
|
<>
|
2023-01-31 13:53:20 +01:00
|
|
|
{/* ------------------------- 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} />
|
|
|
|
{/* ------------------------------------------------------------------ */}
|
|
|
|
|
2022-04-19 19:08:13 +02:00
|
|
|
{appState.isLoading && <LoadingMessage delay={250} />}
|
2020-04-18 01:54:19 +05:30
|
|
|
{appState.errorMessage && (
|
|
|
|
<ErrorDialog
|
|
|
|
message={appState.errorMessage}
|
|
|
|
onClose={() => setAppState({ errorMessage: null })}
|
|
|
|
/>
|
|
|
|
)}
|
2022-11-01 17:29:58 +01:00
|
|
|
{appState.openDialog === "help" && (
|
2021-04-13 01:29:25 +05:30
|
|
|
<HelpDialog
|
|
|
|
onClose={() => {
|
2022-11-01 17:29:58 +01:00
|
|
|
setAppState({ openDialog: null });
|
2021-04-13 01:29:25 +05:30
|
|
|
}}
|
|
|
|
/>
|
2020-04-18 01:54:19 +05:30
|
|
|
)}
|
2023-01-23 16:54:35 +01:00
|
|
|
<ActiveConfirmDialog />
|
2022-11-01 17:29:58 +01:00
|
|
|
{renderImageExportDialog()}
|
2023-01-05 22:04:23 +05:30
|
|
|
{renderJSONExportDialog()}
|
2020-12-27 18:26:30 +02:00
|
|
|
{appState.pasteDialog.shown && (
|
|
|
|
<PasteChartDialog
|
|
|
|
setAppState={setAppState}
|
|
|
|
appState={appState}
|
|
|
|
onInsertChart={onInsertElements}
|
|
|
|
onClose={() =>
|
|
|
|
setAppState({
|
|
|
|
pasteDialog: { shown: false, data: null },
|
|
|
|
})
|
|
|
|
}
|
|
|
|
/>
|
|
|
|
)}
|
2022-08-26 11:46:34 +05:30
|
|
|
{device.isMobile && (
|
|
|
|
<MobileMenu
|
2022-08-22 16:09:24 +05:30
|
|
|
appState={appState}
|
2022-08-26 11:46:34 +05:30
|
|
|
elements={elements}
|
2022-08-22 16:09:24 +05:30
|
|
|
actionManager={actionManager}
|
2022-08-26 11:46:34 +05:30
|
|
|
renderJSONExportDialog={renderJSONExportDialog}
|
|
|
|
renderImageExportDialog={renderImageExportDialog}
|
|
|
|
setAppState={setAppState}
|
2023-01-23 16:12:28 +01:00
|
|
|
onLockToggle={onLockToggle}
|
|
|
|
onHandToolToggle={onHandToolToggle}
|
2022-08-26 11:46:34 +05:30
|
|
|
onPenModeToggle={onPenModeToggle}
|
|
|
|
canvas={canvas}
|
|
|
|
onImageAction={onImageAction}
|
|
|
|
renderTopRightUI={renderTopRightUI}
|
|
|
|
renderCustomStats={renderCustomStats}
|
2022-10-18 06:59:14 +02:00
|
|
|
renderSidebars={renderSidebars}
|
2022-10-17 12:25:24 +02:00
|
|
|
device={device}
|
2022-08-22 16:09:24 +05:30
|
|
|
/>
|
2022-08-26 11:46:34 +05:30
|
|
|
)}
|
|
|
|
|
|
|
|
{!device.isMobile && (
|
2022-08-29 19:26:03 +05:30
|
|
|
<>
|
|
|
|
<div
|
|
|
|
className={clsx("layer-ui__wrapper", {
|
|
|
|
"disable-pointerEvents":
|
|
|
|
appState.draggingElement ||
|
|
|
|
appState.resizingElement ||
|
|
|
|
(appState.editingElement &&
|
|
|
|
!isTextElement(appState.editingElement)),
|
|
|
|
})}
|
|
|
|
style={
|
2022-10-17 12:25:24 +02:00
|
|
|
((appState.openSidebar === "library" &&
|
|
|
|
appState.isSidebarDocked) ||
|
|
|
|
hostSidebarCounters.docked) &&
|
2022-08-29 19:26:03 +05:30
|
|
|
device.canDeviceFitSidebar
|
|
|
|
? { width: `calc(100% - ${LIBRARY_SIDEBAR_WIDTH}px)` }
|
|
|
|
: {}
|
|
|
|
}
|
|
|
|
>
|
2023-01-31 13:53:20 +01:00
|
|
|
{renderWelcomeScreen && <welcomeScreenCenterTunnel.Out />}
|
2022-08-29 19:26:03 +05:30
|
|
|
{renderFixedSideContainer()}
|
|
|
|
<Footer
|
2022-08-26 11:46:34 +05:30
|
|
|
appState={appState}
|
2022-08-29 19:26:03 +05:30
|
|
|
actionManager={actionManager}
|
|
|
|
showExitZenModeBtn={showExitZenModeBtn}
|
2023-01-31 13:53:20 +01:00
|
|
|
renderWelcomeScreen={renderWelcomeScreen}
|
2023-01-05 22:04:23 +05:30
|
|
|
/>
|
2022-08-29 19:26:03 +05:30
|
|
|
{appState.showStats && (
|
|
|
|
<Stats
|
|
|
|
appState={appState}
|
|
|
|
setAppState={setAppState}
|
|
|
|
elements={elements}
|
|
|
|
onClose={() => {
|
|
|
|
actionManager.executeAction(actionToggleStats);
|
|
|
|
}}
|
|
|
|
renderCustomStats={renderCustomStats}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
{appState.scrolledOutside && (
|
|
|
|
<button
|
|
|
|
className="scroll-back-to-content"
|
|
|
|
onClick={() => {
|
|
|
|
setAppState({
|
|
|
|
...calculateScrollCenter(elements, appState, canvas),
|
|
|
|
});
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
{t("buttons.scrollBackToContent")}
|
|
|
|
</button>
|
|
|
|
)}
|
|
|
|
</div>
|
2022-10-18 06:59:14 +02:00
|
|
|
{renderSidebars()}
|
2022-08-29 19:26:03 +05:30
|
|
|
</>
|
2022-08-26 11:46:34 +05:30
|
|
|
)}
|
2022-06-21 20:03:23 +05:00
|
|
|
</>
|
2020-04-18 01:54:19 +05:30
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2023-01-09 10:24:17 +01:00
|
|
|
const stripIrrelevantAppStateProps = (
|
|
|
|
appState: AppState,
|
|
|
|
): Partial<AppState> => {
|
|
|
|
const { suggestedBindings, startBoundElement, cursorButton, ...ret } =
|
|
|
|
appState;
|
|
|
|
return ret;
|
|
|
|
};
|
|
|
|
|
|
|
|
const areEqual = (prevProps: LayerUIProps, nextProps: LayerUIProps) => {
|
|
|
|
// short-circuit early
|
|
|
|
if (prevProps.children !== nextProps.children) {
|
|
|
|
return false;
|
|
|
|
}
|
2020-04-18 01:54:19 +05:30
|
|
|
|
2023-01-09 10:24:17 +01:00
|
|
|
const {
|
|
|
|
canvas: _prevCanvas,
|
|
|
|
// not stable, but shouldn't matter in our case
|
|
|
|
onInsertElements: _prevOnInsertElements,
|
|
|
|
appState: prevAppState,
|
|
|
|
...prev
|
|
|
|
} = prevProps;
|
|
|
|
const {
|
|
|
|
canvas: _nextCanvas,
|
|
|
|
onInsertElements: _nextOnInsertElements,
|
|
|
|
appState: nextAppState,
|
|
|
|
...next
|
|
|
|
} = nextProps;
|
2022-10-17 12:25:24 +02:00
|
|
|
|
2020-04-18 01:54:19 +05:30
|
|
|
return (
|
2023-01-09 10:24:17 +01:00
|
|
|
isShallowEqual(
|
|
|
|
stripIrrelevantAppStateProps(prevAppState),
|
|
|
|
stripIrrelevantAppStateProps(nextAppState),
|
|
|
|
) && isShallowEqual(prev, next)
|
2020-04-18 01:54:19 +05:30
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
export default React.memo(LayerUI, areEqual);
|