From ec350ba8b269b92b37d31a835ece5aac0fc50e9d Mon Sep 17 00:00:00 2001 From: Aakansha Doshi Date: Sat, 20 Aug 2022 22:49:44 +0530 Subject: [PATCH] feat: Introduce ExcalidrawElements and ExcalidrawAppState provider (#5463) * feat: Introduce ExcalidrawData provider so that app state and elements need not be passed to children * fix * fix zen mode * Separate providers for data and elements * pass appState and elements to layerUI * pass appState and elements to selectedShapeActions * pass appState and elements to MobileMenu * pass appState to librarymenu * rename * rename to ExcalidrawAppState --- src/components/Actions.tsx | 22 +++--- src/components/App.tsx | 137 +++++++++++++++++++-------------- src/components/LayerUI.tsx | 6 +- src/components/LibraryMenu.tsx | 5 +- src/components/MobileMenu.tsx | 1 - src/element/Hyperlink.tsx | 5 +- 6 files changed, 96 insertions(+), 80 deletions(-) diff --git a/src/components/Actions.tsx b/src/components/Actions.tsx index 897bc09a..e23b02d0 100644 --- a/src/components/Actions.tsx +++ b/src/components/Actions.tsx @@ -31,12 +31,10 @@ export const SelectedShapeActions = ({ appState, elements, renderAction, - activeTool, }: { appState: AppState; elements: readonly ExcalidrawElement[]; renderAction: ActionManager["renderAction"]; - activeTool: AppState["activeTool"]["type"]; }) => { const targetElements = getTargetElements( getNonDeletedElements(elements), @@ -56,13 +54,13 @@ export const SelectedShapeActions = ({ const isRTL = document.documentElement.getAttribute("dir") === "rtl"; const showFillIcons = - hasBackground(activeTool) || + hasBackground(appState.activeTool.type) || targetElements.some( (element) => hasBackground(element.type) && !isTransparent(element.backgroundColor), ); const showChangeBackgroundIcons = - hasBackground(activeTool) || + hasBackground(appState.activeTool.type) || targetElements.some((element) => hasBackground(element.type)); const showLinkIcon = @@ -79,23 +77,23 @@ export const SelectedShapeActions = ({ return (
- {((hasStrokeColor(activeTool) && - activeTool !== "image" && + {((hasStrokeColor(appState.activeTool.type) && + appState.activeTool.type !== "image" && commonSelectedType !== "image") || targetElements.some((element) => hasStrokeColor(element.type))) && renderAction("changeStrokeColor")} {showChangeBackgroundIcons && renderAction("changeBackgroundColor")} {showFillIcons && renderAction("changeFillStyle")} - {(hasStrokeWidth(activeTool) || + {(hasStrokeWidth(appState.activeTool.type) || targetElements.some((element) => hasStrokeWidth(element.type))) && renderAction("changeStrokeWidth")} - {(activeTool === "freedraw" || + {(appState.activeTool.type === "freedraw" || targetElements.some((element) => element.type === "freedraw")) && renderAction("changeStrokeShape")} - {(hasStrokeStyle(activeTool) || + {(hasStrokeStyle(appState.activeTool.type) || targetElements.some((element) => hasStrokeStyle(element.type))) && ( <> {renderAction("changeStrokeStyle")} @@ -103,12 +101,12 @@ export const SelectedShapeActions = ({ )} - {(canChangeSharpness(activeTool) || + {(canChangeSharpness(appState.activeTool.type) || targetElements.some((element) => canChangeSharpness(element.type))) && ( <>{renderAction("changeSharpness")} )} - {(hasText(activeTool) || + {(hasText(appState.activeTool.type) || targetElements.some((element) => hasText(element.type))) && ( <> {renderAction("changeFontSize")} @@ -123,7 +121,7 @@ export const SelectedShapeActions = ({ (element) => hasBoundTextElement(element) || isBoundToContainer(element), ) && renderAction("changeVerticalAlign")} - {(canHaveArrowheads(activeTool) || + {(canHaveArrowheads(appState.activeTool.type) || targetElements.some((element) => canHaveArrowheads(element.type))) && ( <>{renderAction("changeArrowhead")} )} diff --git a/src/components/App.tsx b/src/components/App.tsx index 4314c3d3..91a6cbe7 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -272,6 +272,7 @@ const deviceContextInitialValue = { }; const DeviceContext = React.createContext(deviceContextInitialValue); export const useDevice = () => useContext(DeviceContext); + const ExcalidrawContainerContext = React.createContext<{ container: HTMLDivElement | null; id: string | null; @@ -279,6 +280,22 @@ const ExcalidrawContainerContext = React.createContext<{ export const useExcalidrawContainer = () => useContext(ExcalidrawContainerContext); +const ExcalidrawElementsContext = React.createContext< + readonly NonDeletedExcalidrawElement[] +>([]); + +const ExcalidrawAppStateContext = React.createContext({ + ...getDefaultAppState(), + width: 0, + height: 0, + offsetLeft: 0, + offsetTop: 0, +}); +export const useExcalidrawElements = () => + useContext(ExcalidrawElementsContext); +export const useExcalidrawAppState = () => + useContext(ExcalidrawAppStateContext); + let didTapTwice: boolean = false; let tappedTwiceTimer = 0; let cursorX = 0; @@ -505,63 +522,69 @@ class App extends React.Component { value={this.excalidrawContainerValue} > - - this.addElementsFromPasteOrLibrary({ - elements, - position: "center", - files: null, - }) - } - langCode={getLanguage().code} - isCollaborating={this.props.isCollaborating} - renderTopRightUI={renderTopRightUI} - renderCustomFooter={renderFooter} - renderCustomStats={renderCustomStats} - showExitZenModeBtn={ - typeof this.props?.zenModeEnabled === "undefined" && - this.state.zenModeEnabled - } - showThemeBtn={ - typeof this.props?.theme === "undefined" && - this.props.UIOptions.canvasActions.theme - } - libraryReturnUrl={this.props.libraryReturnUrl} - UIOptions={this.props.UIOptions} - focusContainer={this.focusContainer} - library={this.library} - id={this.id} - onImageAction={this.onImageAction} - /> -
-
- {selectedElement.length === 1 && this.state.showHyperlinkPopup && ( - - )} - {this.state.toast !== null && ( - this.setToast(null)} - duration={this.state.toast.duration} - closable={this.state.toast.closable} - /> - )} -
{this.renderCanvas()}
+ + + + this.addElementsFromPasteOrLibrary({ + elements, + position: "center", + files: null, + }) + } + langCode={getLanguage().code} + isCollaborating={this.props.isCollaborating} + renderTopRightUI={renderTopRightUI} + renderCustomFooter={renderFooter} + renderCustomStats={renderCustomStats} + showExitZenModeBtn={ + typeof this.props?.zenModeEnabled === "undefined" && + this.state.zenModeEnabled + } + showThemeBtn={ + typeof this.props?.theme === "undefined" && + this.props.UIOptions.canvasActions.theme + } + libraryReturnUrl={this.props.libraryReturnUrl} + UIOptions={this.props.UIOptions} + focusContainer={this.focusContainer} + library={this.library} + id={this.id} + onImageAction={this.onImageAction} + /> +
+
+ {selectedElement.length === 1 && + this.state.showHyperlinkPopup && ( + + )} + {this.state.toast !== null && ( + this.setToast(null)} + duration={this.state.toast.duration} + closable={this.state.toast.closable} + /> + )} +
{this.renderCanvas()}
+ {" "} +
diff --git a/src/components/LayerUI.tsx b/src/components/LayerUI.tsx index 845aabf5..168e701d 100644 --- a/src/components/LayerUI.tsx +++ b/src/components/LayerUI.tsx @@ -71,8 +71,8 @@ const LayerUI = ({ appState, files, setAppState, - canvas, elements, + canvas, onCollabButtonClick, onLockToggle, onPenModeToggle, @@ -210,8 +210,8 @@ const LayerUI = ({ )} @@ -244,7 +244,6 @@ const LayerUI = ({ appState={appState} elements={elements} renderAction={actionManager.renderAction} - activeTool={appState.activeTool.type} /> @@ -279,7 +278,6 @@ const LayerUI = ({ libraryReturnUrl={libraryReturnUrl} focusContainer={focusContainer} library={library} - theme={appState.theme} files={files} id={id} appState={appState} diff --git a/src/components/LibraryMenu.tsx b/src/components/LibraryMenu.tsx index 5ba502f9..c9fc7570 100644 --- a/src/components/LibraryMenu.tsx +++ b/src/components/LibraryMenu.tsx @@ -80,7 +80,6 @@ export const LibraryMenu = ({ onInsertLibraryItems, pendingElements, onAddToLibrary, - theme, setAppState, files, libraryReturnUrl, @@ -93,7 +92,6 @@ export const LibraryMenu = ({ onClose: () => void; onInsertLibraryItems: (libraryItems: LibraryItems) => void; onAddToLibrary: () => void; - theme: AppState["theme"]; files: BinaryFiles; setAppState: React.Component["setState"]; libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"]; @@ -105,7 +103,6 @@ export const LibraryMenu = ({ const ref = useRef(null); const device = useDevice(); - useOnClickOutside( ref, useCallback( @@ -290,7 +287,7 @@ export const LibraryMenu = ({ appState={appState} libraryReturnUrl={libraryReturnUrl} library={library} - theme={theme} + theme={appState.theme} files={files} id={id} selectedItems={selectedItems} diff --git a/src/components/MobileMenu.tsx b/src/components/MobileMenu.tsx index 42b56422..a8a30282 100644 --- a/src/components/MobileMenu.tsx +++ b/src/components/MobileMenu.tsx @@ -221,7 +221,6 @@ export const MobileMenu = ({ appState={appState} elements={elements} renderAction={actionManager.renderAction} - activeTool={appState.activeTool.type} /> ) : null} diff --git a/src/element/Hyperlink.tsx b/src/element/Hyperlink.tsx index 509af764..37ed2b09 100644 --- a/src/element/Hyperlink.tsx +++ b/src/element/Hyperlink.tsx @@ -32,6 +32,7 @@ import { getElementAbsoluteCoords } from "./"; import "./Hyperlink.scss"; import { trackEvent } from "../analytics"; +import { useExcalidrawAppState } from "../components/App"; const CONTAINER_WIDTH = 320; const SPACE_BOTTOM = 85; @@ -48,15 +49,15 @@ let IS_HYPERLINK_TOOLTIP_VISIBLE = false; export const Hyperlink = ({ element, - appState, setAppState, onLinkOpen, }: { element: NonDeletedExcalidrawElement; - appState: AppState; setAppState: React.Component["setState"]; onLinkOpen: ExcalidrawProps["onLinkOpen"]; }) => { + const appState = useExcalidrawAppState(); + const linkVal = element.link || ""; const [inputVal, setInputVal] = useState(linkVal);