Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8ec7801a31 | |||
| 2e719ff671 | |||
| 79d9dc2f8f | |||
| 9013c84524 | |||
| 47f87f4ecb | |||
| 73bf50e8a8 | |||
| 48c3465b19 | |||
| adc4c9f484 | |||
| def1df2c68 |
+25
-21
@@ -709,27 +709,30 @@ const ExcalidrawWrapper = () => {
|
|||||||
toggleTheme: true,
|
toggleTheme: true,
|
||||||
export: {
|
export: {
|
||||||
onExportToBackend,
|
onExportToBackend,
|
||||||
renderCustomUI: (elements, appState, files) => {
|
renderCustomUI: excalidrawAPI
|
||||||
return (
|
? (elements, appState, files) => {
|
||||||
<ExportToExcalidrawPlus
|
return (
|
||||||
elements={elements}
|
<ExportToExcalidrawPlus
|
||||||
appState={appState}
|
elements={elements}
|
||||||
files={files}
|
appState={appState}
|
||||||
onError={(error) => {
|
files={files}
|
||||||
excalidrawAPI?.updateScene({
|
name={excalidrawAPI.getName()}
|
||||||
appState: {
|
onError={(error) => {
|
||||||
errorMessage: error.message,
|
excalidrawAPI?.updateScene({
|
||||||
},
|
appState: {
|
||||||
});
|
errorMessage: error.message,
|
||||||
}}
|
},
|
||||||
onSuccess={() => {
|
});
|
||||||
excalidrawAPI?.updateScene({
|
}}
|
||||||
appState: { openDialog: null },
|
onSuccess={() => {
|
||||||
});
|
excalidrawAPI.updateScene({
|
||||||
}}
|
appState: { openDialog: null },
|
||||||
/>
|
});
|
||||||
);
|
}}
|
||||||
},
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
@@ -775,6 +778,7 @@ const ExcalidrawWrapper = () => {
|
|||||||
excalidrawAPI.getSceneElements(),
|
excalidrawAPI.getSceneElements(),
|
||||||
excalidrawAPI.getAppState(),
|
excalidrawAPI.getAppState(),
|
||||||
excalidrawAPI.getFiles(),
|
excalidrawAPI.getFiles(),
|
||||||
|
excalidrawAPI.getName(),
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export const exportToExcalidrawPlus = async (
|
|||||||
elements: readonly NonDeletedExcalidrawElement[],
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
appState: Partial<AppState>,
|
appState: Partial<AppState>,
|
||||||
files: BinaryFiles,
|
files: BinaryFiles,
|
||||||
|
name: string,
|
||||||
) => {
|
) => {
|
||||||
const firebase = await loadFirebaseStorage();
|
const firebase = await loadFirebaseStorage();
|
||||||
|
|
||||||
@@ -53,7 +54,7 @@ export const exportToExcalidrawPlus = async (
|
|||||||
.ref(`/migrations/scenes/${id}`)
|
.ref(`/migrations/scenes/${id}`)
|
||||||
.put(blob, {
|
.put(blob, {
|
||||||
customMetadata: {
|
customMetadata: {
|
||||||
data: JSON.stringify({ version: 2, name: appState.name }),
|
data: JSON.stringify({ version: 2, name }),
|
||||||
created: Date.now().toString(),
|
created: Date.now().toString(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -89,9 +90,10 @@ export const ExportToExcalidrawPlus: React.FC<{
|
|||||||
elements: readonly NonDeletedExcalidrawElement[];
|
elements: readonly NonDeletedExcalidrawElement[];
|
||||||
appState: Partial<AppState>;
|
appState: Partial<AppState>;
|
||||||
files: BinaryFiles;
|
files: BinaryFiles;
|
||||||
|
name: string;
|
||||||
onError: (error: Error) => void;
|
onError: (error: Error) => void;
|
||||||
onSuccess: () => void;
|
onSuccess: () => void;
|
||||||
}> = ({ elements, appState, files, onError, onSuccess }) => {
|
}> = ({ elements, appState, files, name, onError, onSuccess }) => {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
return (
|
return (
|
||||||
<Card color="primary">
|
<Card color="primary">
|
||||||
@@ -117,7 +119,7 @@ export const ExportToExcalidrawPlus: React.FC<{
|
|||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
try {
|
try {
|
||||||
trackEvent("export", "eplus", `ui (${getFrame()})`);
|
trackEvent("export", "eplus", `ui (${getFrame()})`);
|
||||||
await exportToExcalidrawPlus(elements, appState, files);
|
await exportToExcalidrawPlus(elements, appState, files, name);
|
||||||
onSuccess();
|
onSuccess();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|||||||
@@ -19,6 +19,10 @@ Please add the latest change on the top under the correct section.
|
|||||||
|
|
||||||
- Expose `getVisibleSceneBounds` helper to get scene bounds of visible canvas area. [#7450](https://github.com/excalidraw/excalidraw/pull/7450)
|
- Expose `getVisibleSceneBounds` helper to get scene bounds of visible canvas area. [#7450](https://github.com/excalidraw/excalidraw/pull/7450)
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Keep customData when converting to ExcalidrawElement. [#7656](https://github.com/excalidraw/excalidraw/pull/7656)
|
||||||
|
|
||||||
### Breaking Changes
|
### Breaking Changes
|
||||||
|
|
||||||
- `ExcalidrawEmbeddableElement.validated` was removed and moved to private editor state. This should largely not affect your apps unless you were reading from this attribute. We keep validating embeddable urls internally, and the public [`props.validateEmbeddable`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props#validateembeddable) still applies. [#7539](https://github.com/excalidraw/excalidraw/pull/7539)
|
- `ExcalidrawEmbeddableElement.validated` was removed and moved to private editor state. This should largely not affect your apps unless you were reading from this attribute. We keep validating embeddable urls internally, and the public [`props.validateEmbeddable`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props#validateembeddable) still applies. [#7539](https://github.com/excalidraw/excalidraw/pull/7539)
|
||||||
@@ -57,10 +61,12 @@ Please add the latest change on the top under the correct section.
|
|||||||
|
|
||||||
- `appState.openDialog` type was changed from `null | string` to `null | { name: string }`. [#7336](https://github.com/excalidraw/excalidraw/pull/7336)
|
- `appState.openDialog` type was changed from `null | string` to `null | { name: string }`. [#7336](https://github.com/excalidraw/excalidraw/pull/7336)
|
||||||
|
|
||||||
## 0.17.1 (2023-11-28)
|
## 0.17.3 (2024-02-09)
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
|
- Keep customData when converting to ExcalidrawElement. [#7656](https://github.com/excalidraw/excalidraw/pull/7656)
|
||||||
|
|
||||||
- Umd build for browser since it was breaking in v0.17.0 [#7349](https://github.com/excalidraw/excalidraw/pull/7349). Also make sure that when using `Vite`, the `process.env.IS_PREACT` is set as `"true"` (string) and not a boolean.
|
- Umd build for browser since it was breaking in v0.17.0 [#7349](https://github.com/excalidraw/excalidraw/pull/7349). Also make sure that when using `Vite`, the `process.env.IS_PREACT` is set as `"true"` (string) and not a boolean.
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -69,6 +75,10 @@ define: {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- Disable caching bounds for arrow labels [#7343](https://github.com/excalidraw/excalidraw/pull/7343)
|
||||||
|
|
||||||
|
- Bounds cached prematurely resulting in incorrectly rendered labels [#7339](https://github.com/excalidraw/excalidraw/pull/7339)
|
||||||
|
|
||||||
## Excalidraw Library
|
## Excalidraw Library
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|||||||
@@ -58,7 +58,11 @@ export const actionUnbindText = register({
|
|||||||
element.id,
|
element.id,
|
||||||
);
|
);
|
||||||
resetOriginalContainerCache(element.id);
|
resetOriginalContainerCache(element.id);
|
||||||
const { x, y } = computeBoundTextPosition(element, boundTextElement);
|
const { x, y } = computeBoundTextPosition(
|
||||||
|
element,
|
||||||
|
boundTextElement,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
mutateElement(boundTextElement as ExcalidrawTextElement, {
|
mutateElement(boundTextElement as ExcalidrawTextElement, {
|
||||||
containerId: null,
|
containerId: null,
|
||||||
width,
|
width,
|
||||||
@@ -145,7 +149,11 @@ export const actionBindText = register({
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
const originalContainerHeight = container.height;
|
const originalContainerHeight = container.height;
|
||||||
redrawTextBoundingBox(textElement, container);
|
redrawTextBoundingBox(
|
||||||
|
textElement,
|
||||||
|
container,
|
||||||
|
app.scene.getNonDeletedElementsMap(),
|
||||||
|
);
|
||||||
// overwritting the cache with original container height so
|
// overwritting the cache with original container height so
|
||||||
// it can be restored when unbind
|
// it can be restored when unbind
|
||||||
updateOriginalContainerCache(container.id, originalContainerHeight);
|
updateOriginalContainerCache(container.id, originalContainerHeight);
|
||||||
@@ -286,7 +294,11 @@ export const actionWrapTextInContainer = register({
|
|||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
redrawTextBoundingBox(textElement, container);
|
redrawTextBoundingBox(
|
||||||
|
textElement,
|
||||||
|
container,
|
||||||
|
app.scene.getNonDeletedElementsMap(),
|
||||||
|
);
|
||||||
|
|
||||||
updatedElements = pushContainerBelowText(
|
updatedElements = pushContainerBelowText(
|
||||||
[...updatedElements, container],
|
[...updatedElements, container],
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ export const actionCut = register({
|
|||||||
trackEvent: { category: "element" },
|
trackEvent: { category: "element" },
|
||||||
perform: (elements, appState, event: ClipboardEvent | null, app) => {
|
perform: (elements, appState, event: ClipboardEvent | null, app) => {
|
||||||
actionCopy.perform(elements, appState, event, app);
|
actionCopy.perform(elements, appState, event, app);
|
||||||
return actionDeleteSelected.perform(elements, appState);
|
return actionDeleteSelected.perform(elements, appState, null, app);
|
||||||
},
|
},
|
||||||
contextItemLabel: "labels.cut",
|
contextItemLabel: "labels.cut",
|
||||||
keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.key === KEYS.X,
|
keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.key === KEYS.X,
|
||||||
@@ -138,6 +138,7 @@ export const actionCopyAsSvg = register({
|
|||||||
{
|
{
|
||||||
...appState,
|
...appState,
|
||||||
exportingFrame,
|
exportingFrame,
|
||||||
|
name: app.getName(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
@@ -184,6 +185,7 @@ export const actionCopyAsPng = register({
|
|||||||
await exportCanvas("clipboard", exportedElements, appState, app.files, {
|
await exportCanvas("clipboard", exportedElements, appState, app.files, {
|
||||||
...appState,
|
...appState,
|
||||||
exportingFrame,
|
exportingFrame,
|
||||||
|
name: app.getName(),
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
appState: {
|
appState: {
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ const handleGroupEditingState = (
|
|||||||
export const actionDeleteSelected = register({
|
export const actionDeleteSelected = register({
|
||||||
name: "deleteSelectedElements",
|
name: "deleteSelectedElements",
|
||||||
trackEvent: { category: "element", action: "delete" },
|
trackEvent: { category: "element", action: "delete" },
|
||||||
perform: (elements, appState) => {
|
perform: (elements, appState, formData, app) => {
|
||||||
if (appState.editingLinearElement) {
|
if (appState.editingLinearElement) {
|
||||||
const {
|
const {
|
||||||
elementId,
|
elementId,
|
||||||
@@ -81,7 +81,8 @@ export const actionDeleteSelected = register({
|
|||||||
startBindingElement,
|
startBindingElement,
|
||||||
endBindingElement,
|
endBindingElement,
|
||||||
} = appState.editingLinearElement;
|
} = appState.editingLinearElement;
|
||||||
const element = LinearElementEditor.getElement(elementId);
|
const elementsMap = app.scene.getNonDeletedElementsMap();
|
||||||
|
const element = LinearElementEditor.getElement(elementId, elementsMap);
|
||||||
if (!element) {
|
if (!element) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,10 +35,14 @@ import {
|
|||||||
export const actionDuplicateSelection = register({
|
export const actionDuplicateSelection = register({
|
||||||
name: "duplicateSelection",
|
name: "duplicateSelection",
|
||||||
trackEvent: { category: "element" },
|
trackEvent: { category: "element" },
|
||||||
perform: (elements, appState) => {
|
perform: (elements, appState, formData, app) => {
|
||||||
|
const elementsMap = app.scene.getNonDeletedElementsMap();
|
||||||
// duplicate selected point(s) if editing a line
|
// duplicate selected point(s) if editing a line
|
||||||
if (appState.editingLinearElement) {
|
if (appState.editingLinearElement) {
|
||||||
const ret = LinearElementEditor.duplicateSelectedPoints(appState);
|
const ret = LinearElementEditor.duplicateSelectedPoints(
|
||||||
|
appState,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -26,14 +26,11 @@ export const actionChangeProjectName = register({
|
|||||||
perform: (_elements, appState, value) => {
|
perform: (_elements, appState, value) => {
|
||||||
return { appState: { ...appState, name: value }, commitToHistory: false };
|
return { appState: { ...appState, name: value }, commitToHistory: false };
|
||||||
},
|
},
|
||||||
PanelComponent: ({ appState, updateData, appProps, data }) => (
|
PanelComponent: ({ appState, updateData, appProps, data, app }) => (
|
||||||
<ProjectName
|
<ProjectName
|
||||||
label={t("labels.fileTitle")}
|
label={t("labels.fileTitle")}
|
||||||
value={appState.name || "Unnamed"}
|
value={app.getName()}
|
||||||
onChange={(name: string) => updateData(name)}
|
onChange={(name: string) => updateData(name)}
|
||||||
isNameEditable={
|
|
||||||
typeof appProps.name === "undefined" && !appState.viewModeEnabled
|
|
||||||
}
|
|
||||||
ignoreFocus={data?.ignoreFocus ?? false}
|
ignoreFocus={data?.ignoreFocus ?? false}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
@@ -144,8 +141,13 @@ export const actionSaveToActiveFile = register({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const { fileHandle } = isImageFileHandle(appState.fileHandle)
|
const { fileHandle } = isImageFileHandle(appState.fileHandle)
|
||||||
? await resaveAsImageWithScene(elements, appState, app.files)
|
? await resaveAsImageWithScene(
|
||||||
: await saveAsJSON(elements, appState, app.files);
|
elements,
|
||||||
|
appState,
|
||||||
|
app.files,
|
||||||
|
app.getName(),
|
||||||
|
)
|
||||||
|
: await saveAsJSON(elements, appState, app.files, app.getName());
|
||||||
|
|
||||||
return {
|
return {
|
||||||
commitToHistory: false,
|
commitToHistory: false,
|
||||||
@@ -190,6 +192,7 @@ export const actionSaveFileToDisk = register({
|
|||||||
fileHandle: null,
|
fileHandle: null,
|
||||||
},
|
},
|
||||||
app.files,
|
app.files,
|
||||||
|
app.getName(),
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
commitToHistory: false,
|
commitToHistory: false,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { KEYS } from "../keys";
|
import { KEYS } from "../keys";
|
||||||
import { isInvisiblySmallElement } from "../element";
|
import { isInvisiblySmallElement } from "../element";
|
||||||
import { updateActiveTool } from "../utils";
|
import { arrayToMap, updateActiveTool } from "../utils";
|
||||||
import { ToolButton } from "../components/ToolButton";
|
import { ToolButton } from "../components/ToolButton";
|
||||||
import { done } from "../components/icons";
|
import { done } from "../components/icons";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
@@ -26,10 +26,12 @@ export const actionFinalize = register({
|
|||||||
_,
|
_,
|
||||||
{ interactiveCanvas, focusContainer, scene },
|
{ interactiveCanvas, focusContainer, scene },
|
||||||
) => {
|
) => {
|
||||||
|
const elementsMap = scene.getNonDeletedElementsMap();
|
||||||
|
|
||||||
if (appState.editingLinearElement) {
|
if (appState.editingLinearElement) {
|
||||||
const { elementId, startBindingElement, endBindingElement } =
|
const { elementId, startBindingElement, endBindingElement } =
|
||||||
appState.editingLinearElement;
|
appState.editingLinearElement;
|
||||||
const element = LinearElementEditor.getElement(elementId);
|
const element = LinearElementEditor.getElement(elementId, elementsMap);
|
||||||
|
|
||||||
if (element) {
|
if (element) {
|
||||||
if (isBindingElement(element)) {
|
if (isBindingElement(element)) {
|
||||||
@@ -37,6 +39,7 @@ export const actionFinalize = register({
|
|||||||
element,
|
element,
|
||||||
startBindingElement,
|
startBindingElement,
|
||||||
endBindingElement,
|
endBindingElement,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@@ -125,12 +128,14 @@ export const actionFinalize = register({
|
|||||||
const [x, y] = LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
const [x, y] = LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
||||||
multiPointElement,
|
multiPointElement,
|
||||||
-1,
|
-1,
|
||||||
|
arrayToMap(elements),
|
||||||
);
|
);
|
||||||
maybeBindLinearElement(
|
maybeBindLinearElement(
|
||||||
multiPointElement,
|
multiPointElement,
|
||||||
appState,
|
appState,
|
||||||
Scene.getScene(multiPointElement)!,
|
Scene.getScene(multiPointElement)!,
|
||||||
{ x, y },
|
{ x, y },
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -186,7 +191,7 @@ export const actionFinalize = register({
|
|||||||
// To select the linear element when user has finished mutipoint editing
|
// To select the linear element when user has finished mutipoint editing
|
||||||
selectedLinearElement:
|
selectedLinearElement:
|
||||||
multiPointElement && isLinearElement(multiPointElement)
|
multiPointElement && isLinearElement(multiPointElement)
|
||||||
? new LinearElementEditor(multiPointElement, scene)
|
? new LinearElementEditor(multiPointElement)
|
||||||
: appState.selectedLinearElement,
|
: appState.selectedLinearElement,
|
||||||
pendingImageElementId: null,
|
pendingImageElementId: null,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { getNonDeletedElements } from "../element";
|
|||||||
import {
|
import {
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
NonDeleted,
|
NonDeleted,
|
||||||
NonDeletedElementsMap,
|
|
||||||
NonDeletedSceneElementsMap,
|
NonDeletedSceneElementsMap,
|
||||||
} from "../element/types";
|
} from "../element/types";
|
||||||
import { resizeMultipleElements } from "../element/resizeElements";
|
import { resizeMultipleElements } from "../element/resizeElements";
|
||||||
@@ -68,7 +67,7 @@ export const actionFlipVertical = register({
|
|||||||
|
|
||||||
const flipSelectedElements = (
|
const flipSelectedElements = (
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
elementsMap: NonDeletedElementsMap | NonDeletedSceneElementsMap,
|
elementsMap: NonDeletedSceneElementsMap,
|
||||||
appState: Readonly<AppState>,
|
appState: Readonly<AppState>,
|
||||||
flipDirection: "horizontal" | "vertical",
|
flipDirection: "horizontal" | "vertical",
|
||||||
) => {
|
) => {
|
||||||
@@ -83,6 +82,7 @@ const flipSelectedElements = (
|
|||||||
|
|
||||||
const updatedElements = flipElements(
|
const updatedElements = flipElements(
|
||||||
selectedElements,
|
selectedElements,
|
||||||
|
elements,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
appState,
|
appState,
|
||||||
flipDirection,
|
flipDirection,
|
||||||
@@ -97,7 +97,8 @@ const flipSelectedElements = (
|
|||||||
|
|
||||||
const flipElements = (
|
const flipElements = (
|
||||||
selectedElements: NonDeleted<ExcalidrawElement>[],
|
selectedElements: NonDeleted<ExcalidrawElement>[],
|
||||||
elementsMap: NonDeletedElementsMap | NonDeletedSceneElementsMap,
|
elements: readonly ExcalidrawElement[],
|
||||||
|
elementsMap: NonDeletedSceneElementsMap,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
flipDirection: "horizontal" | "vertical",
|
flipDirection: "horizontal" | "vertical",
|
||||||
): ExcalidrawElement[] => {
|
): ExcalidrawElement[] => {
|
||||||
@@ -113,9 +114,9 @@ const flipElements = (
|
|||||||
flipDirection === "horizontal" ? minY : maxY,
|
flipDirection === "horizontal" ? minY : maxY,
|
||||||
);
|
);
|
||||||
|
|
||||||
(isBindingEnabled(appState)
|
isBindingEnabled(appState)
|
||||||
? bindOrUnbindSelectedElements
|
? bindOrUnbindSelectedElements(selectedElements, elements, elementsMap)
|
||||||
: unbindLinearElements)(selectedElements);
|
: unbindLinearElements(selectedElements, elementsMap);
|
||||||
|
|
||||||
return selectedElements;
|
return selectedElements;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -180,6 +180,8 @@ export const actionUngroup = register({
|
|||||||
trackEvent: { category: "element" },
|
trackEvent: { category: "element" },
|
||||||
perform: (elements, appState, _, app) => {
|
perform: (elements, appState, _, app) => {
|
||||||
const groupIds = getSelectedGroupIds(appState);
|
const groupIds = getSelectedGroupIds(appState);
|
||||||
|
const elementsMap = arrayToMap(elements);
|
||||||
|
|
||||||
if (groupIds.length === 0) {
|
if (groupIds.length === 0) {
|
||||||
return { appState, elements, commitToHistory: false };
|
return { appState, elements, commitToHistory: false };
|
||||||
}
|
}
|
||||||
@@ -226,7 +228,12 @@ export const actionUngroup = register({
|
|||||||
if (frame) {
|
if (frame) {
|
||||||
nextElements = replaceAllElementsInFrame(
|
nextElements = replaceAllElementsInFrame(
|
||||||
nextElements,
|
nextElements,
|
||||||
getElementsInResizingFrame(nextElements, frame, appState),
|
getElementsInResizingFrame(
|
||||||
|
nextElements,
|
||||||
|
frame,
|
||||||
|
appState,
|
||||||
|
elementsMap,
|
||||||
|
),
|
||||||
frame,
|
frame,
|
||||||
app,
|
app,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export const actionToggleLinearEditor = register({
|
|||||||
const editingLinearElement =
|
const editingLinearElement =
|
||||||
appState.editingLinearElement?.elementId === selectedElement.id
|
appState.editingLinearElement?.elementId === selectedElement.id
|
||||||
? null
|
? null
|
||||||
: new LinearElementEditor(selectedElement, app.scene);
|
: new LinearElementEditor(selectedElement);
|
||||||
return {
|
return {
|
||||||
appState: {
|
appState: {
|
||||||
...appState,
|
...appState,
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { getContextMenuLabel } from "../components/hyperlink/Hyperlink";
|
||||||
|
import { LinkIcon } from "../components/icons";
|
||||||
|
import { ToolButton } from "../components/ToolButton";
|
||||||
|
import { isEmbeddableElement } from "../element/typeChecks";
|
||||||
|
import { t } from "../i18n";
|
||||||
|
import { KEYS } from "../keys";
|
||||||
|
import { getSelectedElements } from "../scene";
|
||||||
|
import { getShortcutKey } from "../utils";
|
||||||
|
import { register } from "./register";
|
||||||
|
|
||||||
|
export const actionLink = register({
|
||||||
|
name: "hyperlink",
|
||||||
|
perform: (elements, appState) => {
|
||||||
|
if (appState.showHyperlinkPopup === "editor") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
elements,
|
||||||
|
appState: {
|
||||||
|
...appState,
|
||||||
|
showHyperlinkPopup: "editor",
|
||||||
|
openMenu: null,
|
||||||
|
},
|
||||||
|
commitToHistory: true,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
trackEvent: { category: "hyperlink", action: "click" },
|
||||||
|
keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.key === KEYS.K,
|
||||||
|
contextItemLabel: (elements, appState) =>
|
||||||
|
getContextMenuLabel(elements, appState),
|
||||||
|
predicate: (elements, appState) => {
|
||||||
|
const selectedElements = getSelectedElements(elements, appState);
|
||||||
|
return selectedElements.length === 1;
|
||||||
|
},
|
||||||
|
PanelComponent: ({ elements, appState, updateData }) => {
|
||||||
|
const selectedElements = getSelectedElements(elements, appState);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToolButton
|
||||||
|
type="button"
|
||||||
|
icon={LinkIcon}
|
||||||
|
aria-label={t(getContextMenuLabel(elements, appState))}
|
||||||
|
title={`${
|
||||||
|
isEmbeddableElement(elements[0])
|
||||||
|
? t("labels.link.labelEmbed")
|
||||||
|
: t("labels.link.label")
|
||||||
|
} - ${getShortcutKey("CtrlOrCmd+K")}`}
|
||||||
|
onClick={() => updateData(null)}
|
||||||
|
selected={selectedElements.length === 1 && !!selectedElements[0].link}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -209,6 +209,7 @@ const changeFontSize = (
|
|||||||
redrawTextBoundingBox(
|
redrawTextBoundingBox(
|
||||||
newElement,
|
newElement,
|
||||||
app.scene.getContainerElement(oldElement),
|
app.scene.getContainerElement(oldElement),
|
||||||
|
app.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
newElement = offsetElementAfterFontResize(oldElement, newElement);
|
newElement = offsetElementAfterFontResize(oldElement, newElement);
|
||||||
@@ -730,6 +731,7 @@ export const actionChangeFontFamily = register({
|
|||||||
redrawTextBoundingBox(
|
redrawTextBoundingBox(
|
||||||
newElement,
|
newElement,
|
||||||
app.scene.getContainerElement(oldElement),
|
app.scene.getContainerElement(oldElement),
|
||||||
|
app.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
return newElement;
|
return newElement;
|
||||||
}
|
}
|
||||||
@@ -829,6 +831,7 @@ export const actionChangeTextAlign = register({
|
|||||||
redrawTextBoundingBox(
|
redrawTextBoundingBox(
|
||||||
newElement,
|
newElement,
|
||||||
app.scene.getContainerElement(oldElement),
|
app.scene.getContainerElement(oldElement),
|
||||||
|
app.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
return newElement;
|
return newElement;
|
||||||
}
|
}
|
||||||
@@ -918,6 +921,7 @@ export const actionChangeVerticalAlign = register({
|
|||||||
redrawTextBoundingBox(
|
redrawTextBoundingBox(
|
||||||
newElement,
|
newElement,
|
||||||
app.scene.getContainerElement(oldElement),
|
app.scene.getContainerElement(oldElement),
|
||||||
|
app.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
return newElement;
|
return newElement;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export const actionSelectAll = register({
|
|||||||
// single linear element selected
|
// single linear element selected
|
||||||
Object.keys(selectedElementIds).length === 1 &&
|
Object.keys(selectedElementIds).length === 1 &&
|
||||||
isLinearElement(elements[0])
|
isLinearElement(elements[0])
|
||||||
? new LinearElementEditor(elements[0], app.scene)
|
? new LinearElementEditor(elements[0])
|
||||||
: null,
|
: null,
|
||||||
},
|
},
|
||||||
commitToHistory: true,
|
commitToHistory: true,
|
||||||
|
|||||||
@@ -128,7 +128,11 @@ export const actionPasteStyles = register({
|
|||||||
element.id === newElement.containerId,
|
element.id === newElement.containerId,
|
||||||
) || null;
|
) || null;
|
||||||
}
|
}
|
||||||
redrawTextBoundingBox(newElement, container);
|
redrawTextBoundingBox(
|
||||||
|
newElement,
|
||||||
|
container,
|
||||||
|
app.scene.getNonDeletedElementsMap(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -83,6 +83,6 @@ export { actionToggleObjectsSnapMode } from "./actionToggleObjectsSnapMode";
|
|||||||
|
|
||||||
export { actionToggleStats } from "./actionToggleStats";
|
export { actionToggleStats } from "./actionToggleStats";
|
||||||
export { actionUnbindText, actionBindText } from "./actionBoundText";
|
export { actionUnbindText, actionBindText } from "./actionBoundText";
|
||||||
export { actionLink } from "../element/Hyperlink";
|
export { actionLink } from "./actionLink";
|
||||||
export { actionToggleElementLock } from "./actionElementLock";
|
export { actionToggleElementLock } from "./actionElementLock";
|
||||||
export { actionToggleLinearEditor } from "./actionLinearEditor";
|
export { actionToggleLinearEditor } from "./actionLinearEditor";
|
||||||
|
|||||||
@@ -7,9 +7,7 @@ import {
|
|||||||
EXPORT_SCALES,
|
EXPORT_SCALES,
|
||||||
THEME,
|
THEME,
|
||||||
} from "./constants";
|
} from "./constants";
|
||||||
import { t } from "./i18n";
|
|
||||||
import { AppState, NormalizedZoomValue } from "./types";
|
import { AppState, NormalizedZoomValue } from "./types";
|
||||||
import { getDateTime } from "./utils";
|
|
||||||
|
|
||||||
const defaultExportScale = EXPORT_SCALES.includes(devicePixelRatio)
|
const defaultExportScale = EXPORT_SCALES.includes(devicePixelRatio)
|
||||||
? devicePixelRatio
|
? devicePixelRatio
|
||||||
@@ -65,7 +63,7 @@ export const getDefaultAppState = (): Omit<
|
|||||||
isRotating: false,
|
isRotating: false,
|
||||||
lastPointerDownWith: "mouse",
|
lastPointerDownWith: "mouse",
|
||||||
multiElement: null,
|
multiElement: null,
|
||||||
name: `${t("labels.untitled")}-${getDateTime()}`,
|
name: null,
|
||||||
contextMenu: null,
|
contextMenu: null,
|
||||||
openMenu: null,
|
openMenu: null,
|
||||||
openPopup: null,
|
openPopup: null,
|
||||||
|
|||||||
@@ -270,6 +270,7 @@ import {
|
|||||||
updateStable,
|
updateStable,
|
||||||
addEventListener,
|
addEventListener,
|
||||||
normalizeEOL,
|
normalizeEOL,
|
||||||
|
getDateTime,
|
||||||
} from "../utils";
|
} from "../utils";
|
||||||
import {
|
import {
|
||||||
createSrcDoc,
|
createSrcDoc,
|
||||||
@@ -325,9 +326,7 @@ import {
|
|||||||
showHyperlinkTooltip,
|
showHyperlinkTooltip,
|
||||||
hideHyperlinkToolip,
|
hideHyperlinkToolip,
|
||||||
Hyperlink,
|
Hyperlink,
|
||||||
isPointHittingLink,
|
} from "../components/hyperlink/Hyperlink";
|
||||||
isPointHittingLinkIcon,
|
|
||||||
} from "../element/Hyperlink";
|
|
||||||
import { isLocalLink, normalizeLink, toValidURL } from "../data/url";
|
import { isLocalLink, normalizeLink, toValidURL } from "../data/url";
|
||||||
import { shouldShowBoundingBox } from "../element/transformHandles";
|
import { shouldShowBoundingBox } from "../element/transformHandles";
|
||||||
import { actionUnlockAllElements } from "../actions/actionElementLock";
|
import { actionUnlockAllElements } from "../actions/actionElementLock";
|
||||||
@@ -409,6 +408,10 @@ import { withBatchedUpdates, withBatchedUpdatesThrottled } from "../reactUtils";
|
|||||||
import { getRenderOpacity } from "../renderer/renderElement";
|
import { getRenderOpacity } from "../renderer/renderElement";
|
||||||
import { textWysiwyg } from "../element/textWysiwyg";
|
import { textWysiwyg } from "../element/textWysiwyg";
|
||||||
import { isOverScrollBars } from "../scene/scrollbars";
|
import { isOverScrollBars } from "../scene/scrollbars";
|
||||||
|
import {
|
||||||
|
isPointHittingLink,
|
||||||
|
isPointHittingLinkIcon,
|
||||||
|
} from "./hyperlink/helpers";
|
||||||
|
|
||||||
const AppContext = React.createContext<AppClassProperties>(null!);
|
const AppContext = React.createContext<AppClassProperties>(null!);
|
||||||
const AppPropsContext = React.createContext<AppProps>(null!);
|
const AppPropsContext = React.createContext<AppProps>(null!);
|
||||||
@@ -619,7 +622,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
gridModeEnabled = false,
|
gridModeEnabled = false,
|
||||||
objectsSnapModeEnabled = false,
|
objectsSnapModeEnabled = false,
|
||||||
theme = defaultAppState.theme,
|
theme = defaultAppState.theme,
|
||||||
name = defaultAppState.name,
|
name = `${t("labels.untitled")}-${getDateTime()}`,
|
||||||
} = props;
|
} = props;
|
||||||
this.state = {
|
this.state = {
|
||||||
...defaultAppState,
|
...defaultAppState,
|
||||||
@@ -662,6 +665,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
getSceneElements: this.getSceneElements,
|
getSceneElements: this.getSceneElements,
|
||||||
getAppState: () => this.state,
|
getAppState: () => this.state,
|
||||||
getFiles: () => this.files,
|
getFiles: () => this.files,
|
||||||
|
getName: this.getName,
|
||||||
registerAction: (action: Action) => {
|
registerAction: (action: Action) => {
|
||||||
this.actionManager.registerAction(action);
|
this.actionManager.registerAction(action);
|
||||||
},
|
},
|
||||||
@@ -951,6 +955,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
normalizedWidth,
|
normalizedWidth,
|
||||||
normalizedHeight,
|
normalizedHeight,
|
||||||
this.state,
|
this.state,
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
const hasBeenInitialized = this.initializedEmbeds.has(el.id);
|
const hasBeenInitialized = this.initializedEmbeds.has(el.id);
|
||||||
|
|
||||||
@@ -1285,6 +1290,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
scrollY: this.state.scrollY,
|
scrollY: this.state.scrollY,
|
||||||
zoom: this.state.zoom,
|
zoom: this.state.zoom,
|
||||||
},
|
},
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
// if frame not visible, don't render its name
|
// if frame not visible, don't render its name
|
||||||
@@ -1410,6 +1416,13 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private toggleOverscrollBehavior(event: React.PointerEvent) {
|
||||||
|
// when pointer inside editor, disable overscroll behavior to prevent
|
||||||
|
// panning to trigger history back/forward on MacOS Chrome
|
||||||
|
document.documentElement.style.overscrollBehaviorX =
|
||||||
|
event.type === "pointerenter" ? "none" : "auto";
|
||||||
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const selectedElements = this.scene.getSelectedElements(this.state);
|
const selectedElements = this.scene.getSelectedElements(this.state);
|
||||||
const { renderTopRightUI, renderCustomStats } = this.props;
|
const { renderTopRightUI, renderCustomStats } = this.props;
|
||||||
@@ -1463,6 +1476,8 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
onKeyDown={
|
onKeyDown={
|
||||||
this.props.handleKeyboardGlobally ? undefined : this.onKeyDown
|
this.props.handleKeyboardGlobally ? undefined : this.onKeyDown
|
||||||
}
|
}
|
||||||
|
onPointerEnter={this.toggleOverscrollBehavior}
|
||||||
|
onPointerLeave={this.toggleOverscrollBehavior}
|
||||||
>
|
>
|
||||||
<AppContext.Provider value={this}>
|
<AppContext.Provider value={this}>
|
||||||
<AppPropsContext.Provider value={this.props}>
|
<AppPropsContext.Provider value={this.props}>
|
||||||
@@ -1525,6 +1540,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
<Hyperlink
|
<Hyperlink
|
||||||
key={firstSelectedElement.id}
|
key={firstSelectedElement.id}
|
||||||
element={firstSelectedElement}
|
element={firstSelectedElement}
|
||||||
|
elementsMap={allElementsMap}
|
||||||
setAppState={this.setAppState}
|
setAppState={this.setAppState}
|
||||||
onLinkOpen={this.props.onLinkOpen}
|
onLinkOpen={this.props.onLinkOpen}
|
||||||
setToast={this.setToast}
|
setToast={this.setToast}
|
||||||
@@ -1538,6 +1554,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
isMagicFrameElement(firstSelectedElement) && (
|
isMagicFrameElement(firstSelectedElement) && (
|
||||||
<ElementCanvasButtons
|
<ElementCanvasButtons
|
||||||
element={firstSelectedElement}
|
element={firstSelectedElement}
|
||||||
|
elementsMap={elementsMap}
|
||||||
>
|
>
|
||||||
<ElementCanvasButton
|
<ElementCanvasButton
|
||||||
title={t("labels.convertToCode")}
|
title={t("labels.convertToCode")}
|
||||||
@@ -1558,6 +1575,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
?.status === "done" && (
|
?.status === "done" && (
|
||||||
<ElementCanvasButtons
|
<ElementCanvasButtons
|
||||||
element={firstSelectedElement}
|
element={firstSelectedElement}
|
||||||
|
elementsMap={elementsMap}
|
||||||
>
|
>
|
||||||
<ElementCanvasButton
|
<ElementCanvasButton
|
||||||
title={t("labels.copySource")}
|
title={t("labels.copySource")}
|
||||||
@@ -1725,7 +1743,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.files,
|
this.files,
|
||||||
{
|
{
|
||||||
exportBackground: this.state.exportBackground,
|
exportBackground: this.state.exportBackground,
|
||||||
name: this.state.name,
|
name: this.getName(),
|
||||||
viewBackgroundColor: this.state.viewBackgroundColor,
|
viewBackgroundColor: this.state.viewBackgroundColor,
|
||||||
exportingFrame: opts.exportingFrame,
|
exportingFrame: opts.exportingFrame,
|
||||||
},
|
},
|
||||||
@@ -2124,7 +2142,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
let gridSize = actionResult?.appState?.gridSize || null;
|
let gridSize = actionResult?.appState?.gridSize || null;
|
||||||
const theme =
|
const theme =
|
||||||
actionResult?.appState?.theme || this.props.theme || THEME.LIGHT;
|
actionResult?.appState?.theme || this.props.theme || THEME.LIGHT;
|
||||||
let name = actionResult?.appState?.name ?? this.state.name;
|
const name = actionResult?.appState?.name ?? this.state.name;
|
||||||
const errorMessage =
|
const errorMessage =
|
||||||
actionResult?.appState?.errorMessage ?? this.state.errorMessage;
|
actionResult?.appState?.errorMessage ?? this.state.errorMessage;
|
||||||
if (typeof this.props.viewModeEnabled !== "undefined") {
|
if (typeof this.props.viewModeEnabled !== "undefined") {
|
||||||
@@ -2139,10 +2157,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
gridSize = this.props.gridModeEnabled ? GRID_SIZE : null;
|
gridSize = this.props.gridModeEnabled ? GRID_SIZE : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof this.props.name !== "undefined") {
|
|
||||||
name = this.props.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
editingElement =
|
editingElement =
|
||||||
editingElement || actionResult.appState?.editingElement || null;
|
editingElement || actionResult.appState?.editingElement || null;
|
||||||
|
|
||||||
@@ -2455,6 +2469,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
isSomeElementSelected.clearCache();
|
isSomeElementSelected.clearCache();
|
||||||
selectGroupsForSelectedElements.clearCache();
|
selectGroupsForSelectedElements.clearCache();
|
||||||
touchTimeout = 0;
|
touchTimeout = 0;
|
||||||
|
document.documentElement.style.overscrollBehaviorX = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
private onResize = withBatchedUpdates(() => {
|
private onResize = withBatchedUpdates(() => {
|
||||||
@@ -2591,10 +2606,10 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
|
|
||||||
componentDidUpdate(prevProps: AppProps, prevState: AppState) {
|
componentDidUpdate(prevProps: AppProps, prevState: AppState) {
|
||||||
this.updateEmbeddables();
|
this.updateEmbeddables();
|
||||||
if (
|
const elements = this.scene.getElementsIncludingDeleted();
|
||||||
!this.state.showWelcomeScreen &&
|
const elementsMap = this.scene.getNonDeletedElementsMap();
|
||||||
!this.scene.getElementsIncludingDeleted().length
|
|
||||||
) {
|
if (!this.state.showWelcomeScreen && !elements.length) {
|
||||||
this.setState({ showWelcomeScreen: true });
|
this.setState({ showWelcomeScreen: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2699,12 +2714,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.props.name && prevProps.name !== this.props.name) {
|
|
||||||
this.setState({
|
|
||||||
name: this.props.name,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.excalidrawContainerRef.current?.classList.toggle(
|
this.excalidrawContainerRef.current?.classList.toggle(
|
||||||
"theme--dark",
|
"theme--dark",
|
||||||
this.state.theme === "dark",
|
this.state.theme === "dark",
|
||||||
@@ -2754,27 +2763,21 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
||||||
multiElement,
|
multiElement,
|
||||||
-1,
|
-1,
|
||||||
|
elementsMap,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.history.record(this.state, this.scene.getElementsIncludingDeleted());
|
this.history.record(this.state, elements);
|
||||||
|
|
||||||
// Do not notify consumers if we're still loading the scene. Among other
|
// Do not notify consumers if we're still loading the scene. Among other
|
||||||
// potential issues, this fixes a case where the tab isn't focused during
|
// potential issues, this fixes a case where the tab isn't focused during
|
||||||
// init, which would trigger onChange with empty elements, which would then
|
// init, which would trigger onChange with empty elements, which would then
|
||||||
// override whatever is in localStorage currently.
|
// override whatever is in localStorage currently.
|
||||||
if (!this.state.isLoading) {
|
if (!this.state.isLoading) {
|
||||||
this.props.onChange?.(
|
this.props.onChange?.(elements, this.state, this.files);
|
||||||
this.scene.getElementsIncludingDeleted(),
|
this.onChangeEmitter.trigger(elements, this.state, this.files);
|
||||||
this.state,
|
|
||||||
this.files,
|
|
||||||
);
|
|
||||||
this.onChangeEmitter.trigger(
|
|
||||||
this.scene.getElementsIncludingDeleted(),
|
|
||||||
this.state,
|
|
||||||
this.files,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3124,7 +3127,11 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
newElement,
|
newElement,
|
||||||
this.scene.getElementsMapIncludingDeleted(),
|
this.scene.getElementsMapIncludingDeleted(),
|
||||||
);
|
);
|
||||||
redrawTextBoundingBox(newElement, container);
|
redrawTextBoundingBox(
|
||||||
|
newElement,
|
||||||
|
container,
|
||||||
|
this.scene.getElementsMapIncludingDeleted(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -3834,7 +3841,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
y: element.y + offsetY,
|
y: element.y + offsetY,
|
||||||
});
|
});
|
||||||
|
|
||||||
updateBoundElements(element, {
|
updateBoundElements(element, this.scene.getNonDeletedElementsMap(), {
|
||||||
simultaneouslyUpdated: selectedElements,
|
simultaneouslyUpdated: selectedElements,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -3857,7 +3864,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.setState({
|
this.setState({
|
||||||
editingLinearElement: new LinearElementEditor(
|
editingLinearElement: new LinearElementEditor(
|
||||||
selectedElement,
|
selectedElement,
|
||||||
this.scene,
|
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -4008,9 +4014,14 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
if (isArrowKey(event.key)) {
|
if (isArrowKey(event.key)) {
|
||||||
const selectedElements = this.scene.getSelectedElements(this.state);
|
const selectedElements = this.scene.getSelectedElements(this.state);
|
||||||
|
const elementsMap = this.scene.getNonDeletedElementsMap();
|
||||||
isBindingEnabled(this.state)
|
isBindingEnabled(this.state)
|
||||||
? bindOrUnbindSelectedElements(selectedElements)
|
? bindOrUnbindSelectedElements(
|
||||||
: unbindLinearElements(selectedElements);
|
selectedElements,
|
||||||
|
this.scene.getNonDeletedElements(),
|
||||||
|
elementsMap,
|
||||||
|
)
|
||||||
|
: unbindLinearElements(selectedElements, elementsMap);
|
||||||
this.setState({ suggestedBindings: [] });
|
this.setState({ suggestedBindings: [] });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -4112,6 +4123,14 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
return gesture.pointers.size >= 2;
|
return gesture.pointers.size >= 2;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public getName = () => {
|
||||||
|
return (
|
||||||
|
this.state.name ||
|
||||||
|
this.props.name ||
|
||||||
|
`${t("labels.untitled")}-${getDateTime()}`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
// fires only on Safari
|
// fires only on Safari
|
||||||
private onGestureStart = withBatchedUpdates((event: GestureEvent) => {
|
private onGestureStart = withBatchedUpdates((event: GestureEvent) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -4183,20 +4202,21 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
isExistingElement?: boolean;
|
isExistingElement?: boolean;
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
|
const elementsMap = this.scene.getElementsMapIncludingDeleted();
|
||||||
|
|
||||||
const updateElement = (
|
const updateElement = (
|
||||||
text: string,
|
text: string,
|
||||||
originalText: string,
|
originalText: string,
|
||||||
isDeleted: boolean,
|
isDeleted: boolean,
|
||||||
) => {
|
) => {
|
||||||
this.scene.replaceAllElements([
|
this.scene.replaceAllElements([
|
||||||
|
// Not sure why we include deleted elements as well hence using deleted elements map
|
||||||
...this.scene.getElementsIncludingDeleted().map((_element) => {
|
...this.scene.getElementsIncludingDeleted().map((_element) => {
|
||||||
if (_element.id === element.id && isTextElement(_element)) {
|
if (_element.id === element.id && isTextElement(_element)) {
|
||||||
return updateTextElement(
|
return updateTextElement(
|
||||||
_element,
|
_element,
|
||||||
getContainerElement(
|
getContainerElement(_element, elementsMap),
|
||||||
_element,
|
elementsMap,
|
||||||
this.scene.getElementsMapIncludingDeleted(),
|
|
||||||
),
|
|
||||||
{
|
{
|
||||||
text,
|
text,
|
||||||
isDeleted,
|
isDeleted,
|
||||||
@@ -4228,7 +4248,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
onChange: withBatchedUpdates((text) => {
|
onChange: withBatchedUpdates((text) => {
|
||||||
updateElement(text, text, false);
|
updateElement(text, text, false);
|
||||||
if (isNonDeletedElement(element)) {
|
if (isNonDeletedElement(element)) {
|
||||||
updateBoundElements(element);
|
updateBoundElements(element, elementsMap);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
onSubmit: withBatchedUpdates(({ text, viaKeyboard, originalText }) => {
|
onSubmit: withBatchedUpdates(({ text, viaKeyboard, originalText }) => {
|
||||||
@@ -4367,6 +4387,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
!(isTextElement(element) && element.containerId)),
|
!(isTextElement(element) && element.containerId)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const elementsMap = this.scene.getNonDeletedElementsMap();
|
||||||
return getElementsAtPosition(elements, (element) =>
|
return getElementsAtPosition(elements, (element) =>
|
||||||
hitTest(
|
hitTest(
|
||||||
element,
|
element,
|
||||||
@@ -4374,7 +4395,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.frameNameBoundsCache,
|
this.frameNameBoundsCache,
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
this.scene.getNonDeletedElementsMap(),
|
elementsMap,
|
||||||
),
|
),
|
||||||
).filter((element) => {
|
).filter((element) => {
|
||||||
// hitting a frame's element from outside the frame is not considered a hit
|
// hitting a frame's element from outside the frame is not considered a hit
|
||||||
@@ -4382,7 +4403,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
return containingFrame &&
|
return containingFrame &&
|
||||||
this.state.frameRendering.enabled &&
|
this.state.frameRendering.enabled &&
|
||||||
this.state.frameRendering.clip
|
this.state.frameRendering.clip
|
||||||
? isCursorInFrame({ x, y }, containingFrame)
|
? isCursorInFrame({ x, y }, containingFrame, elementsMap)
|
||||||
: true;
|
: true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -4564,10 +4585,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
) {
|
) {
|
||||||
this.history.resumeRecording();
|
this.history.resumeRecording();
|
||||||
this.setState({
|
this.setState({
|
||||||
editingLinearElement: new LinearElementEditor(
|
editingLinearElement: new LinearElementEditor(selectedElements[0]),
|
||||||
selectedElements[0],
|
|
||||||
this.scene,
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
} else if (
|
} else if (
|
||||||
@@ -4627,6 +4645,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.state,
|
this.state,
|
||||||
sceneX,
|
sceneX,
|
||||||
sceneY,
|
sceneY,
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (container) {
|
if (container) {
|
||||||
@@ -4638,6 +4657,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.state,
|
this.state,
|
||||||
this.frameNameBoundsCache,
|
this.frameNameBoundsCache,
|
||||||
[sceneX, sceneY],
|
[sceneX, sceneY],
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
const midPoint = getContainerCenter(
|
const midPoint = getContainerCenter(
|
||||||
@@ -4678,6 +4698,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
index <= hitElementIndex &&
|
index <= hitElementIndex &&
|
||||||
isPointHittingLink(
|
isPointHittingLink(
|
||||||
element,
|
element,
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
this.state,
|
this.state,
|
||||||
[scenePointer.x, scenePointer.y],
|
[scenePointer.x, scenePointer.y],
|
||||||
this.device.editor.isMobile,
|
this.device.editor.isMobile,
|
||||||
@@ -4708,8 +4729,10 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.lastPointerDownEvent!,
|
this.lastPointerDownEvent!,
|
||||||
this.state,
|
this.state,
|
||||||
);
|
);
|
||||||
|
const elementsMap = this.scene.getNonDeletedElementsMap();
|
||||||
const lastPointerDownHittingLinkIcon = isPointHittingLink(
|
const lastPointerDownHittingLinkIcon = isPointHittingLink(
|
||||||
this.hitLinkElement,
|
this.hitLinkElement,
|
||||||
|
elementsMap,
|
||||||
this.state,
|
this.state,
|
||||||
[lastPointerDownCoords.x, lastPointerDownCoords.y],
|
[lastPointerDownCoords.x, lastPointerDownCoords.y],
|
||||||
this.device.editor.isMobile,
|
this.device.editor.isMobile,
|
||||||
@@ -4720,6 +4743,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
);
|
);
|
||||||
const lastPointerUpHittingLinkIcon = isPointHittingLink(
|
const lastPointerUpHittingLinkIcon = isPointHittingLink(
|
||||||
this.hitLinkElement,
|
this.hitLinkElement,
|
||||||
|
elementsMap,
|
||||||
this.state,
|
this.state,
|
||||||
[lastPointerUpCoords.x, lastPointerUpCoords.y],
|
[lastPointerUpCoords.x, lastPointerUpCoords.y],
|
||||||
this.device.editor.isMobile,
|
this.device.editor.isMobile,
|
||||||
@@ -4756,10 +4780,11 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
}) => {
|
}) => {
|
||||||
|
const elementsMap = this.scene.getNonDeletedElementsMap();
|
||||||
const frames = this.scene
|
const frames = this.scene
|
||||||
.getNonDeletedFramesLikes()
|
.getNonDeletedFramesLikes()
|
||||||
.filter((frame): frame is ExcalidrawFrameLikeElement =>
|
.filter((frame): frame is ExcalidrawFrameLikeElement =>
|
||||||
isCursorInFrame(sceneCoords, frame),
|
isCursorInFrame(sceneCoords, frame, elementsMap),
|
||||||
);
|
);
|
||||||
|
|
||||||
return frames.length ? frames[frames.length - 1] : null;
|
return frames.length ? frames[frames.length - 1] : null;
|
||||||
@@ -4863,6 +4888,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
y: scenePointerY,
|
y: scenePointerY,
|
||||||
},
|
},
|
||||||
event,
|
event,
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.setState((prevState) => {
|
this.setState((prevState) => {
|
||||||
@@ -4902,6 +4928,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
scenePointerX,
|
scenePointerX,
|
||||||
scenePointerY,
|
scenePointerY,
|
||||||
this.state,
|
this.state,
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -5052,6 +5079,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
scenePointerY,
|
scenePointerY,
|
||||||
this.state.zoom,
|
this.state.zoom,
|
||||||
event.pointerType,
|
event.pointerType,
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
if (
|
if (
|
||||||
elementWithTransformHandleType &&
|
elementWithTransformHandleType &&
|
||||||
@@ -5099,7 +5127,11 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
!this.state.selectedElementIds[this.hitLinkElement.id]
|
!this.state.selectedElementIds[this.hitLinkElement.id]
|
||||||
) {
|
) {
|
||||||
setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER);
|
setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER);
|
||||||
showHyperlinkTooltip(this.hitLinkElement, this.state);
|
showHyperlinkTooltip(
|
||||||
|
this.hitLinkElement,
|
||||||
|
this.state,
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
hideHyperlinkToolip();
|
hideHyperlinkToolip();
|
||||||
if (
|
if (
|
||||||
@@ -5277,10 +5309,12 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
scenePointerX: number,
|
scenePointerX: number,
|
||||||
scenePointerY: number,
|
scenePointerY: number,
|
||||||
) {
|
) {
|
||||||
|
const elementsMap = this.scene.getNonDeletedElementsMap();
|
||||||
|
|
||||||
const element = LinearElementEditor.getElement(
|
const element = LinearElementEditor.getElement(
|
||||||
linearElementEditor.elementId,
|
linearElementEditor.elementId,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
const elementsMap = this.scene.getNonDeletedElementsMap();
|
|
||||||
const boundTextElement = getBoundTextElement(element, elementsMap);
|
const boundTextElement = getBoundTextElement(element, elementsMap);
|
||||||
|
|
||||||
if (!element) {
|
if (!element) {
|
||||||
@@ -5295,10 +5329,12 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.state,
|
this.state,
|
||||||
this.frameNameBoundsCache,
|
this.frameNameBoundsCache,
|
||||||
[scenePointerX, scenePointerY],
|
[scenePointerX, scenePointerY],
|
||||||
|
elementsMap,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
hoverPointIndex = LinearElementEditor.getPointIndexUnderCursor(
|
hoverPointIndex = LinearElementEditor.getPointIndexUnderCursor(
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
this.state.zoom,
|
this.state.zoom,
|
||||||
scenePointerX,
|
scenePointerX,
|
||||||
scenePointerY,
|
scenePointerY,
|
||||||
@@ -5728,10 +5764,12 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
if (
|
if (
|
||||||
clicklength < 300 &&
|
clicklength < 300 &&
|
||||||
isIframeLikeElement(this.hitLinkElement) &&
|
isIframeLikeElement(this.hitLinkElement) &&
|
||||||
!isPointHittingLinkIcon(this.hitLinkElement, this.state, [
|
!isPointHittingLinkIcon(
|
||||||
scenePointer.x,
|
this.hitLinkElement,
|
||||||
scenePointer.y,
|
this.scene.getNonDeletedElementsMap(),
|
||||||
])
|
this.state,
|
||||||
|
[scenePointer.x, scenePointer.y],
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
this.handleEmbeddableCenterClick(this.hitLinkElement);
|
this.handleEmbeddableCenterClick(this.hitLinkElement);
|
||||||
} else {
|
} else {
|
||||||
@@ -6029,7 +6067,9 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
): boolean => {
|
): boolean => {
|
||||||
if (this.state.activeTool.type === "selection") {
|
if (this.state.activeTool.type === "selection") {
|
||||||
const elements = this.scene.getNonDeletedElements();
|
const elements = this.scene.getNonDeletedElements();
|
||||||
|
const elementsMap = this.scene.getNonDeletedElementsMap();
|
||||||
const selectedElements = this.scene.getSelectedElements(this.state);
|
const selectedElements = this.scene.getSelectedElements(this.state);
|
||||||
|
|
||||||
if (selectedElements.length === 1 && !this.state.editingLinearElement) {
|
if (selectedElements.length === 1 && !this.state.editingLinearElement) {
|
||||||
const elementWithTransformHandleType =
|
const elementWithTransformHandleType =
|
||||||
getElementWithTransformHandleType(
|
getElementWithTransformHandleType(
|
||||||
@@ -6039,6 +6079,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
pointerDownState.origin.y,
|
pointerDownState.origin.y,
|
||||||
this.state.zoom,
|
this.state.zoom,
|
||||||
event.pointerType,
|
event.pointerType,
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
if (elementWithTransformHandleType != null) {
|
if (elementWithTransformHandleType != null) {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -6062,6 +6103,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
getResizeOffsetXY(
|
getResizeOffsetXY(
|
||||||
pointerDownState.resize.handleType,
|
pointerDownState.resize.handleType,
|
||||||
selectedElements,
|
selectedElements,
|
||||||
|
elementsMap,
|
||||||
pointerDownState.origin.x,
|
pointerDownState.origin.x,
|
||||||
pointerDownState.origin.y,
|
pointerDownState.origin.y,
|
||||||
),
|
),
|
||||||
@@ -6086,7 +6128,8 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.history,
|
this.history,
|
||||||
pointerDownState.origin,
|
pointerDownState.origin,
|
||||||
linearElementEditor,
|
linearElementEditor,
|
||||||
this.scene.getNonDeletedElementsMap(),
|
this.scene.getNonDeletedElements(),
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
if (ret.hitElement) {
|
if (ret.hitElement) {
|
||||||
pointerDownState.hit.element = ret.hitElement;
|
pointerDownState.hit.element = ret.hitElement;
|
||||||
@@ -6342,6 +6385,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.state,
|
this.state,
|
||||||
sceneX,
|
sceneX,
|
||||||
sceneY,
|
sceneY,
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (hasBoundTextElement(element)) {
|
if (hasBoundTextElement(element)) {
|
||||||
@@ -6422,7 +6466,8 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
|
|
||||||
const boundElement = getHoveredElementForBinding(
|
const boundElement = getHoveredElementForBinding(
|
||||||
pointerDownState.origin,
|
pointerDownState.origin,
|
||||||
this.scene,
|
this.scene.getNonDeletedElements(),
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
this.scene.addNewElement(element);
|
this.scene.addNewElement(element);
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -6690,7 +6735,8 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
});
|
});
|
||||||
const boundElement = getHoveredElementForBinding(
|
const boundElement = getHoveredElementForBinding(
|
||||||
pointerDownState.origin,
|
pointerDownState.origin,
|
||||||
this.scene,
|
this.scene.getNonDeletedElements(),
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.scene.addNewElement(element);
|
this.scene.addNewElement(element);
|
||||||
@@ -6836,6 +6882,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.scene.getNonDeletedElements(),
|
this.scene.getNonDeletedElements(),
|
||||||
selectedElements,
|
selectedElements,
|
||||||
this.state,
|
this.state,
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -6859,6 +6906,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.scene.getNonDeletedElements(),
|
this.scene.getNonDeletedElements(),
|
||||||
selectedElements,
|
selectedElements,
|
||||||
this.state,
|
this.state,
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -6958,6 +7006,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const elementsMap = this.scene.getNonDeletedElementsMap();
|
||||||
|
|
||||||
if (this.state.selectedLinearElement) {
|
if (this.state.selectedLinearElement) {
|
||||||
const linearElementEditor =
|
const linearElementEditor =
|
||||||
@@ -6968,6 +7017,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.state.selectedLinearElement,
|
this.state.selectedLinearElement,
|
||||||
pointerCoords,
|
pointerCoords,
|
||||||
this.state,
|
this.state,
|
||||||
|
elementsMap,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
const ret = LinearElementEditor.addMidpoint(
|
const ret = LinearElementEditor.addMidpoint(
|
||||||
@@ -6975,6 +7025,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
pointerCoords,
|
pointerCoords,
|
||||||
this.state,
|
this.state,
|
||||||
!event[KEYS.CTRL_OR_CMD],
|
!event[KEYS.CTRL_OR_CMD],
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
return;
|
return;
|
||||||
@@ -7133,10 +7184,11 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.maybeCacheReferenceSnapPoints(event, selectedElements);
|
this.maybeCacheReferenceSnapPoints(event, selectedElements);
|
||||||
|
|
||||||
const { snapOffset, snapLines } = snapDraggedElements(
|
const { snapOffset, snapLines } = snapDraggedElements(
|
||||||
getSelectedElements(originalElements, this.state),
|
originalElements,
|
||||||
dragOffset,
|
dragOffset,
|
||||||
this.state,
|
this.state,
|
||||||
event,
|
event,
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.setState({ snapLines });
|
this.setState({ snapLines });
|
||||||
@@ -7320,6 +7372,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
event,
|
event,
|
||||||
this.state,
|
this.state,
|
||||||
this.setState.bind(this),
|
this.setState.bind(this),
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
// regular box-select
|
// regular box-select
|
||||||
} else {
|
} else {
|
||||||
@@ -7350,6 +7403,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
const elementsWithinSelection = getElementsWithinSelection(
|
const elementsWithinSelection = getElementsWithinSelection(
|
||||||
elements,
|
elements,
|
||||||
draggingElement,
|
draggingElement,
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.setState((prevState) => {
|
this.setState((prevState) => {
|
||||||
@@ -7392,10 +7446,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
selectedLinearElement:
|
selectedLinearElement:
|
||||||
elementsWithinSelection.length === 1 &&
|
elementsWithinSelection.length === 1 &&
|
||||||
isLinearElement(elementsWithinSelection[0])
|
isLinearElement(elementsWithinSelection[0])
|
||||||
? new LinearElementEditor(
|
? new LinearElementEditor(elementsWithinSelection[0])
|
||||||
elementsWithinSelection[0],
|
|
||||||
this.scene,
|
|
||||||
)
|
|
||||||
: null,
|
: null,
|
||||||
showHyperlinkPopup:
|
showHyperlinkPopup:
|
||||||
elementsWithinSelection.length === 1 &&
|
elementsWithinSelection.length === 1 &&
|
||||||
@@ -7481,7 +7532,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.setState({
|
this.setState({
|
||||||
selectedElementsAreBeingDragged: false,
|
selectedElementsAreBeingDragged: false,
|
||||||
});
|
});
|
||||||
|
const elementsMap = this.scene.getNonDeletedElementsMap();
|
||||||
// Handle end of dragging a point of a linear element, might close a loop
|
// Handle end of dragging a point of a linear element, might close a loop
|
||||||
// and sets binding element
|
// and sets binding element
|
||||||
if (this.state.editingLinearElement) {
|
if (this.state.editingLinearElement) {
|
||||||
@@ -7496,6 +7547,8 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
childEvent,
|
childEvent,
|
||||||
this.state.editingLinearElement,
|
this.state.editingLinearElement,
|
||||||
this.state,
|
this.state,
|
||||||
|
this.scene.getNonDeletedElements(),
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
if (editingLinearElement !== this.state.editingLinearElement) {
|
if (editingLinearElement !== this.state.editingLinearElement) {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -7519,6 +7572,8 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
childEvent,
|
childEvent,
|
||||||
this.state.selectedLinearElement,
|
this.state.selectedLinearElement,
|
||||||
this.state,
|
this.state,
|
||||||
|
this.scene.getNonDeletedElements(),
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { startBindingElement, endBindingElement } =
|
const { startBindingElement, endBindingElement } =
|
||||||
@@ -7529,6 +7584,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
element,
|
element,
|
||||||
startBindingElement,
|
startBindingElement,
|
||||||
endBindingElement,
|
endBindingElement,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7668,6 +7724,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.state,
|
this.state,
|
||||||
this.scene,
|
this.scene,
|
||||||
pointerCoords,
|
pointerCoords,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.setState({ suggestedBindings: [], startBoundElement: null });
|
this.setState({ suggestedBindings: [], startBoundElement: null });
|
||||||
@@ -7685,10 +7742,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
},
|
},
|
||||||
prevState,
|
prevState,
|
||||||
),
|
),
|
||||||
selectedLinearElement: new LinearElementEditor(
|
selectedLinearElement: new LinearElementEditor(draggingElement),
|
||||||
draggingElement,
|
|
||||||
this.scene,
|
|
||||||
),
|
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
this.setState((prevState) => ({
|
this.setState((prevState) => ({
|
||||||
@@ -7738,7 +7792,13 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
const frame = getContainingFrame(linearElement);
|
const frame = getContainingFrame(linearElement);
|
||||||
|
|
||||||
if (frame && linearElement) {
|
if (frame && linearElement) {
|
||||||
if (!elementOverlapsWithFrame(linearElement, frame)) {
|
if (
|
||||||
|
!elementOverlapsWithFrame(
|
||||||
|
linearElement,
|
||||||
|
frame,
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
|
)
|
||||||
|
) {
|
||||||
// remove the linear element from all groups
|
// remove the linear element from all groups
|
||||||
// before removing it from the frame as well
|
// before removing it from the frame as well
|
||||||
mutateElement(linearElement, {
|
mutateElement(linearElement, {
|
||||||
@@ -7849,6 +7909,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
const elementsInsideFrame = getElementsInNewFrame(
|
const elementsInsideFrame = getElementsInNewFrame(
|
||||||
this.scene.getElementsIncludingDeleted(),
|
this.scene.getElementsIncludingDeleted(),
|
||||||
draggingElement,
|
draggingElement,
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.scene.replaceAllElements(
|
this.scene.replaceAllElements(
|
||||||
@@ -7899,6 +7960,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.scene.getElementsIncludingDeleted(),
|
this.scene.getElementsIncludingDeleted(),
|
||||||
frame,
|
frame,
|
||||||
this.state,
|
this.state,
|
||||||
|
elementsMap,
|
||||||
),
|
),
|
||||||
frame,
|
frame,
|
||||||
this,
|
this,
|
||||||
@@ -7920,10 +7982,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
// the one we've hit
|
// the one we've hit
|
||||||
if (selectedELements.length === 1) {
|
if (selectedELements.length === 1) {
|
||||||
this.setState({
|
this.setState({
|
||||||
selectedLinearElement: new LinearElementEditor(
|
selectedLinearElement: new LinearElementEditor(hitElement),
|
||||||
hitElement,
|
|
||||||
this.scene,
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8036,10 +8095,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
selectedLinearElement:
|
selectedLinearElement:
|
||||||
newSelectedElements.length === 1 &&
|
newSelectedElements.length === 1 &&
|
||||||
isLinearElement(newSelectedElements[0])
|
isLinearElement(newSelectedElements[0])
|
||||||
? new LinearElementEditor(
|
? new LinearElementEditor(newSelectedElements[0])
|
||||||
newSelectedElements[0],
|
|
||||||
this.scene,
|
|
||||||
)
|
|
||||||
: prevState.selectedLinearElement,
|
: prevState.selectedLinearElement,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -8113,7 +8169,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
// Don't set `selectedLinearElement` if its same as the hitElement, this is mainly to prevent resetting the `hoverPointIndex` to -1.
|
// Don't set `selectedLinearElement` if its same as the hitElement, this is mainly to prevent resetting the `hoverPointIndex` to -1.
|
||||||
// Future we should update the API to take care of setting the correct `hoverPointIndex` when initialized
|
// Future we should update the API to take care of setting the correct `hoverPointIndex` when initialized
|
||||||
prevState.selectedLinearElement?.elementId !== hitElement.id
|
prevState.selectedLinearElement?.elementId !== hitElement.id
|
||||||
? new LinearElementEditor(hitElement, this.scene)
|
? new LinearElementEditor(hitElement)
|
||||||
: prevState.selectedLinearElement,
|
: prevState.selectedLinearElement,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -8177,9 +8233,16 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (pointerDownState.drag.hasOccurred || isResizing || isRotating) {
|
if (pointerDownState.drag.hasOccurred || isResizing || isRotating) {
|
||||||
(isBindingEnabled(this.state)
|
isBindingEnabled(this.state)
|
||||||
? bindOrUnbindSelectedElements
|
? bindOrUnbindSelectedElements(
|
||||||
: unbindLinearElements)(this.scene.getSelectedElements(this.state));
|
this.scene.getSelectedElements(this.state),
|
||||||
|
this.scene.getNonDeletedElements(),
|
||||||
|
elementsMap,
|
||||||
|
)
|
||||||
|
: unbindLinearElements(
|
||||||
|
this.scene.getSelectedElements(this.state),
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activeTool.type === "laser") {
|
if (activeTool.type === "laser") {
|
||||||
@@ -8656,7 +8719,8 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}): void => {
|
}): void => {
|
||||||
const hoveredBindableElement = getHoveredElementForBinding(
|
const hoveredBindableElement = getHoveredElementForBinding(
|
||||||
pointerCoords,
|
pointerCoords,
|
||||||
this.scene,
|
this.scene.getNonDeletedElements(),
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
this.setState({
|
this.setState({
|
||||||
suggestedBindings:
|
suggestedBindings:
|
||||||
@@ -8683,7 +8747,8 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
(acc: NonDeleted<ExcalidrawBindableElement>[], coords) => {
|
(acc: NonDeleted<ExcalidrawBindableElement>[], coords) => {
|
||||||
const hoveredBindableElement = getHoveredElementForBinding(
|
const hoveredBindableElement = getHoveredElementForBinding(
|
||||||
coords,
|
coords,
|
||||||
this.scene,
|
this.scene.getNonDeletedElements(),
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
if (
|
if (
|
||||||
hoveredBindableElement != null &&
|
hoveredBindableElement != null &&
|
||||||
@@ -8709,7 +8774,11 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
if (selectedElements.length > 50) {
|
if (selectedElements.length > 50) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const suggestedBindings = getEligibleElementsForBinding(selectedElements);
|
const suggestedBindings = getEligibleElementsForBinding(
|
||||||
|
selectedElements,
|
||||||
|
this.scene.getNonDeletedElements(),
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
|
);
|
||||||
this.setState({ suggestedBindings });
|
this.setState({ suggestedBindings });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -8976,7 +9045,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this,
|
this,
|
||||||
),
|
),
|
||||||
selectedLinearElement: isLinearElement(element)
|
selectedLinearElement: isLinearElement(element)
|
||||||
? new LinearElementEditor(element, this.scene)
|
? new LinearElementEditor(element)
|
||||||
: null,
|
: null,
|
||||||
}
|
}
|
||||||
: this.state),
|
: this.state),
|
||||||
@@ -9048,6 +9117,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
x: gridX - pointerDownState.originInGrid.x,
|
x: gridX - pointerDownState.originInGrid.x,
|
||||||
y: gridY - pointerDownState.originInGrid.y,
|
y: gridY - pointerDownState.originInGrid.y,
|
||||||
},
|
},
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
gridX += snapOffset.x;
|
gridX += snapOffset.x;
|
||||||
@@ -9086,6 +9156,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.scene.getNonDeletedElements(),
|
this.scene.getNonDeletedElements(),
|
||||||
draggingElement as ExcalidrawFrameLikeElement,
|
draggingElement as ExcalidrawFrameLikeElement,
|
||||||
this.state,
|
this.state,
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -9205,6 +9276,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.scene.getNonDeletedElements(),
|
this.scene.getNonDeletedElements(),
|
||||||
frame,
|
frame,
|
||||||
this.state,
|
this.state,
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
).forEach((element) => elementsToHighlight.add(element));
|
).forEach((element) => elementsToHighlight.add(element));
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -9501,7 +9573,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// TEST HOOKS
|
// TEST HOOKS
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
h: {
|
h: {
|
||||||
@@ -9514,20 +9585,23 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (import.meta.env.MODE === ENV.TEST || import.meta.env.DEV) {
|
export const createTestHook = () => {
|
||||||
window.h = window.h || ({} as Window["h"]);
|
if (import.meta.env.MODE === ENV.TEST || import.meta.env.DEV) {
|
||||||
|
window.h = window.h || ({} as Window["h"]);
|
||||||
|
|
||||||
Object.defineProperties(window.h, {
|
Object.defineProperties(window.h, {
|
||||||
elements: {
|
elements: {
|
||||||
configurable: true,
|
configurable: true,
|
||||||
get() {
|
get() {
|
||||||
return this.app?.scene.getElementsIncludingDeleted();
|
return this.app?.scene.getElementsIncludingDeleted();
|
||||||
|
},
|
||||||
|
set(elements: ExcalidrawElement[]) {
|
||||||
|
return this.app?.scene.replaceAllElements(elements);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
set(elements: ExcalidrawElement[]) {
|
});
|
||||||
return this.app?.scene.replaceAllElements(elements);
|
}
|
||||||
},
|
};
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
createTestHook();
|
||||||
export default App;
|
export default App;
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ import { Switch } from "./Switch";
|
|||||||
import { Tooltip } from "./Tooltip";
|
import { Tooltip } from "./Tooltip";
|
||||||
|
|
||||||
import "./ImageExportDialog.scss";
|
import "./ImageExportDialog.scss";
|
||||||
import { useAppProps } from "./App";
|
|
||||||
import { FilledButton } from "./FilledButton";
|
import { FilledButton } from "./FilledButton";
|
||||||
import { cloneJSON } from "../utils";
|
import { cloneJSON } from "../utils";
|
||||||
import { prepareElementsForExport } from "../data";
|
import { prepareElementsForExport } from "../data";
|
||||||
@@ -58,6 +57,7 @@ type ImageExportModalProps = {
|
|||||||
files: BinaryFiles;
|
files: BinaryFiles;
|
||||||
actionManager: ActionManager;
|
actionManager: ActionManager;
|
||||||
onExportImage: AppClassProperties["onExportImage"];
|
onExportImage: AppClassProperties["onExportImage"];
|
||||||
|
name: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ImageExportModal = ({
|
const ImageExportModal = ({
|
||||||
@@ -66,14 +66,14 @@ const ImageExportModal = ({
|
|||||||
files,
|
files,
|
||||||
actionManager,
|
actionManager,
|
||||||
onExportImage,
|
onExportImage,
|
||||||
|
name,
|
||||||
}: ImageExportModalProps) => {
|
}: ImageExportModalProps) => {
|
||||||
const hasSelection = isSomeElementSelected(
|
const hasSelection = isSomeElementSelected(
|
||||||
elementsSnapshot,
|
elementsSnapshot,
|
||||||
appStateSnapshot,
|
appStateSnapshot,
|
||||||
);
|
);
|
||||||
|
|
||||||
const appProps = useAppProps();
|
const [projectName, setProjectName] = useState(name);
|
||||||
const [projectName, setProjectName] = useState(appStateSnapshot.name);
|
|
||||||
const [exportSelectionOnly, setExportSelectionOnly] = useState(hasSelection);
|
const [exportSelectionOnly, setExportSelectionOnly] = useState(hasSelection);
|
||||||
const [exportWithBackground, setExportWithBackground] = useState(
|
const [exportWithBackground, setExportWithBackground] = useState(
|
||||||
appStateSnapshot.exportBackground,
|
appStateSnapshot.exportBackground,
|
||||||
@@ -158,10 +158,6 @@ const ImageExportModal = ({
|
|||||||
className="TextInput"
|
className="TextInput"
|
||||||
value={projectName}
|
value={projectName}
|
||||||
style={{ width: "30ch" }}
|
style={{ width: "30ch" }}
|
||||||
disabled={
|
|
||||||
typeof appProps.name !== "undefined" ||
|
|
||||||
appStateSnapshot.viewModeEnabled
|
|
||||||
}
|
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
setProjectName(event.target.value);
|
setProjectName(event.target.value);
|
||||||
actionManager.executeAction(
|
actionManager.executeAction(
|
||||||
@@ -347,6 +343,7 @@ export const ImageExportDialog = ({
|
|||||||
actionManager,
|
actionManager,
|
||||||
onExportImage,
|
onExportImage,
|
||||||
onCloseRequest,
|
onCloseRequest,
|
||||||
|
name,
|
||||||
}: {
|
}: {
|
||||||
appState: UIAppState;
|
appState: UIAppState;
|
||||||
elements: readonly NonDeletedExcalidrawElement[];
|
elements: readonly NonDeletedExcalidrawElement[];
|
||||||
@@ -354,6 +351,7 @@ export const ImageExportDialog = ({
|
|||||||
actionManager: ActionManager;
|
actionManager: ActionManager;
|
||||||
onExportImage: AppClassProperties["onExportImage"];
|
onExportImage: AppClassProperties["onExportImage"];
|
||||||
onCloseRequest: () => void;
|
onCloseRequest: () => void;
|
||||||
|
name: string;
|
||||||
}) => {
|
}) => {
|
||||||
// we need to take a snapshot so that the exported state can't be modified
|
// we need to take a snapshot so that the exported state can't be modified
|
||||||
// while the dialog is open
|
// while the dialog is open
|
||||||
@@ -372,6 +370,7 @@ export const ImageExportDialog = ({
|
|||||||
files={files}
|
files={files}
|
||||||
actionManager={actionManager}
|
actionManager={actionManager}
|
||||||
onExportImage={onExportImage}
|
onExportImage={onExportImage}
|
||||||
|
name={name}
|
||||||
/>
|
/>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -195,6 +195,7 @@ const LayerUI = ({
|
|||||||
actionManager={actionManager}
|
actionManager={actionManager}
|
||||||
onExportImage={onExportImage}
|
onExportImage={onExportImage}
|
||||||
onCloseRequest={() => setAppState({ openDialog: null })}
|
onCloseRequest={() => setAppState({ openDialog: null })}
|
||||||
|
name={app.getName()}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ type Props = {
|
|||||||
value: string;
|
value: string;
|
||||||
onChange: (value: string) => void;
|
onChange: (value: string) => void;
|
||||||
label: string;
|
label: string;
|
||||||
isNameEditable: boolean;
|
|
||||||
ignoreFocus?: boolean;
|
ignoreFocus?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -42,23 +41,17 @@ export const ProjectName = (props: Props) => {
|
|||||||
return (
|
return (
|
||||||
<div className="ProjectName">
|
<div className="ProjectName">
|
||||||
<label className="ProjectName-label" htmlFor="filename">
|
<label className="ProjectName-label" htmlFor="filename">
|
||||||
{`${props.label}${props.isNameEditable ? "" : ":"}`}
|
{`${props.label}:`}
|
||||||
</label>
|
</label>
|
||||||
{props.isNameEditable ? (
|
<input
|
||||||
<input
|
type="text"
|
||||||
type="text"
|
className="TextInput"
|
||||||
className="TextInput"
|
onBlur={handleBlur}
|
||||||
onBlur={handleBlur}
|
onKeyDown={handleKeyDown}
|
||||||
onKeyDown={handleKeyDown}
|
id={`${id}-filename`}
|
||||||
id={`${id}-filename`}
|
value={fileName}
|
||||||
value={fileName}
|
onChange={(event) => setFileName(event.target.value)}
|
||||||
onChange={(event) => setFileName(event.target.value)}
|
/>
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<span className="TextInput TextInput--readonly" id={`${id}-filename`}>
|
|
||||||
{props.value}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
@import "../css/variables.module.scss";
|
@import "../../css/variables.module.scss";
|
||||||
|
|
||||||
.excalidraw-hyperlinkContainer {
|
.excalidraw-hyperlinkContainer {
|
||||||
display: flex;
|
display: flex;
|
||||||
+46
-156
@@ -1,21 +1,20 @@
|
|||||||
import { AppState, ExcalidrawProps, Point, UIAppState } from "../types";
|
import { AppState, ExcalidrawProps, Point } from "../../types";
|
||||||
import {
|
import {
|
||||||
getShortcutKey,
|
|
||||||
sceneCoordsToViewportCoords,
|
sceneCoordsToViewportCoords,
|
||||||
viewportCoordsToSceneCoords,
|
viewportCoordsToSceneCoords,
|
||||||
wrapEvent,
|
wrapEvent,
|
||||||
} from "../utils";
|
} from "../../utils";
|
||||||
import { getEmbedLink, embeddableURLValidator } from "./embeddable";
|
import { getEmbedLink, embeddableURLValidator } from "../../element/embeddable";
|
||||||
import { mutateElement } from "./mutateElement";
|
import { mutateElement } from "../../element/mutateElement";
|
||||||
import {
|
import {
|
||||||
|
ElementsMap,
|
||||||
ExcalidrawEmbeddableElement,
|
ExcalidrawEmbeddableElement,
|
||||||
NonDeletedExcalidrawElement,
|
NonDeletedExcalidrawElement,
|
||||||
} from "./types";
|
} from "../../element/types";
|
||||||
|
|
||||||
import { register } from "../actions/register";
|
import { ToolButton } from "../ToolButton";
|
||||||
import { ToolButton } from "../components/ToolButton";
|
import { FreedrawIcon, TrashIcon } from "../icons";
|
||||||
import { FreedrawIcon, LinkIcon, TrashIcon } from "../components/icons";
|
import { t } from "../../i18n";
|
||||||
import { t } from "../i18n";
|
|
||||||
import {
|
import {
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
@@ -24,21 +23,19 @@ import {
|
|||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { KEYS } from "../keys";
|
import { KEYS } from "../../keys";
|
||||||
import { DEFAULT_LINK_SIZE } from "../renderer/renderElement";
|
import { EVENT, HYPERLINK_TOOLTIP_DELAY } from "../../constants";
|
||||||
import { rotate } from "../math";
|
import { getElementAbsoluteCoords } from "../../element/bounds";
|
||||||
import { EVENT, HYPERLINK_TOOLTIP_DELAY, MIME_TYPES } from "../constants";
|
import { getTooltipDiv, updateTooltipPosition } from "../Tooltip";
|
||||||
import { Bounds } from "./bounds";
|
import { getSelectedElements } from "../../scene";
|
||||||
import { getTooltipDiv, updateTooltipPosition } from "../components/Tooltip";
|
import { isPointHittingElementBoundingBox } from "../../element/collision";
|
||||||
import { getSelectedElements } from "../scene";
|
import { isLocalLink, normalizeLink } from "../../data/url";
|
||||||
import { isPointHittingElementBoundingBox } from "./collision";
|
|
||||||
import { getElementAbsoluteCoords } from ".";
|
|
||||||
import { isLocalLink, normalizeLink } from "../data/url";
|
|
||||||
|
|
||||||
import "./Hyperlink.scss";
|
import "./Hyperlink.scss";
|
||||||
import { trackEvent } from "../analytics";
|
import { trackEvent } from "../../analytics";
|
||||||
import { useAppProps, useExcalidrawAppState } from "../components/App";
|
import { useAppProps, useExcalidrawAppState } from "../App";
|
||||||
import { isEmbeddableElement } from "./typeChecks";
|
import { isEmbeddableElement } from "../../element/typeChecks";
|
||||||
|
import { getLinkHandleFromCoords } from "./helpers";
|
||||||
|
|
||||||
const CONTAINER_WIDTH = 320;
|
const CONTAINER_WIDTH = 320;
|
||||||
const SPACE_BOTTOM = 85;
|
const SPACE_BOTTOM = 85;
|
||||||
@@ -46,11 +43,6 @@ const CONTAINER_PADDING = 5;
|
|||||||
const CONTAINER_HEIGHT = 42;
|
const CONTAINER_HEIGHT = 42;
|
||||||
const AUTO_HIDE_TIMEOUT = 500;
|
const AUTO_HIDE_TIMEOUT = 500;
|
||||||
|
|
||||||
export const EXTERNAL_LINK_IMG = document.createElement("img");
|
|
||||||
EXTERNAL_LINK_IMG.src = `data:${MIME_TYPES.svg}, ${encodeURIComponent(
|
|
||||||
`<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#1971c2" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-external-link"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg>`,
|
|
||||||
)}`;
|
|
||||||
|
|
||||||
let IS_HYPERLINK_TOOLTIP_VISIBLE = false;
|
let IS_HYPERLINK_TOOLTIP_VISIBLE = false;
|
||||||
|
|
||||||
const embeddableLinkCache = new Map<
|
const embeddableLinkCache = new Map<
|
||||||
@@ -60,12 +52,14 @@ const embeddableLinkCache = new Map<
|
|||||||
|
|
||||||
export const Hyperlink = ({
|
export const Hyperlink = ({
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
setAppState,
|
setAppState,
|
||||||
onLinkOpen,
|
onLinkOpen,
|
||||||
setToast,
|
setToast,
|
||||||
updateEmbedValidationStatus,
|
updateEmbedValidationStatus,
|
||||||
}: {
|
}: {
|
||||||
element: NonDeletedExcalidrawElement;
|
element: NonDeletedExcalidrawElement;
|
||||||
|
elementsMap: ElementsMap;
|
||||||
setAppState: React.Component<any, AppState>["setState"];
|
setAppState: React.Component<any, AppState>["setState"];
|
||||||
onLinkOpen: ExcalidrawProps["onLinkOpen"];
|
onLinkOpen: ExcalidrawProps["onLinkOpen"];
|
||||||
setToast: (
|
setToast: (
|
||||||
@@ -182,7 +176,7 @@ export const Hyperlink = ({
|
|||||||
if (timeoutId) {
|
if (timeoutId) {
|
||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
}
|
}
|
||||||
const shouldHide = shouldHideLinkPopup(element, appState, [
|
const shouldHide = shouldHideLinkPopup(element, elementsMap, appState, [
|
||||||
event.clientX,
|
event.clientX,
|
||||||
event.clientY,
|
event.clientY,
|
||||||
]) as boolean;
|
]) as boolean;
|
||||||
@@ -199,7 +193,7 @@ export const Hyperlink = ({
|
|||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [appState, element, isEditing, setAppState]);
|
}, [appState, element, isEditing, setAppState, elementsMap]);
|
||||||
|
|
||||||
const handleRemove = useCallback(() => {
|
const handleRemove = useCallback(() => {
|
||||||
trackEvent("hyperlink", "delete");
|
trackEvent("hyperlink", "delete");
|
||||||
@@ -214,7 +208,7 @@ export const Hyperlink = ({
|
|||||||
trackEvent("hyperlink", "edit", "popup-ui");
|
trackEvent("hyperlink", "edit", "popup-ui");
|
||||||
setAppState({ showHyperlinkPopup: "editor" });
|
setAppState({ showHyperlinkPopup: "editor" });
|
||||||
};
|
};
|
||||||
const { x, y } = getCoordsForPopover(element, appState);
|
const { x, y } = getCoordsForPopover(element, appState, elementsMap);
|
||||||
if (
|
if (
|
||||||
appState.contextMenu ||
|
appState.contextMenu ||
|
||||||
appState.draggingElement ||
|
appState.draggingElement ||
|
||||||
@@ -324,8 +318,9 @@ export const Hyperlink = ({
|
|||||||
const getCoordsForPopover = (
|
const getCoordsForPopover = (
|
||||||
element: NonDeletedExcalidrawElement,
|
element: NonDeletedExcalidrawElement,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
const [x1, y1] = getElementAbsoluteCoords(element);
|
const [x1, y1] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
const { x: viewportX, y: viewportY } = sceneCoordsToViewportCoords(
|
const { x: viewportX, y: viewportY } = sceneCoordsToViewportCoords(
|
||||||
{ sceneX: x1 + element.width / 2, sceneY: y1 },
|
{ sceneX: x1 + element.width / 2, sceneY: y1 },
|
||||||
appState,
|
appState,
|
||||||
@@ -335,51 +330,6 @@ const getCoordsForPopover = (
|
|||||||
return { x, y };
|
return { x, y };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actionLink = register({
|
|
||||||
name: "hyperlink",
|
|
||||||
perform: (elements, appState) => {
|
|
||||||
if (appState.showHyperlinkPopup === "editor") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
elements,
|
|
||||||
appState: {
|
|
||||||
...appState,
|
|
||||||
showHyperlinkPopup: "editor",
|
|
||||||
openMenu: null,
|
|
||||||
},
|
|
||||||
commitToHistory: true,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
trackEvent: { category: "hyperlink", action: "click" },
|
|
||||||
keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.key === KEYS.K,
|
|
||||||
contextItemLabel: (elements, appState) =>
|
|
||||||
getContextMenuLabel(elements, appState),
|
|
||||||
predicate: (elements, appState) => {
|
|
||||||
const selectedElements = getSelectedElements(elements, appState);
|
|
||||||
return selectedElements.length === 1;
|
|
||||||
},
|
|
||||||
PanelComponent: ({ elements, appState, updateData }) => {
|
|
||||||
const selectedElements = getSelectedElements(elements, appState);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ToolButton
|
|
||||||
type="button"
|
|
||||||
icon={LinkIcon}
|
|
||||||
aria-label={t(getContextMenuLabel(elements, appState))}
|
|
||||||
title={`${
|
|
||||||
isEmbeddableElement(elements[0])
|
|
||||||
? t("labels.link.labelEmbed")
|
|
||||||
: t("labels.link.label")
|
|
||||||
} - ${getShortcutKey("CtrlOrCmd+K")}`}
|
|
||||||
onClick={() => updateData(null)}
|
|
||||||
selected={selectedElements.length === 1 && !!selectedElements[0].link}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const getContextMenuLabel = (
|
export const getContextMenuLabel = (
|
||||||
elements: readonly NonDeletedExcalidrawElement[],
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
@@ -395,89 +345,17 @@ export const getContextMenuLabel = (
|
|||||||
return label;
|
return label;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getLinkHandleFromCoords = (
|
|
||||||
[x1, y1, x2, y2]: Bounds,
|
|
||||||
angle: number,
|
|
||||||
appState: Pick<UIAppState, "zoom">,
|
|
||||||
): Bounds => {
|
|
||||||
const size = DEFAULT_LINK_SIZE;
|
|
||||||
const linkWidth = size / appState.zoom.value;
|
|
||||||
const linkHeight = size / appState.zoom.value;
|
|
||||||
const linkMarginY = size / appState.zoom.value;
|
|
||||||
const centerX = (x1 + x2) / 2;
|
|
||||||
const centerY = (y1 + y2) / 2;
|
|
||||||
const centeringOffset = (size - 8) / (2 * appState.zoom.value);
|
|
||||||
const dashedLineMargin = 4 / appState.zoom.value;
|
|
||||||
|
|
||||||
// Same as `ne` resize handle
|
|
||||||
const x = x2 + dashedLineMargin - centeringOffset;
|
|
||||||
const y = y1 - dashedLineMargin - linkMarginY + centeringOffset;
|
|
||||||
|
|
||||||
const [rotatedX, rotatedY] = rotate(
|
|
||||||
x + linkWidth / 2,
|
|
||||||
y + linkHeight / 2,
|
|
||||||
centerX,
|
|
||||||
centerY,
|
|
||||||
angle,
|
|
||||||
);
|
|
||||||
return [
|
|
||||||
rotatedX - linkWidth / 2,
|
|
||||||
rotatedY - linkHeight / 2,
|
|
||||||
linkWidth,
|
|
||||||
linkHeight,
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const isPointHittingLinkIcon = (
|
|
||||||
element: NonDeletedExcalidrawElement,
|
|
||||||
appState: AppState,
|
|
||||||
[x, y]: Point,
|
|
||||||
) => {
|
|
||||||
const threshold = 4 / appState.zoom.value;
|
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
|
||||||
const [linkX, linkY, linkWidth, linkHeight] = getLinkHandleFromCoords(
|
|
||||||
[x1, y1, x2, y2],
|
|
||||||
element.angle,
|
|
||||||
appState,
|
|
||||||
);
|
|
||||||
const hitLink =
|
|
||||||
x > linkX - threshold &&
|
|
||||||
x < linkX + threshold + linkWidth &&
|
|
||||||
y > linkY - threshold &&
|
|
||||||
y < linkY + linkHeight + threshold;
|
|
||||||
return hitLink;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const isPointHittingLink = (
|
|
||||||
element: NonDeletedExcalidrawElement,
|
|
||||||
appState: AppState,
|
|
||||||
[x, y]: Point,
|
|
||||||
isMobile: boolean,
|
|
||||||
) => {
|
|
||||||
if (!element.link || appState.selectedElementIds[element.id]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const threshold = 4 / appState.zoom.value;
|
|
||||||
if (
|
|
||||||
!isMobile &&
|
|
||||||
appState.viewModeEnabled &&
|
|
||||||
isPointHittingElementBoundingBox(element, [x, y], threshold, null)
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return isPointHittingLinkIcon(element, appState, [x, y]);
|
|
||||||
};
|
|
||||||
|
|
||||||
let HYPERLINK_TOOLTIP_TIMEOUT_ID: number | null = null;
|
let HYPERLINK_TOOLTIP_TIMEOUT_ID: number | null = null;
|
||||||
export const showHyperlinkTooltip = (
|
export const showHyperlinkTooltip = (
|
||||||
element: NonDeletedExcalidrawElement,
|
element: NonDeletedExcalidrawElement,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
if (HYPERLINK_TOOLTIP_TIMEOUT_ID) {
|
if (HYPERLINK_TOOLTIP_TIMEOUT_ID) {
|
||||||
clearTimeout(HYPERLINK_TOOLTIP_TIMEOUT_ID);
|
clearTimeout(HYPERLINK_TOOLTIP_TIMEOUT_ID);
|
||||||
}
|
}
|
||||||
HYPERLINK_TOOLTIP_TIMEOUT_ID = window.setTimeout(
|
HYPERLINK_TOOLTIP_TIMEOUT_ID = window.setTimeout(
|
||||||
() => renderTooltip(element, appState),
|
() => renderTooltip(element, appState, elementsMap),
|
||||||
HYPERLINK_TOOLTIP_DELAY,
|
HYPERLINK_TOOLTIP_DELAY,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -485,6 +363,7 @@ export const showHyperlinkTooltip = (
|
|||||||
const renderTooltip = (
|
const renderTooltip = (
|
||||||
element: NonDeletedExcalidrawElement,
|
element: NonDeletedExcalidrawElement,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
if (!element.link) {
|
if (!element.link) {
|
||||||
return;
|
return;
|
||||||
@@ -496,7 +375,7 @@ const renderTooltip = (
|
|||||||
tooltipDiv.style.maxWidth = "20rem";
|
tooltipDiv.style.maxWidth = "20rem";
|
||||||
tooltipDiv.textContent = element.link;
|
tooltipDiv.textContent = element.link;
|
||||||
|
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
|
|
||||||
const [linkX, linkY, linkWidth, linkHeight] = getLinkHandleFromCoords(
|
const [linkX, linkY, linkWidth, linkHeight] = getLinkHandleFromCoords(
|
||||||
[x1, y1, x2, y2],
|
[x1, y1, x2, y2],
|
||||||
@@ -533,8 +412,9 @@ export const hideHyperlinkToolip = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const shouldHideLinkPopup = (
|
const shouldHideLinkPopup = (
|
||||||
element: NonDeletedExcalidrawElement,
|
element: NonDeletedExcalidrawElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
[clientX, clientY]: Point,
|
[clientX, clientY]: Point,
|
||||||
): Boolean => {
|
): Boolean => {
|
||||||
@@ -546,11 +426,17 @@ export const shouldHideLinkPopup = (
|
|||||||
const threshold = 15 / appState.zoom.value;
|
const threshold = 15 / appState.zoom.value;
|
||||||
// hitbox to prevent hiding when hovered in element bounding box
|
// hitbox to prevent hiding when hovered in element bounding box
|
||||||
if (
|
if (
|
||||||
isPointHittingElementBoundingBox(element, [sceneX, sceneY], threshold, null)
|
isPointHittingElementBoundingBox(
|
||||||
|
element,
|
||||||
|
elementsMap,
|
||||||
|
[sceneX, sceneY],
|
||||||
|
threshold,
|
||||||
|
null,
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const [x1, y1, x2] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
// hit box to prevent hiding when hovered in the vertical area between element and popover
|
// hit box to prevent hiding when hovered in the vertical area between element and popover
|
||||||
if (
|
if (
|
||||||
sceneX >= x1 &&
|
sceneX >= x1 &&
|
||||||
@@ -561,7 +447,11 @@ export const shouldHideLinkPopup = (
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// hit box to prevent hiding when hovered around popover within threshold
|
// hit box to prevent hiding when hovered around popover within threshold
|
||||||
const { x: popoverX, y: popoverY } = getCoordsForPopover(element, appState);
|
const { x: popoverX, y: popoverY } = getCoordsForPopover(
|
||||||
|
element,
|
||||||
|
appState,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
clientX >= popoverX - threshold &&
|
clientX >= popoverX - threshold &&
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
import { MIME_TYPES } from "../../constants";
|
||||||
|
import { Bounds, getElementAbsoluteCoords } from "../../element/bounds";
|
||||||
|
import { isPointHittingElementBoundingBox } from "../../element/collision";
|
||||||
|
import { ElementsMap, NonDeletedExcalidrawElement } from "../../element/types";
|
||||||
|
import { rotate } from "../../math";
|
||||||
|
import { DEFAULT_LINK_SIZE } from "../../renderer/renderElement";
|
||||||
|
import { AppState, Point, UIAppState } from "../../types";
|
||||||
|
|
||||||
|
export const EXTERNAL_LINK_IMG = document.createElement("img");
|
||||||
|
EXTERNAL_LINK_IMG.src = `data:${MIME_TYPES.svg}, ${encodeURIComponent(
|
||||||
|
`<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#1971c2" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-external-link"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg>`,
|
||||||
|
)}`;
|
||||||
|
|
||||||
|
export const getLinkHandleFromCoords = (
|
||||||
|
[x1, y1, x2, y2]: Bounds,
|
||||||
|
angle: number,
|
||||||
|
appState: Pick<UIAppState, "zoom">,
|
||||||
|
): Bounds => {
|
||||||
|
const size = DEFAULT_LINK_SIZE;
|
||||||
|
const linkWidth = size / appState.zoom.value;
|
||||||
|
const linkHeight = size / appState.zoom.value;
|
||||||
|
const linkMarginY = size / appState.zoom.value;
|
||||||
|
const centerX = (x1 + x2) / 2;
|
||||||
|
const centerY = (y1 + y2) / 2;
|
||||||
|
const centeringOffset = (size - 8) / (2 * appState.zoom.value);
|
||||||
|
const dashedLineMargin = 4 / appState.zoom.value;
|
||||||
|
|
||||||
|
// Same as `ne` resize handle
|
||||||
|
const x = x2 + dashedLineMargin - centeringOffset;
|
||||||
|
const y = y1 - dashedLineMargin - linkMarginY + centeringOffset;
|
||||||
|
|
||||||
|
const [rotatedX, rotatedY] = rotate(
|
||||||
|
x + linkWidth / 2,
|
||||||
|
y + linkHeight / 2,
|
||||||
|
centerX,
|
||||||
|
centerY,
|
||||||
|
angle,
|
||||||
|
);
|
||||||
|
return [
|
||||||
|
rotatedX - linkWidth / 2,
|
||||||
|
rotatedY - linkHeight / 2,
|
||||||
|
linkWidth,
|
||||||
|
linkHeight,
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isPointHittingLinkIcon = (
|
||||||
|
element: NonDeletedExcalidrawElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
|
appState: AppState,
|
||||||
|
[x, y]: Point,
|
||||||
|
) => {
|
||||||
|
const threshold = 4 / appState.zoom.value;
|
||||||
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
|
const [linkX, linkY, linkWidth, linkHeight] = getLinkHandleFromCoords(
|
||||||
|
[x1, y1, x2, y2],
|
||||||
|
element.angle,
|
||||||
|
appState,
|
||||||
|
);
|
||||||
|
const hitLink =
|
||||||
|
x > linkX - threshold &&
|
||||||
|
x < linkX + threshold + linkWidth &&
|
||||||
|
y > linkY - threshold &&
|
||||||
|
y < linkY + linkHeight + threshold;
|
||||||
|
return hitLink;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isPointHittingLink = (
|
||||||
|
element: NonDeletedExcalidrawElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
|
appState: AppState,
|
||||||
|
[x, y]: Point,
|
||||||
|
isMobile: boolean,
|
||||||
|
) => {
|
||||||
|
if (!element.link || appState.selectedElementIds[element.id]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const threshold = 4 / appState.zoom.value;
|
||||||
|
if (
|
||||||
|
!isMobile &&
|
||||||
|
appState.viewModeEnabled &&
|
||||||
|
isPointHittingElementBoundingBox(
|
||||||
|
element,
|
||||||
|
elementsMap,
|
||||||
|
[x, y],
|
||||||
|
threshold,
|
||||||
|
null,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return isPointHittingLinkIcon(element, elementsMap, appState, [x, y]);
|
||||||
|
};
|
||||||
@@ -381,3 +381,9 @@ export const EDITOR_LS_KEYS = {
|
|||||||
MERMAID_TO_EXCALIDRAW: "mermaid-to-excalidraw",
|
MERMAID_TO_EXCALIDRAW: "mermaid-to-excalidraw",
|
||||||
PUBLISH_LIBRARY: "publish-library-data",
|
PUBLISH_LIBRARY: "publish-library-data",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* not translated as this is used only in public, stateless API as default value
|
||||||
|
* where filename is optional and we can't retrieve name from app state
|
||||||
|
*/
|
||||||
|
export const DEFAULT_FILENAME = "Untitled";
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
|||||||
"type": "arrow",
|
"type": "arrow",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -49,6 +50,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
|||||||
"type": "arrow",
|
"type": "arrow",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -79,6 +81,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"endArrowhead": "arrow",
|
"endArrowhead": "arrow",
|
||||||
"endBinding": {
|
"endBinding": {
|
||||||
"elementId": "ellipse-1",
|
"elementId": "ellipse-1",
|
||||||
@@ -132,6 +135,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"endArrowhead": "arrow",
|
"endArrowhead": "arrow",
|
||||||
"endBinding": {
|
"endBinding": {
|
||||||
"elementId": "ellipse-1",
|
"elementId": "ellipse-1",
|
||||||
@@ -190,6 +194,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
|||||||
"type": "arrow",
|
"type": "arrow",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -227,6 +232,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
"containerId": null,
|
"containerId": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"fontFamily": 1,
|
"fontFamily": 1,
|
||||||
"fontSize": 20,
|
"fontSize": 20,
|
||||||
@@ -271,6 +277,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
"containerId": null,
|
"containerId": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"fontFamily": 1,
|
"fontFamily": 1,
|
||||||
"fontSize": 20,
|
"fontSize": 20,
|
||||||
@@ -313,6 +320,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
"customData": undefined,
|
||||||
"endArrowhead": "arrow",
|
"endArrowhead": "arrow",
|
||||||
"endBinding": {
|
"endBinding": {
|
||||||
"elementId": "text-2",
|
"elementId": "text-2",
|
||||||
@@ -368,6 +376,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
|
|||||||
"baseline": 0,
|
"baseline": 0,
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
"containerId": "id48",
|
"containerId": "id48",
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"fontFamily": 1,
|
"fontFamily": 1,
|
||||||
"fontSize": 20,
|
"fontSize": 20,
|
||||||
@@ -410,6 +419,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
"customData": undefined,
|
||||||
"endArrowhead": "arrow",
|
"endArrowhead": "arrow",
|
||||||
"endBinding": {
|
"endBinding": {
|
||||||
"elementId": "id40",
|
"elementId": "id40",
|
||||||
@@ -465,6 +475,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe
|
|||||||
"baseline": 0,
|
"baseline": 0,
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
"containerId": "id37",
|
"containerId": "id37",
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"fontFamily": 1,
|
"fontFamily": 1,
|
||||||
"fontSize": 20,
|
"fontSize": 20,
|
||||||
@@ -507,6 +518,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe
|
|||||||
"type": "arrow",
|
"type": "arrow",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -542,6 +554,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe
|
|||||||
"type": "arrow",
|
"type": "arrow",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -577,6 +590,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
"customData": undefined,
|
||||||
"endArrowhead": "arrow",
|
"endArrowhead": "arrow",
|
||||||
"endBinding": {
|
"endBinding": {
|
||||||
"elementId": "id44",
|
"elementId": "id44",
|
||||||
@@ -632,6 +646,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when
|
|||||||
"baseline": 0,
|
"baseline": 0,
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
"containerId": "id41",
|
"containerId": "id41",
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"fontFamily": 1,
|
"fontFamily": 1,
|
||||||
"fontSize": 20,
|
"fontSize": 20,
|
||||||
@@ -676,6 +691,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
"containerId": null,
|
"containerId": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"fontFamily": 1,
|
"fontFamily": 1,
|
||||||
"fontSize": 20,
|
"fontSize": 20,
|
||||||
@@ -720,6 +736,7 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
"containerId": null,
|
"containerId": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"fontFamily": 1,
|
"fontFamily": 1,
|
||||||
"fontSize": 20,
|
"fontSize": 20,
|
||||||
@@ -757,6 +774,7 @@ exports[`Test Transform > should not allow duplicate ids 1`] = `
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -787,6 +805,7 @@ exports[`Test Transform > should transform linear elements 1`] = `
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"endArrowhead": "arrow",
|
"endArrowhead": "arrow",
|
||||||
"endBinding": null,
|
"endBinding": null,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
@@ -832,6 +851,7 @@ exports[`Test Transform > should transform linear elements 2`] = `
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"endArrowhead": "triangle",
|
"endArrowhead": "triangle",
|
||||||
"endBinding": null,
|
"endBinding": null,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
@@ -877,6 +897,7 @@ exports[`Test Transform > should transform linear elements 3`] = `
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"endArrowhead": null,
|
"endArrowhead": null,
|
||||||
"endBinding": null,
|
"endBinding": null,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
@@ -922,6 +943,7 @@ exports[`Test Transform > should transform linear elements 4`] = `
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"endArrowhead": null,
|
"endArrowhead": null,
|
||||||
"endBinding": null,
|
"endBinding": null,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
@@ -967,6 +989,7 @@ exports[`Test Transform > should transform regular shapes 1`] = `
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -997,6 +1020,7 @@ exports[`Test Transform > should transform regular shapes 2`] = `
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -1027,6 +1051,7 @@ exports[`Test Transform > should transform regular shapes 3`] = `
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -1057,6 +1082,7 @@ exports[`Test Transform > should transform regular shapes 4`] = `
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "#c0eb75",
|
"backgroundColor": "#c0eb75",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -1087,6 +1113,7 @@ exports[`Test Transform > should transform regular shapes 5`] = `
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "#ffc9c9",
|
"backgroundColor": "#ffc9c9",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -1117,6 +1144,7 @@ exports[`Test Transform > should transform regular shapes 6`] = `
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "#a5d8ff",
|
"backgroundColor": "#a5d8ff",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "cross-hatch",
|
"fillStyle": "cross-hatch",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -1149,6 +1177,7 @@ exports[`Test Transform > should transform text element 1`] = `
|
|||||||
"baseline": 0,
|
"baseline": 0,
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
"containerId": null,
|
"containerId": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"fontFamily": 1,
|
"fontFamily": 1,
|
||||||
"fontSize": 20,
|
"fontSize": 20,
|
||||||
@@ -1188,6 +1217,7 @@ exports[`Test Transform > should transform text element 2`] = `
|
|||||||
"baseline": 0,
|
"baseline": 0,
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
"containerId": null,
|
"containerId": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"fontFamily": 1,
|
"fontFamily": 1,
|
||||||
"fontSize": 20,
|
"fontSize": 20,
|
||||||
@@ -1230,6 +1260,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
"customData": undefined,
|
||||||
"endArrowhead": "arrow",
|
"endArrowhead": "arrow",
|
||||||
"endBinding": null,
|
"endBinding": null,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
@@ -1280,6 +1311,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
"customData": undefined,
|
||||||
"endArrowhead": "arrow",
|
"endArrowhead": "arrow",
|
||||||
"endBinding": null,
|
"endBinding": null,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
@@ -1330,6 +1362,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
"customData": undefined,
|
||||||
"endArrowhead": "arrow",
|
"endArrowhead": "arrow",
|
||||||
"endBinding": null,
|
"endBinding": null,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
@@ -1380,6 +1413,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
"customData": undefined,
|
||||||
"endArrowhead": "arrow",
|
"endArrowhead": "arrow",
|
||||||
"endBinding": null,
|
"endBinding": null,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
@@ -1427,6 +1461,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
|||||||
"baseline": 0,
|
"baseline": 0,
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
"containerId": "id25",
|
"containerId": "id25",
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"fontFamily": 1,
|
"fontFamily": 1,
|
||||||
"fontSize": 20,
|
"fontSize": 20,
|
||||||
@@ -1466,6 +1501,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
|||||||
"baseline": 0,
|
"baseline": 0,
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
"containerId": "id26",
|
"containerId": "id26",
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"fontFamily": 1,
|
"fontFamily": 1,
|
||||||
"fontSize": 20,
|
"fontSize": 20,
|
||||||
@@ -1505,6 +1541,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
|||||||
"baseline": 0,
|
"baseline": 0,
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
"containerId": "id27",
|
"containerId": "id27",
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"fontFamily": 1,
|
"fontFamily": 1,
|
||||||
"fontSize": 20,
|
"fontSize": 20,
|
||||||
@@ -1545,6 +1582,7 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
|||||||
"baseline": 0,
|
"baseline": 0,
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
"containerId": "id28",
|
"containerId": "id28",
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"fontFamily": 1,
|
"fontFamily": 1,
|
||||||
"fontSize": 20,
|
"fontSize": 20,
|
||||||
@@ -1588,6 +1626,7 @@ exports[`Test Transform > should transform to text containers when label provide
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -1623,6 +1662,7 @@ exports[`Test Transform > should transform to text containers when label provide
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -1658,6 +1698,7 @@ exports[`Test Transform > should transform to text containers when label provide
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -1693,6 +1734,7 @@ exports[`Test Transform > should transform to text containers when label provide
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -1728,6 +1770,7 @@ exports[`Test Transform > should transform to text containers when label provide
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -1763,6 +1806,7 @@ exports[`Test Transform > should transform to text containers when label provide
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -1795,6 +1839,7 @@ exports[`Test Transform > should transform to text containers when label provide
|
|||||||
"baseline": 0,
|
"baseline": 0,
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
"containerId": "id13",
|
"containerId": "id13",
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"fontFamily": 1,
|
"fontFamily": 1,
|
||||||
"fontSize": 20,
|
"fontSize": 20,
|
||||||
@@ -1834,6 +1879,7 @@ exports[`Test Transform > should transform to text containers when label provide
|
|||||||
"baseline": 0,
|
"baseline": 0,
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
"containerId": "id14",
|
"containerId": "id14",
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"fontFamily": 1,
|
"fontFamily": 1,
|
||||||
"fontSize": 20,
|
"fontSize": 20,
|
||||||
@@ -1874,6 +1920,7 @@ exports[`Test Transform > should transform to text containers when label provide
|
|||||||
"baseline": 0,
|
"baseline": 0,
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
"containerId": "id15",
|
"containerId": "id15",
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"fontFamily": 1,
|
"fontFamily": 1,
|
||||||
"fontSize": 20,
|
"fontSize": 20,
|
||||||
@@ -1916,6 +1963,7 @@ exports[`Test Transform > should transform to text containers when label provide
|
|||||||
"baseline": 0,
|
"baseline": 0,
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
"containerId": "id16",
|
"containerId": "id16",
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"fontFamily": 1,
|
"fontFamily": 1,
|
||||||
"fontSize": 20,
|
"fontSize": 20,
|
||||||
@@ -1956,6 +2004,7 @@ exports[`Test Transform > should transform to text containers when label provide
|
|||||||
"baseline": 0,
|
"baseline": 0,
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
"containerId": "id17",
|
"containerId": "id17",
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"fontFamily": 1,
|
"fontFamily": 1,
|
||||||
"fontSize": 20,
|
"fontSize": 20,
|
||||||
@@ -1997,6 +2046,7 @@ exports[`Test Transform > should transform to text containers when label provide
|
|||||||
"baseline": 0,
|
"baseline": 0,
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
"containerId": "id18",
|
"containerId": "id18",
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"fontFamily": 1,
|
"fontFamily": 1,
|
||||||
"fontSize": 20,
|
"fontSize": 20,
|
||||||
|
|||||||
@@ -2,7 +2,12 @@ import {
|
|||||||
copyBlobToClipboardAsPng,
|
copyBlobToClipboardAsPng,
|
||||||
copyTextToSystemClipboard,
|
copyTextToSystemClipboard,
|
||||||
} from "../clipboard";
|
} from "../clipboard";
|
||||||
import { DEFAULT_EXPORT_PADDING, isFirefox, MIME_TYPES } from "../constants";
|
import {
|
||||||
|
DEFAULT_EXPORT_PADDING,
|
||||||
|
DEFAULT_FILENAME,
|
||||||
|
isFirefox,
|
||||||
|
MIME_TYPES,
|
||||||
|
} from "../constants";
|
||||||
import { getNonDeletedElements } from "../element";
|
import { getNonDeletedElements } from "../element";
|
||||||
import { isFrameLikeElement } from "../element/typeChecks";
|
import { isFrameLikeElement } from "../element/typeChecks";
|
||||||
import {
|
import {
|
||||||
@@ -84,14 +89,15 @@ export const exportCanvas = async (
|
|||||||
exportBackground,
|
exportBackground,
|
||||||
exportPadding = DEFAULT_EXPORT_PADDING,
|
exportPadding = DEFAULT_EXPORT_PADDING,
|
||||||
viewBackgroundColor,
|
viewBackgroundColor,
|
||||||
name,
|
name = appState.name || DEFAULT_FILENAME,
|
||||||
fileHandle = null,
|
fileHandle = null,
|
||||||
exportingFrame = null,
|
exportingFrame = null,
|
||||||
}: {
|
}: {
|
||||||
exportBackground: boolean;
|
exportBackground: boolean;
|
||||||
exportPadding?: number;
|
exportPadding?: number;
|
||||||
viewBackgroundColor: string;
|
viewBackgroundColor: string;
|
||||||
name: string;
|
/** filename, if applicable */
|
||||||
|
name?: string;
|
||||||
fileHandle?: FileSystemHandle | null;
|
fileHandle?: FileSystemHandle | null;
|
||||||
exportingFrame: ExcalidrawFrameLikeElement | null;
|
exportingFrame: ExcalidrawFrameLikeElement | null;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { fileOpen, fileSave } from "./filesystem";
|
import { fileOpen, fileSave } from "./filesystem";
|
||||||
import { cleanAppStateForExport, clearAppStateForDatabase } from "../appState";
|
import { cleanAppStateForExport, clearAppStateForDatabase } from "../appState";
|
||||||
import {
|
import {
|
||||||
|
DEFAULT_FILENAME,
|
||||||
EXPORT_DATA_TYPES,
|
EXPORT_DATA_TYPES,
|
||||||
EXPORT_SOURCE,
|
EXPORT_SOURCE,
|
||||||
MIME_TYPES,
|
MIME_TYPES,
|
||||||
@@ -71,6 +72,8 @@ export const saveAsJSON = async (
|
|||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
files: BinaryFiles,
|
files: BinaryFiles,
|
||||||
|
/** filename */
|
||||||
|
name: string = appState.name || DEFAULT_FILENAME,
|
||||||
) => {
|
) => {
|
||||||
const serialized = serializeAsJSON(elements, appState, files, "local");
|
const serialized = serializeAsJSON(elements, appState, files, "local");
|
||||||
const blob = new Blob([serialized], {
|
const blob = new Blob([serialized], {
|
||||||
@@ -78,7 +81,7 @@ export const saveAsJSON = async (
|
|||||||
});
|
});
|
||||||
|
|
||||||
const fileHandle = await fileSave(blob, {
|
const fileHandle = await fileSave(blob, {
|
||||||
name: appState.name,
|
name,
|
||||||
extension: "excalidraw",
|
extension: "excalidraw",
|
||||||
description: "Excalidraw file",
|
description: "Excalidraw file",
|
||||||
fileHandle: isImageFileHandle(appState.fileHandle)
|
fileHandle: isImageFileHandle(appState.fileHandle)
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ export const resaveAsImageWithScene = async (
|
|||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
files: BinaryFiles,
|
files: BinaryFiles,
|
||||||
|
name: string,
|
||||||
) => {
|
) => {
|
||||||
const { exportBackground, viewBackgroundColor, name, fileHandle } = appState;
|
const { exportBackground, viewBackgroundColor, fileHandle } = appState;
|
||||||
|
|
||||||
const fileHandleType = getFileHandleType(fileHandle);
|
const fileHandleType = getFileHandleType(fileHandle);
|
||||||
|
|
||||||
|
|||||||
@@ -462,6 +462,7 @@ export const restoreElements = (
|
|||||||
refreshTextDimensions(
|
refreshTextDimensions(
|
||||||
element,
|
element,
|
||||||
getContainerElement(element, restoredElementsMap),
|
getContainerElement(element, restoredElementsMap),
|
||||||
|
restoredElementsMap,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -822,4 +822,22 @@ describe("Test Transform", () => {
|
|||||||
"Duplicate id found for rect-1",
|
"Duplicate id found for rect-1",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should contains customData if provided", () => {
|
||||||
|
const rawData = [
|
||||||
|
{
|
||||||
|
type: "rectangle",
|
||||||
|
x: 100,
|
||||||
|
y: 100,
|
||||||
|
customData: { createdBy: "user01" },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const convertedElements = convertToExcalidrawElements(
|
||||||
|
rawData as ExcalidrawElementSkeleton[],
|
||||||
|
opts,
|
||||||
|
);
|
||||||
|
expect(convertedElements[0].customData).toStrictEqual({
|
||||||
|
createdBy: "user01",
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -39,11 +39,12 @@ import {
|
|||||||
ExcalidrawTextElement,
|
ExcalidrawTextElement,
|
||||||
FileId,
|
FileId,
|
||||||
FontFamilyValues,
|
FontFamilyValues,
|
||||||
|
NonDeletedSceneElementsMap,
|
||||||
TextAlign,
|
TextAlign,
|
||||||
VerticalAlign,
|
VerticalAlign,
|
||||||
} from "../element/types";
|
} from "../element/types";
|
||||||
import { MarkOptional } from "../utility-types";
|
import { MarkOptional } from "../utility-types";
|
||||||
import { arrayToMap, assertNever, cloneJSON, getFontString } from "../utils";
|
import { assertNever, cloneJSON, getFontString, toBrandedType } from "../utils";
|
||||||
import { getSizeFromPoints } from "../points";
|
import { getSizeFromPoints } from "../points";
|
||||||
import { randomId } from "../random";
|
import { randomId } from "../random";
|
||||||
|
|
||||||
@@ -222,7 +223,7 @@ const bindTextToContainer = (
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
redrawTextBoundingBox(textElement, container);
|
redrawTextBoundingBox(textElement, container, elementsMap);
|
||||||
return [container, textElement] as const;
|
return [container, textElement] as const;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -231,6 +232,7 @@ const bindLinearElementToElement = (
|
|||||||
start: ValidLinearElement["start"],
|
start: ValidLinearElement["start"],
|
||||||
end: ValidLinearElement["end"],
|
end: ValidLinearElement["end"],
|
||||||
elementStore: ElementStore,
|
elementStore: ElementStore,
|
||||||
|
elementsMap: NonDeletedSceneElementsMap,
|
||||||
): {
|
): {
|
||||||
linearElement: ExcalidrawLinearElement;
|
linearElement: ExcalidrawLinearElement;
|
||||||
startBoundElement?: ExcalidrawElement;
|
startBoundElement?: ExcalidrawElement;
|
||||||
@@ -316,6 +318,7 @@ const bindLinearElementToElement = (
|
|||||||
linearElement,
|
linearElement,
|
||||||
startBoundElement as ExcalidrawBindableElement,
|
startBoundElement as ExcalidrawBindableElement,
|
||||||
"start",
|
"start",
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -390,6 +393,7 @@ const bindLinearElementToElement = (
|
|||||||
linearElement,
|
linearElement,
|
||||||
endBoundElement as ExcalidrawBindableElement,
|
endBoundElement as ExcalidrawBindableElement,
|
||||||
"end",
|
"end",
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -457,6 +461,10 @@ class ElementStore {
|
|||||||
return Array.from(this.excalidrawElements.values());
|
return Array.from(this.excalidrawElements.values());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
getElementsMap = () => {
|
||||||
|
return toBrandedType<NonDeletedSceneElementsMap>(this.excalidrawElements);
|
||||||
|
};
|
||||||
|
|
||||||
getElement = (id: string) => {
|
getElement = (id: string) => {
|
||||||
return this.excalidrawElements.get(id);
|
return this.excalidrawElements.get(id);
|
||||||
};
|
};
|
||||||
@@ -612,6 +620,7 @@ export const convertToExcalidrawElements = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const elementsMap = elementStore.getElementsMap();
|
||||||
// Add labels and arrow bindings
|
// Add labels and arrow bindings
|
||||||
for (const [id, element] of elementsWithIds) {
|
for (const [id, element] of elementsWithIds) {
|
||||||
const excalidrawElement = elementStore.getElement(id)!;
|
const excalidrawElement = elementStore.getElement(id)!;
|
||||||
@@ -625,7 +634,7 @@ export const convertToExcalidrawElements = (
|
|||||||
let [container, text] = bindTextToContainer(
|
let [container, text] = bindTextToContainer(
|
||||||
excalidrawElement,
|
excalidrawElement,
|
||||||
element?.label,
|
element?.label,
|
||||||
arrayToMap(elementStore.getElements()),
|
elementsMap,
|
||||||
);
|
);
|
||||||
elementStore.add(container);
|
elementStore.add(container);
|
||||||
elementStore.add(text);
|
elementStore.add(text);
|
||||||
@@ -653,6 +662,7 @@ export const convertToExcalidrawElements = (
|
|||||||
originalStart,
|
originalStart,
|
||||||
originalEnd,
|
originalEnd,
|
||||||
elementStore,
|
elementStore,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
container = linearElement;
|
container = linearElement;
|
||||||
elementStore.add(linearElement);
|
elementStore.add(linearElement);
|
||||||
@@ -677,6 +687,7 @@ export const convertToExcalidrawElements = (
|
|||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
elementStore,
|
elementStore,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
|
|
||||||
elementStore.add(linearElement);
|
elementStore.add(linearElement);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { AppState } from "../types";
|
import { AppState } from "../types";
|
||||||
import { sceneCoordsToViewportCoords } from "../utils";
|
import { sceneCoordsToViewportCoords } from "../utils";
|
||||||
import { NonDeletedExcalidrawElement } from "./types";
|
import { ElementsMap, NonDeletedExcalidrawElement } from "./types";
|
||||||
import { getElementAbsoluteCoords } from ".";
|
import { getElementAbsoluteCoords } from ".";
|
||||||
import { useExcalidrawAppState } from "../components/App";
|
import { useExcalidrawAppState } from "../components/App";
|
||||||
|
|
||||||
@@ -11,8 +11,9 @@ const CONTAINER_PADDING = 5;
|
|||||||
const getContainerCoords = (
|
const getContainerCoords = (
|
||||||
element: NonDeletedExcalidrawElement,
|
element: NonDeletedExcalidrawElement,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
const [x1, y1] = getElementAbsoluteCoords(element);
|
const [x1, y1] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
const { x: viewportX, y: viewportY } = sceneCoordsToViewportCoords(
|
const { x: viewportX, y: viewportY } = sceneCoordsToViewportCoords(
|
||||||
{ sceneX: x1 + element.width, sceneY: y1 },
|
{ sceneX: x1 + element.width, sceneY: y1 },
|
||||||
appState,
|
appState,
|
||||||
@@ -25,9 +26,11 @@ const getContainerCoords = (
|
|||||||
export const ElementCanvasButtons = ({
|
export const ElementCanvasButtons = ({
|
||||||
children,
|
children,
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
element: NonDeletedExcalidrawElement;
|
element: NonDeletedExcalidrawElement;
|
||||||
|
elementsMap: ElementsMap;
|
||||||
}) => {
|
}) => {
|
||||||
const appState = useExcalidrawAppState();
|
const appState = useExcalidrawAppState();
|
||||||
|
|
||||||
@@ -42,7 +45,7 @@ export const ElementCanvasButtons = ({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { x, y } = getContainerCoords(element, appState);
|
const { x, y } = getContainerCoords(element, appState, elementsMap);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import {
|
|||||||
NonDeletedExcalidrawElement,
|
NonDeletedExcalidrawElement,
|
||||||
PointBinding,
|
PointBinding,
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
|
ElementsMap,
|
||||||
|
NonDeletedSceneElementsMap,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import { getElementAtPosition } from "../scene";
|
import { getElementAtPosition } from "../scene";
|
||||||
import { AppState } from "../types";
|
import { AppState } from "../types";
|
||||||
@@ -66,6 +68,7 @@ export const bindOrUnbindLinearElement = (
|
|||||||
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
||||||
startBindingElement: ExcalidrawBindableElement | null | "keep",
|
startBindingElement: ExcalidrawBindableElement | null | "keep",
|
||||||
endBindingElement: ExcalidrawBindableElement | null | "keep",
|
endBindingElement: ExcalidrawBindableElement | null | "keep",
|
||||||
|
elementsMap: NonDeletedSceneElementsMap,
|
||||||
): void => {
|
): void => {
|
||||||
const boundToElementIds: Set<ExcalidrawBindableElement["id"]> = new Set();
|
const boundToElementIds: Set<ExcalidrawBindableElement["id"]> = new Set();
|
||||||
const unboundFromElementIds: Set<ExcalidrawBindableElement["id"]> = new Set();
|
const unboundFromElementIds: Set<ExcalidrawBindableElement["id"]> = new Set();
|
||||||
@@ -76,6 +79,7 @@ export const bindOrUnbindLinearElement = (
|
|||||||
"start",
|
"start",
|
||||||
boundToElementIds,
|
boundToElementIds,
|
||||||
unboundFromElementIds,
|
unboundFromElementIds,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
bindOrUnbindLinearElementEdge(
|
bindOrUnbindLinearElementEdge(
|
||||||
linearElement,
|
linearElement,
|
||||||
@@ -84,6 +88,7 @@ export const bindOrUnbindLinearElement = (
|
|||||||
"end",
|
"end",
|
||||||
boundToElementIds,
|
boundToElementIds,
|
||||||
unboundFromElementIds,
|
unboundFromElementIds,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
|
|
||||||
const onlyUnbound = Array.from(unboundFromElementIds).filter(
|
const onlyUnbound = Array.from(unboundFromElementIds).filter(
|
||||||
@@ -111,6 +116,7 @@ const bindOrUnbindLinearElementEdge = (
|
|||||||
boundToElementIds: Set<ExcalidrawBindableElement["id"]>,
|
boundToElementIds: Set<ExcalidrawBindableElement["id"]>,
|
||||||
// Is mutated
|
// Is mutated
|
||||||
unboundFromElementIds: Set<ExcalidrawBindableElement["id"]>,
|
unboundFromElementIds: Set<ExcalidrawBindableElement["id"]>,
|
||||||
|
elementsMap: NonDeletedSceneElementsMap,
|
||||||
): void => {
|
): void => {
|
||||||
if (bindableElement !== "keep") {
|
if (bindableElement !== "keep") {
|
||||||
if (bindableElement != null) {
|
if (bindableElement != null) {
|
||||||
@@ -127,7 +133,12 @@ const bindOrUnbindLinearElementEdge = (
|
|||||||
: startOrEnd === "start" ||
|
: startOrEnd === "start" ||
|
||||||
otherEdgeBindableElement.id !== bindableElement.id)
|
otherEdgeBindableElement.id !== bindableElement.id)
|
||||||
) {
|
) {
|
||||||
bindLinearElement(linearElement, bindableElement, startOrEnd);
|
bindLinearElement(
|
||||||
|
linearElement,
|
||||||
|
bindableElement,
|
||||||
|
startOrEnd,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
boundToElementIds.add(bindableElement.id);
|
boundToElementIds.add(bindableElement.id);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -140,31 +151,48 @@ const bindOrUnbindLinearElementEdge = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const bindOrUnbindSelectedElements = (
|
export const bindOrUnbindSelectedElements = (
|
||||||
elements: NonDeleted<ExcalidrawElement>[],
|
selectedElements: NonDeleted<ExcalidrawElement>[],
|
||||||
|
elements: readonly ExcalidrawElement[],
|
||||||
|
elementsMap: NonDeletedSceneElementsMap,
|
||||||
): void => {
|
): void => {
|
||||||
elements.forEach((element) => {
|
selectedElements.forEach((selectedElement) => {
|
||||||
if (isBindingElement(element)) {
|
if (isBindingElement(selectedElement)) {
|
||||||
bindOrUnbindLinearElement(
|
bindOrUnbindLinearElement(
|
||||||
element,
|
selectedElement,
|
||||||
getElligibleElementForBindingElement(element, "start"),
|
getElligibleElementForBindingElement(
|
||||||
getElligibleElementForBindingElement(element, "end"),
|
selectedElement,
|
||||||
|
"start",
|
||||||
|
elements,
|
||||||
|
elementsMap,
|
||||||
|
),
|
||||||
|
getElligibleElementForBindingElement(
|
||||||
|
selectedElement,
|
||||||
|
"end",
|
||||||
|
elements,
|
||||||
|
elementsMap,
|
||||||
|
),
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
} else if (isBindableElement(element)) {
|
} else if (isBindableElement(selectedElement)) {
|
||||||
maybeBindBindableElement(element);
|
maybeBindBindableElement(selectedElement, elementsMap);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const maybeBindBindableElement = (
|
const maybeBindBindableElement = (
|
||||||
bindableElement: NonDeleted<ExcalidrawBindableElement>,
|
bindableElement: NonDeleted<ExcalidrawBindableElement>,
|
||||||
|
elementsMap: NonDeletedSceneElementsMap,
|
||||||
): void => {
|
): void => {
|
||||||
getElligibleElementsForBindableElementAndWhere(bindableElement).forEach(
|
getElligibleElementsForBindableElementAndWhere(
|
||||||
([linearElement, where]) =>
|
bindableElement,
|
||||||
bindOrUnbindLinearElement(
|
elementsMap,
|
||||||
linearElement,
|
).forEach(([linearElement, where]) =>
|
||||||
where === "end" ? "keep" : bindableElement,
|
bindOrUnbindLinearElement(
|
||||||
where === "start" ? "keep" : bindableElement,
|
linearElement,
|
||||||
),
|
where === "end" ? "keep" : bindableElement,
|
||||||
|
where === "start" ? "keep" : bindableElement,
|
||||||
|
elementsMap,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -173,11 +201,21 @@ export const maybeBindLinearElement = (
|
|||||||
appState: AppState,
|
appState: AppState,
|
||||||
scene: Scene,
|
scene: Scene,
|
||||||
pointerCoords: { x: number; y: number },
|
pointerCoords: { x: number; y: number },
|
||||||
|
elementsMap: NonDeletedSceneElementsMap,
|
||||||
): void => {
|
): void => {
|
||||||
if (appState.startBoundElement != null) {
|
if (appState.startBoundElement != null) {
|
||||||
bindLinearElement(linearElement, appState.startBoundElement, "start");
|
bindLinearElement(
|
||||||
|
linearElement,
|
||||||
|
appState.startBoundElement,
|
||||||
|
"start",
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const hoveredElement = getHoveredElementForBinding(pointerCoords, scene);
|
const hoveredElement = getHoveredElementForBinding(
|
||||||
|
pointerCoords,
|
||||||
|
scene.getNonDeletedElements(),
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
if (
|
if (
|
||||||
hoveredElement != null &&
|
hoveredElement != null &&
|
||||||
!isLinearElementSimpleAndAlreadyBoundOnOppositeEdge(
|
!isLinearElementSimpleAndAlreadyBoundOnOppositeEdge(
|
||||||
@@ -186,7 +224,7 @@ export const maybeBindLinearElement = (
|
|||||||
"end",
|
"end",
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
bindLinearElement(linearElement, hoveredElement, "end");
|
bindLinearElement(linearElement, hoveredElement, "end", elementsMap);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -194,11 +232,17 @@ export const bindLinearElement = (
|
|||||||
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
||||||
hoveredElement: ExcalidrawBindableElement,
|
hoveredElement: ExcalidrawBindableElement,
|
||||||
startOrEnd: "start" | "end",
|
startOrEnd: "start" | "end",
|
||||||
|
elementsMap: NonDeletedSceneElementsMap,
|
||||||
): void => {
|
): void => {
|
||||||
mutateElement(linearElement, {
|
mutateElement(linearElement, {
|
||||||
[startOrEnd === "start" ? "startBinding" : "endBinding"]: {
|
[startOrEnd === "start" ? "startBinding" : "endBinding"]: {
|
||||||
elementId: hoveredElement.id,
|
elementId: hoveredElement.id,
|
||||||
...calculateFocusAndGap(linearElement, hoveredElement, startOrEnd),
|
...calculateFocusAndGap(
|
||||||
|
linearElement,
|
||||||
|
hoveredElement,
|
||||||
|
startOrEnd,
|
||||||
|
elementsMap,
|
||||||
|
),
|
||||||
} as PointBinding,
|
} as PointBinding,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -240,10 +284,11 @@ export const isLinearElementSimpleAndAlreadyBound = (
|
|||||||
|
|
||||||
export const unbindLinearElements = (
|
export const unbindLinearElements = (
|
||||||
elements: NonDeleted<ExcalidrawElement>[],
|
elements: NonDeleted<ExcalidrawElement>[],
|
||||||
|
elementsMap: NonDeletedSceneElementsMap,
|
||||||
): void => {
|
): void => {
|
||||||
elements.forEach((element) => {
|
elements.forEach((element) => {
|
||||||
if (isBindingElement(element)) {
|
if (isBindingElement(element)) {
|
||||||
bindOrUnbindLinearElement(element, null, null);
|
bindOrUnbindLinearElement(element, null, null, elementsMap);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -266,13 +311,14 @@ export const getHoveredElementForBinding = (
|
|||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
},
|
},
|
||||||
scene: Scene,
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
|
elementsMap: NonDeletedSceneElementsMap,
|
||||||
): NonDeleted<ExcalidrawBindableElement> | null => {
|
): NonDeleted<ExcalidrawBindableElement> | null => {
|
||||||
const hoveredElement = getElementAtPosition(
|
const hoveredElement = getElementAtPosition(
|
||||||
scene.getNonDeletedElements(),
|
elements,
|
||||||
(element) =>
|
(element) =>
|
||||||
isBindableElement(element, false) &&
|
isBindableElement(element, false) &&
|
||||||
bindingBorderTest(element, pointerCoords),
|
bindingBorderTest(element, pointerCoords, elementsMap),
|
||||||
);
|
);
|
||||||
return hoveredElement as NonDeleted<ExcalidrawBindableElement> | null;
|
return hoveredElement as NonDeleted<ExcalidrawBindableElement> | null;
|
||||||
};
|
};
|
||||||
@@ -281,21 +327,33 @@ const calculateFocusAndGap = (
|
|||||||
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
||||||
hoveredElement: ExcalidrawBindableElement,
|
hoveredElement: ExcalidrawBindableElement,
|
||||||
startOrEnd: "start" | "end",
|
startOrEnd: "start" | "end",
|
||||||
|
elementsMap: NonDeletedSceneElementsMap,
|
||||||
): { focus: number; gap: number } => {
|
): { focus: number; gap: number } => {
|
||||||
const direction = startOrEnd === "start" ? -1 : 1;
|
const direction = startOrEnd === "start" ? -1 : 1;
|
||||||
const edgePointIndex = direction === -1 ? 0 : linearElement.points.length - 1;
|
const edgePointIndex = direction === -1 ? 0 : linearElement.points.length - 1;
|
||||||
const adjacentPointIndex = edgePointIndex - direction;
|
const adjacentPointIndex = edgePointIndex - direction;
|
||||||
|
|
||||||
const edgePoint = LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
const edgePoint = LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
||||||
linearElement,
|
linearElement,
|
||||||
edgePointIndex,
|
edgePointIndex,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
const adjacentPoint = LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
const adjacentPoint = LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
||||||
linearElement,
|
linearElement,
|
||||||
adjacentPointIndex,
|
adjacentPointIndex,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
focus: determineFocusDistance(hoveredElement, adjacentPoint, edgePoint),
|
focus: determineFocusDistance(
|
||||||
gap: Math.max(1, distanceToBindableElement(hoveredElement, edgePoint)),
|
hoveredElement,
|
||||||
|
adjacentPoint,
|
||||||
|
edgePoint,
|
||||||
|
elementsMap,
|
||||||
|
),
|
||||||
|
gap: Math.max(
|
||||||
|
1,
|
||||||
|
distanceToBindableElement(hoveredElement, edgePoint, elementsMap),
|
||||||
|
),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -306,6 +364,8 @@ const calculateFocusAndGap = (
|
|||||||
// in explicitly.
|
// in explicitly.
|
||||||
export const updateBoundElements = (
|
export const updateBoundElements = (
|
||||||
changedElement: NonDeletedExcalidrawElement,
|
changedElement: NonDeletedExcalidrawElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
|
|
||||||
options?: {
|
options?: {
|
||||||
simultaneouslyUpdated?: readonly ExcalidrawElement[];
|
simultaneouslyUpdated?: readonly ExcalidrawElement[];
|
||||||
newSize?: { width: number; height: number };
|
newSize?: { width: number; height: number };
|
||||||
@@ -355,12 +415,14 @@ export const updateBoundElements = (
|
|||||||
"start",
|
"start",
|
||||||
startBinding,
|
startBinding,
|
||||||
changedElement as ExcalidrawBindableElement,
|
changedElement as ExcalidrawBindableElement,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
updateBoundPoint(
|
updateBoundPoint(
|
||||||
element,
|
element,
|
||||||
"end",
|
"end",
|
||||||
endBinding,
|
endBinding,
|
||||||
changedElement as ExcalidrawBindableElement,
|
changedElement as ExcalidrawBindableElement,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
const boundText = getBoundTextElement(
|
const boundText = getBoundTextElement(
|
||||||
element,
|
element,
|
||||||
@@ -393,6 +455,7 @@ const updateBoundPoint = (
|
|||||||
startOrEnd: "start" | "end",
|
startOrEnd: "start" | "end",
|
||||||
binding: PointBinding | null | undefined,
|
binding: PointBinding | null | undefined,
|
||||||
changedElement: ExcalidrawBindableElement,
|
changedElement: ExcalidrawBindableElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): void => {
|
): void => {
|
||||||
if (
|
if (
|
||||||
binding == null ||
|
binding == null ||
|
||||||
@@ -414,11 +477,13 @@ const updateBoundPoint = (
|
|||||||
const adjacentPoint = LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
const adjacentPoint = LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
||||||
linearElement,
|
linearElement,
|
||||||
adjacentPointIndex,
|
adjacentPointIndex,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
const focusPointAbsolute = determineFocusPoint(
|
const focusPointAbsolute = determineFocusPoint(
|
||||||
bindingElement,
|
bindingElement,
|
||||||
binding.focus,
|
binding.focus,
|
||||||
adjacentPoint,
|
adjacentPoint,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
let newEdgePoint;
|
let newEdgePoint;
|
||||||
// The linear element was not originally pointing inside the bound shape,
|
// The linear element was not originally pointing inside the bound shape,
|
||||||
@@ -431,6 +496,7 @@ const updateBoundPoint = (
|
|||||||
adjacentPoint,
|
adjacentPoint,
|
||||||
focusPointAbsolute,
|
focusPointAbsolute,
|
||||||
binding.gap,
|
binding.gap,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
if (intersections.length === 0) {
|
if (intersections.length === 0) {
|
||||||
// This should never happen, since focusPoint should always be
|
// This should never happen, since focusPoint should always be
|
||||||
@@ -449,6 +515,7 @@ const updateBoundPoint = (
|
|||||||
point: LinearElementEditor.pointFromAbsoluteCoords(
|
point: LinearElementEditor.pointFromAbsoluteCoords(
|
||||||
linearElement,
|
linearElement,
|
||||||
newEdgePoint,
|
newEdgePoint,
|
||||||
|
elementsMap,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -479,30 +546,47 @@ const maybeCalculateNewGapWhenScaling = (
|
|||||||
|
|
||||||
// TODO: this is a bottleneck, optimise
|
// TODO: this is a bottleneck, optimise
|
||||||
export const getEligibleElementsForBinding = (
|
export const getEligibleElementsForBinding = (
|
||||||
elements: NonDeleted<ExcalidrawElement>[],
|
selectedElements: NonDeleted<ExcalidrawElement>[],
|
||||||
|
elements: readonly ExcalidrawElement[],
|
||||||
|
elementsMap: NonDeletedSceneElementsMap,
|
||||||
): SuggestedBinding[] => {
|
): SuggestedBinding[] => {
|
||||||
const includedElementIds = new Set(elements.map(({ id }) => id));
|
const includedElementIds = new Set(selectedElements.map(({ id }) => id));
|
||||||
return elements.flatMap((element) =>
|
return selectedElements.flatMap((selectedElement) =>
|
||||||
isBindingElement(element, false)
|
isBindingElement(selectedElement, false)
|
||||||
? (getElligibleElementsForBindingElement(
|
? (getElligibleElementsForBindingElement(
|
||||||
element as NonDeleted<ExcalidrawLinearElement>,
|
selectedElement as NonDeleted<ExcalidrawLinearElement>,
|
||||||
|
elements,
|
||||||
|
elementsMap,
|
||||||
).filter(
|
).filter(
|
||||||
(element) => !includedElementIds.has(element.id),
|
(element) => !includedElementIds.has(element.id),
|
||||||
) as SuggestedBinding[])
|
) as SuggestedBinding[])
|
||||||
: isBindableElement(element, false)
|
: isBindableElement(selectedElement, false)
|
||||||
? getElligibleElementsForBindableElementAndWhere(element).filter(
|
? getElligibleElementsForBindableElementAndWhere(
|
||||||
(binding) => !includedElementIds.has(binding[0].id),
|
selectedElement,
|
||||||
)
|
elementsMap,
|
||||||
|
).filter((binding) => !includedElementIds.has(binding[0].id))
|
||||||
: [],
|
: [],
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getElligibleElementsForBindingElement = (
|
const getElligibleElementsForBindingElement = (
|
||||||
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
||||||
|
elements: readonly ExcalidrawElement[],
|
||||||
|
elementsMap: NonDeletedSceneElementsMap,
|
||||||
): NonDeleted<ExcalidrawBindableElement>[] => {
|
): NonDeleted<ExcalidrawBindableElement>[] => {
|
||||||
return [
|
return [
|
||||||
getElligibleElementForBindingElement(linearElement, "start"),
|
getElligibleElementForBindingElement(
|
||||||
getElligibleElementForBindingElement(linearElement, "end"),
|
linearElement,
|
||||||
|
"start",
|
||||||
|
elements,
|
||||||
|
elementsMap,
|
||||||
|
),
|
||||||
|
getElligibleElementForBindingElement(
|
||||||
|
linearElement,
|
||||||
|
"end",
|
||||||
|
elements,
|
||||||
|
elementsMap,
|
||||||
|
),
|
||||||
].filter(
|
].filter(
|
||||||
(element): element is NonDeleted<ExcalidrawBindableElement> =>
|
(element): element is NonDeleted<ExcalidrawBindableElement> =>
|
||||||
element != null,
|
element != null,
|
||||||
@@ -512,27 +596,37 @@ const getElligibleElementsForBindingElement = (
|
|||||||
const getElligibleElementForBindingElement = (
|
const getElligibleElementForBindingElement = (
|
||||||
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
||||||
startOrEnd: "start" | "end",
|
startOrEnd: "start" | "end",
|
||||||
|
elements: readonly ExcalidrawElement[],
|
||||||
|
elementsMap: NonDeletedSceneElementsMap,
|
||||||
): NonDeleted<ExcalidrawBindableElement> | null => {
|
): NonDeleted<ExcalidrawBindableElement> | null => {
|
||||||
return getHoveredElementForBinding(
|
return getHoveredElementForBinding(
|
||||||
getLinearElementEdgeCoors(linearElement, startOrEnd),
|
getLinearElementEdgeCoors(linearElement, startOrEnd, elementsMap),
|
||||||
Scene.getScene(linearElement)!,
|
elements,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getLinearElementEdgeCoors = (
|
const getLinearElementEdgeCoors = (
|
||||||
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
||||||
startOrEnd: "start" | "end",
|
startOrEnd: "start" | "end",
|
||||||
|
elementsMap: NonDeletedSceneElementsMap,
|
||||||
): { x: number; y: number } => {
|
): { x: number; y: number } => {
|
||||||
const index = startOrEnd === "start" ? 0 : -1;
|
const index = startOrEnd === "start" ? 0 : -1;
|
||||||
return tupleToCoors(
|
return tupleToCoors(
|
||||||
LinearElementEditor.getPointAtIndexGlobalCoordinates(linearElement, index),
|
LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
||||||
|
linearElement,
|
||||||
|
index,
|
||||||
|
elementsMap,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getElligibleElementsForBindableElementAndWhere = (
|
const getElligibleElementsForBindableElementAndWhere = (
|
||||||
bindableElement: NonDeleted<ExcalidrawBindableElement>,
|
bindableElement: NonDeleted<ExcalidrawBindableElement>,
|
||||||
|
elementsMap: NonDeletedSceneElementsMap,
|
||||||
): SuggestedPointBinding[] => {
|
): SuggestedPointBinding[] => {
|
||||||
return Scene.getScene(bindableElement)!
|
const scene = Scene.getScene(bindableElement)!;
|
||||||
|
return scene
|
||||||
.getNonDeletedElements()
|
.getNonDeletedElements()
|
||||||
.map((element) => {
|
.map((element) => {
|
||||||
if (!isBindingElement(element, false)) {
|
if (!isBindingElement(element, false)) {
|
||||||
@@ -542,11 +636,13 @@ const getElligibleElementsForBindableElementAndWhere = (
|
|||||||
element,
|
element,
|
||||||
"start",
|
"start",
|
||||||
bindableElement,
|
bindableElement,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
const canBindEnd = isLinearElementEligibleForNewBindingByBindable(
|
const canBindEnd = isLinearElementEligibleForNewBindingByBindable(
|
||||||
element,
|
element,
|
||||||
"end",
|
"end",
|
||||||
bindableElement,
|
bindableElement,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
if (!canBindStart && !canBindEnd) {
|
if (!canBindStart && !canBindEnd) {
|
||||||
return null;
|
return null;
|
||||||
@@ -564,6 +660,7 @@ const isLinearElementEligibleForNewBindingByBindable = (
|
|||||||
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
linearElement: NonDeleted<ExcalidrawLinearElement>,
|
||||||
startOrEnd: "start" | "end",
|
startOrEnd: "start" | "end",
|
||||||
bindableElement: NonDeleted<ExcalidrawBindableElement>,
|
bindableElement: NonDeleted<ExcalidrawBindableElement>,
|
||||||
|
elementsMap: NonDeletedSceneElementsMap,
|
||||||
): boolean => {
|
): boolean => {
|
||||||
const existingBinding =
|
const existingBinding =
|
||||||
linearElement[startOrEnd === "start" ? "startBinding" : "endBinding"];
|
linearElement[startOrEnd === "start" ? "startBinding" : "endBinding"];
|
||||||
@@ -576,7 +673,8 @@ const isLinearElementEligibleForNewBindingByBindable = (
|
|||||||
) &&
|
) &&
|
||||||
bindingBorderTest(
|
bindingBorderTest(
|
||||||
bindableElement,
|
bindableElement,
|
||||||
getLinearElementEdgeCoors(linearElement, startOrEnd),
|
getLinearElementEdgeCoors(linearElement, startOrEnd, elementsMap),
|
||||||
|
elementsMap,
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { ROUNDNESS } from "../constants";
|
import { ROUNDNESS } from "../constants";
|
||||||
|
import { arrayToMap } from "../utils";
|
||||||
import { getElementAbsoluteCoords, getElementBounds } from "./bounds";
|
import { getElementAbsoluteCoords, getElementBounds } from "./bounds";
|
||||||
import { ExcalidrawElement, ExcalidrawLinearElement } from "./types";
|
import { ExcalidrawElement, ExcalidrawLinearElement } from "./types";
|
||||||
|
|
||||||
@@ -35,35 +36,41 @@ const _ce = ({
|
|||||||
|
|
||||||
describe("getElementAbsoluteCoords", () => {
|
describe("getElementAbsoluteCoords", () => {
|
||||||
it("test x1 coordinate", () => {
|
it("test x1 coordinate", () => {
|
||||||
const [x1] = getElementAbsoluteCoords(_ce({ x: 10, y: 0, w: 10, h: 0 }));
|
const element = _ce({ x: 10, y: 20, w: 10, h: 0 });
|
||||||
|
const [x1] = getElementAbsoluteCoords(element, arrayToMap([element]));
|
||||||
expect(x1).toEqual(10);
|
expect(x1).toEqual(10);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("test x2 coordinate", () => {
|
it("test x2 coordinate", () => {
|
||||||
const [, , x2] = getElementAbsoluteCoords(
|
const element = _ce({ x: 10, y: 20, w: 10, h: 0 });
|
||||||
_ce({ x: 10, y: 0, w: 10, h: 0 }),
|
const [, , x2] = getElementAbsoluteCoords(element, arrayToMap([element]));
|
||||||
);
|
|
||||||
expect(x2).toEqual(20);
|
expect(x2).toEqual(20);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("test y1 coordinate", () => {
|
it("test y1 coordinate", () => {
|
||||||
const [, y1] = getElementAbsoluteCoords(_ce({ x: 0, y: 10, w: 0, h: 10 }));
|
const element = _ce({ x: 0, y: 10, w: 0, h: 10 });
|
||||||
|
const [, y1] = getElementAbsoluteCoords(element, arrayToMap([element]));
|
||||||
expect(y1).toEqual(10);
|
expect(y1).toEqual(10);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("test y2 coordinate", () => {
|
it("test y2 coordinate", () => {
|
||||||
const [, , , y2] = getElementAbsoluteCoords(
|
const element = _ce({ x: 0, y: 10, w: 0, h: 10 });
|
||||||
_ce({ x: 0, y: 10, w: 0, h: 10 }),
|
const [, , , y2] = getElementAbsoluteCoords(element, arrayToMap([element]));
|
||||||
);
|
|
||||||
expect(y2).toEqual(20);
|
expect(y2).toEqual(20);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("getElementBounds", () => {
|
describe("getElementBounds", () => {
|
||||||
it("rectangle", () => {
|
it("rectangle", () => {
|
||||||
const [x1, y1, x2, y2] = getElementBounds(
|
const element = _ce({
|
||||||
_ce({ x: 40, y: 30, w: 20, h: 10, a: Math.PI / 4, t: "rectangle" }),
|
x: 40,
|
||||||
);
|
y: 30,
|
||||||
|
w: 20,
|
||||||
|
h: 10,
|
||||||
|
a: Math.PI / 4,
|
||||||
|
t: "rectangle",
|
||||||
|
});
|
||||||
|
const [x1, y1, x2, y2] = getElementBounds(element, arrayToMap([element]));
|
||||||
expect(x1).toEqual(39.39339828220179);
|
expect(x1).toEqual(39.39339828220179);
|
||||||
expect(y1).toEqual(24.393398282201787);
|
expect(y1).toEqual(24.393398282201787);
|
||||||
expect(x2).toEqual(60.60660171779821);
|
expect(x2).toEqual(60.60660171779821);
|
||||||
@@ -71,9 +78,17 @@ describe("getElementBounds", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("diamond", () => {
|
it("diamond", () => {
|
||||||
const [x1, y1, x2, y2] = getElementBounds(
|
const element = _ce({
|
||||||
_ce({ x: 40, y: 30, w: 20, h: 10, a: Math.PI / 4, t: "diamond" }),
|
x: 40,
|
||||||
);
|
y: 30,
|
||||||
|
w: 20,
|
||||||
|
h: 10,
|
||||||
|
a: Math.PI / 4,
|
||||||
|
t: "diamond",
|
||||||
|
});
|
||||||
|
|
||||||
|
const [x1, y1, x2, y2] = getElementBounds(element, arrayToMap([element]));
|
||||||
|
|
||||||
expect(x1).toEqual(42.928932188134524);
|
expect(x1).toEqual(42.928932188134524);
|
||||||
expect(y1).toEqual(27.928932188134524);
|
expect(y1).toEqual(27.928932188134524);
|
||||||
expect(x2).toEqual(57.071067811865476);
|
expect(x2).toEqual(57.071067811865476);
|
||||||
@@ -81,9 +96,16 @@ describe("getElementBounds", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("ellipse", () => {
|
it("ellipse", () => {
|
||||||
const [x1, y1, x2, y2] = getElementBounds(
|
const element = _ce({
|
||||||
_ce({ x: 40, y: 30, w: 20, h: 10, a: Math.PI / 4, t: "ellipse" }),
|
x: 40,
|
||||||
);
|
y: 30,
|
||||||
|
w: 20,
|
||||||
|
h: 10,
|
||||||
|
a: Math.PI / 4,
|
||||||
|
t: "ellipse",
|
||||||
|
});
|
||||||
|
|
||||||
|
const [x1, y1, x2, y2] = getElementBounds(element, arrayToMap([element]));
|
||||||
expect(x1).toEqual(42.09430584957905);
|
expect(x1).toEqual(42.09430584957905);
|
||||||
expect(y1).toEqual(27.09430584957905);
|
expect(y1).toEqual(27.09430584957905);
|
||||||
expect(x2).toEqual(57.90569415042095);
|
expect(x2).toEqual(57.90569415042095);
|
||||||
@@ -91,7 +113,7 @@ describe("getElementBounds", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("curved line", () => {
|
it("curved line", () => {
|
||||||
const [x1, y1, x2, y2] = getElementBounds({
|
const element = {
|
||||||
..._ce({
|
..._ce({
|
||||||
t: "line",
|
t: "line",
|
||||||
x: 449.58203125,
|
x: 449.58203125,
|
||||||
@@ -105,7 +127,9 @@ describe("getElementBounds", () => {
|
|||||||
[67.33984375, 92.48828125] as [number, number],
|
[67.33984375, 92.48828125] as [number, number],
|
||||||
[-102.7890625, 52.15625] as [number, number],
|
[-102.7890625, 52.15625] as [number, number],
|
||||||
],
|
],
|
||||||
} as ExcalidrawLinearElement);
|
} as ExcalidrawLinearElement;
|
||||||
|
|
||||||
|
const [x1, y1, x2, y2] = getElementBounds(element, arrayToMap([element]));
|
||||||
expect(x1).toEqual(360.3176068760539);
|
expect(x1).toEqual(360.3176068760539);
|
||||||
expect(y1).toEqual(185.90654264413516);
|
expect(y1).toEqual(185.90654264413516);
|
||||||
expect(x2).toEqual(480.87005902729743);
|
expect(x2).toEqual(480.87005902729743);
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import {
|
|||||||
ExcalidrawFreeDrawElement,
|
ExcalidrawFreeDrawElement,
|
||||||
NonDeleted,
|
NonDeleted,
|
||||||
ExcalidrawTextElementWithContainer,
|
ExcalidrawTextElementWithContainer,
|
||||||
ElementsMapOrArray,
|
|
||||||
ElementsMap,
|
ElementsMap,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import { distance2d, rotate, rotatePoint } from "../math";
|
import { distance2d, rotate, rotatePoint } from "../math";
|
||||||
@@ -25,7 +24,7 @@ import { getBoundTextElement, getContainerElement } from "./textElement";
|
|||||||
import { LinearElementEditor } from "./linearElementEditor";
|
import { LinearElementEditor } from "./linearElementEditor";
|
||||||
import { Mutable } from "../utility-types";
|
import { Mutable } from "../utility-types";
|
||||||
import { ShapeCache } from "../scene/ShapeCache";
|
import { ShapeCache } from "../scene/ShapeCache";
|
||||||
import Scene from "../scene/Scene";
|
import { arrayToMap } from "../utils";
|
||||||
|
|
||||||
export type RectangleBox = {
|
export type RectangleBox = {
|
||||||
x: number;
|
x: number;
|
||||||
@@ -63,7 +62,7 @@ export class ElementBounds {
|
|||||||
}
|
}
|
||||||
>();
|
>();
|
||||||
|
|
||||||
static getBounds(element: ExcalidrawElement) {
|
static getBounds(element: ExcalidrawElement, elementsMap: ElementsMap) {
|
||||||
const cachedBounds = ElementBounds.boundsCache.get(element);
|
const cachedBounds = ElementBounds.boundsCache.get(element);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -75,23 +74,12 @@ export class ElementBounds {
|
|||||||
) {
|
) {
|
||||||
return cachedBounds.bounds;
|
return cachedBounds.bounds;
|
||||||
}
|
}
|
||||||
const scene = Scene.getScene(element);
|
const bounds = ElementBounds.calculateBounds(element, elementsMap);
|
||||||
const bounds = ElementBounds.calculateBounds(
|
|
||||||
element,
|
|
||||||
scene?.getNonDeletedElementsMap() || new Map(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// hack to ensure that downstream checks could retrieve element Scene
|
ElementBounds.boundsCache.set(element, {
|
||||||
// so as to have correctly calculated bounds
|
version: element.version,
|
||||||
// FIXME remove when we get rid of all the id:Scene / element:Scene mapping
|
bounds,
|
||||||
const shouldCache = !!scene;
|
});
|
||||||
|
|
||||||
if (shouldCache) {
|
|
||||||
ElementBounds.boundsCache.set(element, {
|
|
||||||
version: element.version,
|
|
||||||
bounds,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return bounds;
|
return bounds;
|
||||||
}
|
}
|
||||||
@@ -102,8 +90,10 @@ export class ElementBounds {
|
|||||||
): Bounds {
|
): Bounds {
|
||||||
let bounds: Bounds;
|
let bounds: Bounds;
|
||||||
|
|
||||||
const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(
|
||||||
|
element,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
if (isFreeDrawElement(element)) {
|
if (isFreeDrawElement(element)) {
|
||||||
const [minX, minY, maxX, maxY] = getBoundsFromPoints(
|
const [minX, minY, maxX, maxY] = getBoundsFromPoints(
|
||||||
element.points.map(([x, y]) =>
|
element.points.map(([x, y]) =>
|
||||||
@@ -159,10 +149,9 @@ export class ElementBounds {
|
|||||||
// This set of functions retrieves the absolute position of the 4 points.
|
// This set of functions retrieves the absolute position of the 4 points.
|
||||||
export const getElementAbsoluteCoords = (
|
export const getElementAbsoluteCoords = (
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
includeBoundText: boolean = false,
|
includeBoundText: boolean = false,
|
||||||
): [number, number, number, number, number, number] => {
|
): [number, number, number, number, number, number] => {
|
||||||
const elementsMap =
|
|
||||||
Scene.getScene(element)?.getElementsMapIncludingDeleted() || new Map();
|
|
||||||
if (isFreeDrawElement(element)) {
|
if (isFreeDrawElement(element)) {
|
||||||
return getFreeDrawElementAbsoluteCoords(element);
|
return getFreeDrawElementAbsoluteCoords(element);
|
||||||
} else if (isLinearElement(element)) {
|
} else if (isLinearElement(element)) {
|
||||||
@@ -179,6 +168,7 @@ export const getElementAbsoluteCoords = (
|
|||||||
const coords = LinearElementEditor.getBoundTextElementPosition(
|
const coords = LinearElementEditor.getBoundTextElementPosition(
|
||||||
container,
|
container,
|
||||||
element as ExcalidrawTextElementWithContainer,
|
element as ExcalidrawTextElementWithContainer,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
return [
|
return [
|
||||||
coords.x,
|
coords.x,
|
||||||
@@ -207,8 +197,12 @@ export const getElementAbsoluteCoords = (
|
|||||||
*/
|
*/
|
||||||
export const getElementLineSegments = (
|
export const getElementLineSegments = (
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): [Point, Point][] => {
|
): [Point, Point][] => {
|
||||||
const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(
|
||||||
|
element,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
|
||||||
const center: Point = [cx, cy];
|
const center: Point = [cx, cy];
|
||||||
|
|
||||||
@@ -703,6 +697,7 @@ const getLinearElementRotatedBounds = (
|
|||||||
if (boundTextElement) {
|
if (boundTextElement) {
|
||||||
const coordsWithBoundText = LinearElementEditor.getMinMaxXYWithBoundText(
|
const coordsWithBoundText = LinearElementEditor.getMinMaxXYWithBoundText(
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
[x, y, x, y],
|
[x, y, x, y],
|
||||||
boundTextElement,
|
boundTextElement,
|
||||||
);
|
);
|
||||||
@@ -727,6 +722,7 @@ const getLinearElementRotatedBounds = (
|
|||||||
if (boundTextElement) {
|
if (boundTextElement) {
|
||||||
const coordsWithBoundText = LinearElementEditor.getMinMaxXYWithBoundText(
|
const coordsWithBoundText = LinearElementEditor.getMinMaxXYWithBoundText(
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
coords,
|
coords,
|
||||||
boundTextElement,
|
boundTextElement,
|
||||||
);
|
);
|
||||||
@@ -740,11 +736,17 @@ const getLinearElementRotatedBounds = (
|
|||||||
return coords;
|
return coords;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getElementBounds = (element: ExcalidrawElement): Bounds => {
|
export const getElementBounds = (
|
||||||
return ElementBounds.getBounds(element);
|
element: ExcalidrawElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
|
): Bounds => {
|
||||||
|
return ElementBounds.getBounds(element, elementsMap);
|
||||||
};
|
};
|
||||||
export const getCommonBounds = (elements: ElementsMapOrArray): Bounds => {
|
|
||||||
if ("size" in elements ? !elements.size : !elements.length) {
|
export const getCommonBounds = (
|
||||||
|
elements: readonly ExcalidrawElement[],
|
||||||
|
): Bounds => {
|
||||||
|
if (!elements.length) {
|
||||||
return [0, 0, 0, 0];
|
return [0, 0, 0, 0];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -753,8 +755,10 @@ export const getCommonBounds = (elements: ElementsMapOrArray): Bounds => {
|
|||||||
let minY = Infinity;
|
let minY = Infinity;
|
||||||
let maxY = -Infinity;
|
let maxY = -Infinity;
|
||||||
|
|
||||||
|
const elementsMap = arrayToMap(elements);
|
||||||
|
|
||||||
elements.forEach((element) => {
|
elements.forEach((element) => {
|
||||||
const [x1, y1, x2, y2] = getElementBounds(element);
|
const [x1, y1, x2, y2] = getElementBounds(element, elementsMap);
|
||||||
minX = Math.min(minX, x1);
|
minX = Math.min(minX, x1);
|
||||||
minY = Math.min(minY, y1);
|
minY = Math.min(minY, y1);
|
||||||
maxX = Math.max(maxX, x2);
|
maxX = Math.max(maxX, x2);
|
||||||
@@ -860,9 +864,9 @@ export const getClosestElementBounds = (
|
|||||||
|
|
||||||
let minDistance = Infinity;
|
let minDistance = Infinity;
|
||||||
let closestElement = elements[0];
|
let closestElement = elements[0];
|
||||||
|
const elementsMap = arrayToMap(elements);
|
||||||
elements.forEach((element) => {
|
elements.forEach((element) => {
|
||||||
const [x1, y1, x2, y2] = getElementBounds(element);
|
const [x1, y1, x2, y2] = getElementBounds(element, elementsMap);
|
||||||
const distance = distance2d((x1 + x2) / 2, (y1 + y2) / 2, from.x, from.y);
|
const distance = distance2d((x1 + x2) / 2, (y1 + y2) / 2, from.x, from.y);
|
||||||
|
|
||||||
if (distance < minDistance) {
|
if (distance < minDistance) {
|
||||||
@@ -871,7 +875,7 @@ export const getClosestElementBounds = (
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return getElementBounds(closestElement);
|
return getElementBounds(closestElement, elementsMap);
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface BoundingBox {
|
export interface BoundingBox {
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ export const hitTest = (
|
|||||||
) {
|
) {
|
||||||
return isPointHittingElementBoundingBox(
|
return isPointHittingElementBoundingBox(
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
point,
|
point,
|
||||||
threshold,
|
threshold,
|
||||||
frameNameBoundsCache,
|
frameNameBoundsCache,
|
||||||
@@ -116,6 +117,7 @@ export const hitTest = (
|
|||||||
appState,
|
appState,
|
||||||
frameNameBoundsCache,
|
frameNameBoundsCache,
|
||||||
point,
|
point,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -145,9 +147,11 @@ export const isHittingElementBoundingBoxWithoutHittingElement = (
|
|||||||
appState,
|
appState,
|
||||||
frameNameBoundsCache,
|
frameNameBoundsCache,
|
||||||
[x, y],
|
[x, y],
|
||||||
|
elementsMap,
|
||||||
) &&
|
) &&
|
||||||
isPointHittingElementBoundingBox(
|
isPointHittingElementBoundingBox(
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
[x, y],
|
[x, y],
|
||||||
threshold,
|
threshold,
|
||||||
frameNameBoundsCache,
|
frameNameBoundsCache,
|
||||||
@@ -160,6 +164,7 @@ export const isHittingElementNotConsideringBoundingBox = (
|
|||||||
appState: AppState,
|
appState: AppState,
|
||||||
frameNameBoundsCache: FrameNameBoundsCache | null,
|
frameNameBoundsCache: FrameNameBoundsCache | null,
|
||||||
point: Point,
|
point: Point,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): boolean => {
|
): boolean => {
|
||||||
const threshold = 10 / appState.zoom.value;
|
const threshold = 10 / appState.zoom.value;
|
||||||
const check = isTextElement(element)
|
const check = isTextElement(element)
|
||||||
@@ -169,6 +174,7 @@ export const isHittingElementNotConsideringBoundingBox = (
|
|||||||
: isNearCheck;
|
: isNearCheck;
|
||||||
return hitTestPointAgainstElement({
|
return hitTestPointAgainstElement({
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
point,
|
point,
|
||||||
threshold,
|
threshold,
|
||||||
check,
|
check,
|
||||||
@@ -183,6 +189,7 @@ const isElementSelected = (
|
|||||||
|
|
||||||
export const isPointHittingElementBoundingBox = (
|
export const isPointHittingElementBoundingBox = (
|
||||||
element: NonDeleted<ExcalidrawElement>,
|
element: NonDeleted<ExcalidrawElement>,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
[x, y]: Point,
|
[x, y]: Point,
|
||||||
threshold: number,
|
threshold: number,
|
||||||
frameNameBoundsCache: FrameNameBoundsCache | null,
|
frameNameBoundsCache: FrameNameBoundsCache | null,
|
||||||
@@ -194,6 +201,7 @@ export const isPointHittingElementBoundingBox = (
|
|||||||
if (isFrameLikeElement(element)) {
|
if (isFrameLikeElement(element)) {
|
||||||
return hitTestPointAgainstElement({
|
return hitTestPointAgainstElement({
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
point: [x, y],
|
point: [x, y],
|
||||||
threshold,
|
threshold,
|
||||||
check: isInsideCheck,
|
check: isInsideCheck,
|
||||||
@@ -201,7 +209,7 @@ export const isPointHittingElementBoundingBox = (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
const elementCenterX = (x1 + x2) / 2;
|
const elementCenterX = (x1 + x2) / 2;
|
||||||
const elementCenterY = (y1 + y2) / 2;
|
const elementCenterY = (y1 + y2) / 2;
|
||||||
// reverse rotate to take element's angle into account.
|
// reverse rotate to take element's angle into account.
|
||||||
@@ -224,12 +232,14 @@ export const isPointHittingElementBoundingBox = (
|
|||||||
export const bindingBorderTest = (
|
export const bindingBorderTest = (
|
||||||
element: NonDeleted<ExcalidrawBindableElement>,
|
element: NonDeleted<ExcalidrawBindableElement>,
|
||||||
{ x, y }: { x: number; y: number },
|
{ x, y }: { x: number; y: number },
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): boolean => {
|
): boolean => {
|
||||||
const threshold = maxBindingGap(element, element.width, element.height);
|
const threshold = maxBindingGap(element, element.width, element.height);
|
||||||
const check = isOutsideCheck;
|
const check = isOutsideCheck;
|
||||||
const point: Point = [x, y];
|
const point: Point = [x, y];
|
||||||
return hitTestPointAgainstElement({
|
return hitTestPointAgainstElement({
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
point,
|
point,
|
||||||
threshold,
|
threshold,
|
||||||
check,
|
check,
|
||||||
@@ -251,6 +261,7 @@ export const maxBindingGap = (
|
|||||||
|
|
||||||
type HitTestArgs = {
|
type HitTestArgs = {
|
||||||
element: NonDeletedExcalidrawElement;
|
element: NonDeletedExcalidrawElement;
|
||||||
|
elementsMap: ElementsMap;
|
||||||
point: Point;
|
point: Point;
|
||||||
threshold: number;
|
threshold: number;
|
||||||
check: (distance: number, threshold: number) => boolean;
|
check: (distance: number, threshold: number) => boolean;
|
||||||
@@ -266,19 +277,28 @@ const hitTestPointAgainstElement = (args: HitTestArgs): boolean => {
|
|||||||
case "text":
|
case "text":
|
||||||
case "diamond":
|
case "diamond":
|
||||||
case "ellipse":
|
case "ellipse":
|
||||||
const distance = distanceToBindableElement(args.element, args.point);
|
const distance = distanceToBindableElement(
|
||||||
|
args.element,
|
||||||
|
args.point,
|
||||||
|
args.elementsMap,
|
||||||
|
);
|
||||||
return args.check(distance, args.threshold);
|
return args.check(distance, args.threshold);
|
||||||
case "freedraw": {
|
case "freedraw": {
|
||||||
if (
|
if (
|
||||||
!args.check(
|
!args.check(
|
||||||
distanceToRectangle(args.element, args.point),
|
distanceToRectangle(args.element, args.point, args.elementsMap),
|
||||||
args.threshold,
|
args.threshold,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return hitTestFreeDrawElement(args.element, args.point, args.threshold);
|
return hitTestFreeDrawElement(
|
||||||
|
args.element,
|
||||||
|
args.point,
|
||||||
|
args.threshold,
|
||||||
|
args.elementsMap,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
case "arrow":
|
case "arrow":
|
||||||
case "line":
|
case "line":
|
||||||
@@ -293,7 +313,7 @@ const hitTestPointAgainstElement = (args: HitTestArgs): boolean => {
|
|||||||
// check distance to frame element first
|
// check distance to frame element first
|
||||||
if (
|
if (
|
||||||
args.check(
|
args.check(
|
||||||
distanceToBindableElement(args.element, args.point),
|
distanceToBindableElement(args.element, args.point, args.elementsMap),
|
||||||
args.threshold,
|
args.threshold,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
@@ -316,6 +336,7 @@ const hitTestPointAgainstElement = (args: HitTestArgs): boolean => {
|
|||||||
export const distanceToBindableElement = (
|
export const distanceToBindableElement = (
|
||||||
element: ExcalidrawBindableElement,
|
element: ExcalidrawBindableElement,
|
||||||
point: Point,
|
point: Point,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): number => {
|
): number => {
|
||||||
switch (element.type) {
|
switch (element.type) {
|
||||||
case "rectangle":
|
case "rectangle":
|
||||||
@@ -325,11 +346,11 @@ export const distanceToBindableElement = (
|
|||||||
case "embeddable":
|
case "embeddable":
|
||||||
case "frame":
|
case "frame":
|
||||||
case "magicframe":
|
case "magicframe":
|
||||||
return distanceToRectangle(element, point);
|
return distanceToRectangle(element, point, elementsMap);
|
||||||
case "diamond":
|
case "diamond":
|
||||||
return distanceToDiamond(element, point);
|
return distanceToDiamond(element, point, elementsMap);
|
||||||
case "ellipse":
|
case "ellipse":
|
||||||
return distanceToEllipse(element, point);
|
return distanceToEllipse(element, point, elementsMap);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -358,8 +379,13 @@ const distanceToRectangle = (
|
|||||||
| ExcalidrawIframeLikeElement
|
| ExcalidrawIframeLikeElement
|
||||||
| ExcalidrawFrameLikeElement,
|
| ExcalidrawFrameLikeElement,
|
||||||
point: Point,
|
point: Point,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): number => {
|
): number => {
|
||||||
const [, pointRel, hwidth, hheight] = pointRelativeToElement(element, point);
|
const [, pointRel, hwidth, hheight] = pointRelativeToElement(
|
||||||
|
element,
|
||||||
|
point,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
return Math.max(
|
return Math.max(
|
||||||
GAPoint.distanceToLine(pointRel, GALine.equation(0, 1, -hheight)),
|
GAPoint.distanceToLine(pointRel, GALine.equation(0, 1, -hheight)),
|
||||||
GAPoint.distanceToLine(pointRel, GALine.equation(1, 0, -hwidth)),
|
GAPoint.distanceToLine(pointRel, GALine.equation(1, 0, -hwidth)),
|
||||||
@@ -377,8 +403,13 @@ const distanceToRectangleBox = (box: RectangleBox, point: Point): number => {
|
|||||||
const distanceToDiamond = (
|
const distanceToDiamond = (
|
||||||
element: ExcalidrawDiamondElement,
|
element: ExcalidrawDiamondElement,
|
||||||
point: Point,
|
point: Point,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): number => {
|
): number => {
|
||||||
const [, pointRel, hwidth, hheight] = pointRelativeToElement(element, point);
|
const [, pointRel, hwidth, hheight] = pointRelativeToElement(
|
||||||
|
element,
|
||||||
|
point,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
const side = GALine.equation(hheight, hwidth, -hheight * hwidth);
|
const side = GALine.equation(hheight, hwidth, -hheight * hwidth);
|
||||||
return GAPoint.distanceToLine(pointRel, side);
|
return GAPoint.distanceToLine(pointRel, side);
|
||||||
};
|
};
|
||||||
@@ -386,16 +417,22 @@ const distanceToDiamond = (
|
|||||||
const distanceToEllipse = (
|
const distanceToEllipse = (
|
||||||
element: ExcalidrawEllipseElement,
|
element: ExcalidrawEllipseElement,
|
||||||
point: Point,
|
point: Point,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): number => {
|
): number => {
|
||||||
const [pointRel, tangent] = ellipseParamsForTest(element, point);
|
const [pointRel, tangent] = ellipseParamsForTest(element, point, elementsMap);
|
||||||
return -GALine.sign(tangent) * GAPoint.distanceToLine(pointRel, tangent);
|
return -GALine.sign(tangent) * GAPoint.distanceToLine(pointRel, tangent);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ellipseParamsForTest = (
|
const ellipseParamsForTest = (
|
||||||
element: ExcalidrawEllipseElement,
|
element: ExcalidrawEllipseElement,
|
||||||
point: Point,
|
point: Point,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): [GA.Point, GA.Line] => {
|
): [GA.Point, GA.Line] => {
|
||||||
const [, pointRel, hwidth, hheight] = pointRelativeToElement(element, point);
|
const [, pointRel, hwidth, hheight] = pointRelativeToElement(
|
||||||
|
element,
|
||||||
|
point,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
const [px, py] = GAPoint.toTuple(pointRel);
|
const [px, py] = GAPoint.toTuple(pointRel);
|
||||||
|
|
||||||
// We're working in positive quadrant, so start with `t = 45deg`, `tx=cos(t)`
|
// We're working in positive quadrant, so start with `t = 45deg`, `tx=cos(t)`
|
||||||
@@ -440,6 +477,7 @@ const hitTestFreeDrawElement = (
|
|||||||
element: ExcalidrawFreeDrawElement,
|
element: ExcalidrawFreeDrawElement,
|
||||||
point: Point,
|
point: Point,
|
||||||
threshold: number,
|
threshold: number,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): boolean => {
|
): boolean => {
|
||||||
// Check point-distance-to-line-segment for every segment in the
|
// Check point-distance-to-line-segment for every segment in the
|
||||||
// element's points (its input points, not its outline points).
|
// element's points (its input points, not its outline points).
|
||||||
@@ -454,7 +492,10 @@ const hitTestFreeDrawElement = (
|
|||||||
y = point[1] - element.y;
|
y = point[1] - element.y;
|
||||||
} else {
|
} else {
|
||||||
// Counter-rotate the point around center before testing
|
// Counter-rotate the point around center before testing
|
||||||
const [minX, minY, maxX, maxY] = getElementAbsoluteCoords(element);
|
const [minX, minY, maxX, maxY] = getElementAbsoluteCoords(
|
||||||
|
element,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
const rotatedPoint = rotatePoint(
|
const rotatedPoint = rotatePoint(
|
||||||
point,
|
point,
|
||||||
[minX + (maxX - minX) / 2, minY + (maxY - minY) / 2],
|
[minX + (maxX - minX) / 2, minY + (maxY - minY) / 2],
|
||||||
@@ -520,6 +561,7 @@ const hitTestLinear = (args: HitTestArgs): boolean => {
|
|||||||
const [point, pointAbs, hwidth, hheight] = pointRelativeToElement(
|
const [point, pointAbs, hwidth, hheight] = pointRelativeToElement(
|
||||||
args.element,
|
args.element,
|
||||||
args.point,
|
args.point,
|
||||||
|
args.elementsMap,
|
||||||
);
|
);
|
||||||
const side1 = GALine.equation(0, 1, -hheight);
|
const side1 = GALine.equation(0, 1, -hheight);
|
||||||
const side2 = GALine.equation(1, 0, -hwidth);
|
const side2 = GALine.equation(1, 0, -hwidth);
|
||||||
@@ -572,9 +614,10 @@ const hitTestLinear = (args: HitTestArgs): boolean => {
|
|||||||
const pointRelativeToElement = (
|
const pointRelativeToElement = (
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
pointTuple: Point,
|
pointTuple: Point,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): [GA.Point, GA.Point, number, number] => {
|
): [GA.Point, GA.Point, number, number] => {
|
||||||
const point = GAPoint.from(pointTuple);
|
const point = GAPoint.from(pointTuple);
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
const center = coordsCenter(x1, y1, x2, y2);
|
const center = coordsCenter(x1, y1, x2, y2);
|
||||||
// GA has angle orientation opposite to `rotate`
|
// GA has angle orientation opposite to `rotate`
|
||||||
const rotate = GATransform.rotation(center, element.angle);
|
const rotate = GATransform.rotation(center, element.angle);
|
||||||
@@ -609,11 +652,12 @@ const pointRelativeToDivElement = (
|
|||||||
// Returns point in absolute coordinates
|
// Returns point in absolute coordinates
|
||||||
export const pointInAbsoluteCoords = (
|
export const pointInAbsoluteCoords = (
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
// Point relative to the element position
|
// Point relative to the element position
|
||||||
point: Point,
|
point: Point,
|
||||||
): Point => {
|
): Point => {
|
||||||
const [x, y] = point;
|
const [x, y] = point;
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
const cx = (x2 - x1) / 2;
|
const cx = (x2 - x1) / 2;
|
||||||
const cy = (y2 - y1) / 2;
|
const cy = (y2 - y1) / 2;
|
||||||
const [rotatedX, rotatedY] = rotate(x, y, cx, cy, element.angle);
|
const [rotatedX, rotatedY] = rotate(x, y, cx, cy, element.angle);
|
||||||
@@ -622,8 +666,9 @@ export const pointInAbsoluteCoords = (
|
|||||||
|
|
||||||
const relativizationToElementCenter = (
|
const relativizationToElementCenter = (
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): GA.Transform => {
|
): GA.Transform => {
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
const center = coordsCenter(x1, y1, x2, y2);
|
const center = coordsCenter(x1, y1, x2, y2);
|
||||||
// GA has angle orientation opposite to `rotate`
|
// GA has angle orientation opposite to `rotate`
|
||||||
const rotate = GATransform.rotation(center, element.angle);
|
const rotate = GATransform.rotation(center, element.angle);
|
||||||
@@ -649,12 +694,14 @@ const coordsCenter = (
|
|||||||
// of the element.
|
// of the element.
|
||||||
export const determineFocusDistance = (
|
export const determineFocusDistance = (
|
||||||
element: ExcalidrawBindableElement,
|
element: ExcalidrawBindableElement,
|
||||||
|
|
||||||
// Point on the line, in absolute coordinates
|
// Point on the line, in absolute coordinates
|
||||||
a: Point,
|
a: Point,
|
||||||
// Another point on the line, in absolute coordinates (closer to element)
|
// Another point on the line, in absolute coordinates (closer to element)
|
||||||
b: Point,
|
b: Point,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): number => {
|
): number => {
|
||||||
const relateToCenter = relativizationToElementCenter(element);
|
const relateToCenter = relativizationToElementCenter(element, elementsMap);
|
||||||
const aRel = GATransform.apply(relateToCenter, GAPoint.from(a));
|
const aRel = GATransform.apply(relateToCenter, GAPoint.from(a));
|
||||||
const bRel = GATransform.apply(relateToCenter, GAPoint.from(b));
|
const bRel = GATransform.apply(relateToCenter, GAPoint.from(b));
|
||||||
const line = GALine.through(aRel, bRel);
|
const line = GALine.through(aRel, bRel);
|
||||||
@@ -693,13 +740,14 @@ export const determineFocusPoint = (
|
|||||||
// returned focusPoint
|
// returned focusPoint
|
||||||
focus: number,
|
focus: number,
|
||||||
adjecentPoint: Point,
|
adjecentPoint: Point,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): Point => {
|
): Point => {
|
||||||
if (focus === 0) {
|
if (focus === 0) {
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
const center = coordsCenter(x1, y1, x2, y2);
|
const center = coordsCenter(x1, y1, x2, y2);
|
||||||
return GAPoint.toTuple(center);
|
return GAPoint.toTuple(center);
|
||||||
}
|
}
|
||||||
const relateToCenter = relativizationToElementCenter(element);
|
const relateToCenter = relativizationToElementCenter(element, elementsMap);
|
||||||
const adjecentPointRel = GATransform.apply(
|
const adjecentPointRel = GATransform.apply(
|
||||||
relateToCenter,
|
relateToCenter,
|
||||||
GAPoint.from(adjecentPoint),
|
GAPoint.from(adjecentPoint),
|
||||||
@@ -728,14 +776,16 @@ export const determineFocusPoint = (
|
|||||||
// and the `element`, in ascending order of distance from `a`.
|
// and the `element`, in ascending order of distance from `a`.
|
||||||
export const intersectElementWithLine = (
|
export const intersectElementWithLine = (
|
||||||
element: ExcalidrawBindableElement,
|
element: ExcalidrawBindableElement,
|
||||||
|
|
||||||
// Point on the line, in absolute coordinates
|
// Point on the line, in absolute coordinates
|
||||||
a: Point,
|
a: Point,
|
||||||
// Another point on the line, in absolute coordinates
|
// Another point on the line, in absolute coordinates
|
||||||
b: Point,
|
b: Point,
|
||||||
// If given, the element is inflated by this value
|
// If given, the element is inflated by this value
|
||||||
gap: number = 0,
|
gap: number = 0,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): Point[] => {
|
): Point[] => {
|
||||||
const relateToCenter = relativizationToElementCenter(element);
|
const relateToCenter = relativizationToElementCenter(element, elementsMap);
|
||||||
const aRel = GATransform.apply(relateToCenter, GAPoint.from(a));
|
const aRel = GATransform.apply(relateToCenter, GAPoint.from(a));
|
||||||
const bRel = GATransform.apply(relateToCenter, GAPoint.from(b));
|
const bRel = GATransform.apply(relateToCenter, GAPoint.from(b));
|
||||||
const line = GALine.through(aRel, bRel);
|
const line = GALine.through(aRel, bRel);
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export const dragSelectedElements = (
|
|||||||
updateElementCoords(pointerDownState, textElement, adjustedOffset);
|
updateElementCoords(pointerDownState, textElement, adjustedOffset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateBoundElements(element, {
|
updateBoundElements(element, scene.getElementsMapIncludingDeleted(), {
|
||||||
simultaneouslyUpdated: Array.from(elementsToUpdate),
|
simultaneouslyUpdated: Array.from(elementsToUpdate),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import {
|
|||||||
ExcalidrawBindableElement,
|
ExcalidrawBindableElement,
|
||||||
ExcalidrawTextElementWithContainer,
|
ExcalidrawTextElementWithContainer,
|
||||||
ElementsMap,
|
ElementsMap,
|
||||||
|
NonDeletedExcalidrawElement,
|
||||||
|
NonDeletedSceneElementsMap,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import {
|
import {
|
||||||
distance2d,
|
distance2d,
|
||||||
@@ -36,7 +38,6 @@ import {
|
|||||||
import { mutateElement } from "./mutateElement";
|
import { mutateElement } from "./mutateElement";
|
||||||
import History from "../history";
|
import History from "../history";
|
||||||
|
|
||||||
import Scene from "../scene/Scene";
|
|
||||||
import {
|
import {
|
||||||
bindOrUnbindLinearElement,
|
bindOrUnbindLinearElement,
|
||||||
getHoveredElementForBinding,
|
getHoveredElementForBinding,
|
||||||
@@ -86,11 +87,10 @@ export class LinearElementEditor {
|
|||||||
public readonly hoverPointIndex: number;
|
public readonly hoverPointIndex: number;
|
||||||
public readonly segmentMidPointHoveredCoords: Point | null;
|
public readonly segmentMidPointHoveredCoords: Point | null;
|
||||||
|
|
||||||
constructor(element: NonDeleted<ExcalidrawLinearElement>, scene: Scene) {
|
constructor(element: NonDeleted<ExcalidrawLinearElement>) {
|
||||||
this.elementId = element.id as string & {
|
this.elementId = element.id as string & {
|
||||||
_brand: "excalidrawLinearElementId";
|
_brand: "excalidrawLinearElementId";
|
||||||
};
|
};
|
||||||
Scene.mapElementToScene(this.elementId, scene);
|
|
||||||
LinearElementEditor.normalizePoints(element);
|
LinearElementEditor.normalizePoints(element);
|
||||||
|
|
||||||
this.selectedPointsIndices = null;
|
this.selectedPointsIndices = null;
|
||||||
@@ -123,8 +123,11 @@ export class LinearElementEditor {
|
|||||||
* @param id the `elementId` from the instance of this class (so that we can
|
* @param id the `elementId` from the instance of this class (so that we can
|
||||||
* statically guarantee this method returns an ExcalidrawLinearElement)
|
* statically guarantee this method returns an ExcalidrawLinearElement)
|
||||||
*/
|
*/
|
||||||
static getElement(id: InstanceType<typeof LinearElementEditor>["elementId"]) {
|
static getElement(
|
||||||
const element = Scene.getScene(id)?.getNonDeletedElement(id);
|
id: InstanceType<typeof LinearElementEditor>["elementId"],
|
||||||
|
elementsMap: ElementsMap,
|
||||||
|
) {
|
||||||
|
const element = elementsMap.get(id);
|
||||||
if (element) {
|
if (element) {
|
||||||
return element as NonDeleted<ExcalidrawLinearElement>;
|
return element as NonDeleted<ExcalidrawLinearElement>;
|
||||||
}
|
}
|
||||||
@@ -135,6 +138,7 @@ export class LinearElementEditor {
|
|||||||
event: PointerEvent,
|
event: PointerEvent,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
setState: React.Component<any, AppState>["setState"],
|
setState: React.Component<any, AppState>["setState"],
|
||||||
|
elementsMap: NonDeletedSceneElementsMap,
|
||||||
) {
|
) {
|
||||||
if (
|
if (
|
||||||
!appState.editingLinearElement ||
|
!appState.editingLinearElement ||
|
||||||
@@ -145,16 +149,18 @@ export class LinearElementEditor {
|
|||||||
const { editingLinearElement } = appState;
|
const { editingLinearElement } = appState;
|
||||||
const { selectedPointsIndices, elementId } = editingLinearElement;
|
const { selectedPointsIndices, elementId } = editingLinearElement;
|
||||||
|
|
||||||
const element = LinearElementEditor.getElement(elementId);
|
const element = LinearElementEditor.getElement(elementId, elementsMap);
|
||||||
if (!element) {
|
if (!element) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [selectionX1, selectionY1, selectionX2, selectionY2] =
|
const [selectionX1, selectionY1, selectionX2, selectionY2] =
|
||||||
getElementAbsoluteCoords(appState.draggingElement);
|
getElementAbsoluteCoords(appState.draggingElement, elementsMap);
|
||||||
|
|
||||||
const pointsSceneCoords =
|
const pointsSceneCoords = LinearElementEditor.getPointsGlobalCoordinates(
|
||||||
LinearElementEditor.getPointsGlobalCoordinates(element);
|
element,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
|
||||||
const nextSelectedPoints = pointsSceneCoords.reduce(
|
const nextSelectedPoints = pointsSceneCoords.reduce(
|
||||||
(acc: number[], point, index) => {
|
(acc: number[], point, index) => {
|
||||||
@@ -194,13 +200,13 @@ export class LinearElementEditor {
|
|||||||
pointSceneCoords: { x: number; y: number }[],
|
pointSceneCoords: { x: number; y: number }[],
|
||||||
) => void,
|
) => void,
|
||||||
linearElementEditor: LinearElementEditor,
|
linearElementEditor: LinearElementEditor,
|
||||||
elementsMap: ElementsMap,
|
elementsMap: NonDeletedSceneElementsMap,
|
||||||
): boolean {
|
): boolean {
|
||||||
if (!linearElementEditor) {
|
if (!linearElementEditor) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const { selectedPointsIndices, elementId } = linearElementEditor;
|
const { selectedPointsIndices, elementId } = linearElementEditor;
|
||||||
const element = LinearElementEditor.getElement(elementId);
|
const element = LinearElementEditor.getElement(elementId, elementsMap);
|
||||||
if (!element) {
|
if (!element) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -222,6 +228,7 @@ export class LinearElementEditor {
|
|||||||
|
|
||||||
const [width, height] = LinearElementEditor._getShiftLockedDelta(
|
const [width, height] = LinearElementEditor._getShiftLockedDelta(
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
referencePoint,
|
referencePoint,
|
||||||
[scenePointerX, scenePointerY],
|
[scenePointerX, scenePointerY],
|
||||||
event[KEYS.CTRL_OR_CMD] ? null : appState.gridSize,
|
event[KEYS.CTRL_OR_CMD] ? null : appState.gridSize,
|
||||||
@@ -239,6 +246,7 @@ export class LinearElementEditor {
|
|||||||
} else {
|
} else {
|
||||||
const newDraggingPointPosition = LinearElementEditor.createPointAt(
|
const newDraggingPointPosition = LinearElementEditor.createPointAt(
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
scenePointerX - linearElementEditor.pointerOffset.x,
|
scenePointerX - linearElementEditor.pointerOffset.x,
|
||||||
scenePointerY - linearElementEditor.pointerOffset.y,
|
scenePointerY - linearElementEditor.pointerOffset.y,
|
||||||
event[KEYS.CTRL_OR_CMD] ? null : appState.gridSize,
|
event[KEYS.CTRL_OR_CMD] ? null : appState.gridSize,
|
||||||
@@ -255,6 +263,7 @@ export class LinearElementEditor {
|
|||||||
linearElementEditor.pointerDownState.lastClickedPoint
|
linearElementEditor.pointerDownState.lastClickedPoint
|
||||||
? LinearElementEditor.createPointAt(
|
? LinearElementEditor.createPointAt(
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
scenePointerX - linearElementEditor.pointerOffset.x,
|
scenePointerX - linearElementEditor.pointerOffset.x,
|
||||||
scenePointerY - linearElementEditor.pointerOffset.y,
|
scenePointerY - linearElementEditor.pointerOffset.y,
|
||||||
event[KEYS.CTRL_OR_CMD] ? null : appState.gridSize,
|
event[KEYS.CTRL_OR_CMD] ? null : appState.gridSize,
|
||||||
@@ -290,6 +299,7 @@ export class LinearElementEditor {
|
|||||||
LinearElementEditor.getPointGlobalCoordinates(
|
LinearElementEditor.getPointGlobalCoordinates(
|
||||||
element,
|
element,
|
||||||
element.points[0],
|
element.points[0],
|
||||||
|
elementsMap,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -303,6 +313,7 @@ export class LinearElementEditor {
|
|||||||
LinearElementEditor.getPointGlobalCoordinates(
|
LinearElementEditor.getPointGlobalCoordinates(
|
||||||
element,
|
element,
|
||||||
element.points[lastSelectedIndex],
|
element.points[lastSelectedIndex],
|
||||||
|
elementsMap,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -323,10 +334,12 @@ export class LinearElementEditor {
|
|||||||
event: PointerEvent,
|
event: PointerEvent,
|
||||||
editingLinearElement: LinearElementEditor,
|
editingLinearElement: LinearElementEditor,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
|
elementsMap: NonDeletedSceneElementsMap,
|
||||||
): LinearElementEditor {
|
): LinearElementEditor {
|
||||||
const { elementId, selectedPointsIndices, isDragging, pointerDownState } =
|
const { elementId, selectedPointsIndices, isDragging, pointerDownState } =
|
||||||
editingLinearElement;
|
editingLinearElement;
|
||||||
const element = LinearElementEditor.getElement(elementId);
|
const element = LinearElementEditor.getElement(elementId, elementsMap);
|
||||||
if (!element) {
|
if (!element) {
|
||||||
return editingLinearElement;
|
return editingLinearElement;
|
||||||
}
|
}
|
||||||
@@ -364,9 +377,11 @@ export class LinearElementEditor {
|
|||||||
LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
||||||
element,
|
element,
|
||||||
selectedPoint!,
|
selectedPoint!,
|
||||||
|
elementsMap,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Scene.getScene(element)!,
|
elements,
|
||||||
|
elementsMap,
|
||||||
)
|
)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
@@ -425,15 +440,23 @@ export class LinearElementEditor {
|
|||||||
) {
|
) {
|
||||||
return editorMidPointsCache.points;
|
return editorMidPointsCache.points;
|
||||||
}
|
}
|
||||||
LinearElementEditor.updateEditorMidPointsCache(element, appState);
|
LinearElementEditor.updateEditorMidPointsCache(
|
||||||
|
element,
|
||||||
|
elementsMap,
|
||||||
|
appState,
|
||||||
|
);
|
||||||
return editorMidPointsCache.points!;
|
return editorMidPointsCache.points!;
|
||||||
};
|
};
|
||||||
|
|
||||||
static updateEditorMidPointsCache = (
|
static updateEditorMidPointsCache = (
|
||||||
element: NonDeleted<ExcalidrawLinearElement>,
|
element: NonDeleted<ExcalidrawLinearElement>,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
appState: InteractiveCanvasAppState,
|
appState: InteractiveCanvasAppState,
|
||||||
) => {
|
) => {
|
||||||
const points = LinearElementEditor.getPointsGlobalCoordinates(element);
|
const points = LinearElementEditor.getPointsGlobalCoordinates(
|
||||||
|
element,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
|
||||||
let index = 0;
|
let index = 0;
|
||||||
const midpoints: (Point | null)[] = [];
|
const midpoints: (Point | null)[] = [];
|
||||||
@@ -455,6 +478,7 @@ export class LinearElementEditor {
|
|||||||
points[index],
|
points[index],
|
||||||
points[index + 1],
|
points[index + 1],
|
||||||
index + 1,
|
index + 1,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
midpoints.push(segmentMidPoint);
|
midpoints.push(segmentMidPoint);
|
||||||
index++;
|
index++;
|
||||||
@@ -471,12 +495,13 @@ export class LinearElementEditor {
|
|||||||
elementsMap: ElementsMap,
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
const { elementId } = linearElementEditor;
|
const { elementId } = linearElementEditor;
|
||||||
const element = LinearElementEditor.getElement(elementId);
|
const element = LinearElementEditor.getElement(elementId, elementsMap);
|
||||||
if (!element) {
|
if (!element) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const clickedPointIndex = LinearElementEditor.getPointIndexUnderCursor(
|
const clickedPointIndex = LinearElementEditor.getPointIndexUnderCursor(
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
appState.zoom,
|
appState.zoom,
|
||||||
scenePointer.x,
|
scenePointer.x,
|
||||||
scenePointer.y,
|
scenePointer.y,
|
||||||
@@ -484,7 +509,10 @@ export class LinearElementEditor {
|
|||||||
if (clickedPointIndex >= 0) {
|
if (clickedPointIndex >= 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const points = LinearElementEditor.getPointsGlobalCoordinates(element);
|
const points = LinearElementEditor.getPointsGlobalCoordinates(
|
||||||
|
element,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
if (points.length >= 3 && !appState.editingLinearElement) {
|
if (points.length >= 3 && !appState.editingLinearElement) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -550,6 +578,7 @@ export class LinearElementEditor {
|
|||||||
startPoint: Point,
|
startPoint: Point,
|
||||||
endPoint: Point,
|
endPoint: Point,
|
||||||
endPointIndex: number,
|
endPointIndex: number,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) {
|
) {
|
||||||
let segmentMidPoint = centerPoint(startPoint, endPoint);
|
let segmentMidPoint = centerPoint(startPoint, endPoint);
|
||||||
if (element.points.length > 2 && element.roundness) {
|
if (element.points.length > 2 && element.roundness) {
|
||||||
@@ -574,6 +603,7 @@ export class LinearElementEditor {
|
|||||||
segmentMidPoint = LinearElementEditor.getPointGlobalCoordinates(
|
segmentMidPoint = LinearElementEditor.getPointGlobalCoordinates(
|
||||||
element,
|
element,
|
||||||
[tx, ty],
|
[tx, ty],
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -589,6 +619,7 @@ export class LinearElementEditor {
|
|||||||
) {
|
) {
|
||||||
const element = LinearElementEditor.getElement(
|
const element = LinearElementEditor.getElement(
|
||||||
linearElementEditor.elementId,
|
linearElementEditor.elementId,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
if (!element) {
|
if (!element) {
|
||||||
return -1;
|
return -1;
|
||||||
@@ -614,7 +645,8 @@ export class LinearElementEditor {
|
|||||||
history: History,
|
history: History,
|
||||||
scenePointer: { x: number; y: number },
|
scenePointer: { x: number; y: number },
|
||||||
linearElementEditor: LinearElementEditor,
|
linearElementEditor: LinearElementEditor,
|
||||||
elementsMap: ElementsMap,
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
|
elementsMap: NonDeletedSceneElementsMap,
|
||||||
): {
|
): {
|
||||||
didAddPoint: boolean;
|
didAddPoint: boolean;
|
||||||
hitElement: NonDeleted<ExcalidrawElement> | null;
|
hitElement: NonDeleted<ExcalidrawElement> | null;
|
||||||
@@ -631,7 +663,7 @@ export class LinearElementEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { elementId } = linearElementEditor;
|
const { elementId } = linearElementEditor;
|
||||||
const element = LinearElementEditor.getElement(elementId);
|
const element = LinearElementEditor.getElement(elementId, elementsMap);
|
||||||
|
|
||||||
if (!element) {
|
if (!element) {
|
||||||
return ret;
|
return ret;
|
||||||
@@ -658,6 +690,7 @@ export class LinearElementEditor {
|
|||||||
...element.points,
|
...element.points,
|
||||||
LinearElementEditor.createPointAt(
|
LinearElementEditor.createPointAt(
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
scenePointer.x,
|
scenePointer.x,
|
||||||
scenePointer.y,
|
scenePointer.y,
|
||||||
event[KEYS.CTRL_OR_CMD] ? null : appState.gridSize,
|
event[KEYS.CTRL_OR_CMD] ? null : appState.gridSize,
|
||||||
@@ -683,7 +716,8 @@ export class LinearElementEditor {
|
|||||||
lastUncommittedPoint: null,
|
lastUncommittedPoint: null,
|
||||||
endBindingElement: getHoveredElementForBinding(
|
endBindingElement: getHoveredElementForBinding(
|
||||||
scenePointer,
|
scenePointer,
|
||||||
Scene.getScene(element)!,
|
elements,
|
||||||
|
elementsMap,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -693,6 +727,7 @@ export class LinearElementEditor {
|
|||||||
|
|
||||||
const clickedPointIndex = LinearElementEditor.getPointIndexUnderCursor(
|
const clickedPointIndex = LinearElementEditor.getPointIndexUnderCursor(
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
appState.zoom,
|
appState.zoom,
|
||||||
scenePointer.x,
|
scenePointer.x,
|
||||||
scenePointer.y,
|
scenePointer.y,
|
||||||
@@ -713,11 +748,12 @@ export class LinearElementEditor {
|
|||||||
element,
|
element,
|
||||||
startBindingElement,
|
startBindingElement,
|
||||||
endBindingElement,
|
endBindingElement,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
const cx = (x1 + x2) / 2;
|
const cx = (x1 + x2) / 2;
|
||||||
const cy = (y1 + y2) / 2;
|
const cy = (y1 + y2) / 2;
|
||||||
const targetPoint =
|
const targetPoint =
|
||||||
@@ -779,12 +815,13 @@ export class LinearElementEditor {
|
|||||||
scenePointerX: number,
|
scenePointerX: number,
|
||||||
scenePointerY: number,
|
scenePointerY: number,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): LinearElementEditor | null {
|
): LinearElementEditor | null {
|
||||||
if (!appState.editingLinearElement) {
|
if (!appState.editingLinearElement) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const { elementId, lastUncommittedPoint } = appState.editingLinearElement;
|
const { elementId, lastUncommittedPoint } = appState.editingLinearElement;
|
||||||
const element = LinearElementEditor.getElement(elementId);
|
const element = LinearElementEditor.getElement(elementId, elementsMap);
|
||||||
if (!element) {
|
if (!element) {
|
||||||
return appState.editingLinearElement;
|
return appState.editingLinearElement;
|
||||||
}
|
}
|
||||||
@@ -809,6 +846,7 @@ export class LinearElementEditor {
|
|||||||
|
|
||||||
const [width, height] = LinearElementEditor._getShiftLockedDelta(
|
const [width, height] = LinearElementEditor._getShiftLockedDelta(
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
lastCommittedPoint,
|
lastCommittedPoint,
|
||||||
[scenePointerX, scenePointerY],
|
[scenePointerX, scenePointerY],
|
||||||
event[KEYS.CTRL_OR_CMD] ? null : appState.gridSize,
|
event[KEYS.CTRL_OR_CMD] ? null : appState.gridSize,
|
||||||
@@ -821,6 +859,7 @@ export class LinearElementEditor {
|
|||||||
} else {
|
} else {
|
||||||
newPoint = LinearElementEditor.createPointAt(
|
newPoint = LinearElementEditor.createPointAt(
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
scenePointerX - appState.editingLinearElement.pointerOffset.x,
|
scenePointerX - appState.editingLinearElement.pointerOffset.x,
|
||||||
scenePointerY - appState.editingLinearElement.pointerOffset.y,
|
scenePointerY - appState.editingLinearElement.pointerOffset.y,
|
||||||
event[KEYS.CTRL_OR_CMD] ? null : appState.gridSize,
|
event[KEYS.CTRL_OR_CMD] ? null : appState.gridSize,
|
||||||
@@ -847,8 +886,9 @@ export class LinearElementEditor {
|
|||||||
static getPointGlobalCoordinates(
|
static getPointGlobalCoordinates(
|
||||||
element: NonDeleted<ExcalidrawLinearElement>,
|
element: NonDeleted<ExcalidrawLinearElement>,
|
||||||
point: Point,
|
point: Point,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) {
|
) {
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
const cx = (x1 + x2) / 2;
|
const cx = (x1 + x2) / 2;
|
||||||
const cy = (y1 + y2) / 2;
|
const cy = (y1 + y2) / 2;
|
||||||
|
|
||||||
@@ -860,8 +900,9 @@ export class LinearElementEditor {
|
|||||||
/** scene coords */
|
/** scene coords */
|
||||||
static getPointsGlobalCoordinates(
|
static getPointsGlobalCoordinates(
|
||||||
element: NonDeleted<ExcalidrawLinearElement>,
|
element: NonDeleted<ExcalidrawLinearElement>,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): Point[] {
|
): Point[] {
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
const cx = (x1 + x2) / 2;
|
const cx = (x1 + x2) / 2;
|
||||||
const cy = (y1 + y2) / 2;
|
const cy = (y1 + y2) / 2;
|
||||||
return element.points.map((point) => {
|
return element.points.map((point) => {
|
||||||
@@ -873,13 +914,15 @@ export class LinearElementEditor {
|
|||||||
|
|
||||||
static getPointAtIndexGlobalCoordinates(
|
static getPointAtIndexGlobalCoordinates(
|
||||||
element: NonDeleted<ExcalidrawLinearElement>,
|
element: NonDeleted<ExcalidrawLinearElement>,
|
||||||
|
|
||||||
indexMaybeFromEnd: number, // -1 for last element
|
indexMaybeFromEnd: number, // -1 for last element
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): Point {
|
): Point {
|
||||||
const index =
|
const index =
|
||||||
indexMaybeFromEnd < 0
|
indexMaybeFromEnd < 0
|
||||||
? element.points.length + indexMaybeFromEnd
|
? element.points.length + indexMaybeFromEnd
|
||||||
: indexMaybeFromEnd;
|
: indexMaybeFromEnd;
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
const cx = (x1 + x2) / 2;
|
const cx = (x1 + x2) / 2;
|
||||||
const cy = (y1 + y2) / 2;
|
const cy = (y1 + y2) / 2;
|
||||||
|
|
||||||
@@ -893,8 +936,9 @@ export class LinearElementEditor {
|
|||||||
static pointFromAbsoluteCoords(
|
static pointFromAbsoluteCoords(
|
||||||
element: NonDeleted<ExcalidrawLinearElement>,
|
element: NonDeleted<ExcalidrawLinearElement>,
|
||||||
absoluteCoords: Point,
|
absoluteCoords: Point,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): Point {
|
): Point {
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
const cx = (x1 + x2) / 2;
|
const cx = (x1 + x2) / 2;
|
||||||
const cy = (y1 + y2) / 2;
|
const cy = (y1 + y2) / 2;
|
||||||
const [x, y] = rotate(
|
const [x, y] = rotate(
|
||||||
@@ -909,12 +953,15 @@ export class LinearElementEditor {
|
|||||||
|
|
||||||
static getPointIndexUnderCursor(
|
static getPointIndexUnderCursor(
|
||||||
element: NonDeleted<ExcalidrawLinearElement>,
|
element: NonDeleted<ExcalidrawLinearElement>,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
zoom: AppState["zoom"],
|
zoom: AppState["zoom"],
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
) {
|
) {
|
||||||
const pointHandles =
|
const pointHandles = LinearElementEditor.getPointsGlobalCoordinates(
|
||||||
LinearElementEditor.getPointsGlobalCoordinates(element);
|
element,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
let idx = pointHandles.length;
|
let idx = pointHandles.length;
|
||||||
// loop from right to left because points on the right are rendered over
|
// loop from right to left because points on the right are rendered over
|
||||||
// points on the left, thus should take precedence when clicking, if they
|
// points on the left, thus should take precedence when clicking, if they
|
||||||
@@ -934,12 +981,13 @@ export class LinearElementEditor {
|
|||||||
|
|
||||||
static createPointAt(
|
static createPointAt(
|
||||||
element: NonDeleted<ExcalidrawLinearElement>,
|
element: NonDeleted<ExcalidrawLinearElement>,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
scenePointerX: number,
|
scenePointerX: number,
|
||||||
scenePointerY: number,
|
scenePointerY: number,
|
||||||
gridSize: number | null,
|
gridSize: number | null,
|
||||||
): Point {
|
): Point {
|
||||||
const pointerOnGrid = getGridPoint(scenePointerX, scenePointerY, gridSize);
|
const pointerOnGrid = getGridPoint(scenePointerX, scenePointerY, gridSize);
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
const cx = (x1 + x2) / 2;
|
const cx = (x1 + x2) / 2;
|
||||||
const cy = (y1 + y2) / 2;
|
const cy = (y1 + y2) / 2;
|
||||||
const [rotatedX, rotatedY] = rotate(
|
const [rotatedX, rotatedY] = rotate(
|
||||||
@@ -980,14 +1028,14 @@ export class LinearElementEditor {
|
|||||||
mutateElement(element, LinearElementEditor.getNormalizedPoints(element));
|
mutateElement(element, LinearElementEditor.getNormalizedPoints(element));
|
||||||
}
|
}
|
||||||
|
|
||||||
static duplicateSelectedPoints(appState: AppState) {
|
static duplicateSelectedPoints(appState: AppState, elementsMap: ElementsMap) {
|
||||||
if (!appState.editingLinearElement) {
|
if (!appState.editingLinearElement) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { selectedPointsIndices, elementId } = appState.editingLinearElement;
|
const { selectedPointsIndices, elementId } = appState.editingLinearElement;
|
||||||
|
|
||||||
const element = LinearElementEditor.getElement(elementId);
|
const element = LinearElementEditor.getElement(elementId, elementsMap);
|
||||||
|
|
||||||
if (!element || selectedPointsIndices === null) {
|
if (!element || selectedPointsIndices === null) {
|
||||||
return false;
|
return false;
|
||||||
@@ -1149,9 +1197,11 @@ export class LinearElementEditor {
|
|||||||
linearElementEditor: LinearElementEditor,
|
linearElementEditor: LinearElementEditor,
|
||||||
pointerCoords: PointerCoords,
|
pointerCoords: PointerCoords,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) {
|
) {
|
||||||
const element = LinearElementEditor.getElement(
|
const element = LinearElementEditor.getElement(
|
||||||
linearElementEditor.elementId,
|
linearElementEditor.elementId,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!element) {
|
if (!element) {
|
||||||
@@ -1190,9 +1240,11 @@ export class LinearElementEditor {
|
|||||||
pointerCoords: PointerCoords,
|
pointerCoords: PointerCoords,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
snapToGrid: boolean,
|
snapToGrid: boolean,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) {
|
) {
|
||||||
const element = LinearElementEditor.getElement(
|
const element = LinearElementEditor.getElement(
|
||||||
linearElementEditor.elementId,
|
linearElementEditor.elementId,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
if (!element) {
|
if (!element) {
|
||||||
return;
|
return;
|
||||||
@@ -1208,6 +1260,7 @@ export class LinearElementEditor {
|
|||||||
|
|
||||||
const midpoint = LinearElementEditor.createPointAt(
|
const midpoint = LinearElementEditor.createPointAt(
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
pointerCoords.x,
|
pointerCoords.x,
|
||||||
pointerCoords.y,
|
pointerCoords.y,
|
||||||
snapToGrid ? appState.gridSize : null,
|
snapToGrid ? appState.gridSize : null,
|
||||||
@@ -1260,6 +1313,7 @@ export class LinearElementEditor {
|
|||||||
|
|
||||||
private static _getShiftLockedDelta(
|
private static _getShiftLockedDelta(
|
||||||
element: NonDeleted<ExcalidrawLinearElement>,
|
element: NonDeleted<ExcalidrawLinearElement>,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
referencePoint: Point,
|
referencePoint: Point,
|
||||||
scenePointer: Point,
|
scenePointer: Point,
|
||||||
gridSize: number | null,
|
gridSize: number | null,
|
||||||
@@ -1267,6 +1321,7 @@ export class LinearElementEditor {
|
|||||||
const referencePointCoords = LinearElementEditor.getPointGlobalCoordinates(
|
const referencePointCoords = LinearElementEditor.getPointGlobalCoordinates(
|
||||||
element,
|
element,
|
||||||
referencePoint,
|
referencePoint,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
|
|
||||||
const [gridX, gridY] = getGridPoint(
|
const [gridX, gridY] = getGridPoint(
|
||||||
@@ -1288,8 +1343,12 @@ export class LinearElementEditor {
|
|||||||
static getBoundTextElementPosition = (
|
static getBoundTextElementPosition = (
|
||||||
element: ExcalidrawLinearElement,
|
element: ExcalidrawLinearElement,
|
||||||
boundTextElement: ExcalidrawTextElementWithContainer,
|
boundTextElement: ExcalidrawTextElementWithContainer,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): { x: number; y: number } => {
|
): { x: number; y: number } => {
|
||||||
const points = LinearElementEditor.getPointsGlobalCoordinates(element);
|
const points = LinearElementEditor.getPointsGlobalCoordinates(
|
||||||
|
element,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
if (points.length < 2) {
|
if (points.length < 2) {
|
||||||
mutateElement(boundTextElement, { isDeleted: true });
|
mutateElement(boundTextElement, { isDeleted: true });
|
||||||
}
|
}
|
||||||
@@ -1300,6 +1359,7 @@ export class LinearElementEditor {
|
|||||||
const midPoint = LinearElementEditor.getPointGlobalCoordinates(
|
const midPoint = LinearElementEditor.getPointGlobalCoordinates(
|
||||||
element,
|
element,
|
||||||
element.points[index],
|
element.points[index],
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
x = midPoint[0] - boundTextElement.width / 2;
|
x = midPoint[0] - boundTextElement.width / 2;
|
||||||
y = midPoint[1] - boundTextElement.height / 2;
|
y = midPoint[1] - boundTextElement.height / 2;
|
||||||
@@ -1319,6 +1379,7 @@ export class LinearElementEditor {
|
|||||||
points[index],
|
points[index],
|
||||||
points[index + 1],
|
points[index + 1],
|
||||||
index + 1,
|
index + 1,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
x = midSegmentMidpoint[0] - boundTextElement.width / 2;
|
x = midSegmentMidpoint[0] - boundTextElement.width / 2;
|
||||||
@@ -1329,6 +1390,7 @@ export class LinearElementEditor {
|
|||||||
|
|
||||||
static getMinMaxXYWithBoundText = (
|
static getMinMaxXYWithBoundText = (
|
||||||
element: ExcalidrawLinearElement,
|
element: ExcalidrawLinearElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
elementBounds: Bounds,
|
elementBounds: Bounds,
|
||||||
boundTextElement: ExcalidrawTextElementWithContainer,
|
boundTextElement: ExcalidrawTextElementWithContainer,
|
||||||
): [number, number, number, number, number, number] => {
|
): [number, number, number, number, number, number] => {
|
||||||
@@ -1339,6 +1401,7 @@ export class LinearElementEditor {
|
|||||||
LinearElementEditor.getBoundTextElementPosition(
|
LinearElementEditor.getBoundTextElementPosition(
|
||||||
element,
|
element,
|
||||||
boundTextElement,
|
boundTextElement,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
const boundTextX2 = boundTextX1 + boundTextElement.width;
|
const boundTextX2 = boundTextX1 + boundTextElement.width;
|
||||||
const boundTextY2 = boundTextY1 + boundTextElement.height;
|
const boundTextY2 = boundTextY1 + boundTextElement.height;
|
||||||
@@ -1479,6 +1542,7 @@ export class LinearElementEditor {
|
|||||||
if (boundTextElement) {
|
if (boundTextElement) {
|
||||||
coords = LinearElementEditor.getMinMaxXYWithBoundText(
|
coords = LinearElementEditor.getMinMaxXYWithBoundText(
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
[x1, y1, x2, y2],
|
[x1, y1, x2, y2],
|
||||||
boundTextElement,
|
boundTextElement,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
ExcalidrawEmbeddableElement,
|
ExcalidrawEmbeddableElement,
|
||||||
ExcalidrawMagicFrameElement,
|
ExcalidrawMagicFrameElement,
|
||||||
ExcalidrawIframeElement,
|
ExcalidrawIframeElement,
|
||||||
|
ElementsMap,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import {
|
import {
|
||||||
arrayToMap,
|
arrayToMap,
|
||||||
@@ -68,6 +69,7 @@ export type ElementConstructorOpts = MarkOptional<
|
|||||||
| "roundness"
|
| "roundness"
|
||||||
| "locked"
|
| "locked"
|
||||||
| "opacity"
|
| "opacity"
|
||||||
|
| "customData"
|
||||||
>;
|
>;
|
||||||
|
|
||||||
const _newElementBase = <T extends ExcalidrawElement>(
|
const _newElementBase = <T extends ExcalidrawElement>(
|
||||||
@@ -121,6 +123,7 @@ const _newElementBase = <T extends ExcalidrawElement>(
|
|||||||
updated: getUpdatedTimestamp(),
|
updated: getUpdatedTimestamp(),
|
||||||
link,
|
link,
|
||||||
locked,
|
locked,
|
||||||
|
customData: rest.customData,
|
||||||
};
|
};
|
||||||
return element;
|
return element;
|
||||||
};
|
};
|
||||||
@@ -258,6 +261,7 @@ export const newTextElement = (
|
|||||||
|
|
||||||
const getAdjustedDimensions = (
|
const getAdjustedDimensions = (
|
||||||
element: ExcalidrawTextElement,
|
element: ExcalidrawTextElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
nextText: string,
|
nextText: string,
|
||||||
): {
|
): {
|
||||||
x: number;
|
x: number;
|
||||||
@@ -292,7 +296,7 @@ const getAdjustedDimensions = (
|
|||||||
x = element.x - offsets.x;
|
x = element.x - offsets.x;
|
||||||
y = element.y - offsets.y;
|
y = element.y - offsets.y;
|
||||||
} else {
|
} else {
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
|
|
||||||
const [nextX1, nextY1, nextX2, nextY2] = getResizedElementAbsoluteCoords(
|
const [nextX1, nextY1, nextX2, nextY2] = getResizedElementAbsoluteCoords(
|
||||||
element,
|
element,
|
||||||
@@ -333,6 +337,7 @@ const getAdjustedDimensions = (
|
|||||||
export const refreshTextDimensions = (
|
export const refreshTextDimensions = (
|
||||||
textElement: ExcalidrawTextElement,
|
textElement: ExcalidrawTextElement,
|
||||||
container: ExcalidrawTextContainer | null,
|
container: ExcalidrawTextContainer | null,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
text = textElement.text,
|
text = textElement.text,
|
||||||
) => {
|
) => {
|
||||||
if (textElement.isDeleted) {
|
if (textElement.isDeleted) {
|
||||||
@@ -345,13 +350,14 @@ export const refreshTextDimensions = (
|
|||||||
getBoundTextMaxWidth(container, textElement),
|
getBoundTextMaxWidth(container, textElement),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const dimensions = getAdjustedDimensions(textElement, text);
|
const dimensions = getAdjustedDimensions(textElement, elementsMap, text);
|
||||||
return { text, ...dimensions };
|
return { text, ...dimensions };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateTextElement = (
|
export const updateTextElement = (
|
||||||
textElement: ExcalidrawTextElement,
|
textElement: ExcalidrawTextElement,
|
||||||
container: ExcalidrawTextContainer | null,
|
container: ExcalidrawTextContainer | null,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
{
|
{
|
||||||
text,
|
text,
|
||||||
isDeleted,
|
isDeleted,
|
||||||
@@ -365,7 +371,7 @@ export const updateTextElement = (
|
|||||||
return newElementWith(textElement, {
|
return newElementWith(textElement, {
|
||||||
originalText,
|
originalText,
|
||||||
isDeleted: isDeleted ?? textElement.isDeleted,
|
isDeleted: isDeleted ?? textElement.isDeleted,
|
||||||
...refreshTextDimensions(textElement, container, originalText),
|
...refreshTextDimensions(textElement, container, elementsMap, originalText),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -86,11 +86,12 @@ export const transformElements = (
|
|||||||
if (transformHandleType === "rotation") {
|
if (transformHandleType === "rotation") {
|
||||||
rotateSingleElement(
|
rotateSingleElement(
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
pointerX,
|
pointerX,
|
||||||
pointerY,
|
pointerY,
|
||||||
shouldRotateWithDiscreteAngle,
|
shouldRotateWithDiscreteAngle,
|
||||||
);
|
);
|
||||||
updateBoundElements(element);
|
updateBoundElements(element, elementsMap);
|
||||||
} else if (
|
} else if (
|
||||||
isTextElement(element) &&
|
isTextElement(element) &&
|
||||||
(transformHandleType === "nw" ||
|
(transformHandleType === "nw" ||
|
||||||
@@ -106,7 +107,7 @@ export const transformElements = (
|
|||||||
pointerX,
|
pointerX,
|
||||||
pointerY,
|
pointerY,
|
||||||
);
|
);
|
||||||
updateBoundElements(element);
|
updateBoundElements(element, elementsMap);
|
||||||
} else if (transformHandleType) {
|
} else if (transformHandleType) {
|
||||||
resizeSingleElement(
|
resizeSingleElement(
|
||||||
originalElements,
|
originalElements,
|
||||||
@@ -157,11 +158,12 @@ export const transformElements = (
|
|||||||
|
|
||||||
const rotateSingleElement = (
|
const rotateSingleElement = (
|
||||||
element: NonDeletedExcalidrawElement,
|
element: NonDeletedExcalidrawElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
pointerX: number,
|
pointerX: number,
|
||||||
pointerY: number,
|
pointerY: number,
|
||||||
shouldRotateWithDiscreteAngle: boolean,
|
shouldRotateWithDiscreteAngle: boolean,
|
||||||
) => {
|
) => {
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
const cx = (x1 + x2) / 2;
|
const cx = (x1 + x2) / 2;
|
||||||
const cy = (y1 + y2) / 2;
|
const cy = (y1 + y2) / 2;
|
||||||
let angle: number;
|
let angle: number;
|
||||||
@@ -266,7 +268,7 @@ const resizeSingleTextElement = (
|
|||||||
pointerX: number,
|
pointerX: number,
|
||||||
pointerY: number,
|
pointerY: number,
|
||||||
) => {
|
) => {
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
const cx = (x1 + x2) / 2;
|
const cx = (x1 + x2) / 2;
|
||||||
const cy = (y1 + y2) / 2;
|
const cy = (y1 + y2) / 2;
|
||||||
// rotation pointer with reverse angle
|
// rotation pointer with reverse angle
|
||||||
@@ -629,7 +631,7 @@ export const resizeSingleElement = (
|
|||||||
) {
|
) {
|
||||||
mutateElement(element, resizedElement);
|
mutateElement(element, resizedElement);
|
||||||
|
|
||||||
updateBoundElements(element, {
|
updateBoundElements(element, elementsMap, {
|
||||||
newSize: { width: resizedElement.width, height: resizedElement.height },
|
newSize: { width: resizedElement.width, height: resizedElement.height },
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -696,7 +698,11 @@ export const resizeMultipleElements = (
|
|||||||
if (!isBoundToContainer(text)) {
|
if (!isBoundToContainer(text)) {
|
||||||
return acc;
|
return acc;
|
||||||
}
|
}
|
||||||
const xy = LinearElementEditor.getBoundTextElementPosition(orig, text);
|
const xy = LinearElementEditor.getBoundTextElementPosition(
|
||||||
|
orig,
|
||||||
|
text,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
return [...acc, { ...text, ...xy }];
|
return [...acc, { ...text, ...xy }];
|
||||||
}, [] as ExcalidrawTextElementWithContainer[]);
|
}, [] as ExcalidrawTextElementWithContainer[]);
|
||||||
|
|
||||||
@@ -879,7 +885,7 @@ export const resizeMultipleElements = (
|
|||||||
|
|
||||||
mutateElement(element, update, false);
|
mutateElement(element, update, false);
|
||||||
|
|
||||||
updateBoundElements(element, {
|
updateBoundElements(element, elementsMap, {
|
||||||
simultaneouslyUpdated: elementsToUpdate,
|
simultaneouslyUpdated: elementsToUpdate,
|
||||||
newSize: { width, height },
|
newSize: { width, height },
|
||||||
});
|
});
|
||||||
@@ -921,7 +927,7 @@ const rotateMultipleElements = (
|
|||||||
elements
|
elements
|
||||||
.filter((element) => !isFrameLikeElement(element))
|
.filter((element) => !isFrameLikeElement(element))
|
||||||
.forEach((element) => {
|
.forEach((element) => {
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
const cx = (x1 + x2) / 2;
|
const cx = (x1 + x2) / 2;
|
||||||
const cy = (y1 + y2) / 2;
|
const cy = (y1 + y2) / 2;
|
||||||
const origAngle =
|
const origAngle =
|
||||||
@@ -942,7 +948,9 @@ const rotateMultipleElements = (
|
|||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
updateBoundElements(element, { simultaneouslyUpdated: elements });
|
updateBoundElements(element, elementsMap, {
|
||||||
|
simultaneouslyUpdated: elements,
|
||||||
|
});
|
||||||
|
|
||||||
const boundText = getBoundTextElement(element, elementsMap);
|
const boundText = getBoundTextElement(element, elementsMap);
|
||||||
if (boundText && !isArrowElement(element)) {
|
if (boundText && !isArrowElement(element)) {
|
||||||
@@ -964,12 +972,13 @@ const rotateMultipleElements = (
|
|||||||
export const getResizeOffsetXY = (
|
export const getResizeOffsetXY = (
|
||||||
transformHandleType: MaybeTransformHandleType,
|
transformHandleType: MaybeTransformHandleType,
|
||||||
selectedElements: NonDeletedExcalidrawElement[],
|
selectedElements: NonDeletedExcalidrawElement[],
|
||||||
|
elementsMap: ElementsMap,
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
): [number, number] => {
|
): [number, number] => {
|
||||||
const [x1, y1, x2, y2] =
|
const [x1, y1, x2, y2] =
|
||||||
selectedElements.length === 1
|
selectedElements.length === 1
|
||||||
? getElementAbsoluteCoords(selectedElements[0])
|
? getElementAbsoluteCoords(selectedElements[0], elementsMap)
|
||||||
: getCommonBounds(selectedElements);
|
: getCommonBounds(selectedElements);
|
||||||
const cx = (x1 + x2) / 2;
|
const cx = (x1 + x2) / 2;
|
||||||
const cy = (y1 + y2) / 2;
|
const cy = (y1 + y2) / 2;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {
|
|||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
PointerType,
|
PointerType,
|
||||||
NonDeletedExcalidrawElement,
|
NonDeletedExcalidrawElement,
|
||||||
|
ElementsMap,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -27,6 +28,7 @@ const isInsideTransformHandle = (
|
|||||||
|
|
||||||
export const resizeTest = (
|
export const resizeTest = (
|
||||||
element: NonDeletedExcalidrawElement,
|
element: NonDeletedExcalidrawElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
@@ -38,7 +40,7 @@ export const resizeTest = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { rotation: rotationTransformHandle, ...transformHandles } =
|
const { rotation: rotationTransformHandle, ...transformHandles } =
|
||||||
getTransformHandles(element, zoom, pointerType);
|
getTransformHandles(element, zoom, elementsMap, pointerType);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
rotationTransformHandle &&
|
rotationTransformHandle &&
|
||||||
@@ -70,6 +72,7 @@ export const getElementWithTransformHandleType = (
|
|||||||
scenePointerY: number,
|
scenePointerY: number,
|
||||||
zoom: Zoom,
|
zoom: Zoom,
|
||||||
pointerType: PointerType,
|
pointerType: PointerType,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
return elements.reduce((result, element) => {
|
return elements.reduce((result, element) => {
|
||||||
if (result) {
|
if (result) {
|
||||||
@@ -77,6 +80,7 @@ export const getElementWithTransformHandleType = (
|
|||||||
}
|
}
|
||||||
const transformHandleType = resizeTest(
|
const transformHandleType = resizeTest(
|
||||||
element,
|
element,
|
||||||
|
elementsMap,
|
||||||
appState,
|
appState,
|
||||||
scenePointerX,
|
scenePointerX,
|
||||||
scenePointerY,
|
scenePointerY,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ExcalidrawElement } from "./types";
|
import { ElementsMap, ExcalidrawElement } from "./types";
|
||||||
import { mutateElement } from "./mutateElement";
|
import { mutateElement } from "./mutateElement";
|
||||||
import { isFreeDrawElement, isLinearElement } from "./typeChecks";
|
import { isFreeDrawElement, isLinearElement } from "./typeChecks";
|
||||||
import { SHIFT_LOCKING_ANGLE } from "../constants";
|
import { SHIFT_LOCKING_ANGLE } from "../constants";
|
||||||
@@ -26,8 +26,9 @@ export const isElementInViewport = (
|
|||||||
scrollX: number;
|
scrollX: number;
|
||||||
scrollY: number;
|
scrollY: number;
|
||||||
},
|
},
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
const [x1, y1, x2, y2] = getElementBounds(element); // scene coordinates
|
const [x1, y1, x2, y2] = getElementBounds(element, elementsMap); // scene coordinates
|
||||||
const topLeftSceneCoords = viewportCoordsToSceneCoords(
|
const topLeftSceneCoords = viewportCoordsToSceneCoords(
|
||||||
{
|
{
|
||||||
clientX: viewTransformations.offsetLeft,
|
clientX: viewTransformations.offsetLeft,
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ const splitIntoLines = (text: string) => {
|
|||||||
export const redrawTextBoundingBox = (
|
export const redrawTextBoundingBox = (
|
||||||
textElement: ExcalidrawTextElement,
|
textElement: ExcalidrawTextElement,
|
||||||
container: ExcalidrawElement | null,
|
container: ExcalidrawElement | null,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
let maxWidth = undefined;
|
let maxWidth = undefined;
|
||||||
const boundTextUpdates = {
|
const boundTextUpdates = {
|
||||||
@@ -110,7 +111,11 @@ export const redrawTextBoundingBox = (
|
|||||||
...textElement,
|
...textElement,
|
||||||
...boundTextUpdates,
|
...boundTextUpdates,
|
||||||
} as ExcalidrawTextElementWithContainer;
|
} as ExcalidrawTextElementWithContainer;
|
||||||
const { x, y } = computeBoundTextPosition(container, updatedTextElement);
|
const { x, y } = computeBoundTextPosition(
|
||||||
|
container,
|
||||||
|
updatedTextElement,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
boundTextUpdates.x = x;
|
boundTextUpdates.x = x;
|
||||||
boundTextUpdates.y = y;
|
boundTextUpdates.y = y;
|
||||||
}
|
}
|
||||||
@@ -119,11 +124,11 @@ export const redrawTextBoundingBox = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const bindTextToShapeAfterDuplication = (
|
export const bindTextToShapeAfterDuplication = (
|
||||||
sceneElements: ExcalidrawElement[],
|
newElements: ExcalidrawElement[],
|
||||||
oldElements: ExcalidrawElement[],
|
oldElements: ExcalidrawElement[],
|
||||||
oldIdToDuplicatedId: Map<ExcalidrawElement["id"], ExcalidrawElement["id"]>,
|
oldIdToDuplicatedId: Map<ExcalidrawElement["id"], ExcalidrawElement["id"]>,
|
||||||
): void => {
|
): void => {
|
||||||
const sceneElementMap = arrayToMap(sceneElements) as Map<
|
const newElementsMap = arrayToMap(newElements) as Map<
|
||||||
ExcalidrawElement["id"],
|
ExcalidrawElement["id"],
|
||||||
ExcalidrawElement
|
ExcalidrawElement
|
||||||
>;
|
>;
|
||||||
@@ -134,7 +139,7 @@ export const bindTextToShapeAfterDuplication = (
|
|||||||
if (boundTextElementId) {
|
if (boundTextElementId) {
|
||||||
const newTextElementId = oldIdToDuplicatedId.get(boundTextElementId);
|
const newTextElementId = oldIdToDuplicatedId.get(boundTextElementId);
|
||||||
if (newTextElementId) {
|
if (newTextElementId) {
|
||||||
const newContainer = sceneElementMap.get(newElementId);
|
const newContainer = newElementsMap.get(newElementId);
|
||||||
if (newContainer) {
|
if (newContainer) {
|
||||||
mutateElement(newContainer, {
|
mutateElement(newContainer, {
|
||||||
boundElements: (element.boundElements || [])
|
boundElements: (element.boundElements || [])
|
||||||
@@ -149,7 +154,7 @@ export const bindTextToShapeAfterDuplication = (
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const newTextElement = sceneElementMap.get(newTextElementId);
|
const newTextElement = newElementsMap.get(newTextElementId);
|
||||||
if (newTextElement && isTextElement(newTextElement)) {
|
if (newTextElement && isTextElement(newTextElement)) {
|
||||||
mutateElement(newTextElement, {
|
mutateElement(newTextElement, {
|
||||||
containerId: newContainer ? newElementId : null,
|
containerId: newContainer ? newElementId : null,
|
||||||
@@ -236,7 +241,7 @@ export const handleBindTextResize = (
|
|||||||
if (!isArrowElement(container)) {
|
if (!isArrowElement(container)) {
|
||||||
mutateElement(
|
mutateElement(
|
||||||
textElement,
|
textElement,
|
||||||
computeBoundTextPosition(container, textElement),
|
computeBoundTextPosition(container, textElement, elementsMap),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -245,11 +250,13 @@ export const handleBindTextResize = (
|
|||||||
export const computeBoundTextPosition = (
|
export const computeBoundTextPosition = (
|
||||||
container: ExcalidrawElement,
|
container: ExcalidrawElement,
|
||||||
boundTextElement: ExcalidrawTextElementWithContainer,
|
boundTextElement: ExcalidrawTextElementWithContainer,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
if (isArrowElement(container)) {
|
if (isArrowElement(container)) {
|
||||||
return LinearElementEditor.getBoundTextElementPosition(
|
return LinearElementEditor.getBoundTextElementPosition(
|
||||||
container,
|
container,
|
||||||
boundTextElement,
|
boundTextElement,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const containerCoords = getContainerCoords(container);
|
const containerCoords = getContainerCoords(container);
|
||||||
@@ -698,12 +705,16 @@ export const getContainerCenter = (
|
|||||||
y: container.y + container.height / 2,
|
y: container.y + container.height / 2,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const points = LinearElementEditor.getPointsGlobalCoordinates(container);
|
const points = LinearElementEditor.getPointsGlobalCoordinates(
|
||||||
|
container,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
if (points.length % 2 === 1) {
|
if (points.length % 2 === 1) {
|
||||||
const index = Math.floor(container.points.length / 2);
|
const index = Math.floor(container.points.length / 2);
|
||||||
const midPoint = LinearElementEditor.getPointGlobalCoordinates(
|
const midPoint = LinearElementEditor.getPointGlobalCoordinates(
|
||||||
container,
|
container,
|
||||||
container.points[index],
|
container.points[index],
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
return { x: midPoint[0], y: midPoint[1] };
|
return { x: midPoint[0], y: midPoint[1] };
|
||||||
}
|
}
|
||||||
@@ -719,6 +730,7 @@ export const getContainerCenter = (
|
|||||||
points[index],
|
points[index],
|
||||||
points[index + 1],
|
points[index + 1],
|
||||||
index + 1,
|
index + 1,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return { x: midSegmentMidpoint[0], y: midSegmentMidpoint[1] };
|
return { x: midSegmentMidpoint[0], y: midSegmentMidpoint[1] };
|
||||||
@@ -757,11 +769,13 @@ export const getTextElementAngle = (
|
|||||||
export const getBoundTextElementPosition = (
|
export const getBoundTextElementPosition = (
|
||||||
container: ExcalidrawElement,
|
container: ExcalidrawElement,
|
||||||
boundTextElement: ExcalidrawTextElementWithContainer,
|
boundTextElement: ExcalidrawTextElementWithContainer,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
if (isArrowElement(container)) {
|
if (isArrowElement(container)) {
|
||||||
return LinearElementEditor.getBoundTextElementPosition(
|
return LinearElementEditor.getBoundTextElementPosition(
|
||||||
container,
|
container,
|
||||||
boundTextElement,
|
boundTextElement,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -804,6 +818,7 @@ export const getTextBindableContainerAtPosition = (
|
|||||||
appState: AppState,
|
appState: AppState,
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): ExcalidrawTextContainer | null => {
|
): ExcalidrawTextContainer | null => {
|
||||||
const selectedElements = getSelectedElements(elements, appState);
|
const selectedElements = getSelectedElements(elements, appState);
|
||||||
if (selectedElements.length === 1) {
|
if (selectedElements.length === 1) {
|
||||||
@@ -817,7 +832,10 @@ export const getTextBindableContainerAtPosition = (
|
|||||||
if (elements[index].isDeleted) {
|
if (elements[index].isDeleted) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(elements[index]);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(
|
||||||
|
elements[index],
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
if (
|
if (
|
||||||
isArrowElement(elements[index]) &&
|
isArrowElement(elements[index]) &&
|
||||||
isHittingElementNotConsideringBoundingBox(
|
isHittingElementNotConsideringBoundingBox(
|
||||||
@@ -825,6 +843,7 @@ export const getTextBindableContainerAtPosition = (
|
|||||||
appState,
|
appState,
|
||||||
null,
|
null,
|
||||||
[x, y],
|
[x, y],
|
||||||
|
elementsMap,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
hitElement = elements[index];
|
hitElement = elements[index];
|
||||||
|
|||||||
@@ -121,13 +121,13 @@ export const textWysiwyg = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { textAlign, verticalAlign } = updatedTextElement;
|
const { textAlign, verticalAlign } = updatedTextElement;
|
||||||
|
const elementsMap = app.scene.getNonDeletedElementsMap();
|
||||||
if (updatedTextElement && isTextElement(updatedTextElement)) {
|
if (updatedTextElement && isTextElement(updatedTextElement)) {
|
||||||
let coordX = updatedTextElement.x;
|
let coordX = updatedTextElement.x;
|
||||||
let coordY = updatedTextElement.y;
|
let coordY = updatedTextElement.y;
|
||||||
const container = getContainerElement(
|
const container = getContainerElement(
|
||||||
updatedTextElement,
|
updatedTextElement,
|
||||||
app.scene.getElementsMapIncludingDeleted(),
|
app.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
let maxWidth = updatedTextElement.width;
|
let maxWidth = updatedTextElement.width;
|
||||||
|
|
||||||
@@ -143,6 +143,7 @@ export const textWysiwyg = ({
|
|||||||
LinearElementEditor.getBoundTextElementPosition(
|
LinearElementEditor.getBoundTextElementPosition(
|
||||||
container,
|
container,
|
||||||
updatedTextElement as ExcalidrawTextElementWithContainer,
|
updatedTextElement as ExcalidrawTextElementWithContainer,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
coordX = boundTextCoords.x;
|
coordX = boundTextCoords.x;
|
||||||
coordY = boundTextCoords.y;
|
coordY = boundTextCoords.y;
|
||||||
@@ -200,6 +201,7 @@ export const textWysiwyg = ({
|
|||||||
const { y } = computeBoundTextPosition(
|
const { y } = computeBoundTextPosition(
|
||||||
container,
|
container,
|
||||||
updatedTextElement as ExcalidrawTextElementWithContainer,
|
updatedTextElement as ExcalidrawTextElementWithContainer,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
coordY = y;
|
coordY = y;
|
||||||
}
|
}
|
||||||
@@ -326,7 +328,7 @@ export const textWysiwyg = ({
|
|||||||
}
|
}
|
||||||
const container = getContainerElement(
|
const container = getContainerElement(
|
||||||
element,
|
element,
|
||||||
app.scene.getElementsMapIncludingDeleted(),
|
app.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
const font = getFontString({
|
const font = getFontString({
|
||||||
@@ -513,7 +515,7 @@ export const textWysiwyg = ({
|
|||||||
let text = editable.value;
|
let text = editable.value;
|
||||||
const container = getContainerElement(
|
const container = getContainerElement(
|
||||||
updateElement,
|
updateElement,
|
||||||
app.scene.getElementsMapIncludingDeleted(),
|
app.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (container) {
|
if (container) {
|
||||||
@@ -541,7 +543,11 @@ export const textWysiwyg = ({
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
redrawTextBoundingBox(updateElement, container);
|
redrawTextBoundingBox(
|
||||||
|
updateElement,
|
||||||
|
container,
|
||||||
|
app.scene.getNonDeletedElementsMap(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onSubmit({
|
onSubmit({
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
ElementsMap,
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
NonDeletedExcalidrawElement,
|
NonDeletedExcalidrawElement,
|
||||||
PointerType,
|
PointerType,
|
||||||
@@ -230,6 +231,8 @@ export const getTransformHandlesFromCoords = (
|
|||||||
export const getTransformHandles = (
|
export const getTransformHandles = (
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
zoom: Zoom,
|
zoom: Zoom,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
|
|
||||||
pointerType: PointerType = "mouse",
|
pointerType: PointerType = "mouse",
|
||||||
): TransformHandles => {
|
): TransformHandles => {
|
||||||
// so that when locked element is selected (especially when you toggle lock
|
// so that when locked element is selected (especially when you toggle lock
|
||||||
@@ -267,7 +270,7 @@ export const getTransformHandles = (
|
|||||||
? DEFAULT_TRANSFORM_HANDLE_SPACING + 8
|
? DEFAULT_TRANSFORM_HANDLE_SPACING + 8
|
||||||
: DEFAULT_TRANSFORM_HANDLE_SPACING;
|
: DEFAULT_TRANSFORM_HANDLE_SPACING;
|
||||||
return getTransformHandlesFromCoords(
|
return getTransformHandlesFromCoords(
|
||||||
getElementAbsoluteCoords(element, true),
|
getElementAbsoluteCoords(element, elementsMap, true),
|
||||||
element.angle,
|
element.angle,
|
||||||
zoom,
|
zoom,
|
||||||
pointerType,
|
pointerType,
|
||||||
|
|||||||
@@ -65,10 +65,11 @@ export const bindElementsToFramesAfterDuplication = (
|
|||||||
export function isElementIntersectingFrame(
|
export function isElementIntersectingFrame(
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
frame: ExcalidrawFrameLikeElement,
|
frame: ExcalidrawFrameLikeElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) {
|
) {
|
||||||
const frameLineSegments = getElementLineSegments(frame);
|
const frameLineSegments = getElementLineSegments(frame, elementsMap);
|
||||||
|
|
||||||
const elementLineSegments = getElementLineSegments(element);
|
const elementLineSegments = getElementLineSegments(element, elementsMap);
|
||||||
|
|
||||||
const intersecting = frameLineSegments.some((frameLineSegment) =>
|
const intersecting = frameLineSegments.some((frameLineSegment) =>
|
||||||
elementLineSegments.some((elementLineSegment) =>
|
elementLineSegments.some((elementLineSegment) =>
|
||||||
@@ -82,9 +83,10 @@ export function isElementIntersectingFrame(
|
|||||||
export const getElementsCompletelyInFrame = (
|
export const getElementsCompletelyInFrame = (
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
frame: ExcalidrawFrameLikeElement,
|
frame: ExcalidrawFrameLikeElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) =>
|
) =>
|
||||||
omitGroupsContainingFrameLikes(
|
omitGroupsContainingFrameLikes(
|
||||||
getElementsWithinSelection(elements, frame, false),
|
getElementsWithinSelection(elements, frame, elementsMap, false),
|
||||||
).filter(
|
).filter(
|
||||||
(element) =>
|
(element) =>
|
||||||
(!isFrameLikeElement(element) && !element.frameId) ||
|
(!isFrameLikeElement(element) && !element.frameId) ||
|
||||||
@@ -95,8 +97,9 @@ export const isElementContainingFrame = (
|
|||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
frame: ExcalidrawFrameLikeElement,
|
frame: ExcalidrawFrameLikeElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
return getElementsWithinSelection(elements, element).some(
|
return getElementsWithinSelection(elements, element, elementsMap).some(
|
||||||
(e) => e.id === frame.id,
|
(e) => e.id === frame.id,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -104,13 +107,22 @@ export const isElementContainingFrame = (
|
|||||||
export const getElementsIntersectingFrame = (
|
export const getElementsIntersectingFrame = (
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
frame: ExcalidrawFrameLikeElement,
|
frame: ExcalidrawFrameLikeElement,
|
||||||
) => elements.filter((element) => isElementIntersectingFrame(element, frame));
|
) => {
|
||||||
|
const elementsMap = arrayToMap(elements);
|
||||||
|
return elements.filter((element) =>
|
||||||
|
isElementIntersectingFrame(element, frame, elementsMap),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const elementsAreInFrameBounds = (
|
export const elementsAreInFrameBounds = (
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
frame: ExcalidrawFrameLikeElement,
|
frame: ExcalidrawFrameLikeElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
const [frameX1, frameY1, frameX2, frameY2] = getElementAbsoluteCoords(frame);
|
const [frameX1, frameY1, frameX2, frameY2] = getElementAbsoluteCoords(
|
||||||
|
frame,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
|
||||||
const [elementX1, elementY1, elementX2, elementY2] =
|
const [elementX1, elementY1, elementX2, elementY2] =
|
||||||
getCommonBounds(elements);
|
getCommonBounds(elements);
|
||||||
@@ -126,11 +138,12 @@ export const elementsAreInFrameBounds = (
|
|||||||
export const elementOverlapsWithFrame = (
|
export const elementOverlapsWithFrame = (
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
frame: ExcalidrawFrameLikeElement,
|
frame: ExcalidrawFrameLikeElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
return (
|
return (
|
||||||
elementsAreInFrameBounds([element], frame) ||
|
elementsAreInFrameBounds([element], frame, elementsMap) ||
|
||||||
isElementIntersectingFrame(element, frame) ||
|
isElementIntersectingFrame(element, frame, elementsMap) ||
|
||||||
isElementContainingFrame([frame], element, frame)
|
isElementContainingFrame([frame], element, frame, elementsMap)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -140,8 +153,9 @@ export const isCursorInFrame = (
|
|||||||
y: number;
|
y: number;
|
||||||
},
|
},
|
||||||
frame: NonDeleted<ExcalidrawFrameLikeElement>,
|
frame: NonDeleted<ExcalidrawFrameLikeElement>,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
const [fx1, fy1, fx2, fy2] = getElementAbsoluteCoords(frame);
|
const [fx1, fy1, fx2, fy2] = getElementAbsoluteCoords(frame, elementsMap);
|
||||||
|
|
||||||
return isPointWithinBounds(
|
return isPointWithinBounds(
|
||||||
[fx1, fy1],
|
[fx1, fy1],
|
||||||
@@ -155,6 +169,7 @@ export const groupsAreAtLeastIntersectingTheFrame = (
|
|||||||
groupIds: readonly string[],
|
groupIds: readonly string[],
|
||||||
frame: ExcalidrawFrameLikeElement,
|
frame: ExcalidrawFrameLikeElement,
|
||||||
) => {
|
) => {
|
||||||
|
const elementsMap = arrayToMap(elements);
|
||||||
const elementsInGroup = groupIds.flatMap((groupId) =>
|
const elementsInGroup = groupIds.flatMap((groupId) =>
|
||||||
getElementsInGroup(elements, groupId),
|
getElementsInGroup(elements, groupId),
|
||||||
);
|
);
|
||||||
@@ -165,8 +180,8 @@ export const groupsAreAtLeastIntersectingTheFrame = (
|
|||||||
|
|
||||||
return !!elementsInGroup.find(
|
return !!elementsInGroup.find(
|
||||||
(element) =>
|
(element) =>
|
||||||
elementsAreInFrameBounds([element], frame) ||
|
elementsAreInFrameBounds([element], frame, elementsMap) ||
|
||||||
isElementIntersectingFrame(element, frame),
|
isElementIntersectingFrame(element, frame, elementsMap),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -175,6 +190,7 @@ export const groupsAreCompletelyOutOfFrame = (
|
|||||||
groupIds: readonly string[],
|
groupIds: readonly string[],
|
||||||
frame: ExcalidrawFrameLikeElement,
|
frame: ExcalidrawFrameLikeElement,
|
||||||
) => {
|
) => {
|
||||||
|
const elementsMap = arrayToMap(elements);
|
||||||
const elementsInGroup = groupIds.flatMap((groupId) =>
|
const elementsInGroup = groupIds.flatMap((groupId) =>
|
||||||
getElementsInGroup(elements, groupId),
|
getElementsInGroup(elements, groupId),
|
||||||
);
|
);
|
||||||
@@ -186,8 +202,8 @@ export const groupsAreCompletelyOutOfFrame = (
|
|||||||
return (
|
return (
|
||||||
elementsInGroup.find(
|
elementsInGroup.find(
|
||||||
(element) =>
|
(element) =>
|
||||||
elementsAreInFrameBounds([element], frame) ||
|
elementsAreInFrameBounds([element], frame, elementsMap) ||
|
||||||
isElementIntersectingFrame(element, frame),
|
isElementIntersectingFrame(element, frame, elementsMap),
|
||||||
) === undefined
|
) === undefined
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -258,14 +274,15 @@ export const getElementsInResizingFrame = (
|
|||||||
allElements: ExcalidrawElementsIncludingDeleted,
|
allElements: ExcalidrawElementsIncludingDeleted,
|
||||||
frame: ExcalidrawFrameLikeElement,
|
frame: ExcalidrawFrameLikeElement,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
): ExcalidrawElement[] => {
|
): ExcalidrawElement[] => {
|
||||||
const prevElementsInFrame = getFrameChildren(allElements, frame.id);
|
const prevElementsInFrame = getFrameChildren(allElements, frame.id);
|
||||||
const nextElementsInFrame = new Set<ExcalidrawElement>(prevElementsInFrame);
|
const nextElementsInFrame = new Set<ExcalidrawElement>(prevElementsInFrame);
|
||||||
|
|
||||||
const elementsCompletelyInFrame = new Set([
|
const elementsCompletelyInFrame = new Set([
|
||||||
...getElementsCompletelyInFrame(allElements, frame),
|
...getElementsCompletelyInFrame(allElements, frame, elementsMap),
|
||||||
...prevElementsInFrame.filter((element) =>
|
...prevElementsInFrame.filter((element) =>
|
||||||
isElementContainingFrame(allElements, element, frame),
|
isElementContainingFrame(allElements, element, frame, elementsMap),
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -283,7 +300,7 @@ export const getElementsInResizingFrame = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
for (const element of elementsNotCompletelyInFrame) {
|
for (const element of elementsNotCompletelyInFrame) {
|
||||||
if (!isElementIntersectingFrame(element, frame)) {
|
if (!isElementIntersectingFrame(element, frame, elementsMap)) {
|
||||||
if (element.groupIds.length === 0) {
|
if (element.groupIds.length === 0) {
|
||||||
nextElementsInFrame.delete(element);
|
nextElementsInFrame.delete(element);
|
||||||
}
|
}
|
||||||
@@ -334,7 +351,7 @@ export const getElementsInResizingFrame = (
|
|||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
const elementsInGroup = getElementsInGroup(allElements, id);
|
const elementsInGroup = getElementsInGroup(allElements, id);
|
||||||
|
|
||||||
if (elementsAreInFrameBounds(elementsInGroup, frame)) {
|
if (elementsAreInFrameBounds(elementsInGroup, frame, elementsMap)) {
|
||||||
for (const element of elementsInGroup) {
|
for (const element of elementsInGroup) {
|
||||||
nextElementsInFrame.add(element);
|
nextElementsInFrame.add(element);
|
||||||
}
|
}
|
||||||
@@ -348,12 +365,13 @@ export const getElementsInResizingFrame = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getElementsInNewFrame = (
|
export const getElementsInNewFrame = (
|
||||||
allElements: ExcalidrawElementsIncludingDeleted,
|
elements: ExcalidrawElementsIncludingDeleted,
|
||||||
frame: ExcalidrawFrameLikeElement,
|
frame: ExcalidrawFrameLikeElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
return omitGroupsContainingFrameLikes(
|
return omitGroupsContainingFrameLikes(
|
||||||
allElements,
|
elements,
|
||||||
getElementsCompletelyInFrame(allElements, frame),
|
getElementsCompletelyInFrame(elements, frame, elementsMap),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -388,7 +406,7 @@ export const filterElementsEligibleAsFrameChildren = (
|
|||||||
frame: ExcalidrawFrameLikeElement,
|
frame: ExcalidrawFrameLikeElement,
|
||||||
) => {
|
) => {
|
||||||
const otherFrames = new Set<ExcalidrawFrameLikeElement["id"]>();
|
const otherFrames = new Set<ExcalidrawFrameLikeElement["id"]>();
|
||||||
|
const elementsMap = arrayToMap(elements);
|
||||||
elements = omitGroupsContainingFrameLikes(elements);
|
elements = omitGroupsContainingFrameLikes(elements);
|
||||||
|
|
||||||
for (const element of elements) {
|
for (const element of elements) {
|
||||||
@@ -415,14 +433,18 @@ export const filterElementsEligibleAsFrameChildren = (
|
|||||||
if (!processedGroups.has(shallowestGroupId)) {
|
if (!processedGroups.has(shallowestGroupId)) {
|
||||||
processedGroups.add(shallowestGroupId);
|
processedGroups.add(shallowestGroupId);
|
||||||
const groupElements = getElementsInGroup(elements, shallowestGroupId);
|
const groupElements = getElementsInGroup(elements, shallowestGroupId);
|
||||||
if (groupElements.some((el) => elementOverlapsWithFrame(el, frame))) {
|
if (
|
||||||
|
groupElements.some((el) =>
|
||||||
|
elementOverlapsWithFrame(el, frame, elementsMap),
|
||||||
|
)
|
||||||
|
) {
|
||||||
for (const child of groupElements) {
|
for (const child of groupElements) {
|
||||||
eligibleElements.push(child);
|
eligibleElements.push(child);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const overlaps = elementOverlapsWithFrame(element, frame);
|
const overlaps = elementOverlapsWithFrame(element, frame, elementsMap);
|
||||||
if (overlaps) {
|
if (overlaps) {
|
||||||
eligibleElements.push(element);
|
eligibleElements.push(element);
|
||||||
}
|
}
|
||||||
@@ -682,12 +704,12 @@ export const getTargetFrame = (
|
|||||||
// given an element, return if the element is in some frame
|
// given an element, return if the element is in some frame
|
||||||
export const isElementInFrame = (
|
export const isElementInFrame = (
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
allElements: ElementsMap,
|
allElementsMap: ElementsMap,
|
||||||
appState: StaticCanvasAppState,
|
appState: StaticCanvasAppState,
|
||||||
) => {
|
) => {
|
||||||
const frame = getTargetFrame(element, allElements, appState);
|
const frame = getTargetFrame(element, allElementsMap, appState);
|
||||||
const _element = isTextElement(element)
|
const _element = isTextElement(element)
|
||||||
? getContainerElement(element, allElements) || element
|
? getContainerElement(element, allElementsMap) || element
|
||||||
: element;
|
: element;
|
||||||
|
|
||||||
if (frame) {
|
if (frame) {
|
||||||
@@ -703,16 +725,18 @@ export const isElementInFrame = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (_element.groupIds.length === 0) {
|
if (_element.groupIds.length === 0) {
|
||||||
return elementOverlapsWithFrame(_element, frame);
|
return elementOverlapsWithFrame(_element, frame, allElementsMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
const allElementsInGroup = new Set(
|
const allElementsInGroup = new Set(
|
||||||
_element.groupIds.flatMap((gid) => getElementsInGroup(allElements, gid)),
|
_element.groupIds.flatMap((gid) =>
|
||||||
|
getElementsInGroup(allElementsMap, gid),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (appState.editingGroupId && appState.selectedElementsAreBeingDragged) {
|
if (appState.editingGroupId && appState.selectedElementsAreBeingDragged) {
|
||||||
const selectedElements = new Set(
|
const selectedElements = new Set(
|
||||||
getSelectedElements(allElements, appState),
|
getSelectedElements(allElementsMap, appState),
|
||||||
);
|
);
|
||||||
|
|
||||||
const editingGroupOverlapsFrame = appState.frameToHighlight !== null;
|
const editingGroupOverlapsFrame = appState.frameToHighlight !== null;
|
||||||
@@ -733,7 +757,7 @@ export const isElementInFrame = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const elementInGroup of allElementsInGroup) {
|
for (const elementInGroup of allElementsInGroup) {
|
||||||
if (elementOverlapsWithFrame(elementInGroup, frame)) {
|
if (elementOverlapsWithFrame(elementInGroup, frame, allElementsMap)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
ExcalidrawTextElementWithContainer,
|
ExcalidrawTextElementWithContainer,
|
||||||
ExcalidrawFrameLikeElement,
|
ExcalidrawFrameLikeElement,
|
||||||
NonDeletedSceneElementsMap,
|
NonDeletedSceneElementsMap,
|
||||||
|
ElementsMap,
|
||||||
} from "../element/types";
|
} from "../element/types";
|
||||||
import {
|
import {
|
||||||
isTextElement,
|
isTextElement,
|
||||||
@@ -137,6 +138,7 @@ export interface ExcalidrawElementWithCanvas {
|
|||||||
|
|
||||||
const cappedElementCanvasSize = (
|
const cappedElementCanvasSize = (
|
||||||
element: NonDeletedExcalidrawElement,
|
element: NonDeletedExcalidrawElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
zoom: Zoom,
|
zoom: Zoom,
|
||||||
): {
|
): {
|
||||||
width: number;
|
width: number;
|
||||||
@@ -155,7 +157,7 @@ const cappedElementCanvasSize = (
|
|||||||
|
|
||||||
const padding = getCanvasPadding(element);
|
const padding = getCanvasPadding(element);
|
||||||
|
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
const elementWidth =
|
const elementWidth =
|
||||||
isLinearElement(element) || isFreeDrawElement(element)
|
isLinearElement(element) || isFreeDrawElement(element)
|
||||||
? distance(x1, x2)
|
? distance(x1, x2)
|
||||||
@@ -200,7 +202,11 @@ const generateElementCanvas = (
|
|||||||
const context = canvas.getContext("2d")!;
|
const context = canvas.getContext("2d")!;
|
||||||
const padding = getCanvasPadding(element);
|
const padding = getCanvasPadding(element);
|
||||||
|
|
||||||
const { width, height, scale } = cappedElementCanvasSize(element, zoom);
|
const { width, height, scale } = cappedElementCanvasSize(
|
||||||
|
element,
|
||||||
|
elementsMap,
|
||||||
|
zoom,
|
||||||
|
);
|
||||||
|
|
||||||
canvas.width = width;
|
canvas.width = width;
|
||||||
canvas.height = height;
|
canvas.height = height;
|
||||||
@@ -209,7 +215,7 @@ const generateElementCanvas = (
|
|||||||
let canvasOffsetY = 0;
|
let canvasOffsetY = 0;
|
||||||
|
|
||||||
if (isLinearElement(element) || isFreeDrawElement(element)) {
|
if (isLinearElement(element) || isFreeDrawElement(element)) {
|
||||||
const [x1, y1] = getElementAbsoluteCoords(element);
|
const [x1, y1] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
|
|
||||||
canvasOffsetX =
|
canvasOffsetX =
|
||||||
element.x > x1
|
element.x > x1
|
||||||
@@ -468,7 +474,7 @@ const drawElementFromCanvas = (
|
|||||||
const element = elementWithCanvas.element;
|
const element = elementWithCanvas.element;
|
||||||
const padding = getCanvasPadding(element);
|
const padding = getCanvasPadding(element);
|
||||||
const zoom = elementWithCanvas.scale;
|
const zoom = elementWithCanvas.scale;
|
||||||
let [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
let [x1, y1, x2, y2] = getElementAbsoluteCoords(element, allElementsMap);
|
||||||
|
|
||||||
// Free draw elements will otherwise "shuffle" as the min x and y change
|
// Free draw elements will otherwise "shuffle" as the min x and y change
|
||||||
if (isFreeDrawElement(element)) {
|
if (isFreeDrawElement(element)) {
|
||||||
@@ -513,8 +519,10 @@ const drawElementFromCanvas = (
|
|||||||
elementWithCanvas.canvas.height,
|
elementWithCanvas.canvas.height,
|
||||||
);
|
);
|
||||||
|
|
||||||
const [, , , , boundTextCx, boundTextCy] =
|
const [, , , , boundTextCx, boundTextCy] = getElementAbsoluteCoords(
|
||||||
getElementAbsoluteCoords(boundTextElement);
|
boundTextElement,
|
||||||
|
allElementsMap,
|
||||||
|
);
|
||||||
|
|
||||||
tempCanvasContext.rotate(-element.angle);
|
tempCanvasContext.rotate(-element.angle);
|
||||||
|
|
||||||
@@ -694,7 +702,7 @@ export const renderElement = (
|
|||||||
ShapeCache.generateElementShape(element, null);
|
ShapeCache.generateElementShape(element, null);
|
||||||
|
|
||||||
if (renderConfig.isExporting) {
|
if (renderConfig.isExporting) {
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
const cx = (x1 + x2) / 2 + appState.scrollX;
|
const cx = (x1 + x2) / 2 + appState.scrollX;
|
||||||
const cy = (y1 + y2) / 2 + appState.scrollY;
|
const cy = (y1 + y2) / 2 + appState.scrollY;
|
||||||
const shiftX = (x2 - x1) / 2 - (element.x - x1);
|
const shiftX = (x2 - x1) / 2 - (element.x - x1);
|
||||||
@@ -737,7 +745,7 @@ export const renderElement = (
|
|||||||
// rely on existing shapes
|
// rely on existing shapes
|
||||||
ShapeCache.generateElementShape(element, renderConfig);
|
ShapeCache.generateElementShape(element, renderConfig);
|
||||||
if (renderConfig.isExporting) {
|
if (renderConfig.isExporting) {
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
const cx = (x1 + x2) / 2 + appState.scrollX;
|
const cx = (x1 + x2) / 2 + appState.scrollX;
|
||||||
const cy = (y1 + y2) / 2 + appState.scrollY;
|
const cy = (y1 + y2) / 2 + appState.scrollY;
|
||||||
let shiftX = (x2 - x1) / 2 - (element.x - x1);
|
let shiftX = (x2 - x1) / 2 - (element.x - x1);
|
||||||
@@ -749,6 +757,7 @@ export const renderElement = (
|
|||||||
LinearElementEditor.getBoundTextElementPosition(
|
LinearElementEditor.getBoundTextElementPosition(
|
||||||
container,
|
container,
|
||||||
element as ExcalidrawTextElementWithContainer,
|
element as ExcalidrawTextElementWithContainer,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
shiftX = (x2 - x1) / 2 - (boundTextCoords.x - x1);
|
shiftX = (x2 - x1) / 2 - (boundTextCoords.x - x1);
|
||||||
shiftY = (y2 - y1) / 2 - (boundTextCoords.y - y1);
|
shiftY = (y2 - y1) / 2 - (boundTextCoords.y - y1);
|
||||||
@@ -804,8 +813,10 @@ export const renderElement = (
|
|||||||
tempCanvasContext.rotate(-element.angle);
|
tempCanvasContext.rotate(-element.angle);
|
||||||
|
|
||||||
// Shift the canvas to center of bound text
|
// Shift the canvas to center of bound text
|
||||||
const [, , , , boundTextCx, boundTextCy] =
|
const [, , , , boundTextCx, boundTextCy] = getElementAbsoluteCoords(
|
||||||
getElementAbsoluteCoords(boundTextElement);
|
boundTextElement,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
const boundTextShiftX = (x1 + x2) / 2 - boundTextCx;
|
const boundTextShiftX = (x1 + x2) / 2 - boundTextCx;
|
||||||
const boundTextShiftY = (y1 + y2) / 2 - boundTextCy;
|
const boundTextShiftY = (y1 + y2) / 2 - boundTextCy;
|
||||||
tempCanvasContext.translate(-boundTextShiftX, -boundTextShiftY);
|
tempCanvasContext.translate(-boundTextShiftX, -boundTextShiftY);
|
||||||
@@ -939,17 +950,18 @@ export const renderElementToSvg = (
|
|||||||
renderConfig: SVGRenderConfig,
|
renderConfig: SVGRenderConfig,
|
||||||
) => {
|
) => {
|
||||||
const offset = { x: offsetX, y: offsetY };
|
const offset = { x: offsetX, y: offsetY };
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
let cx = (x2 - x1) / 2 - (element.x - x1);
|
let cx = (x2 - x1) / 2 - (element.x - x1);
|
||||||
let cy = (y2 - y1) / 2 - (element.y - y1);
|
let cy = (y2 - y1) / 2 - (element.y - y1);
|
||||||
if (isTextElement(element)) {
|
if (isTextElement(element)) {
|
||||||
const container = getContainerElement(element, elementsMap);
|
const container = getContainerElement(element, elementsMap);
|
||||||
if (isArrowElement(container)) {
|
if (isArrowElement(container)) {
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(container);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(container, elementsMap);
|
||||||
|
|
||||||
const boundTextCoords = LinearElementEditor.getBoundTextElementPosition(
|
const boundTextCoords = LinearElementEditor.getBoundTextElementPosition(
|
||||||
container,
|
container,
|
||||||
element as ExcalidrawTextElementWithContainer,
|
element as ExcalidrawTextElementWithContainer,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
cx = (x2 - x1) / 2 - (boundTextCoords.x - x1);
|
cx = (x2 - x1) / 2 - (boundTextCoords.x - x1);
|
||||||
cy = (y2 - y1) / 2 - (boundTextCoords.y - y1);
|
cy = (y2 - y1) / 2 - (boundTextCoords.y - y1);
|
||||||
@@ -1151,6 +1163,7 @@ export const renderElementToSvg = (
|
|||||||
const boundTextCoords = LinearElementEditor.getBoundTextElementPosition(
|
const boundTextCoords = LinearElementEditor.getBoundTextElementPosition(
|
||||||
element,
|
element,
|
||||||
boundText,
|
boundText,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
|
|
||||||
const maskX = offsetX + boundTextCoords.x - element.x;
|
const maskX = offsetX + boundTextCoords.x - element.x;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
GroupId,
|
GroupId,
|
||||||
ExcalidrawBindableElement,
|
ExcalidrawBindableElement,
|
||||||
ExcalidrawFrameLikeElement,
|
ExcalidrawFrameLikeElement,
|
||||||
|
ElementsMap,
|
||||||
} from "../element/types";
|
} from "../element/types";
|
||||||
import {
|
import {
|
||||||
getElementAbsoluteCoords,
|
getElementAbsoluteCoords,
|
||||||
@@ -72,7 +73,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
EXTERNAL_LINK_IMG,
|
EXTERNAL_LINK_IMG,
|
||||||
getLinkHandleFromCoords,
|
getLinkHandleFromCoords,
|
||||||
} from "../element/Hyperlink";
|
} from "../components/hyperlink/helpers";
|
||||||
import { renderSnaps } from "./renderSnaps";
|
import { renderSnaps } from "./renderSnaps";
|
||||||
import {
|
import {
|
||||||
isEmbeddableElement,
|
isEmbeddableElement,
|
||||||
@@ -256,7 +257,10 @@ const renderLinearPointHandles = (
|
|||||||
context.save();
|
context.save();
|
||||||
context.translate(appState.scrollX, appState.scrollY);
|
context.translate(appState.scrollX, appState.scrollY);
|
||||||
context.lineWidth = 1 / appState.zoom.value;
|
context.lineWidth = 1 / appState.zoom.value;
|
||||||
const points = LinearElementEditor.getPointsGlobalCoordinates(element);
|
const points = LinearElementEditor.getPointsGlobalCoordinates(
|
||||||
|
element,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
|
||||||
const { POINT_HANDLE_SIZE } = LinearElementEditor;
|
const { POINT_HANDLE_SIZE } = LinearElementEditor;
|
||||||
const radius = appState.editingLinearElement
|
const radius = appState.editingLinearElement
|
||||||
@@ -340,6 +344,7 @@ const highlightPoint = (
|
|||||||
const renderLinearElementPointHighlight = (
|
const renderLinearElementPointHighlight = (
|
||||||
context: CanvasRenderingContext2D,
|
context: CanvasRenderingContext2D,
|
||||||
appState: InteractiveCanvasAppState,
|
appState: InteractiveCanvasAppState,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
const { elementId, hoverPointIndex } = appState.selectedLinearElement!;
|
const { elementId, hoverPointIndex } = appState.selectedLinearElement!;
|
||||||
if (
|
if (
|
||||||
@@ -349,13 +354,15 @@ const renderLinearElementPointHighlight = (
|
|||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const element = LinearElementEditor.getElement(elementId);
|
const element = LinearElementEditor.getElement(elementId, elementsMap);
|
||||||
|
|
||||||
if (!element) {
|
if (!element) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const point = LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
const point = LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
||||||
element,
|
element,
|
||||||
hoverPointIndex,
|
hoverPointIndex,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
context.save();
|
context.save();
|
||||||
context.translate(appState.scrollX, appState.scrollY);
|
context.translate(appState.scrollX, appState.scrollY);
|
||||||
@@ -510,12 +517,22 @@ const _renderInteractiveScene = ({
|
|||||||
appState.suggestedBindings
|
appState.suggestedBindings
|
||||||
.filter((binding) => binding != null)
|
.filter((binding) => binding != null)
|
||||||
.forEach((suggestedBinding) => {
|
.forEach((suggestedBinding) => {
|
||||||
renderBindingHighlight(context, appState, suggestedBinding!);
|
renderBindingHighlight(
|
||||||
|
context,
|
||||||
|
appState,
|
||||||
|
suggestedBinding!,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appState.frameToHighlight) {
|
if (appState.frameToHighlight) {
|
||||||
renderFrameHighlight(context, appState, appState.frameToHighlight);
|
renderFrameHighlight(
|
||||||
|
context,
|
||||||
|
appState,
|
||||||
|
appState.frameToHighlight,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appState.elementsToHighlight) {
|
if (appState.elementsToHighlight) {
|
||||||
@@ -545,7 +562,7 @@ const _renderInteractiveScene = ({
|
|||||||
appState.selectedLinearElement &&
|
appState.selectedLinearElement &&
|
||||||
appState.selectedLinearElement.hoverPointIndex >= 0
|
appState.selectedLinearElement.hoverPointIndex >= 0
|
||||||
) {
|
) {
|
||||||
renderLinearElementPointHighlight(context, appState);
|
renderLinearElementPointHighlight(context, appState, elementsMap);
|
||||||
}
|
}
|
||||||
// Paint selected elements
|
// Paint selected elements
|
||||||
if (!appState.multiElement && !appState.editingLinearElement) {
|
if (!appState.multiElement && !appState.editingLinearElement) {
|
||||||
@@ -608,7 +625,7 @@ const _renderInteractiveScene = ({
|
|||||||
|
|
||||||
if (selectionColors.length) {
|
if (selectionColors.length) {
|
||||||
const [elementX1, elementY1, elementX2, elementY2, cx, cy] =
|
const [elementX1, elementY1, elementX2, elementY2, cx, cy] =
|
||||||
getElementAbsoluteCoords(element, true);
|
getElementAbsoluteCoords(element, elementsMap, true);
|
||||||
selections.push({
|
selections.push({
|
||||||
angle: element.angle,
|
angle: element.angle,
|
||||||
elementX1,
|
elementX1,
|
||||||
@@ -666,7 +683,8 @@ const _renderInteractiveScene = ({
|
|||||||
const transformHandles = getTransformHandles(
|
const transformHandles = getTransformHandles(
|
||||||
selectedElements[0],
|
selectedElements[0],
|
||||||
appState.zoom,
|
appState.zoom,
|
||||||
"mouse", // when we render we don't know which pointer type so use mouse
|
elementsMap,
|
||||||
|
"mouse", // when we render we don't know which pointer type so use mouse,
|
||||||
);
|
);
|
||||||
if (!appState.viewModeEnabled && showBoundingBox) {
|
if (!appState.viewModeEnabled && showBoundingBox) {
|
||||||
renderTransformHandles(
|
renderTransformHandles(
|
||||||
@@ -868,7 +886,7 @@ const _renderInteractiveScene = ({
|
|||||||
let scrollBars;
|
let scrollBars;
|
||||||
if (renderConfig.renderScrollbars) {
|
if (renderConfig.renderScrollbars) {
|
||||||
scrollBars = getScrollBars(
|
scrollBars = getScrollBars(
|
||||||
elementsMap,
|
visibleElements,
|
||||||
normalizedWidth,
|
normalizedWidth,
|
||||||
normalizedHeight,
|
normalizedHeight,
|
||||||
appState,
|
appState,
|
||||||
@@ -953,7 +971,11 @@ const _renderStaticScene = ({
|
|||||||
element.groupIds.length > 0 &&
|
element.groupIds.length > 0 &&
|
||||||
appState.frameToHighlight &&
|
appState.frameToHighlight &&
|
||||||
appState.selectedElementIds[element.id] &&
|
appState.selectedElementIds[element.id] &&
|
||||||
(elementOverlapsWithFrame(element, appState.frameToHighlight) ||
|
(elementOverlapsWithFrame(
|
||||||
|
element,
|
||||||
|
appState.frameToHighlight,
|
||||||
|
elementsMap,
|
||||||
|
) ||
|
||||||
element.groupIds.find((groupId) => groupsToBeAddedToFrame.has(groupId)))
|
element.groupIds.find((groupId) => groupsToBeAddedToFrame.has(groupId)))
|
||||||
) {
|
) {
|
||||||
element.groupIds.forEach((groupId) =>
|
element.groupIds.forEach((groupId) =>
|
||||||
@@ -1004,7 +1026,7 @@ const _renderStaticScene = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!isExporting) {
|
if (!isExporting) {
|
||||||
renderLinkIcon(element, context, appState);
|
renderLinkIcon(element, context, appState, elementsMap);
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@@ -1048,7 +1070,7 @@ const _renderStaticScene = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!isExporting) {
|
if (!isExporting) {
|
||||||
renderLinkIcon(element, context, appState);
|
renderLinkIcon(element, context, appState, elementsMap);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// - when exporting the whole canvas, we DO NOT apply clipping
|
// - when exporting the whole canvas, we DO NOT apply clipping
|
||||||
@@ -1247,6 +1269,7 @@ const renderBindingHighlight = (
|
|||||||
context: CanvasRenderingContext2D,
|
context: CanvasRenderingContext2D,
|
||||||
appState: InteractiveCanvasAppState,
|
appState: InteractiveCanvasAppState,
|
||||||
suggestedBinding: SuggestedBinding,
|
suggestedBinding: SuggestedBinding,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
const renderHighlight = Array.isArray(suggestedBinding)
|
const renderHighlight = Array.isArray(suggestedBinding)
|
||||||
? renderBindingHighlightForSuggestedPointBinding
|
? renderBindingHighlightForSuggestedPointBinding
|
||||||
@@ -1254,7 +1277,7 @@ const renderBindingHighlight = (
|
|||||||
|
|
||||||
context.save();
|
context.save();
|
||||||
context.translate(appState.scrollX, appState.scrollY);
|
context.translate(appState.scrollX, appState.scrollY);
|
||||||
renderHighlight(context, suggestedBinding as any);
|
renderHighlight(context, suggestedBinding as any, elementsMap);
|
||||||
|
|
||||||
context.restore();
|
context.restore();
|
||||||
};
|
};
|
||||||
@@ -1262,8 +1285,9 @@ const renderBindingHighlight = (
|
|||||||
const renderBindingHighlightForBindableElement = (
|
const renderBindingHighlightForBindableElement = (
|
||||||
context: CanvasRenderingContext2D,
|
context: CanvasRenderingContext2D,
|
||||||
element: ExcalidrawBindableElement,
|
element: ExcalidrawBindableElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
const width = x2 - x1;
|
const width = x2 - x1;
|
||||||
const height = y2 - y1;
|
const height = y2 - y1;
|
||||||
const threshold = maxBindingGap(element, width, height);
|
const threshold = maxBindingGap(element, width, height);
|
||||||
@@ -1323,8 +1347,9 @@ const renderFrameHighlight = (
|
|||||||
context: CanvasRenderingContext2D,
|
context: CanvasRenderingContext2D,
|
||||||
appState: InteractiveCanvasAppState,
|
appState: InteractiveCanvasAppState,
|
||||||
frame: NonDeleted<ExcalidrawFrameLikeElement>,
|
frame: NonDeleted<ExcalidrawFrameLikeElement>,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(frame);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(frame, elementsMap);
|
||||||
const width = x2 - x1;
|
const width = x2 - x1;
|
||||||
const height = y2 - y1;
|
const height = y2 - y1;
|
||||||
|
|
||||||
@@ -1398,6 +1423,7 @@ const renderElementsBoxHighlight = (
|
|||||||
const renderBindingHighlightForSuggestedPointBinding = (
|
const renderBindingHighlightForSuggestedPointBinding = (
|
||||||
context: CanvasRenderingContext2D,
|
context: CanvasRenderingContext2D,
|
||||||
suggestedBinding: SuggestedPointBinding,
|
suggestedBinding: SuggestedPointBinding,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
const [element, startOrEnd, bindableElement] = suggestedBinding;
|
const [element, startOrEnd, bindableElement] = suggestedBinding;
|
||||||
|
|
||||||
@@ -1416,6 +1442,7 @@ const renderBindingHighlightForSuggestedPointBinding = (
|
|||||||
const [x, y] = LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
const [x, y] = LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
||||||
element,
|
element,
|
||||||
index,
|
index,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
fillCircle(context, x, y, threshold);
|
fillCircle(context, x, y, threshold);
|
||||||
});
|
});
|
||||||
@@ -1426,9 +1453,10 @@ const renderLinkIcon = (
|
|||||||
element: NonDeletedExcalidrawElement,
|
element: NonDeletedExcalidrawElement,
|
||||||
context: CanvasRenderingContext2D,
|
context: CanvasRenderingContext2D,
|
||||||
appState: StaticCanvasAppState,
|
appState: StaticCanvasAppState,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
if (element.link && !appState.selectedElementIds[element.id]) {
|
if (element.link && !appState.selectedElementIds[element.id]) {
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
const [x, y, width, height] = getLinkHandleFromCoords(
|
const [x, y, width, height] = getLinkHandleFromCoords(
|
||||||
[x1, y1, x2, y2],
|
[x1, y1, x2, y2],
|
||||||
element.angle,
|
element.angle,
|
||||||
|
|||||||
@@ -60,10 +60,8 @@ export class Fonts {
|
|||||||
return newElementWith(element, {
|
return newElementWith(element, {
|
||||||
...refreshTextDimensions(
|
...refreshTextDimensions(
|
||||||
element,
|
element,
|
||||||
getContainerElement(
|
getContainerElement(element, this.scene.getNonDeletedElementsMap()),
|
||||||
element,
|
this.scene.getNonDeletedElementsMap(),
|
||||||
this.scene.getElementsMapIncludingDeleted(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,13 +40,19 @@ export class Renderer {
|
|||||||
const visibleElements: NonDeletedExcalidrawElement[] = [];
|
const visibleElements: NonDeletedExcalidrawElement[] = [];
|
||||||
for (const element of elementsMap.values()) {
|
for (const element of elementsMap.values()) {
|
||||||
if (
|
if (
|
||||||
isElementInViewport(element, width, height, {
|
isElementInViewport(
|
||||||
zoom,
|
element,
|
||||||
offsetLeft,
|
width,
|
||||||
offsetTop,
|
height,
|
||||||
scrollX,
|
{
|
||||||
scrollY,
|
zoom,
|
||||||
})
|
offsetLeft,
|
||||||
|
offsetTop,
|
||||||
|
scrollX,
|
||||||
|
scrollY,
|
||||||
|
},
|
||||||
|
elementsMap,
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
visibleElements.push(element);
|
visibleElements.push(element);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ import {
|
|||||||
getFrameLikeTitle,
|
getFrameLikeTitle,
|
||||||
getRootElements,
|
getRootElements,
|
||||||
} from "../frame";
|
} from "../frame";
|
||||||
import { newTextElement } from "../element";
|
import { newTextElement } from "../element/newElement";
|
||||||
import { Mutable } from "../utility-types";
|
import { Mutable } from "../utility-types";
|
||||||
import { newElementWith } from "../element/mutateElement";
|
import { newElementWith } from "../element/mutateElement";
|
||||||
import Scene from "./Scene";
|
import Scene from "./Scene";
|
||||||
@@ -392,8 +392,9 @@ export const exportToSvg = async (
|
|||||||
const frameElements = getFrameLikeElements(elements);
|
const frameElements = getFrameLikeElements(elements);
|
||||||
|
|
||||||
let exportingFrameClipPath = "";
|
let exportingFrameClipPath = "";
|
||||||
|
const elementsMap = arrayToMap(elements);
|
||||||
for (const frame of frameElements) {
|
for (const frame of frameElements) {
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(frame);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(frame, elementsMap);
|
||||||
const cx = (x2 - x1) / 2 - (frame.x - x1);
|
const cx = (x2 - x1) / 2 - (frame.x - x1);
|
||||||
const cy = (y2 - y1) / 2 - (frame.y - y1);
|
const cy = (y2 - y1) / 2 - (frame.y - y1);
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,21 @@
|
|||||||
import { getCommonBounds } from "../element";
|
import { getCommonBounds } from "../element";
|
||||||
import { InteractiveCanvasAppState } from "../types";
|
import { InteractiveCanvasAppState } from "../types";
|
||||||
import { RenderableElementsMap, ScrollBars } from "./types";
|
import { ScrollBars } from "./types";
|
||||||
import { getGlobalCSSVariable } from "../utils";
|
import { getGlobalCSSVariable } from "../utils";
|
||||||
import { getLanguage } from "../i18n";
|
import { getLanguage } from "../i18n";
|
||||||
|
import { ExcalidrawElement } from "../element/types";
|
||||||
|
|
||||||
export const SCROLLBAR_MARGIN = 4;
|
export const SCROLLBAR_MARGIN = 4;
|
||||||
export const SCROLLBAR_WIDTH = 6;
|
export const SCROLLBAR_WIDTH = 6;
|
||||||
export const SCROLLBAR_COLOR = "rgba(0,0,0,0.3)";
|
export const SCROLLBAR_COLOR = "rgba(0,0,0,0.3)";
|
||||||
|
|
||||||
export const getScrollBars = (
|
export const getScrollBars = (
|
||||||
elements: RenderableElementsMap,
|
elements: readonly ExcalidrawElement[],
|
||||||
viewportWidth: number,
|
viewportWidth: number,
|
||||||
viewportHeight: number,
|
viewportHeight: number,
|
||||||
appState: InteractiveCanvasAppState,
|
appState: InteractiveCanvasAppState,
|
||||||
): ScrollBars => {
|
): ScrollBars => {
|
||||||
if (!elements.size) {
|
if (!elements.length) {
|
||||||
return {
|
return {
|
||||||
horizontal: null,
|
horizontal: null,
|
||||||
vertical: null,
|
vertical: null,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
ElementsMap,
|
||||||
ElementsMapOrArray,
|
ElementsMapOrArray,
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
NonDeletedExcalidrawElement,
|
NonDeletedExcalidrawElement,
|
||||||
@@ -44,18 +45,24 @@ export const excludeElementsInFramesFromSelection = <
|
|||||||
export const getElementsWithinSelection = (
|
export const getElementsWithinSelection = (
|
||||||
elements: readonly NonDeletedExcalidrawElement[],
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
selection: NonDeletedExcalidrawElement,
|
selection: NonDeletedExcalidrawElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
excludeElementsInFrames: boolean = true,
|
excludeElementsInFrames: boolean = true,
|
||||||
) => {
|
) => {
|
||||||
const [selectionX1, selectionY1, selectionX2, selectionY2] =
|
const [selectionX1, selectionY1, selectionX2, selectionY2] =
|
||||||
getElementAbsoluteCoords(selection);
|
getElementAbsoluteCoords(selection, elementsMap);
|
||||||
|
|
||||||
let elementsInSelection = elements.filter((element) => {
|
let elementsInSelection = elements.filter((element) => {
|
||||||
let [elementX1, elementY1, elementX2, elementY2] =
|
let [elementX1, elementY1, elementX2, elementY2] = getElementBounds(
|
||||||
getElementBounds(element);
|
element,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
|
||||||
const containingFrame = getContainingFrame(element);
|
const containingFrame = getContainingFrame(element);
|
||||||
if (containingFrame) {
|
if (containingFrame) {
|
||||||
const [fx1, fy1, fx2, fy2] = getElementBounds(containingFrame);
|
const [fx1, fy1, fx2, fy2] = getElementBounds(
|
||||||
|
containingFrame,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
|
||||||
elementX1 = Math.max(fx1, elementX1);
|
elementX1 = Math.max(fx1, elementX1);
|
||||||
elementY1 = Math.max(fy1, elementY1);
|
elementY1 = Math.max(fy1, elementY1);
|
||||||
@@ -82,7 +89,7 @@ export const getElementsWithinSelection = (
|
|||||||
const containingFrame = getContainingFrame(element);
|
const containingFrame = getContainingFrame(element);
|
||||||
|
|
||||||
if (containingFrame) {
|
if (containingFrame) {
|
||||||
return elementOverlapsWithFrame(element, containingFrame);
|
return elementOverlapsWithFrame(element, containingFrame, elementsMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -95,6 +102,7 @@ export const getVisibleAndNonSelectedElements = (
|
|||||||
elements: readonly NonDeletedExcalidrawElement[],
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
selectedElements: readonly NonDeletedExcalidrawElement[],
|
selectedElements: readonly NonDeletedExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
const selectedElementsSet = new Set(
|
const selectedElementsSet = new Set(
|
||||||
selectedElements.map((element) => element.id),
|
selectedElements.map((element) => element.id),
|
||||||
@@ -105,6 +113,7 @@ export const getVisibleAndNonSelectedElements = (
|
|||||||
appState.width,
|
appState.width,
|
||||||
appState.height,
|
appState.height,
|
||||||
appState,
|
appState,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
|
|
||||||
return !selectedElementsSet.has(element.id) && isVisible;
|
return !selectedElementsSet.has(element.id) && isVisible;
|
||||||
|
|||||||
@@ -8,15 +8,18 @@ import {
|
|||||||
import { MaybeTransformHandleType } from "./element/transformHandles";
|
import { MaybeTransformHandleType } from "./element/transformHandles";
|
||||||
import { isBoundToContainer, isFrameLikeElement } from "./element/typeChecks";
|
import { isBoundToContainer, isFrameLikeElement } from "./element/typeChecks";
|
||||||
import {
|
import {
|
||||||
|
ElementsMap,
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
NonDeletedExcalidrawElement,
|
NonDeletedExcalidrawElement,
|
||||||
} from "./element/types";
|
} from "./element/types";
|
||||||
import { getMaximumGroups } from "./groups";
|
import { getMaximumGroups } from "./groups";
|
||||||
import { KEYS } from "./keys";
|
import { KEYS } from "./keys";
|
||||||
import { rangeIntersection, rangesOverlap, rotatePoint } from "./math";
|
import { rangeIntersection, rangesOverlap, rotatePoint } from "./math";
|
||||||
import { getVisibleAndNonSelectedElements } from "./scene/selection";
|
import {
|
||||||
|
getSelectedElements,
|
||||||
|
getVisibleAndNonSelectedElements,
|
||||||
|
} from "./scene/selection";
|
||||||
import { AppState, KeyboardModifiersObject, Point } from "./types";
|
import { AppState, KeyboardModifiersObject, Point } from "./types";
|
||||||
import { arrayToMap } from "./utils";
|
|
||||||
|
|
||||||
const SNAP_DISTANCE = 8;
|
const SNAP_DISTANCE = 8;
|
||||||
|
|
||||||
@@ -167,6 +170,7 @@ export const areRoughlyEqual = (a: number, b: number, precision = 0.01) => {
|
|||||||
|
|
||||||
export const getElementsCorners = (
|
export const getElementsCorners = (
|
||||||
elements: ExcalidrawElement[],
|
elements: ExcalidrawElement[],
|
||||||
|
elementsMap: ElementsMap,
|
||||||
{
|
{
|
||||||
omitCenter,
|
omitCenter,
|
||||||
boundingBoxCorners,
|
boundingBoxCorners,
|
||||||
@@ -185,7 +189,10 @@ export const getElementsCorners = (
|
|||||||
if (elements.length === 1) {
|
if (elements.length === 1) {
|
||||||
const element = elements[0];
|
const element = elements[0];
|
||||||
|
|
||||||
let [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(element);
|
let [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(
|
||||||
|
element,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
|
||||||
if (dragOffset) {
|
if (dragOffset) {
|
||||||
x1 += dragOffset.x;
|
x1 += dragOffset.x;
|
||||||
@@ -262,6 +269,7 @@ const getReferenceElements = (
|
|||||||
elements: readonly NonDeletedExcalidrawElement[],
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
selectedElements: NonDeletedExcalidrawElement[],
|
selectedElements: NonDeletedExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
const selectedFrames = selectedElements
|
const selectedFrames = selectedElements
|
||||||
.filter((element) => isFrameLikeElement(element))
|
.filter((element) => isFrameLikeElement(element))
|
||||||
@@ -271,6 +279,7 @@ const getReferenceElements = (
|
|||||||
elements,
|
elements,
|
||||||
selectedElements,
|
selectedElements,
|
||||||
appState,
|
appState,
|
||||||
|
elementsMap,
|
||||||
).filter(
|
).filter(
|
||||||
(element) => !(element.frameId && selectedFrames.includes(element.frameId)),
|
(element) => !(element.frameId && selectedFrames.includes(element.frameId)),
|
||||||
);
|
);
|
||||||
@@ -280,17 +289,16 @@ export const getVisibleGaps = (
|
|||||||
elements: readonly NonDeletedExcalidrawElement[],
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
selectedElements: ExcalidrawElement[],
|
selectedElements: ExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
const referenceElements: ExcalidrawElement[] = getReferenceElements(
|
const referenceElements: ExcalidrawElement[] = getReferenceElements(
|
||||||
elements,
|
elements,
|
||||||
selectedElements,
|
selectedElements,
|
||||||
appState,
|
appState,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
|
|
||||||
const referenceBounds = getMaximumGroups(
|
const referenceBounds = getMaximumGroups(referenceElements, elementsMap)
|
||||||
referenceElements,
|
|
||||||
arrayToMap(elements),
|
|
||||||
)
|
|
||||||
.filter(
|
.filter(
|
||||||
(elementsGroup) =>
|
(elementsGroup) =>
|
||||||
!(elementsGroup.length === 1 && isBoundToContainer(elementsGroup[0])),
|
!(elementsGroup.length === 1 && isBoundToContainer(elementsGroup[0])),
|
||||||
@@ -569,19 +577,20 @@ export const getReferenceSnapPoints = (
|
|||||||
elements: readonly NonDeletedExcalidrawElement[],
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
selectedElements: ExcalidrawElement[],
|
selectedElements: ExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
const referenceElements = getReferenceElements(
|
const referenceElements = getReferenceElements(
|
||||||
elements,
|
elements,
|
||||||
selectedElements,
|
selectedElements,
|
||||||
appState,
|
appState,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
|
return getMaximumGroups(referenceElements, elementsMap)
|
||||||
return getMaximumGroups(referenceElements, arrayToMap(elements))
|
|
||||||
.filter(
|
.filter(
|
||||||
(elementsGroup) =>
|
(elementsGroup) =>
|
||||||
!(elementsGroup.length === 1 && isBoundToContainer(elementsGroup[0])),
|
!(elementsGroup.length === 1 && isBoundToContainer(elementsGroup[0])),
|
||||||
)
|
)
|
||||||
.flatMap((elementGroup) => getElementsCorners(elementGroup));
|
.flatMap((elementGroup) => getElementsCorners(elementGroup, elementsMap));
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPointSnaps = (
|
const getPointSnaps = (
|
||||||
@@ -641,11 +650,13 @@ const getPointSnaps = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const snapDraggedElements = (
|
export const snapDraggedElements = (
|
||||||
selectedElements: ExcalidrawElement[],
|
elements: ExcalidrawElement[],
|
||||||
dragOffset: Vector2D,
|
dragOffset: Vector2D,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
event: KeyboardModifiersObject,
|
event: KeyboardModifiersObject,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
|
const selectedElements = getSelectedElements(elements, appState);
|
||||||
if (
|
if (
|
||||||
!isSnappingEnabled({ appState, event, selectedElements }) ||
|
!isSnappingEnabled({ appState, event, selectedElements }) ||
|
||||||
selectedElements.length === 0
|
selectedElements.length === 0
|
||||||
@@ -658,7 +669,6 @@ export const snapDraggedElements = (
|
|||||||
snapLines: [],
|
snapLines: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
dragOffset.x = round(dragOffset.x);
|
dragOffset.x = round(dragOffset.x);
|
||||||
dragOffset.y = round(dragOffset.y);
|
dragOffset.y = round(dragOffset.y);
|
||||||
const nearestSnapsX: Snaps = [];
|
const nearestSnapsX: Snaps = [];
|
||||||
@@ -669,7 +679,7 @@ export const snapDraggedElements = (
|
|||||||
y: snapDistance,
|
y: snapDistance,
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectionPoints = getElementsCorners(selectedElements, {
|
const selectionPoints = getElementsCorners(selectedElements, elementsMap, {
|
||||||
dragOffset,
|
dragOffset,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -719,7 +729,7 @@ export const snapDraggedElements = (
|
|||||||
|
|
||||||
getPointSnaps(
|
getPointSnaps(
|
||||||
selectedElements,
|
selectedElements,
|
||||||
getElementsCorners(selectedElements, {
|
getElementsCorners(selectedElements, elementsMap, {
|
||||||
dragOffset: newDragOffset,
|
dragOffset: newDragOffset,
|
||||||
}),
|
}),
|
||||||
appState,
|
appState,
|
||||||
@@ -1204,6 +1214,7 @@ export const snapNewElement = (
|
|||||||
event: KeyboardModifiersObject,
|
event: KeyboardModifiersObject,
|
||||||
origin: Vector2D,
|
origin: Vector2D,
|
||||||
dragOffset: Vector2D,
|
dragOffset: Vector2D,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
if (
|
if (
|
||||||
!isSnappingEnabled({ event, selectedElements: [draggingElement], appState })
|
!isSnappingEnabled({ event, selectedElements: [draggingElement], appState })
|
||||||
@@ -1248,7 +1259,7 @@ export const snapNewElement = (
|
|||||||
nearestSnapsX.length = 0;
|
nearestSnapsX.length = 0;
|
||||||
nearestSnapsY.length = 0;
|
nearestSnapsY.length = 0;
|
||||||
|
|
||||||
const corners = getElementsCorners([draggingElement], {
|
const corners = getElementsCorners([draggingElement], elementsMap, {
|
||||||
boundingBoxCorners: true,
|
boundingBoxCorners: true,
|
||||||
omitCenter: true,
|
omitCenter: true,
|
||||||
});
|
});
|
||||||
@@ -1276,6 +1287,7 @@ export const getSnapLinesAtPointer = (
|
|||||||
appState: AppState,
|
appState: AppState,
|
||||||
pointer: Vector2D,
|
pointer: Vector2D,
|
||||||
event: KeyboardModifiersObject,
|
event: KeyboardModifiersObject,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
) => {
|
) => {
|
||||||
if (!isSnappingEnabled({ event, selectedElements: [], appState })) {
|
if (!isSnappingEnabled({ event, selectedElements: [], appState })) {
|
||||||
return {
|
return {
|
||||||
@@ -1288,6 +1300,7 @@ export const getSnapLinesAtPointer = (
|
|||||||
elements,
|
elements,
|
||||||
[],
|
[],
|
||||||
appState,
|
appState,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
|
|
||||||
const snapDistance = getSnapDistance(appState.zoom.value);
|
const snapDistance = getSnapDistance(appState.zoom.value);
|
||||||
@@ -1301,7 +1314,7 @@ export const getSnapLinesAtPointer = (
|
|||||||
const verticalSnapLines: PointerSnapLine[] = [];
|
const verticalSnapLines: PointerSnapLine[] = [];
|
||||||
|
|
||||||
for (const referenceElement of referenceElements) {
|
for (const referenceElement of referenceElements) {
|
||||||
const corners = getElementsCorners([referenceElement]);
|
const corners = getElementsCorners([referenceElement], elementsMap);
|
||||||
|
|
||||||
for (const corner of corners) {
|
for (const corner of corners) {
|
||||||
const offsetX = corner[0] - pointer.x;
|
const offsetX = corner[0] - pointer.x;
|
||||||
|
|||||||
@@ -387,6 +387,7 @@ exports[`contextMenu element > right-clicking on a group should select whole gro
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "red",
|
"backgroundColor": "red",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [
|
"groupIds": [
|
||||||
@@ -421,6 +422,7 @@ exports[`contextMenu element > right-clicking on a group should select whole gro
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "red",
|
"backgroundColor": "red",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [
|
"groupIds": [
|
||||||
@@ -584,6 +586,7 @@ exports[`contextMenu element > selecting 'Add to library' in context menu adds e
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -643,6 +646,7 @@ exports[`contextMenu element > selecting 'Add to library' in context menu adds e
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -786,6 +790,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -818,6 +823,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -877,6 +883,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -920,6 +927,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -949,6 +957,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -992,6 +1001,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -1021,6 +1031,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -1164,6 +1175,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -1196,6 +1208,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -1255,6 +1268,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -1298,6 +1312,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -1327,6 +1342,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -1370,6 +1386,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -1399,6 +1416,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -1544,6 +1562,7 @@ exports[`contextMenu element > selecting 'Copy styles' in context menu copies st
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -1603,6 +1622,7 @@ exports[`contextMenu element > selecting 'Copy styles' in context menu copies st
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -1744,6 +1764,7 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -1803,6 +1824,7 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -1844,6 +1866,7 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -1987,6 +2010,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -2019,6 +2043,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -2078,6 +2103,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -2121,6 +2147,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -2150,6 +2177,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -2298,6 +2326,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [
|
"groupIds": [
|
||||||
@@ -2332,6 +2361,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [
|
"groupIds": [
|
||||||
@@ -2393,6 +2423,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -2436,6 +2467,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -2465,6 +2497,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -2511,6 +2544,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [
|
"groupIds": [
|
||||||
@@ -2542,6 +2576,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [
|
"groupIds": [
|
||||||
@@ -2689,6 +2724,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "#a5d8ff",
|
"backgroundColor": "#a5d8ff",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "cross-hatch",
|
"fillStyle": "cross-hatch",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -2721,6 +2757,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "#a5d8ff",
|
"backgroundColor": "#a5d8ff",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "cross-hatch",
|
"fillStyle": "cross-hatch",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -2780,6 +2817,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -2823,6 +2861,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -2852,6 +2891,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -2895,6 +2935,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -2924,6 +2965,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -2967,6 +3009,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -2996,6 +3039,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "#a5d8ff",
|
"backgroundColor": "#a5d8ff",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -3039,6 +3083,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -3068,6 +3113,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "#a5d8ff",
|
"backgroundColor": "#a5d8ff",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "cross-hatch",
|
"fillStyle": "cross-hatch",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -3111,6 +3157,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -3140,6 +3187,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "#a5d8ff",
|
"backgroundColor": "#a5d8ff",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "cross-hatch",
|
"fillStyle": "cross-hatch",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -3183,6 +3231,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -3212,6 +3261,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "#a5d8ff",
|
"backgroundColor": "#a5d8ff",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "cross-hatch",
|
"fillStyle": "cross-hatch",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -3255,6 +3305,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -3284,6 +3335,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "#a5d8ff",
|
"backgroundColor": "#a5d8ff",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "cross-hatch",
|
"fillStyle": "cross-hatch",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -3327,6 +3379,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "#a5d8ff",
|
"backgroundColor": "#a5d8ff",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "cross-hatch",
|
"fillStyle": "cross-hatch",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -3356,6 +3409,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "#a5d8ff",
|
"backgroundColor": "#a5d8ff",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "cross-hatch",
|
"fillStyle": "cross-hatch",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -3499,6 +3553,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -3531,6 +3586,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -3590,6 +3646,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -3633,6 +3690,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -3662,6 +3720,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -3705,6 +3764,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -3734,6 +3794,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -3877,6 +3938,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -3909,6 +3971,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -3968,6 +4031,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -4011,6 +4075,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -4040,6 +4105,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -4083,6 +4149,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -4112,6 +4179,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -4258,6 +4326,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -4290,6 +4359,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -4349,6 +4419,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -4392,6 +4463,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -4421,6 +4493,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -4467,6 +4540,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [
|
"groupIds": [
|
||||||
@@ -4498,6 +4572,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [
|
"groupIds": [
|
||||||
@@ -4544,6 +4619,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -4573,6 +4649,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -4992,6 +5069,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -5024,6 +5102,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -5083,6 +5162,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -5126,6 +5206,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -5155,6 +5236,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -5576,6 +5658,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [
|
"groupIds": [
|
||||||
@@ -5610,6 +5693,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [
|
"groupIds": [
|
||||||
@@ -5671,6 +5755,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -5714,6 +5799,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -5743,6 +5829,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -5789,6 +5876,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [
|
"groupIds": [
|
||||||
@@ -5820,6 +5908,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [
|
"groupIds": [
|
||||||
@@ -6872,6 +6961,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] el
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -6904,6 +6994,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] el
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "red",
|
"backgroundColor": "red",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -6936,6 +7027,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] el
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "red",
|
"backgroundColor": "red",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -6995,6 +7087,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] hi
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ exports[`Test dragCreate > add element to the scene when pointer dragging long e
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"endArrowhead": "arrow",
|
"endArrowhead": "arrow",
|
||||||
"endBinding": null,
|
"endBinding": null,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
@@ -56,6 +57,7 @@ exports[`Test dragCreate > add element to the scene when pointer dragging long e
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -90,6 +92,7 @@ exports[`Test dragCreate > add element to the scene when pointer dragging long e
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -122,6 +125,7 @@ exports[`Test dragCreate > add element to the scene when pointer dragging long e
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"endArrowhead": null,
|
"endArrowhead": null,
|
||||||
"endBinding": null,
|
"endBinding": null,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
@@ -171,6 +175,7 @@ exports[`Test dragCreate > add element to the scene when pointer dragging long e
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ exports[`duplicate element on move when ALT is clicked > rectangle 5`] = `
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -37,6 +38,7 @@ exports[`duplicate element on move when ALT is clicked > rectangle 6`] = `
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -69,6 +71,7 @@ exports[`move element > rectangle 5`] = `
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -106,6 +109,7 @@ exports[`move element > rectangles with binding arrow 5`] = `
|
|||||||
"type": "arrow",
|
"type": "arrow",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -143,6 +147,7 @@ exports[`move element > rectangles with binding arrow 6`] = `
|
|||||||
"type": "arrow",
|
"type": "arrow",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -175,6 +180,7 @@ exports[`move element > rectangles with binding arrow 7`] = `
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"endArrowhead": null,
|
"endArrowhead": null,
|
||||||
"endBinding": {
|
"endBinding": {
|
||||||
"elementId": "id1",
|
"elementId": "id1",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ exports[`multi point mode in linear elements > arrow 3`] = `
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"endArrowhead": "arrow",
|
"endArrowhead": "arrow",
|
||||||
"endBinding": null,
|
"endBinding": null,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
@@ -59,6 +60,7 @@ exports[`multi point mode in linear elements > line 3`] = `
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"endArrowhead": null,
|
"endArrowhead": null,
|
||||||
"endBinding": null,
|
"endBinding": null,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,7 @@ exports[`select single element on the scene > arrow 1`] = `
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"endArrowhead": "arrow",
|
"endArrowhead": "arrow",
|
||||||
"endBinding": null,
|
"endBinding": null,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
@@ -52,6 +53,7 @@ exports[`select single element on the scene > arrow escape 1`] = `
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"endArrowhead": null,
|
"endArrowhead": null,
|
||||||
"endBinding": null,
|
"endBinding": null,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
@@ -99,6 +101,7 @@ exports[`select single element on the scene > diamond 1`] = `
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -131,6 +134,7 @@ exports[`select single element on the scene > ellipse 1`] = `
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -163,6 +167,7 @@ exports[`select single element on the scene > rectangle 1`] = `
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": null,
|
"boundElements": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { getTransformHandles } from "../element/transformHandles";
|
|||||||
import { API } from "./helpers/api";
|
import { API } from "./helpers/api";
|
||||||
import { KEYS } from "../keys";
|
import { KEYS } from "../keys";
|
||||||
import { actionWrapTextInContainer } from "../actions/actionBoundText";
|
import { actionWrapTextInContainer } from "../actions/actionBoundText";
|
||||||
|
import { arrayToMap } from "../utils";
|
||||||
|
|
||||||
const { h } = window;
|
const { h } = window;
|
||||||
|
|
||||||
@@ -91,8 +92,12 @@ describe("element binding", () => {
|
|||||||
expect(arrow.startBinding?.elementId).toBe(rectLeft.id);
|
expect(arrow.startBinding?.elementId).toBe(rectLeft.id);
|
||||||
expect(arrow.endBinding?.elementId).toBe(rectRight.id);
|
expect(arrow.endBinding?.elementId).toBe(rectRight.id);
|
||||||
|
|
||||||
const rotation = getTransformHandles(arrow, h.state.zoom, "mouse")
|
const rotation = getTransformHandles(
|
||||||
.rotation!;
|
arrow,
|
||||||
|
h.state.zoom,
|
||||||
|
arrayToMap(h.elements),
|
||||||
|
"mouse",
|
||||||
|
).rotation!;
|
||||||
const rotationHandleX = rotation[0] + rotation[2] / 2;
|
const rotationHandleX = rotation[0] + rotation[2] / 2;
|
||||||
const rotationHandleY = rotation[1] + rotation[3] / 2;
|
const rotationHandleY = rotation[1] + rotation[3] / 2;
|
||||||
mouse.down(rotationHandleX, rotationHandleY);
|
mouse.down(rotationHandleX, rotationHandleY);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { getElementBounds } from "../element";
|
|||||||
import { NormalizedZoomValue } from "../types";
|
import { NormalizedZoomValue } from "../types";
|
||||||
import { API } from "./helpers/api";
|
import { API } from "./helpers/api";
|
||||||
import { createPasteEvent, serializeAsClipboardJSON } from "../clipboard";
|
import { createPasteEvent, serializeAsClipboardJSON } from "../clipboard";
|
||||||
|
import { arrayToMap } from "../utils";
|
||||||
|
|
||||||
const { h } = window;
|
const { h } = window;
|
||||||
|
|
||||||
@@ -138,6 +139,8 @@ describe("paste text as single lines", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should space items correctly", async () => {
|
it("should space items correctly", async () => {
|
||||||
|
const elementsMap = arrayToMap(h.elements);
|
||||||
|
|
||||||
const text = "hkhkjhki\njgkjhffjh\njgkjhffjh";
|
const text = "hkhkjhki\njgkjhffjh\njgkjhffjh";
|
||||||
const lineHeightPx =
|
const lineHeightPx =
|
||||||
getLineHeightInPx(
|
getLineHeightInPx(
|
||||||
@@ -149,16 +152,17 @@ describe("paste text as single lines", () => {
|
|||||||
pasteWithCtrlCmdV(text);
|
pasteWithCtrlCmdV(text);
|
||||||
await waitFor(async () => {
|
await waitFor(async () => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const [fx, firstElY] = getElementBounds(h.elements[0]);
|
const [fx, firstElY] = getElementBounds(h.elements[0], elementsMap);
|
||||||
for (let i = 1; i < h.elements.length; i++) {
|
for (let i = 1; i < h.elements.length; i++) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const [fx, elY] = getElementBounds(h.elements[i]);
|
const [fx, elY] = getElementBounds(h.elements[i], elementsMap);
|
||||||
expect(elY).toEqual(firstElY + lineHeightPx * i);
|
expect(elY).toEqual(firstElY + lineHeightPx * i);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should leave a space for blank new lines", async () => {
|
it("should leave a space for blank new lines", async () => {
|
||||||
|
const elementsMap = arrayToMap(h.elements);
|
||||||
const text = "hkhkjhki\n\njgkjhffjh";
|
const text = "hkhkjhki\n\njgkjhffjh";
|
||||||
const lineHeightPx =
|
const lineHeightPx =
|
||||||
getLineHeightInPx(
|
getLineHeightInPx(
|
||||||
@@ -168,11 +172,12 @@ describe("paste text as single lines", () => {
|
|||||||
10 / h.app.state.zoom.value;
|
10 / h.app.state.zoom.value;
|
||||||
mouse.moveTo(100, 100);
|
mouse.moveTo(100, 100);
|
||||||
pasteWithCtrlCmdV(text);
|
pasteWithCtrlCmdV(text);
|
||||||
|
|
||||||
await waitFor(async () => {
|
await waitFor(async () => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const [fx, firstElY] = getElementBounds(h.elements[0]);
|
const [fx, firstElY] = getElementBounds(h.elements[0], elementsMap);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const [lx, lastElY] = getElementBounds(h.elements[1]);
|
const [lx, lastElY] = getElementBounds(h.elements[1], elementsMap);
|
||||||
expect(lastElY).toEqual(firstElY + lineHeightPx * 2);
|
expect(lastElY).toEqual(firstElY + lineHeightPx * 2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ exports[`restoreElements > should restore arrow element correctly 1`] = `
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": [],
|
"boundElements": [],
|
||||||
|
"customData": undefined,
|
||||||
"endArrowhead": null,
|
"endArrowhead": null,
|
||||||
"endBinding": null,
|
"endBinding": null,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
@@ -52,6 +53,7 @@ exports[`restoreElements > should restore correctly with rectangle, ellipse and
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "blue",
|
"backgroundColor": "blue",
|
||||||
"boundElements": [],
|
"boundElements": [],
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "cross-hatch",
|
"fillStyle": "cross-hatch",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [
|
"groupIds": [
|
||||||
@@ -88,6 +90,7 @@ exports[`restoreElements > should restore correctly with rectangle, ellipse and
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "blue",
|
"backgroundColor": "blue",
|
||||||
"boundElements": [],
|
"boundElements": [],
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "cross-hatch",
|
"fillStyle": "cross-hatch",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [
|
"groupIds": [
|
||||||
@@ -124,6 +127,7 @@ exports[`restoreElements > should restore correctly with rectangle, ellipse and
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "blue",
|
"backgroundColor": "blue",
|
||||||
"boundElements": [],
|
"boundElements": [],
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "cross-hatch",
|
"fillStyle": "cross-hatch",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [
|
"groupIds": [
|
||||||
@@ -160,6 +164,7 @@ exports[`restoreElements > should restore freedraw element correctly 1`] = `
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": [],
|
"boundElements": [],
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -196,6 +201,7 @@ exports[`restoreElements > should restore line and draw elements correctly 1`] =
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": [],
|
"boundElements": [],
|
||||||
|
"customData": undefined,
|
||||||
"endArrowhead": null,
|
"endArrowhead": null,
|
||||||
"endBinding": null,
|
"endBinding": null,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
@@ -243,6 +249,7 @@ exports[`restoreElements > should restore line and draw elements correctly 2`] =
|
|||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"boundElements": [],
|
"boundElements": [],
|
||||||
|
"customData": undefined,
|
||||||
"endArrowhead": null,
|
"endArrowhead": null,
|
||||||
"endBinding": null,
|
"endBinding": null,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
@@ -292,6 +299,7 @@ exports[`restoreElements > should restore text element correctly passing value f
|
|||||||
"baseline": 0,
|
"baseline": 0,
|
||||||
"boundElements": [],
|
"boundElements": [],
|
||||||
"containerId": null,
|
"containerId": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"fontFamily": 1,
|
"fontFamily": 1,
|
||||||
"fontSize": 14,
|
"fontSize": 14,
|
||||||
@@ -333,6 +341,7 @@ exports[`restoreElements > should restore text element correctly with unknown fo
|
|||||||
"baseline": 0,
|
"baseline": 0,
|
||||||
"boundElements": [],
|
"boundElements": [],
|
||||||
"containerId": null,
|
"containerId": null,
|
||||||
|
"customData": undefined,
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"fontFamily": 1,
|
"fontFamily": 1,
|
||||||
"fontSize": 10,
|
"fontSize": 10,
|
||||||
|
|||||||
@@ -303,7 +303,7 @@ describe("<Excalidraw/>", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("Test name prop", () => {
|
describe("Test name prop", () => {
|
||||||
it('should allow editing name when the name prop is "undefined"', async () => {
|
it("should allow editing name", async () => {
|
||||||
const { container } = await render(<Excalidraw />);
|
const { container } = await render(<Excalidraw />);
|
||||||
//open menu
|
//open menu
|
||||||
toggleMenu(container);
|
toggleMenu(container);
|
||||||
@@ -315,7 +315,7 @@ describe("<Excalidraw/>", () => {
|
|||||||
expect(textInput?.nodeName).toBe("INPUT");
|
expect(textInput?.nodeName).toBe("INPUT");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set the name and not allow editing when the name prop is present"', async () => {
|
it('should set the name when the name prop is present"', async () => {
|
||||||
const name = "test";
|
const name = "test";
|
||||||
const { container } = await render(<Excalidraw name={name} />);
|
const { container } = await render(<Excalidraw name={name} />);
|
||||||
//open menu
|
//open menu
|
||||||
@@ -326,7 +326,6 @@ describe("<Excalidraw/>", () => {
|
|||||||
) as HTMLInputElement;
|
) as HTMLInputElement;
|
||||||
expect(textInput?.value).toEqual(name);
|
expect(textInput?.value).toEqual(name);
|
||||||
expect(textInput?.nodeName).toBe("INPUT");
|
expect(textInput?.nodeName).toBe("INPUT");
|
||||||
expect(textInput?.disabled).toBe(true);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import * as blob from "../data/blob";
|
|||||||
import { KEYS } from "../keys";
|
import { KEYS } from "../keys";
|
||||||
import { getBoundTextElementPosition } from "../element/textElement";
|
import { getBoundTextElementPosition } from "../element/textElement";
|
||||||
import { createPasteEvent } from "../clipboard";
|
import { createPasteEvent } from "../clipboard";
|
||||||
import { cloneJSON } from "../utils";
|
import { arrayToMap, cloneJSON } from "../utils";
|
||||||
|
|
||||||
const { h } = window;
|
const { h } = window;
|
||||||
const mouse = new Pointer("mouse");
|
const mouse = new Pointer("mouse");
|
||||||
@@ -194,9 +194,10 @@ const checkElementsBoundingBox = async (
|
|||||||
element2: ExcalidrawElement,
|
element2: ExcalidrawElement,
|
||||||
toleranceInPx: number = 0,
|
toleranceInPx: number = 0,
|
||||||
) => {
|
) => {
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element1);
|
const elementsMap = arrayToMap([element1, element2]);
|
||||||
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element1, elementsMap);
|
||||||
|
|
||||||
const [x12, y12, x22, y22] = getElementAbsoluteCoords(element2);
|
const [x12, y12, x22, y22] = getElementAbsoluteCoords(element2, elementsMap);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
// Check if width and height did not change
|
// Check if width and height did not change
|
||||||
@@ -853,7 +854,11 @@ describe("mutliple elements", () => {
|
|||||||
h.app.actionManager.executeAction(actionFlipVertical);
|
h.app.actionManager.executeAction(actionFlipVertical);
|
||||||
|
|
||||||
const arrowText = h.elements[1] as ExcalidrawTextElementWithContainer;
|
const arrowText = h.elements[1] as ExcalidrawTextElementWithContainer;
|
||||||
const arrowTextPos = getBoundTextElementPosition(arrow.get(), arrowText)!;
|
const arrowTextPos = getBoundTextElementPosition(
|
||||||
|
arrow.get(),
|
||||||
|
arrowText,
|
||||||
|
arrayToMap(h.elements),
|
||||||
|
)!;
|
||||||
const rectText = h.elements[3] as ExcalidrawTextElementWithContainer;
|
const rectText = h.elements[3] as ExcalidrawTextElementWithContainer;
|
||||||
|
|
||||||
expect(arrow.x).toBeCloseTo(180);
|
expect(arrow.x).toBeCloseTo(180);
|
||||||
|
|||||||
@@ -31,8 +31,11 @@ import { getSelectedElements } from "../../scene/selection";
|
|||||||
import { isLinearElementType } from "../../element/typeChecks";
|
import { isLinearElementType } from "../../element/typeChecks";
|
||||||
import { Mutable } from "../../utility-types";
|
import { Mutable } from "../../utility-types";
|
||||||
import { assertNever } from "../../utils";
|
import { assertNever } from "../../utils";
|
||||||
|
import { createTestHook } from "../../components/App";
|
||||||
|
|
||||||
const readFile = util.promisify(fs.readFile);
|
const readFile = util.promisify(fs.readFile);
|
||||||
|
// so that window.h is available when App.tsx is not imported as well.
|
||||||
|
createTestHook();
|
||||||
|
|
||||||
const { h } = window;
|
const { h } = window;
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,11 @@ import {
|
|||||||
import { getCommonBounds, getElementPointsCoords } from "../../element/bounds";
|
import { getCommonBounds, getElementPointsCoords } from "../../element/bounds";
|
||||||
import { rotatePoint } from "../../math";
|
import { rotatePoint } from "../../math";
|
||||||
import { getTextEditor } from "../queries/dom";
|
import { getTextEditor } from "../queries/dom";
|
||||||
|
import { arrayToMap } from "../../utils";
|
||||||
|
import { createTestHook } from "../../components/App";
|
||||||
|
|
||||||
|
// so that window.h is available when App.tsx is not imported as well.
|
||||||
|
createTestHook();
|
||||||
|
|
||||||
const { h } = window;
|
const { h } = window;
|
||||||
|
|
||||||
@@ -286,9 +291,12 @@ const transform = (
|
|||||||
let handleCoords: TransformHandle | undefined;
|
let handleCoords: TransformHandle | undefined;
|
||||||
|
|
||||||
if (elements.length === 1) {
|
if (elements.length === 1) {
|
||||||
handleCoords = getTransformHandles(elements[0], h.state.zoom, "mouse")[
|
handleCoords = getTransformHandles(
|
||||||
handle
|
elements[0],
|
||||||
];
|
h.state.zoom,
|
||||||
|
arrayToMap(h.elements),
|
||||||
|
"mouse",
|
||||||
|
)[handle];
|
||||||
} else {
|
} else {
|
||||||
const [x1, y1, x2, y2] = getCommonBounds(elements);
|
const [x1, y1, x2, y2] = getCommonBounds(elements);
|
||||||
const isFrameSelected = elements.some(isFrameLikeElement);
|
const isFrameSelected = elements.some(isFrameLikeElement);
|
||||||
@@ -456,7 +464,6 @@ export class UI {
|
|||||||
mouse.reset();
|
mouse.reset();
|
||||||
mouse.up(x + width, y + height);
|
mouse.up(x + width, y + height);
|
||||||
}
|
}
|
||||||
|
|
||||||
const origElement = h.elements[h.elements.length - 1] as any;
|
const origElement = h.elements[h.elements.length - 1] as any;
|
||||||
|
|
||||||
if (angle !== 0) {
|
if (angle !== 0) {
|
||||||
|
|||||||
@@ -343,6 +343,8 @@ describe("Test Linear Elements", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should update all the midpoints when element position changed", async () => {
|
it("should update all the midpoints when element position changed", async () => {
|
||||||
|
const elementsMap = arrayToMap(h.elements);
|
||||||
|
|
||||||
createThreePointerLinearElement("line", {
|
createThreePointerLinearElement("line", {
|
||||||
type: ROUNDNESS.PROPORTIONAL_RADIUS,
|
type: ROUNDNESS.PROPORTIONAL_RADIUS,
|
||||||
});
|
});
|
||||||
@@ -351,7 +353,10 @@ describe("Test Linear Elements", () => {
|
|||||||
expect(line.points.length).toEqual(3);
|
expect(line.points.length).toEqual(3);
|
||||||
enterLineEditingMode(line);
|
enterLineEditingMode(line);
|
||||||
|
|
||||||
const points = LinearElementEditor.getPointsGlobalCoordinates(line);
|
const points = LinearElementEditor.getPointsGlobalCoordinates(
|
||||||
|
line,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
expect([line.x, line.y]).toEqual(points[0]);
|
expect([line.x, line.y]).toEqual(points[0]);
|
||||||
|
|
||||||
const midPoints = LinearElementEditor.getEditorMidPoints(
|
const midPoints = LinearElementEditor.getEditorMidPoints(
|
||||||
@@ -465,7 +470,11 @@ describe("Test Linear Elements", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should update only the first segment midpoint when its point is dragged", async () => {
|
it("should update only the first segment midpoint when its point is dragged", async () => {
|
||||||
const points = LinearElementEditor.getPointsGlobalCoordinates(line);
|
const elementsMap = arrayToMap(h.elements);
|
||||||
|
const points = LinearElementEditor.getPointsGlobalCoordinates(
|
||||||
|
line,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
const midPoints = LinearElementEditor.getEditorMidPoints(
|
const midPoints = LinearElementEditor.getEditorMidPoints(
|
||||||
line,
|
line,
|
||||||
h.app.scene.getNonDeletedElementsMap(),
|
h.app.scene.getNonDeletedElementsMap(),
|
||||||
@@ -482,7 +491,10 @@ describe("Test Linear Elements", () => {
|
|||||||
);
|
);
|
||||||
expect(renderStaticScene.mock.calls.length).toMatchInlineSnapshot(`8`);
|
expect(renderStaticScene.mock.calls.length).toMatchInlineSnapshot(`8`);
|
||||||
|
|
||||||
const newPoints = LinearElementEditor.getPointsGlobalCoordinates(line);
|
const newPoints = LinearElementEditor.getPointsGlobalCoordinates(
|
||||||
|
line,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
expect([newPoints[0][0], newPoints[0][1]]).toEqual([
|
expect([newPoints[0][0], newPoints[0][1]]).toEqual([
|
||||||
points[0][0] - delta,
|
points[0][0] - delta,
|
||||||
points[0][1] - delta,
|
points[0][1] - delta,
|
||||||
@@ -499,7 +511,11 @@ describe("Test Linear Elements", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should hide midpoints in the segment when points moved close", async () => {
|
it("should hide midpoints in the segment when points moved close", async () => {
|
||||||
const points = LinearElementEditor.getPointsGlobalCoordinates(line);
|
const elementsMap = arrayToMap(h.elements);
|
||||||
|
const points = LinearElementEditor.getPointsGlobalCoordinates(
|
||||||
|
line,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
const midPoints = LinearElementEditor.getEditorMidPoints(
|
const midPoints = LinearElementEditor.getEditorMidPoints(
|
||||||
line,
|
line,
|
||||||
h.app.scene.getNonDeletedElementsMap(),
|
h.app.scene.getNonDeletedElementsMap(),
|
||||||
@@ -516,7 +532,10 @@ describe("Test Linear Elements", () => {
|
|||||||
);
|
);
|
||||||
expect(renderStaticScene.mock.calls.length).toMatchInlineSnapshot(`8`);
|
expect(renderStaticScene.mock.calls.length).toMatchInlineSnapshot(`8`);
|
||||||
|
|
||||||
const newPoints = LinearElementEditor.getPointsGlobalCoordinates(line);
|
const newPoints = LinearElementEditor.getPointsGlobalCoordinates(
|
||||||
|
line,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
expect([newPoints[0][0], newPoints[0][1]]).toEqual([
|
expect([newPoints[0][0], newPoints[0][1]]).toEqual([
|
||||||
points[0][0] + delta,
|
points[0][0] + delta,
|
||||||
points[0][1] + delta,
|
points[0][1] + delta,
|
||||||
@@ -535,7 +554,10 @@ describe("Test Linear Elements", () => {
|
|||||||
it("should remove the midpoint when one of the points in the segment is deleted", async () => {
|
it("should remove the midpoint when one of the points in the segment is deleted", async () => {
|
||||||
const line = h.elements[0] as ExcalidrawLinearElement;
|
const line = h.elements[0] as ExcalidrawLinearElement;
|
||||||
enterLineEditingMode(line);
|
enterLineEditingMode(line);
|
||||||
const points = LinearElementEditor.getPointsGlobalCoordinates(line);
|
const points = LinearElementEditor.getPointsGlobalCoordinates(
|
||||||
|
line,
|
||||||
|
arrayToMap(h.elements),
|
||||||
|
);
|
||||||
|
|
||||||
// dragging line from last segment midpoint
|
// dragging line from last segment midpoint
|
||||||
drag(lastSegmentMidpoint, [
|
drag(lastSegmentMidpoint, [
|
||||||
@@ -637,7 +659,11 @@ describe("Test Linear Elements", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should update all the midpoints when its point is dragged", async () => {
|
it("should update all the midpoints when its point is dragged", async () => {
|
||||||
const points = LinearElementEditor.getPointsGlobalCoordinates(line);
|
const elementsMap = arrayToMap(h.elements);
|
||||||
|
const points = LinearElementEditor.getPointsGlobalCoordinates(
|
||||||
|
line,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
const midPoints = LinearElementEditor.getEditorMidPoints(
|
const midPoints = LinearElementEditor.getEditorMidPoints(
|
||||||
line,
|
line,
|
||||||
h.app.scene.getNonDeletedElementsMap(),
|
h.app.scene.getNonDeletedElementsMap(),
|
||||||
@@ -649,7 +675,10 @@ describe("Test Linear Elements", () => {
|
|||||||
// Drag from first point
|
// Drag from first point
|
||||||
drag(hitCoords, [hitCoords[0] - delta, hitCoords[1] - delta]);
|
drag(hitCoords, [hitCoords[0] - delta, hitCoords[1] - delta]);
|
||||||
|
|
||||||
const newPoints = LinearElementEditor.getPointsGlobalCoordinates(line);
|
const newPoints = LinearElementEditor.getPointsGlobalCoordinates(
|
||||||
|
line,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
expect([newPoints[0][0], newPoints[0][1]]).toEqual([
|
expect([newPoints[0][0], newPoints[0][1]]).toEqual([
|
||||||
points[0][0] - delta,
|
points[0][0] - delta,
|
||||||
points[0][1] - delta,
|
points[0][1] - delta,
|
||||||
@@ -678,7 +707,11 @@ describe("Test Linear Elements", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should hide midpoints in the segment when points moved close", async () => {
|
it("should hide midpoints in the segment when points moved close", async () => {
|
||||||
const points = LinearElementEditor.getPointsGlobalCoordinates(line);
|
const elementsMap = arrayToMap(h.elements);
|
||||||
|
const points = LinearElementEditor.getPointsGlobalCoordinates(
|
||||||
|
line,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
const midPoints = LinearElementEditor.getEditorMidPoints(
|
const midPoints = LinearElementEditor.getEditorMidPoints(
|
||||||
line,
|
line,
|
||||||
h.app.scene.getNonDeletedElementsMap(),
|
h.app.scene.getNonDeletedElementsMap(),
|
||||||
@@ -695,7 +728,10 @@ describe("Test Linear Elements", () => {
|
|||||||
);
|
);
|
||||||
expect(renderStaticScene.mock.calls.length).toMatchInlineSnapshot(`8`);
|
expect(renderStaticScene.mock.calls.length).toMatchInlineSnapshot(`8`);
|
||||||
|
|
||||||
const newPoints = LinearElementEditor.getPointsGlobalCoordinates(line);
|
const newPoints = LinearElementEditor.getPointsGlobalCoordinates(
|
||||||
|
line,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
expect([newPoints[0][0], newPoints[0][1]]).toEqual([
|
expect([newPoints[0][0], newPoints[0][1]]).toEqual([
|
||||||
points[0][0] + delta,
|
points[0][0] + delta,
|
||||||
points[0][1] + delta,
|
points[0][1] + delta,
|
||||||
@@ -712,6 +748,8 @@ describe("Test Linear Elements", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should update all the midpoints when a point is deleted", async () => {
|
it("should update all the midpoints when a point is deleted", async () => {
|
||||||
|
const elementsMap = arrayToMap(h.elements);
|
||||||
|
|
||||||
drag(lastSegmentMidpoint, [
|
drag(lastSegmentMidpoint, [
|
||||||
lastSegmentMidpoint[0] + delta,
|
lastSegmentMidpoint[0] + delta,
|
||||||
lastSegmentMidpoint[1] + delta,
|
lastSegmentMidpoint[1] + delta,
|
||||||
@@ -723,7 +761,10 @@ describe("Test Linear Elements", () => {
|
|||||||
h.app.scene.getNonDeletedElementsMap(),
|
h.app.scene.getNonDeletedElementsMap(),
|
||||||
h.state,
|
h.state,
|
||||||
);
|
);
|
||||||
const points = LinearElementEditor.getPointsGlobalCoordinates(line);
|
const points = LinearElementEditor.getPointsGlobalCoordinates(
|
||||||
|
line,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
|
||||||
// delete 3rd point
|
// delete 3rd point
|
||||||
deletePoint(points[2]);
|
deletePoint(points[2]);
|
||||||
@@ -837,6 +878,7 @@ describe("Test Linear Elements", () => {
|
|||||||
const position = LinearElementEditor.getBoundTextElementPosition(
|
const position = LinearElementEditor.getBoundTextElementPosition(
|
||||||
container,
|
container,
|
||||||
textElement,
|
textElement,
|
||||||
|
arrayToMap(h.elements),
|
||||||
);
|
);
|
||||||
expect(position).toMatchInlineSnapshot(`
|
expect(position).toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
@@ -859,6 +901,7 @@ describe("Test Linear Elements", () => {
|
|||||||
const position = LinearElementEditor.getBoundTextElementPosition(
|
const position = LinearElementEditor.getBoundTextElementPosition(
|
||||||
container,
|
container,
|
||||||
textElement,
|
textElement,
|
||||||
|
arrayToMap(h.elements),
|
||||||
);
|
);
|
||||||
expect(position).toMatchInlineSnapshot(`
|
expect(position).toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
@@ -893,6 +936,7 @@ describe("Test Linear Elements", () => {
|
|||||||
const position = LinearElementEditor.getBoundTextElementPosition(
|
const position = LinearElementEditor.getBoundTextElementPosition(
|
||||||
container,
|
container,
|
||||||
textElement,
|
textElement,
|
||||||
|
arrayToMap(h.elements),
|
||||||
);
|
);
|
||||||
expect(position).toMatchInlineSnapshot(`
|
expect(position).toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
@@ -1012,8 +1056,13 @@ describe("Test Linear Elements", () => {
|
|||||||
);
|
);
|
||||||
expect(container.width).toBe(70);
|
expect(container.width).toBe(70);
|
||||||
expect(container.height).toBe(50);
|
expect(container.height).toBe(50);
|
||||||
expect(getBoundTextElementPosition(container, textElement))
|
expect(
|
||||||
.toMatchInlineSnapshot(`
|
getBoundTextElementPosition(
|
||||||
|
container,
|
||||||
|
textElement,
|
||||||
|
arrayToMap(h.elements),
|
||||||
|
),
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
"x": 75,
|
"x": 75,
|
||||||
"y": 60,
|
"y": 60,
|
||||||
@@ -1051,8 +1100,13 @@ describe("Test Linear Elements", () => {
|
|||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
expect(getBoundTextElementPosition(container, textElement))
|
expect(
|
||||||
.toMatchInlineSnapshot(`
|
getBoundTextElementPosition(
|
||||||
|
container,
|
||||||
|
textElement,
|
||||||
|
arrayToMap(h.elements),
|
||||||
|
),
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
"x": 271.11716195150507,
|
"x": 271.11716195150507,
|
||||||
"y": 45,
|
"y": 45,
|
||||||
@@ -1090,7 +1144,8 @@ describe("Test Linear Elements", () => {
|
|||||||
arrow,
|
arrow,
|
||||||
);
|
);
|
||||||
expect(container.width).toBe(40);
|
expect(container.width).toBe(40);
|
||||||
expect(getBoundTextElementPosition(container, textElement))
|
const elementsMap = arrayToMap(h.elements);
|
||||||
|
expect(getBoundTextElementPosition(container, textElement, elementsMap))
|
||||||
.toMatchInlineSnapshot(`
|
.toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
"x": 25,
|
"x": 25,
|
||||||
@@ -1102,7 +1157,10 @@ describe("Test Linear Elements", () => {
|
|||||||
collaboration made
|
collaboration made
|
||||||
easy"
|
easy"
|
||||||
`);
|
`);
|
||||||
const points = LinearElementEditor.getPointsGlobalCoordinates(container);
|
const points = LinearElementEditor.getPointsGlobalCoordinates(
|
||||||
|
container,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
|
||||||
// Drag from last point
|
// Drag from last point
|
||||||
drag(points[1], [points[1][0] + 300, points[1][1]]);
|
drag(points[1], [points[1][0] + 300, points[1][1]]);
|
||||||
@@ -1115,7 +1173,7 @@ describe("Test Linear Elements", () => {
|
|||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
expect(getBoundTextElementPosition(container, textElement))
|
expect(getBoundTextElementPosition(container, textElement, elementsMap))
|
||||||
.toMatchInlineSnapshot(`
|
.toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
"x": 75,
|
"x": 75,
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import React from "react";
|
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
import { render, fireEvent } from "./test-utils";
|
import { render, fireEvent } from "./test-utils";
|
||||||
import { Excalidraw } from "../index";
|
import { Excalidraw } from "../index";
|
||||||
@@ -75,12 +74,13 @@ describe("move element", () => {
|
|||||||
const rectA = UI.createElement("rectangle", { size: 100 });
|
const rectA = UI.createElement("rectangle", { size: 100 });
|
||||||
const rectB = UI.createElement("rectangle", { x: 200, y: 0, size: 300 });
|
const rectB = UI.createElement("rectangle", { x: 200, y: 0, size: 300 });
|
||||||
const line = UI.createElement("line", { x: 110, y: 50, size: 80 });
|
const line = UI.createElement("line", { x: 110, y: 50, size: 80 });
|
||||||
|
const elementsMap = h.app.scene.getNonDeletedElementsMap();
|
||||||
// bind line to two rectangles
|
// bind line to two rectangles
|
||||||
bindOrUnbindLinearElement(
|
bindOrUnbindLinearElement(
|
||||||
line.get() as NonDeleted<ExcalidrawLinearElement>,
|
line.get() as NonDeleted<ExcalidrawLinearElement>,
|
||||||
rectA.get() as ExcalidrawRectangleElement,
|
rectA.get() as ExcalidrawRectangleElement,
|
||||||
rectB.get() as ExcalidrawRectangleElement,
|
rectB.get() as ExcalidrawRectangleElement,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
|
|
||||||
// select the second rectangles
|
// select the second rectangles
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { API } from "./helpers/api";
|
|||||||
import { KEYS } from "../keys";
|
import { KEYS } from "../keys";
|
||||||
import { isLinearElement } from "../element/typeChecks";
|
import { isLinearElement } from "../element/typeChecks";
|
||||||
import { LinearElementEditor } from "../element/linearElementEditor";
|
import { LinearElementEditor } from "../element/linearElementEditor";
|
||||||
|
import { arrayToMap } from "../utils";
|
||||||
|
|
||||||
ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
|
ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
|
||||||
|
|
||||||
@@ -301,10 +302,12 @@ describe("arrow element", () => {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
const label = await UI.editText(arrow, "Hello");
|
const label = await UI.editText(arrow, "Hello");
|
||||||
|
const elementsMap = arrayToMap(h.elements);
|
||||||
UI.resize(arrow, "se", [50, 30]);
|
UI.resize(arrow, "se", [50, 30]);
|
||||||
let labelPos = LinearElementEditor.getBoundTextElementPosition(
|
let labelPos = LinearElementEditor.getBoundTextElementPosition(
|
||||||
arrow,
|
arrow,
|
||||||
label,
|
label,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(labelPos.x + label.width / 2).toBeCloseTo(
|
expect(labelPos.x + label.width / 2).toBeCloseTo(
|
||||||
@@ -317,7 +320,11 @@ describe("arrow element", () => {
|
|||||||
expect(label.fontSize).toEqual(20);
|
expect(label.fontSize).toEqual(20);
|
||||||
|
|
||||||
UI.resize(arrow, "w", [20, 0]);
|
UI.resize(arrow, "w", [20, 0]);
|
||||||
labelPos = LinearElementEditor.getBoundTextElementPosition(arrow, label);
|
labelPos = LinearElementEditor.getBoundTextElementPosition(
|
||||||
|
arrow,
|
||||||
|
label,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
|
||||||
expect(labelPos.x + label.width / 2).toBeCloseTo(
|
expect(labelPos.x + label.width / 2).toBeCloseTo(
|
||||||
arrow.x + arrow.points[2][0],
|
arrow.x + arrow.points[2][0],
|
||||||
@@ -743,15 +750,17 @@ describe("multiple selection", () => {
|
|||||||
const selectionTop = 20 - topArrowLabel.height / 2;
|
const selectionTop = 20 - topArrowLabel.height / 2;
|
||||||
const move = [80, 0] as [number, number];
|
const move = [80, 0] as [number, number];
|
||||||
const scale = move[0] / selectionWidth + 1;
|
const scale = move[0] / selectionWidth + 1;
|
||||||
|
const elementsMap = arrayToMap(h.elements);
|
||||||
UI.resize([topArrow.get(), bottomArrow.get()], "se", move);
|
UI.resize([topArrow.get(), bottomArrow.get()], "se", move);
|
||||||
const topArrowLabelPos = LinearElementEditor.getBoundTextElementPosition(
|
const topArrowLabelPos = LinearElementEditor.getBoundTextElementPosition(
|
||||||
topArrow,
|
topArrow,
|
||||||
topArrowLabel,
|
topArrowLabel,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
const bottomArrowLabelPos = LinearElementEditor.getBoundTextElementPosition(
|
const bottomArrowLabelPos = LinearElementEditor.getBoundTextElementPosition(
|
||||||
bottomArrow,
|
bottomArrow,
|
||||||
bottomArrowLabel,
|
bottomArrowLabel,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(topArrow.x).toBeCloseTo(0);
|
expect(topArrow.x).toBeCloseTo(0);
|
||||||
@@ -944,12 +953,13 @@ describe("multiple selection", () => {
|
|||||||
const scaleX = move[0] / selectionWidth + 1;
|
const scaleX = move[0] / selectionWidth + 1;
|
||||||
const scaleY = -scaleX;
|
const scaleY = -scaleX;
|
||||||
const lineOrigBounds = getBoundsFromPoints(line);
|
const lineOrigBounds = getBoundsFromPoints(line);
|
||||||
|
const elementsMap = arrayToMap(h.elements);
|
||||||
UI.resize([line, image, rectangle, boundArrow], "se", move);
|
UI.resize([line, image, rectangle, boundArrow], "se", move);
|
||||||
const lineNewBounds = getBoundsFromPoints(line);
|
const lineNewBounds = getBoundsFromPoints(line);
|
||||||
const arrowLabelPos = LinearElementEditor.getBoundTextElementPosition(
|
const arrowLabelPos = LinearElementEditor.getBoundTextElementPosition(
|
||||||
boundArrow,
|
boundArrow,
|
||||||
arrowLabel,
|
arrowLabel,
|
||||||
|
elementsMap,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(line.x).toBeCloseTo(60 * scaleX);
|
expect(line.x).toBeCloseTo(60 * scaleX);
|
||||||
|
|||||||
@@ -247,7 +247,7 @@ export interface AppState {
|
|||||||
scrollY: number;
|
scrollY: number;
|
||||||
cursorButton: "up" | "down";
|
cursorButton: "up" | "down";
|
||||||
scrolledOutside: boolean;
|
scrolledOutside: boolean;
|
||||||
name: string;
|
name: string | null;
|
||||||
isResizing: boolean;
|
isResizing: boolean;
|
||||||
isRotating: boolean;
|
isRotating: boolean;
|
||||||
zoom: Zoom;
|
zoom: Zoom;
|
||||||
@@ -435,6 +435,7 @@ export interface ExcalidrawProps {
|
|||||||
objectsSnapModeEnabled?: boolean;
|
objectsSnapModeEnabled?: boolean;
|
||||||
libraryReturnUrl?: string;
|
libraryReturnUrl?: string;
|
||||||
theme?: Theme;
|
theme?: Theme;
|
||||||
|
// @TODO come with better API before v0.18.0
|
||||||
name?: string;
|
name?: string;
|
||||||
renderCustomStats?: (
|
renderCustomStats?: (
|
||||||
elements: readonly NonDeletedExcalidrawElement[],
|
elements: readonly NonDeletedExcalidrawElement[],
|
||||||
@@ -577,6 +578,7 @@ export type AppClassProperties = {
|
|||||||
setOpenDialog: App["setOpenDialog"];
|
setOpenDialog: App["setOpenDialog"];
|
||||||
insertEmbeddableElement: App["insertEmbeddableElement"];
|
insertEmbeddableElement: App["insertEmbeddableElement"];
|
||||||
onMagicframeToolSelect: App["onMagicframeToolSelect"];
|
onMagicframeToolSelect: App["onMagicframeToolSelect"];
|
||||||
|
getName: App["getName"];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PointerDownState = Readonly<{
|
export type PointerDownState = Readonly<{
|
||||||
@@ -651,10 +653,11 @@ export type ExcalidrawImperativeAPI = {
|
|||||||
history: {
|
history: {
|
||||||
clear: InstanceType<typeof App>["resetHistory"];
|
clear: InstanceType<typeof App>["resetHistory"];
|
||||||
};
|
};
|
||||||
scrollToContent: InstanceType<typeof App>["scrollToContent"];
|
|
||||||
getSceneElements: InstanceType<typeof App>["getSceneElements"];
|
getSceneElements: InstanceType<typeof App>["getSceneElements"];
|
||||||
getAppState: () => InstanceType<typeof App>["state"];
|
getAppState: () => InstanceType<typeof App>["state"];
|
||||||
getFiles: () => InstanceType<typeof App>["files"];
|
getFiles: () => InstanceType<typeof App>["files"];
|
||||||
|
getName: InstanceType<typeof App>["getName"];
|
||||||
|
scrollToContent: InstanceType<typeof App>["scrollToContent"];
|
||||||
registerAction: (action: Action) => void;
|
registerAction: (action: Action) => void;
|
||||||
refresh: InstanceType<typeof App>["refresh"];
|
refresh: InstanceType<typeof App>["refresh"];
|
||||||
setToast: InstanceType<typeof App>["setToast"];
|
setToast: InstanceType<typeof App>["setToast"];
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
import { isValueInRange, rotatePoint } from "../excalidraw/math";
|
import { isValueInRange, rotatePoint } from "../excalidraw/math";
|
||||||
import type { Point } from "../excalidraw/types";
|
import type { Point } from "../excalidraw/types";
|
||||||
import { Bounds, getElementBounds } from "../excalidraw/element/bounds";
|
import { Bounds, getElementBounds } from "../excalidraw/element/bounds";
|
||||||
|
import { arrayToMap } from "../excalidraw/utils";
|
||||||
|
|
||||||
type Element = NonDeletedExcalidrawElement;
|
type Element = NonDeletedExcalidrawElement;
|
||||||
type Elements = readonly NonDeletedExcalidrawElement[];
|
type Elements = readonly NonDeletedExcalidrawElement[];
|
||||||
@@ -158,7 +159,7 @@ export const elementsOverlappingBBox = ({
|
|||||||
type: "overlap" | "contain" | "inside";
|
type: "overlap" | "contain" | "inside";
|
||||||
}) => {
|
}) => {
|
||||||
if (isExcalidrawElement(bounds)) {
|
if (isExcalidrawElement(bounds)) {
|
||||||
bounds = getElementBounds(bounds);
|
bounds = getElementBounds(bounds, arrayToMap(elements));
|
||||||
}
|
}
|
||||||
const adjustedBBox: Bounds = [
|
const adjustedBBox: Bounds = [
|
||||||
bounds[0] - errorMargin,
|
bounds[0] - errorMargin,
|
||||||
|
|||||||
Reference in New Issue
Block a user