perf: use UIAppState
where possible to reduce UI rerenders (#6560)
This commit is contained in:
parent
026949204d
commit
560231d365
@ -14,7 +14,7 @@ import {
|
|||||||
hasText,
|
hasText,
|
||||||
} from "../scene";
|
} from "../scene";
|
||||||
import { SHAPES } from "../shapes";
|
import { SHAPES } from "../shapes";
|
||||||
import { AppState, Zoom } from "../types";
|
import { UIAppState, Zoom } from "../types";
|
||||||
import {
|
import {
|
||||||
capitalizeString,
|
capitalizeString,
|
||||||
isTransparent,
|
isTransparent,
|
||||||
@ -28,19 +28,20 @@ import { trackEvent } from "../analytics";
|
|||||||
import { hasBoundTextElement } from "../element/typeChecks";
|
import { hasBoundTextElement } from "../element/typeChecks";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { actionToggleZenMode } from "../actions";
|
import { actionToggleZenMode } from "../actions";
|
||||||
import "./Actions.scss";
|
|
||||||
import { Tooltip } from "./Tooltip";
|
import { Tooltip } from "./Tooltip";
|
||||||
import {
|
import {
|
||||||
shouldAllowVerticalAlign,
|
shouldAllowVerticalAlign,
|
||||||
suppportsHorizontalAlign,
|
suppportsHorizontalAlign,
|
||||||
} from "../element/textElement";
|
} from "../element/textElement";
|
||||||
|
|
||||||
|
import "./Actions.scss";
|
||||||
|
|
||||||
export const SelectedShapeActions = ({
|
export const SelectedShapeActions = ({
|
||||||
appState,
|
appState,
|
||||||
elements,
|
elements,
|
||||||
renderAction,
|
renderAction,
|
||||||
}: {
|
}: {
|
||||||
appState: AppState;
|
appState: UIAppState;
|
||||||
elements: readonly ExcalidrawElement[];
|
elements: readonly ExcalidrawElement[];
|
||||||
renderAction: ActionManager["renderAction"];
|
renderAction: ActionManager["renderAction"];
|
||||||
}) => {
|
}) => {
|
||||||
@ -215,10 +216,10 @@ export const ShapesSwitcher = ({
|
|||||||
appState,
|
appState,
|
||||||
}: {
|
}: {
|
||||||
canvas: HTMLCanvasElement | null;
|
canvas: HTMLCanvasElement | null;
|
||||||
activeTool: AppState["activeTool"];
|
activeTool: UIAppState["activeTool"];
|
||||||
setAppState: React.Component<any, AppState>["setState"];
|
setAppState: React.Component<any, UIAppState>["setState"];
|
||||||
onImageAction: (data: { pointerType: PointerType | null }) => void;
|
onImageAction: (data: { pointerType: PointerType | null }) => void;
|
||||||
appState: AppState;
|
appState: UIAppState;
|
||||||
}) => (
|
}) => (
|
||||||
<>
|
<>
|
||||||
{SHAPES.map(({ value, icon, key, numericKey, fillable }, index) => {
|
{SHAPES.map(({ value, icon, key, numericKey, fillable }, index) => {
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||||
import { getSelectedElements } from "../scene";
|
import { getSelectedElements } from "../scene";
|
||||||
|
import { Device, UIAppState } from "../types";
|
||||||
import "./HintViewer.scss";
|
|
||||||
import { AppState, Device } from "../types";
|
|
||||||
import {
|
import {
|
||||||
isImageElement,
|
isImageElement,
|
||||||
isLinearElement,
|
isLinearElement,
|
||||||
@ -13,8 +11,10 @@ import {
|
|||||||
import { getShortcutKey } from "../utils";
|
import { getShortcutKey } from "../utils";
|
||||||
import { isEraserActive } from "../appState";
|
import { isEraserActive } from "../appState";
|
||||||
|
|
||||||
|
import "./HintViewer.scss";
|
||||||
|
|
||||||
interface HintViewerProps {
|
interface HintViewerProps {
|
||||||
appState: AppState;
|
appState: UIAppState;
|
||||||
elements: readonly NonDeletedExcalidrawElement[];
|
elements: readonly NonDeletedExcalidrawElement[];
|
||||||
isMobile: boolean;
|
isMobile: boolean;
|
||||||
device: Device;
|
device: Device;
|
||||||
|
@ -4,11 +4,10 @@ import { canvasToBlob } from "../data/blob";
|
|||||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { getSelectedElements, isSomeElementSelected } from "../scene";
|
import { getSelectedElements, isSomeElementSelected } from "../scene";
|
||||||
import { AppState, BinaryFiles } from "../types";
|
import { BinaryFiles, UIAppState } from "../types";
|
||||||
import { Dialog } from "./Dialog";
|
import { Dialog } from "./Dialog";
|
||||||
import { clipboard } from "./icons";
|
import { clipboard } from "./icons";
|
||||||
import Stack from "./Stack";
|
import Stack from "./Stack";
|
||||||
import "./ExportDialog.scss";
|
|
||||||
import OpenColor from "open-color";
|
import OpenColor from "open-color";
|
||||||
import { CheckboxItem } from "./CheckboxItem";
|
import { CheckboxItem } from "./CheckboxItem";
|
||||||
import { DEFAULT_EXPORT_PADDING, isFirefox } from "../constants";
|
import { DEFAULT_EXPORT_PADDING, isFirefox } from "../constants";
|
||||||
@ -16,6 +15,8 @@ import { nativeFileSystemSupported } from "../data/filesystem";
|
|||||||
import { ActionManager } from "../actions/manager";
|
import { ActionManager } from "../actions/manager";
|
||||||
import { exportToCanvas } from "../packages/utils";
|
import { exportToCanvas } from "../packages/utils";
|
||||||
|
|
||||||
|
import "./ExportDialog.scss";
|
||||||
|
|
||||||
const supportsContextFilters =
|
const supportsContextFilters =
|
||||||
"filter" in document.createElement("canvas").getContext("2d")!;
|
"filter" in document.createElement("canvas").getContext("2d")!;
|
||||||
|
|
||||||
@ -70,7 +71,7 @@ const ImageExportModal = ({
|
|||||||
onExportToSvg,
|
onExportToSvg,
|
||||||
onExportToClipboard,
|
onExportToClipboard,
|
||||||
}: {
|
}: {
|
||||||
appState: AppState;
|
appState: UIAppState;
|
||||||
elements: readonly NonDeletedExcalidrawElement[];
|
elements: readonly NonDeletedExcalidrawElement[];
|
||||||
files: BinaryFiles;
|
files: BinaryFiles;
|
||||||
exportPadding?: number;
|
exportPadding?: number;
|
||||||
@ -216,8 +217,8 @@ export const ImageExportDialog = ({
|
|||||||
onExportToSvg,
|
onExportToSvg,
|
||||||
onExportToClipboard,
|
onExportToClipboard,
|
||||||
}: {
|
}: {
|
||||||
appState: AppState;
|
appState: UIAppState;
|
||||||
setAppState: React.Component<any, AppState>["setState"];
|
setAppState: React.Component<any, UIAppState>["setState"];
|
||||||
elements: readonly NonDeletedExcalidrawElement[];
|
elements: readonly NonDeletedExcalidrawElement[];
|
||||||
files: BinaryFiles;
|
files: BinaryFiles;
|
||||||
exportPadding?: number;
|
exportPadding?: number;
|
||||||
|
@ -2,7 +2,7 @@ import React from "react";
|
|||||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
|
|
||||||
import { AppState, ExportOpts, BinaryFiles } from "../types";
|
import { ExportOpts, BinaryFiles, UIAppState } from "../types";
|
||||||
import { Dialog } from "./Dialog";
|
import { Dialog } from "./Dialog";
|
||||||
import { exportToFileIcon, LinkIcon } from "./icons";
|
import { exportToFileIcon, LinkIcon } from "./icons";
|
||||||
import { ToolButton } from "./ToolButton";
|
import { ToolButton } from "./ToolButton";
|
||||||
@ -28,7 +28,7 @@ const JSONExportModal = ({
|
|||||||
exportOpts,
|
exportOpts,
|
||||||
canvas,
|
canvas,
|
||||||
}: {
|
}: {
|
||||||
appState: AppState;
|
appState: UIAppState;
|
||||||
files: BinaryFiles;
|
files: BinaryFiles;
|
||||||
elements: readonly NonDeletedExcalidrawElement[];
|
elements: readonly NonDeletedExcalidrawElement[];
|
||||||
actionManager: ActionManager;
|
actionManager: ActionManager;
|
||||||
@ -96,12 +96,12 @@ export const JSONExportDialog = ({
|
|||||||
setAppState,
|
setAppState,
|
||||||
}: {
|
}: {
|
||||||
elements: readonly NonDeletedExcalidrawElement[];
|
elements: readonly NonDeletedExcalidrawElement[];
|
||||||
appState: AppState;
|
appState: UIAppState;
|
||||||
files: BinaryFiles;
|
files: BinaryFiles;
|
||||||
actionManager: ActionManager;
|
actionManager: ActionManager;
|
||||||
exportOpts: ExportOpts;
|
exportOpts: ExportOpts;
|
||||||
canvas: HTMLCanvasElement | null;
|
canvas: HTMLCanvasElement | null;
|
||||||
setAppState: React.Component<any, AppState>["setState"];
|
setAppState: React.Component<any, UIAppState>["setState"];
|
||||||
}) => {
|
}) => {
|
||||||
const handleClose = React.useCallback(() => {
|
const handleClose = React.useCallback(() => {
|
||||||
setAppState({ openDialog: null });
|
setAppState({ openDialog: null });
|
||||||
|
@ -8,7 +8,13 @@ import { NonDeletedExcalidrawElement } from "../element/types";
|
|||||||
import { Language, t } from "../i18n";
|
import { Language, t } from "../i18n";
|
||||||
import { calculateScrollCenter } from "../scene";
|
import { calculateScrollCenter } from "../scene";
|
||||||
import { ExportType } from "../scene/types";
|
import { ExportType } from "../scene/types";
|
||||||
import { AppProps, AppState, ExcalidrawProps, BinaryFiles } from "../types";
|
import {
|
||||||
|
AppProps,
|
||||||
|
AppState,
|
||||||
|
ExcalidrawProps,
|
||||||
|
BinaryFiles,
|
||||||
|
UIAppState,
|
||||||
|
} from "../types";
|
||||||
import { capitalizeString, isShallowEqual, muteFSAbortError } from "../utils";
|
import { capitalizeString, isShallowEqual, muteFSAbortError } from "../utils";
|
||||||
import { SelectedShapeActions, ShapesSwitcher } from "./Actions";
|
import { SelectedShapeActions, ShapesSwitcher } from "./Actions";
|
||||||
import { ErrorDialog } from "./ErrorDialog";
|
import { ErrorDialog } from "./ErrorDialog";
|
||||||
@ -49,7 +55,7 @@ import "./Toolbar.scss";
|
|||||||
|
|
||||||
interface LayerUIProps {
|
interface LayerUIProps {
|
||||||
actionManager: ActionManager;
|
actionManager: ActionManager;
|
||||||
appState: AppState;
|
appState: UIAppState;
|
||||||
files: BinaryFiles;
|
files: BinaryFiles;
|
||||||
canvas: HTMLCanvasElement | null;
|
canvas: HTMLCanvasElement | null;
|
||||||
setAppState: React.Component<any, AppState>["setState"];
|
setAppState: React.Component<any, AppState>["setState"];
|
||||||
@ -144,7 +150,8 @@ const LayerUI = ({
|
|||||||
const fileHandle = await exportCanvas(
|
const fileHandle = await exportCanvas(
|
||||||
type,
|
type,
|
||||||
exportedElements,
|
exportedElements,
|
||||||
appState,
|
// FIXME once we split UI canvas from element canvas
|
||||||
|
appState as AppState,
|
||||||
files,
|
files,
|
||||||
{
|
{
|
||||||
exportBackground: appState.exportBackground,
|
exportBackground: appState.exportBackground,
|
||||||
@ -458,9 +465,9 @@ const LayerUI = ({
|
|||||||
<button
|
<button
|
||||||
className="scroll-back-to-content"
|
className="scroll-back-to-content"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setAppState({
|
setAppState((appState) => ({
|
||||||
...calculateScrollCenter(elements, appState, canvas),
|
...calculateScrollCenter(elements, appState, canvas),
|
||||||
});
|
}));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t("buttons.scrollBackToContent")}
|
{t("buttons.scrollBackToContent")}
|
||||||
@ -484,14 +491,15 @@ const LayerUI = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const stripIrrelevantAppStateProps = (
|
const stripIrrelevantAppStateProps = (appState: AppState): UIAppState => {
|
||||||
appState: AppState,
|
const {
|
||||||
): Omit<
|
suggestedBindings,
|
||||||
AppState,
|
startBoundElement,
|
||||||
"suggestedBindings" | "startBoundElement" | "cursorButton"
|
cursorButton,
|
||||||
> => {
|
scrollX,
|
||||||
const { suggestedBindings, startBoundElement, cursorButton, ...ret } =
|
scrollY,
|
||||||
appState;
|
...ret
|
||||||
|
} = appState;
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -506,8 +514,10 @@ const areEqual = (prevProps: LayerUIProps, nextProps: LayerUIProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
isShallowEqual(
|
isShallowEqual(
|
||||||
stripIrrelevantAppStateProps(prevAppState),
|
// asserting AppState because we're being passed the whole AppState
|
||||||
stripIrrelevantAppStateProps(nextAppState),
|
// but resolve to only the UI-relevant props
|
||||||
|
stripIrrelevantAppStateProps(prevAppState as AppState),
|
||||||
|
stripIrrelevantAppStateProps(nextAppState as AppState),
|
||||||
{
|
{
|
||||||
selectedElementIds: isShallowEqual,
|
selectedElementIds: isShallowEqual,
|
||||||
selectedGroupIds: isShallowEqual,
|
selectedGroupIds: isShallowEqual,
|
||||||
|
@ -5,7 +5,12 @@ import Library, {
|
|||||||
} from "../data/library";
|
} from "../data/library";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { randomId } from "../random";
|
import { randomId } from "../random";
|
||||||
import { LibraryItems, LibraryItem, AppState, ExcalidrawProps } from "../types";
|
import {
|
||||||
|
LibraryItems,
|
||||||
|
LibraryItem,
|
||||||
|
ExcalidrawProps,
|
||||||
|
UIAppState,
|
||||||
|
} from "../types";
|
||||||
import LibraryMenuItems from "./LibraryMenuItems";
|
import LibraryMenuItems from "./LibraryMenuItems";
|
||||||
import { trackEvent } from "../analytics";
|
import { trackEvent } from "../analytics";
|
||||||
import { atom, useAtom } from "jotai";
|
import { atom, useAtom } from "jotai";
|
||||||
@ -44,11 +49,11 @@ export const LibraryMenuContent = ({
|
|||||||
pendingElements: LibraryItem["elements"];
|
pendingElements: LibraryItem["elements"];
|
||||||
onInsertLibraryItems: (libraryItems: LibraryItems) => void;
|
onInsertLibraryItems: (libraryItems: LibraryItems) => void;
|
||||||
onAddToLibrary: () => void;
|
onAddToLibrary: () => void;
|
||||||
setAppState: React.Component<any, AppState>["setState"];
|
setAppState: React.Component<any, UIAppState>["setState"];
|
||||||
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
|
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
|
||||||
library: Library;
|
library: Library;
|
||||||
id: string;
|
id: string;
|
||||||
appState: AppState;
|
appState: UIAppState;
|
||||||
selectedItems: LibraryItem["id"][];
|
selectedItems: LibraryItem["id"][];
|
||||||
onSelectItems: (id: LibraryItem["id"][]) => void;
|
onSelectItems: (id: LibraryItem["id"][]) => void;
|
||||||
}) => {
|
}) => {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { VERSIONS } from "../constants";
|
import { VERSIONS } from "../constants";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { AppState, ExcalidrawProps } from "../types";
|
import { ExcalidrawProps, UIAppState } from "../types";
|
||||||
|
|
||||||
const LibraryMenuBrowseButton = ({
|
const LibraryMenuBrowseButton = ({
|
||||||
theme,
|
theme,
|
||||||
@ -8,7 +8,7 @@ const LibraryMenuBrowseButton = ({
|
|||||||
libraryReturnUrl,
|
libraryReturnUrl,
|
||||||
}: {
|
}: {
|
||||||
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
|
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
|
||||||
theme: AppState["theme"];
|
theme: UIAppState["theme"];
|
||||||
id: string;
|
id: string;
|
||||||
}) => {
|
}) => {
|
||||||
const referrer =
|
const referrer =
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { LibraryItem, ExcalidrawProps, AppState } from "../types";
|
import { LibraryItem, ExcalidrawProps, UIAppState } from "../types";
|
||||||
import LibraryMenuBrowseButton from "./LibraryMenuBrowseButton";
|
import LibraryMenuBrowseButton from "./LibraryMenuBrowseButton";
|
||||||
import { LibraryDropdownMenu } from "./LibraryMenuHeaderContent";
|
import { LibraryDropdownMenu } from "./LibraryMenuHeaderContent";
|
||||||
|
|
||||||
@ -13,7 +13,7 @@ export const LibraryMenuControlButtons = ({
|
|||||||
selectedItems: LibraryItem["id"][];
|
selectedItems: LibraryItem["id"][];
|
||||||
onSelectItems: (id: LibraryItem["id"][]) => void;
|
onSelectItems: (id: LibraryItem["id"][]) => void;
|
||||||
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
|
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
|
||||||
theme: AppState["theme"];
|
theme: UIAppState["theme"];
|
||||||
id: string;
|
id: string;
|
||||||
style: React.CSSProperties;
|
style: React.CSSProperties;
|
||||||
}) => {
|
}) => {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { jotaiScope } from "../jotai";
|
import { jotaiScope } from "../jotai";
|
||||||
import { AppState, LibraryItem, LibraryItems } from "../types";
|
import { LibraryItem, LibraryItems, UIAppState } from "../types";
|
||||||
import { useApp, useExcalidrawAppState, useExcalidrawSetAppState } from "./App";
|
import { useApp, useExcalidrawSetAppState } from "./App";
|
||||||
import { saveLibraryAsJSON } from "../data/json";
|
import { saveLibraryAsJSON } from "../data/json";
|
||||||
import Library, { libraryItemsAtom } from "../data/library";
|
import Library, { libraryItemsAtom } from "../data/library";
|
||||||
import {
|
import {
|
||||||
@ -21,6 +21,7 @@ import PublishLibrary from "./PublishLibrary";
|
|||||||
import { Dialog } from "./Dialog";
|
import { Dialog } from "./Dialog";
|
||||||
import DropdownMenu from "./dropdownMenu/DropdownMenu";
|
import DropdownMenu from "./dropdownMenu/DropdownMenu";
|
||||||
import { isLibraryMenuOpenAtom } from "./LibraryMenu";
|
import { isLibraryMenuOpenAtom } from "./LibraryMenu";
|
||||||
|
import { useUIAppState } from "../context/ui-appState";
|
||||||
|
|
||||||
const getSelectedItems = (
|
const getSelectedItems = (
|
||||||
libraryItems: LibraryItems,
|
libraryItems: LibraryItems,
|
||||||
@ -28,13 +29,13 @@ const getSelectedItems = (
|
|||||||
) => libraryItems.filter((item) => selectedItems.includes(item.id));
|
) => libraryItems.filter((item) => selectedItems.includes(item.id));
|
||||||
|
|
||||||
export const LibraryDropdownMenuButton: React.FC<{
|
export const LibraryDropdownMenuButton: React.FC<{
|
||||||
setAppState: React.Component<any, AppState>["setState"];
|
setAppState: React.Component<any, UIAppState>["setState"];
|
||||||
selectedItems: LibraryItem["id"][];
|
selectedItems: LibraryItem["id"][];
|
||||||
library: Library;
|
library: Library;
|
||||||
onRemoveFromLibrary: () => void;
|
onRemoveFromLibrary: () => void;
|
||||||
resetLibrary: () => void;
|
resetLibrary: () => void;
|
||||||
onSelectItems: (items: LibraryItem["id"][]) => void;
|
onSelectItems: (items: LibraryItem["id"][]) => void;
|
||||||
appState: AppState;
|
appState: UIAppState;
|
||||||
}> = ({
|
}> = ({
|
||||||
setAppState,
|
setAppState,
|
||||||
selectedItems,
|
selectedItems,
|
||||||
@ -270,7 +271,7 @@ export const LibraryDropdownMenu = ({
|
|||||||
onSelectItems: (id: LibraryItem["id"][]) => void;
|
onSelectItems: (id: LibraryItem["id"][]) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { library } = useApp();
|
const { library } = useApp();
|
||||||
const appState = useExcalidrawAppState();
|
const appState = useUIAppState();
|
||||||
const setAppState = useExcalidrawSetAppState();
|
const setAppState = useExcalidrawSetAppState();
|
||||||
|
|
||||||
const [libraryItemsData] = useAtom(libraryItemsAtom, jotaiScope);
|
const [libraryItemsData] = useAtom(libraryItemsAtom, jotaiScope);
|
||||||
|
@ -2,17 +2,22 @@ import React, { useState } from "react";
|
|||||||
import { serializeLibraryAsJSON } from "../data/json";
|
import { serializeLibraryAsJSON } from "../data/json";
|
||||||
import { ExcalidrawElement, NonDeleted } from "../element/types";
|
import { ExcalidrawElement, NonDeleted } from "../element/types";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { AppState, ExcalidrawProps, LibraryItem, LibraryItems } from "../types";
|
import {
|
||||||
|
ExcalidrawProps,
|
||||||
|
LibraryItem,
|
||||||
|
LibraryItems,
|
||||||
|
UIAppState,
|
||||||
|
} from "../types";
|
||||||
import { arrayToMap, chunk } from "../utils";
|
import { arrayToMap, chunk } from "../utils";
|
||||||
import { LibraryUnit } from "./LibraryUnit";
|
import { LibraryUnit } from "./LibraryUnit";
|
||||||
import Stack from "./Stack";
|
import Stack from "./Stack";
|
||||||
|
|
||||||
import "./LibraryMenuItems.scss";
|
|
||||||
import { MIME_TYPES } from "../constants";
|
import { MIME_TYPES } from "../constants";
|
||||||
import Spinner from "./Spinner";
|
import Spinner from "./Spinner";
|
||||||
import { duplicateElements } from "../element/newElement";
|
import { duplicateElements } from "../element/newElement";
|
||||||
import { LibraryMenuControlButtons } from "./LibraryMenuControlButtons";
|
import { LibraryMenuControlButtons } from "./LibraryMenuControlButtons";
|
||||||
|
|
||||||
|
import "./LibraryMenuItems.scss";
|
||||||
|
|
||||||
const CELLS_PER_ROW = 4;
|
const CELLS_PER_ROW = 4;
|
||||||
|
|
||||||
const LibraryMenuItems = ({
|
const LibraryMenuItems = ({
|
||||||
@ -35,7 +40,7 @@ const LibraryMenuItems = ({
|
|||||||
selectedItems: LibraryItem["id"][];
|
selectedItems: LibraryItem["id"][];
|
||||||
onSelectItems: (id: LibraryItem["id"][]) => void;
|
onSelectItems: (id: LibraryItem["id"][]) => void;
|
||||||
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
|
libraryReturnUrl: ExcalidrawProps["libraryReturnUrl"];
|
||||||
theme: AppState["theme"];
|
theme: UIAppState["theme"];
|
||||||
id: string;
|
id: string;
|
||||||
}) => {
|
}) => {
|
||||||
const [lastSelectedItem, setLastSelectedItem] = useState<
|
const [lastSelectedItem, setLastSelectedItem] = useState<
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { AppState, Device, ExcalidrawProps } from "../types";
|
import { AppState, Device, ExcalidrawProps, UIAppState } from "../types";
|
||||||
import { ActionManager } from "../actions/manager";
|
import { ActionManager } from "../actions/manager";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import Stack from "./Stack";
|
import Stack from "./Stack";
|
||||||
@ -21,7 +21,7 @@ import { isHandToolActive } from "../appState";
|
|||||||
import { useTunnels } from "../context/tunnels";
|
import { useTunnels } from "../context/tunnels";
|
||||||
|
|
||||||
type MobileMenuProps = {
|
type MobileMenuProps = {
|
||||||
appState: AppState;
|
appState: UIAppState;
|
||||||
actionManager: ActionManager;
|
actionManager: ActionManager;
|
||||||
renderJSONExportDialog: () => React.ReactNode;
|
renderJSONExportDialog: () => React.ReactNode;
|
||||||
renderImageExportDialog: () => React.ReactNode;
|
renderImageExportDialog: () => React.ReactNode;
|
||||||
@ -35,7 +35,7 @@ type MobileMenuProps = {
|
|||||||
onImageAction: (data: { insertOnCanvasDirectly: boolean }) => void;
|
onImageAction: (data: { insertOnCanvasDirectly: boolean }) => void;
|
||||||
renderTopRightUI?: (
|
renderTopRightUI?: (
|
||||||
isMobile: boolean,
|
isMobile: boolean,
|
||||||
appState: AppState,
|
appState: UIAppState,
|
||||||
) => JSX.Element | null;
|
) => JSX.Element | null;
|
||||||
renderCustomStats?: ExcalidrawProps["renderCustomStats"];
|
renderCustomStats?: ExcalidrawProps["renderCustomStats"];
|
||||||
renderSidebars: () => JSX.Element | null;
|
renderSidebars: () => JSX.Element | null;
|
||||||
@ -193,9 +193,9 @@ export const MobileMenu = ({
|
|||||||
<button
|
<button
|
||||||
className="scroll-back-to-content"
|
className="scroll-back-to-content"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setAppState({
|
setAppState((appState) => ({
|
||||||
...calculateScrollCenter(elements, appState, canvas),
|
...calculateScrollCenter(elements, appState, canvas),
|
||||||
});
|
}));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t("buttons.scrollBackToContent")}
|
{t("buttons.scrollBackToContent")}
|
||||||
|
@ -5,9 +5,10 @@ import { ChartElements, renderSpreadsheet, Spreadsheet } from "../charts";
|
|||||||
import { ChartType } from "../element/types";
|
import { ChartType } from "../element/types";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { exportToSvg } from "../scene/export";
|
import { exportToSvg } from "../scene/export";
|
||||||
import { AppState } from "../types";
|
import { UIAppState } from "../types";
|
||||||
import { useApp } from "./App";
|
import { useApp } from "./App";
|
||||||
import { Dialog } from "./Dialog";
|
import { Dialog } from "./Dialog";
|
||||||
|
|
||||||
import "./PasteChartDialog.scss";
|
import "./PasteChartDialog.scss";
|
||||||
|
|
||||||
type OnInsertChart = (chartType: ChartType, elements: ChartElements) => void;
|
type OnInsertChart = (chartType: ChartType, elements: ChartElements) => void;
|
||||||
@ -80,9 +81,9 @@ export const PasteChartDialog = ({
|
|||||||
appState,
|
appState,
|
||||||
onClose,
|
onClose,
|
||||||
}: {
|
}: {
|
||||||
appState: AppState;
|
appState: UIAppState;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
setAppState: React.Component<any, AppState>["setState"];
|
setAppState: React.Component<any, UIAppState>["setState"];
|
||||||
}) => {
|
}) => {
|
||||||
const { onInsertElements } = useApp();
|
const { onInsertElements } = useApp();
|
||||||
const handleClose = React.useCallback(() => {
|
const handleClose = React.useCallback(() => {
|
||||||
|
@ -4,7 +4,7 @@ import OpenColor from "open-color";
|
|||||||
import { Dialog } from "./Dialog";
|
import { Dialog } from "./Dialog";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
|
|
||||||
import { AppState, LibraryItems, LibraryItem } from "../types";
|
import { LibraryItems, LibraryItem, UIAppState } from "../types";
|
||||||
import { exportToCanvas, exportToSvg } from "../packages/utils";
|
import { exportToCanvas, exportToSvg } from "../packages/utils";
|
||||||
import {
|
import {
|
||||||
EXPORT_DATA_TYPES,
|
EXPORT_DATA_TYPES,
|
||||||
@ -135,7 +135,7 @@ const SingleLibraryItem = ({
|
|||||||
onRemove,
|
onRemove,
|
||||||
}: {
|
}: {
|
||||||
libItem: LibraryItem;
|
libItem: LibraryItem;
|
||||||
appState: AppState;
|
appState: UIAppState;
|
||||||
index: number;
|
index: number;
|
||||||
onChange: (val: string, index: number) => void;
|
onChange: (val: string, index: number) => void;
|
||||||
onRemove: (id: string) => void;
|
onRemove: (id: string) => void;
|
||||||
@ -231,7 +231,7 @@ const PublishLibrary = ({
|
|||||||
}: {
|
}: {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
libraryItems: LibraryItems;
|
libraryItems: LibraryItems;
|
||||||
appState: AppState;
|
appState: UIAppState;
|
||||||
onSuccess: (data: {
|
onSuccess: (data: {
|
||||||
url: string;
|
url: string;
|
||||||
authorName: string;
|
authorName: string;
|
||||||
|
@ -18,11 +18,7 @@ import {
|
|||||||
} from "./common";
|
} from "./common";
|
||||||
import { SidebarHeader } from "./SidebarHeader";
|
import { SidebarHeader } from "./SidebarHeader";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import {
|
import { useDevice, useExcalidrawSetAppState } from "../App";
|
||||||
useDevice,
|
|
||||||
useExcalidrawAppState,
|
|
||||||
useExcalidrawSetAppState,
|
|
||||||
} from "../App";
|
|
||||||
import { updateObject } from "../../utils";
|
import { updateObject } from "../../utils";
|
||||||
import { KEYS } from "../../keys";
|
import { KEYS } from "../../keys";
|
||||||
import { EVENT } from "../../constants";
|
import { EVENT } from "../../constants";
|
||||||
@ -33,6 +29,7 @@ import { SidebarTabs } from "./SidebarTabs";
|
|||||||
import { SidebarTab } from "./SidebarTab";
|
import { SidebarTab } from "./SidebarTab";
|
||||||
|
|
||||||
import "./Sidebar.scss";
|
import "./Sidebar.scss";
|
||||||
|
import { useUIAppState } from "../../context/ui-appState";
|
||||||
|
|
||||||
// FIXME replace this with the implem from ColorPicker once it's merged
|
// FIXME replace this with the implem from ColorPicker once it's merged
|
||||||
const useOnClickOutside = (
|
const useOnClickOutside = (
|
||||||
@ -185,7 +182,7 @@ SidebarInner.displayName = "SidebarInner";
|
|||||||
|
|
||||||
export const Sidebar = Object.assign(
|
export const Sidebar = Object.assign(
|
||||||
forwardRef((props: SidebarProps, ref: React.ForwardedRef<HTMLDivElement>) => {
|
forwardRef((props: SidebarProps, ref: React.ForwardedRef<HTMLDivElement>) => {
|
||||||
const appState = useExcalidrawAppState();
|
const appState = useUIAppState();
|
||||||
|
|
||||||
const { onStateChange } = props;
|
const { onStateChange } = props;
|
||||||
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import { useExcalidrawSetAppState, useExcalidrawAppState } from "../App";
|
import { useExcalidrawSetAppState } from "../App";
|
||||||
import { SidebarTriggerProps } from "./common";
|
import { SidebarTriggerProps } from "./common";
|
||||||
|
import { useUIAppState } from "../../context/ui-appState";
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
import "./SidebarTrigger.scss";
|
import "./SidebarTrigger.scss";
|
||||||
import clsx from "clsx";
|
|
||||||
|
|
||||||
export const SidebarTrigger = ({
|
export const SidebarTrigger = ({
|
||||||
name,
|
name,
|
||||||
@ -15,8 +16,7 @@ export const SidebarTrigger = ({
|
|||||||
style,
|
style,
|
||||||
}: SidebarTriggerProps) => {
|
}: SidebarTriggerProps) => {
|
||||||
const setAppState = useExcalidrawSetAppState();
|
const setAppState = useExcalidrawSetAppState();
|
||||||
// TODO replace with sidebar context
|
const appState = useUIAppState();
|
||||||
const appState = useExcalidrawAppState();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<label title={title}>
|
<label title={title}>
|
||||||
|
@ -3,14 +3,14 @@ import { getCommonBounds } from "../element/bounds";
|
|||||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { getTargetElements } from "../scene";
|
import { getTargetElements } from "../scene";
|
||||||
import { AppState, ExcalidrawProps } from "../types";
|
import { ExcalidrawProps, UIAppState } from "../types";
|
||||||
import { CloseIcon } from "./icons";
|
import { CloseIcon } from "./icons";
|
||||||
import { Island } from "./Island";
|
import { Island } from "./Island";
|
||||||
import "./Stats.scss";
|
import "./Stats.scss";
|
||||||
|
|
||||||
export const Stats = (props: {
|
export const Stats = (props: {
|
||||||
appState: AppState;
|
appState: UIAppState;
|
||||||
setAppState: React.Component<any, AppState>["setState"];
|
setAppState: React.Component<any, UIAppState>["setState"];
|
||||||
elements: readonly NonDeletedExcalidrawElement[];
|
elements: readonly NonDeletedExcalidrawElement[];
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
renderCustomStats: ExcalidrawProps["renderCustomStats"];
|
renderCustomStats: ExcalidrawProps["renderCustomStats"];
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { useDevice, useExcalidrawAppState } from "../App";
|
import { useUIAppState } from "../../context/ui-appState";
|
||||||
|
import { useDevice } from "../App";
|
||||||
|
|
||||||
const MenuTrigger = ({
|
const MenuTrigger = ({
|
||||||
className = "",
|
className = "",
|
||||||
@ -10,7 +11,7 @@ const MenuTrigger = ({
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
onToggle: () => void;
|
onToggle: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const appState = useExcalidrawAppState();
|
const appState = useUIAppState();
|
||||||
const device = useDevice();
|
const device = useDevice();
|
||||||
const classNames = clsx(
|
const classNames = clsx(
|
||||||
`dropdown-menu-button ${className}`,
|
`dropdown-menu-button ${className}`,
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { actionShortcuts } from "../../actions";
|
import { actionShortcuts } from "../../actions";
|
||||||
import { ActionManager } from "../../actions/manager";
|
import { ActionManager } from "../../actions/manager";
|
||||||
import { AppState } from "../../types";
|
|
||||||
import {
|
import {
|
||||||
ExitZenModeAction,
|
ExitZenModeAction,
|
||||||
FinalizeAction,
|
FinalizeAction,
|
||||||
@ -13,6 +12,7 @@ import { useTunnels } from "../../context/tunnels";
|
|||||||
import { HelpButton } from "../HelpButton";
|
import { HelpButton } from "../HelpButton";
|
||||||
import { Section } from "../Section";
|
import { Section } from "../Section";
|
||||||
import Stack from "../Stack";
|
import Stack from "../Stack";
|
||||||
|
import { UIAppState } from "../../types";
|
||||||
|
|
||||||
const Footer = ({
|
const Footer = ({
|
||||||
appState,
|
appState,
|
||||||
@ -20,7 +20,7 @@ const Footer = ({
|
|||||||
showExitZenModeBtn,
|
showExitZenModeBtn,
|
||||||
renderWelcomeScreen,
|
renderWelcomeScreen,
|
||||||
}: {
|
}: {
|
||||||
appState: AppState;
|
appState: UIAppState;
|
||||||
actionManager: ActionManager;
|
actionManager: ActionManager;
|
||||||
showExitZenModeBtn: boolean;
|
showExitZenModeBtn: boolean;
|
||||||
renderWelcomeScreen: boolean;
|
renderWelcomeScreen: boolean;
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { useExcalidrawAppState } from "../App";
|
|
||||||
import { useTunnels } from "../../context/tunnels";
|
import { useTunnels } from "../../context/tunnels";
|
||||||
import "./FooterCenter.scss";
|
import "./FooterCenter.scss";
|
||||||
|
import { useUIAppState } from "../../context/ui-appState";
|
||||||
|
|
||||||
const FooterCenter = ({ children }: { children?: React.ReactNode }) => {
|
const FooterCenter = ({ children }: { children?: React.ReactNode }) => {
|
||||||
const { FooterCenterTunnel } = useTunnels();
|
const { FooterCenterTunnel } = useTunnels();
|
||||||
const appState = useExcalidrawAppState();
|
const appState = useUIAppState();
|
||||||
return (
|
return (
|
||||||
<FooterCenterTunnel.In>
|
<FooterCenterTunnel.In>
|
||||||
<div
|
<div
|
||||||
|
@ -3,9 +3,9 @@ import { usersIcon } from "../icons";
|
|||||||
import { Button } from "../Button";
|
import { Button } from "../Button";
|
||||||
|
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { useExcalidrawAppState } from "../App";
|
|
||||||
|
|
||||||
import "./LiveCollaborationTrigger.scss";
|
import "./LiveCollaborationTrigger.scss";
|
||||||
|
import { useUIAppState } from "../../context/ui-appState";
|
||||||
|
|
||||||
const LiveCollaborationTrigger = ({
|
const LiveCollaborationTrigger = ({
|
||||||
isCollaborating,
|
isCollaborating,
|
||||||
@ -15,7 +15,7 @@ const LiveCollaborationTrigger = ({
|
|||||||
isCollaborating: boolean;
|
isCollaborating: boolean;
|
||||||
onSelect: () => void;
|
onSelect: () => void;
|
||||||
} & React.ButtonHTMLAttributes<HTMLButtonElement>) => {
|
} & React.ButtonHTMLAttributes<HTMLButtonElement>) => {
|
||||||
const appState = useExcalidrawAppState();
|
const appState = useUIAppState();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
import { getShortcutFromShortcutName } from "../../actions/shortcuts";
|
import { getShortcutFromShortcutName } from "../../actions/shortcuts";
|
||||||
import { useI18n } from "../../i18n";
|
import { useI18n } from "../../i18n";
|
||||||
import {
|
import { useExcalidrawSetAppState, useExcalidrawActionManager } from "../App";
|
||||||
useExcalidrawAppState,
|
|
||||||
useExcalidrawSetAppState,
|
|
||||||
useExcalidrawActionManager,
|
|
||||||
} from "../App";
|
|
||||||
import {
|
import {
|
||||||
ExportIcon,
|
ExportIcon,
|
||||||
ExportImageIcon,
|
ExportImageIcon,
|
||||||
@ -32,6 +28,7 @@ import clsx from "clsx";
|
|||||||
import { useSetAtom } from "jotai";
|
import { useSetAtom } from "jotai";
|
||||||
import { activeConfirmDialogAtom } from "../ActiveConfirmDialog";
|
import { activeConfirmDialogAtom } from "../ActiveConfirmDialog";
|
||||||
import { jotaiScope } from "../../jotai";
|
import { jotaiScope } from "../../jotai";
|
||||||
|
import { useUIAppState } from "../../context/ui-appState";
|
||||||
|
|
||||||
export const LoadScene = () => {
|
export const LoadScene = () => {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
@ -139,7 +136,7 @@ ClearCanvas.displayName = "ClearCanvas";
|
|||||||
|
|
||||||
export const ToggleTheme = () => {
|
export const ToggleTheme = () => {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const appState = useExcalidrawAppState();
|
const appState = useUIAppState();
|
||||||
const actionManager = useExcalidrawActionManager();
|
const actionManager = useExcalidrawActionManager();
|
||||||
|
|
||||||
if (!actionManager.isActionEnabled(actionToggleTheme)) {
|
if (!actionManager.isActionEnabled(actionToggleTheme)) {
|
||||||
@ -172,7 +169,7 @@ ToggleTheme.displayName = "ToggleTheme";
|
|||||||
|
|
||||||
export const ChangeCanvasBackground = () => {
|
export const ChangeCanvasBackground = () => {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const appState = useExcalidrawAppState();
|
const appState = useUIAppState();
|
||||||
const actionManager = useExcalidrawActionManager();
|
const actionManager = useExcalidrawActionManager();
|
||||||
|
|
||||||
if (appState.viewModeEnabled) {
|
if (appState.viewModeEnabled) {
|
||||||
|
@ -1,9 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import {
|
import { useDevice, useExcalidrawSetAppState } from "../App";
|
||||||
useDevice,
|
|
||||||
useExcalidrawAppState,
|
|
||||||
useExcalidrawSetAppState,
|
|
||||||
} from "../App";
|
|
||||||
import DropdownMenu from "../dropdownMenu/DropdownMenu";
|
import DropdownMenu from "../dropdownMenu/DropdownMenu";
|
||||||
|
|
||||||
import * as DefaultItems from "./DefaultItems";
|
import * as DefaultItems from "./DefaultItems";
|
||||||
@ -14,6 +10,7 @@ import { HamburgerMenuIcon } from "../icons";
|
|||||||
import { withInternalFallback } from "../hoc/withInternalFallback";
|
import { withInternalFallback } from "../hoc/withInternalFallback";
|
||||||
import { composeEventHandlers } from "../../utils";
|
import { composeEventHandlers } from "../../utils";
|
||||||
import { useTunnels } from "../../context/tunnels";
|
import { useTunnels } from "../../context/tunnels";
|
||||||
|
import { useUIAppState } from "../../context/ui-appState";
|
||||||
|
|
||||||
const MainMenu = Object.assign(
|
const MainMenu = Object.assign(
|
||||||
withInternalFallback(
|
withInternalFallback(
|
||||||
@ -30,7 +27,7 @@ const MainMenu = Object.assign(
|
|||||||
}) => {
|
}) => {
|
||||||
const { MainMenuTunnel } = useTunnels();
|
const { MainMenuTunnel } = useTunnels();
|
||||||
const device = useDevice();
|
const device = useDevice();
|
||||||
const appState = useExcalidrawAppState();
|
const appState = useUIAppState();
|
||||||
const setAppState = useExcalidrawSetAppState();
|
const setAppState = useExcalidrawSetAppState();
|
||||||
const onClickOutside = device.isMobile
|
const onClickOutside = device.isMobile
|
||||||
? undefined
|
? undefined
|
||||||
|
@ -1,13 +1,10 @@
|
|||||||
import { actionLoadScene, actionShortcuts } from "../../actions";
|
import { actionLoadScene, actionShortcuts } from "../../actions";
|
||||||
import { getShortcutFromShortcutName } from "../../actions/shortcuts";
|
import { getShortcutFromShortcutName } from "../../actions/shortcuts";
|
||||||
import { t, useI18n } from "../../i18n";
|
import { t, useI18n } from "../../i18n";
|
||||||
import {
|
import { useDevice, useExcalidrawActionManager } from "../App";
|
||||||
useDevice,
|
|
||||||
useExcalidrawActionManager,
|
|
||||||
useExcalidrawAppState,
|
|
||||||
} from "../App";
|
|
||||||
import { useTunnels } from "../../context/tunnels";
|
import { useTunnels } from "../../context/tunnels";
|
||||||
import { ExcalLogo, HelpIcon, LoadIcon, usersIcon } from "../icons";
|
import { ExcalLogo, HelpIcon, LoadIcon, usersIcon } from "../icons";
|
||||||
|
import { useUIAppState } from "../../context/ui-appState";
|
||||||
|
|
||||||
const WelcomeScreenMenuItemContent = ({
|
const WelcomeScreenMenuItemContent = ({
|
||||||
icon,
|
icon,
|
||||||
@ -148,7 +145,7 @@ const MenuItemHelp = () => {
|
|||||||
MenuItemHelp.displayName = "MenuItemHelp";
|
MenuItemHelp.displayName = "MenuItemHelp";
|
||||||
|
|
||||||
const MenuItemLoadScene = () => {
|
const MenuItemLoadScene = () => {
|
||||||
const appState = useExcalidrawAppState();
|
const appState = useUIAppState();
|
||||||
const actionManager = useExcalidrawActionManager();
|
const actionManager = useExcalidrawActionManager();
|
||||||
|
|
||||||
if (appState.viewModeEnabled) {
|
if (appState.viewModeEnabled) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { AppState } from "../types";
|
import { UIAppState } from "../types";
|
||||||
|
|
||||||
export const UIAppStateContext = React.createContext<AppState>(null!);
|
export const UIAppStateContext = React.createContext<UIAppState>(null!);
|
||||||
export const useUIAppState = () => React.useContext(UIAppStateContext);
|
export const useUIAppState = () => React.useContext(UIAppStateContext);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { AppState, ExcalidrawProps, Point } from "../types";
|
import { AppState, ExcalidrawProps, Point, UIAppState } from "../types";
|
||||||
import {
|
import {
|
||||||
getShortcutKey,
|
getShortcutKey,
|
||||||
sceneCoordsToViewportCoords,
|
sceneCoordsToViewportCoords,
|
||||||
@ -297,10 +297,11 @@ export const getContextMenuLabel = (
|
|||||||
: "labels.link.create";
|
: "labels.link.create";
|
||||||
return label;
|
return label;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getLinkHandleFromCoords = (
|
export const getLinkHandleFromCoords = (
|
||||||
[x1, y1, x2, y2]: Bounds,
|
[x1, y1, x2, y2]: Bounds,
|
||||||
angle: number,
|
angle: number,
|
||||||
appState: AppState,
|
appState: UIAppState,
|
||||||
): [x: number, y: number, width: number, height: number] => {
|
): [x: number, y: number, width: number, height: number] => {
|
||||||
const size = DEFAULT_LINK_SIZE;
|
const size = DEFAULT_LINK_SIZE;
|
||||||
const linkWidth = size / appState.zoom.value;
|
const linkWidth = size / appState.zoom.value;
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { AppState } from "../types";
|
|
||||||
import { NonDeletedExcalidrawElement } from "./types";
|
import { NonDeletedExcalidrawElement } from "./types";
|
||||||
import { getSelectedElements } from "../scene";
|
import { getSelectedElements } from "../scene";
|
||||||
|
import { UIAppState } from "../types";
|
||||||
|
|
||||||
export const showSelectedShapeActions = (
|
export const showSelectedShapeActions = (
|
||||||
appState: AppState,
|
appState: UIAppState,
|
||||||
elements: readonly NonDeletedExcalidrawElement[],
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
) =>
|
) =>
|
||||||
Boolean(
|
Boolean(
|
||||||
|
@ -7,8 +7,9 @@ import {
|
|||||||
import { DEFAULT_VERSION } from "../constants";
|
import { DEFAULT_VERSION } from "../constants";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { copyTextToSystemClipboard } from "../clipboard";
|
import { copyTextToSystemClipboard } from "../clipboard";
|
||||||
import { AppState } from "../types";
|
|
||||||
import { NonDeletedExcalidrawElement } from "../element/types";
|
import { NonDeletedExcalidrawElement } from "../element/types";
|
||||||
|
import { UIAppState } from "../types";
|
||||||
|
|
||||||
type StorageSizes = { scene: number; total: number };
|
type StorageSizes = { scene: number; total: number };
|
||||||
|
|
||||||
const STORAGE_SIZE_TIMEOUT = 500;
|
const STORAGE_SIZE_TIMEOUT = 500;
|
||||||
@ -23,7 +24,7 @@ const getStorageSizes = debounce((cb: (sizes: StorageSizes) => void) => {
|
|||||||
type Props = {
|
type Props = {
|
||||||
setToast: (message: string) => void;
|
setToast: (message: string) => void;
|
||||||
elements: readonly NonDeletedExcalidrawElement[];
|
elements: readonly NonDeletedExcalidrawElement[];
|
||||||
appState: AppState;
|
appState: UIAppState;
|
||||||
};
|
};
|
||||||
const CustomStats = (props: Props) => {
|
const CustomStats = (props: Props) => {
|
||||||
const [storageSizes, setStorageSizes] = useState<StorageSizes>({
|
const [storageSizes, setStorageSizes] = useState<StorageSizes>({
|
||||||
|
@ -18,7 +18,7 @@ import { getFrame } from "../../utils";
|
|||||||
|
|
||||||
const exportToExcalidrawPlus = async (
|
const exportToExcalidrawPlus = async (
|
||||||
elements: readonly NonDeletedExcalidrawElement[],
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: Partial<AppState>,
|
||||||
files: BinaryFiles,
|
files: BinaryFiles,
|
||||||
) => {
|
) => {
|
||||||
const firebase = await loadFirebaseStorage();
|
const firebase = await loadFirebaseStorage();
|
||||||
@ -75,7 +75,7 @@ const exportToExcalidrawPlus = async (
|
|||||||
|
|
||||||
export const ExportToExcalidrawPlus: React.FC<{
|
export const ExportToExcalidrawPlus: React.FC<{
|
||||||
elements: readonly NonDeletedExcalidrawElement[];
|
elements: readonly NonDeletedExcalidrawElement[];
|
||||||
appState: AppState;
|
appState: Partial<AppState>;
|
||||||
files: BinaryFiles;
|
files: BinaryFiles;
|
||||||
onError: (error: Error) => void;
|
onError: (error: Error) => void;
|
||||||
}> = ({ elements, appState, files, onError }) => {
|
}> = ({ elements, appState, files, onError }) => {
|
||||||
|
@ -284,7 +284,7 @@ export const loadScene = async (
|
|||||||
|
|
||||||
export const exportToBackend = async (
|
export const exportToBackend = async (
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: Partial<AppState>,
|
||||||
files: BinaryFiles,
|
files: BinaryFiles,
|
||||||
) => {
|
) => {
|
||||||
const encryptionKey = await generateEncryptionKey("string");
|
const encryptionKey = await generateEncryptionKey("string");
|
||||||
|
@ -32,6 +32,7 @@ import {
|
|||||||
ExcalidrawImperativeAPI,
|
ExcalidrawImperativeAPI,
|
||||||
BinaryFiles,
|
BinaryFiles,
|
||||||
ExcalidrawInitialDataState,
|
ExcalidrawInitialDataState,
|
||||||
|
UIAppState,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
import {
|
import {
|
||||||
debounce,
|
debounce,
|
||||||
@ -550,7 +551,7 @@ const ExcalidrawWrapper = () => {
|
|||||||
|
|
||||||
const onExportToBackend = async (
|
const onExportToBackend = async (
|
||||||
exportedElements: readonly NonDeletedExcalidrawElement[],
|
exportedElements: readonly NonDeletedExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: Partial<AppState>,
|
||||||
files: BinaryFiles,
|
files: BinaryFiles,
|
||||||
canvas: HTMLCanvasElement | null,
|
canvas: HTMLCanvasElement | null,
|
||||||
) => {
|
) => {
|
||||||
@ -581,7 +582,7 @@ const ExcalidrawWrapper = () => {
|
|||||||
|
|
||||||
const renderCustomStats = (
|
const renderCustomStats = (
|
||||||
elements: readonly NonDeletedExcalidrawElement[],
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: UIAppState,
|
||||||
) => {
|
) => {
|
||||||
return (
|
return (
|
||||||
<CustomStats
|
<CustomStats
|
||||||
|
@ -30,7 +30,7 @@ export const getElementsWithinSelection = (
|
|||||||
|
|
||||||
export const isSomeElementSelected = (
|
export const isSomeElementSelected = (
|
||||||
elements: readonly NonDeletedExcalidrawElement[],
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: Pick<AppState, "selectedElementIds">,
|
||||||
): boolean =>
|
): boolean =>
|
||||||
elements.some((element) => appState.selectedElementIds[element.id]);
|
elements.some((element) => appState.selectedElementIds[element.id]);
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ export const isSomeElementSelected = (
|
|||||||
*/
|
*/
|
||||||
export const getCommonAttributeOfSelectedElements = <T>(
|
export const getCommonAttributeOfSelectedElements = <T>(
|
||||||
elements: readonly NonDeletedExcalidrawElement[],
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: Pick<AppState, "selectedElementIds">,
|
||||||
getAttribute: (element: ExcalidrawElement) => T,
|
getAttribute: (element: ExcalidrawElement) => T,
|
||||||
): T | null => {
|
): T | null => {
|
||||||
const attributes = Array.from(
|
const attributes = Array.from(
|
||||||
@ -55,7 +55,7 @@ export const getCommonAttributeOfSelectedElements = <T>(
|
|||||||
|
|
||||||
export const getSelectedElements = (
|
export const getSelectedElements = (
|
||||||
elements: readonly NonDeletedExcalidrawElement[],
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: Pick<AppState, "selectedElementIds">,
|
||||||
includeBoundTextElement: boolean = false,
|
includeBoundTextElement: boolean = false,
|
||||||
) =>
|
) =>
|
||||||
elements.filter((element) => {
|
elements.filter((element) => {
|
||||||
@ -74,7 +74,7 @@ export const getSelectedElements = (
|
|||||||
|
|
||||||
export const getTargetElements = (
|
export const getTargetElements = (
|
||||||
elements: readonly NonDeletedExcalidrawElement[],
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: Pick<AppState, "selectedElementIds" | "editingElement">,
|
||||||
) =>
|
) =>
|
||||||
appState.editingElement
|
appState.editingElement
|
||||||
? [appState.editingElement]
|
? [appState.editingElement]
|
||||||
|
19
src/types.ts
19
src/types.ts
@ -1,3 +1,4 @@
|
|||||||
|
import React from "react";
|
||||||
import {
|
import {
|
||||||
PointerType,
|
PointerType,
|
||||||
ExcalidrawLinearElement,
|
ExcalidrawLinearElement,
|
||||||
@ -32,7 +33,6 @@ import type { FileSystemHandle } from "./data/filesystem";
|
|||||||
import type { IMAGE_MIME_TYPES, MIME_TYPES } from "./constants";
|
import type { IMAGE_MIME_TYPES, MIME_TYPES } from "./constants";
|
||||||
import { ContextMenuItems } from "./components/ContextMenu";
|
import { ContextMenuItems } from "./components/ContextMenu";
|
||||||
import { Merge, ForwardRef, ValueOf } from "./utility-types";
|
import { Merge, ForwardRef, ValueOf } from "./utility-types";
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
export type Point = Readonly<RoughPoint>;
|
export type Point = Readonly<RoughPoint>;
|
||||||
|
|
||||||
@ -218,6 +218,15 @@ export type AppState = {
|
|||||||
selectedLinearElement: LinearElementEditor | null;
|
selectedLinearElement: LinearElementEditor | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type UIAppState = Omit<
|
||||||
|
AppState,
|
||||||
|
| "suggestedBindings"
|
||||||
|
| "startBoundElement"
|
||||||
|
| "cursorButton"
|
||||||
|
| "scrollX"
|
||||||
|
| "scrollY"
|
||||||
|
>;
|
||||||
|
|
||||||
export type NormalizedZoomValue = number & { _brand: "normalizedZoom" };
|
export type NormalizedZoomValue = number & { _brand: "normalizedZoom" };
|
||||||
|
|
||||||
export type Zoom = Readonly<{
|
export type Zoom = Readonly<{
|
||||||
@ -314,7 +323,7 @@ export interface ExcalidrawProps {
|
|||||||
) => Promise<boolean> | boolean;
|
) => Promise<boolean> | boolean;
|
||||||
renderTopRightUI?: (
|
renderTopRightUI?: (
|
||||||
isMobile: boolean,
|
isMobile: boolean,
|
||||||
appState: AppState,
|
appState: UIAppState,
|
||||||
) => JSX.Element | null;
|
) => JSX.Element | null;
|
||||||
langCode?: Language["code"];
|
langCode?: Language["code"];
|
||||||
viewModeEnabled?: boolean;
|
viewModeEnabled?: boolean;
|
||||||
@ -325,7 +334,7 @@ export interface ExcalidrawProps {
|
|||||||
name?: string;
|
name?: string;
|
||||||
renderCustomStats?: (
|
renderCustomStats?: (
|
||||||
elements: readonly NonDeletedExcalidrawElement[],
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: UIAppState,
|
||||||
) => JSX.Element;
|
) => JSX.Element;
|
||||||
UIOptions?: Partial<UIOptions>;
|
UIOptions?: Partial<UIOptions>;
|
||||||
detectScroll?: boolean;
|
detectScroll?: boolean;
|
||||||
@ -364,13 +373,13 @@ export type ExportOpts = {
|
|||||||
saveFileToDisk?: boolean;
|
saveFileToDisk?: boolean;
|
||||||
onExportToBackend?: (
|
onExportToBackend?: (
|
||||||
exportedElements: readonly NonDeletedExcalidrawElement[],
|
exportedElements: readonly NonDeletedExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: UIAppState,
|
||||||
files: BinaryFiles,
|
files: BinaryFiles,
|
||||||
canvas: HTMLCanvasElement | null,
|
canvas: HTMLCanvasElement | null,
|
||||||
) => void;
|
) => void;
|
||||||
renderCustomUI?: (
|
renderCustomUI?: (
|
||||||
exportedElements: readonly NonDeletedExcalidrawElement[],
|
exportedElements: readonly NonDeletedExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: UIAppState,
|
||||||
files: BinaryFiles,
|
files: BinaryFiles,
|
||||||
canvas: HTMLCanvasElement | null,
|
canvas: HTMLCanvasElement | null,
|
||||||
) => JSX.Element;
|
) => JSX.Element;
|
||||||
|
@ -372,7 +372,7 @@ export const setEraserCursor = (
|
|||||||
|
|
||||||
export const setCursorForShape = (
|
export const setCursorForShape = (
|
||||||
canvas: HTMLCanvasElement | null,
|
canvas: HTMLCanvasElement | null,
|
||||||
appState: AppState,
|
appState: Pick<AppState, "activeTool" | "theme">,
|
||||||
) => {
|
) => {
|
||||||
if (!canvas) {
|
if (!canvas) {
|
||||||
return;
|
return;
|
||||||
@ -787,7 +787,12 @@ export const isShallowEqual = <
|
|||||||
? comparator(objA[key], objB[key])
|
? comparator(objA[key], objB[key])
|
||||||
: objA[key] === objB[key];
|
: objA[key] === objB[key];
|
||||||
if (!ret && debug) {
|
if (!ret && debug) {
|
||||||
console.warn(`isShallowEqual: ${key} not equal ->`, objA[key], objB[key]);
|
console.info(
|
||||||
|
`%cisShallowEqual: ${key} not equal ->`,
|
||||||
|
"color: #8B4000",
|
||||||
|
objA[key],
|
||||||
|
objB[key],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user