Browse Source

Refactor LayerUI (#1434)

* chore(gitignore): add .idea to gitignore

* refactor(layerui): pass named function to react.memo so that in dev tools the name shows up

This makes debugging easier as well

* refactor(layerui): break the functional component into multiple render methods
Aakansha Doshi 5 years ago
parent
commit
a18342b5b5
3 changed files with 221 additions and 199 deletions
  1. 1 0
      .gitignore
  2. 1 1
      src/components/App.tsx
  3. 219 198
      src/components/LayerUI.tsx

+ 1 - 0
.gitignore

@@ -12,3 +12,4 @@ static
 yarn-debug.log*
 yarn-error.log*
 yarn.lock
+.idea

+ 1 - 1
src/components/App.tsx

@@ -114,7 +114,7 @@ import {
   TAP_TWICE_TIMEOUT,
 } from "../time_constants";
 
-import { LayerUI } from "./LayerUI";
+import LayerUI from "./LayerUI";
 import { ScrollBars, SceneState } from "../scene/types";
 import { generateCollaborationLink, getCollaborationLinkData } from "../data";
 import { mutateElement, newElementWith } from "../element/mutateElement";

+ 219 - 198
src/components/LayerUI.tsx

@@ -40,212 +40,233 @@ interface LayerUIProps {
   onLockToggle: () => void;
 }
 
-export const LayerUI = React.memo(
-  ({
-    actionManager,
-    appState,
-    setAppState,
-    canvas,
-    elements,
-    onRoomCreate,
-    onUsernameChange,
-    onRoomDestroy,
-    onLockToggle,
-  }: LayerUIProps) => {
-    const isMobile = useIsMobile();
+const LayerUI = ({
+  actionManager,
+  appState,
+  setAppState,
+  canvas,
+  elements,
+  onRoomCreate,
+  onUsernameChange,
+  onRoomDestroy,
+  onLockToggle,
+}: LayerUIProps) => {
+  const isMobile = useIsMobile();
 
-    function renderExportDialog() {
-      const createExporter = (type: ExportType): ExportCB => (
-        exportedElements,
-        scale,
-      ) => {
-        if (canvas) {
-          exportCanvas(type, exportedElements, appState, canvas, {
-            exportBackground: appState.exportBackground,
-            name: appState.name,
-            viewBackgroundColor: appState.viewBackgroundColor,
-            scale,
-          });
-        }
-      };
-      return (
-        <ExportDialog
-          elements={elements}
-          appState={appState}
-          actionManager={actionManager}
-          onExportToPng={createExporter("png")}
-          onExportToSvg={createExporter("svg")}
-          onExportToClipboard={createExporter("clipboard")}
-          onExportToBackend={(exportedElements) => {
-            if (canvas) {
-              exportCanvas(
-                "backend",
-                exportedElements,
-                {
-                  ...appState,
-                  selectedElementIds: {},
-                },
-                canvas,
-                appState,
-              );
-            }
-          }}
-        />
-      );
-    }
-
-    return isMobile ? (
-      <MobileMenu
-        appState={appState}
+  const renderExportDialog = () => {
+    const createExporter = (type: ExportType): ExportCB => (
+      exportedElements,
+      scale,
+    ) => {
+      if (canvas) {
+        exportCanvas(type, exportedElements, appState, canvas, {
+          exportBackground: appState.exportBackground,
+          name: appState.name,
+          viewBackgroundColor: appState.viewBackgroundColor,
+          scale,
+        });
+      }
+    };
+    return (
+      <ExportDialog
         elements={elements}
+        appState={appState}
         actionManager={actionManager}
-        exportButton={renderExportDialog()}
-        setAppState={setAppState}
-        onUsernameChange={onUsernameChange}
-        onRoomCreate={onRoomCreate}
-        onRoomDestroy={onRoomDestroy}
-        onLockToggle={onLockToggle}
+        onExportToPng={createExporter("png")}
+        onExportToSvg={createExporter("svg")}
+        onExportToClipboard={createExporter("clipboard")}
+        onExportToBackend={(exportedElements) => {
+          if (canvas) {
+            exportCanvas(
+              "backend",
+              exportedElements,
+              {
+                ...appState,
+                selectedElementIds: {},
+              },
+              canvas,
+              appState,
+            );
+          }
+        }}
       />
-    ) : (
-      <>
-        {appState.isLoading && <LoadingMessage />}
-        {appState.errorMessage && (
-          <ErrorDialog
-            message={appState.errorMessage}
-            onClose={() => setAppState({ errorMessage: null })}
-          />
-        )}
-        {appState.showShortcutsDialog && (
-          <ShortcutsDialog
-            onClose={() => setAppState({ showShortcutsDialog: null })}
-          />
-        )}
-        <FixedSideContainer side="top">
-          <HintViewer appState={appState} elements={elements} />
-          <div className="App-menu App-menu_top">
-            <Stack.Col gap={4}>
-              <Section heading="canvasActions">
-                {/* the zIndex ensures this menu has higher stacking order,
-                     see https://github.com/excalidraw/excalidraw/pull/1445 */}
-                <Island padding={4} style={{ zIndex: 1 }}>
-                  <Stack.Col gap={4}>
-                    <Stack.Row gap={1} justifyContent={"space-between"}>
-                      {actionManager.renderAction("loadScene")}
-                      {actionManager.renderAction("saveScene")}
-                      {renderExportDialog()}
-                      {actionManager.renderAction("clearCanvas")}
-                      <RoomDialog
-                        isCollaborating={appState.isCollaborating}
-                        collaboratorCount={appState.collaborators.size}
-                        username={appState.username}
-                        onUsernameChange={onUsernameChange}
-                        onRoomCreate={onRoomCreate}
-                        onRoomDestroy={onRoomDestroy}
+    );
+  };
+
+  const renderCanvasActions = () => (
+    <Section heading="canvasActions">
+      {/* the zIndex ensures this menu has higher stacking order,
+         see https://github.com/excalidraw/excalidraw/pull/1445 */}
+      <Island padding={4} style={{ zIndex: 1 }}>
+        <Stack.Col gap={4}>
+          <Stack.Row gap={1} justifyContent="space-between">
+            {actionManager.renderAction("loadScene")}
+            {actionManager.renderAction("saveScene")}
+            {renderExportDialog()}
+            {actionManager.renderAction("clearCanvas")}
+            <RoomDialog
+              isCollaborating={appState.isCollaborating}
+              collaboratorCount={appState.collaborators.size}
+              username={appState.username}
+              onUsernameChange={onUsernameChange}
+              onRoomCreate={onRoomCreate}
+              onRoomDestroy={onRoomDestroy}
+            />
+          </Stack.Row>
+          {actionManager.renderAction("changeViewBackgroundColor")}
+        </Stack.Col>
+      </Island>
+    </Section>
+  );
+
+  const renderSelectedShapeActions = () => (
+    <Section heading="selectedShapeActions">
+      <Island className={CLASSES.SHAPE_ACTIONS_MENU} padding={4}>
+        <SelectedShapeActions
+          appState={appState}
+          elements={elements}
+          renderAction={actionManager.renderAction}
+          elementType={appState.elementType}
+        />
+      </Island>
+    </Section>
+  );
+
+  const renderFixedSideContainer = () => {
+    const shouldRenderSelectedShapeActions = showSelectedShapeActions(
+      appState,
+      elements,
+    );
+    return (
+      <FixedSideContainer side="top">
+        <HintViewer appState={appState} elements={elements} />
+        <div className="App-menu App-menu_top">
+          <Stack.Col gap={4}>
+            {renderCanvasActions()}
+            {shouldRenderSelectedShapeActions && renderSelectedShapeActions()}
+          </Stack.Col>
+          <Section heading="shapes">
+            {(heading) => (
+              <Stack.Col gap={4} align="start">
+                <Stack.Row gap={1}>
+                  <Island padding={1}>
+                    {heading}
+                    <Stack.Row gap={1}>
+                      <ShapesSwitcher
+                        elementType={appState.elementType}
+                        setAppState={setAppState}
                       />
                     </Stack.Row>
-                    {actionManager.renderAction("changeViewBackgroundColor")}
-                  </Stack.Col>
-                </Island>
-              </Section>
-              {showSelectedShapeActions(appState, elements) && (
-                <Section heading="selectedShapeActions">
-                  <Island className={CLASSES.SHAPE_ACTIONS_MENU} padding={4}>
-                    <SelectedShapeActions
-                      appState={appState}
-                      elements={elements}
-                      renderAction={actionManager.renderAction}
-                      elementType={appState.elementType}
-                    />
                   </Island>
-                </Section>
-              )}
-            </Stack.Col>
-            <Section heading="shapes">
-              {(heading) => (
-                <Stack.Col gap={4} align="start">
-                  <Stack.Row gap={1}>
-                    <Island padding={1}>
-                      {heading}
-                      <Stack.Row gap={1}>
-                        <ShapesSwitcher
-                          elementType={appState.elementType}
-                          setAppState={setAppState}
-                        />
-                      </Stack.Row>
-                    </Island>
-                    <LockIcon
-                      checked={appState.elementLocked}
-                      onChange={onLockToggle}
-                      title={t("toolBar.lock")}
-                    />
-                  </Stack.Row>
-                </Stack.Col>
-              )}
-            </Section>
-            <div />
-          </div>
-          <div className="App-menu App-menu_bottom">
-            <Stack.Col gap={2}>
-              <Section heading="canvasActions">
-                <Island padding={1}>
-                  <ZoomActions
-                    renderAction={actionManager.renderAction}
-                    zoom={appState.zoom}
+                  <LockIcon
+                    checked={appState.elementLocked}
+                    onChange={onLockToggle}
+                    title={t("toolBar.lock")}
                   />
-                </Island>
-              </Section>
-            </Stack.Col>
-          </div>
-        </FixedSideContainer>
-        <aside>
-          <GitHubCorner />
-        </aside>
-        <footer role="contentinfo">
-          <LanguageList
-            onChange={(lng) => {
-              setLanguage(lng);
-              setAppState({});
-            }}
-            languages={languages}
-            floating
-          />
-          {actionManager.renderAction("toggleShortcuts")}
-          {appState.scrolledOutside && (
-            <button
-              className="scroll-back-to-content"
-              onClick={() => {
-                setAppState({ ...calculateScrollCenter(elements) });
-              }}
-            >
-              {t("buttons.scrollBackToContent")}
-            </button>
-          )}
-        </footer>
-      </>
+                </Stack.Row>
+              </Stack.Col>
+            )}
+          </Section>
+          <div />
+        </div>
+        <div className="App-menu App-menu_bottom">
+          <Stack.Col gap={2}>
+            <Section heading="canvasActions">
+              <Island padding={1}>
+                <ZoomActions
+                  renderAction={actionManager.renderAction}
+                  zoom={appState.zoom}
+                />
+              </Island>
+            </Section>
+          </Stack.Col>
+        </div>
+      </FixedSideContainer>
     );
-  },
-  (prev, next) => {
-    const getNecessaryObj = (appState: AppState): Partial<AppState> => {
-      const {
-        draggingElement,
-        resizingElement,
-        multiElement,
-        editingElement,
-        isResizing,
-        cursorX,
-        cursorY,
-        ...ret
-      } = appState;
-      return ret;
-    };
-    const prevAppState = getNecessaryObj(prev.appState);
-    const nextAppState = getNecessaryObj(next.appState);
+  };
 
-    const keys = Object.keys(prevAppState) as (keyof Partial<AppState>)[];
+  const renderFooter = () => (
+    <footer role="contentinfo">
+      <LanguageList
+        onChange={(lng) => {
+          setLanguage(lng);
+          setAppState({});
+        }}
+        languages={languages}
+        floating
+      />
+      {actionManager.renderAction("toggleShortcuts")}
+      {appState.scrolledOutside && (
+        <button
+          className="scroll-back-to-content"
+          onClick={() => {
+            setAppState({ ...calculateScrollCenter(elements) });
+          }}
+        >
+          {t("buttons.scrollBackToContent")}
+        </button>
+      )}
+    </footer>
+  );
 
-    return (
-      prev.elements === next.elements &&
-      keys.every((key) => prevAppState[key] === nextAppState[key])
-    );
-  },
-);
+  return isMobile ? (
+    <MobileMenu
+      appState={appState}
+      elements={elements}
+      actionManager={actionManager}
+      exportButton={renderExportDialog()}
+      setAppState={setAppState}
+      onUsernameChange={onUsernameChange}
+      onRoomCreate={onRoomCreate}
+      onRoomDestroy={onRoomDestroy}
+      onLockToggle={onLockToggle}
+    />
+  ) : (
+    <>
+      {appState.isLoading && <LoadingMessage />}
+      {appState.errorMessage && (
+        <ErrorDialog
+          message={appState.errorMessage}
+          onClose={() => setAppState({ errorMessage: null })}
+        />
+      )}
+      {appState.showShortcutsDialog && (
+        <ShortcutsDialog
+          onClose={() => setAppState({ showShortcutsDialog: null })}
+        />
+      )}
+      {renderFixedSideContainer()}
+      <aside>
+        <GitHubCorner />
+      </aside>
+      {renderFooter()}
+    </>
+  );
+};
+
+const areEqual = (prev: LayerUIProps, next: LayerUIProps) => {
+  const getNecessaryObj = (appState: AppState): Partial<AppState> => {
+    const {
+      draggingElement,
+      resizingElement,
+      multiElement,
+      editingElement,
+      isResizing,
+      cursorX,
+      cursorY,
+      ...ret
+    } = appState;
+    return ret;
+  };
+  const prevAppState = getNecessaryObj(prev.appState);
+  const nextAppState = getNecessaryObj(next.appState);
+
+  const keys = Object.keys(prevAppState) as (keyof Partial<AppState>)[];
+
+  return (
+    prev.elements === next.elements &&
+    keys.every((key) => prevAppState[key] === nextAppState[key])
+  );
+};
+
+export default React.memo(LayerUI, areEqual);