MobileMenu.tsx 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. import React from "react";
  2. import { AppState } from "../types";
  3. import { ActionManager } from "../actions/manager";
  4. import { t, setLanguage } from "../i18n";
  5. import Stack from "./Stack";
  6. import { LanguageList } from "./LanguageList";
  7. import { showSelectedShapeActions } from "../element";
  8. import { NonDeletedExcalidrawElement } from "../element/types";
  9. import { FixedSideContainer } from "./FixedSideContainer";
  10. import { Island } from "./Island";
  11. import { HintViewer } from "./HintViewer";
  12. import { calculateScrollCenter } from "../scene";
  13. import { SelectedShapeActions, ShapesSwitcher } from "./Actions";
  14. import { Section } from "./Section";
  15. import { RoomDialog } from "./RoomDialog";
  16. import { SCROLLBAR_WIDTH, SCROLLBAR_MARGIN } from "../scene/scrollbars";
  17. import { LockIcon } from "./LockIcon";
  18. import { LoadingMessage } from "./LoadingMessage";
  19. import { UserList } from "./UserList";
  20. type MobileMenuProps = {
  21. appState: AppState;
  22. actionManager: ActionManager;
  23. exportButton: React.ReactNode;
  24. setAppState: any;
  25. elements: readonly NonDeletedExcalidrawElement[];
  26. onRoomCreate: () => void;
  27. onUsernameChange: (username: string) => void;
  28. onRoomDestroy: () => void;
  29. onLockToggle: () => void;
  30. canvas: HTMLCanvasElement | null;
  31. };
  32. export const MobileMenu = ({
  33. appState,
  34. elements,
  35. actionManager,
  36. exportButton,
  37. setAppState,
  38. onRoomCreate,
  39. onUsernameChange,
  40. onRoomDestroy,
  41. onLockToggle,
  42. canvas,
  43. }: MobileMenuProps) => (
  44. <>
  45. {appState.isLoading && <LoadingMessage />}
  46. <FixedSideContainer side="top">
  47. <Section heading="shapes">
  48. {(heading) => (
  49. <Stack.Col gap={4} align="center">
  50. <Stack.Row gap={1}>
  51. <Island padding={1}>
  52. {heading}
  53. <Stack.Row gap={1}>
  54. <ShapesSwitcher
  55. elementType={appState.elementType}
  56. setAppState={setAppState}
  57. />
  58. </Stack.Row>
  59. </Island>
  60. <LockIcon
  61. checked={appState.elementLocked}
  62. onChange={onLockToggle}
  63. title={t("toolBar.lock")}
  64. />
  65. </Stack.Row>
  66. </Stack.Col>
  67. )}
  68. </Section>
  69. <HintViewer appState={appState} elements={elements} />
  70. </FixedSideContainer>
  71. <div
  72. className="App-bottom-bar"
  73. style={{
  74. marginBottom: SCROLLBAR_WIDTH + SCROLLBAR_MARGIN * 2,
  75. marginLeft: SCROLLBAR_WIDTH + SCROLLBAR_MARGIN * 2,
  76. marginRight: SCROLLBAR_WIDTH + SCROLLBAR_MARGIN * 2,
  77. }}
  78. >
  79. <Island padding={3}>
  80. {appState.openMenu === "canvas" ? (
  81. <Section className="App-mobile-menu" heading="canvasActions">
  82. <div className="panelColumn">
  83. <Stack.Col gap={4}>
  84. {actionManager.renderAction("loadScene")}
  85. {actionManager.renderAction("saveScene")}
  86. {actionManager.renderAction("saveAsScene")}
  87. {exportButton}
  88. {actionManager.renderAction("clearCanvas")}
  89. <RoomDialog
  90. isCollaborating={appState.isCollaborating}
  91. collaboratorCount={appState.collaborators.size}
  92. username={appState.username}
  93. onUsernameChange={onUsernameChange}
  94. onRoomCreate={onRoomCreate}
  95. onRoomDestroy={onRoomDestroy}
  96. />
  97. {actionManager.renderAction("changeViewBackgroundColor")}
  98. <fieldset>
  99. <legend>{t("labels.language")}</legend>
  100. <LanguageList
  101. onChange={async (lng) => {
  102. await setLanguage(lng);
  103. setAppState({});
  104. }}
  105. />
  106. </fieldset>
  107. <fieldset>
  108. <legend>{t("labels.collaborators")}</legend>
  109. <UserList mobile>
  110. {Array.from(appState.collaborators)
  111. // Collaborator is either not initialized or is actually the current user.
  112. .filter(([_, client]) => Object.keys(client).length !== 0)
  113. .map(([clientId, client]) => (
  114. <React.Fragment key={clientId}>
  115. {actionManager.renderAction(
  116. "goToCollaborator",
  117. clientId,
  118. )}
  119. </React.Fragment>
  120. ))}
  121. </UserList>
  122. </fieldset>
  123. </Stack.Col>
  124. </div>
  125. </Section>
  126. ) : appState.openMenu === "shape" &&
  127. showSelectedShapeActions(appState, elements) ? (
  128. <Section className="App-mobile-menu" heading="selectedShapeActions">
  129. <SelectedShapeActions
  130. appState={appState}
  131. elements={elements}
  132. renderAction={actionManager.renderAction}
  133. elementType={appState.elementType}
  134. />
  135. </Section>
  136. ) : null}
  137. <footer className="App-toolbar">
  138. <div className="App-toolbar-content">
  139. {actionManager.renderAction("toggleCanvasMenu")}
  140. {actionManager.renderAction("toggleEditMenu")}
  141. {actionManager.renderAction("undo")}
  142. {actionManager.renderAction("redo")}
  143. {actionManager.renderAction(
  144. appState.multiElement ? "finalize" : "duplicateSelection",
  145. )}
  146. {actionManager.renderAction("deleteSelectedElements")}
  147. </div>
  148. {appState.scrolledOutside && (
  149. <button
  150. className="scroll-back-to-content"
  151. onClick={() => {
  152. setAppState({
  153. ...calculateScrollCenter(elements, appState, canvas),
  154. });
  155. }}
  156. >
  157. {t("buttons.scrollBackToContent")}
  158. </button>
  159. )}
  160. </footer>
  161. </Island>
  162. </div>
  163. </>
  164. );