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
This commit is contained in:
Aakansha Doshi 2020-04-18 01:54:19 +05:30 committed by GitHub
parent 9131813661
commit a18342b5b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 221 additions and 199 deletions

1
.gitignore vendored
View File

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

View File

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

View File

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