Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0458834681 |
+1
-13
@@ -126,8 +126,6 @@ import DebugCanvas, {
|
|||||||
loadSavedDebugState,
|
loadSavedDebugState,
|
||||||
} from "./components/DebugCanvas";
|
} from "./components/DebugCanvas";
|
||||||
import { AIComponents } from "./components/AI";
|
import { AIComponents } from "./components/AI";
|
||||||
import type { SaveWarningRef } from "./components/SaveWarning";
|
|
||||||
import { SaveWarning } from "./components/SaveWarning";
|
|
||||||
|
|
||||||
polyfill();
|
polyfill();
|
||||||
|
|
||||||
@@ -333,8 +331,6 @@ const ExcalidrawWrapper = () => {
|
|||||||
|
|
||||||
const [langCode, setLangCode] = useAppLangCode();
|
const [langCode, setLangCode] = useAppLangCode();
|
||||||
|
|
||||||
const activityRef = useRef<SaveWarningRef | null>(null);
|
|
||||||
|
|
||||||
// initial state
|
// initial state
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
@@ -619,8 +615,6 @@ const ExcalidrawWrapper = () => {
|
|||||||
collabAPI.syncElements(elements);
|
collabAPI.syncElements(elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
activityRef.current?.activity();
|
|
||||||
|
|
||||||
// this check is redundant, but since this is a hot path, it's best
|
// this check is redundant, but since this is a hot path, it's best
|
||||||
// not to evaludate the nested expression every time
|
// not to evaludate the nested expression every time
|
||||||
if (!LocalData.isSavePaused()) {
|
if (!LocalData.isSavePaused()) {
|
||||||
@@ -655,12 +649,7 @@ const ExcalidrawWrapper = () => {
|
|||||||
|
|
||||||
// Render the debug scene if the debug canvas is available
|
// Render the debug scene if the debug canvas is available
|
||||||
if (debugCanvasRef.current && excalidrawAPI) {
|
if (debugCanvasRef.current && excalidrawAPI) {
|
||||||
debugRenderer(
|
debugRenderer(debugCanvasRef.current, appState, window.devicePixelRatio);
|
||||||
debugCanvasRef.current,
|
|
||||||
appState,
|
|
||||||
window.devicePixelRatio,
|
|
||||||
() => forceRefresh((prev) => !prev),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -862,7 +851,6 @@ const ExcalidrawWrapper = () => {
|
|||||||
setTheme={(theme) => setAppTheme(theme)}
|
setTheme={(theme) => setAppTheme(theme)}
|
||||||
refresh={() => forceRefresh((prev) => !prev)}
|
refresh={() => forceRefresh((prev) => !prev)}
|
||||||
/>
|
/>
|
||||||
<SaveWarning ref={activityRef} />
|
|
||||||
<AppWelcomeScreen
|
<AppWelcomeScreen
|
||||||
onCollabDialogOpen={onCollabDialogOpen}
|
onCollabDialogOpen={onCollabDialogOpen}
|
||||||
isCollabEnabled={!isCollabDisabled}
|
isCollabEnabled={!isCollabDisabled}
|
||||||
|
|||||||
@@ -68,17 +68,12 @@ const _debugRenderer = (
|
|||||||
canvas: HTMLCanvasElement,
|
canvas: HTMLCanvasElement,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
scale: number,
|
scale: number,
|
||||||
refresh: () => void,
|
|
||||||
) => {
|
) => {
|
||||||
const [normalizedWidth, normalizedHeight] = getNormalizedCanvasDimensions(
|
const [normalizedWidth, normalizedHeight] = getNormalizedCanvasDimensions(
|
||||||
canvas,
|
canvas,
|
||||||
scale,
|
scale,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (appState.height !== canvas.height || appState.width !== canvas.width) {
|
|
||||||
refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
const context = bootstrapCanvas({
|
const context = bootstrapCanvas({
|
||||||
canvas,
|
canvas,
|
||||||
scale,
|
scale,
|
||||||
@@ -143,13 +138,8 @@ export const saveDebugState = (debug: { enabled: boolean }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const debugRenderer = throttleRAF(
|
export const debugRenderer = throttleRAF(
|
||||||
(
|
(canvas: HTMLCanvasElement, appState: AppState, scale: number) => {
|
||||||
canvas: HTMLCanvasElement,
|
_debugRenderer(canvas, appState, scale);
|
||||||
appState: AppState,
|
|
||||||
scale: number,
|
|
||||||
refresh: () => void,
|
|
||||||
) => {
|
|
||||||
_debugRenderer(canvas, appState, scale, refresh);
|
|
||||||
},
|
},
|
||||||
{ trailing: true },
|
{ trailing: true },
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
import { forwardRef, useImperativeHandle, useRef } from "react";
|
|
||||||
import { t } from "../../packages/excalidraw/i18n";
|
|
||||||
import { getShortcutKey } from "../../packages/excalidraw/utils";
|
|
||||||
|
|
||||||
export type SaveWarningRef = {
|
|
||||||
activity: () => Promise<void>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SaveWarning = forwardRef<SaveWarningRef, {}>((props, ref) => {
|
|
||||||
const dialogRef = useRef<HTMLDivElement | null>(null);
|
|
||||||
const timerRef = useRef<NodeJS.Timeout | null>(null);
|
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
|
||||||
/**
|
|
||||||
* Call this API method via the ref to hide warning message
|
|
||||||
* and start an idle timer again.
|
|
||||||
*/
|
|
||||||
activity: async () => {
|
|
||||||
if (timerRef.current != null) {
|
|
||||||
clearTimeout(timerRef.current);
|
|
||||||
dialogRef.current?.classList.remove("animate");
|
|
||||||
}
|
|
||||||
|
|
||||||
timerRef.current = setTimeout(() => {
|
|
||||||
timerRef.current = null;
|
|
||||||
dialogRef.current?.classList.add("animate");
|
|
||||||
}, 5000);
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div ref={dialogRef} className="alert-save">
|
|
||||||
<div className="dialog">
|
|
||||||
{t("alerts.saveYourContent", {
|
|
||||||
shortcut: getShortcutKey("CtrlOrCmd + S"),
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
@@ -18,43 +18,6 @@
|
|||||||
margin-inline-start: auto;
|
margin-inline-start: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert-save {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 10;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 10vh;
|
|
||||||
margin-inline: auto;
|
|
||||||
width: fit-content;
|
|
||||||
|
|
||||||
opacity: 0;
|
|
||||||
transition: all 0s;
|
|
||||||
|
|
||||||
&.animate {
|
|
||||||
opacity: 1;
|
|
||||||
transition: all 0.2s ease-in;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog {
|
|
||||||
margin-inline: 10px;
|
|
||||||
padding: 1rem;
|
|
||||||
padding-inline: 1.25rem;
|
|
||||||
|
|
||||||
resize: none;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
background-color: var(--color-warning);
|
|
||||||
border-radius: var(--border-radius-md);
|
|
||||||
border: 1px solid var(--dialog-border-color);
|
|
||||||
|
|
||||||
font-size: 0.875rem;
|
|
||||||
text-align: center;
|
|
||||||
line-height: 1.5;
|
|
||||||
color: var(--color-text-warning);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.encrypted-icon {
|
.encrypted-icon {
|
||||||
border-radius: var(--space-factor);
|
border-radius: var(--space-factor);
|
||||||
color: var(--color-primary);
|
color: var(--color-primary);
|
||||||
|
|||||||
@@ -1,211 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { Excalidraw } from "../index";
|
|
||||||
import { render } from "../tests/test-utils";
|
|
||||||
import { API } from "../tests/helpers/api";
|
|
||||||
import { point } from "../../math";
|
|
||||||
import { actionFlipHorizontal, actionFlipVertical } from "./actionFlip";
|
|
||||||
|
|
||||||
const { h } = window;
|
|
||||||
|
|
||||||
describe("flipping re-centers selection", () => {
|
|
||||||
it("elbow arrow touches group selection side yet it remains in place after multiple moves", async () => {
|
|
||||||
const elements = [
|
|
||||||
API.createElement({
|
|
||||||
type: "rectangle",
|
|
||||||
id: "rec1",
|
|
||||||
x: 100,
|
|
||||||
y: 100,
|
|
||||||
width: 100,
|
|
||||||
height: 100,
|
|
||||||
boundElements: [{ id: "arr", type: "arrow" }],
|
|
||||||
}),
|
|
||||||
API.createElement({
|
|
||||||
type: "rectangle",
|
|
||||||
id: "rec2",
|
|
||||||
x: 220,
|
|
||||||
y: 250,
|
|
||||||
width: 100,
|
|
||||||
height: 100,
|
|
||||||
boundElements: [{ id: "arr", type: "arrow" }],
|
|
||||||
}),
|
|
||||||
API.createElement({
|
|
||||||
type: "arrow",
|
|
||||||
id: "arr",
|
|
||||||
x: 149.9,
|
|
||||||
y: 95,
|
|
||||||
width: 156,
|
|
||||||
height: 239.9,
|
|
||||||
startBinding: {
|
|
||||||
elementId: "rec1",
|
|
||||||
focus: 0,
|
|
||||||
gap: 5,
|
|
||||||
fixedPoint: [0.49, -0.05],
|
|
||||||
},
|
|
||||||
endBinding: {
|
|
||||||
elementId: "rec2",
|
|
||||||
focus: 0,
|
|
||||||
gap: 5,
|
|
||||||
fixedPoint: [-0.05, 0.49],
|
|
||||||
},
|
|
||||||
startArrowhead: null,
|
|
||||||
endArrowhead: "arrow",
|
|
||||||
points: [
|
|
||||||
point(0, 0),
|
|
||||||
point(0, -35),
|
|
||||||
point(-90.9, -35),
|
|
||||||
point(-90.9, 204.9),
|
|
||||||
point(65.1, 204.9),
|
|
||||||
],
|
|
||||||
elbowed: true,
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
await render(<Excalidraw initialData={{ elements }} />);
|
|
||||||
|
|
||||||
API.setSelectedElements(elements);
|
|
||||||
|
|
||||||
expect(Object.keys(h.state.selectedElementIds).length).toBe(3);
|
|
||||||
|
|
||||||
API.executeAction(actionFlipHorizontal);
|
|
||||||
API.executeAction(actionFlipHorizontal);
|
|
||||||
API.executeAction(actionFlipHorizontal);
|
|
||||||
API.executeAction(actionFlipHorizontal);
|
|
||||||
|
|
||||||
const rec1 = h.elements.find((el) => el.id === "rec1");
|
|
||||||
expect(rec1?.x).toBeCloseTo(100);
|
|
||||||
expect(rec1?.y).toBeCloseTo(100);
|
|
||||||
|
|
||||||
const rec2 = h.elements.find((el) => el.id === "rec2");
|
|
||||||
expect(rec2?.x).toBeCloseTo(220);
|
|
||||||
expect(rec2?.y).toBeCloseTo(250);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("flipping arrowheads", () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
await render(<Excalidraw />);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("flipping bound arrow should flip arrowheads only", () => {
|
|
||||||
const rect = API.createElement({
|
|
||||||
type: "rectangle",
|
|
||||||
boundElements: [{ type: "arrow", id: "arrow1" }],
|
|
||||||
});
|
|
||||||
const arrow = API.createElement({
|
|
||||||
type: "arrow",
|
|
||||||
id: "arrow1",
|
|
||||||
startArrowhead: "arrow",
|
|
||||||
endArrowhead: null,
|
|
||||||
endBinding: {
|
|
||||||
elementId: rect.id,
|
|
||||||
focus: 0.5,
|
|
||||||
gap: 5,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
API.setElements([rect, arrow]);
|
|
||||||
API.setSelectedElements([arrow]);
|
|
||||||
|
|
||||||
expect(API.getElement(arrow).startArrowhead).toBe("arrow");
|
|
||||||
expect(API.getElement(arrow).endArrowhead).toBe(null);
|
|
||||||
|
|
||||||
API.executeAction(actionFlipHorizontal);
|
|
||||||
expect(API.getElement(arrow).startArrowhead).toBe(null);
|
|
||||||
expect(API.getElement(arrow).endArrowhead).toBe("arrow");
|
|
||||||
|
|
||||||
API.executeAction(actionFlipHorizontal);
|
|
||||||
expect(API.getElement(arrow).startArrowhead).toBe("arrow");
|
|
||||||
expect(API.getElement(arrow).endArrowhead).toBe(null);
|
|
||||||
|
|
||||||
API.executeAction(actionFlipVertical);
|
|
||||||
expect(API.getElement(arrow).startArrowhead).toBe(null);
|
|
||||||
expect(API.getElement(arrow).endArrowhead).toBe("arrow");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("flipping bound arrow should flip arrowheads only 2", () => {
|
|
||||||
const rect = API.createElement({
|
|
||||||
type: "rectangle",
|
|
||||||
boundElements: [{ type: "arrow", id: "arrow1" }],
|
|
||||||
});
|
|
||||||
const rect2 = API.createElement({
|
|
||||||
type: "rectangle",
|
|
||||||
boundElements: [{ type: "arrow", id: "arrow1" }],
|
|
||||||
});
|
|
||||||
const arrow = API.createElement({
|
|
||||||
type: "arrow",
|
|
||||||
id: "arrow1",
|
|
||||||
startArrowhead: "arrow",
|
|
||||||
endArrowhead: "circle",
|
|
||||||
startBinding: {
|
|
||||||
elementId: rect.id,
|
|
||||||
focus: 0.5,
|
|
||||||
gap: 5,
|
|
||||||
},
|
|
||||||
endBinding: {
|
|
||||||
elementId: rect2.id,
|
|
||||||
focus: 0.5,
|
|
||||||
gap: 5,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
API.setElements([rect, rect2, arrow]);
|
|
||||||
API.setSelectedElements([arrow]);
|
|
||||||
|
|
||||||
expect(API.getElement(arrow).startArrowhead).toBe("arrow");
|
|
||||||
expect(API.getElement(arrow).endArrowhead).toBe("circle");
|
|
||||||
|
|
||||||
API.executeAction(actionFlipHorizontal);
|
|
||||||
expect(API.getElement(arrow).startArrowhead).toBe("circle");
|
|
||||||
expect(API.getElement(arrow).endArrowhead).toBe("arrow");
|
|
||||||
|
|
||||||
API.executeAction(actionFlipVertical);
|
|
||||||
expect(API.getElement(arrow).startArrowhead).toBe("arrow");
|
|
||||||
expect(API.getElement(arrow).endArrowhead).toBe("circle");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("flipping unbound arrow shouldn't flip arrowheads", () => {
|
|
||||||
const arrow = API.createElement({
|
|
||||||
type: "arrow",
|
|
||||||
id: "arrow1",
|
|
||||||
startArrowhead: "arrow",
|
|
||||||
endArrowhead: "circle",
|
|
||||||
});
|
|
||||||
|
|
||||||
API.setElements([arrow]);
|
|
||||||
API.setSelectedElements([arrow]);
|
|
||||||
|
|
||||||
expect(API.getElement(arrow).startArrowhead).toBe("arrow");
|
|
||||||
expect(API.getElement(arrow).endArrowhead).toBe("circle");
|
|
||||||
|
|
||||||
API.executeAction(actionFlipHorizontal);
|
|
||||||
expect(API.getElement(arrow).startArrowhead).toBe("arrow");
|
|
||||||
expect(API.getElement(arrow).endArrowhead).toBe("circle");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("flipping bound arrow shouldn't flip arrowheads if selected alongside non-arrow eleemnt", () => {
|
|
||||||
const rect = API.createElement({
|
|
||||||
type: "rectangle",
|
|
||||||
boundElements: [{ type: "arrow", id: "arrow1" }],
|
|
||||||
});
|
|
||||||
const arrow = API.createElement({
|
|
||||||
type: "arrow",
|
|
||||||
id: "arrow1",
|
|
||||||
startArrowhead: "arrow",
|
|
||||||
endArrowhead: null,
|
|
||||||
endBinding: {
|
|
||||||
elementId: rect.id,
|
|
||||||
focus: 0.5,
|
|
||||||
gap: 5,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
API.setElements([rect, arrow]);
|
|
||||||
API.setSelectedElements([rect, arrow]);
|
|
||||||
|
|
||||||
expect(API.getElement(arrow).startArrowhead).toBe("arrow");
|
|
||||||
expect(API.getElement(arrow).endArrowhead).toBe(null);
|
|
||||||
|
|
||||||
API.executeAction(actionFlipHorizontal);
|
|
||||||
expect(API.getElement(arrow).startArrowhead).toBe("arrow");
|
|
||||||
expect(API.getElement(arrow).endArrowhead).toBe(null);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -2,8 +2,6 @@ import { register } from "./register";
|
|||||||
import { getSelectedElements } from "../scene";
|
import { getSelectedElements } from "../scene";
|
||||||
import { getNonDeletedElements } from "../element";
|
import { getNonDeletedElements } from "../element";
|
||||||
import type {
|
import type {
|
||||||
ExcalidrawArrowElement,
|
|
||||||
ExcalidrawElbowArrowElement,
|
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
NonDeleted,
|
NonDeleted,
|
||||||
NonDeletedSceneElementsMap,
|
NonDeletedSceneElementsMap,
|
||||||
@@ -20,13 +18,7 @@ import {
|
|||||||
import { updateFrameMembershipOfSelectedElements } from "../frame";
|
import { updateFrameMembershipOfSelectedElements } from "../frame";
|
||||||
import { flipHorizontal, flipVertical } from "../components/icons";
|
import { flipHorizontal, flipVertical } from "../components/icons";
|
||||||
import { StoreAction } from "../store";
|
import { StoreAction } from "../store";
|
||||||
import {
|
import { isLinearElement } from "../element/typeChecks";
|
||||||
isArrowElement,
|
|
||||||
isElbowArrow,
|
|
||||||
isLinearElement,
|
|
||||||
} from "../element/typeChecks";
|
|
||||||
import { mutateElbowArrow } from "../element/routing";
|
|
||||||
import { mutateElement, newElementWith } from "../element/mutateElement";
|
|
||||||
|
|
||||||
export const actionFlipHorizontal = register({
|
export const actionFlipHorizontal = register({
|
||||||
name: "flipHorizontal",
|
name: "flipHorizontal",
|
||||||
@@ -117,23 +109,7 @@ const flipElements = (
|
|||||||
flipDirection: "horizontal" | "vertical",
|
flipDirection: "horizontal" | "vertical",
|
||||||
app: AppClassProperties,
|
app: AppClassProperties,
|
||||||
): ExcalidrawElement[] => {
|
): ExcalidrawElement[] => {
|
||||||
if (
|
const { minX, minY, maxX, maxY } = getCommonBoundingBox(selectedElements);
|
||||||
selectedElements.every(
|
|
||||||
(element) =>
|
|
||||||
isArrowElement(element) && (element.startBinding || element.endBinding),
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return selectedElements.map((element) => {
|
|
||||||
const _element = element as ExcalidrawArrowElement;
|
|
||||||
return newElementWith(_element, {
|
|
||||||
startArrowhead: _element.endArrowhead,
|
|
||||||
endArrowhead: _element.startArrowhead,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const { minX, minY, maxX, maxY, midX, midY } =
|
|
||||||
getCommonBoundingBox(selectedElements);
|
|
||||||
|
|
||||||
resizeMultipleElements(
|
resizeMultipleElements(
|
||||||
elementsMap,
|
elementsMap,
|
||||||
@@ -155,48 +131,5 @@ const flipElements = (
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// flipping arrow elements (and potentially other) makes the selection group
|
|
||||||
// "move" across the canvas because of how arrows can bump against the "wall"
|
|
||||||
// of the selection, so we need to center the group back to the original
|
|
||||||
// position so that repeated flips don't accumulate the offset
|
|
||||||
|
|
||||||
const { elbowArrows, otherElements } = selectedElements.reduce(
|
|
||||||
(
|
|
||||||
acc: {
|
|
||||||
elbowArrows: ExcalidrawElbowArrowElement[];
|
|
||||||
otherElements: ExcalidrawElement[];
|
|
||||||
},
|
|
||||||
element,
|
|
||||||
) =>
|
|
||||||
isElbowArrow(element)
|
|
||||||
? { ...acc, elbowArrows: acc.elbowArrows.concat(element) }
|
|
||||||
: { ...acc, otherElements: acc.otherElements.concat(element) },
|
|
||||||
{ elbowArrows: [], otherElements: [] },
|
|
||||||
);
|
|
||||||
|
|
||||||
const { midX: newMidX, midY: newMidY } =
|
|
||||||
getCommonBoundingBox(selectedElements);
|
|
||||||
const [diffX, diffY] = [midX - newMidX, midY - newMidY];
|
|
||||||
otherElements.forEach((element) =>
|
|
||||||
mutateElement(element, {
|
|
||||||
x: element.x + diffX,
|
|
||||||
y: element.y + diffY,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
elbowArrows.forEach((element) =>
|
|
||||||
mutateElbowArrow(
|
|
||||||
element,
|
|
||||||
elementsMap,
|
|
||||||
element.points,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
{
|
|
||||||
informMutation: false,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
return selectedElements;
|
return selectedElements;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1685,6 +1685,19 @@ export const actionChangeArrowType = register({
|
|||||||
: {}),
|
: {}),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
mutateElement(
|
||||||
|
newElement,
|
||||||
|
{
|
||||||
|
startBinding: newElement.startBinding
|
||||||
|
? { ...newElement.startBinding, fixedPoint: null }
|
||||||
|
: null,
|
||||||
|
endBinding: newElement.endBinding
|
||||||
|
? { ...newElement.endBinding, fixedPoint: null }
|
||||||
|
: null,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return newElement;
|
return newElement;
|
||||||
|
|||||||
@@ -1,5 +1,90 @@
|
|||||||
import oc from "open-color";
|
import oc from "open-color";
|
||||||
import type { Merge } from "./utility-types";
|
import type { Merge } from "./utility-types";
|
||||||
|
import { clamp } from "../math/utils";
|
||||||
|
import tinycolor from "tinycolor2";
|
||||||
|
import { degreesToRadians } from "../math/angle";
|
||||||
|
import type { Degrees } from "../math/types";
|
||||||
|
|
||||||
|
function cssHueRotate(
|
||||||
|
red: number,
|
||||||
|
green: number,
|
||||||
|
blue: number,
|
||||||
|
degrees: Degrees,
|
||||||
|
): { r: number; g: number; b: number } {
|
||||||
|
// normalize
|
||||||
|
const r = red / 255;
|
||||||
|
const g = green / 255;
|
||||||
|
const b = blue / 255;
|
||||||
|
|
||||||
|
// Convert degrees to radians
|
||||||
|
const a = degreesToRadians(degrees);
|
||||||
|
|
||||||
|
const c = Math.cos(a);
|
||||||
|
const s = Math.sin(a);
|
||||||
|
|
||||||
|
// rotation matrix
|
||||||
|
const matrix = [
|
||||||
|
0.213 + c * 0.787 - s * 0.213,
|
||||||
|
0.715 - c * 0.715 - s * 0.715,
|
||||||
|
0.072 - c * 0.072 + s * 0.928,
|
||||||
|
0.213 - c * 0.213 + s * 0.143,
|
||||||
|
0.715 + c * 0.285 + s * 0.14,
|
||||||
|
0.072 - c * 0.072 - s * 0.283,
|
||||||
|
0.213 - c * 0.213 - s * 0.787,
|
||||||
|
0.715 - c * 0.715 + s * 0.715,
|
||||||
|
0.072 + c * 0.928 + s * 0.072,
|
||||||
|
];
|
||||||
|
|
||||||
|
// transform
|
||||||
|
const newR = r * matrix[0] + g * matrix[1] + b * matrix[2];
|
||||||
|
const newG = r * matrix[3] + g * matrix[4] + b * matrix[5];
|
||||||
|
const newB = r * matrix[6] + g * matrix[7] + b * matrix[8];
|
||||||
|
|
||||||
|
// clamp the values to [0, 1] range and convert back to [0, 255]
|
||||||
|
return {
|
||||||
|
r: Math.round(Math.max(0, Math.min(1, newR)) * 255),
|
||||||
|
g: Math.round(Math.max(0, Math.min(1, newG)) * 255),
|
||||||
|
b: Math.round(Math.max(0, Math.min(1, newB)) * 255),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const cssInvert = (
|
||||||
|
r: number,
|
||||||
|
g: number,
|
||||||
|
b: number,
|
||||||
|
percent: number,
|
||||||
|
): { r: number; g: number; b: number } => {
|
||||||
|
const p = clamp(percent, 0, 100) / 100;
|
||||||
|
|
||||||
|
// Function to invert a single color component
|
||||||
|
const invertComponent = (color: number): number => {
|
||||||
|
// Apply the invert formula
|
||||||
|
const inverted = color * (1 - p) + (255 - color) * p;
|
||||||
|
// Round to the nearest integer and clamp to [0, 255]
|
||||||
|
return Math.round(clamp(inverted, 0, 255));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Calculate the inverted RGB components
|
||||||
|
const invertedR = invertComponent(r);
|
||||||
|
const invertedG = invertComponent(g);
|
||||||
|
const invertedB = invertComponent(b);
|
||||||
|
|
||||||
|
return { r: invertedR, g: invertedG, b: invertedB };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const applyDarkModeFilter = (color: string) => {
|
||||||
|
let tc = tinycolor(color);
|
||||||
|
|
||||||
|
const _alpha = tc._a;
|
||||||
|
|
||||||
|
// order of operations matters
|
||||||
|
// (corresponds to "filter: invert(invertPercent) hue-rotate(hueDegrees)" in css)
|
||||||
|
tc = tinycolor(cssInvert(tc._r, tc._g, tc._b, 93));
|
||||||
|
tc = tinycolor(cssHueRotate(tc._r, tc._g, tc._b, 180 as Degrees));
|
||||||
|
tc.setAlpha(_alpha);
|
||||||
|
|
||||||
|
return tc.toHex8String();
|
||||||
|
};
|
||||||
|
|
||||||
// FIXME can't put to utils.ts rn because of circular dependency
|
// FIXME can't put to utils.ts rn because of circular dependency
|
||||||
const pick = <R extends Record<string, any>, K extends readonly (keyof R)[]>(
|
const pick = <R extends Record<string, any>, K extends readonly (keyof R)[]>(
|
||||||
|
|||||||
@@ -185,7 +185,6 @@ import type {
|
|||||||
MagicGenerationData,
|
MagicGenerationData,
|
||||||
ExcalidrawNonSelectionElement,
|
ExcalidrawNonSelectionElement,
|
||||||
ExcalidrawArrowElement,
|
ExcalidrawArrowElement,
|
||||||
NonDeletedSceneElementsMap,
|
|
||||||
} from "../element/types";
|
} from "../element/types";
|
||||||
import { getCenter, getDistance } from "../gesture";
|
import { getCenter, getDistance } from "../gesture";
|
||||||
import {
|
import {
|
||||||
@@ -288,7 +287,6 @@ import {
|
|||||||
getDateTime,
|
getDateTime,
|
||||||
isShallowEqual,
|
isShallowEqual,
|
||||||
arrayToMap,
|
arrayToMap,
|
||||||
toBrandedType,
|
|
||||||
} from "../utils";
|
} from "../utils";
|
||||||
import {
|
import {
|
||||||
createSrcDoc,
|
createSrcDoc,
|
||||||
@@ -437,7 +435,7 @@ import { actionTextAutoResize } from "../actions/actionTextAutoResize";
|
|||||||
import { getVisibleSceneBounds } from "../element/bounds";
|
import { getVisibleSceneBounds } from "../element/bounds";
|
||||||
import { isMaybeMermaidDefinition } from "../mermaid";
|
import { isMaybeMermaidDefinition } from "../mermaid";
|
||||||
import NewElementCanvas from "./canvases/NewElementCanvas";
|
import NewElementCanvas from "./canvases/NewElementCanvas";
|
||||||
import { mutateElbowArrow, updateElbowArrow } from "../element/routing";
|
import { mutateElbowArrow } from "../element/routing";
|
||||||
import {
|
import {
|
||||||
FlowChartCreator,
|
FlowChartCreator,
|
||||||
FlowChartNavigator,
|
FlowChartNavigator,
|
||||||
@@ -1702,6 +1700,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
elementsPendingErasure: this.elementsPendingErasure,
|
elementsPendingErasure: this.elementsPendingErasure,
|
||||||
pendingFlowchartNodes:
|
pendingFlowchartNodes:
|
||||||
this.flowChartCreator.pendingNodes,
|
this.flowChartCreator.pendingNodes,
|
||||||
|
theme: this.state.theme,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{this.state.newElement && (
|
{this.state.newElement && (
|
||||||
@@ -1722,6 +1721,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
elementsPendingErasure:
|
elementsPendingErasure:
|
||||||
this.elementsPendingErasure,
|
this.elementsPendingErasure,
|
||||||
pendingFlowchartNodes: null,
|
pendingFlowchartNodes: null,
|
||||||
|
theme: this.state.theme,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -2697,6 +2697,11 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
activeTool: updateActiveTool(this.state, { type: "selection" }),
|
activeTool: updateActiveTool(this.state, { type: "selection" }),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (prevState.theme !== this.state.theme) {
|
||||||
|
this.scene
|
||||||
|
.getElementsIncludingDeleted()
|
||||||
|
.forEach((element) => ShapeCache.delete(element));
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
this.state.activeTool.type === "eraser" &&
|
this.state.activeTool.type === "eraser" &&
|
||||||
prevState.theme !== this.state.theme
|
prevState.theme !== this.state.theme
|
||||||
@@ -3111,45 +3116,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
retainSeed?: boolean;
|
retainSeed?: boolean;
|
||||||
fitToContent?: boolean;
|
fitToContent?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
let elements = opts.elements.map((el, _, elements) => {
|
const elements = restoreElements(opts.elements, null, undefined);
|
||||||
if (isElbowArrow(el)) {
|
|
||||||
const startEndElements = [
|
|
||||||
el.startBinding &&
|
|
||||||
elements.find((l) => l.id === el.startBinding?.elementId),
|
|
||||||
el.endBinding &&
|
|
||||||
elements.find((l) => l.id === el.endBinding?.elementId),
|
|
||||||
];
|
|
||||||
const startBinding = startEndElements[0] ? el.startBinding : null;
|
|
||||||
const endBinding = startEndElements[1] ? el.endBinding : null;
|
|
||||||
return {
|
|
||||||
...el,
|
|
||||||
...updateElbowArrow(
|
|
||||||
{
|
|
||||||
...el,
|
|
||||||
startBinding,
|
|
||||||
endBinding,
|
|
||||||
},
|
|
||||||
toBrandedType<NonDeletedSceneElementsMap>(
|
|
||||||
new Map(
|
|
||||||
startEndElements
|
|
||||||
.filter((x) => x != null)
|
|
||||||
.map(
|
|
||||||
(el) =>
|
|
||||||
[el!.id, el] as [
|
|
||||||
string,
|
|
||||||
Ordered<NonDeletedExcalidrawElement>,
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
[el.points[0], el.points[el.points.length - 1]],
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return el;
|
|
||||||
});
|
|
||||||
elements = restoreElements(elements, null, undefined);
|
|
||||||
const [minX, minY, maxX, maxY] = getCommonBounds(elements);
|
const [minX, minY, maxX, maxY] = getCommonBounds(elements);
|
||||||
|
|
||||||
const elementsCenterX = distance(minX, maxX) / 2;
|
const elementsCenterX = distance(minX, maxX) / 2;
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ body.excalidraw-cursor-resize * {
|
|||||||
// recommends surface color of #121212, 93% yields #111111 for #FFF
|
// recommends surface color of #121212, 93% yields #111111 for #FFF
|
||||||
|
|
||||||
canvas {
|
canvas {
|
||||||
filter: var(--theme-filter);
|
// filter: var(--theme-filter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -189,7 +189,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$theme-filter: "invert(93%) hue-rotate(180deg)";
|
$theme-filter: "invert(93%) hue-rotate(180deg)"; // prod
|
||||||
|
// $theme-filter: "invert(93%)"; // prod
|
||||||
|
// $theme-filter: "hue-rotate(180deg)"; // prod
|
||||||
|
// $theme-filter: "hue-rotate(180deg) invert(93%)";
|
||||||
|
|
||||||
$right-sidebar-width: "302px";
|
$right-sidebar-width: "302px";
|
||||||
|
|
||||||
:export {
|
:export {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import type {
|
|||||||
ExcalidrawLinearElement,
|
ExcalidrawLinearElement,
|
||||||
ExcalidrawSelectionElement,
|
ExcalidrawSelectionElement,
|
||||||
ExcalidrawTextElement,
|
ExcalidrawTextElement,
|
||||||
FixedPointBinding,
|
|
||||||
FontFamilyValues,
|
FontFamilyValues,
|
||||||
OrderedExcalidrawElement,
|
OrderedExcalidrawElement,
|
||||||
PointBinding,
|
PointBinding,
|
||||||
@@ -22,7 +21,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
isArrowElement,
|
isArrowElement,
|
||||||
isElbowArrow,
|
isElbowArrow,
|
||||||
isFixedPointBinding,
|
|
||||||
isLinearElement,
|
isLinearElement,
|
||||||
isTextElement,
|
isTextElement,
|
||||||
isUsingAdaptiveRadius,
|
isUsingAdaptiveRadius,
|
||||||
@@ -103,8 +101,8 @@ const getFontFamilyByName = (fontFamilyName: string): FontFamilyValues => {
|
|||||||
|
|
||||||
const repairBinding = (
|
const repairBinding = (
|
||||||
element: ExcalidrawLinearElement,
|
element: ExcalidrawLinearElement,
|
||||||
binding: PointBinding | FixedPointBinding | null,
|
binding: PointBinding | null,
|
||||||
): PointBinding | FixedPointBinding | null => {
|
): PointBinding | null => {
|
||||||
if (!binding) {
|
if (!binding) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -112,11 +110,9 @@ const repairBinding = (
|
|||||||
return {
|
return {
|
||||||
...binding,
|
...binding,
|
||||||
focus: binding.focus || 0,
|
focus: binding.focus || 0,
|
||||||
...(isElbowArrow(element) && isFixedPointBinding(binding)
|
fixedPoint: isElbowArrow(element)
|
||||||
? {
|
? normalizeFixedPoint(binding.fixedPoint ?? [0, 0])
|
||||||
fixedPoint: normalizeFixedPoint(binding.fixedPoint ?? [0, 0]),
|
: null,
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ import {
|
|||||||
isBindingElement,
|
isBindingElement,
|
||||||
isBoundToContainer,
|
isBoundToContainer,
|
||||||
isElbowArrow,
|
isElbowArrow,
|
||||||
isFixedPointBinding,
|
|
||||||
isFrameLikeElement,
|
isFrameLikeElement,
|
||||||
isLinearElement,
|
isLinearElement,
|
||||||
isRectangularElement,
|
isRectangularElement,
|
||||||
@@ -798,7 +797,7 @@ export const bindPointToSnapToElementOutline = (
|
|||||||
isVertical
|
isVertical
|
||||||
? Math.abs(p[1] - i[1]) < 0.1
|
? Math.abs(p[1] - i[1]) < 0.1
|
||||||
: Math.abs(p[0] - i[0]) < 0.1,
|
: Math.abs(p[0] - i[0]) < 0.1,
|
||||||
)[0] ?? p;
|
)[0] ?? point;
|
||||||
}
|
}
|
||||||
|
|
||||||
return p;
|
return p;
|
||||||
@@ -1014,7 +1013,7 @@ const updateBoundPoint = (
|
|||||||
const direction = startOrEnd === "startBinding" ? -1 : 1;
|
const direction = startOrEnd === "startBinding" ? -1 : 1;
|
||||||
const edgePointIndex = direction === -1 ? 0 : linearElement.points.length - 1;
|
const edgePointIndex = direction === -1 ? 0 : linearElement.points.length - 1;
|
||||||
|
|
||||||
if (isElbowArrow(linearElement) && isFixedPointBinding(binding)) {
|
if (isElbowArrow(linearElement)) {
|
||||||
const fixedPoint =
|
const fixedPoint =
|
||||||
normalizeFixedPoint(binding.fixedPoint) ??
|
normalizeFixedPoint(binding.fixedPoint) ??
|
||||||
calculateFixedPointForElbowArrowBinding(
|
calculateFixedPointForElbowArrowBinding(
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export const dragSelectedElements = (
|
|||||||
) => {
|
) => {
|
||||||
if (
|
if (
|
||||||
_selectedElements.length === 1 &&
|
_selectedElements.length === 1 &&
|
||||||
|
isArrowElement(_selectedElements[0]) &&
|
||||||
isElbowArrow(_selectedElements[0]) &&
|
isElbowArrow(_selectedElements[0]) &&
|
||||||
(_selectedElements[0].startBinding || _selectedElements[0].endBinding)
|
(_selectedElements[0].startBinding || _selectedElements[0].endBinding)
|
||||||
) {
|
) {
|
||||||
@@ -42,7 +43,13 @@ export const dragSelectedElements = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const selectedElements = _selectedElements.filter(
|
const selectedElements = _selectedElements.filter(
|
||||||
(el) => !(isElbowArrow(el) && el.startBinding && el.endBinding),
|
(el) =>
|
||||||
|
!(
|
||||||
|
isArrowElement(el) &&
|
||||||
|
isElbowArrow(el) &&
|
||||||
|
el.startBinding &&
|
||||||
|
el.endBinding
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// we do not want a frame and its elements to be selected at the same time
|
// we do not want a frame and its elements to be selected at the same time
|
||||||
|
|||||||
@@ -102,7 +102,6 @@ export class LinearElementEditor {
|
|||||||
public readonly endBindingElement: ExcalidrawBindableElement | null | "keep";
|
public readonly endBindingElement: ExcalidrawBindableElement | null | "keep";
|
||||||
public readonly hoverPointIndex: number;
|
public readonly hoverPointIndex: number;
|
||||||
public readonly segmentMidPointHoveredCoords: GlobalPoint | null;
|
public readonly segmentMidPointHoveredCoords: GlobalPoint | null;
|
||||||
public readonly elbowed: boolean;
|
|
||||||
|
|
||||||
constructor(element: NonDeleted<ExcalidrawLinearElement>) {
|
constructor(element: NonDeleted<ExcalidrawLinearElement>) {
|
||||||
this.elementId = element.id as string & {
|
this.elementId = element.id as string & {
|
||||||
@@ -132,7 +131,6 @@ export class LinearElementEditor {
|
|||||||
};
|
};
|
||||||
this.hoverPointIndex = -1;
|
this.hoverPointIndex = -1;
|
||||||
this.segmentMidPointHoveredCoords = null;
|
this.segmentMidPointHoveredCoords = null;
|
||||||
this.elbowed = isElbowArrow(element) && element.elbowed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -1479,9 +1477,7 @@ export class LinearElementEditor {
|
|||||||
nextPoints,
|
nextPoints,
|
||||||
vector(offsetX, offsetY),
|
vector(offsetX, offsetY),
|
||||||
bindings,
|
bindings,
|
||||||
{
|
options,
|
||||||
isDragging: options?.isDragging,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const nextCoords = getElementPointsCoords(element, nextPoints);
|
const nextCoords = getElementPointsCoords(element, nextPoints);
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import type {
|
|||||||
ExcalidrawTextElementWithContainer,
|
ExcalidrawTextElementWithContainer,
|
||||||
ExcalidrawImageElement,
|
ExcalidrawImageElement,
|
||||||
ElementsMap,
|
ElementsMap,
|
||||||
ExcalidrawArrowElement,
|
|
||||||
NonDeletedSceneElementsMap,
|
NonDeletedSceneElementsMap,
|
||||||
SceneElementsMap,
|
SceneElementsMap,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
@@ -910,8 +909,6 @@ export const resizeMultipleElements = (
|
|||||||
fontSize?: ExcalidrawTextElement["fontSize"];
|
fontSize?: ExcalidrawTextElement["fontSize"];
|
||||||
scale?: ExcalidrawImageElement["scale"];
|
scale?: ExcalidrawImageElement["scale"];
|
||||||
boundTextFontSize?: ExcalidrawTextElement["fontSize"];
|
boundTextFontSize?: ExcalidrawTextElement["fontSize"];
|
||||||
startBinding?: ExcalidrawArrowElement["startBinding"];
|
|
||||||
endBinding?: ExcalidrawArrowElement["endBinding"];
|
|
||||||
};
|
};
|
||||||
}[] = [];
|
}[] = [];
|
||||||
|
|
||||||
@@ -996,6 +993,19 @@ export const resizeMultipleElements = (
|
|||||||
|
|
||||||
mutateElement(element, update, false);
|
mutateElement(element, update, false);
|
||||||
|
|
||||||
|
if (isArrowElement(element) && isElbowArrow(element)) {
|
||||||
|
mutateElbowArrow(
|
||||||
|
element,
|
||||||
|
elementsMap,
|
||||||
|
element.points,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
{
|
||||||
|
informMutation: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
updateBoundElements(element, elementsMap, {
|
updateBoundElements(element, elementsMap, {
|
||||||
simultaneouslyUpdated: elementsToUpdate,
|
simultaneouslyUpdated: elementsToUpdate,
|
||||||
oldSize: { width: oldWidth, height: oldHeight },
|
oldSize: { width: oldWidth, height: oldHeight },
|
||||||
@@ -1049,7 +1059,7 @@ const rotateMultipleElements = (
|
|||||||
(centerAngle + origAngle - element.angle) as Radians,
|
(centerAngle + origAngle - element.angle) as Radians,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isElbowArrow(element)) {
|
if (isArrowElement(element) && isElbowArrow(element)) {
|
||||||
const points = getArrowLocalFixedPoints(element, elementsMap);
|
const points = getArrowLocalFixedPoints(element, elementsMap);
|
||||||
mutateElbowArrow(element, elementsMap, points);
|
mutateElbowArrow(element, elementsMap, points);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -94,16 +94,7 @@ describe("elbow arrow routing", () => {
|
|||||||
|
|
||||||
describe("elbow arrow ui", () => {
|
describe("elbow arrow ui", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
localStorage.clear();
|
|
||||||
await render(<Excalidraw handleKeyboardGlobally={true} />);
|
await render(<Excalidraw handleKeyboardGlobally={true} />);
|
||||||
|
|
||||||
fireEvent.contextMenu(GlobalTestState.interactiveCanvas, {
|
|
||||||
button: 2,
|
|
||||||
clientX: 1,
|
|
||||||
clientY: 1,
|
|
||||||
});
|
|
||||||
const contextMenu = UI.queryContextMenu();
|
|
||||||
fireEvent.click(queryByTestId(contextMenu!, "stats")!);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("can follow bound shapes", async () => {
|
it("can follow bound shapes", async () => {
|
||||||
@@ -139,8 +130,8 @@ describe("elbow arrow ui", () => {
|
|||||||
expect(arrow.elbowed).toBe(true);
|
expect(arrow.elbowed).toBe(true);
|
||||||
expect(arrow.points).toEqual([
|
expect(arrow.points).toEqual([
|
||||||
[0, 0],
|
[0, 0],
|
||||||
[45, 0],
|
[35, 0],
|
||||||
[45, 200],
|
[35, 200],
|
||||||
[90, 200],
|
[90, 200],
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
@@ -172,6 +163,14 @@ describe("elbow arrow ui", () => {
|
|||||||
h.state,
|
h.state,
|
||||||
)[0] as ExcalidrawArrowElement;
|
)[0] as ExcalidrawArrowElement;
|
||||||
|
|
||||||
|
fireEvent.contextMenu(GlobalTestState.interactiveCanvas, {
|
||||||
|
button: 2,
|
||||||
|
clientX: 1,
|
||||||
|
clientY: 1,
|
||||||
|
});
|
||||||
|
const contextMenu = UI.queryContextMenu();
|
||||||
|
fireEvent.click(queryByTestId(contextMenu!, "stats")!);
|
||||||
|
|
||||||
mouse.click(51, 51);
|
mouse.click(51, 51);
|
||||||
|
|
||||||
const inputAngle = UI.queryStatsProperty("A")?.querySelector(
|
const inputAngle = UI.queryStatsProperty("A")?.querySelector(
|
||||||
@@ -183,8 +182,8 @@ describe("elbow arrow ui", () => {
|
|||||||
[0, 0],
|
[0, 0],
|
||||||
[35, 0],
|
[35, 0],
|
||||||
[35, 90],
|
[35, 90],
|
||||||
[35, 90], // Note that coordinates are rounded above!
|
[25, 90],
|
||||||
[35, 165],
|
[25, 165],
|
||||||
[103, 165],
|
[103, 165],
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -36,11 +36,11 @@ import {
|
|||||||
HEADING_UP,
|
HEADING_UP,
|
||||||
vectorToHeading,
|
vectorToHeading,
|
||||||
} from "./heading";
|
} from "./heading";
|
||||||
import type { ElementUpdate } from "./mutateElement";
|
|
||||||
import { mutateElement } from "./mutateElement";
|
import { mutateElement } from "./mutateElement";
|
||||||
import { isBindableElement, isRectanguloidElement } from "./typeChecks";
|
import { isBindableElement, isRectanguloidElement } from "./typeChecks";
|
||||||
import type {
|
import type {
|
||||||
ExcalidrawElbowArrowElement,
|
ExcalidrawElbowArrowElement,
|
||||||
|
FixedPointBinding,
|
||||||
NonDeletedSceneElementsMap,
|
NonDeletedSceneElementsMap,
|
||||||
SceneElementsMap,
|
SceneElementsMap,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
@@ -72,48 +72,16 @@ export const mutateElbowArrow = (
|
|||||||
elementsMap: NonDeletedSceneElementsMap | SceneElementsMap,
|
elementsMap: NonDeletedSceneElementsMap | SceneElementsMap,
|
||||||
nextPoints: readonly LocalPoint[],
|
nextPoints: readonly LocalPoint[],
|
||||||
offset?: Vector,
|
offset?: Vector,
|
||||||
otherUpdates?: Omit<
|
otherUpdates?: {
|
||||||
ElementUpdate<ExcalidrawElbowArrowElement>,
|
startBinding?: FixedPointBinding | null;
|
||||||
"angle" | "x" | "y" | "width" | "height" | "elbowed" | "points"
|
endBinding?: FixedPointBinding | null;
|
||||||
>,
|
|
||||||
options?: {
|
|
||||||
isDragging?: boolean;
|
|
||||||
informMutation?: boolean;
|
|
||||||
},
|
},
|
||||||
) => {
|
|
||||||
const update = updateElbowArrow(
|
|
||||||
arrow,
|
|
||||||
elementsMap,
|
|
||||||
nextPoints,
|
|
||||||
offset,
|
|
||||||
options,
|
|
||||||
);
|
|
||||||
if (update) {
|
|
||||||
mutateElement(
|
|
||||||
arrow,
|
|
||||||
{
|
|
||||||
...otherUpdates,
|
|
||||||
...update,
|
|
||||||
angle: 0 as Radians,
|
|
||||||
},
|
|
||||||
options?.informMutation,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
console.error("Elbow arrow cannot find a route");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const updateElbowArrow = (
|
|
||||||
arrow: ExcalidrawElbowArrowElement,
|
|
||||||
elementsMap: NonDeletedSceneElementsMap | SceneElementsMap,
|
|
||||||
nextPoints: readonly LocalPoint[],
|
|
||||||
offset?: Vector,
|
|
||||||
options?: {
|
options?: {
|
||||||
isDragging?: boolean;
|
isDragging?: boolean;
|
||||||
disableBinding?: boolean;
|
disableBinding?: boolean;
|
||||||
informMutation?: boolean;
|
informMutation?: boolean;
|
||||||
},
|
},
|
||||||
): ElementUpdate<ExcalidrawElbowArrowElement> | null => {
|
) => {
|
||||||
const origStartGlobalPoint: GlobalPoint = pointTranslate(
|
const origStartGlobalPoint: GlobalPoint = pointTranslate(
|
||||||
pointTranslate<LocalPoint, GlobalPoint>(
|
pointTranslate<LocalPoint, GlobalPoint>(
|
||||||
nextPoints[0],
|
nextPoints[0],
|
||||||
@@ -267,8 +235,6 @@ export const updateElbowArrow = (
|
|||||||
BASE_PADDING,
|
BASE_PADDING,
|
||||||
),
|
),
|
||||||
boundsOverlap,
|
boundsOverlap,
|
||||||
hoveredStartElement && aabbForElement(hoveredStartElement),
|
|
||||||
hoveredEndElement && aabbForElement(hoveredEndElement),
|
|
||||||
);
|
);
|
||||||
const startDonglePosition = getDonglePosition(
|
const startDonglePosition = getDonglePosition(
|
||||||
dynamicAABBs[0],
|
dynamicAABBs[0],
|
||||||
@@ -329,10 +295,18 @@ export const updateElbowArrow = (
|
|||||||
startDongle && points.unshift(startGlobalPoint);
|
startDongle && points.unshift(startGlobalPoint);
|
||||||
endDongle && points.push(endGlobalPoint);
|
endDongle && points.push(endGlobalPoint);
|
||||||
|
|
||||||
return normalizedArrowElementUpdate(simplifyElbowArrowPoints(points), 0, 0);
|
mutateElement(
|
||||||
|
arrow,
|
||||||
|
{
|
||||||
|
...otherUpdates,
|
||||||
|
...normalizedArrowElementUpdate(simplifyElbowArrowPoints(points), 0, 0),
|
||||||
|
angle: 0 as Radians,
|
||||||
|
},
|
||||||
|
options?.informMutation,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.error("Elbow arrow cannot find a route");
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const offsetFromHeading = (
|
const offsetFromHeading = (
|
||||||
@@ -501,11 +475,7 @@ const generateDynamicAABBs = (
|
|||||||
startDifference?: [number, number, number, number],
|
startDifference?: [number, number, number, number],
|
||||||
endDifference?: [number, number, number, number],
|
endDifference?: [number, number, number, number],
|
||||||
disableSideHack?: boolean,
|
disableSideHack?: boolean,
|
||||||
startElementBounds?: Bounds | null,
|
|
||||||
endElementBounds?: Bounds | null,
|
|
||||||
): Bounds[] => {
|
): Bounds[] => {
|
||||||
const startEl = startElementBounds ?? a;
|
|
||||||
const endEl = endElementBounds ?? b;
|
|
||||||
const [startUp, startRight, startDown, startLeft] = startDifference ?? [
|
const [startUp, startRight, startDown, startLeft] = startDifference ?? [
|
||||||
0, 0, 0, 0,
|
0, 0, 0, 0,
|
||||||
];
|
];
|
||||||
@@ -514,29 +484,29 @@ const generateDynamicAABBs = (
|
|||||||
const first = [
|
const first = [
|
||||||
a[0] > b[2]
|
a[0] > b[2]
|
||||||
? a[1] > b[3] || a[3] < b[1]
|
? a[1] > b[3] || a[3] < b[1]
|
||||||
? Math.min((startEl[0] + endEl[2]) / 2, a[0] - startLeft)
|
? Math.min((a[0] + b[2]) / 2, a[0] - startLeft)
|
||||||
: (startEl[0] + endEl[2]) / 2
|
: (a[0] + b[2]) / 2
|
||||||
: a[0] > b[0]
|
: a[0] > b[0]
|
||||||
? a[0] - startLeft
|
? a[0] - startLeft
|
||||||
: common[0] - startLeft,
|
: common[0] - startLeft,
|
||||||
a[1] > b[3]
|
a[1] > b[3]
|
||||||
? a[0] > b[2] || a[2] < b[0]
|
? a[0] > b[2] || a[2] < b[0]
|
||||||
? Math.min((startEl[1] + endEl[3]) / 2, a[1] - startUp)
|
? Math.min((a[1] + b[3]) / 2, a[1] - startUp)
|
||||||
: (startEl[1] + endEl[3]) / 2
|
: (a[1] + b[3]) / 2
|
||||||
: a[1] > b[1]
|
: a[1] > b[1]
|
||||||
? a[1] - startUp
|
? a[1] - startUp
|
||||||
: common[1] - startUp,
|
: common[1] - startUp,
|
||||||
a[2] < b[0]
|
a[2] < b[0]
|
||||||
? a[1] > b[3] || a[3] < b[1]
|
? a[1] > b[3] || a[3] < b[1]
|
||||||
? Math.max((startEl[2] + endEl[0]) / 2, a[2] + startRight)
|
? Math.max((a[2] + b[0]) / 2, a[2] + startRight)
|
||||||
: (startEl[2] + endEl[0]) / 2
|
: (a[2] + b[0]) / 2
|
||||||
: a[2] < b[2]
|
: a[2] < b[2]
|
||||||
? a[2] + startRight
|
? a[2] + startRight
|
||||||
: common[2] + startRight,
|
: common[2] + startRight,
|
||||||
a[3] < b[1]
|
a[3] < b[1]
|
||||||
? a[0] > b[2] || a[2] < b[0]
|
? a[0] > b[2] || a[2] < b[0]
|
||||||
? Math.max((startEl[3] + endEl[1]) / 2, a[3] + startDown)
|
? Math.max((a[3] + b[1]) / 2, a[3] + startDown)
|
||||||
: (startEl[3] + endEl[1]) / 2
|
: (a[3] + b[1]) / 2
|
||||||
: a[3] < b[3]
|
: a[3] < b[3]
|
||||||
? a[3] + startDown
|
? a[3] + startDown
|
||||||
: common[3] + startDown,
|
: common[3] + startDown,
|
||||||
@@ -544,29 +514,29 @@ const generateDynamicAABBs = (
|
|||||||
const second = [
|
const second = [
|
||||||
b[0] > a[2]
|
b[0] > a[2]
|
||||||
? b[1] > a[3] || b[3] < a[1]
|
? b[1] > a[3] || b[3] < a[1]
|
||||||
? Math.min((endEl[0] + startEl[2]) / 2, b[0] - endLeft)
|
? Math.min((b[0] + a[2]) / 2, b[0] - endLeft)
|
||||||
: (endEl[0] + startEl[2]) / 2
|
: (b[0] + a[2]) / 2
|
||||||
: b[0] > a[0]
|
: b[0] > a[0]
|
||||||
? b[0] - endLeft
|
? b[0] - endLeft
|
||||||
: common[0] - endLeft,
|
: common[0] - endLeft,
|
||||||
b[1] > a[3]
|
b[1] > a[3]
|
||||||
? b[0] > a[2] || b[2] < a[0]
|
? b[0] > a[2] || b[2] < a[0]
|
||||||
? Math.min((endEl[1] + startEl[3]) / 2, b[1] - endUp)
|
? Math.min((b[1] + a[3]) / 2, b[1] - endUp)
|
||||||
: (endEl[1] + startEl[3]) / 2
|
: (b[1] + a[3]) / 2
|
||||||
: b[1] > a[1]
|
: b[1] > a[1]
|
||||||
? b[1] - endUp
|
? b[1] - endUp
|
||||||
: common[1] - endUp,
|
: common[1] - endUp,
|
||||||
b[2] < a[0]
|
b[2] < a[0]
|
||||||
? b[1] > a[3] || b[3] < a[1]
|
? b[1] > a[3] || b[3] < a[1]
|
||||||
? Math.max((endEl[2] + startEl[0]) / 2, b[2] + endRight)
|
? Math.max((b[2] + a[0]) / 2, b[2] + endRight)
|
||||||
: (endEl[2] + startEl[0]) / 2
|
: (b[2] + a[0]) / 2
|
||||||
: b[2] < a[2]
|
: b[2] < a[2]
|
||||||
? b[2] + endRight
|
? b[2] + endRight
|
||||||
: common[2] + endRight,
|
: common[2] + endRight,
|
||||||
b[3] < a[1]
|
b[3] < a[1]
|
||||||
? b[0] > a[2] || b[2] < a[0]
|
? b[0] > a[2] || b[2] < a[0]
|
||||||
? Math.max((endEl[3] + startEl[1]) / 2, b[3] + endDown)
|
? Math.max((b[3] + a[1]) / 2, b[3] + endDown)
|
||||||
: (endEl[3] + startEl[1]) / 2
|
: (b[3] + a[1]) / 2
|
||||||
: b[3] < a[3]
|
: b[3] < a[3]
|
||||||
? b[3] + endDown
|
? b[3] + endDown
|
||||||
: common[3] + endDown,
|
: common[3] + endDown,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
isBoundToContainer,
|
isBoundToContainer,
|
||||||
isTextElement,
|
isTextElement,
|
||||||
} from "./typeChecks";
|
} from "./typeChecks";
|
||||||
import { CLASSES, isSafari, POINTER_BUTTON } from "../constants";
|
import { CLASSES, isSafari, POINTER_BUTTON, THEME } from "../constants";
|
||||||
import type {
|
import type {
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
ExcalidrawLinearElement,
|
ExcalidrawLinearElement,
|
||||||
@@ -50,6 +50,7 @@ import {
|
|||||||
originalContainerCache,
|
originalContainerCache,
|
||||||
updateOriginalContainerCache,
|
updateOriginalContainerCache,
|
||||||
} from "./containerCache";
|
} from "./containerCache";
|
||||||
|
import { applyDarkModeFilter } from "../colors";
|
||||||
|
|
||||||
const getTransform = (
|
const getTransform = (
|
||||||
width: number,
|
width: number,
|
||||||
@@ -273,10 +274,15 @@ export const textWysiwyg = ({
|
|||||||
textAlign,
|
textAlign,
|
||||||
verticalAlign,
|
verticalAlign,
|
||||||
color: updatedTextElement.strokeColor,
|
color: updatedTextElement.strokeColor,
|
||||||
|
// color:
|
||||||
|
// appState.theme === THEME.DARK
|
||||||
|
// ? applyDarkModeFilter(updatedTextElement.strokeColor)
|
||||||
|
// : updatedTextElement.strokeColor,
|
||||||
opacity: updatedTextElement.opacity / 100,
|
opacity: updatedTextElement.opacity / 100,
|
||||||
filter: "var(--theme-filter)",
|
filter: "var(--theme-filter)",
|
||||||
maxHeight: `${editorMaxHeight}px`,
|
maxHeight: `${editorMaxHeight}px`,
|
||||||
});
|
});
|
||||||
|
// console.log("...", updatedTextElement.strokeColor);
|
||||||
editable.scrollTop = 0;
|
editable.scrollTop = 0;
|
||||||
// For some reason updating font attribute doesn't set font family
|
// For some reason updating font attribute doesn't set font family
|
||||||
// hence updating font family explicitly for test environment
|
// hence updating font family explicitly for test environment
|
||||||
|
|||||||
@@ -320,12 +320,9 @@ export const getDefaultRoundnessTypeForElement = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const isFixedPointBinding = (
|
export const isFixedPointBinding = (
|
||||||
binding: PointBinding | FixedPointBinding,
|
binding: PointBinding,
|
||||||
): binding is FixedPointBinding => {
|
): binding is FixedPointBinding => {
|
||||||
return (
|
return binding.fixedPoint != null;
|
||||||
Object.hasOwn(binding, "fixedPoint") &&
|
|
||||||
(binding as FixedPointBinding).fixedPoint != null
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Move this to @excalidraw/math
|
// TODO: Move this to @excalidraw/math
|
||||||
|
|||||||
@@ -193,7 +193,6 @@ export type ExcalidrawElement =
|
|||||||
| ExcalidrawGenericElement
|
| ExcalidrawGenericElement
|
||||||
| ExcalidrawTextElement
|
| ExcalidrawTextElement
|
||||||
| ExcalidrawLinearElement
|
| ExcalidrawLinearElement
|
||||||
| ExcalidrawArrowElement
|
|
||||||
| ExcalidrawFreeDrawElement
|
| ExcalidrawFreeDrawElement
|
||||||
| ExcalidrawImageElement
|
| ExcalidrawImageElement
|
||||||
| ExcalidrawFrameElement
|
| ExcalidrawFrameElement
|
||||||
@@ -269,19 +268,15 @@ export type PointBinding = {
|
|||||||
elementId: ExcalidrawBindableElement["id"];
|
elementId: ExcalidrawBindableElement["id"];
|
||||||
focus: number;
|
focus: number;
|
||||||
gap: number;
|
gap: number;
|
||||||
|
// Represents the fixed point binding information in form of a vertical and
|
||||||
|
// horizontal ratio (i.e. a percentage value in the 0.0-1.0 range). This ratio
|
||||||
|
// gives the user selected fixed point by multiplying the bound element width
|
||||||
|
// with fixedPoint[0] and the bound element height with fixedPoint[1] to get the
|
||||||
|
// bound element-local point coordinate.
|
||||||
|
fixedPoint: FixedPoint | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FixedPointBinding = Merge<
|
export type FixedPointBinding = Merge<PointBinding, { fixedPoint: FixedPoint }>;
|
||||||
PointBinding,
|
|
||||||
{
|
|
||||||
// Represents the fixed point binding information in form of a vertical and
|
|
||||||
// horizontal ratio (i.e. a percentage value in the 0.0-1.0 range). This ratio
|
|
||||||
// gives the user selected fixed point by multiplying the bound element width
|
|
||||||
// with fixedPoint[0] and the bound element height with fixedPoint[1] to get the
|
|
||||||
// bound element-local point coordinate.
|
|
||||||
fixedPoint: FixedPoint;
|
|
||||||
}
|
|
||||||
>;
|
|
||||||
|
|
||||||
export type Arrowhead =
|
export type Arrowhead =
|
||||||
| "arrow"
|
| "arrow"
|
||||||
|
|||||||
Vendored
+10
@@ -104,3 +104,13 @@ declare namespace jest {
|
|||||||
toBeNonNaNNumber(): void;
|
toBeNonNaNNumber(): void;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare namespace tinycolor {
|
||||||
|
interface Instance {
|
||||||
|
_r: number;
|
||||||
|
_g: number;
|
||||||
|
_b: number;
|
||||||
|
_a: number;
|
||||||
|
_ok: boolean;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -230,8 +230,7 @@
|
|||||||
"resetLibrary": "This will clear your library. Are you sure?",
|
"resetLibrary": "This will clear your library. Are you sure?",
|
||||||
"removeItemsFromsLibrary": "Delete {{count}} item(s) from library?",
|
"removeItemsFromsLibrary": "Delete {{count}} item(s) from library?",
|
||||||
"invalidEncryptionKey": "Encryption key must be of 22 characters. Live collaboration is disabled.",
|
"invalidEncryptionKey": "Encryption key must be of 22 characters. Live collaboration is disabled.",
|
||||||
"collabOfflineWarning": "No internet connection available.\nYour changes will not be saved!",
|
"collabOfflineWarning": "No internet connection available.\nYour changes will not be saved!"
|
||||||
"saveYourContent": "Don't forget to save your content ({{shortcut}})!"
|
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"unsupportedFileType": "Unsupported file type.",
|
"unsupportedFileType": "Unsupported file type.",
|
||||||
|
|||||||
@@ -63,6 +63,7 @@
|
|||||||
"@radix-ui/react-popover": "1.0.3",
|
"@radix-ui/react-popover": "1.0.3",
|
||||||
"@radix-ui/react-tabs": "1.0.2",
|
"@radix-ui/react-tabs": "1.0.2",
|
||||||
"@tldraw/vec": "1.7.1",
|
"@tldraw/vec": "1.7.1",
|
||||||
|
"@types/tinycolor2": "1.4.6",
|
||||||
"browser-fs-access": "0.29.1",
|
"browser-fs-access": "0.29.1",
|
||||||
"canvas-roundrect-polyfill": "0.0.1",
|
"canvas-roundrect-polyfill": "0.0.1",
|
||||||
"clsx": "1.1.1",
|
"clsx": "1.1.1",
|
||||||
@@ -84,6 +85,7 @@
|
|||||||
"pwacompat": "2.0.17",
|
"pwacompat": "2.0.17",
|
||||||
"roughjs": "4.6.4",
|
"roughjs": "4.6.4",
|
||||||
"sass": "1.51.0",
|
"sass": "1.51.0",
|
||||||
|
"tinycolor2": "1.6.0",
|
||||||
"tunnel-rat": "0.1.2"
|
"tunnel-rat": "0.1.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type { StaticCanvasAppState, AppState } from "../types";
|
|||||||
import type { StaticCanvasRenderConfig } from "../scene/types";
|
import type { StaticCanvasRenderConfig } from "../scene/types";
|
||||||
|
|
||||||
import { THEME, THEME_FILTER } from "../constants";
|
import { THEME, THEME_FILTER } from "../constants";
|
||||||
|
import { applyDarkModeFilter } from "../colors";
|
||||||
|
|
||||||
export const fillCircle = (
|
export const fillCircle = (
|
||||||
context: CanvasRenderingContext2D,
|
context: CanvasRenderingContext2D,
|
||||||
@@ -50,7 +51,7 @@ export const bootstrapCanvas = ({
|
|||||||
context.scale(scale, scale);
|
context.scale(scale, scale);
|
||||||
|
|
||||||
if (isExporting && theme === THEME.DARK) {
|
if (isExporting && theme === THEME.DARK) {
|
||||||
context.filter = THEME_FILTER;
|
// context.filter = THEME_FILTER;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Paint background
|
// Paint background
|
||||||
@@ -64,7 +65,10 @@ export const bootstrapCanvas = ({
|
|||||||
context.clearRect(0, 0, normalizedWidth, normalizedHeight);
|
context.clearRect(0, 0, normalizedWidth, normalizedHeight);
|
||||||
}
|
}
|
||||||
context.save();
|
context.save();
|
||||||
context.fillStyle = viewBackgroundColor;
|
context.fillStyle =
|
||||||
|
theme === THEME.DARK
|
||||||
|
? applyDarkModeFilter(viewBackgroundColor)
|
||||||
|
: viewBackgroundColor;
|
||||||
context.fillRect(0, 0, normalizedWidth, normalizedHeight);
|
context.fillRect(0, 0, normalizedWidth, normalizedHeight);
|
||||||
context.restore();
|
context.restore();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ import {
|
|||||||
} from "./helpers";
|
} from "./helpers";
|
||||||
import oc from "open-color";
|
import oc from "open-color";
|
||||||
import {
|
import {
|
||||||
|
isArrowElement,
|
||||||
isElbowArrow,
|
isElbowArrow,
|
||||||
isFrameLikeElement,
|
isFrameLikeElement,
|
||||||
isLinearElement,
|
isLinearElement,
|
||||||
@@ -806,6 +807,7 @@ const _renderInteractiveScene = ({
|
|||||||
// Elbow arrow elements cannot be selected when bound on either end
|
// Elbow arrow elements cannot be selected when bound on either end
|
||||||
(
|
(
|
||||||
isSingleLinearElementSelected &&
|
isSingleLinearElementSelected &&
|
||||||
|
isArrowElement(element) &&
|
||||||
isElbowArrow(element) &&
|
isElbowArrow(element) &&
|
||||||
(element.startBinding || element.endBinding)
|
(element.startBinding || element.endBinding)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ import { ShapeCache } from "../scene/ShapeCache";
|
|||||||
import { getVerticalOffset } from "../fonts";
|
import { getVerticalOffset } from "../fonts";
|
||||||
import { isRightAngleRads } from "../../math";
|
import { isRightAngleRads } from "../../math";
|
||||||
import { getCornerRadius } from "../shapes";
|
import { getCornerRadius } from "../shapes";
|
||||||
|
import { applyDarkModeFilter } from "../colors";
|
||||||
|
|
||||||
// using a stronger invert (100% vs our regular 93%) and saturate
|
// using a stronger invert (100% vs our regular 93%) and saturate
|
||||||
// as a temp hack to make images in dark theme look closer to original
|
// as a temp hack to make images in dark theme look closer to original
|
||||||
@@ -247,9 +248,9 @@ const generateElementCanvas = (
|
|||||||
const rc = rough.canvas(canvas);
|
const rc = rough.canvas(canvas);
|
||||||
|
|
||||||
// in dark theme, revert the image color filter
|
// in dark theme, revert the image color filter
|
||||||
if (shouldResetImageFilter(element, renderConfig, appState)) {
|
// if (shouldResetImageFilter(element, renderConfig, appState)) {
|
||||||
context.filter = IMAGE_INVERT_FILTER;
|
// context.filter = IMAGE_INVERT_FILTER;
|
||||||
}
|
// }
|
||||||
|
|
||||||
drawElementOnCanvas(element, rc, context, renderConfig, appState);
|
drawElementOnCanvas(element, rc, context, renderConfig, appState);
|
||||||
|
|
||||||
@@ -403,7 +404,10 @@ const drawElementOnCanvas = (
|
|||||||
case "freedraw": {
|
case "freedraw": {
|
||||||
// Draw directly to canvas
|
// Draw directly to canvas
|
||||||
context.save();
|
context.save();
|
||||||
context.fillStyle = element.strokeColor;
|
context.fillStyle =
|
||||||
|
appState.theme === THEME.DARK
|
||||||
|
? applyDarkModeFilter(element.strokeColor)
|
||||||
|
: element.strokeColor;
|
||||||
|
|
||||||
const path = getFreeDrawPath2D(element) as Path2D;
|
const path = getFreeDrawPath2D(element) as Path2D;
|
||||||
const fillShape = ShapeCache.get(element);
|
const fillShape = ShapeCache.get(element);
|
||||||
@@ -412,7 +416,10 @@ const drawElementOnCanvas = (
|
|||||||
rc.draw(fillShape);
|
rc.draw(fillShape);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.fillStyle = element.strokeColor;
|
context.fillStyle =
|
||||||
|
appState.theme === THEME.DARK
|
||||||
|
? applyDarkModeFilter(element.strokeColor)
|
||||||
|
: element.strokeColor;
|
||||||
context.fill(path);
|
context.fill(path);
|
||||||
|
|
||||||
context.restore();
|
context.restore();
|
||||||
@@ -458,7 +465,10 @@ const drawElementOnCanvas = (
|
|||||||
context.canvas.setAttribute("dir", rtl ? "rtl" : "ltr");
|
context.canvas.setAttribute("dir", rtl ? "rtl" : "ltr");
|
||||||
context.save();
|
context.save();
|
||||||
context.font = getFontString(element);
|
context.font = getFontString(element);
|
||||||
context.fillStyle = element.strokeColor;
|
context.fillStyle =
|
||||||
|
appState.theme === THEME.DARK
|
||||||
|
? applyDarkModeFilter(element.strokeColor)
|
||||||
|
: element.strokeColor;
|
||||||
context.textAlign = element.textAlign as CanvasTextAlign;
|
context.textAlign = element.textAlign as CanvasTextAlign;
|
||||||
|
|
||||||
// Canvas does not support multiline text by default
|
// Canvas does not support multiline text by default
|
||||||
@@ -699,7 +709,10 @@ export const renderElement = (
|
|||||||
context.fillStyle = "rgba(0, 0, 200, 0.04)";
|
context.fillStyle = "rgba(0, 0, 200, 0.04)";
|
||||||
|
|
||||||
context.lineWidth = FRAME_STYLE.strokeWidth / appState.zoom.value;
|
context.lineWidth = FRAME_STYLE.strokeWidth / appState.zoom.value;
|
||||||
context.strokeStyle = FRAME_STYLE.strokeColor;
|
context.strokeStyle =
|
||||||
|
appState.theme === THEME.DARK
|
||||||
|
? applyDarkModeFilter(element.strokeColor)
|
||||||
|
: FRAME_STYLE.strokeColor;
|
||||||
|
|
||||||
// TODO change later to only affect AI frames
|
// TODO change later to only affect AI frames
|
||||||
if (isMagicFrameElement(element)) {
|
if (isMagicFrameElement(element)) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
MAX_DECIMALS_FOR_SVG_EXPORT,
|
MAX_DECIMALS_FOR_SVG_EXPORT,
|
||||||
MIME_TYPES,
|
MIME_TYPES,
|
||||||
SVG_NS,
|
SVG_NS,
|
||||||
|
THEME,
|
||||||
} from "../constants";
|
} from "../constants";
|
||||||
import { normalizeLink, toValidURL } from "../data/url";
|
import { normalizeLink, toValidURL } from "../data/url";
|
||||||
import { getElementAbsoluteCoords } from "../element";
|
import { getElementAbsoluteCoords } from "../element";
|
||||||
@@ -37,6 +38,7 @@ import { getFontFamilyString, isRTL, isTestEnv } from "../utils";
|
|||||||
import { getFreeDrawSvgPath, IMAGE_INVERT_FILTER } from "./renderElement";
|
import { getFreeDrawSvgPath, IMAGE_INVERT_FILTER } from "./renderElement";
|
||||||
import { getVerticalOffset } from "../fonts";
|
import { getVerticalOffset } from "../fonts";
|
||||||
import { getCornerRadius, isPathALoop } from "../shapes";
|
import { getCornerRadius, isPathALoop } from "../shapes";
|
||||||
|
import { applyDarkModeFilter } from "../colors";
|
||||||
|
|
||||||
const roughSVGDrawWithPrecision = (
|
const roughSVGDrawWithPrecision = (
|
||||||
rsvg: RoughSVG,
|
rsvg: RoughSVG,
|
||||||
@@ -139,7 +141,7 @@ const renderElementToSvg = (
|
|||||||
case "rectangle":
|
case "rectangle":
|
||||||
case "diamond":
|
case "diamond":
|
||||||
case "ellipse": {
|
case "ellipse": {
|
||||||
const shape = ShapeCache.generateElementShape(element, null);
|
const shape = ShapeCache.generateElementShape(element, renderConfig);
|
||||||
const node = roughSVGDrawWithPrecision(
|
const node = roughSVGDrawWithPrecision(
|
||||||
rsvg,
|
rsvg,
|
||||||
shape,
|
shape,
|
||||||
@@ -389,7 +391,12 @@ const renderElementToSvg = (
|
|||||||
);
|
);
|
||||||
node.setAttribute("stroke", "none");
|
node.setAttribute("stroke", "none");
|
||||||
const path = svgRoot.ownerDocument!.createElementNS(SVG_NS, "path");
|
const path = svgRoot.ownerDocument!.createElementNS(SVG_NS, "path");
|
||||||
path.setAttribute("fill", element.strokeColor);
|
path.setAttribute(
|
||||||
|
"fill",
|
||||||
|
renderConfig.theme === THEME.DARK
|
||||||
|
? applyDarkModeFilter(element.strokeColor)
|
||||||
|
: element.strokeColor,
|
||||||
|
);
|
||||||
path.setAttribute("d", getFreeDrawSvgPath(element));
|
path.setAttribute("d", getFreeDrawSvgPath(element));
|
||||||
node.appendChild(path);
|
node.appendChild(path);
|
||||||
|
|
||||||
@@ -526,7 +533,12 @@ const renderElementToSvg = (
|
|||||||
rect.setAttribute("ry", FRAME_STYLE.radius.toString());
|
rect.setAttribute("ry", FRAME_STYLE.radius.toString());
|
||||||
|
|
||||||
rect.setAttribute("fill", "none");
|
rect.setAttribute("fill", "none");
|
||||||
rect.setAttribute("stroke", FRAME_STYLE.strokeColor);
|
rect.setAttribute(
|
||||||
|
"stroke",
|
||||||
|
renderConfig.theme === THEME.DARK
|
||||||
|
? applyDarkModeFilter(FRAME_STYLE.strokeColor)
|
||||||
|
: FRAME_STYLE.strokeColor,
|
||||||
|
);
|
||||||
rect.setAttribute("stroke-width", FRAME_STYLE.strokeWidth.toString());
|
rect.setAttribute("stroke-width", FRAME_STYLE.strokeWidth.toString());
|
||||||
|
|
||||||
addToRoot(rect, element);
|
addToRoot(rect, element);
|
||||||
@@ -577,7 +589,12 @@ const renderElementToSvg = (
|
|||||||
text.setAttribute("y", `${i * lineHeightPx + verticalOffset}`);
|
text.setAttribute("y", `${i * lineHeightPx + verticalOffset}`);
|
||||||
text.setAttribute("font-family", getFontFamilyString(element));
|
text.setAttribute("font-family", getFontFamilyString(element));
|
||||||
text.setAttribute("font-size", `${element.fontSize}px`);
|
text.setAttribute("font-size", `${element.fontSize}px`);
|
||||||
text.setAttribute("fill", element.strokeColor);
|
text.setAttribute(
|
||||||
|
"fill",
|
||||||
|
renderConfig.theme === THEME.DARK
|
||||||
|
? applyDarkModeFilter(element.strokeColor)
|
||||||
|
: element.strokeColor,
|
||||||
|
);
|
||||||
text.setAttribute("text-anchor", textAnchor);
|
text.setAttribute("text-anchor", textAnchor);
|
||||||
text.setAttribute("style", "white-space: pre;");
|
text.setAttribute("style", "white-space: pre;");
|
||||||
text.setAttribute("direction", direction);
|
text.setAttribute("direction", direction);
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import type {
|
|||||||
import { generateFreeDrawShape } from "../renderer/renderElement";
|
import { generateFreeDrawShape } from "../renderer/renderElement";
|
||||||
import { isTransparent, assertNever } from "../utils";
|
import { isTransparent, assertNever } from "../utils";
|
||||||
import { simplify } from "points-on-curve";
|
import { simplify } from "points-on-curve";
|
||||||
import { ROUGHNESS } from "../constants";
|
import { ROUGHNESS, THEME } from "../constants";
|
||||||
import {
|
import {
|
||||||
isElbowArrow,
|
isElbowArrow,
|
||||||
isEmbeddableElement,
|
isEmbeddableElement,
|
||||||
@@ -22,7 +22,7 @@ import {
|
|||||||
isLinearElement,
|
isLinearElement,
|
||||||
} from "../element/typeChecks";
|
} from "../element/typeChecks";
|
||||||
import { canChangeRoundness } from "./comparisons";
|
import { canChangeRoundness } from "./comparisons";
|
||||||
import type { EmbedsValidationStatus } from "../types";
|
import type { AppState, EmbedsValidationStatus } from "../types";
|
||||||
import {
|
import {
|
||||||
point,
|
point,
|
||||||
pointDistance,
|
pointDistance,
|
||||||
@@ -30,6 +30,7 @@ import {
|
|||||||
type LocalPoint,
|
type LocalPoint,
|
||||||
} from "../../math";
|
} from "../../math";
|
||||||
import { getCornerRadius, isPathALoop } from "../shapes";
|
import { getCornerRadius, isPathALoop } from "../shapes";
|
||||||
|
import { applyDarkModeFilter } from "../colors";
|
||||||
|
|
||||||
const getDashArrayDashed = (strokeWidth: number) => [8, 8 + strokeWidth];
|
const getDashArrayDashed = (strokeWidth: number) => [8, 8 + strokeWidth];
|
||||||
|
|
||||||
@@ -61,6 +62,7 @@ function adjustRoughness(element: ExcalidrawElement): number {
|
|||||||
export const generateRoughOptions = (
|
export const generateRoughOptions = (
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
continuousPath = false,
|
continuousPath = false,
|
||||||
|
isDarkMode: boolean = false,
|
||||||
): Options => {
|
): Options => {
|
||||||
const options: Options = {
|
const options: Options = {
|
||||||
seed: element.seed,
|
seed: element.seed,
|
||||||
@@ -85,7 +87,9 @@ export const generateRoughOptions = (
|
|||||||
fillWeight: element.strokeWidth / 2,
|
fillWeight: element.strokeWidth / 2,
|
||||||
hachureGap: element.strokeWidth * 4,
|
hachureGap: element.strokeWidth * 4,
|
||||||
roughness: adjustRoughness(element),
|
roughness: adjustRoughness(element),
|
||||||
stroke: element.strokeColor,
|
stroke: isDarkMode
|
||||||
|
? applyDarkModeFilter(element.strokeColor)
|
||||||
|
: element.strokeColor,
|
||||||
preserveVertices:
|
preserveVertices:
|
||||||
continuousPath || element.roughness < ROUGHNESS.cartoonist,
|
continuousPath || element.roughness < ROUGHNESS.cartoonist,
|
||||||
};
|
};
|
||||||
@@ -99,6 +103,8 @@ export const generateRoughOptions = (
|
|||||||
options.fillStyle = element.fillStyle;
|
options.fillStyle = element.fillStyle;
|
||||||
options.fill = isTransparent(element.backgroundColor)
|
options.fill = isTransparent(element.backgroundColor)
|
||||||
? undefined
|
? undefined
|
||||||
|
: isDarkMode
|
||||||
|
? applyDarkModeFilter(element.backgroundColor)
|
||||||
: element.backgroundColor;
|
: element.backgroundColor;
|
||||||
if (element.type === "ellipse") {
|
if (element.type === "ellipse") {
|
||||||
options.curveFitting = 1;
|
options.curveFitting = 1;
|
||||||
@@ -112,6 +118,8 @@ export const generateRoughOptions = (
|
|||||||
options.fill =
|
options.fill =
|
||||||
element.backgroundColor === "transparent"
|
element.backgroundColor === "transparent"
|
||||||
? undefined
|
? undefined
|
||||||
|
: isDarkMode
|
||||||
|
? applyDarkModeFilter(element.backgroundColor)
|
||||||
: element.backgroundColor;
|
: element.backgroundColor;
|
||||||
}
|
}
|
||||||
return options;
|
return options;
|
||||||
@@ -165,6 +173,7 @@ const getArrowheadShapes = (
|
|||||||
generator: RoughGenerator,
|
generator: RoughGenerator,
|
||||||
options: Options,
|
options: Options,
|
||||||
canvasBackgroundColor: string,
|
canvasBackgroundColor: string,
|
||||||
|
isDarkMode: boolean,
|
||||||
) => {
|
) => {
|
||||||
const arrowheadPoints = getArrowheadPoints(
|
const arrowheadPoints = getArrowheadPoints(
|
||||||
element,
|
element,
|
||||||
@@ -192,10 +201,14 @@ const getArrowheadShapes = (
|
|||||||
fill:
|
fill:
|
||||||
arrowhead === "circle_outline"
|
arrowhead === "circle_outline"
|
||||||
? canvasBackgroundColor
|
? canvasBackgroundColor
|
||||||
|
: isDarkMode
|
||||||
|
? applyDarkModeFilter(element.strokeColor)
|
||||||
: element.strokeColor,
|
: element.strokeColor,
|
||||||
|
|
||||||
fillStyle: "solid",
|
fillStyle: "solid",
|
||||||
stroke: element.strokeColor,
|
stroke: isDarkMode
|
||||||
|
? applyDarkModeFilter(element.strokeColor)
|
||||||
|
: element.strokeColor,
|
||||||
roughness: Math.min(0.5, options.roughness || 0),
|
roughness: Math.min(0.5, options.roughness || 0),
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
@@ -220,6 +233,8 @@ const getArrowheadShapes = (
|
|||||||
fill:
|
fill:
|
||||||
arrowhead === "triangle_outline"
|
arrowhead === "triangle_outline"
|
||||||
? canvasBackgroundColor
|
? canvasBackgroundColor
|
||||||
|
: isDarkMode
|
||||||
|
? applyDarkModeFilter(element.strokeColor)
|
||||||
: element.strokeColor,
|
: element.strokeColor,
|
||||||
fillStyle: "solid",
|
fillStyle: "solid",
|
||||||
roughness: Math.min(1, options.roughness || 0),
|
roughness: Math.min(1, options.roughness || 0),
|
||||||
@@ -248,6 +263,8 @@ const getArrowheadShapes = (
|
|||||||
fill:
|
fill:
|
||||||
arrowhead === "diamond_outline"
|
arrowhead === "diamond_outline"
|
||||||
? canvasBackgroundColor
|
? canvasBackgroundColor
|
||||||
|
: isDarkMode
|
||||||
|
? applyDarkModeFilter(element.strokeColor)
|
||||||
: element.strokeColor,
|
: element.strokeColor,
|
||||||
fillStyle: "solid",
|
fillStyle: "solid",
|
||||||
roughness: Math.min(1, options.roughness || 0),
|
roughness: Math.min(1, options.roughness || 0),
|
||||||
@@ -291,12 +308,15 @@ export const _generateElementShape = (
|
|||||||
isExporting,
|
isExporting,
|
||||||
canvasBackgroundColor,
|
canvasBackgroundColor,
|
||||||
embedsValidationStatus,
|
embedsValidationStatus,
|
||||||
|
theme,
|
||||||
}: {
|
}: {
|
||||||
isExporting: boolean;
|
isExporting: boolean;
|
||||||
canvasBackgroundColor: string;
|
canvasBackgroundColor: string;
|
||||||
embedsValidationStatus: EmbedsValidationStatus | null;
|
embedsValidationStatus: EmbedsValidationStatus | null;
|
||||||
|
theme: AppState["theme"];
|
||||||
},
|
},
|
||||||
): Drawable | Drawable[] | null => {
|
): Drawable | Drawable[] | null => {
|
||||||
|
const isDarkMode = theme === THEME.DARK;
|
||||||
switch (element.type) {
|
switch (element.type) {
|
||||||
case "rectangle":
|
case "rectangle":
|
||||||
case "iframe":
|
case "iframe":
|
||||||
@@ -322,6 +342,7 @@ export const _generateElementShape = (
|
|||||||
embedsValidationStatus,
|
embedsValidationStatus,
|
||||||
),
|
),
|
||||||
true,
|
true,
|
||||||
|
isDarkMode,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -337,6 +358,7 @@ export const _generateElementShape = (
|
|||||||
embedsValidationStatus,
|
embedsValidationStatus,
|
||||||
),
|
),
|
||||||
false,
|
false,
|
||||||
|
isDarkMode,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -374,7 +396,7 @@ export const _generateElementShape = (
|
|||||||
C ${topX} ${topY}, ${topX} ${topY}, ${topX + verticalRadius} ${
|
C ${topX} ${topY}, ${topX} ${topY}, ${topX + verticalRadius} ${
|
||||||
topY + horizontalRadius
|
topY + horizontalRadius
|
||||||
}`,
|
}`,
|
||||||
generateRoughOptions(element, true),
|
generateRoughOptions(element, true, isDarkMode),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
shape = generator.polygon(
|
shape = generator.polygon(
|
||||||
@@ -384,7 +406,7 @@ export const _generateElementShape = (
|
|||||||
[bottomX, bottomY],
|
[bottomX, bottomY],
|
||||||
[leftX, leftY],
|
[leftX, leftY],
|
||||||
],
|
],
|
||||||
generateRoughOptions(element),
|
generateRoughOptions(element, undefined, isDarkMode),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return shape;
|
return shape;
|
||||||
@@ -395,14 +417,14 @@ export const _generateElementShape = (
|
|||||||
element.height / 2,
|
element.height / 2,
|
||||||
element.width,
|
element.width,
|
||||||
element.height,
|
element.height,
|
||||||
generateRoughOptions(element),
|
generateRoughOptions(element, undefined, isDarkMode),
|
||||||
);
|
);
|
||||||
return shape;
|
return shape;
|
||||||
}
|
}
|
||||||
case "line":
|
case "line":
|
||||||
case "arrow": {
|
case "arrow": {
|
||||||
let shape: ElementShapes[typeof element.type];
|
let shape: ElementShapes[typeof element.type];
|
||||||
const options = generateRoughOptions(element);
|
const options = generateRoughOptions(element, undefined, isDarkMode);
|
||||||
|
|
||||||
// points array can be empty in the beginning, so it is important to add
|
// points array can be empty in the beginning, so it is important to add
|
||||||
// initial position to it
|
// initial position to it
|
||||||
@@ -414,7 +436,7 @@ export const _generateElementShape = (
|
|||||||
shape = [
|
shape = [
|
||||||
generator.path(
|
generator.path(
|
||||||
generateElbowArrowShape(points, 16),
|
generateElbowArrowShape(points, 16),
|
||||||
generateRoughOptions(element, true),
|
generateRoughOptions(element, true, isDarkMode),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
} else if (!element.roundness) {
|
} else if (!element.roundness) {
|
||||||
@@ -446,6 +468,7 @@ export const _generateElementShape = (
|
|||||||
generator,
|
generator,
|
||||||
options,
|
options,
|
||||||
canvasBackgroundColor,
|
canvasBackgroundColor,
|
||||||
|
isDarkMode,
|
||||||
);
|
);
|
||||||
shape.push(...shapes);
|
shape.push(...shapes);
|
||||||
}
|
}
|
||||||
@@ -463,6 +486,7 @@ export const _generateElementShape = (
|
|||||||
generator,
|
generator,
|
||||||
options,
|
options,
|
||||||
canvasBackgroundColor,
|
canvasBackgroundColor,
|
||||||
|
isDarkMode,
|
||||||
);
|
);
|
||||||
shape.push(...shapes);
|
shape.push(...shapes);
|
||||||
}
|
}
|
||||||
@@ -477,7 +501,7 @@ export const _generateElementShape = (
|
|||||||
// generate rough polygon to fill freedraw shape
|
// generate rough polygon to fill freedraw shape
|
||||||
const simplifiedPoints = simplify(element.points, 0.75);
|
const simplifiedPoints = simplify(element.points, 0.75);
|
||||||
shape = generator.curve(simplifiedPoints as [number, number][], {
|
shape = generator.curve(simplifiedPoints as [number, number][], {
|
||||||
...generateRoughOptions(element),
|
...generateRoughOptions(element, undefined, isDarkMode),
|
||||||
stroke: "none",
|
stroke: "none",
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { _generateElementShape } from "./Shape";
|
|||||||
import type { ElementShape, ElementShapes } from "./types";
|
import type { ElementShape, ElementShapes } from "./types";
|
||||||
import { COLOR_PALETTE } from "../colors";
|
import { COLOR_PALETTE } from "../colors";
|
||||||
import type { AppState, EmbedsValidationStatus } from "../types";
|
import type { AppState, EmbedsValidationStatus } from "../types";
|
||||||
|
import { THEME } from "..";
|
||||||
|
|
||||||
export class ShapeCache {
|
export class ShapeCache {
|
||||||
private static rg = new RoughGenerator();
|
private static rg = new RoughGenerator();
|
||||||
@@ -52,6 +53,7 @@ export class ShapeCache {
|
|||||||
isExporting: boolean;
|
isExporting: boolean;
|
||||||
canvasBackgroundColor: AppState["viewBackgroundColor"];
|
canvasBackgroundColor: AppState["viewBackgroundColor"];
|
||||||
embedsValidationStatus: EmbedsValidationStatus;
|
embedsValidationStatus: EmbedsValidationStatus;
|
||||||
|
theme: AppState["theme"];
|
||||||
} | null,
|
} | null,
|
||||||
) => {
|
) => {
|
||||||
// when exporting, always regenerated to guarantee the latest shape
|
// when exporting, always regenerated to guarantee the latest shape
|
||||||
@@ -74,6 +76,7 @@ export class ShapeCache {
|
|||||||
isExporting: false,
|
isExporting: false,
|
||||||
canvasBackgroundColor: COLOR_PALETTE.white,
|
canvasBackgroundColor: COLOR_PALETTE.white,
|
||||||
embedsValidationStatus: null,
|
embedsValidationStatus: null,
|
||||||
|
theme: THEME.LIGHT,
|
||||||
},
|
},
|
||||||
) as T["type"] extends keyof ElementShapes
|
) as T["type"] extends keyof ElementShapes
|
||||||
? ElementShapes[T["type"]]
|
? ElementShapes[T["type"]]
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import { syncInvalidIndices } from "../fractionalIndex";
|
|||||||
import { renderStaticScene } from "../renderer/staticScene";
|
import { renderStaticScene } from "../renderer/staticScene";
|
||||||
import { Fonts } from "../fonts";
|
import { Fonts } from "../fonts";
|
||||||
import type { Font } from "../fonts/ExcalidrawFont";
|
import type { Font } from "../fonts/ExcalidrawFont";
|
||||||
|
import { applyDarkModeFilter } from "../colors";
|
||||||
|
|
||||||
const SVG_EXPORT_TAG = `<!-- svg-source:excalidraw -->`;
|
const SVG_EXPORT_TAG = `<!-- svg-source:excalidraw -->`;
|
||||||
|
|
||||||
@@ -185,11 +186,6 @@ export const exportToCanvas = async (
|
|||||||
exportingFrame ?? null,
|
exportingFrame ?? null,
|
||||||
appState.frameRendering ?? null,
|
appState.frameRendering ?? null,
|
||||||
);
|
);
|
||||||
// for canvas export, don't clip if exporting a specific frame as it would
|
|
||||||
// clip the corners of the content
|
|
||||||
if (exportingFrame) {
|
|
||||||
frameRendering.clip = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const elementsForRender = prepareElementsForRender({
|
const elementsForRender = prepareElementsForRender({
|
||||||
elements,
|
elements,
|
||||||
@@ -219,6 +215,8 @@ export const exportToCanvas = async (
|
|||||||
files,
|
files,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const theme = appState.exportWithDarkMode ? THEME.DARK : THEME.LIGHT;
|
||||||
|
|
||||||
renderStaticScene({
|
renderStaticScene({
|
||||||
canvas,
|
canvas,
|
||||||
rc: rough.canvas(canvas),
|
rc: rough.canvas(canvas),
|
||||||
@@ -238,7 +236,7 @@ export const exportToCanvas = async (
|
|||||||
scrollY: -minY + exportPadding,
|
scrollY: -minY + exportPadding,
|
||||||
zoom: defaultAppState.zoom,
|
zoom: defaultAppState.zoom,
|
||||||
shouldCacheIgnoreZoom: false,
|
shouldCacheIgnoreZoom: false,
|
||||||
theme: appState.exportWithDarkMode ? THEME.DARK : THEME.LIGHT,
|
theme,
|
||||||
},
|
},
|
||||||
renderConfig: {
|
renderConfig: {
|
||||||
canvasBackgroundColor: viewBackgroundColor,
|
canvasBackgroundColor: viewBackgroundColor,
|
||||||
@@ -249,6 +247,7 @@ export const exportToCanvas = async (
|
|||||||
embedsValidationStatus: new Map(),
|
embedsValidationStatus: new Map(),
|
||||||
elementsPendingErasure: new Set(),
|
elementsPendingErasure: new Set(),
|
||||||
pendingFlowchartNodes: null,
|
pendingFlowchartNodes: null,
|
||||||
|
theme,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -335,7 +334,7 @@ export const exportToSvg = async (
|
|||||||
svgRoot.setAttribute("width", `${width * exportScale}`);
|
svgRoot.setAttribute("width", `${width * exportScale}`);
|
||||||
svgRoot.setAttribute("height", `${height * exportScale}`);
|
svgRoot.setAttribute("height", `${height * exportScale}`);
|
||||||
if (exportWithDarkMode) {
|
if (exportWithDarkMode) {
|
||||||
svgRoot.setAttribute("filter", THEME_FILTER);
|
// svgRoot.setAttribute("filter", THEME_FILTER);
|
||||||
}
|
}
|
||||||
|
|
||||||
const offsetX = -minX + exportPadding;
|
const offsetX = -minX + exportPadding;
|
||||||
@@ -356,11 +355,6 @@ export const exportToSvg = async (
|
|||||||
}) rotate(${frame.angle} ${cx} ${cy})"
|
}) rotate(${frame.angle} ${cx} ${cy})"
|
||||||
width="${frame.width}"
|
width="${frame.width}"
|
||||||
height="${frame.height}"
|
height="${frame.height}"
|
||||||
${
|
|
||||||
exportingFrame
|
|
||||||
? ""
|
|
||||||
: `rx=${FRAME_STYLE.radius} ry=${FRAME_STYLE.radius}`
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
</rect>
|
</rect>
|
||||||
</clipPath>`;
|
</clipPath>`;
|
||||||
@@ -386,7 +380,12 @@ export const exportToSvg = async (
|
|||||||
rect.setAttribute("y", "0");
|
rect.setAttribute("y", "0");
|
||||||
rect.setAttribute("width", `${width}`);
|
rect.setAttribute("width", `${width}`);
|
||||||
rect.setAttribute("height", `${height}`);
|
rect.setAttribute("height", `${height}`);
|
||||||
rect.setAttribute("fill", viewBackgroundColor);
|
rect.setAttribute(
|
||||||
|
"fill",
|
||||||
|
appState.exportWithDarkMode
|
||||||
|
? applyDarkModeFilter(viewBackgroundColor)
|
||||||
|
: viewBackgroundColor,
|
||||||
|
);
|
||||||
svgRoot.appendChild(rect);
|
svgRoot.appendChild(rect);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,6 +393,8 @@ export const exportToSvg = async (
|
|||||||
|
|
||||||
const renderEmbeddables = opts?.renderEmbeddables ?? false;
|
const renderEmbeddables = opts?.renderEmbeddables ?? false;
|
||||||
|
|
||||||
|
const theme = appState.exportWithDarkMode ? THEME.DARK : THEME.LIGHT;
|
||||||
|
|
||||||
renderSceneToSvg(
|
renderSceneToSvg(
|
||||||
elementsForRender,
|
elementsForRender,
|
||||||
toBrandedType<RenderableElementsMap>(arrayToMap(elementsForRender)),
|
toBrandedType<RenderableElementsMap>(arrayToMap(elementsForRender)),
|
||||||
@@ -415,6 +416,7 @@ export const exportToSvg = async (
|
|||||||
.map((element) => [element.id, true]),
|
.map((element) => [element.id, true]),
|
||||||
)
|
)
|
||||||
: new Map(),
|
: new Map(),
|
||||||
|
theme,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export type StaticCanvasRenderConfig = {
|
|||||||
embedsValidationStatus: EmbedsValidationStatus;
|
embedsValidationStatus: EmbedsValidationStatus;
|
||||||
elementsPendingErasure: ElementsPendingErasure;
|
elementsPendingErasure: ElementsPendingErasure;
|
||||||
pendingFlowchartNodes: PendingExcalidrawElements | null;
|
pendingFlowchartNodes: PendingExcalidrawElements | null;
|
||||||
|
theme: AppState["theme"];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SVGRenderConfig = {
|
export type SVGRenderConfig = {
|
||||||
@@ -46,6 +47,7 @@ export type SVGRenderConfig = {
|
|||||||
frameRendering: AppState["frameRendering"];
|
frameRendering: AppState["frameRendering"];
|
||||||
canvasBackgroundColor: AppState["viewBackgroundColor"];
|
canvasBackgroundColor: AppState["viewBackgroundColor"];
|
||||||
embedsValidationStatus: EmbedsValidationStatus;
|
embedsValidationStatus: EmbedsValidationStatus;
|
||||||
|
theme: AppState["theme"];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type InteractiveCanvasRenderConfig = {
|
export type InteractiveCanvasRenderConfig = {
|
||||||
|
|||||||
@@ -8430,7 +8430,6 @@ exports[`regression tests > key 5 selects arrow tool > [end of test] appState 1`
|
|||||||
"selectedElementsAreBeingDragged": false,
|
"selectedElementsAreBeingDragged": false,
|
||||||
"selectedGroupIds": {},
|
"selectedGroupIds": {},
|
||||||
"selectedLinearElement": LinearElementEditor {
|
"selectedLinearElement": LinearElementEditor {
|
||||||
"elbowed": false,
|
|
||||||
"elementId": "id0",
|
"elementId": "id0",
|
||||||
"endBindingElement": "keep",
|
"endBindingElement": "keep",
|
||||||
"hoverPointIndex": -1,
|
"hoverPointIndex": -1,
|
||||||
@@ -8650,7 +8649,6 @@ exports[`regression tests > key 6 selects line tool > [end of test] appState 1`]
|
|||||||
"selectedElementsAreBeingDragged": false,
|
"selectedElementsAreBeingDragged": false,
|
||||||
"selectedGroupIds": {},
|
"selectedGroupIds": {},
|
||||||
"selectedLinearElement": LinearElementEditor {
|
"selectedLinearElement": LinearElementEditor {
|
||||||
"elbowed": false,
|
|
||||||
"elementId": "id0",
|
"elementId": "id0",
|
||||||
"endBindingElement": "keep",
|
"endBindingElement": "keep",
|
||||||
"hoverPointIndex": -1,
|
"hoverPointIndex": -1,
|
||||||
@@ -9060,7 +9058,6 @@ exports[`regression tests > key a selects arrow tool > [end of test] appState 1`
|
|||||||
"selectedElementsAreBeingDragged": false,
|
"selectedElementsAreBeingDragged": false,
|
||||||
"selectedGroupIds": {},
|
"selectedGroupIds": {},
|
||||||
"selectedLinearElement": LinearElementEditor {
|
"selectedLinearElement": LinearElementEditor {
|
||||||
"elbowed": false,
|
|
||||||
"elementId": "id0",
|
"elementId": "id0",
|
||||||
"endBindingElement": "keep",
|
"endBindingElement": "keep",
|
||||||
"hoverPointIndex": -1,
|
"hoverPointIndex": -1,
|
||||||
@@ -9457,7 +9454,6 @@ exports[`regression tests > key l selects line tool > [end of test] appState 1`]
|
|||||||
"selectedElementsAreBeingDragged": false,
|
"selectedElementsAreBeingDragged": false,
|
||||||
"selectedGroupIds": {},
|
"selectedGroupIds": {},
|
||||||
"selectedLinearElement": LinearElementEditor {
|
"selectedLinearElement": LinearElementEditor {
|
||||||
"elbowed": false,
|
|
||||||
"elementId": "id0",
|
"elementId": "id0",
|
||||||
"endBindingElement": "keep",
|
"endBindingElement": "keep",
|
||||||
"hoverPointIndex": -1,
|
"hoverPointIndex": -1,
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ import type {
|
|||||||
ExcalidrawFrameElement,
|
ExcalidrawFrameElement,
|
||||||
ExcalidrawElementType,
|
ExcalidrawElementType,
|
||||||
ExcalidrawMagicFrameElement,
|
ExcalidrawMagicFrameElement,
|
||||||
ExcalidrawElbowArrowElement,
|
|
||||||
ExcalidrawArrowElement,
|
|
||||||
} from "../../element/types";
|
} from "../../element/types";
|
||||||
import { newElement, newTextElement, newLinearElement } from "../../element";
|
import { newElement, newTextElement, newLinearElement } from "../../element";
|
||||||
import { DEFAULT_VERTICAL_ALIGN, ROUNDNESS } from "../../constants";
|
import { DEFAULT_VERTICAL_ALIGN, ROUNDNESS } from "../../constants";
|
||||||
@@ -129,10 +127,6 @@ export class API {
|
|||||||
expect(API.getSelectedElements().length).toBe(0);
|
expect(API.getSelectedElements().length).toBe(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
static getElement = <T extends ExcalidrawElement>(element: T): T => {
|
|
||||||
return h.app.scene.getElementsMapIncludingDeleted().get(element.id) as T || element;
|
|
||||||
}
|
|
||||||
|
|
||||||
static createElement = <
|
static createElement = <
|
||||||
T extends Exclude<ExcalidrawElementType, "selection"> = "rectangle",
|
T extends Exclude<ExcalidrawElementType, "selection"> = "rectangle",
|
||||||
>({
|
>({
|
||||||
@@ -185,16 +179,10 @@ export class API {
|
|||||||
scale?: T extends "image" ? ExcalidrawImageElement["scale"] : never;
|
scale?: T extends "image" ? ExcalidrawImageElement["scale"] : never;
|
||||||
status?: T extends "image" ? ExcalidrawImageElement["status"] : never;
|
status?: T extends "image" ? ExcalidrawImageElement["status"] : never;
|
||||||
startBinding?: T extends "arrow"
|
startBinding?: T extends "arrow"
|
||||||
? ExcalidrawArrowElement["startBinding"] | ExcalidrawElbowArrowElement["startBinding"]
|
? ExcalidrawLinearElement["startBinding"]
|
||||||
: never;
|
: never;
|
||||||
endBinding?: T extends "arrow"
|
endBinding?: T extends "arrow"
|
||||||
? ExcalidrawArrowElement["endBinding"] | ExcalidrawElbowArrowElement["endBinding"]
|
? ExcalidrawLinearElement["endBinding"]
|
||||||
: never;
|
|
||||||
startArrowhead?: T extends "arrow"
|
|
||||||
? ExcalidrawArrowElement["startArrowhead"] | ExcalidrawElbowArrowElement["startArrowhead"]
|
|
||||||
: never;
|
|
||||||
endArrowhead?: T extends "arrow"
|
|
||||||
? ExcalidrawArrowElement["endArrowhead"] | ExcalidrawElbowArrowElement["endArrowhead"]
|
|
||||||
: never;
|
: never;
|
||||||
elbowed?: boolean;
|
elbowed?: boolean;
|
||||||
}): T extends "arrow" | "line"
|
}): T extends "arrow" | "line"
|
||||||
@@ -352,8 +340,6 @@ export class API {
|
|||||||
if (element.type === "arrow") {
|
if (element.type === "arrow") {
|
||||||
element.startBinding = rest.startBinding ?? null;
|
element.startBinding = rest.startBinding ?? null;
|
||||||
element.endBinding = rest.endBinding ?? null;
|
element.endBinding = rest.endBinding ?? null;
|
||||||
element.startArrowhead = rest.startArrowhead ?? null;
|
|
||||||
element.endArrowhead = rest.endArrowhead ?? null;
|
|
||||||
}
|
}
|
||||||
if (id) {
|
if (id) {
|
||||||
element.id = id;
|
element.id = id;
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ import type {
|
|||||||
ExcalidrawGenericElement,
|
ExcalidrawGenericElement,
|
||||||
ExcalidrawLinearElement,
|
ExcalidrawLinearElement,
|
||||||
ExcalidrawTextElement,
|
ExcalidrawTextElement,
|
||||||
FixedPointBinding,
|
|
||||||
FractionalIndex,
|
FractionalIndex,
|
||||||
SceneElementsMap,
|
SceneElementsMap,
|
||||||
} from "../element/types";
|
} from "../element/types";
|
||||||
@@ -2050,13 +2049,13 @@ describe("history", () => {
|
|||||||
focus: -0.001587301587301948,
|
focus: -0.001587301587301948,
|
||||||
gap: 5,
|
gap: 5,
|
||||||
fixedPoint: [1.0318471337579618, 0.49920634920634904],
|
fixedPoint: [1.0318471337579618, 0.49920634920634904],
|
||||||
} as FixedPointBinding,
|
},
|
||||||
endBinding: {
|
endBinding: {
|
||||||
elementId: "u2JGnnmoJ0VATV4vCNJE5",
|
elementId: "u2JGnnmoJ0VATV4vCNJE5",
|
||||||
focus: -0.0016129032258049847,
|
focus: -0.0016129032258049847,
|
||||||
gap: 3.537079145500037,
|
gap: 3.537079145500037,
|
||||||
fixedPoint: [0.4991935483870975, -0.03875193720914723],
|
fixedPoint: [0.4991935483870975, -0.03875193720914723],
|
||||||
} as FixedPointBinding,
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
storeAction: StoreAction.CAPTURE,
|
storeAction: StoreAction.CAPTURE,
|
||||||
@@ -4456,7 +4455,7 @@ describe("history", () => {
|
|||||||
elements: [
|
elements: [
|
||||||
h.elements[0],
|
h.elements[0],
|
||||||
newElementWith(h.elements[1], { boundElements: [] }),
|
newElementWith(h.elements[1], { boundElements: [] }),
|
||||||
newElementWith(h.elements[2] as ExcalidrawElbowArrowElement, {
|
newElementWith(h.elements[2] as ExcalidrawLinearElement, {
|
||||||
endBinding: {
|
endBinding: {
|
||||||
elementId: remoteContainer.id,
|
elementId: remoteContainer.id,
|
||||||
gap: 1,
|
gap: 1,
|
||||||
@@ -4656,7 +4655,7 @@ describe("history", () => {
|
|||||||
// Simulate remote update
|
// Simulate remote update
|
||||||
API.updateScene({
|
API.updateScene({
|
||||||
elements: [
|
elements: [
|
||||||
newElementWith(h.elements[0] as ExcalidrawElbowArrowElement, {
|
newElementWith(h.elements[0] as ExcalidrawLinearElement, {
|
||||||
startBinding: {
|
startBinding: {
|
||||||
elementId: rect1.id,
|
elementId: rect1.id,
|
||||||
gap: 1,
|
gap: 1,
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { render } from "./test-utils";
|
|||||||
import { reseed } from "../random";
|
import { reseed } from "../random";
|
||||||
import { UI, Keyboard, Pointer } from "./helpers/ui";
|
import { UI, Keyboard, Pointer } from "./helpers/ui";
|
||||||
import type {
|
import type {
|
||||||
ExcalidrawElbowArrowElement,
|
|
||||||
ExcalidrawFreeDrawElement,
|
ExcalidrawFreeDrawElement,
|
||||||
ExcalidrawLinearElement,
|
ExcalidrawLinearElement,
|
||||||
} from "../element/types";
|
} from "../element/types";
|
||||||
@@ -334,62 +333,6 @@ describe("arrow element", () => {
|
|||||||
expect(label.angle).toBeCloseTo(0);
|
expect(label.angle).toBeCloseTo(0);
|
||||||
expect(label.fontSize).toEqual(20);
|
expect(label.fontSize).toEqual(20);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("flips the fixed point binding on negative resize for single bindable", () => {
|
|
||||||
const rectangle = UI.createElement("rectangle", {
|
|
||||||
x: -100,
|
|
||||||
y: -75,
|
|
||||||
width: 95,
|
|
||||||
height: 100,
|
|
||||||
});
|
|
||||||
UI.clickTool("arrow");
|
|
||||||
UI.clickOnTestId("elbow-arrow");
|
|
||||||
mouse.reset();
|
|
||||||
mouse.moveTo(-5, 0);
|
|
||||||
mouse.click();
|
|
||||||
mouse.moveTo(120, 200);
|
|
||||||
mouse.click();
|
|
||||||
|
|
||||||
const arrow = h.scene.getSelectedElements(
|
|
||||||
h.state,
|
|
||||||
)[0] as ExcalidrawElbowArrowElement;
|
|
||||||
|
|
||||||
expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(1.05);
|
|
||||||
expect(arrow.startBinding?.fixedPoint?.[1]).toBeCloseTo(0.75);
|
|
||||||
|
|
||||||
UI.resize(rectangle, "se", [-200, -150]);
|
|
||||||
|
|
||||||
expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(1.05);
|
|
||||||
expect(arrow.startBinding?.fixedPoint?.[1]).toBeCloseTo(0.75);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("flips the fixed point binding on negative resize for group selection", () => {
|
|
||||||
const rectangle = UI.createElement("rectangle", {
|
|
||||||
x: -100,
|
|
||||||
y: -75,
|
|
||||||
width: 95,
|
|
||||||
height: 100,
|
|
||||||
});
|
|
||||||
UI.clickTool("arrow");
|
|
||||||
UI.clickOnTestId("elbow-arrow");
|
|
||||||
mouse.reset();
|
|
||||||
mouse.moveTo(-5, 0);
|
|
||||||
mouse.click();
|
|
||||||
mouse.moveTo(120, 200);
|
|
||||||
mouse.click();
|
|
||||||
|
|
||||||
const arrow = h.scene.getSelectedElements(
|
|
||||||
h.state,
|
|
||||||
)[0] as ExcalidrawElbowArrowElement;
|
|
||||||
|
|
||||||
expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(1.05);
|
|
||||||
expect(arrow.startBinding?.fixedPoint?.[1]).toBeCloseTo(0.75);
|
|
||||||
|
|
||||||
UI.resize([rectangle, arrow], "nw", [300, 350]);
|
|
||||||
|
|
||||||
expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(-0.144, 2);
|
|
||||||
expect(arrow.startBinding?.fixedPoint?.[1]).toBeCloseTo(0.25);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("text element", () => {
|
describe("text element", () => {
|
||||||
@@ -885,6 +828,7 @@ describe("multiple selection", () => {
|
|||||||
expect(leftBoundArrow.endBinding?.elementId).toBe(
|
expect(leftBoundArrow.endBinding?.elementId).toBe(
|
||||||
leftArrowBinding.elementId,
|
leftArrowBinding.elementId,
|
||||||
);
|
);
|
||||||
|
expect(leftBoundArrow.endBinding?.fixedPoint).toBeNull();
|
||||||
expect(leftBoundArrow.endBinding?.focus).toBe(leftArrowBinding.focus);
|
expect(leftBoundArrow.endBinding?.focus).toBe(leftArrowBinding.focus);
|
||||||
|
|
||||||
expect(rightBoundArrow.x).toBeCloseTo(210);
|
expect(rightBoundArrow.x).toBeCloseTo(210);
|
||||||
@@ -899,6 +843,7 @@ describe("multiple selection", () => {
|
|||||||
expect(rightBoundArrow.endBinding?.elementId).toBe(
|
expect(rightBoundArrow.endBinding?.elementId).toBe(
|
||||||
rightArrowBinding.elementId,
|
rightArrowBinding.elementId,
|
||||||
);
|
);
|
||||||
|
expect(rightBoundArrow.endBinding?.fixedPoint).toBeNull();
|
||||||
expect(rightBoundArrow.endBinding?.focus).toBe(rightArrowBinding.focus);
|
expect(rightBoundArrow.endBinding?.focus).toBe(rightArrowBinding.focus);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -110,8 +110,8 @@ export const debugDrawBoundingBox = (
|
|||||||
export const debugDrawBounds = (
|
export const debugDrawBounds = (
|
||||||
box: Bounds | Bounds[],
|
box: Bounds | Bounds[],
|
||||||
opts?: {
|
opts?: {
|
||||||
color?: string;
|
color: string;
|
||||||
permanent?: boolean;
|
permanent: boolean;
|
||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
(isBounds(box) ? [box] : box).forEach((bbox) =>
|
(isBounds(box) ? [box] : box).forEach((bbox) =>
|
||||||
@@ -136,7 +136,7 @@ export const debugDrawBounds = (
|
|||||||
],
|
],
|
||||||
{
|
{
|
||||||
color: opts?.color ?? "green",
|
color: opts?.color ?? "green",
|
||||||
permanent: !!opts?.permanent,
|
permanent: opts?.permanent,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3299,6 +3299,11 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/jest" "*"
|
"@types/jest" "*"
|
||||||
|
|
||||||
|
"@types/tinycolor2@1.4.6":
|
||||||
|
version "1.4.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/tinycolor2/-/tinycolor2-1.4.6.tgz#670cbc0caf4e58dd61d1e3a6f26386e473087f06"
|
||||||
|
integrity sha512-iEN8J0BoMnsWBqjVbWH/c0G0Hh7O21lpR2/+PrvAVgWdzL7eexIFm4JN/Wn10PTcmNdtS6U67r499mlWMXOxNw==
|
||||||
|
|
||||||
"@types/trusted-types@^2.0.2":
|
"@types/trusted-types@^2.0.2":
|
||||||
version "2.0.7"
|
version "2.0.7"
|
||||||
resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.7.tgz#baccb07a970b91707df3a3e8ba6896c57ead2d11"
|
resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.7.tgz#baccb07a970b91707df3a3e8ba6896c57ead2d11"
|
||||||
@@ -9976,6 +9981,11 @@ tinybench@^2.8.0:
|
|||||||
resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.9.0.tgz#103c9f8ba6d7237a47ab6dd1dcff77251863426b"
|
resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.9.0.tgz#103c9f8ba6d7237a47ab6dd1dcff77251863426b"
|
||||||
integrity sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==
|
integrity sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==
|
||||||
|
|
||||||
|
tinycolor2@1.6.0:
|
||||||
|
version "1.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.6.0.tgz#f98007460169b0263b97072c5ae92484ce02d09e"
|
||||||
|
integrity sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==
|
||||||
|
|
||||||
tinypool@^1.0.0:
|
tinypool@^1.0.0:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-1.0.1.tgz#c64233c4fac4304e109a64340178760116dbe1fe"
|
resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-1.0.1.tgz#c64233c4fac4304e109a64340178760116dbe1fe"
|
||||||
|
|||||||
Reference in New Issue
Block a user