mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
Refactor (#862)
* Initial factoring out of parts of the LayerUI component 2360 → 2224 LOC * Create a Section component * Break up src/index.tsx * Refactor actions to reduce duplication, fix CSS Also consolidate icons * Move scene/data.ts to its own directory * Fix accidental reverts, banish further single-character variables * ACTIVE_ELEM_COLOR → ACTIVE_ELEMENT_COLOR * Further refactoring the icons file * Log all errors * Pointer Event polyfill to make the tests work * add test hooks & fix tests Co-authored-by: dwelle <luzar.david@gmail.com>
This commit is contained in:
parent
1a6431a04a
commit
c6a0cfc2b1
49 changed files with 3498 additions and 3372 deletions
233
src/components/LayerUI.tsx
Normal file
233
src/components/LayerUI.tsx
Normal file
|
@ -0,0 +1,233 @@
|
|||
import React from "react";
|
||||
import { showSelectedShapeActions } from "../element";
|
||||
import { calculateScrollCenter, getTargetElement } from "../scene";
|
||||
import { exportCanvas } from "../data";
|
||||
|
||||
import { AppState } from "../types";
|
||||
import { ExcalidrawElement } from "../element/types";
|
||||
|
||||
import { ActionManager } from "../actions/manager";
|
||||
import { Island } from "./Island";
|
||||
import Stack from "./Stack";
|
||||
import { FixedSideContainer } from "./FixedSideContainer";
|
||||
import { LockIcon } from "./LockIcon";
|
||||
import { ExportDialog, ExportCB } from "./ExportDialog";
|
||||
import { LanguageList } from "./LanguageList";
|
||||
import { t, languages, setLanguage } from "../i18n";
|
||||
import { HintViewer } from "./HintViewer";
|
||||
import useIsMobile from "../is-mobile";
|
||||
|
||||
import { ExportType } from "../scene/types";
|
||||
import { MobileMenu } from "./MobileMenu";
|
||||
import { ZoomActions, SelectedShapeActions, ShapesSwitcher } from "./Actions";
|
||||
import { Section } from "./Section";
|
||||
|
||||
interface LayerUIProps {
|
||||
actionManager: ActionManager;
|
||||
appState: AppState;
|
||||
canvas: HTMLCanvasElement | null;
|
||||
setAppState: any;
|
||||
elements: readonly ExcalidrawElement[];
|
||||
language: string;
|
||||
setElements: (elements: readonly ExcalidrawElement[]) => void;
|
||||
}
|
||||
|
||||
export const LayerUI = React.memo(
|
||||
({
|
||||
actionManager,
|
||||
appState,
|
||||
setAppState,
|
||||
canvas,
|
||||
elements,
|
||||
language,
|
||||
setElements,
|
||||
}: LayerUIProps) => {
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
function renderExportDialog() {
|
||||
const createExporter = (type: ExportType): ExportCB => (
|
||||
exportedElements,
|
||||
scale,
|
||||
) => {
|
||||
if (canvas) {
|
||||
exportCanvas(type, exportedElements, 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.map(element => ({
|
||||
...element,
|
||||
isSelected: false,
|
||||
})),
|
||||
canvas,
|
||||
appState,
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return isMobile ? (
|
||||
<MobileMenu
|
||||
appState={appState}
|
||||
elements={elements}
|
||||
setElements={setElements}
|
||||
actionManager={actionManager}
|
||||
exportButton={renderExportDialog()}
|
||||
setAppState={setAppState}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<FixedSideContainer side="top">
|
||||
<HintViewer
|
||||
elementType={appState.elementType}
|
||||
multiMode={appState.multiElement !== null}
|
||||
isResizing={appState.isResizing}
|
||||
elements={elements}
|
||||
/>
|
||||
<div className="App-menu App-menu_top">
|
||||
<Stack.Col gap={4} align="end">
|
||||
<Section className="App-right-menu" heading="canvasActions">
|
||||
<Island padding={4}>
|
||||
<Stack.Col gap={4}>
|
||||
<Stack.Row justifyContent={"space-between"}>
|
||||
{actionManager.renderAction("loadScene")}
|
||||
{actionManager.renderAction("saveScene")}
|
||||
{renderExportDialog()}
|
||||
{actionManager.renderAction("clearCanvas")}
|
||||
</Stack.Row>
|
||||
{actionManager.renderAction("changeViewBackgroundColor")}
|
||||
</Stack.Col>
|
||||
</Island>
|
||||
</Section>
|
||||
{showSelectedShapeActions(appState, elements) && (
|
||||
<Section
|
||||
className="App-right-menu"
|
||||
heading="selectedShapeActions"
|
||||
>
|
||||
<Island padding={4}>
|
||||
<SelectedShapeActions
|
||||
targetElements={getTargetElement(
|
||||
appState.editingElement,
|
||||
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}
|
||||
setElements={setElements}
|
||||
elements={elements}
|
||||
/>
|
||||
</Stack.Row>
|
||||
</Island>
|
||||
<LockIcon
|
||||
checked={appState.elementLocked}
|
||||
onChange={() => {
|
||||
setAppState({
|
||||
elementLocked: !appState.elementLocked,
|
||||
elementType: appState.elementLocked
|
||||
? "selection"
|
||||
: appState.elementType,
|
||||
});
|
||||
}}
|
||||
title={t("toolBar.lock")}
|
||||
isButton={isMobile}
|
||||
/>
|
||||
</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>
|
||||
<footer role="contentinfo">
|
||||
<LanguageList
|
||||
onChange={lng => {
|
||||
setLanguage(lng);
|
||||
setAppState({});
|
||||
}}
|
||||
languages={languages}
|
||||
currentLanguage={language}
|
||||
floating
|
||||
/>
|
||||
{appState.scrolledOutside && (
|
||||
<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>)[];
|
||||
|
||||
return (
|
||||
prev.language === next.language &&
|
||||
prev.elements === next.elements &&
|
||||
keys.every(key => prevAppState[key] === nextAppState[key])
|
||||
);
|
||||
},
|
||||
);
|
Loading…
Add table
Add a link
Reference in a new issue