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";
|
2021-11-17 23:53:43 +05:30
|
|
|
import { AppProps, AppState, ExcalidrawProps, BinaryFiles } from "../types";
|
2021-01-05 20:06:14 +02:00
|
|
|
import { muteFSAbortError } from "../utils";
|
2022-08-22 16:09:24 +05:30
|
|
|
import { SelectedShapeActions, ShapesSwitcher } from "./Actions";
|
2020-12-05 20:00:53 +05:30
|
|
|
import CollabButton from "./CollabButton";
|
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";
|
2022-11-01 17:29:58 +01:00
|
|
|
import { isMenuOpenAtom, useDevice } from "../components/App";
|
2022-06-21 20:03:23 +05:00
|
|
|
import { Stats } from "./Stats";
|
|
|
|
import { actionToggleStats } from "../actions/actionToggleStats";
|
2022-08-22 16:09:24 +05:30
|
|
|
import Footer from "./Footer";
|
2022-11-01 17:29:58 +01:00
|
|
|
import {
|
|
|
|
ExportImageIcon,
|
|
|
|
HamburgerMenuIcon,
|
|
|
|
WelcomeScreenMenuArrow,
|
|
|
|
WelcomeScreenTopToolbarArrow,
|
|
|
|
} from "./icons";
|
|
|
|
import { MenuLinks, Separator } from "./MenuUtils";
|
|
|
|
import { useOutsideClickHook } from "../hooks/useOutsideClick";
|
|
|
|
import WelcomeScreen from "./WelcomeScreen";
|
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";
|
2022-11-01 17:29:58 +01:00
|
|
|
import { LanguageList } from "../excalidraw-app/components/LanguageList";
|
|
|
|
import WelcomeScreenDecor from "./WelcomeScreenDecor";
|
|
|
|
import { getShortcutFromShortcutName } from "../actions/shortcuts";
|
|
|
|
import MenuItem from "./MenuItem";
|
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-12-05 20:00:53 +05:30
|
|
|
onCollabButtonClick?: () => void;
|
2020-03-24 19:51:49 +09:00
|
|
|
onLockToggle: () => 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"];
|
2020-12-05 20:00:53 +05:30
|
|
|
isCollaborating: boolean;
|
2022-06-21 20:03:23 +05:00
|
|
|
renderTopRightUI?: ExcalidrawProps["renderTopRightUI"];
|
|
|
|
renderCustomFooter?: ExcalidrawProps["renderFooter"];
|
|
|
|
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;
|
2020-03-07 10:20:38 -05:00
|
|
|
}
|
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-12-05 20:00:53 +05:30
|
|
|
onCollabButtonClick,
|
2020-04-18 01:54:19 +05:30
|
|
|
onLockToggle,
|
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,
|
2020-12-05 20:00:53 +05:30
|
|
|
isCollaborating,
|
2021-05-13 21:02:59 +05:30
|
|
|
renderTopRightUI,
|
2021-01-04 02:21:52 +05:30
|
|
|
renderCustomFooter,
|
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,
|
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}
|
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 [isMenuOpen, setIsMenuOpen] = useAtom(isMenuOpenAtom);
|
|
|
|
const menuRef = useOutsideClickHook(() => setIsMenuOpen(false));
|
2021-05-25 21:37:14 +02:00
|
|
|
|
2022-11-01 17:29:58 +01:00
|
|
|
const renderCanvasActions = () => (
|
|
|
|
<div style={{ position: "relative" }}>
|
|
|
|
<WelcomeScreenDecor
|
|
|
|
shouldRender={renderWelcomeScreen && !appState.isLoading}
|
|
|
|
>
|
|
|
|
<div className="virgil WelcomeScreen-decor WelcomeScreen-decor--menu-pointer">
|
|
|
|
{WelcomeScreenMenuArrow}
|
|
|
|
<div>{t("welcomeScreen.menuHints")}</div>
|
|
|
|
</div>
|
|
|
|
</WelcomeScreenDecor>
|
|
|
|
|
|
|
|
<button
|
|
|
|
data-prevent-outside-click
|
|
|
|
className={clsx("menu-button", "zen-mode-transition", {
|
2022-07-14 16:13:10 +05:30
|
|
|
"transition-left": appState.zenModeEnabled,
|
2021-02-02 02:26:42 +05:30
|
|
|
})}
|
2022-11-01 17:29:58 +01:00
|
|
|
onClick={() => setIsMenuOpen(!isMenuOpen)}
|
|
|
|
type="button"
|
2021-02-02 02:26:42 +05:30
|
|
|
>
|
2022-11-01 17:29:58 +01:00
|
|
|
{HamburgerMenuIcon}
|
|
|
|
</button>
|
2021-10-21 17:35:28 +05:30
|
|
|
|
2022-11-01 17:29:58 +01:00
|
|
|
{isMenuOpen && (
|
|
|
|
<div
|
|
|
|
ref={menuRef}
|
|
|
|
style={{ position: "absolute", top: "100%", marginTop: ".25rem" }}
|
|
|
|
>
|
|
|
|
<Section heading="canvasActions">
|
|
|
|
{/* the zIndex ensures this menu has higher stacking order,
|
2020-04-18 01:54:19 +05:30
|
|
|
see https://github.com/excalidraw/excalidraw/pull/1445 */}
|
2022-11-01 17:29:58 +01:00
|
|
|
<Island
|
|
|
|
className="menu-container"
|
|
|
|
padding={2}
|
|
|
|
style={{ zIndex: 1 }}
|
|
|
|
>
|
2022-11-01 22:25:12 +01:00
|
|
|
{!appState.viewModeEnabled &&
|
|
|
|
actionManager.renderAction("loadScene")}
|
2022-11-01 17:29:58 +01:00
|
|
|
{/* // TODO barnabasmolnar/editor-redesign */}
|
|
|
|
{/* is this fine here? */}
|
|
|
|
{appState.fileHandle &&
|
|
|
|
actionManager.renderAction("saveToActiveFile")}
|
|
|
|
{renderJSONExportDialog()}
|
|
|
|
<MenuItem
|
|
|
|
label={t("buttons.exportImage")}
|
|
|
|
icon={ExportImageIcon}
|
|
|
|
dataTestId="image-export-button"
|
|
|
|
onClick={() => setAppState({ openDialog: "imageExport" })}
|
|
|
|
shortcut={getShortcutFromShortcutName("imageExport")}
|
2020-12-05 20:00:53 +05:30
|
|
|
/>
|
2022-11-01 17:29:58 +01:00
|
|
|
{onCollabButtonClick && (
|
|
|
|
<CollabButton
|
|
|
|
isCollaborating={isCollaborating}
|
|
|
|
collaboratorCount={appState.collaborators.size}
|
|
|
|
onClick={onCollabButtonClick}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
{actionManager.renderAction("toggleShortcuts", undefined, true)}
|
2022-11-01 22:25:12 +01:00
|
|
|
{!appState.viewModeEnabled &&
|
|
|
|
actionManager.renderAction("clearCanvas")}
|
2022-11-01 17:29:58 +01:00
|
|
|
<Separator />
|
|
|
|
<MenuLinks />
|
|
|
|
<Separator />
|
|
|
|
<div
|
|
|
|
style={{
|
|
|
|
display: "flex",
|
|
|
|
flexDirection: "column",
|
|
|
|
rowGap: ".5rem",
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<div>{actionManager.renderAction("toggleTheme")}</div>
|
|
|
|
<div style={{ padding: "0 0.625rem" }}>
|
|
|
|
<LanguageList style={{ width: "100%" }} />
|
|
|
|
</div>
|
2022-11-01 22:25:12 +01:00
|
|
|
{!appState.viewModeEnabled && (
|
|
|
|
<div>
|
|
|
|
<div style={{ fontSize: ".75rem", marginBottom: ".5rem" }}>
|
|
|
|
{t("labels.canvasBackground")}
|
|
|
|
</div>
|
|
|
|
<div style={{ padding: "0 0.625rem" }}>
|
|
|
|
{actionManager.renderAction("changeViewBackgroundColor")}
|
|
|
|
</div>
|
2022-11-01 17:29:58 +01:00
|
|
|
</div>
|
2022-11-01 22:25:12 +01:00
|
|
|
)}
|
2022-11-01 17:29:58 +01:00
|
|
|
</div>
|
|
|
|
</Island>
|
|
|
|
</Section>
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</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">
|
2022-11-01 17:29:58 +01:00
|
|
|
{renderWelcomeScreen && !appState.isLoading && (
|
2022-11-01 22:25:12 +01:00
|
|
|
<WelcomeScreen appState={appState} actionManager={actionManager} />
|
2022-11-01 17:29:58 +01:00
|
|
|
)}
|
2020-04-18 01:54:19 +05:30
|
|
|
<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" }}>
|
|
|
|
<WelcomeScreenDecor
|
|
|
|
shouldRender={renderWelcomeScreen && !appState.isLoading}
|
2021-12-15 15:31:44 +01:00
|
|
|
>
|
2022-11-01 17:29:58 +01:00
|
|
|
<div className="virgil WelcomeScreen-decor WelcomeScreen-decor--top-toolbar-pointer">
|
|
|
|
<div className="WelcomeScreen-decor--top-toolbar-pointer__label">
|
|
|
|
{t("welcomeScreen.toolbarHints")}
|
|
|
|
</div>
|
|
|
|
{WelcomeScreenTopToolbarArrow}
|
|
|
|
</div>
|
|
|
|
</WelcomeScreenDecor>
|
|
|
|
|
|
|
|
<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
|
|
|
|
zenModeEnabled={appState.zenModeEnabled}
|
|
|
|
checked={appState.activeTool.locked}
|
|
|
|
onChange={() => onLockToggle()}
|
|
|
|
title={t("toolBar.lock")}
|
|
|
|
/>
|
|
|
|
<div className="App-toolbar__divider"></div>
|
|
|
|
|
|
|
|
<ShapesSwitcher
|
|
|
|
appState={appState}
|
|
|
|
canvas={canvas}
|
|
|
|
activeTool={appState.activeTool}
|
|
|
|
setAppState={setAppState}
|
|
|
|
onImageAction={({ pointerType }) => {
|
|
|
|
onImageAction({
|
|
|
|
insertOnCanvasDirectly: pointerType !== "mouse",
|
|
|
|
});
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
{/* {actionManager.renderAction("eraser", {
|
|
|
|
// size: "small",
|
|
|
|
})} */}
|
|
|
|
</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
|
|
|
>
|
2022-06-15 15:35:57 +02:00
|
|
|
<UserList
|
|
|
|
collaborators={appState.collaborators}
|
|
|
|
actionManager={actionManager}
|
|
|
|
/>
|
2022-11-01 17:29:58 +01:00
|
|
|
{onCollabButtonClick && (
|
|
|
|
<CollabButton
|
|
|
|
isInHamburgerMenu={false}
|
|
|
|
isCollaborating={isCollaborating}
|
|
|
|
collaboratorCount={appState.collaborators.size}
|
|
|
|
onClick={onCollabButtonClick}
|
|
|
|
/>
|
|
|
|
)}
|
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
|
|
|
<>
|
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
|
|
|
)}
|
2022-11-01 17:29:58 +01:00
|
|
|
{renderImageExportDialog()}
|
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-11-01 17:29:58 +01:00
|
|
|
renderWelcomeScreen={renderWelcomeScreen}
|
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}
|
|
|
|
onCollabButtonClick={onCollabButtonClick}
|
|
|
|
onLockToggle={() => onLockToggle()}
|
|
|
|
onPenModeToggle={onPenModeToggle}
|
|
|
|
canvas={canvas}
|
|
|
|
isCollaborating={isCollaborating}
|
2022-08-22 16:09:24 +05:30
|
|
|
renderCustomFooter={renderCustomFooter}
|
2022-08-26 11:46:34 +05:30
|
|
|
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)` }
|
|
|
|
: {}
|
|
|
|
}
|
|
|
|
>
|
|
|
|
{renderFixedSideContainer()}
|
|
|
|
<Footer
|
2022-11-01 17:29:58 +01:00
|
|
|
renderWelcomeScreen={renderWelcomeScreen}
|
2022-08-26 11:46:34 +05:30
|
|
|
appState={appState}
|
2022-08-29 19:26:03 +05:30
|
|
|
actionManager={actionManager}
|
|
|
|
renderCustomFooter={renderCustomFooter}
|
|
|
|
showExitZenModeBtn={showExitZenModeBtn}
|
2022-08-26 11:46:34 +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
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
const areEqual = (prev: LayerUIProps, next: LayerUIProps) => {
|
|
|
|
const getNecessaryObj = (appState: AppState): Partial<AppState> => {
|
2020-08-08 21:04:15 -07:00
|
|
|
const {
|
|
|
|
suggestedBindings,
|
|
|
|
startBoundElement: boundElement,
|
|
|
|
...ret
|
|
|
|
} = appState;
|
2020-04-18 01:54:19 +05:30
|
|
|
return ret;
|
|
|
|
};
|
|
|
|
const prevAppState = getNecessaryObj(prev.appState);
|
|
|
|
const nextAppState = getNecessaryObj(next.appState);
|
|
|
|
|
|
|
|
const keys = Object.keys(prevAppState) as (keyof Partial<AppState>)[];
|
2022-10-17 12:25:24 +02:00
|
|
|
|
2020-04-18 01:54:19 +05:30
|
|
|
return (
|
2021-03-06 14:23:25 +01:00
|
|
|
prev.renderCustomFooter === next.renderCustomFooter &&
|
2022-10-17 12:25:24 +02:00
|
|
|
prev.renderTopRightUI === next.renderTopRightUI &&
|
|
|
|
prev.renderCustomStats === next.renderCustomStats &&
|
|
|
|
prev.renderCustomSidebar === next.renderCustomSidebar &&
|
2021-01-04 02:21:52 +05:30
|
|
|
prev.langCode === next.langCode &&
|
2020-04-18 01:54:19 +05:30
|
|
|
prev.elements === next.elements &&
|
2021-10-30 23:40:35 +02:00
|
|
|
prev.files === next.files &&
|
2020-04-18 01:54:19 +05:30
|
|
|
keys.every((key) => prevAppState[key] === nextAppState[key])
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
export default React.memo(LayerUI, areEqual);
|