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:
parent
9131813661
commit
a18342b5b5
1
.gitignore
vendored
1
.gitignore
vendored
@ -12,3 +12,4 @@ static
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
yarn.lock
|
||||
.idea
|
||||
|
@ -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";
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user