Bläddra i källkod

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
vanilla_orig
Aakansha Doshi 5 år sedan
förälder
incheckning
a18342b5b5
Inget konto är kopplat till bidragsgivarens mejladress
3 ändrade filer med 221 tillägg och 199 borttagningar
  1. 1
    0
      .gitignore
  2. 1
    1
      src/components/App.tsx
  3. 219
    198
      src/components/LayerUI.tsx

+ 1
- 0
.gitignore Visa fil

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

+ 1
- 1
src/components/App.tsx Visa fil

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

+ 219
- 198
src/components/LayerUI.tsx Visa fil

@@ -40,212 +40,233 @@ interface LayerUIProps {
40 40
   onLockToggle: () => void;
41 41
 }
42 42
 
43
-export const LayerUI = React.memo(
44
-  ({
45
-    actionManager,
46
-    appState,
47
-    setAppState,
48
-    canvas,
49
-    elements,
50
-    onRoomCreate,
51
-    onUsernameChange,
52
-    onRoomDestroy,
53
-    onLockToggle,
54
-  }: LayerUIProps) => {
55
-    const isMobile = useIsMobile();
43
+const LayerUI = ({
44
+  actionManager,
45
+  appState,
46
+  setAppState,
47
+  canvas,
48
+  elements,
49
+  onRoomCreate,
50
+  onUsernameChange,
51
+  onRoomDestroy,
52
+  onLockToggle,
53
+}: LayerUIProps) => {
54
+  const isMobile = useIsMobile();
56 55
 
57
-    function renderExportDialog() {
58
-      const createExporter = (type: ExportType): ExportCB => (
59
-        exportedElements,
60
-        scale,
61
-      ) => {
62
-        if (canvas) {
63
-          exportCanvas(type, exportedElements, appState, canvas, {
64
-            exportBackground: appState.exportBackground,
65
-            name: appState.name,
66
-            viewBackgroundColor: appState.viewBackgroundColor,
67
-            scale,
68
-          });
69
-        }
70
-      };
71
-      return (
72
-        <ExportDialog
73
-          elements={elements}
74
-          appState={appState}
75
-          actionManager={actionManager}
76
-          onExportToPng={createExporter("png")}
77
-          onExportToSvg={createExporter("svg")}
78
-          onExportToClipboard={createExporter("clipboard")}
79
-          onExportToBackend={(exportedElements) => {
80
-            if (canvas) {
81
-              exportCanvas(
82
-                "backend",
83
-                exportedElements,
84
-                {
85
-                  ...appState,
86
-                  selectedElementIds: {},
87
-                },
88
-                canvas,
89
-                appState,
90
-              );
91
-            }
92
-          }}
93
-        />
94
-      );
95
-    }
96
-
97
-    return isMobile ? (
98
-      <MobileMenu
99
-        appState={appState}
56
+  const renderExportDialog = () => {
57
+    const createExporter = (type: ExportType): ExportCB => (
58
+      exportedElements,
59
+      scale,
60
+    ) => {
61
+      if (canvas) {
62
+        exportCanvas(type, exportedElements, appState, canvas, {
63
+          exportBackground: appState.exportBackground,
64
+          name: appState.name,
65
+          viewBackgroundColor: appState.viewBackgroundColor,
66
+          scale,
67
+        });
68
+      }
69
+    };
70
+    return (
71
+      <ExportDialog
100 72
         elements={elements}
73
+        appState={appState}
101 74
         actionManager={actionManager}
102
-        exportButton={renderExportDialog()}
103
-        setAppState={setAppState}
104
-        onUsernameChange={onUsernameChange}
105
-        onRoomCreate={onRoomCreate}
106
-        onRoomDestroy={onRoomDestroy}
107
-        onLockToggle={onLockToggle}
75
+        onExportToPng={createExporter("png")}
76
+        onExportToSvg={createExporter("svg")}
77
+        onExportToClipboard={createExporter("clipboard")}
78
+        onExportToBackend={(exportedElements) => {
79
+          if (canvas) {
80
+            exportCanvas(
81
+              "backend",
82
+              exportedElements,
83
+              {
84
+                ...appState,
85
+                selectedElementIds: {},
86
+              },
87
+              canvas,
88
+              appState,
89
+            );
90
+          }
91
+        }}
108 92
       />
109
-    ) : (
110
-      <>
111
-        {appState.isLoading && <LoadingMessage />}
112
-        {appState.errorMessage && (
113
-          <ErrorDialog
114
-            message={appState.errorMessage}
115
-            onClose={() => setAppState({ errorMessage: null })}
116
-          />
117
-        )}
118
-        {appState.showShortcutsDialog && (
119
-          <ShortcutsDialog
120
-            onClose={() => setAppState({ showShortcutsDialog: null })}
121
-          />
122
-        )}
123
-        <FixedSideContainer side="top">
124
-          <HintViewer appState={appState} elements={elements} />
125
-          <div className="App-menu App-menu_top">
126
-            <Stack.Col gap={4}>
127
-              <Section heading="canvasActions">
128
-                {/* the zIndex ensures this menu has higher stacking order,
129
-                     see https://github.com/excalidraw/excalidraw/pull/1445 */}
130
-                <Island padding={4} style={{ zIndex: 1 }}>
131
-                  <Stack.Col gap={4}>
132
-                    <Stack.Row gap={1} justifyContent={"space-between"}>
133
-                      {actionManager.renderAction("loadScene")}
134
-                      {actionManager.renderAction("saveScene")}
135
-                      {renderExportDialog()}
136
-                      {actionManager.renderAction("clearCanvas")}
137
-                      <RoomDialog
138
-                        isCollaborating={appState.isCollaborating}
139
-                        collaboratorCount={appState.collaborators.size}
140
-                        username={appState.username}
141
-                        onUsernameChange={onUsernameChange}
142
-                        onRoomCreate={onRoomCreate}
143
-                        onRoomDestroy={onRoomDestroy}
93
+    );
94
+  };
95
+
96
+  const renderCanvasActions = () => (
97
+    <Section heading="canvasActions">
98
+      {/* the zIndex ensures this menu has higher stacking order,
99
+         see https://github.com/excalidraw/excalidraw/pull/1445 */}
100
+      <Island padding={4} style={{ zIndex: 1 }}>
101
+        <Stack.Col gap={4}>
102
+          <Stack.Row gap={1} justifyContent="space-between">
103
+            {actionManager.renderAction("loadScene")}
104
+            {actionManager.renderAction("saveScene")}
105
+            {renderExportDialog()}
106
+            {actionManager.renderAction("clearCanvas")}
107
+            <RoomDialog
108
+              isCollaborating={appState.isCollaborating}
109
+              collaboratorCount={appState.collaborators.size}
110
+              username={appState.username}
111
+              onUsernameChange={onUsernameChange}
112
+              onRoomCreate={onRoomCreate}
113
+              onRoomDestroy={onRoomDestroy}
114
+            />
115
+          </Stack.Row>
116
+          {actionManager.renderAction("changeViewBackgroundColor")}
117
+        </Stack.Col>
118
+      </Island>
119
+    </Section>
120
+  );
121
+
122
+  const renderSelectedShapeActions = () => (
123
+    <Section heading="selectedShapeActions">
124
+      <Island className={CLASSES.SHAPE_ACTIONS_MENU} padding={4}>
125
+        <SelectedShapeActions
126
+          appState={appState}
127
+          elements={elements}
128
+          renderAction={actionManager.renderAction}
129
+          elementType={appState.elementType}
130
+        />
131
+      </Island>
132
+    </Section>
133
+  );
134
+
135
+  const renderFixedSideContainer = () => {
136
+    const shouldRenderSelectedShapeActions = showSelectedShapeActions(
137
+      appState,
138
+      elements,
139
+    );
140
+    return (
141
+      <FixedSideContainer side="top">
142
+        <HintViewer appState={appState} elements={elements} />
143
+        <div className="App-menu App-menu_top">
144
+          <Stack.Col gap={4}>
145
+            {renderCanvasActions()}
146
+            {shouldRenderSelectedShapeActions && renderSelectedShapeActions()}
147
+          </Stack.Col>
148
+          <Section heading="shapes">
149
+            {(heading) => (
150
+              <Stack.Col gap={4} align="start">
151
+                <Stack.Row gap={1}>
152
+                  <Island padding={1}>
153
+                    {heading}
154
+                    <Stack.Row gap={1}>
155
+                      <ShapesSwitcher
156
+                        elementType={appState.elementType}
157
+                        setAppState={setAppState}
144 158
                       />
145 159
                     </Stack.Row>
146
-                    {actionManager.renderAction("changeViewBackgroundColor")}
147
-                  </Stack.Col>
148
-                </Island>
149
-              </Section>
150
-              {showSelectedShapeActions(appState, elements) && (
151
-                <Section heading="selectedShapeActions">
152
-                  <Island className={CLASSES.SHAPE_ACTIONS_MENU} padding={4}>
153
-                    <SelectedShapeActions
154
-                      appState={appState}
155
-                      elements={elements}
156
-                      renderAction={actionManager.renderAction}
157
-                      elementType={appState.elementType}
158
-                    />
159 160
                   </Island>
160
-                </Section>
161
-              )}
162
-            </Stack.Col>
163
-            <Section heading="shapes">
164
-              {(heading) => (
165
-                <Stack.Col gap={4} align="start">
166
-                  <Stack.Row gap={1}>
167
-                    <Island padding={1}>
168
-                      {heading}
169
-                      <Stack.Row gap={1}>
170
-                        <ShapesSwitcher
171
-                          elementType={appState.elementType}
172
-                          setAppState={setAppState}
173
-                        />
174
-                      </Stack.Row>
175
-                    </Island>
176
-                    <LockIcon
177
-                      checked={appState.elementLocked}
178
-                      onChange={onLockToggle}
179
-                      title={t("toolBar.lock")}
180
-                    />
181
-                  </Stack.Row>
182
-                </Stack.Col>
183
-              )}
184
-            </Section>
185
-            <div />
186
-          </div>
187
-          <div className="App-menu App-menu_bottom">
188
-            <Stack.Col gap={2}>
189
-              <Section heading="canvasActions">
190
-                <Island padding={1}>
191
-                  <ZoomActions
192
-                    renderAction={actionManager.renderAction}
193
-                    zoom={appState.zoom}
161
+                  <LockIcon
162
+                    checked={appState.elementLocked}
163
+                    onChange={onLockToggle}
164
+                    title={t("toolBar.lock")}
194 165
                   />
195
-                </Island>
196
-              </Section>
197
-            </Stack.Col>
198
-          </div>
199
-        </FixedSideContainer>
200
-        <aside>
201
-          <GitHubCorner />
202
-        </aside>
203
-        <footer role="contentinfo">
204
-          <LanguageList
205
-            onChange={(lng) => {
206
-              setLanguage(lng);
207
-              setAppState({});
208
-            }}
209
-            languages={languages}
210
-            floating
211
-          />
212
-          {actionManager.renderAction("toggleShortcuts")}
213
-          {appState.scrolledOutside && (
214
-            <button
215
-              className="scroll-back-to-content"
216
-              onClick={() => {
217
-                setAppState({ ...calculateScrollCenter(elements) });
218
-              }}
219
-            >
220
-              {t("buttons.scrollBackToContent")}
221
-            </button>
222
-          )}
223
-        </footer>
224
-      </>
166
+                </Stack.Row>
167
+              </Stack.Col>
168
+            )}
169
+          </Section>
170
+          <div />
171
+        </div>
172
+        <div className="App-menu App-menu_bottom">
173
+          <Stack.Col gap={2}>
174
+            <Section heading="canvasActions">
175
+              <Island padding={1}>
176
+                <ZoomActions
177
+                  renderAction={actionManager.renderAction}
178
+                  zoom={appState.zoom}
179
+                />
180
+              </Island>
181
+            </Section>
182
+          </Stack.Col>
183
+        </div>
184
+      </FixedSideContainer>
225 185
     );
226
-  },
227
-  (prev, next) => {
228
-    const getNecessaryObj = (appState: AppState): Partial<AppState> => {
229
-      const {
230
-        draggingElement,
231
-        resizingElement,
232
-        multiElement,
233
-        editingElement,
234
-        isResizing,
235
-        cursorX,
236
-        cursorY,
237
-        ...ret
238
-      } = appState;
239
-      return ret;
240
-    };
241
-    const prevAppState = getNecessaryObj(prev.appState);
242
-    const nextAppState = getNecessaryObj(next.appState);
186
+  };
243 187
 
244
-    const keys = Object.keys(prevAppState) as (keyof Partial<AppState>)[];
188
+  const renderFooter = () => (
189
+    <footer role="contentinfo">
190
+      <LanguageList
191
+        onChange={(lng) => {
192
+          setLanguage(lng);
193
+          setAppState({});
194
+        }}
195
+        languages={languages}
196
+        floating
197
+      />
198
+      {actionManager.renderAction("toggleShortcuts")}
199
+      {appState.scrolledOutside && (
200
+        <button
201
+          className="scroll-back-to-content"
202
+          onClick={() => {
203
+            setAppState({ ...calculateScrollCenter(elements) });
204
+          }}
205
+        >
206
+          {t("buttons.scrollBackToContent")}
207
+        </button>
208
+      )}
209
+    </footer>
210
+  );
245 211
 
246
-    return (
247
-      prev.elements === next.elements &&
248
-      keys.every((key) => prevAppState[key] === nextAppState[key])
249
-    );
250
-  },
251
-);
212
+  return isMobile ? (
213
+    <MobileMenu
214
+      appState={appState}
215
+      elements={elements}
216
+      actionManager={actionManager}
217
+      exportButton={renderExportDialog()}
218
+      setAppState={setAppState}
219
+      onUsernameChange={onUsernameChange}
220
+      onRoomCreate={onRoomCreate}
221
+      onRoomDestroy={onRoomDestroy}
222
+      onLockToggle={onLockToggle}
223
+    />
224
+  ) : (
225
+    <>
226
+      {appState.isLoading && <LoadingMessage />}
227
+      {appState.errorMessage && (
228
+        <ErrorDialog
229
+          message={appState.errorMessage}
230
+          onClose={() => setAppState({ errorMessage: null })}
231
+        />
232
+      )}
233
+      {appState.showShortcutsDialog && (
234
+        <ShortcutsDialog
235
+          onClose={() => setAppState({ showShortcutsDialog: null })}
236
+        />
237
+      )}
238
+      {renderFixedSideContainer()}
239
+      <aside>
240
+        <GitHubCorner />
241
+      </aside>
242
+      {renderFooter()}
243
+    </>
244
+  );
245
+};
246
+
247
+const areEqual = (prev: LayerUIProps, next: LayerUIProps) => {
248
+  const getNecessaryObj = (appState: AppState): Partial<AppState> => {
249
+    const {
250
+      draggingElement,
251
+      resizingElement,
252
+      multiElement,
253
+      editingElement,
254
+      isResizing,
255
+      cursorX,
256
+      cursorY,
257
+      ...ret
258
+    } = appState;
259
+    return ret;
260
+  };
261
+  const prevAppState = getNecessaryObj(prev.appState);
262
+  const nextAppState = getNecessaryObj(next.appState);
263
+
264
+  const keys = Object.keys(prevAppState) as (keyof Partial<AppState>)[];
265
+
266
+  return (
267
+    prev.elements === next.elements &&
268
+    keys.every((key) => prevAppState[key] === nextAppState[key])
269
+  );
270
+};
271
+
272
+export default React.memo(LayerUI, areEqual);

Laddar…
Avbryt
Spara