2020-03-07 10:20:38 -05:00
|
|
|
import React from "react";
|
|
|
|
import { AppState } from "../types";
|
|
|
|
import { ActionManager } from "../actions/manager";
|
2021-01-04 02:21:52 +05:30
|
|
|
import { t } from "../i18n";
|
2020-03-07 10:20:38 -05:00
|
|
|
import Stack from "./Stack";
|
|
|
|
import { showSelectedShapeActions } from "../element";
|
2020-04-08 09:49:52 -07:00
|
|
|
import { NonDeletedExcalidrawElement } from "../element/types";
|
2020-03-07 10:20:38 -05:00
|
|
|
import { FixedSideContainer } from "./FixedSideContainer";
|
|
|
|
import { Island } from "./Island";
|
|
|
|
import { HintViewer } from "./HintViewer";
|
2020-04-02 16:52:24 +09:00
|
|
|
import { calculateScrollCenter } from "../scene";
|
2020-03-07 10:20:38 -05:00
|
|
|
import { SelectedShapeActions, ShapesSwitcher } from "./Actions";
|
|
|
|
import { Section } from "./Section";
|
2020-12-05 20:00:53 +05:30
|
|
|
import CollabButton from "./CollabButton";
|
2020-03-18 11:31:40 -04:00
|
|
|
import { SCROLLBAR_WIDTH, SCROLLBAR_MARGIN } from "../scene/scrollbars";
|
2021-06-11 23:13:05 +02:00
|
|
|
import { LockButton } from "./LockButton";
|
2020-06-19 11:36:49 +01:00
|
|
|
import { UserList } from "./UserList";
|
2020-08-13 04:35:31 -07:00
|
|
|
import { BackgroundPickerAndDarkModeToggle } from "./BackgroundPickerAndDarkModeToggle";
|
2021-06-11 23:13:05 +02:00
|
|
|
import { LibraryButton } from "./LibraryButton";
|
2020-03-07 10:20:38 -05:00
|
|
|
|
|
|
|
type MobileMenuProps = {
|
|
|
|
appState: AppState;
|
|
|
|
actionManager: ActionManager;
|
2021-05-25 21:37:14 +02:00
|
|
|
renderJSONExportDialog: () => React.ReactNode;
|
|
|
|
renderImageExportDialog: () => React.ReactNode;
|
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-07-20 00:12:56 +03:00
|
|
|
libraryMenu: JSX.Element | null;
|
2020-12-05 20:00:53 +05:30
|
|
|
onCollabButtonClick?: () => void;
|
2020-03-24 19:51:49 +09:00
|
|
|
onLockToggle: () => void;
|
2020-05-30 17:32:32 +05:30
|
|
|
canvas: HTMLCanvasElement | null;
|
2020-12-05 20:00:53 +05:30
|
|
|
isCollaborating: boolean;
|
2021-05-15 14:49:58 +05:30
|
|
|
renderCustomFooter?: (isMobile: boolean, appState: AppState) => JSX.Element;
|
2021-02-02 02:26:42 +05:30
|
|
|
viewModeEnabled: boolean;
|
2021-03-15 11:33:46 -07:00
|
|
|
showThemeBtn: boolean;
|
2020-03-07 10:20:38 -05:00
|
|
|
};
|
|
|
|
|
2020-05-20 16:21:37 +03:00
|
|
|
export const MobileMenu = ({
|
2020-03-07 10:20:38 -05:00
|
|
|
appState,
|
|
|
|
elements,
|
2020-07-20 00:12:56 +03:00
|
|
|
libraryMenu,
|
2020-03-07 10:20:38 -05:00
|
|
|
actionManager,
|
2021-05-25 21:37:14 +02:00
|
|
|
renderJSONExportDialog,
|
|
|
|
renderImageExportDialog,
|
2020-03-07 10:20:38 -05:00
|
|
|
setAppState,
|
2020-12-05 20:00:53 +05:30
|
|
|
onCollabButtonClick,
|
2020-03-24 19:51:49 +09:00
|
|
|
onLockToggle,
|
2020-05-30 17:32:32 +05:30
|
|
|
canvas,
|
2020-12-05 20:00:53 +05:30
|
|
|
isCollaborating,
|
2021-01-04 02:21:52 +05:30
|
|
|
renderCustomFooter,
|
2021-02-02 02:26:42 +05:30
|
|
|
viewModeEnabled,
|
2021-03-15 11:33:46 -07:00
|
|
|
showThemeBtn,
|
2021-02-02 02:26:42 +05:30
|
|
|
}: MobileMenuProps) => {
|
2021-02-04 16:52:16 +01:00
|
|
|
const renderToolbar = () => {
|
2021-02-02 02:26:42 +05:30
|
|
|
return (
|
2021-02-04 16:21:48 +01:00
|
|
|
<FixedSideContainer side="top" className="App-top-bar">
|
2021-02-02 02:26:42 +05:30
|
|
|
<Section heading="shapes">
|
|
|
|
{(heading) => (
|
|
|
|
<Stack.Col gap={4} align="center">
|
|
|
|
<Stack.Row gap={1}>
|
|
|
|
<Island padding={1}>
|
|
|
|
{heading}
|
|
|
|
<Stack.Row gap={1}>
|
|
|
|
<ShapesSwitcher
|
2021-03-03 14:04:02 +01:00
|
|
|
canvas={canvas}
|
2021-02-02 02:26:42 +05:30
|
|
|
elementType={appState.elementType}
|
|
|
|
setAppState={setAppState}
|
|
|
|
/>
|
|
|
|
</Stack.Row>
|
|
|
|
</Island>
|
2021-06-11 23:13:05 +02:00
|
|
|
<LockButton
|
2021-02-02 02:26:42 +05:30
|
|
|
checked={appState.elementLocked}
|
|
|
|
onChange={onLockToggle}
|
|
|
|
title={t("toolBar.lock")}
|
2020-08-13 04:35:31 -07:00
|
|
|
/>
|
2021-06-11 23:13:05 +02:00
|
|
|
<LibraryButton appState={appState} setAppState={setAppState} />
|
2021-02-02 02:26:42 +05:30
|
|
|
</Stack.Row>
|
|
|
|
{libraryMenu}
|
|
|
|
</Stack.Col>
|
2020-05-20 16:21:37 +03:00
|
|
|
)}
|
2021-02-02 02:26:42 +05:30
|
|
|
</Section>
|
|
|
|
<HintViewer appState={appState} elements={elements} />
|
|
|
|
</FixedSideContainer>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
const renderAppToolbar = () => {
|
|
|
|
if (viewModeEnabled) {
|
|
|
|
return (
|
|
|
|
<div className="App-toolbar-content">
|
|
|
|
{actionManager.renderAction("toggleCanvasMenu")}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return (
|
|
|
|
<div className="App-toolbar-content">
|
|
|
|
{actionManager.renderAction("toggleCanvasMenu")}
|
|
|
|
{actionManager.renderAction("toggleEditMenu")}
|
|
|
|
{actionManager.renderAction("undo")}
|
|
|
|
{actionManager.renderAction("redo")}
|
|
|
|
{actionManager.renderAction(
|
|
|
|
appState.multiElement ? "finalize" : "duplicateSelection",
|
|
|
|
)}
|
|
|
|
{actionManager.renderAction("deleteSelectedElements")}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
const renderCanvasActions = () => {
|
|
|
|
if (viewModeEnabled) {
|
|
|
|
return (
|
|
|
|
<>
|
2021-05-25 21:37:14 +02:00
|
|
|
{renderJSONExportDialog()}
|
|
|
|
{renderImageExportDialog()}
|
2021-02-02 02:26:42 +05:30
|
|
|
</>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
{actionManager.renderAction("clearCanvas")}
|
2021-05-25 21:37:14 +02:00
|
|
|
{actionManager.renderAction("loadScene")}
|
|
|
|
{renderJSONExportDialog()}
|
|
|
|
{renderImageExportDialog()}
|
2021-02-02 02:26:42 +05:30
|
|
|
{onCollabButtonClick && (
|
|
|
|
<CollabButton
|
|
|
|
isCollaborating={isCollaborating}
|
|
|
|
collaboratorCount={appState.collaborators.size}
|
|
|
|
onClick={onCollabButtonClick}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
{
|
|
|
|
<BackgroundPickerAndDarkModeToggle
|
|
|
|
actionManager={actionManager}
|
|
|
|
appState={appState}
|
|
|
|
setAppState={setAppState}
|
2021-03-15 11:33:46 -07:00
|
|
|
showThemeBtn={showThemeBtn}
|
2021-02-02 02:26:42 +05:30
|
|
|
/>
|
|
|
|
}
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
return (
|
|
|
|
<>
|
2021-02-04 16:52:16 +01:00
|
|
|
{!viewModeEnabled && renderToolbar()}
|
2021-02-02 02:26:42 +05:30
|
|
|
<div
|
|
|
|
className="App-bottom-bar"
|
|
|
|
style={{
|
|
|
|
marginBottom: SCROLLBAR_WIDTH + SCROLLBAR_MARGIN * 2,
|
|
|
|
marginLeft: SCROLLBAR_WIDTH + SCROLLBAR_MARGIN * 2,
|
|
|
|
marginRight: SCROLLBAR_WIDTH + SCROLLBAR_MARGIN * 2,
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<Island padding={0}>
|
|
|
|
{appState.openMenu === "canvas" ? (
|
|
|
|
<Section className="App-mobile-menu" heading="canvasActions">
|
|
|
|
<div className="panelColumn">
|
|
|
|
<Stack.Col gap={4}>
|
|
|
|
{renderCanvasActions()}
|
2021-05-15 14:49:58 +05:30
|
|
|
{renderCustomFooter?.(true, appState)}
|
2021-02-05 15:45:44 +01:00
|
|
|
{appState.collaborators.size > 0 && (
|
|
|
|
<fieldset>
|
|
|
|
<legend>{t("labels.collaborators")}</legend>
|
|
|
|
<UserList mobile>
|
|
|
|
{Array.from(appState.collaborators)
|
|
|
|
// Collaborator is either not initialized or is actually the current user.
|
|
|
|
.filter(
|
|
|
|
([_, client]) => Object.keys(client).length !== 0,
|
|
|
|
)
|
|
|
|
.map(([clientId, client]) => (
|
|
|
|
<React.Fragment key={clientId}>
|
2021-07-15 18:48:03 +02:00
|
|
|
{actionManager.renderAction("goToCollaborator", {
|
|
|
|
id: clientId,
|
|
|
|
})}
|
2021-02-05 15:45:44 +01:00
|
|
|
</React.Fragment>
|
|
|
|
))}
|
|
|
|
</UserList>
|
|
|
|
</fieldset>
|
|
|
|
)}
|
2021-02-02 02:26:42 +05:30
|
|
|
</Stack.Col>
|
|
|
|
</div>
|
|
|
|
</Section>
|
|
|
|
) : appState.openMenu === "shape" &&
|
|
|
|
!viewModeEnabled &&
|
|
|
|
showSelectedShapeActions(appState, elements) ? (
|
|
|
|
<Section className="App-mobile-menu" heading="selectedShapeActions">
|
|
|
|
<SelectedShapeActions
|
|
|
|
appState={appState}
|
|
|
|
elements={elements}
|
|
|
|
renderAction={actionManager.renderAction}
|
|
|
|
elementType={appState.elementType}
|
|
|
|
/>
|
|
|
|
</Section>
|
|
|
|
) : null}
|
|
|
|
<footer className="App-toolbar">
|
|
|
|
{renderAppToolbar()}
|
|
|
|
{appState.scrolledOutside && !appState.openMenu && (
|
|
|
|
<button
|
|
|
|
className="scroll-back-to-content"
|
|
|
|
onClick={() => {
|
|
|
|
setAppState({
|
|
|
|
...calculateScrollCenter(elements, appState, canvas),
|
|
|
|
});
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
{t("buttons.scrollBackToContent")}
|
|
|
|
</button>
|
|
|
|
)}
|
|
|
|
</footer>
|
|
|
|
</Island>
|
|
|
|
</div>
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
};
|