mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-10-06 23:59:56 +02:00
Compare commits
3 Commits
dependabot
...
dwelle/dou
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a22927d4d1 | ||
![]() |
ca9b7a505e | ||
![]() |
36b387f973 |
@@ -3,20 +3,6 @@
|
||||
"rules": {
|
||||
"import/no-anonymous-default-export": "off",
|
||||
"no-restricted-globals": "off",
|
||||
"@typescript-eslint/consistent-type-imports": [
|
||||
"error",
|
||||
{
|
||||
"prefer": "type-imports",
|
||||
"disallowTypeAnnotations": false,
|
||||
"fixStyle": "separate-type-imports"
|
||||
}
|
||||
],
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
"name": "jotai",
|
||||
"message": "Do not import from \"jotai\" directly. Use our app-specific modules (\"editor-jotai\" or \"app-jotai\")."
|
||||
}
|
||||
]
|
||||
"@typescript-eslint/consistent-type-imports": ["error", { "prefer": "type-imports", "disallowTypeAnnotations": false, "fixStyle": "separate-type-imports" }]
|
||||
}
|
||||
}
|
||||
|
@@ -12,7 +12,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@excalidraw/excalidraw": "*",
|
||||
"next": "14.2",
|
||||
"next": "14.1",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0"
|
||||
},
|
||||
|
@@ -90,13 +90,9 @@ import {
|
||||
import { AppMainMenu } from "./components/AppMainMenu";
|
||||
import { AppWelcomeScreen } from "./components/AppWelcomeScreen";
|
||||
import { AppFooter } from "./components/AppFooter";
|
||||
import {
|
||||
Provider,
|
||||
useAtom,
|
||||
useAtomValue,
|
||||
useAtomWithInitialValue,
|
||||
appJotaiStore,
|
||||
} from "./app-jotai";
|
||||
import { Provider, useAtom, useAtomValue } from "jotai";
|
||||
import { useAtomWithInitialValue } from "../packages/excalidraw/jotai";
|
||||
import { appJotaiStore } from "./app-jotai";
|
||||
|
||||
import "./index.scss";
|
||||
import type { ResolutionType } from "../packages/excalidraw/utility-types";
|
||||
@@ -121,7 +117,7 @@ import {
|
||||
share,
|
||||
youtubeIcon,
|
||||
} from "../packages/excalidraw/components/icons";
|
||||
import { useHandleAppTheme } from "./useHandleAppTheme";
|
||||
import { appThemeAtom, useHandleAppTheme } from "./useHandleAppTheme";
|
||||
import { getPreferredLanguage } from "./app-language/language-detector";
|
||||
import { useAppLangCode } from "./app-language/language-state";
|
||||
import DebugCanvas, {
|
||||
@@ -332,7 +328,8 @@ const ExcalidrawWrapper = () => {
|
||||
const [errorMessage, setErrorMessage] = useState("");
|
||||
const isCollabDisabled = isRunningInIframe();
|
||||
|
||||
const { editorTheme, appTheme, setAppTheme } = useHandleAppTheme();
|
||||
const [appTheme, setAppTheme] = useAtom(appThemeAtom);
|
||||
const { editorTheme } = useHandleAppTheme();
|
||||
|
||||
const [langCode, setLangCode] = useAppLangCode();
|
||||
|
||||
@@ -1144,7 +1141,7 @@ const ExcalidrawApp = () => {
|
||||
|
||||
return (
|
||||
<TopErrorBoundary>
|
||||
<Provider store={appJotaiStore}>
|
||||
<Provider unstable_createStore={() => appJotaiStore}>
|
||||
<ExcalidrawWrapper />
|
||||
</Provider>
|
||||
</TopErrorBoundary>
|
||||
|
@@ -1,37 +1,3 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import {
|
||||
atom,
|
||||
Provider,
|
||||
useAtom,
|
||||
useAtomValue,
|
||||
useSetAtom,
|
||||
createStore,
|
||||
type PrimitiveAtom,
|
||||
} from "jotai";
|
||||
import { useLayoutEffect } from "react";
|
||||
import { unstable_createStore } from "jotai";
|
||||
|
||||
export const appJotaiStore = createStore();
|
||||
|
||||
export { atom, Provider, useAtom, useAtomValue, useSetAtom };
|
||||
|
||||
export const useAtomWithInitialValue = <
|
||||
T extends unknown,
|
||||
A extends PrimitiveAtom<T>,
|
||||
>(
|
||||
atom: A,
|
||||
initialValue: T | (() => T),
|
||||
) => {
|
||||
const [value, setValue] = useAtom(atom);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (typeof initialValue === "function") {
|
||||
// @ts-ignore
|
||||
setValue(initialValue());
|
||||
} else {
|
||||
setValue(initialValue);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return [value, setValue] as const;
|
||||
};
|
||||
export const appJotaiStore = unstable_createStore();
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { useSetAtom } from "jotai";
|
||||
import React from "react";
|
||||
import { useI18n, languages } from "../../packages/excalidraw/i18n";
|
||||
import { useSetAtom } from "../app-jotai";
|
||||
import { appLangCodeAtom } from "./language-state";
|
||||
|
||||
export const LanguageList = ({ style }: { style?: React.CSSProperties }) => {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { atom, useAtom } from "jotai";
|
||||
import { useEffect } from "react";
|
||||
import { atom, useAtom } from "../app-jotai";
|
||||
import { getPreferredLanguage, languageDetector } from "./language-detector";
|
||||
|
||||
export const appLangCodeAtom = atom(getPreferredLanguage());
|
||||
|
@@ -79,7 +79,8 @@ import { newElementWith } from "../../packages/excalidraw/element/mutateElement"
|
||||
import { decryptData } from "../../packages/excalidraw/data/encryption";
|
||||
import { resetBrowserStateVersions } from "../data/tabSync";
|
||||
import { LocalData } from "../data/LocalData";
|
||||
import { appJotaiStore, atom } from "../app-jotai";
|
||||
import { atom } from "jotai";
|
||||
import { appJotaiStore } from "../app-jotai";
|
||||
import type { Mutable, ValueOf } from "../../packages/excalidraw/utility-types";
|
||||
import { getVisibleSceneBounds } from "../../packages/excalidraw/element/bounds";
|
||||
import { withBatchedUpdates } from "../../packages/excalidraw/reactUtils";
|
||||
|
@@ -2,9 +2,9 @@ import { Tooltip } from "../../packages/excalidraw/components/Tooltip";
|
||||
import { warning } from "../../packages/excalidraw/components/icons";
|
||||
import clsx from "clsx";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { atom } from "../app-jotai";
|
||||
|
||||
import "./CollabError.scss";
|
||||
import { atom } from "jotai";
|
||||
|
||||
type ErrorIndicator = {
|
||||
message: string | null;
|
||||
|
@@ -32,7 +32,7 @@
|
||||
"firebase": "8.3.3",
|
||||
"i18next-browser-languagedetector": "6.1.4",
|
||||
"idb-keyval": "6.0.3",
|
||||
"jotai": "2.11.0",
|
||||
"jotai": "1.13.1",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"socket.io-client": "4.7.2",
|
||||
|
@@ -18,11 +18,11 @@ import { TextField } from "../../packages/excalidraw/components/TextField";
|
||||
import { FilledButton } from "../../packages/excalidraw/components/FilledButton";
|
||||
import type { CollabAPI } from "../collab/Collab";
|
||||
import { activeRoomLinkAtom } from "../collab/Collab";
|
||||
import { useUIAppState } from "../../packages/excalidraw/context/ui-appState";
|
||||
import { useCopyStatus } from "../../packages/excalidraw/hooks/useCopiedIndicator";
|
||||
import { atom, useAtom, useAtomValue } from "../app-jotai";
|
||||
import { atom, useAtom, useAtomValue } from "jotai";
|
||||
|
||||
import "./ShareDialog.scss";
|
||||
import { useUIAppState } from "../../packages/excalidraw/context/ui-appState";
|
||||
import { useCopyStatus } from "../../packages/excalidraw/hooks/useCopiedIndicator";
|
||||
|
||||
type OnExportToBackend = () => void;
|
||||
type ShareDialogType = "share" | "collaborationOnly";
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import { atom, useAtom } from "jotai";
|
||||
import { useEffect, useLayoutEffect, useState } from "react";
|
||||
import { THEME } from "../packages/excalidraw";
|
||||
import { EVENT } from "../packages/excalidraw/constants";
|
||||
@@ -5,18 +6,18 @@ import type { Theme } from "../packages/excalidraw/element/types";
|
||||
import { CODES, KEYS } from "../packages/excalidraw/keys";
|
||||
import { STORAGE_KEYS } from "./app_constants";
|
||||
|
||||
export const appThemeAtom = atom<Theme | "system">(
|
||||
(localStorage.getItem(STORAGE_KEYS.LOCAL_STORAGE_THEME) as
|
||||
| Theme
|
||||
| "system"
|
||||
| null) || THEME.LIGHT,
|
||||
);
|
||||
|
||||
const getDarkThemeMediaQuery = (): MediaQueryList | undefined =>
|
||||
window.matchMedia?.("(prefers-color-scheme: dark)");
|
||||
|
||||
export const useHandleAppTheme = () => {
|
||||
const [appTheme, setAppTheme] = useState<Theme | "system">(() => {
|
||||
return (
|
||||
(localStorage.getItem(STORAGE_KEYS.LOCAL_STORAGE_THEME) as
|
||||
| Theme
|
||||
| "system"
|
||||
| null) || THEME.LIGHT
|
||||
);
|
||||
});
|
||||
const [appTheme, setAppTheme] = useAtom(appThemeAtom);
|
||||
const [editorTheme, setEditorTheme] = useState<Theme>(THEME.LIGHT);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -65,5 +66,5 @@ export const useHandleAppTheme = () => {
|
||||
}
|
||||
}, [appTheme]);
|
||||
|
||||
return { editorTheme, appTheme, setAppTheme };
|
||||
return { editorTheme };
|
||||
};
|
||||
|
@@ -7,7 +7,7 @@ import { getNonDeletedElements } from "../element";
|
||||
import type { ExcalidrawElement } from "../element/types";
|
||||
import type { AppClassProperties, AppState } from "../types";
|
||||
import { mutateElement, newElementWith } from "../element/mutateElement";
|
||||
import { getElementsInGroup, selectGroupsForSelectedElements } from "../groups";
|
||||
import { getElementsInGroup } from "../groups";
|
||||
import { LinearElementEditor } from "../element/linearElementEditor";
|
||||
import { fixBindingsAfterDeletion } from "../element/binding";
|
||||
import {
|
||||
@@ -33,99 +33,48 @@ const deleteSelectedElements = (
|
||||
).map((el) => el.id),
|
||||
);
|
||||
|
||||
const selectedElementIds: Record<ExcalidrawElement["id"], true> = {};
|
||||
|
||||
let shouldSelectEditingGroup = true;
|
||||
|
||||
const nextElements = elements.map((el) => {
|
||||
if (appState.selectedElementIds[el.id]) {
|
||||
if (el.boundElements) {
|
||||
el.boundElements.forEach((candidate) => {
|
||||
const bound = app.scene.getNonDeletedElementsMap().get(candidate.id);
|
||||
if (bound && isElbowArrow(bound)) {
|
||||
mutateElement(bound, {
|
||||
startBinding:
|
||||
el.id === bound.startBinding?.elementId
|
||||
? null
|
||||
: bound.startBinding,
|
||||
endBinding:
|
||||
el.id === bound.endBinding?.elementId ? null : bound.endBinding,
|
||||
});
|
||||
mutateElbowArrow(bound, elementsMap, bound.points);
|
||||
}
|
||||
});
|
||||
}
|
||||
return newElementWith(el, { isDeleted: true });
|
||||
}
|
||||
|
||||
// if deleting a frame, remove the children from it and select them
|
||||
if (el.frameId && framesToBeDeleted.has(el.frameId)) {
|
||||
shouldSelectEditingGroup = false;
|
||||
selectedElementIds[el.id] = true;
|
||||
return newElementWith(el, { frameId: null });
|
||||
}
|
||||
|
||||
if (isBoundToContainer(el) && appState.selectedElementIds[el.containerId]) {
|
||||
return newElementWith(el, { isDeleted: true });
|
||||
}
|
||||
return el;
|
||||
});
|
||||
|
||||
let nextEditingGroupId = appState.editingGroupId;
|
||||
|
||||
// select next eligible element in currently editing group or supergroup
|
||||
if (shouldSelectEditingGroup && appState.editingGroupId) {
|
||||
const elems = getElementsInGroup(
|
||||
nextElements,
|
||||
appState.editingGroupId,
|
||||
).filter((el) => !el.isDeleted);
|
||||
if (elems.length > 1) {
|
||||
if (elems[0]) {
|
||||
selectedElementIds[elems[0].id] = true;
|
||||
}
|
||||
} else {
|
||||
nextEditingGroupId = null;
|
||||
if (elems[0]) {
|
||||
selectedElementIds[elems[0].id] = true;
|
||||
}
|
||||
|
||||
const lastElementInGroup = elems[0];
|
||||
if (lastElementInGroup) {
|
||||
const editingGroupIdx = lastElementInGroup.groupIds.findIndex(
|
||||
(groupId) => {
|
||||
return groupId === appState.editingGroupId;
|
||||
},
|
||||
);
|
||||
const superGroupId = lastElementInGroup.groupIds[editingGroupIdx + 1];
|
||||
if (superGroupId) {
|
||||
const elems = getElementsInGroup(nextElements, superGroupId).filter(
|
||||
(el) => !el.isDeleted,
|
||||
);
|
||||
if (elems.length > 1) {
|
||||
nextEditingGroupId = superGroupId;
|
||||
|
||||
elems.forEach((el) => {
|
||||
selectedElementIds[el.id] = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
elements: nextElements,
|
||||
elements: elements.map((el) => {
|
||||
if (appState.selectedElementIds[el.id]) {
|
||||
if (el.boundElements) {
|
||||
el.boundElements.forEach((candidate) => {
|
||||
const bound = app.scene
|
||||
.getNonDeletedElementsMap()
|
||||
.get(candidate.id);
|
||||
if (bound && isElbowArrow(bound)) {
|
||||
mutateElement(bound, {
|
||||
startBinding:
|
||||
el.id === bound.startBinding?.elementId
|
||||
? null
|
||||
: bound.startBinding,
|
||||
endBinding:
|
||||
el.id === bound.endBinding?.elementId
|
||||
? null
|
||||
: bound.endBinding,
|
||||
});
|
||||
mutateElbowArrow(bound, elementsMap, bound.points);
|
||||
}
|
||||
});
|
||||
}
|
||||
return newElementWith(el, { isDeleted: true });
|
||||
}
|
||||
|
||||
if (el.frameId && framesToBeDeleted.has(el.frameId)) {
|
||||
return newElementWith(el, { isDeleted: true });
|
||||
}
|
||||
|
||||
if (
|
||||
isBoundToContainer(el) &&
|
||||
appState.selectedElementIds[el.containerId]
|
||||
) {
|
||||
return newElementWith(el, { isDeleted: true });
|
||||
}
|
||||
return el;
|
||||
}),
|
||||
appState: {
|
||||
...appState,
|
||||
...selectGroupsForSelectedElements(
|
||||
{
|
||||
selectedElementIds,
|
||||
editingGroupId: nextEditingGroupId,
|
||||
},
|
||||
nextElements,
|
||||
appState,
|
||||
null,
|
||||
),
|
||||
selectedElementIds: {},
|
||||
selectedGroupIds: {},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { getCommonBounds, getNonDeletedElements } from "../element";
|
||||
import { getNonDeletedElements } from "../element";
|
||||
import type { ExcalidrawElement } from "../element/types";
|
||||
import { addElementsToFrame, removeAllElementsFromFrame } from "../frame";
|
||||
import { removeAllElementsFromFrame } from "../frame";
|
||||
import { getFrameChildren } from "../frame";
|
||||
import { KEYS } from "../keys";
|
||||
import type { AppClassProperties, AppState, UIAppState } from "../types";
|
||||
@@ -10,8 +10,6 @@ import { register } from "./register";
|
||||
import { isFrameLikeElement } from "../element/typeChecks";
|
||||
import { frameToolIcon } from "../components/icons";
|
||||
import { StoreAction } from "../store";
|
||||
import { getSelectedElements } from "../scene";
|
||||
import { newFrameElement } from "../element/newElement";
|
||||
|
||||
const isSingleFrameSelected = (
|
||||
appState: UIAppState,
|
||||
@@ -146,46 +144,3 @@ export const actionSetFrameAsActiveTool = register({
|
||||
!event.altKey &&
|
||||
event.key.toLocaleLowerCase() === KEYS.F,
|
||||
});
|
||||
|
||||
export const actionWrapSelectionInFrame = register({
|
||||
name: "wrapSelectionInFrame",
|
||||
label: "labels.wrapSelectionInFrame",
|
||||
trackEvent: { category: "element" },
|
||||
predicate: (elements, appState, _, app) => {
|
||||
const selectedElements = getSelectedElements(elements, appState);
|
||||
|
||||
return (
|
||||
selectedElements.length > 0 &&
|
||||
!selectedElements.some((element) => isFrameLikeElement(element))
|
||||
);
|
||||
},
|
||||
perform: (elements, appState, _, app) => {
|
||||
const selectedElements = getSelectedElements(elements, appState);
|
||||
|
||||
const [x1, y1, x2, y2] = getCommonBounds(
|
||||
selectedElements,
|
||||
app.scene.getNonDeletedElementsMap(),
|
||||
);
|
||||
const PADDING = 16;
|
||||
const frame = newFrameElement({
|
||||
x: x1 - PADDING,
|
||||
y: y1 - PADDING,
|
||||
width: x2 - x1 + PADDING * 2,
|
||||
height: y2 - y1 + PADDING * 2,
|
||||
});
|
||||
|
||||
const nextElements = addElementsToFrame(
|
||||
[...app.scene.getElementsIncludingDeleted(), frame],
|
||||
selectedElements,
|
||||
frame,
|
||||
);
|
||||
|
||||
return {
|
||||
elements: nextElements,
|
||||
appState: {
|
||||
selectedElementIds: { [frame.id]: true },
|
||||
},
|
||||
storeAction: StoreAction.CAPTURE,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
@@ -47,7 +47,6 @@ export type ShortcutName =
|
||||
| "saveFileToDisk"
|
||||
| "saveToActiveFile"
|
||||
| "toggleShortcuts"
|
||||
| "wrapSelectionInFrame"
|
||||
>
|
||||
| "saveScene"
|
||||
| "imageExport"
|
||||
@@ -113,7 +112,6 @@ const shortcutMap: Record<ShortcutName, string[]> = {
|
||||
saveToActiveFile: [getShortcutKey("CtrlOrCmd+S")],
|
||||
toggleShortcuts: [getShortcutKey("?")],
|
||||
searchMenu: [getShortcutKey("CtrlOrCmd+F")],
|
||||
wrapSelectionInFrame: [],
|
||||
};
|
||||
|
||||
export const getShortcutFromShortcutName = (name: ShortcutName, idx = 0) => {
|
||||
|
@@ -137,8 +137,7 @@ export type ActionName =
|
||||
| "searchMenu"
|
||||
| "copyElementLink"
|
||||
| "linkToElement"
|
||||
| "cropEditor"
|
||||
| "wrapSelectionInFrame";
|
||||
| "cropEditor";
|
||||
|
||||
export type PanelComponentProps = {
|
||||
elements: readonly ExcalidrawElement[];
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { atom, useAtom } from "jotai";
|
||||
import { actionClearCanvas } from "../actions";
|
||||
import { t } from "../i18n";
|
||||
import { atom, useAtom } from "../editor-jotai";
|
||||
import { jotaiScope } from "../jotai";
|
||||
import { useExcalidrawActionManager } from "./App";
|
||||
import ConfirmDialog from "./ConfirmDialog";
|
||||
|
||||
@@ -9,6 +10,7 @@ export const activeConfirmDialogAtom = atom<"clearCanvas" | null>(null);
|
||||
export const ActiveConfirmDialog = () => {
|
||||
const [activeConfirmDialog, setActiveConfirmDialog] = useAtom(
|
||||
activeConfirmDialogAtom,
|
||||
jotaiScope,
|
||||
);
|
||||
const actionManager = useExcalidrawActionManager();
|
||||
|
||||
|
@@ -91,6 +91,7 @@ import {
|
||||
DEFAULT_REDUCED_GLOBAL_ALPHA,
|
||||
isSafari,
|
||||
type EXPORT_IMAGE_TYPES,
|
||||
DOUBLE_CLICK_POINTERUP_TIMEOUT,
|
||||
} from "../constants";
|
||||
import type { ExportedElements } from "../data";
|
||||
import { exportCanvas, loadFromBlob } from "../data";
|
||||
@@ -378,10 +379,9 @@ import { actionPaste } from "../actions/actionClipboard";
|
||||
import {
|
||||
actionRemoveAllElementsFromFrame,
|
||||
actionSelectAllElementsInFrame,
|
||||
actionWrapSelectionInFrame,
|
||||
} from "../actions/actionFrame";
|
||||
import { actionToggleHandTool, zoomToFit } from "../actions/actionCanvas";
|
||||
import { editorJotaiStore } from "../editor-jotai";
|
||||
import { jotaiStore } from "../jotai";
|
||||
import { activeConfirmDialogAtom } from "./ActiveConfirmDialog";
|
||||
import { ImageSceneDataError } from "../errors";
|
||||
import {
|
||||
@@ -2077,7 +2077,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
};
|
||||
|
||||
private openEyeDropper = ({ type }: { type: "stroke" | "background" }) => {
|
||||
editorJotaiStore.set(activeEyeDropperAtom, {
|
||||
jotaiStore.set(activeEyeDropperAtom, {
|
||||
swapPreviewOnAlt: true,
|
||||
colorPickerType:
|
||||
type === "stroke" ? "elementStroke" : "elementBackground",
|
||||
@@ -3325,7 +3325,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
openSidebar:
|
||||
this.state.openSidebar &&
|
||||
this.device.editor.canFitSidebar &&
|
||||
editorJotaiStore.get(isSidebarDockedAtom)
|
||||
jotaiStore.get(isSidebarDockedAtom)
|
||||
? this.state.openSidebar
|
||||
: null,
|
||||
...selectGroupsForSelectedElements(
|
||||
@@ -4553,7 +4553,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
event[KEYS.CTRL_OR_CMD] &&
|
||||
(event.key === KEYS.BACKSPACE || event.key === KEYS.DELETE)
|
||||
) {
|
||||
editorJotaiStore.set(activeConfirmDialogAtom, "clearCanvas");
|
||||
jotaiStore.set(activeConfirmDialogAtom, "clearCanvas");
|
||||
}
|
||||
|
||||
// eye dropper
|
||||
@@ -4696,10 +4696,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
if (nextActiveTool.type === "hand") {
|
||||
setCursor(this.interactiveCanvas, CURSOR_TYPE.GRAB);
|
||||
} else if (!isHoldingSpace) {
|
||||
setCursorForShape(this.interactiveCanvas, {
|
||||
...this.state,
|
||||
activeTool: nextActiveTool,
|
||||
});
|
||||
setCursorForShape(this.interactiveCanvas, this.state);
|
||||
}
|
||||
if (isToolIcon(document.activeElement)) {
|
||||
this.focusContainer();
|
||||
@@ -5353,6 +5350,14 @@ class App extends React.Component<AppProps, AppState> {
|
||||
private handleCanvasDoubleClick = (
|
||||
event: React.MouseEvent<HTMLCanvasElement>,
|
||||
) => {
|
||||
if (
|
||||
this.lastPointerDownEvent &&
|
||||
event.timeStamp - this.lastPointerDownEvent.timeStamp >
|
||||
DOUBLE_CLICK_POINTERUP_TIMEOUT
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// case: double-clicking with arrow/line tool selected would both create
|
||||
// text and enter multiElement mode
|
||||
if (this.state.multiElement) {
|
||||
@@ -6283,6 +6288,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
event: React.PointerEvent<HTMLElement>,
|
||||
) => {
|
||||
this.maybeCleanupAfterMissingPointerUp(event.nativeEvent);
|
||||
|
||||
this.maybeUnfollowRemoteUser();
|
||||
|
||||
if (this.state.searchMatches) {
|
||||
@@ -6292,7 +6298,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
focus: false,
|
||||
})),
|
||||
}));
|
||||
editorJotaiStore.set(searchItemInFocusAtom, null);
|
||||
jotaiStore.set(searchItemInFocusAtom, null);
|
||||
}
|
||||
|
||||
// since contextMenu options are potentially evaluated on each render,
|
||||
@@ -10665,10 +10671,8 @@ class App extends React.Component<AppProps, AppState> {
|
||||
actionCut,
|
||||
actionCopy,
|
||||
actionPaste,
|
||||
CONTEXT_MENU_SEPARATOR,
|
||||
actionSelectAllElementsInFrame,
|
||||
actionRemoveAllElementsFromFrame,
|
||||
actionWrapSelectionInFrame,
|
||||
CONTEXT_MENU_SEPARATOR,
|
||||
actionToggleCropEditor,
|
||||
CONTEXT_MENU_SEPARATOR,
|
||||
|
@@ -1,9 +1,10 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { getColor } from "./ColorPicker";
|
||||
import { useAtom } from "jotai";
|
||||
import type { ColorPickerType } from "./colorPickerUtils";
|
||||
import { activeColorPickerSectionAtom } from "./colorPickerUtils";
|
||||
import { eyeDropperIcon } from "../icons";
|
||||
import { useAtom } from "../../editor-jotai";
|
||||
import { jotaiScope } from "../../jotai";
|
||||
import { KEYS } from "../../keys";
|
||||
import { activeEyeDropperAtom } from "../EyeDropper";
|
||||
import clsx from "clsx";
|
||||
@@ -56,7 +57,10 @@ export const ColorInput = ({
|
||||
}
|
||||
}, [activeSection]);
|
||||
|
||||
const [eyeDropperState, setEyeDropperState] = useAtom(activeEyeDropperAtom);
|
||||
const [eyeDropperState, setEyeDropperState] = useAtom(
|
||||
activeEyeDropperAtom,
|
||||
jotaiScope,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
|
@@ -5,6 +5,7 @@ import { TopPicks } from "./TopPicks";
|
||||
import { ButtonSeparator } from "../ButtonSeparator";
|
||||
import { Picker } from "./Picker";
|
||||
import * as Popover from "@radix-ui/react-popover";
|
||||
import { useAtom } from "jotai";
|
||||
import type { ColorPickerType } from "./colorPickerUtils";
|
||||
import { activeColorPickerSectionAtom } from "./colorPickerUtils";
|
||||
import { useExcalidrawContainer } from "../App";
|
||||
@@ -14,7 +15,7 @@ import PickerHeading from "./PickerHeading";
|
||||
import { t } from "../../i18n";
|
||||
import clsx from "clsx";
|
||||
import { useRef } from "react";
|
||||
import { useAtom } from "../../editor-jotai";
|
||||
import { jotaiScope } from "../../jotai";
|
||||
import { ColorInput } from "./ColorInput";
|
||||
import { activeEyeDropperAtom } from "../EyeDropper";
|
||||
import { PropertiesPopover } from "../PropertiesPopover";
|
||||
@@ -75,7 +76,10 @@ const ColorPickerPopupContent = ({
|
||||
const { container } = useExcalidrawContainer();
|
||||
const [, setActiveColorPickerSection] = useAtom(activeColorPickerSectionAtom);
|
||||
|
||||
const [eyeDropperState, setEyeDropperState] = useAtom(activeEyeDropperAtom);
|
||||
const [eyeDropperState, setEyeDropperState] = useAtom(
|
||||
activeEyeDropperAtom,
|
||||
jotaiScope,
|
||||
);
|
||||
|
||||
const colorInputJSX = (
|
||||
<div>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import clsx from "clsx";
|
||||
import { useAtom } from "../../editor-jotai";
|
||||
import { useAtom } from "jotai";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { activeColorPickerSectionAtom } from "./colorPickerUtils";
|
||||
import HotkeyLabel from "./HotkeyLabel";
|
||||
|
@@ -5,7 +5,7 @@ import type { ExcalidrawElement } from "../../element/types";
|
||||
import { ShadeList } from "./ShadeList";
|
||||
|
||||
import PickerColorList from "./PickerColorList";
|
||||
import { useAtom } from "../../editor-jotai";
|
||||
import { useAtom } from "jotai";
|
||||
import { CustomColorList } from "./CustomColorList";
|
||||
import { colorPickerKeyNavHandler } from "./keyboardNavHandlers";
|
||||
import PickerHeading from "./PickerHeading";
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import clsx from "clsx";
|
||||
import { useAtom } from "../../editor-jotai";
|
||||
import { useAtom } from "jotai";
|
||||
import { useEffect, useRef } from "react";
|
||||
import {
|
||||
activeColorPickerSectionAtom,
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import clsx from "clsx";
|
||||
import { useAtom } from "../../editor-jotai";
|
||||
import { useAtom } from "jotai";
|
||||
import { useEffect, useRef } from "react";
|
||||
import {
|
||||
activeColorPickerSectionAtom,
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import type { ExcalidrawElement } from "../../element/types";
|
||||
import { atom } from "jotai";
|
||||
import type { ColorPickerColor, ColorPaletteCustom } from "../../colors";
|
||||
import { MAX_CUSTOM_COLORS_USED_IN_CANVAS } from "../../colors";
|
||||
import { atom } from "../../editor-jotai";
|
||||
|
||||
export const getColorNameAndShadeFromColor = ({
|
||||
palette,
|
||||
|
@@ -36,7 +36,7 @@ import {
|
||||
getShortcutKey,
|
||||
isWritableElement,
|
||||
} from "../../utils";
|
||||
import { atom, useAtom, editorJotaiStore } from "../../editor-jotai";
|
||||
import { atom, useAtom } from "jotai";
|
||||
import { deburr } from "../../deburr";
|
||||
import type { MarkRequired } from "../../utility-types";
|
||||
import { InlineIcon } from "../InlineIcon";
|
||||
@@ -48,6 +48,7 @@ import {
|
||||
actionLink,
|
||||
actionToggleSearchMenu,
|
||||
} from "../../actions";
|
||||
import { jotaiStore } from "../../jotai";
|
||||
import { activeConfirmDialogAtom } from "../ActiveConfirmDialog";
|
||||
import type { CommandPaletteItem } from "./types";
|
||||
import * as defaultItems from "./defaultCommandPaletteItems";
|
||||
@@ -262,7 +263,6 @@ function CommandPaletteInner({
|
||||
actionManager.actions.cut,
|
||||
actionManager.actions.copy,
|
||||
actionManager.actions.deleteSelectedElements,
|
||||
actionManager.actions.wrapSelectionInFrame,
|
||||
actionManager.actions.copyStyles,
|
||||
actionManager.actions.pasteStyles,
|
||||
actionManager.actions.bringToFront,
|
||||
@@ -348,7 +348,7 @@ function CommandPaletteInner({
|
||||
keywords: ["delete", "destroy"],
|
||||
viewMode: false,
|
||||
perform: () => {
|
||||
editorJotaiStore.set(activeConfirmDialogAtom, "clearCanvas");
|
||||
jotaiStore.set(activeConfirmDialogAtom, "clearCanvas");
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@@ -5,9 +5,10 @@ import { Dialog } from "./Dialog";
|
||||
|
||||
import "./ConfirmDialog.scss";
|
||||
import DialogActionButton from "./DialogActionButton";
|
||||
import { useSetAtom } from "jotai";
|
||||
import { isLibraryMenuOpenAtom } from "./LibraryMenu";
|
||||
import { useExcalidrawContainer, useExcalidrawSetAppState } from "./App";
|
||||
import { useSetAtom } from "../editor-jotai";
|
||||
import { jotaiScope } from "../jotai";
|
||||
|
||||
interface Props extends Omit<DialogProps, "onCloseRequest"> {
|
||||
onConfirm: () => void;
|
||||
@@ -26,7 +27,7 @@ const ConfirmDialog = (props: Props) => {
|
||||
...rest
|
||||
} = props;
|
||||
const setAppState = useExcalidrawSetAppState();
|
||||
const setIsLibraryMenuOpen = useSetAtom(isLibraryMenuOpenAtom);
|
||||
const setIsLibraryMenuOpen = useSetAtom(isLibraryMenuOpenAtom, jotaiScope);
|
||||
const { container } = useExcalidrawContainer();
|
||||
|
||||
return (
|
||||
|
@@ -11,8 +11,9 @@ import "./Dialog.scss";
|
||||
import { Island } from "./Island";
|
||||
import { Modal } from "./Modal";
|
||||
import { queryFocusableElements } from "../utils";
|
||||
import { useSetAtom } from "jotai";
|
||||
import { isLibraryMenuOpenAtom } from "./LibraryMenu";
|
||||
import { useSetAtom } from "../editor-jotai";
|
||||
import { jotaiScope } from "../jotai";
|
||||
import { t } from "../i18n";
|
||||
import { CloseIcon } from "./icons";
|
||||
|
||||
@@ -91,7 +92,7 @@ export const Dialog = (props: DialogProps) => {
|
||||
}, [islandNode, props.autofocus]);
|
||||
|
||||
const setAppState = useExcalidrawSetAppState();
|
||||
const setIsLibraryMenuOpen = useSetAtom(isLibraryMenuOpenAtom);
|
||||
const setIsLibraryMenuOpen = useSetAtom(isLibraryMenuOpenAtom, jotaiScope);
|
||||
|
||||
const onClose = () => {
|
||||
setAppState({ openMenu: null });
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import { atom } from "jotai";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { rgbToHex } from "../colors";
|
||||
@@ -13,7 +14,6 @@ import { useStable } from "../hooks/useStable";
|
||||
import "./EyeDropper.scss";
|
||||
import type { ColorPickerType } from "./ColorPicker/colorPickerUtils";
|
||||
import type { ExcalidrawElement } from "../element/types";
|
||||
import { atom } from "../editor-jotai";
|
||||
|
||||
export type EyeDropperProperties = {
|
||||
keepOpenOnAlt: boolean;
|
||||
|
@@ -1,14 +1,15 @@
|
||||
import React, { useEffect } from "react";
|
||||
import * as Popover from "@radix-ui/react-popover";
|
||||
|
||||
import "./IconPicker.scss";
|
||||
import { isArrowKey, KEYS } from "../keys";
|
||||
import { getLanguage, t } from "../i18n";
|
||||
import clsx from "clsx";
|
||||
import Collapsible from "./Stats/Collapsible";
|
||||
import { atom, useAtom } from "../editor-jotai";
|
||||
import { atom, useAtom } from "jotai";
|
||||
import { jotaiScope } from "../jotai";
|
||||
import { useDevice } from "..";
|
||||
|
||||
import "./IconPicker.scss";
|
||||
|
||||
const moreOptionsAtom = atom(false);
|
||||
|
||||
type Option<T> = {
|
||||
@@ -93,7 +94,10 @@ function Picker<T>({
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
const [showMoreOptions, setShowMoreOptions] = useAtom(moreOptionsAtom);
|
||||
const [showMoreOptions, setShowMoreOptions] = useAtom(
|
||||
moreOptionsAtom,
|
||||
jotaiScope,
|
||||
);
|
||||
|
||||
const alwaysVisibleOptions = React.useMemo(
|
||||
() => options.slice(0, numberOfOptionsToAlwaysShow),
|
||||
|
@@ -41,7 +41,8 @@ import { trackEvent } from "../analytics";
|
||||
import { useDevice } from "./App";
|
||||
import Footer from "./footer/Footer";
|
||||
import { isSidebarDockedAtom } from "./Sidebar/Sidebar";
|
||||
import { useAtom, useAtomValue } from "../editor-jotai";
|
||||
import { jotaiScope } from "../jotai";
|
||||
import { Provider, useAtom, useAtomValue } from "jotai";
|
||||
import MainMenu from "./main-menu/MainMenu";
|
||||
import { ActiveConfirmDialog } from "./ActiveConfirmDialog";
|
||||
import { OverwriteConfirmDialog } from "./OverwriteConfirm/OverwriteConfirm";
|
||||
@@ -147,9 +148,10 @@ const LayerUI = ({
|
||||
const device = useDevice();
|
||||
const tunnels = useInitializeTunnels();
|
||||
|
||||
const TunnelsJotaiProvider = tunnels.tunnelsJotai.Provider;
|
||||
|
||||
const [eyeDropperState, setEyeDropperState] = useAtom(activeEyeDropperAtom);
|
||||
const [eyeDropperState, setEyeDropperState] = useAtom(
|
||||
activeEyeDropperAtom,
|
||||
jotaiScope,
|
||||
);
|
||||
|
||||
const renderJSONExportDialog = () => {
|
||||
if (!UIOptions.canvasActions.export) {
|
||||
@@ -380,7 +382,7 @@ const LayerUI = ({
|
||||
);
|
||||
};
|
||||
|
||||
const isSidebarDocked = useAtomValue(isSidebarDockedAtom);
|
||||
const isSidebarDocked = useAtomValue(isSidebarDockedAtom, jotaiScope);
|
||||
|
||||
const layerUIJSX = (
|
||||
<>
|
||||
@@ -564,11 +566,11 @@ const LayerUI = ({
|
||||
|
||||
return (
|
||||
<UIAppStateContext.Provider value={appState}>
|
||||
<TunnelsJotaiProvider>
|
||||
<Provider scope={tunnels.jotaiScope}>
|
||||
<TunnelsContext.Provider value={tunnels}>
|
||||
{layerUIJSX}
|
||||
</TunnelsContext.Provider>
|
||||
</TunnelsJotaiProvider>
|
||||
</Provider>
|
||||
</UIAppStateContext.Provider>
|
||||
);
|
||||
};
|
||||
|
@@ -14,7 +14,8 @@ import type {
|
||||
} from "../types";
|
||||
import LibraryMenuItems from "./LibraryMenuItems";
|
||||
import { trackEvent } from "../analytics";
|
||||
import { atom, useAtom } from "../editor-jotai";
|
||||
import { atom, useAtom } from "jotai";
|
||||
import { jotaiScope } from "../jotai";
|
||||
import Spinner from "./Spinner";
|
||||
import {
|
||||
useApp,
|
||||
@@ -60,7 +61,7 @@ export const LibraryMenuContent = ({
|
||||
selectedItems: LibraryItem["id"][];
|
||||
onSelectItems: (id: LibraryItem["id"][]) => void;
|
||||
}) => {
|
||||
const [libraryItemsData] = useAtom(libraryItemsAtom);
|
||||
const [libraryItemsData] = useAtom(libraryItemsAtom, jotaiScope);
|
||||
|
||||
const _onAddToLibrary = useCallback(
|
||||
(elements: LibraryItem["elements"]) => {
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { useCallback, useState } from "react";
|
||||
import { t } from "../i18n";
|
||||
import Trans from "./Trans";
|
||||
import { useAtom } from "../editor-jotai";
|
||||
import { jotaiScope } from "../jotai";
|
||||
import type { LibraryItem, LibraryItems, UIAppState } from "../types";
|
||||
import { useApp, useExcalidrawSetAppState } from "./App";
|
||||
import { saveLibraryAsJSON } from "../data/json";
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
import { ToolButton } from "./ToolButton";
|
||||
import { fileOpen } from "../data/filesystem";
|
||||
import { muteFSAbortError } from "../utils";
|
||||
import { useAtom } from "jotai";
|
||||
import ConfirmDialog from "./ConfirmDialog";
|
||||
import PublishLibrary from "./PublishLibrary";
|
||||
import { Dialog } from "./Dialog";
|
||||
@@ -50,9 +51,10 @@ export const LibraryDropdownMenuButton: React.FC<{
|
||||
appState,
|
||||
className,
|
||||
}) => {
|
||||
const [libraryItemsData] = useAtom(libraryItemsAtom);
|
||||
const [libraryItemsData] = useAtom(libraryItemsAtom, jotaiScope);
|
||||
const [isLibraryMenuOpen, setIsLibraryMenuOpen] = useAtom(
|
||||
isLibraryMenuOpenAtom,
|
||||
jotaiScope,
|
||||
);
|
||||
|
||||
const renderRemoveLibAlert = () => {
|
||||
@@ -284,7 +286,7 @@ export const LibraryDropdownMenu = ({
|
||||
const appState = useUIAppState();
|
||||
const setAppState = useExcalidrawSetAppState();
|
||||
|
||||
const [libraryItemsData] = useAtom(libraryItemsAtom);
|
||||
const [libraryItemsData] = useAtom(libraryItemsAtom, jotaiScope);
|
||||
|
||||
const removeFromLibrary = async (libraryItems: LibraryItems) => {
|
||||
const nextItems = libraryItems.filter(
|
||||
|
@@ -1,7 +1,8 @@
|
||||
import React from "react";
|
||||
import { useAtom } from "jotai";
|
||||
|
||||
import { useTunnels } from "../../context/tunnels";
|
||||
import { useAtom } from "../../editor-jotai";
|
||||
import { jotaiScope } from "../../jotai";
|
||||
import { Dialog } from "../Dialog";
|
||||
import { withInternalFallback } from "../hoc/withInternalFallback";
|
||||
import { overwriteConfirmStateAtom } from "./OverwriteConfirmState";
|
||||
@@ -22,6 +23,7 @@ const OverwriteConfirmDialog = Object.assign(
|
||||
const { OverwriteConfirmDialogTunnel } = useTunnels();
|
||||
const [overwriteConfirmState, setState] = useAtom(
|
||||
overwriteConfirmStateAtom,
|
||||
jotaiScope,
|
||||
);
|
||||
|
||||
if (!overwriteConfirmState.active) {
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { atom, editorJotaiStore } from "../../editor-jotai";
|
||||
import { atom } from "jotai";
|
||||
import { jotaiStore } from "../../jotai";
|
||||
import type React from "react";
|
||||
|
||||
export type OverwriteConfirmState =
|
||||
@@ -31,7 +32,7 @@ export async function openConfirmModal({
|
||||
color: "danger" | "warning";
|
||||
}) {
|
||||
return new Promise<boolean>((resolve) => {
|
||||
editorJotaiStore.set(overwriteConfirmStateAtom, {
|
||||
jotaiStore.set(overwriteConfirmStateAtom, {
|
||||
active: true,
|
||||
onConfirm: () => resolve(true),
|
||||
onClose: () => resolve(false),
|
||||
|
@@ -11,7 +11,8 @@ import { measureText } from "../element/textElement";
|
||||
import { addEventListener, getFontString } from "../utils";
|
||||
import { KEYS } from "../keys";
|
||||
import clsx from "clsx";
|
||||
import { atom, useAtom } from "../editor-jotai";
|
||||
import { atom, useAtom } from "jotai";
|
||||
import { jotaiScope } from "../jotai";
|
||||
import { t } from "../i18n";
|
||||
import { isElementCompletelyInViewport } from "../element/sizeHelpers";
|
||||
import { randomInteger } from "../random";
|
||||
@@ -57,7 +58,7 @@ export const SearchMenu = () => {
|
||||
|
||||
const searchInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const [inputValue, setInputValue] = useAtom(searchQueryAtom);
|
||||
const [inputValue, setInputValue] = useAtom(searchQueryAtom, jotaiScope);
|
||||
const searchQuery = inputValue.trim() as SearchQuery;
|
||||
|
||||
const [isSearching, setIsSearching] = useState(false);
|
||||
@@ -69,7 +70,10 @@ export const SearchMenu = () => {
|
||||
const searchedQueryRef = useRef<SearchQuery | null>(null);
|
||||
const lastSceneNonceRef = useRef<number | undefined>(undefined);
|
||||
|
||||
const [focusIndex, setFocusIndex] = useAtom(searchItemInFocusAtom);
|
||||
const [focusIndex, setFocusIndex] = useAtom(
|
||||
searchItemInFocusAtom,
|
||||
jotaiScope,
|
||||
);
|
||||
const elementsMap = app.scene.getNonDeletedElementsMap();
|
||||
|
||||
useEffect(() => {
|
||||
|
@@ -8,7 +8,8 @@ import React, {
|
||||
useCallback,
|
||||
} from "react";
|
||||
import { Island } from "../Island";
|
||||
import { atom, useSetAtom } from "../../editor-jotai";
|
||||
import { atom, useSetAtom } from "jotai";
|
||||
import { jotaiScope } from "../../jotai";
|
||||
import type { SidebarProps, SidebarPropsContextValue } from "./common";
|
||||
import { SidebarPropsContext } from "./common";
|
||||
import { SidebarHeader } from "./SidebarHeader";
|
||||
@@ -57,7 +58,7 @@ export const SidebarInner = forwardRef(
|
||||
|
||||
const setAppState = useExcalidrawSetAppState();
|
||||
|
||||
const setIsSidebarDockedAtom = useSetAtom(isSidebarDockedAtom);
|
||||
const setIsSidebarDockedAtom = useSetAtom(isSidebarDockedAtom, jotaiScope);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
setIsSidebarDockedAtom(!!docked);
|
||||
|
@@ -237,7 +237,6 @@ const MultiPosition = ({
|
||||
const [x1, y1] = getCommonBounds(elementsInUnit);
|
||||
return Math.round((property === "x" ? x1 : y1) * 100) / 100;
|
||||
}
|
||||
|
||||
const [el] = elementsInUnit;
|
||||
const [cx, cy] = [el.x + el.width / 2, el.y + el.height / 2];
|
||||
|
||||
|
@@ -25,7 +25,7 @@ import type { BinaryFiles } from "../../types";
|
||||
import { ArrowRightIcon } from "../icons";
|
||||
|
||||
import "./TTDDialog.scss";
|
||||
import { atom, useAtom } from "../../editor-jotai";
|
||||
import { atom, useAtom } from "jotai";
|
||||
import { trackEvent } from "../../analytics";
|
||||
import { InlineIcon } from "../InlineIcon";
|
||||
import { TTDDialogSubmitShortcut } from "./TTDDialogSubmitShortcut";
|
||||
|
@@ -4,7 +4,6 @@ import fallbackLangData from "../locales/en.json";
|
||||
|
||||
import Trans from "./Trans";
|
||||
import type { TranslationKeys } from "../i18n";
|
||||
import { EditorJotaiProvider } from "../editor-jotai";
|
||||
|
||||
describe("Test <Trans/>", () => {
|
||||
it("should translate the the strings correctly", () => {
|
||||
@@ -18,7 +17,7 @@ describe("Test <Trans/>", () => {
|
||||
};
|
||||
|
||||
const { getByTestId } = render(
|
||||
<EditorJotaiProvider>
|
||||
<>
|
||||
<div data-testid="test1">
|
||||
<Trans
|
||||
i18nKey={"transTest.key1" as unknown as TranslationKeys}
|
||||
@@ -52,7 +51,7 @@ describe("Test <Trans/>", () => {
|
||||
connect-link={(el) => <a href="https://example.com">{el}</a>}
|
||||
/>
|
||||
</div>
|
||||
</EditorJotaiProvider>,
|
||||
</>,
|
||||
);
|
||||
|
||||
expect(getByTestId("test1").innerHTML).toEqual("Hello world");
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { atom, useAtom } from "jotai";
|
||||
import React, { useLayoutEffect, useRef } from "react";
|
||||
import { useTunnels } from "../../context/tunnels";
|
||||
import { atom } from "../../editor-jotai";
|
||||
|
||||
export const withInternalFallback = <P,>(
|
||||
componentName: string,
|
||||
@@ -13,11 +13,9 @@ export const withInternalFallback = <P,>(
|
||||
__fallback?: boolean;
|
||||
}
|
||||
> = (props) => {
|
||||
const {
|
||||
tunnelsJotai: { useAtom },
|
||||
} = useTunnels();
|
||||
const { jotaiScope } = useTunnels();
|
||||
// for rerenders
|
||||
const [, setCounter] = useAtom(renderAtom);
|
||||
const [, setCounter] = useAtom(renderAtom, jotaiScope);
|
||||
// for initial & subsequent renders. Tracked as component state
|
||||
// due to excalidraw multi-instance scanerios.
|
||||
const metaRef = useRef({
|
||||
|
@@ -32,8 +32,9 @@ import {
|
||||
actionToggleTheme,
|
||||
} from "../../actions";
|
||||
import clsx from "clsx";
|
||||
import { useSetAtom } from "jotai";
|
||||
import { activeConfirmDialogAtom } from "../ActiveConfirmDialog";
|
||||
import { useSetAtom } from "../../editor-jotai";
|
||||
import { jotaiScope } from "../../jotai";
|
||||
import { useUIAppState } from "../../context/ui-appState";
|
||||
import { openConfirmModal } from "../OverwriteConfirm/OverwriteConfirmState";
|
||||
import Trans from "../Trans";
|
||||
@@ -188,7 +189,10 @@ Help.displayName = "Help";
|
||||
export const ClearCanvas = () => {
|
||||
const { t } = useI18n();
|
||||
|
||||
const setActiveConfirmDialog = useSetAtom(activeConfirmDialogAtom);
|
||||
const setActiveConfirmDialog = useSetAtom(
|
||||
activeConfirmDialogAtom,
|
||||
jotaiScope,
|
||||
);
|
||||
const actionManager = useExcalidrawActionManager();
|
||||
|
||||
if (!actionManager.isActionEnabled(actionClearCanvas)) {
|
||||
|
@@ -255,6 +255,14 @@ export const EXPORT_SOURCE =
|
||||
// time in milliseconds
|
||||
export const IMAGE_RENDER_TIMEOUT = 500;
|
||||
export const TAP_TWICE_TIMEOUT = 300;
|
||||
/**
|
||||
* The time the user has from 2nd pointerdown to following pointerup
|
||||
* before it's not considered a double click.
|
||||
*
|
||||
* Helps prevent cases where you double-click by mistake but then drag/keep
|
||||
* the pointer down for to cancel the double click or do another action.
|
||||
*/
|
||||
export const DOUBLE_CLICK_POINTERUP_TIMEOUT = 300;
|
||||
export const TOUCH_CTX_MENU_TIMEOUT = 500;
|
||||
export const TITLE_TIMEOUT = 10000;
|
||||
export const VERSION_TIMEOUT = 30000;
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import React from "react";
|
||||
import tunnel from "tunnel-rat";
|
||||
import { createIsolation } from "jotai-scope";
|
||||
|
||||
export type Tunnel = ReturnType<typeof tunnel>;
|
||||
|
||||
@@ -15,17 +14,13 @@ type TunnelsContextValue = {
|
||||
DefaultSidebarTabTriggersTunnel: Tunnel;
|
||||
OverwriteConfirmDialogTunnel: Tunnel;
|
||||
TTDDialogTriggerTunnel: Tunnel;
|
||||
// this can be removed once we create jotai stores per each editor
|
||||
// instance
|
||||
tunnelsJotai: ReturnType<typeof createIsolation>;
|
||||
jotaiScope: symbol;
|
||||
};
|
||||
|
||||
export const TunnelsContext = React.createContext<TunnelsContextValue>(null!);
|
||||
|
||||
export const useTunnels = () => React.useContext(TunnelsContext);
|
||||
|
||||
const tunnelsJotai = createIsolation();
|
||||
|
||||
export const useInitializeTunnels = () => {
|
||||
return React.useMemo((): TunnelsContextValue => {
|
||||
return {
|
||||
@@ -39,7 +34,7 @@ export const useInitializeTunnels = () => {
|
||||
DefaultSidebarTabTriggersTunnel: tunnel(),
|
||||
OverwriteConfirmDialogTunnel: tunnel(),
|
||||
TTDDialogTriggerTunnel: tunnel(),
|
||||
tunnelsJotai,
|
||||
jotaiScope: Symbol(),
|
||||
};
|
||||
}, []);
|
||||
};
|
||||
|
@@ -95,7 +95,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
"height": 35,
|
||||
"height": 33.519031369643244,
|
||||
"id": Any<String>,
|
||||
"index": "a2",
|
||||
"isDeleted": false,
|
||||
@@ -109,8 +109,8 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
||||
0.5,
|
||||
],
|
||||
[
|
||||
394.5,
|
||||
34.5,
|
||||
382.47606040672997,
|
||||
34.019031369643244,
|
||||
],
|
||||
],
|
||||
"roughness": 1,
|
||||
@@ -128,9 +128,9 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
||||
"strokeWidth": 2,
|
||||
"type": "arrow",
|
||||
"updated": 1,
|
||||
"version": 4,
|
||||
"version": 7,
|
||||
"versionNonce": Any<Number>,
|
||||
"width": 395,
|
||||
"width": 381.97606040672997,
|
||||
"x": 247,
|
||||
"y": 420,
|
||||
}
|
||||
@@ -167,7 +167,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
||||
0,
|
||||
],
|
||||
[
|
||||
399.5,
|
||||
389.5,
|
||||
0,
|
||||
],
|
||||
],
|
||||
@@ -186,10 +186,10 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
||||
"strokeWidth": 2,
|
||||
"type": "arrow",
|
||||
"updated": 1,
|
||||
"version": 4,
|
||||
"version": 6,
|
||||
"versionNonce": Any<Number>,
|
||||
"width": 400,
|
||||
"x": 227,
|
||||
"width": 390,
|
||||
"x": 237,
|
||||
"y": 450,
|
||||
}
|
||||
`;
|
||||
@@ -319,7 +319,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
|
||||
"verticalAlign": "top",
|
||||
"width": 100,
|
||||
"x": 560,
|
||||
"y": 226.5,
|
||||
"y": 236.95454545454544,
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -339,13 +339,13 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
|
||||
"endBinding": {
|
||||
"elementId": "text-2",
|
||||
"fixedPoint": null,
|
||||
"focus": 0,
|
||||
"focus": 1.625925925925924,
|
||||
"gap": 14,
|
||||
},
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
"height": 0,
|
||||
"height": 18.278619528619487,
|
||||
"id": Any<String>,
|
||||
"index": "a2",
|
||||
"isDeleted": false,
|
||||
@@ -356,11 +356,11 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
|
||||
"points": [
|
||||
[
|
||||
0.5,
|
||||
0,
|
||||
-0.5,
|
||||
],
|
||||
[
|
||||
99.5,
|
||||
0,
|
||||
357.2037037037038,
|
||||
-17.778619528619487,
|
||||
],
|
||||
],
|
||||
"roughness": 1,
|
||||
@@ -378,11 +378,11 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
|
||||
"strokeWidth": 2,
|
||||
"type": "arrow",
|
||||
"updated": 1,
|
||||
"version": 4,
|
||||
"version": 6,
|
||||
"versionNonce": Any<Number>,
|
||||
"width": 100,
|
||||
"x": 255,
|
||||
"y": 239,
|
||||
"width": 357.7037037037038,
|
||||
"x": 171,
|
||||
"y": 249.45454545454544,
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -482,7 +482,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe
|
||||
"strokeWidth": 2,
|
||||
"type": "arrow",
|
||||
"updated": 1,
|
||||
"version": 4,
|
||||
"version": 6,
|
||||
"versionNonce": Any<Number>,
|
||||
"width": 100,
|
||||
"x": 255,
|
||||
@@ -660,7 +660,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when
|
||||
"strokeWidth": 2,
|
||||
"type": "arrow",
|
||||
"updated": 1,
|
||||
"version": 4,
|
||||
"version": 6,
|
||||
"versionNonce": Any<Number>,
|
||||
"width": 100,
|
||||
"x": 255,
|
||||
@@ -1505,7 +1505,7 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
||||
0,
|
||||
],
|
||||
[
|
||||
272.485,
|
||||
270.98528125,
|
||||
0,
|
||||
],
|
||||
],
|
||||
@@ -1526,10 +1526,10 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
||||
"strokeWidth": 2,
|
||||
"type": "arrow",
|
||||
"updated": 1,
|
||||
"version": 4,
|
||||
"version": 7,
|
||||
"versionNonce": Any<Number>,
|
||||
"width": 272.985,
|
||||
"x": 111.262,
|
||||
"width": 270.48528125,
|
||||
"x": 112.76171875,
|
||||
"y": 57,
|
||||
}
|
||||
`;
|
||||
@@ -1587,11 +1587,11 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
||||
"strokeWidth": 2,
|
||||
"type": "arrow",
|
||||
"updated": 1,
|
||||
"version": 4,
|
||||
"version": 6,
|
||||
"versionNonce": Any<Number>,
|
||||
"width": 0,
|
||||
"x": 77.017,
|
||||
"y": 79,
|
||||
"x": 83.015625,
|
||||
"y": 81.5,
|
||||
}
|
||||
`;
|
||||
|
||||
|
@@ -8,7 +8,8 @@ import type {
|
||||
} from "../types";
|
||||
import { restoreLibraryItems } from "./restore";
|
||||
import type App from "../components/App";
|
||||
import { atom, editorJotaiStore } from "../editor-jotai";
|
||||
import { atom } from "jotai";
|
||||
import { jotaiStore } from "../jotai";
|
||||
import type { ExcalidrawElement } from "../element/types";
|
||||
import { getCommonBoundingBox } from "../element/bounds";
|
||||
import { AbortError } from "../errors";
|
||||
@@ -190,13 +191,13 @@ class Library {
|
||||
|
||||
private notifyListeners = () => {
|
||||
if (this.updateQueue.length > 0) {
|
||||
editorJotaiStore.set(libraryItemsAtom, (s) => ({
|
||||
jotaiStore.set(libraryItemsAtom, (s) => ({
|
||||
status: "loading",
|
||||
libraryItems: this.currLibraryItems,
|
||||
isInitialized: s.isInitialized,
|
||||
}));
|
||||
} else {
|
||||
editorJotaiStore.set(libraryItemsAtom, {
|
||||
jotaiStore.set(libraryItemsAtom, {
|
||||
status: "loaded",
|
||||
libraryItems: this.currLibraryItems,
|
||||
isInitialized: true,
|
||||
@@ -224,7 +225,7 @@ class Library {
|
||||
destroy = () => {
|
||||
this.updateQueue = [];
|
||||
this.currLibraryItems = [];
|
||||
editorJotaiStore.set(libraryItemSvgsCache, new Map());
|
||||
jotaiStore.set(libraryItemSvgsCache, new Map());
|
||||
// TODO uncomment after/if we make jotai store scoped to each excal instance
|
||||
// jotaiStore.set(libraryItemsAtom, {
|
||||
// status: "loading",
|
||||
|
@@ -1,13 +0,0 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { atom, createStore, type PrimitiveAtom } from "jotai";
|
||||
import { createIsolation } from "jotai-scope";
|
||||
|
||||
const jotai = createIsolation();
|
||||
|
||||
export { atom, PrimitiveAtom };
|
||||
export const { useAtom, useSetAtom, useAtomValue, useStore } = jotai;
|
||||
export const EditorJotaiProvider: ReturnType<
|
||||
typeof createIsolation
|
||||
>["Provider"] = jotai.Provider;
|
||||
|
||||
export const editorJotaiStore: ReturnType<typeof createStore> = createStore();
|
@@ -504,6 +504,12 @@ export const bindLinearElement = (
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
// update bound elements to make sure the binding tips are in sync with
|
||||
// the normalized gap from above
|
||||
if (!isElbowArrow(linearElement)) {
|
||||
updateBoundElements(hoveredElement, elementsMap);
|
||||
}
|
||||
};
|
||||
|
||||
// Don't bind both ends of a simple segment
|
||||
|
@@ -105,10 +105,6 @@ export const selectGroupsForSelectedElements = (function () {
|
||||
const groupElementsIndex: Record<GroupId, string[]> = {};
|
||||
const selectedElementIdsInGroups = elements.reduce(
|
||||
(acc: Record<string, true>, element) => {
|
||||
if (element.isDeleted) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
const groupId = element.groupIds.find((id) => selectedGroupIds[id]);
|
||||
|
||||
if (groupId) {
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { atom, useAtom } from "jotai";
|
||||
import { useEffect, useState } from "react";
|
||||
import { COLOR_PALETTE } from "../colors";
|
||||
import { atom, useAtom } from "../editor-jotai";
|
||||
import { jotaiScope } from "../jotai";
|
||||
import { exportToSvg } from "../../utils/export";
|
||||
import type { LibraryItem } from "../types";
|
||||
|
||||
@@ -63,7 +64,7 @@ export const useLibraryItemSvg = (
|
||||
};
|
||||
|
||||
export const useLibraryCache = () => {
|
||||
const [svgCache] = useAtom(libraryItemSvgsCache);
|
||||
const [svgCache] = useAtom(libraryItemSvgsCache, jotaiScope);
|
||||
|
||||
const clearLibraryCache = () => svgCache.clear();
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { useEffect } from "react";
|
||||
import { atom, useAtom } from "../editor-jotai";
|
||||
import { atom, useAtom } from "jotai";
|
||||
import throttle from "lodash.throttle";
|
||||
|
||||
const scrollPositionAtom = atom<number>(0);
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import fallbackLangData from "./locales/en.json";
|
||||
import percentages from "./locales/percentages.json";
|
||||
import { useAtomValue, editorJotaiStore, atom } from "./editor-jotai";
|
||||
import { jotaiScope, jotaiStore } from "./jotai";
|
||||
import { atom, useAtomValue } from "jotai";
|
||||
import type { NestedKeyOf } from "./utility-types";
|
||||
|
||||
const COMPLETION_THRESHOLD = 85;
|
||||
@@ -102,7 +103,7 @@ export const setLanguage = async (lang: Language) => {
|
||||
}
|
||||
}
|
||||
|
||||
editorJotaiStore.set(editorLangCodeAtom, lang.code);
|
||||
jotaiStore.set(editorLangCodeAtom, lang.code);
|
||||
};
|
||||
|
||||
export const getLanguage = () => currentLang;
|
||||
@@ -164,6 +165,6 @@ const editorLangCodeAtom = atom(defaultLang.code);
|
||||
// - component is rendered internally by <Excalidraw>, but the component
|
||||
// is memoized w/o being updated on `langCode`, `AppState`, or `UIAppState`
|
||||
export const useI18n = () => {
|
||||
const langCode = useAtomValue(editorLangCodeAtom);
|
||||
const langCode = useAtomValue(editorLangCodeAtom, jotaiScope);
|
||||
return { t, langCode };
|
||||
};
|
||||
|
@@ -11,7 +11,8 @@ import "./fonts/fonts.css";
|
||||
import type { AppProps, ExcalidrawProps } from "./types";
|
||||
import { defaultLang } from "./i18n";
|
||||
import { DEFAULT_UI_OPTIONS } from "./constants";
|
||||
import { EditorJotaiProvider, editorJotaiStore } from "./editor-jotai";
|
||||
import { Provider } from "jotai";
|
||||
import { jotaiScope, jotaiStore } from "./jotai";
|
||||
import Footer from "./components/footer/FooterCenter";
|
||||
import MainMenu from "./components/main-menu/MainMenu";
|
||||
import WelcomeScreen from "./components/welcome-screen/WelcomeScreen";
|
||||
@@ -107,7 +108,7 @@ const ExcalidrawBase = (props: ExcalidrawProps) => {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<EditorJotaiProvider store={editorJotaiStore}>
|
||||
<Provider unstable_createStore={() => jotaiStore} scope={jotaiScope}>
|
||||
<InitializeApp langCode={langCode} theme={theme}>
|
||||
<App
|
||||
onChange={onChange}
|
||||
@@ -144,7 +145,7 @@ const ExcalidrawBase = (props: ExcalidrawProps) => {
|
||||
{children}
|
||||
</App>
|
||||
</InitializeApp>
|
||||
</EditorJotaiProvider>
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
|
||||
|
28
packages/excalidraw/jotai.ts
Normal file
28
packages/excalidraw/jotai.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import type { PrimitiveAtom } from "jotai";
|
||||
import { unstable_createStore, useAtom } from "jotai";
|
||||
import { useLayoutEffect } from "react";
|
||||
|
||||
export const jotaiScope = Symbol();
|
||||
export const jotaiStore = unstable_createStore();
|
||||
|
||||
export const useAtomWithInitialValue = <
|
||||
T extends unknown,
|
||||
A extends PrimitiveAtom<T>,
|
||||
>(
|
||||
atom: A,
|
||||
initialValue: T | (() => T),
|
||||
) => {
|
||||
const [value, setValue] = useAtom(atom);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (typeof initialValue === "function") {
|
||||
// @ts-ignore
|
||||
setValue(initialValue());
|
||||
} else {
|
||||
setValue(initialValue);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return [value, setValue] as const;
|
||||
};
|
@@ -164,8 +164,7 @@
|
||||
"imageCropping": "Image cropping",
|
||||
"unCroppedDimension": "Uncropped dimension",
|
||||
"copyElementLink": "Copy link to object",
|
||||
"linkToElement": "Link to object",
|
||||
"wrapSelectionInFrame": "Wrap selection in frame"
|
||||
"linkToElement": "Link to object"
|
||||
},
|
||||
"elementLink": {
|
||||
"title": "Link to object",
|
||||
|
@@ -70,8 +70,7 @@
|
||||
"fractional-indexing": "3.2.0",
|
||||
"fuzzy": "0.1.3",
|
||||
"image-blob-reduce": "3.0.1",
|
||||
"jotai": "2.11.0",
|
||||
"jotai-scope": "0.7.2",
|
||||
"jotai": "1.13.1",
|
||||
"lodash.throttle": "4.1.1",
|
||||
"nanoid": "3.3.3",
|
||||
"open-color": "1.9.1",
|
||||
|
@@ -97,7 +97,6 @@ exports[`contextMenu element > right-clicking on a group should select whole gro
|
||||
"category": "element",
|
||||
},
|
||||
},
|
||||
"separator",
|
||||
{
|
||||
"label": "labels.selectAllElementsInFrame",
|
||||
"name": "selectAllElementsInFrame",
|
||||
@@ -116,15 +115,6 @@ exports[`contextMenu element > right-clicking on a group should select whole gro
|
||||
"category": "history",
|
||||
},
|
||||
},
|
||||
{
|
||||
"label": "labels.wrapSelectionInFrame",
|
||||
"name": "wrapSelectionInFrame",
|
||||
"perform": [Function],
|
||||
"predicate": [Function],
|
||||
"trackEvent": {
|
||||
"category": "element",
|
||||
},
|
||||
},
|
||||
"separator",
|
||||
{
|
||||
"PanelComponent": [Function],
|
||||
@@ -4741,7 +4731,6 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
||||
"category": "element",
|
||||
},
|
||||
},
|
||||
"separator",
|
||||
{
|
||||
"label": "labels.selectAllElementsInFrame",
|
||||
"name": "selectAllElementsInFrame",
|
||||
@@ -4760,15 +4749,6 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
||||
"category": "history",
|
||||
},
|
||||
},
|
||||
{
|
||||
"label": "labels.wrapSelectionInFrame",
|
||||
"name": "wrapSelectionInFrame",
|
||||
"perform": [Function],
|
||||
"predicate": [Function],
|
||||
"trackEvent": {
|
||||
"category": "element",
|
||||
},
|
||||
},
|
||||
"separator",
|
||||
{
|
||||
"PanelComponent": [Function],
|
||||
@@ -5962,7 +5942,6 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
||||
"category": "element",
|
||||
},
|
||||
},
|
||||
"separator",
|
||||
{
|
||||
"label": "labels.selectAllElementsInFrame",
|
||||
"name": "selectAllElementsInFrame",
|
||||
@@ -5981,15 +5960,6 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
||||
"category": "history",
|
||||
},
|
||||
},
|
||||
{
|
||||
"label": "labels.wrapSelectionInFrame",
|
||||
"name": "wrapSelectionInFrame",
|
||||
"perform": [Function],
|
||||
"predicate": [Function],
|
||||
"trackEvent": {
|
||||
"category": "element",
|
||||
},
|
||||
},
|
||||
"separator",
|
||||
{
|
||||
"PanelComponent": [Function],
|
||||
@@ -7906,7 +7876,6 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
||||
"category": "element",
|
||||
},
|
||||
},
|
||||
"separator",
|
||||
{
|
||||
"label": "labels.selectAllElementsInFrame",
|
||||
"name": "selectAllElementsInFrame",
|
||||
@@ -7925,15 +7894,6 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
||||
"category": "history",
|
||||
},
|
||||
},
|
||||
{
|
||||
"label": "labels.wrapSelectionInFrame",
|
||||
"name": "wrapSelectionInFrame",
|
||||
"perform": [Function],
|
||||
"predicate": [Function],
|
||||
"trackEvent": {
|
||||
"category": "element",
|
||||
},
|
||||
},
|
||||
"separator",
|
||||
{
|
||||
"PanelComponent": [Function],
|
||||
@@ -8894,7 +8854,6 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
||||
"category": "element",
|
||||
},
|
||||
},
|
||||
"separator",
|
||||
{
|
||||
"label": "labels.selectAllElementsInFrame",
|
||||
"name": "selectAllElementsInFrame",
|
||||
@@ -8913,15 +8872,6 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
||||
"category": "history",
|
||||
},
|
||||
},
|
||||
{
|
||||
"label": "labels.wrapSelectionInFrame",
|
||||
"name": "wrapSelectionInFrame",
|
||||
"perform": [Function],
|
||||
"predicate": [Function],
|
||||
"trackEvent": {
|
||||
"category": "element",
|
||||
},
|
||||
},
|
||||
"separator",
|
||||
{
|
||||
"PanelComponent": [Function],
|
||||
|
@@ -197,7 +197,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
"height": 99,
|
||||
"height": 125,
|
||||
"id": "id166",
|
||||
"index": "a2",
|
||||
"isDeleted": false,
|
||||
@@ -211,8 +211,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||
0,
|
||||
],
|
||||
[
|
||||
"98.20800",
|
||||
99,
|
||||
125,
|
||||
125,
|
||||
],
|
||||
],
|
||||
"roughness": 1,
|
||||
@@ -226,9 +226,9 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||
"strokeWidth": 2,
|
||||
"type": "arrow",
|
||||
"updated": 1,
|
||||
"version": 40,
|
||||
"width": "98.20800",
|
||||
"x": 1,
|
||||
"version": 47,
|
||||
"width": 125,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
}
|
||||
`;
|
||||
@@ -298,7 +298,7 @@ History {
|
||||
"focus": "0.00990",
|
||||
"gap": 1,
|
||||
},
|
||||
"height": "0.98017",
|
||||
"height": "0.98000",
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
@@ -306,7 +306,7 @@ History {
|
||||
],
|
||||
[
|
||||
98,
|
||||
"-0.98017",
|
||||
"-0.98000",
|
||||
],
|
||||
],
|
||||
"startBinding": {
|
||||
@@ -320,10 +320,10 @@ History {
|
||||
"endBinding": {
|
||||
"elementId": "id165",
|
||||
"fixedPoint": null,
|
||||
"focus": "-0.02000",
|
||||
"focus": "-0.02040",
|
||||
"gap": 1,
|
||||
},
|
||||
"height": "0.00169",
|
||||
"height": "0.02000",
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
@@ -331,13 +331,13 @@ History {
|
||||
],
|
||||
[
|
||||
98,
|
||||
"0.00169",
|
||||
"0.02000",
|
||||
],
|
||||
],
|
||||
"startBinding": {
|
||||
"elementId": "id164",
|
||||
"fixedPoint": null,
|
||||
"focus": "0.02000",
|
||||
"focus": "0.01959",
|
||||
"gap": 1,
|
||||
},
|
||||
},
|
||||
@@ -393,18 +393,20 @@ History {
|
||||
"focus": 0,
|
||||
"gap": 1,
|
||||
},
|
||||
"height": 99,
|
||||
"height": 125,
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0,
|
||||
],
|
||||
[
|
||||
"98.20800",
|
||||
99,
|
||||
125,
|
||||
125,
|
||||
],
|
||||
],
|
||||
"startBinding": null,
|
||||
"width": 125,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
},
|
||||
"inserted": {
|
||||
@@ -414,7 +416,7 @@ History {
|
||||
"focus": "0.00990",
|
||||
"gap": 1,
|
||||
},
|
||||
"height": "0.98161",
|
||||
"height": "0.98000",
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
@@ -422,7 +424,7 @@ History {
|
||||
],
|
||||
[
|
||||
98,
|
||||
"-0.98161",
|
||||
"-0.98000",
|
||||
],
|
||||
],
|
||||
"startBinding": {
|
||||
@@ -431,7 +433,9 @@ History {
|
||||
"focus": "0.02970",
|
||||
"gap": 1,
|
||||
},
|
||||
"y": "0.99245",
|
||||
"width": 98,
|
||||
"x": 1,
|
||||
"y": "0.99000",
|
||||
},
|
||||
},
|
||||
"id169" => Delta {
|
||||
@@ -823,9 +827,9 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||
"strokeWidth": 2,
|
||||
"type": "arrow",
|
||||
"updated": 1,
|
||||
"version": 30,
|
||||
"width": 0,
|
||||
"x": 200,
|
||||
"version": 37,
|
||||
"width": 100,
|
||||
"x": 150,
|
||||
"y": 0,
|
||||
}
|
||||
`;
|
||||
@@ -862,6 +866,8 @@ History {
|
||||
0,
|
||||
],
|
||||
],
|
||||
"width": 0,
|
||||
"x": 149,
|
||||
},
|
||||
"inserted": {
|
||||
"points": [
|
||||
@@ -870,10 +876,12 @@ History {
|
||||
0,
|
||||
],
|
||||
[
|
||||
100,
|
||||
"98.00000",
|
||||
0,
|
||||
],
|
||||
],
|
||||
"width": "98.00000",
|
||||
"x": "1.00000",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -930,6 +938,8 @@ History {
|
||||
],
|
||||
],
|
||||
"startBinding": null,
|
||||
"width": 100,
|
||||
"x": 150,
|
||||
},
|
||||
"inserted": {
|
||||
"endBinding": {
|
||||
@@ -954,6 +964,8 @@ History {
|
||||
"focus": 0,
|
||||
"gap": 1,
|
||||
},
|
||||
"width": 0,
|
||||
"x": 149,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -2363,9 +2375,9 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
||||
"strokeWidth": 2,
|
||||
"type": "arrow",
|
||||
"updated": 1,
|
||||
"version": 10,
|
||||
"version": 12,
|
||||
"width": 498,
|
||||
"x": 1,
|
||||
"x": "1.00000",
|
||||
"y": 0,
|
||||
}
|
||||
`;
|
||||
@@ -2504,7 +2516,7 @@ History {
|
||||
0,
|
||||
],
|
||||
[
|
||||
100,
|
||||
"98.00000",
|
||||
0,
|
||||
],
|
||||
],
|
||||
@@ -2523,8 +2535,8 @@ History {
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"type": "arrow",
|
||||
"width": 100,
|
||||
"x": 0,
|
||||
"width": "98.00000",
|
||||
"x": 1,
|
||||
"y": 0,
|
||||
},
|
||||
"inserted": {
|
||||
@@ -15167,9 +15179,9 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
||||
"strokeWidth": 2,
|
||||
"type": "arrow",
|
||||
"updated": 1,
|
||||
"version": 10,
|
||||
"version": 12,
|
||||
"width": "98.00000",
|
||||
"x": 1,
|
||||
"x": "1.00000",
|
||||
"y": 0,
|
||||
}
|
||||
`;
|
||||
@@ -15208,7 +15220,7 @@ History {
|
||||
0,
|
||||
],
|
||||
[
|
||||
100,
|
||||
"98.00000",
|
||||
0,
|
||||
],
|
||||
],
|
||||
@@ -15221,7 +15233,7 @@ History {
|
||||
0,
|
||||
],
|
||||
[
|
||||
100,
|
||||
"98.00000",
|
||||
0,
|
||||
],
|
||||
],
|
||||
@@ -15517,7 +15529,7 @@ History {
|
||||
0,
|
||||
],
|
||||
[
|
||||
100,
|
||||
"98.00000",
|
||||
0,
|
||||
],
|
||||
],
|
||||
@@ -15536,8 +15548,8 @@ History {
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"type": "arrow",
|
||||
"width": 100,
|
||||
"x": 0,
|
||||
"width": "98.00000",
|
||||
"x": 1,
|
||||
"y": 0,
|
||||
},
|
||||
"inserted": {
|
||||
@@ -15866,9 +15878,9 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
||||
"strokeWidth": 2,
|
||||
"type": "arrow",
|
||||
"updated": 1,
|
||||
"version": 10,
|
||||
"version": 12,
|
||||
"width": "98.00000",
|
||||
"x": 1,
|
||||
"x": "1.00000",
|
||||
"y": 0,
|
||||
}
|
||||
`;
|
||||
@@ -16140,7 +16152,7 @@ History {
|
||||
0,
|
||||
],
|
||||
[
|
||||
100,
|
||||
"98.00000",
|
||||
0,
|
||||
],
|
||||
],
|
||||
@@ -16159,8 +16171,8 @@ History {
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"type": "arrow",
|
||||
"width": 100,
|
||||
"x": 0,
|
||||
"width": "98.00000",
|
||||
"x": 1,
|
||||
"y": 0,
|
||||
},
|
||||
"inserted": {
|
||||
@@ -16489,9 +16501,9 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
||||
"strokeWidth": 2,
|
||||
"type": "arrow",
|
||||
"updated": 1,
|
||||
"version": 10,
|
||||
"version": 12,
|
||||
"width": "98.00000",
|
||||
"x": 1,
|
||||
"x": "1.00000",
|
||||
"y": 0,
|
||||
}
|
||||
`;
|
||||
@@ -16763,7 +16775,7 @@ History {
|
||||
0,
|
||||
],
|
||||
[
|
||||
100,
|
||||
"98.00000",
|
||||
0,
|
||||
],
|
||||
],
|
||||
@@ -16782,8 +16794,8 @@ History {
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"type": "arrow",
|
||||
"width": 100,
|
||||
"x": 0,
|
||||
"width": "98.00000",
|
||||
"x": 1,
|
||||
"y": 0,
|
||||
},
|
||||
"inserted": {
|
||||
@@ -17110,9 +17122,9 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
||||
"strokeWidth": 2,
|
||||
"type": "arrow",
|
||||
"updated": 1,
|
||||
"version": 10,
|
||||
"version": 12,
|
||||
"width": "98.00000",
|
||||
"x": 1,
|
||||
"x": "1.00000",
|
||||
"y": 0,
|
||||
}
|
||||
`;
|
||||
@@ -17168,7 +17180,7 @@ History {
|
||||
0,
|
||||
],
|
||||
[
|
||||
100,
|
||||
"98.00000",
|
||||
0,
|
||||
],
|
||||
],
|
||||
@@ -17186,7 +17198,7 @@ History {
|
||||
0,
|
||||
],
|
||||
[
|
||||
100,
|
||||
"98.00000",
|
||||
0,
|
||||
],
|
||||
],
|
||||
@@ -17455,7 +17467,7 @@ History {
|
||||
0,
|
||||
],
|
||||
[
|
||||
100,
|
||||
"98.00000",
|
||||
0,
|
||||
],
|
||||
],
|
||||
@@ -17474,8 +17486,8 @@ History {
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"type": "arrow",
|
||||
"width": 100,
|
||||
"x": 0,
|
||||
"width": "98.00000",
|
||||
"x": 1,
|
||||
"y": 0,
|
||||
},
|
||||
"inserted": {
|
||||
@@ -17828,9 +17840,9 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
||||
"strokeWidth": 2,
|
||||
"type": "arrow",
|
||||
"updated": 1,
|
||||
"version": 11,
|
||||
"version": 13,
|
||||
"width": "98.00000",
|
||||
"x": 1,
|
||||
"x": "1.00000",
|
||||
"y": 0,
|
||||
}
|
||||
`;
|
||||
@@ -17901,7 +17913,7 @@ History {
|
||||
0,
|
||||
],
|
||||
[
|
||||
100,
|
||||
"98.00000",
|
||||
0,
|
||||
],
|
||||
],
|
||||
@@ -17920,7 +17932,7 @@ History {
|
||||
0,
|
||||
],
|
||||
[
|
||||
100,
|
||||
"98.00000",
|
||||
0,
|
||||
],
|
||||
],
|
||||
@@ -18189,7 +18201,7 @@ History {
|
||||
0,
|
||||
],
|
||||
[
|
||||
100,
|
||||
"98.00000",
|
||||
0,
|
||||
],
|
||||
],
|
||||
@@ -18208,8 +18220,8 @@ History {
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"type": "arrow",
|
||||
"width": 100,
|
||||
"x": 0,
|
||||
"width": "98.00000",
|
||||
"x": 1,
|
||||
"y": 0,
|
||||
},
|
||||
"inserted": {
|
||||
|
@@ -173,7 +173,7 @@ exports[`move element > rectangles with binding arrow 6`] = `
|
||||
"type": "rectangle",
|
||||
"updated": 1,
|
||||
"version": 7,
|
||||
"versionNonce": 745419401,
|
||||
"versionNonce": 2066753033,
|
||||
"width": 300,
|
||||
"x": 201,
|
||||
"y": 2,
|
||||
@@ -232,8 +232,8 @@ exports[`move element > rectangles with binding arrow 7`] = `
|
||||
"strokeWidth": 2,
|
||||
"type": "arrow",
|
||||
"updated": 1,
|
||||
"version": 11,
|
||||
"versionNonce": 1996028265,
|
||||
"version": 15,
|
||||
"versionNonce": 271613161,
|
||||
"width": 81,
|
||||
"x": 110,
|
||||
"y": 50,
|
||||
|
@@ -4307,20 +4307,14 @@ History {
|
||||
"appStateChange": AppStateChange {
|
||||
"delta": Delta {
|
||||
"deleted": {
|
||||
"editingGroupId": null,
|
||||
"selectedElementIds": {
|
||||
"id1": true,
|
||||
},
|
||||
"selectedGroupIds": {
|
||||
"id4": false,
|
||||
},
|
||||
},
|
||||
"inserted": {
|
||||
"editingGroupId": "id4",
|
||||
"selectedElementIds": {
|
||||
"id0": true,
|
||||
},
|
||||
"selectedGroupIds": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -4343,16 +4337,14 @@ History {
|
||||
"appStateChange": AppStateChange {
|
||||
"delta": Delta {
|
||||
"deleted": {
|
||||
"editingGroupId": null,
|
||||
"selectedElementIds": {},
|
||||
"selectedGroupIds": {},
|
||||
},
|
||||
"inserted": {
|
||||
"editingGroupId": "id4",
|
||||
"selectedElementIds": {
|
||||
"id1": true,
|
||||
},
|
||||
"selectedGroupIds": {
|
||||
"id4": false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@@ -120,7 +120,6 @@ describe("contextMenu element", () => {
|
||||
"cut",
|
||||
"copy",
|
||||
"paste",
|
||||
"wrapSelectionInFrame",
|
||||
"copyStyles",
|
||||
"pasteStyles",
|
||||
"deleteSelectedElements",
|
||||
@@ -214,7 +213,6 @@ describe("contextMenu element", () => {
|
||||
"cut",
|
||||
"copy",
|
||||
"paste",
|
||||
"wrapSelectionInFrame",
|
||||
"copyStyles",
|
||||
"pasteStyles",
|
||||
"deleteSelectedElements",
|
||||
@@ -271,7 +269,6 @@ describe("contextMenu element", () => {
|
||||
"cut",
|
||||
"copy",
|
||||
"paste",
|
||||
"wrapSelectionInFrame",
|
||||
"copyStyles",
|
||||
"pasteStyles",
|
||||
"deleteSelectedElements",
|
||||
|
13
yarn.lock
13
yarn.lock
@@ -7339,15 +7339,10 @@ jest-worker@^27.4.5:
|
||||
merge-stream "^2.0.0"
|
||||
supports-color "^8.0.0"
|
||||
|
||||
jotai-scope@0.7.2:
|
||||
version "0.7.2"
|
||||
resolved "https://registry.yarnpkg.com/jotai-scope/-/jotai-scope-0.7.2.tgz#3e9ec5b743bd9f36b08b32cf5151786049bfcce7"
|
||||
integrity sha512-Gwed97f3dDObrO43++2lRcgOqw4O2sdr4JCjP/7eHK1oPACDJ7xKHGScpJX9XaflU+KBHXF+VhwECnzcaQiShg==
|
||||
|
||||
jotai@2.11.0:
|
||||
version "2.11.0"
|
||||
resolved "https://registry.yarnpkg.com/jotai/-/jotai-2.11.0.tgz#923f8351e0b2d721036af892c0ae25625049d120"
|
||||
integrity sha512-zKfoBBD1uDw3rljwHkt0fWuja1B76R7CjznuBO+mSX6jpsO1EBeWNRKpeaQho9yPI/pvCv4recGfgOXGxwPZvQ==
|
||||
jotai@1.13.1:
|
||||
version "1.13.1"
|
||||
resolved "https://registry.yarnpkg.com/jotai/-/jotai-1.13.1.tgz#20cc46454cbb39096b12fddfa635b873b3668236"
|
||||
integrity sha512-RUmH1S4vLsG3V6fbGlKzGJnLrDcC/HNb5gH2AeA9DzuJknoVxSGvvg8OBB7lke+gDc4oXmdVsaKn/xDUhWZ0vw==
|
||||
|
||||
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
|
||||
version "4.0.0"
|
||||
|
Reference in New Issue
Block a user