Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0bd3a077bf |
@@ -1,7 +0,0 @@
|
|||||||
# VITE_DEBUG_DOM
|
|
||||||
# When "true", testing-library failures (waitFor / getBy*) include the full
|
|
||||||
# serialized DOM in the error message. It's off by default because it's noisy.
|
|
||||||
#
|
|
||||||
# Flip it to "true" (or use `VITE_DEBUG_DOM=true yarn test`) when you need to
|
|
||||||
# inspect the DOM of a failing test.
|
|
||||||
VITE_DEBUG_DOM=false
|
|
||||||
@@ -82,13 +82,6 @@ export default defineConfig(({ mode }) => {
|
|||||||
"../packages/fractional-indexing/src/index.ts",
|
"../packages/fractional-indexing/src/index.ts",
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
find: /^@excalidraw\/laser-pointer$/,
|
|
||||||
replacement: path.resolve(
|
|
||||||
__dirname,
|
|
||||||
"../packages/laser-pointer/src/index.ts",
|
|
||||||
),
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
|
|||||||
+1
-2
@@ -57,8 +57,7 @@
|
|||||||
"build:excalidraw": "yarn --cwd ./packages/excalidraw build:esm",
|
"build:excalidraw": "yarn --cwd ./packages/excalidraw build:esm",
|
||||||
"build:math": "yarn --cwd ./packages/math build:esm",
|
"build:math": "yarn --cwd ./packages/math build:esm",
|
||||||
"build:fractional-indexing": "yarn --cwd ./packages/fractional-indexing build:esm",
|
"build:fractional-indexing": "yarn --cwd ./packages/fractional-indexing build:esm",
|
||||||
"build:laser-pointer": "yarn --cwd ./packages/laser-pointer build:esm",
|
"build:packages": "yarn build:common && yarn build:fractional-indexing && yarn build:math && yarn build:element && yarn build:excalidraw",
|
||||||
"build:packages": "yarn build:common && yarn build:fractional-indexing && yarn build:laser-pointer && yarn build:math && yarn build:element && yarn build:excalidraw",
|
|
||||||
"build:version": "yarn --cwd ./excalidraw-app build:version",
|
"build:version": "yarn --cwd ./excalidraw-app build:version",
|
||||||
"build": "yarn --cwd ./excalidraw-app build",
|
"build": "yarn --cwd ./excalidraw-app build",
|
||||||
"build:preview": "yarn --cwd ./excalidraw-app build:preview",
|
"build:preview": "yarn --cwd ./excalidraw-app build:preview",
|
||||||
|
|||||||
@@ -404,47 +404,11 @@ export const ROUGHNESS = {
|
|||||||
cartoonist: 2,
|
cartoonist: 2,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type StrokeWidthKey = "thin" | "medium" | "bold";
|
export const STROKE_WIDTH = {
|
||||||
|
|
||||||
export const STROKE_WIDTH_KEYS: readonly StrokeWidthKey[] = [
|
|
||||||
"thin",
|
|
||||||
"medium",
|
|
||||||
"bold",
|
|
||||||
];
|
|
||||||
|
|
||||||
export const STROKE_WIDTH: Readonly<
|
|
||||||
Record<StrokeWidthKey | "extraBold", ExcalidrawElement["strokeWidth"]>
|
|
||||||
> = {
|
|
||||||
thin: 1,
|
thin: 1,
|
||||||
medium: 2,
|
|
||||||
bold: 4,
|
|
||||||
extraBold: 8, // unused (may be introduced in the future)
|
|
||||||
};
|
|
||||||
|
|
||||||
// freedraw schema 2.0 uses thinner stroke, but to maintain backwards and
|
|
||||||
// forwards compatibility, instead of changing the shape renderer, we scale
|
|
||||||
// the stroke width by 1/2 (previous, thin was 1, medium 2 etc.)
|
|
||||||
//
|
|
||||||
// note that in the UI, STROKE_WIDTH.thin == FREEDRAW_STROKE_WIDTH.thin still
|
|
||||||
export const FREEDRAW_STROKE_WIDTH: Readonly<
|
|
||||||
Record<StrokeWidthKey | "extraBold", ExcalidrawElement["strokeWidth"]>
|
|
||||||
> = {
|
|
||||||
thin: 0.5,
|
|
||||||
medium: 1,
|
|
||||||
bold: 2,
|
bold: 2,
|
||||||
extraBold: 4, // legacy (may be used again in the future)
|
extraBold: 4,
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
export const getStrokeWidthByKey = (
|
|
||||||
elementType: ExcalidrawElement["type"],
|
|
||||||
strokeWidthKey: StrokeWidthKey,
|
|
||||||
): ExcalidrawElement["strokeWidth"] => {
|
|
||||||
return elementType === "freedraw"
|
|
||||||
? FREEDRAW_STROKE_WIDTH[strokeWidthKey]
|
|
||||||
: STROKE_WIDTH[strokeWidthKey];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const DEFAULT_ELEMENT_STROKE_WIDTH_KEY: StrokeWidthKey = "medium";
|
|
||||||
|
|
||||||
export const DEFAULT_ELEMENT_PROPS: {
|
export const DEFAULT_ELEMENT_PROPS: {
|
||||||
strokeColor: ExcalidrawElement["strokeColor"];
|
strokeColor: ExcalidrawElement["strokeColor"];
|
||||||
@@ -459,7 +423,7 @@ export const DEFAULT_ELEMENT_PROPS: {
|
|||||||
strokeColor: COLOR_PALETTE.black,
|
strokeColor: COLOR_PALETTE.black,
|
||||||
backgroundColor: COLOR_PALETTE.transparent,
|
backgroundColor: COLOR_PALETTE.transparent,
|
||||||
fillStyle: "solid",
|
fillStyle: "solid",
|
||||||
strokeWidth: STROKE_WIDTH[DEFAULT_ELEMENT_STROKE_WIDTH_KEY],
|
strokeWidth: 2,
|
||||||
strokeStyle: "solid",
|
strokeStyle: "solid",
|
||||||
roughness: ROUGHNESS.artist,
|
roughness: ROUGHNESS.artist,
|
||||||
opacity: 100,
|
opacity: 100,
|
||||||
@@ -550,6 +514,3 @@ export const BIND_MODE_TIMEOUT = 700; // ms
|
|||||||
export const MOBILE_ACTION_BUTTON_BG = {
|
export const MOBILE_ACTION_BUTTON_BG = {
|
||||||
background: "var(--mobile-action-button-bg)",
|
background: "var(--mobile-action-button-bg)",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const DEFAULT_STROKE_STREAMLINE = 0.5;
|
|
||||||
export const DEFAULT_STROKE_STREAMLINE_PRECISE = 0.2;
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
{
|
{
|
||||||
"extends": "../tsconfig.base.json",
|
"extends": "../tsconfig.base.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "./dist/types",
|
"outDir": "./dist/types"
|
||||||
"rootDir": "../"
|
|
||||||
},
|
},
|
||||||
"include": ["src/**/*", "global.d.ts"],
|
"include": ["src/**/*", "global.d.ts"],
|
||||||
"exclude": ["**/*.test.*", "tests", "types", "examples", "dist"]
|
"exclude": ["**/*.test.*", "tests", "types", "examples", "dist"]
|
||||||
|
|||||||
@@ -643,13 +643,10 @@ const getBindingStrategyForDraggingBindingElementEndpoints_simple = (
|
|||||||
let start: BindingStrategy = { mode: undefined };
|
let start: BindingStrategy = { mode: undefined };
|
||||||
let end: BindingStrategy = { mode: undefined };
|
let end: BindingStrategy = { mode: undefined };
|
||||||
|
|
||||||
if (arrow.points.length < 2) {
|
invariant(
|
||||||
console.error(
|
arrow.points.length > 1,
|
||||||
"Attempting to bind a linear element with less than 2 points",
|
"Do not attempt to bind linear elements with a single point",
|
||||||
);
|
);
|
||||||
// a single-point can't be bound -> cancel
|
|
||||||
return { start: { mode: undefined }, end: { mode: undefined } };
|
|
||||||
}
|
|
||||||
|
|
||||||
// If none of the ends are dragged, we don't change anything
|
// If none of the ends are dragged, we don't change anything
|
||||||
if (!startDragged && !endDragged) {
|
if (!startDragged && !endDragged) {
|
||||||
@@ -893,13 +890,10 @@ const getBindingStrategyForDraggingBindingElementEndpoints_complex = (
|
|||||||
let start: BindingStrategy = { mode: undefined };
|
let start: BindingStrategy = { mode: undefined };
|
||||||
let end: BindingStrategy = { mode: undefined };
|
let end: BindingStrategy = { mode: undefined };
|
||||||
|
|
||||||
if (arrow.points.length < 2) {
|
invariant(
|
||||||
console.error(
|
arrow.points.length > 1,
|
||||||
"Attempting to bind a linear element with less than 2 points",
|
"Do not attempt to bind linear elements with a single point",
|
||||||
);
|
);
|
||||||
// a single-point can't be bound -> cancel
|
|
||||||
return { start: { mode: undefined }, end: { mode: undefined } };
|
|
||||||
}
|
|
||||||
|
|
||||||
// If none of the ends are dragged, we don't change anything
|
// If none of the ends are dragged, we don't change anything
|
||||||
if (!startDragged && !endDragged) {
|
if (!startDragged && !endDragged) {
|
||||||
|
|||||||
@@ -38,8 +38,6 @@ export const hasStrokeStyle = (type: ElementOrToolType) =>
|
|||||||
type === "arrow" ||
|
type === "arrow" ||
|
||||||
type === "line";
|
type === "line";
|
||||||
|
|
||||||
export const hasFreedrawMode = (type: ElementOrToolType) => type === "freedraw";
|
|
||||||
|
|
||||||
export const canChangeRoundness = (type: ElementOrToolType) =>
|
export const canChangeRoundness = (type: ElementOrToolType) =>
|
||||||
type === "rectangle" ||
|
type === "rectangle" ||
|
||||||
type === "iframe" ||
|
type === "iframe" ||
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import {
|
|||||||
DEFAULT_FONT_SIZE,
|
DEFAULT_FONT_SIZE,
|
||||||
DEFAULT_TEXT_ALIGN,
|
DEFAULT_TEXT_ALIGN,
|
||||||
DEFAULT_VERTICAL_ALIGN,
|
DEFAULT_VERTICAL_ALIGN,
|
||||||
DEFAULT_STROKE_STREAMLINE,
|
|
||||||
VERTICAL_ALIGN,
|
VERTICAL_ALIGN,
|
||||||
randomInteger,
|
randomInteger,
|
||||||
randomId,
|
randomId,
|
||||||
@@ -445,7 +444,6 @@ export const newFreeDrawElement = (
|
|||||||
type: "freedraw";
|
type: "freedraw";
|
||||||
points?: ExcalidrawFreeDrawElement["points"];
|
points?: ExcalidrawFreeDrawElement["points"];
|
||||||
simulatePressure: boolean;
|
simulatePressure: boolean;
|
||||||
strokeOptions?: ExcalidrawFreeDrawElement["strokeOptions"];
|
|
||||||
pressures?: ExcalidrawFreeDrawElement["pressures"];
|
pressures?: ExcalidrawFreeDrawElement["pressures"];
|
||||||
} & ElementConstructorOpts,
|
} & ElementConstructorOpts,
|
||||||
): NonDeleted<ExcalidrawFreeDrawElement> => {
|
): NonDeleted<ExcalidrawFreeDrawElement> => {
|
||||||
@@ -454,10 +452,6 @@ export const newFreeDrawElement = (
|
|||||||
points: opts.points || [],
|
points: opts.points || [],
|
||||||
pressures: opts.pressures || [],
|
pressures: opts.pressures || [],
|
||||||
simulatePressure: opts.simulatePressure,
|
simulatePressure: opts.simulatePressure,
|
||||||
strokeOptions: opts.strokeOptions ?? {
|
|
||||||
variability: "variable",
|
|
||||||
streamline: DEFAULT_STROKE_STREAMLINE,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -889,10 +889,8 @@ export const renderElement = (
|
|||||||
case "embeddable": {
|
case "embeddable": {
|
||||||
if (renderConfig.isExporting) {
|
if (renderConfig.isExporting) {
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
|
||||||
const centerX = (x1 + x2) / 2;
|
const cx = (x1 + x2) / 2 + appState.scrollX;
|
||||||
const centerY = (y1 + y2) / 2;
|
const cy = (y1 + y2) / 2 + appState.scrollY;
|
||||||
const cx = centerX + appState.scrollX;
|
|
||||||
const cy = centerY + appState.scrollY;
|
|
||||||
let shiftX = (x2 - x1) / 2 - (element.x - x1);
|
let shiftX = (x2 - x1) / 2 - (element.x - x1);
|
||||||
let shiftY = (y2 - y1) / 2 - (element.y - y1);
|
let shiftY = (y2 - y1) / 2 - (element.y - y1);
|
||||||
if (isTextElement(element)) {
|
if (isTextElement(element)) {
|
||||||
@@ -914,49 +912,64 @@ export const renderElement = (
|
|||||||
const boundTextElement = getBoundTextElement(element, elementsMap);
|
const boundTextElement = getBoundTextElement(element, elementsMap);
|
||||||
|
|
||||||
if (isArrowElement(element) && boundTextElement) {
|
if (isArrowElement(element) && boundTextElement) {
|
||||||
// Draw arrow directly as vector and clear label hole separately.
|
const tempCanvas = document.createElement("canvas");
|
||||||
// This avoids temp-canvas bitmap blit which introduces resampling blur.
|
|
||||||
|
const tempCanvasContext = tempCanvas.getContext("2d")!;
|
||||||
|
|
||||||
|
// Take max dimensions of arrow canvas so that when canvas is rotated
|
||||||
|
// the arrow doesn't get clipped
|
||||||
|
const maxDim = Math.max(distance(x1, x2), distance(y1, y2));
|
||||||
|
const padding = getCanvasPadding(element);
|
||||||
|
tempCanvas.width =
|
||||||
|
maxDim * appState.exportScale + padding * 10 * appState.exportScale;
|
||||||
|
tempCanvas.height =
|
||||||
|
maxDim * appState.exportScale + padding * 10 * appState.exportScale;
|
||||||
|
|
||||||
|
tempCanvasContext.translate(
|
||||||
|
tempCanvas.width / 2,
|
||||||
|
tempCanvas.height / 2,
|
||||||
|
);
|
||||||
|
tempCanvasContext.scale(appState.exportScale, appState.exportScale);
|
||||||
|
|
||||||
|
// Shift the canvas to left most point of the arrow
|
||||||
shiftX = element.width / 2 - (element.x - x1);
|
shiftX = element.width / 2 - (element.x - x1);
|
||||||
shiftY = element.height / 2 - (element.y - y1);
|
shiftY = element.height / 2 - (element.y - y1);
|
||||||
|
|
||||||
context.save();
|
tempCanvasContext.rotate(element.angle);
|
||||||
context.rotate(element.angle);
|
const tempRc = rough.canvas(tempCanvas);
|
||||||
context.translate(-shiftX, -shiftY);
|
|
||||||
drawElementOnCanvas(element, rc, context, renderConfig);
|
|
||||||
context.restore();
|
|
||||||
|
|
||||||
|
tempCanvasContext.translate(-shiftX, -shiftY);
|
||||||
|
|
||||||
|
drawElementOnCanvas(element, tempRc, tempCanvasContext, renderConfig);
|
||||||
|
|
||||||
|
tempCanvasContext.translate(shiftX, shiftY);
|
||||||
|
|
||||||
|
tempCanvasContext.rotate(-element.angle);
|
||||||
|
|
||||||
|
// Shift the canvas to center of bound text
|
||||||
const [, , , , boundTextCx, boundTextCy] = getElementAbsoluteCoords(
|
const [, , , , boundTextCx, boundTextCy] = getElementAbsoluteCoords(
|
||||||
boundTextElement,
|
boundTextElement,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
);
|
);
|
||||||
const holeX =
|
const boundTextShiftX = (x1 + x2) / 2 - boundTextCx;
|
||||||
boundTextCx -
|
const boundTextShiftY = (y1 + y2) / 2 - boundTextCy;
|
||||||
centerX -
|
tempCanvasContext.translate(-boundTextShiftX, -boundTextShiftY);
|
||||||
boundTextElement.width / 2 -
|
|
||||||
BOUND_TEXT_PADDING;
|
|
||||||
const holeY =
|
|
||||||
boundTextCy -
|
|
||||||
centerY -
|
|
||||||
boundTextElement.height / 2 -
|
|
||||||
BOUND_TEXT_PADDING;
|
|
||||||
const holeWidth = boundTextElement.width + BOUND_TEXT_PADDING * 2;
|
|
||||||
const holeHeight = boundTextElement.height + BOUND_TEXT_PADDING * 2;
|
|
||||||
|
|
||||||
const isTransparentHole =
|
// Clear the bound text area
|
||||||
"viewBackgroundColor" in appState &&
|
tempCanvasContext.clearRect(
|
||||||
(appState.viewBackgroundColor === "transparent" ||
|
-boundTextElement.width / 2,
|
||||||
!appState.viewBackgroundColor);
|
-boundTextElement.height / 2,
|
||||||
if (!isTransparentHole) {
|
boundTextElement.width,
|
||||||
context.save();
|
boundTextElement.height,
|
||||||
context.fillStyle = applyDarkModeFilter(
|
);
|
||||||
renderConfig.canvasBackgroundColor,
|
context.scale(1 / appState.exportScale, 1 / appState.exportScale);
|
||||||
renderConfig.theme === THEME.DARK,
|
context.drawImage(
|
||||||
);
|
tempCanvas,
|
||||||
context.fillRect(holeX, holeY, holeWidth, holeHeight);
|
-tempCanvas.width / 2,
|
||||||
context.restore();
|
-tempCanvas.height / 2,
|
||||||
} else {
|
tempCanvas.width,
|
||||||
context.clearRect(holeX, holeY, holeWidth, holeHeight);
|
tempCanvas.height,
|
||||||
}
|
);
|
||||||
} else {
|
} else {
|
||||||
context.rotate(element.angle);
|
context.rotate(element.angle);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { simplify } from "points-on-curve";
|
import { simplify } from "points-on-curve";
|
||||||
import { getStroke } from "perfect-freehand";
|
import { getStroke } from "perfect-freehand";
|
||||||
import { LaserPointer } from "@excalidraw/laser-pointer";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
type GeometricShape,
|
type GeometricShape,
|
||||||
@@ -25,7 +24,6 @@ import {
|
|||||||
COLOR_PALETTE,
|
COLOR_PALETTE,
|
||||||
LINE_POLYGON_POINT_MERGE_DISTANCE,
|
LINE_POLYGON_POINT_MERGE_DISTANCE,
|
||||||
applyDarkModeFilter,
|
applyDarkModeFilter,
|
||||||
DEFAULT_STROKE_STREAMLINE,
|
|
||||||
} from "@excalidraw/common";
|
} from "@excalidraw/common";
|
||||||
|
|
||||||
import { RoughGenerator } from "roughjs/bin/generator";
|
import { RoughGenerator } from "roughjs/bin/generator";
|
||||||
@@ -1173,87 +1171,27 @@ const getFreeDrawSvgPath = (element: ExcalidrawFreeDrawElement) => {
|
|||||||
) as SVGPathString;
|
) as SVGPathString;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
export const getFreedrawOutlinePoints = (
|
||||||
* Freedraw stroke geometry tuning constants.
|
|
||||||
*
|
|
||||||
* These factors are not derived analytically — they were tuned empirically by
|
|
||||||
* visually comparing rendered strokes until they matched the desired feel.
|
|
||||||
* Treat them as magic numbers backed by visual verification.
|
|
||||||
*/
|
|
||||||
const VARIABLE_WIDTH_FREEDRAW = {
|
|
||||||
/** Stroke size relative to `strokeWidth` for pressure-sensitive strokes. */
|
|
||||||
SIZE_FACTOR: 4.25,
|
|
||||||
THINNING: 0.6,
|
|
||||||
SMOOTHING: 0.5,
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
const CONSTANT_WIDTH_FREEDRAW = {
|
|
||||||
/** Stroke size relative to `strokeWidth` for uniform (laser) strokes. */
|
|
||||||
SIZE_FACTOR: 1.4,
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
const getFreedrawStreamline = (element: ExcalidrawFreeDrawElement) =>
|
|
||||||
element.strokeOptions?.streamline ?? DEFAULT_STROKE_STREAMLINE;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pressure-sensitive (variable width) freedraw outline, rendered with
|
|
||||||
* perfect-freehand. This is the original Excalidraw freedraw look.
|
|
||||||
*/
|
|
||||||
const getVariableWidthFreedrawOutline = (
|
|
||||||
element: ExcalidrawFreeDrawElement,
|
element: ExcalidrawFreeDrawElement,
|
||||||
): [number, number][] => {
|
) => {
|
||||||
// If input points are empty (should they ever be?) return a dot
|
// If input points are empty (should they ever be?) return a dot
|
||||||
const inputPoints = element.simulatePressure
|
const inputPoints = element.simulatePressure
|
||||||
? element.points
|
? element.points
|
||||||
: element.points.length
|
: element.points.length
|
||||||
? element.points.map(
|
? element.points.map(([x, y], i) => [x, y, element.pressures[i]])
|
||||||
([x, y], i) => [x, y, element.pressures[i]] as [number, number, number],
|
|
||||||
)
|
|
||||||
: [[0, 0, 0.5]];
|
: [[0, 0, 0.5]];
|
||||||
|
|
||||||
return getStroke(inputPoints as number[][], {
|
return getStroke(inputPoints as number[][], {
|
||||||
simulatePressure: element.simulatePressure,
|
simulatePressure: element.simulatePressure,
|
||||||
size: element.strokeWidth * VARIABLE_WIDTH_FREEDRAW.SIZE_FACTOR,
|
size: element.strokeWidth * 4.25,
|
||||||
thinning: VARIABLE_WIDTH_FREEDRAW.THINNING,
|
thinning: 0.6,
|
||||||
smoothing: VARIABLE_WIDTH_FREEDRAW.SMOOTHING,
|
smoothing: 0.5,
|
||||||
streamline: getFreedrawStreamline(element),
|
streamline: 0.5,
|
||||||
easing: (t) => Math.sin((t * Math.PI) / 2), // https://easings.net/#easeOutSine
|
easing: (t) => Math.sin((t * Math.PI) / 2), // https://easings.net/#easeOutSine
|
||||||
last: true,
|
last: true,
|
||||||
}) as [number, number][];
|
}) as [number, number][];
|
||||||
};
|
};
|
||||||
|
|
||||||
const createLaserPointer = (element: ExcalidrawFreeDrawElement) =>
|
|
||||||
new LaserPointer({
|
|
||||||
size: element.strokeWidth * CONSTANT_WIDTH_FREEDRAW.SIZE_FACTOR,
|
|
||||||
streamline: getFreedrawStreamline(element),
|
|
||||||
simplify: 0,
|
|
||||||
sizeMapping: (details) => Math.max(0.1, details.pressure),
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uniform (constant width) freedraw outline, rendered with the laser-pointer
|
|
||||||
* geometry. Pressure is pinned to 1 so the stroke keeps a constant width.
|
|
||||||
*/
|
|
||||||
const getConstantWidthFreedrawOutline = (
|
|
||||||
element: ExcalidrawFreeDrawElement,
|
|
||||||
): [number, number][] => {
|
|
||||||
const laserPointer = createLaserPointer(element);
|
|
||||||
element.points.map(([x, y]) => laserPointer.addPoint([x, y, 1]));
|
|
||||||
|
|
||||||
return laserPointer
|
|
||||||
.getStrokeOutline()
|
|
||||||
.map(([x, y]) => [x, y] as [number, number]);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getFreedrawOutlinePoints = (
|
|
||||||
element: ExcalidrawFreeDrawElement,
|
|
||||||
): [number, number][] => {
|
|
||||||
// Unknown/absent variability falls back to the original variable rendering.
|
|
||||||
return element.strokeOptions?.variability === "constant"
|
|
||||||
? getConstantWidthFreedrawOutline(element)
|
|
||||||
: getVariableWidthFreedrawOutline(element);
|
|
||||||
};
|
|
||||||
|
|
||||||
const med = (A: number[], B: number[]) => {
|
const med = (A: number[], B: number[]) => {
|
||||||
return [(A[0] + B[0]) / 2, (A[1] + B[1]) / 2];
|
return [(A[0] + B[0]) / 2, (A[1] + B[1]) / 2];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -384,20 +384,12 @@ export type ExcalidrawElbowArrowElement = Merge<
|
|||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export type StrokeVariability = "variable" | "constant";
|
|
||||||
|
|
||||||
export type StrokeOptions = Readonly<{
|
|
||||||
variability: StrokeVariability;
|
|
||||||
streamline: number;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
export type ExcalidrawFreeDrawElement = _ExcalidrawElementBase &
|
export type ExcalidrawFreeDrawElement = _ExcalidrawElementBase &
|
||||||
Readonly<{
|
Readonly<{
|
||||||
type: "freedraw";
|
type: "freedraw";
|
||||||
points: readonly LocalPoint[];
|
points: readonly LocalPoint[];
|
||||||
pressures: readonly number[];
|
pressures: readonly number[];
|
||||||
simulatePressure: boolean;
|
simulatePressure: boolean;
|
||||||
strokeOptions: StrokeOptions;
|
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type FileId = string & { _brand: "FileId" };
|
export type FileId = string & { _brand: "FileId" };
|
||||||
|
|||||||
@@ -178,64 +178,6 @@ describe("binding for simple arrows", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("self-binding (both ends to the same element) single-click finalize", () => {
|
|
||||||
// rect spans x:200..400, y:200..400; orbit ring is ~15px outside the outline
|
|
||||||
const INSIDE: [number, number] = [250, 250];
|
|
||||||
const ORBIT_LEFT: [number, number] = [187, 300];
|
|
||||||
const ORBIT_RIGHT: [number, number] = [413, 300];
|
|
||||||
const MIDDLE: [number, number] = [550, 100];
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
mouse.reset();
|
|
||||||
await act(() => setLanguage(defaultLang));
|
|
||||||
await render(<Excalidraw handleKeyboardGlobally={true} />);
|
|
||||||
UI.createElement("rectangle", {
|
|
||||||
x: 200,
|
|
||||||
y: 200,
|
|
||||||
width: 200,
|
|
||||||
height: 200,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const drawSelfArrow = (start: [number, number], end: [number, number]) => {
|
|
||||||
UI.clickTool("arrow");
|
|
||||||
mouse.reset();
|
|
||||||
mouse.clickAt(...start);
|
|
||||||
mouse.moveTo(...MIDDLE);
|
|
||||||
mouse.clickAt(...MIDDLE); // commit a middle point so it's a multi-point arrow
|
|
||||||
mouse.moveTo(...end);
|
|
||||||
mouse.clickAt(...end); // single click at the end
|
|
||||||
};
|
|
||||||
|
|
||||||
it("orbit -> orbit finalizes on a single click", () => {
|
|
||||||
drawSelfArrow(ORBIT_LEFT, ORBIT_RIGHT);
|
|
||||||
|
|
||||||
const arrow = h.elements[h.elements.length - 1] as ExcalidrawArrowElement;
|
|
||||||
expect(h.state.multiElement).toBe(null);
|
|
||||||
expect(h.state.activeTool.type).toBe("selection");
|
|
||||||
expect(arrow.startBinding?.elementId).toBe(arrow.endBinding?.elementId);
|
|
||||||
expect(arrow.endBinding?.elementId).not.toBe(undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("inside -> orbit finalizes on a single click", () => {
|
|
||||||
drawSelfArrow(INSIDE, ORBIT_RIGHT);
|
|
||||||
|
|
||||||
const arrow = h.elements[h.elements.length - 1] as ExcalidrawArrowElement;
|
|
||||||
expect(h.state.multiElement).toBe(null);
|
|
||||||
expect(h.state.activeTool.type).toBe("selection");
|
|
||||||
expect(arrow.startBinding?.elementId).toBe(arrow.endBinding?.elementId);
|
|
||||||
expect(arrow.endBinding?.elementId).not.toBe(undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("inside -> inside keep in multi-point mode (no single-click finalize)", () => {
|
|
||||||
drawSelfArrow(INSIDE, [INSIDE[0] + 50, INSIDE[1] + 50]); // end dropped inside the rect
|
|
||||||
|
|
||||||
// ambiguous → must be confirmed with a second click, so still in progress
|
|
||||||
expect(h.state.multiElement).not.toBe(null);
|
|
||||||
expect(h.state.activeTool.type).toBe("arrow");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("when arrow is outside of shape", () => {
|
describe("when arrow is outside of shape", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
mouse.reset();
|
mouse.reset();
|
||||||
@@ -461,7 +403,6 @@ describe("binding for simple arrows", () => {
|
|||||||
mouse.moveTo(340, 251);
|
mouse.moveTo(340, 251);
|
||||||
mouse.moveTo(410, 251);
|
mouse.moveTo(410, 251);
|
||||||
mouse.clickAt(410, 251);
|
mouse.clickAt(410, 251);
|
||||||
mouse.clickAt(410, 251);
|
|
||||||
const arrow = h.elements[h.elements.length - 1] as any;
|
const arrow = h.elements[h.elements.length - 1] as any;
|
||||||
|
|
||||||
expect(arrow.startBinding?.elementId).toBe(rectLeft.id);
|
expect(arrow.startBinding?.elementId).toBe(rectLeft.id);
|
||||||
@@ -506,7 +447,6 @@ describe("binding for simple arrows", () => {
|
|||||||
mouse.moveTo(350, 251);
|
mouse.moveTo(350, 251);
|
||||||
mouse.moveTo(410, 251);
|
mouse.moveTo(410, 251);
|
||||||
mouse.clickAt(410, 251);
|
mouse.clickAt(410, 251);
|
||||||
mouse.clickAt(410, 251);
|
|
||||||
|
|
||||||
const arrow = API.getSelectedElement() as ExcalidrawArrowElement;
|
const arrow = API.getSelectedElement() as ExcalidrawArrowElement;
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
Excalidraw,
|
Excalidraw,
|
||||||
} from "@excalidraw/excalidraw";
|
} from "@excalidraw/excalidraw";
|
||||||
import { arrayToMap } from "@excalidraw/common";
|
import { arrayToMap } from "@excalidraw/common";
|
||||||
|
import { degreesToRadians, pointFrom } from "@excalidraw/math";
|
||||||
|
|
||||||
import { API } from "@excalidraw/excalidraw/tests/helpers/api";
|
import { API } from "@excalidraw/excalidraw/tests/helpers/api";
|
||||||
import { Keyboard, Pointer, UI } from "@excalidraw/excalidraw/tests/helpers/ui";
|
import { Keyboard, Pointer, UI } from "@excalidraw/excalidraw/tests/helpers/ui";
|
||||||
@@ -14,6 +15,8 @@ import {
|
|||||||
|
|
||||||
import { getSelectedElements } from "@excalidraw/excalidraw/scene";
|
import { getSelectedElements } from "@excalidraw/excalidraw/scene";
|
||||||
|
|
||||||
|
import type { Degrees } from "@excalidraw/math";
|
||||||
|
|
||||||
import { elementOverlapsWithFrame } from "../src/frame";
|
import { elementOverlapsWithFrame } from "../src/frame";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@@ -154,6 +157,108 @@ describe("adding elements to frames", () => {
|
|||||||
).toBe(true);
|
).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("uses precise geometry (not the bounding box) to detect frame overlap", () => {
|
||||||
|
// a 300x200 frame at the origin, with shapes positioned just outside each
|
||||||
|
// corner so that their bounding box clips the frame but their actual
|
||||||
|
// outline does not. The precise `isElementIntersectingFrame` must report
|
||||||
|
// no overlap.
|
||||||
|
const overlapFrame = API.createElement({
|
||||||
|
type: "frame",
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 300,
|
||||||
|
height: 200,
|
||||||
|
});
|
||||||
|
// clips the bottom-right corner; the rhombus tip stays out
|
||||||
|
const diamond = API.createElement({
|
||||||
|
type: "diamond",
|
||||||
|
x: 290,
|
||||||
|
y: 175,
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
});
|
||||||
|
// clips the bottom-left corner; rotated (NOT 45°, which would degenerate
|
||||||
|
// into an axis-aligned square whose shape fills its AABB)
|
||||||
|
const diamondRotated = API.createElement({
|
||||||
|
type: "diamond",
|
||||||
|
x: -50,
|
||||||
|
y: 195,
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
angle: degreesToRadians(45 as Degrees),
|
||||||
|
});
|
||||||
|
// clips the top-left corner; the curve doesn't reach the corner
|
||||||
|
const ellipse = API.createElement({
|
||||||
|
type: "ellipse",
|
||||||
|
x: -90,
|
||||||
|
y: -90,
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
});
|
||||||
|
// clips the top edge; the polyline weaves outside the frame
|
||||||
|
const line = API.createElement({
|
||||||
|
type: "line",
|
||||||
|
x: 130,
|
||||||
|
y: -20,
|
||||||
|
width: 293,
|
||||||
|
height: 183,
|
||||||
|
points: [
|
||||||
|
pointFrom(0, 0),
|
||||||
|
pointFrom(-165, -96),
|
||||||
|
pointFrom(-293, -16),
|
||||||
|
pointFrom(-162, 87),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
// clips the bottom-left corner; the stroke stays outside
|
||||||
|
const freedraw = API.createElement({
|
||||||
|
type: "freedraw",
|
||||||
|
x: -15,
|
||||||
|
y: 180,
|
||||||
|
width: 207,
|
||||||
|
height: 209,
|
||||||
|
points: [
|
||||||
|
pointFrom(0, 0),
|
||||||
|
pointFrom(-69, 99),
|
||||||
|
pointFrom(-33, 183),
|
||||||
|
pointFrom(48, 209),
|
||||||
|
pointFrom(124, 195),
|
||||||
|
pointFrom(138, 145),
|
||||||
|
pointFrom(123, 85),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
// clips the top-right corner, but its rounded corner is cut away from
|
||||||
|
// exactly that corner — so its actual outline stays outside
|
||||||
|
const roundedRect = API.createElement({
|
||||||
|
type: "rectangle",
|
||||||
|
x: 295,
|
||||||
|
y: -100,
|
||||||
|
width: 160,
|
||||||
|
height: 110,
|
||||||
|
roundness: { type: 3 },
|
||||||
|
});
|
||||||
|
|
||||||
|
API.setElements([
|
||||||
|
overlapFrame,
|
||||||
|
diamond,
|
||||||
|
diamondRotated,
|
||||||
|
ellipse,
|
||||||
|
line,
|
||||||
|
freedraw,
|
||||||
|
roundedRect,
|
||||||
|
]);
|
||||||
|
const elementsMap = arrayToMap(h.elements);
|
||||||
|
const f = overlapFrame as ExcalidrawFrameLikeElement;
|
||||||
|
|
||||||
|
expect(elementOverlapsWithFrame(diamond, f, elementsMap)).toBe(false);
|
||||||
|
expect(elementOverlapsWithFrame(diamondRotated, f, elementsMap)).toBe(
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
expect(elementOverlapsWithFrame(ellipse, f, elementsMap)).toBe(false);
|
||||||
|
expect(elementOverlapsWithFrame(line, f, elementsMap)).toBe(false);
|
||||||
|
expect(elementOverlapsWithFrame(freedraw, f, elementsMap)).toBe(false);
|
||||||
|
expect(elementOverlapsWithFrame(roundedRect, f, elementsMap)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
it("should not add a newly created element to a frame behind a non-frame element", () => {
|
it("should not add a newly created element to a frame behind a non-frame element", () => {
|
||||||
const cover = API.createElement({
|
const cover = API.createElement({
|
||||||
id: "cover",
|
id: "cover",
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"extends": "../tsconfig.base.json",
|
"extends": "../tsconfig.base.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"rootDir": "../",
|
|
||||||
"outDir": "./dist/types"
|
"outDir": "./dist/types"
|
||||||
},
|
},
|
||||||
"include": ["src/**/*", "global.d.ts"],
|
"include": ["src/**/*", "global.d.ts"],
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import {
|
|||||||
VERTICAL_ALIGN,
|
VERTICAL_ALIGN,
|
||||||
arrayToMap,
|
arrayToMap,
|
||||||
getFontString,
|
getFontString,
|
||||||
getStrokeWidthByKey,
|
|
||||||
} from "@excalidraw/common";
|
} from "@excalidraw/common";
|
||||||
import {
|
import {
|
||||||
getOriginalContainerHeightFromCache,
|
getOriginalContainerHeightFromCache,
|
||||||
@@ -250,10 +249,7 @@ export const actionWrapTextInContainer = register({
|
|||||||
fillStyle: appState.currentItemFillStyle,
|
fillStyle: appState.currentItemFillStyle,
|
||||||
strokeColor: appState.currentItemStrokeColor,
|
strokeColor: appState.currentItemStrokeColor,
|
||||||
roughness: appState.currentItemRoughness,
|
roughness: appState.currentItemRoughness,
|
||||||
strokeWidth: getStrokeWidthByKey(
|
strokeWidth: appState.currentItemStrokeWidth,
|
||||||
"rectangle",
|
|
||||||
appState.currentItemStrokeWidthKey,
|
|
||||||
),
|
|
||||||
strokeStyle: appState.currentItemStrokeStyle,
|
strokeStyle: appState.currentItemStrokeStyle,
|
||||||
roundness:
|
roundness:
|
||||||
appState.currentItemRoundness === "round"
|
appState.currentItemRoundness === "round"
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ export const actionFinalize = register<FormData>({
|
|||||||
label: "",
|
label: "",
|
||||||
trackEvent: false,
|
trackEvent: false,
|
||||||
perform: (elements, appState, data, app) => {
|
perform: (elements, appState, data, app) => {
|
||||||
let shouldCommit = true;
|
|
||||||
let newElements = elements;
|
let newElements = elements;
|
||||||
const { interactiveCanvas, focusContainer, scene } = app;
|
const { interactiveCanvas, focusContainer, scene } = app;
|
||||||
const elementsMap = scene.getNonDeletedElementsMap();
|
const elementsMap = scene.getNonDeletedElementsMap();
|
||||||
@@ -223,44 +222,9 @@ export const actionFinalize = register<FormData>({
|
|||||||
!lastCommittedPoint ||
|
!lastCommittedPoint ||
|
||||||
points[points.length - 1] !== lastCommittedPoint
|
points[points.length - 1] !== lastCommittedPoint
|
||||||
) {
|
) {
|
||||||
shouldCommit = false;
|
|
||||||
scene.mutateElement(element, {
|
scene.mutateElement(element, {
|
||||||
points: element.points.slice(0, -1),
|
points: element.points.slice(0, -1),
|
||||||
});
|
});
|
||||||
if (
|
|
||||||
isBindingElement(element) &&
|
|
||||||
element.endBinding &&
|
|
||||||
// after slicing the trailing point a <2-point arrow may be left
|
|
||||||
element.points.length > 1
|
|
||||||
) {
|
|
||||||
const newArrow = !!appState.newElement;
|
|
||||||
const draggedPoints: PointsPositionUpdates = new Map([
|
|
||||||
[
|
|
||||||
element.points.length - 1,
|
|
||||||
{
|
|
||||||
point: element.points[element.points.length - 1],
|
|
||||||
isDragging: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
const globalPoint =
|
|
||||||
LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
|
||||||
element,
|
|
||||||
-1,
|
|
||||||
elementsMap,
|
|
||||||
);
|
|
||||||
bindOrUnbindBindingElement(
|
|
||||||
element,
|
|
||||||
draggedPoints,
|
|
||||||
globalPoint[0],
|
|
||||||
globalPoint[1],
|
|
||||||
scene,
|
|
||||||
appState,
|
|
||||||
{
|
|
||||||
newArrow,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -380,9 +344,7 @@ export const actionFinalize = register<FormData>({
|
|||||||
selectedLinearElement,
|
selectedLinearElement,
|
||||||
},
|
},
|
||||||
// TODO: #7348 we should not capture everything, but if we don't, it leads to incosistencies -> revisit
|
// TODO: #7348 we should not capture everything, but if we don't, it leads to incosistencies -> revisit
|
||||||
captureUpdate: shouldCommit
|
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
||||||
? CaptureUpdateAction.IMMEDIATELY
|
|
||||||
: CaptureUpdateAction.NEVER,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
keyTest: (event, appState) =>
|
keyTest: (event, appState) =>
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { fireEvent, queryByTestId } from "@testing-library/react";
|
import { queryByTestId } from "@testing-library/react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
COLOR_PALETTE,
|
COLOR_PALETTE,
|
||||||
DEFAULT_ELEMENT_BACKGROUND_PICKS,
|
DEFAULT_ELEMENT_BACKGROUND_PICKS,
|
||||||
FREEDRAW_STROKE_WIDTH,
|
|
||||||
FONT_FAMILY,
|
FONT_FAMILY,
|
||||||
STROKE_WIDTH,
|
STROKE_WIDTH,
|
||||||
} from "@excalidraw/common";
|
} from "@excalidraw/common";
|
||||||
@@ -129,62 +128,6 @@ describe("element locking", () => {
|
|||||||
expect(thinStrokeWidthButton).toBeChecked();
|
expect(thinStrokeWidthButton).toBeChecked();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should highlight common stroke width key across freedraw and non-freedraw elements", () => {
|
|
||||||
const rect = API.createElement({
|
|
||||||
type: "rectangle",
|
|
||||||
strokeWidth: STROKE_WIDTH.medium,
|
|
||||||
});
|
|
||||||
const freedraw = API.createElement({
|
|
||||||
type: "freedraw",
|
|
||||||
strokeWidth: FREEDRAW_STROKE_WIDTH.medium,
|
|
||||||
});
|
|
||||||
API.setElements([rect, freedraw]);
|
|
||||||
API.setSelectedElements([rect, freedraw]);
|
|
||||||
|
|
||||||
expect(queryByTestId(document.body, `strokeWidth-medium`)).toBeChecked();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should apply stroke width by element type", () => {
|
|
||||||
const rect = API.createElement({
|
|
||||||
type: "rectangle",
|
|
||||||
strokeWidth: STROKE_WIDTH.thin,
|
|
||||||
});
|
|
||||||
const freedraw = API.createElement({
|
|
||||||
type: "freedraw",
|
|
||||||
strokeWidth: FREEDRAW_STROKE_WIDTH.thin,
|
|
||||||
});
|
|
||||||
API.setElements([rect, freedraw]);
|
|
||||||
API.setSelectedElements([rect, freedraw]);
|
|
||||||
|
|
||||||
const boldStrokeWidthButton = queryByTestId(
|
|
||||||
document.body,
|
|
||||||
`strokeWidth-bold`,
|
|
||||||
);
|
|
||||||
expect(boldStrokeWidthButton).not.toBe(null);
|
|
||||||
fireEvent.click(boldStrokeWidthButton!);
|
|
||||||
|
|
||||||
const selectedElements = API.getSelectedElements();
|
|
||||||
const selectedRect = selectedElements.find(
|
|
||||||
(element) => element.type === "rectangle",
|
|
||||||
);
|
|
||||||
const selectedFreedraw = selectedElements.find(
|
|
||||||
(element) => element.type === "freedraw",
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(selectedRect?.strokeWidth).toBe(STROKE_WIDTH.bold);
|
|
||||||
expect(selectedFreedraw?.strokeWidth).toBe(FREEDRAW_STROKE_WIDTH.bold);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should create new elements with stroke width by element type", () => {
|
|
||||||
API.setAppState({ currentItemStrokeWidthKey: "bold" });
|
|
||||||
|
|
||||||
const rect = API.createElement({ type: "rectangle" });
|
|
||||||
const freedraw = API.createElement({ type: "freedraw" });
|
|
||||||
|
|
||||||
expect(rect.strokeWidth).toBe(STROKE_WIDTH.bold);
|
|
||||||
expect(freedraw.strokeWidth).toBe(FREEDRAW_STROKE_WIDTH.bold);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not highlight any stroke width button if no common style", () => {
|
it("should not highlight any stroke width button if no common style", () => {
|
||||||
const rect1 = API.createElement({
|
const rect1 = API.createElement({
|
||||||
type: "rectangle",
|
type: "rectangle",
|
||||||
@@ -192,7 +135,7 @@ describe("element locking", () => {
|
|||||||
});
|
});
|
||||||
const rect2 = API.createElement({
|
const rect2 = API.createElement({
|
||||||
type: "rectangle",
|
type: "rectangle",
|
||||||
strokeWidth: STROKE_WIDTH.medium,
|
strokeWidth: STROKE_WIDTH.bold,
|
||||||
});
|
});
|
||||||
API.setElements([rect1, rect2]);
|
API.setElements([rect1, rect2]);
|
||||||
API.setSelectedElements([rect1, rect2]);
|
API.setSelectedElements([rect1, rect2]);
|
||||||
@@ -202,17 +145,17 @@ describe("element locking", () => {
|
|||||||
queryByTestId(document.body, `strokeWidth-thin`),
|
queryByTestId(document.body, `strokeWidth-thin`),
|
||||||
).not.toBeChecked();
|
).not.toBeChecked();
|
||||||
expect(
|
expect(
|
||||||
queryByTestId(document.body, `strokeWidth-medium`),
|
queryByTestId(document.body, `strokeWidth-bold`),
|
||||||
).not.toBeChecked();
|
).not.toBeChecked();
|
||||||
expect(
|
expect(
|
||||||
queryByTestId(document.body, `strokeWidth-bold`),
|
queryByTestId(document.body, `strokeWidth-extraBold`),
|
||||||
).not.toBeChecked();
|
).not.toBeChecked();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show properties of different element types when selected", () => {
|
it("should show properties of different element types when selected", () => {
|
||||||
const rect = API.createElement({
|
const rect = API.createElement({
|
||||||
type: "rectangle",
|
type: "rectangle",
|
||||||
strokeWidth: STROKE_WIDTH.medium,
|
strokeWidth: STROKE_WIDTH.bold,
|
||||||
});
|
});
|
||||||
const text = API.createElement({
|
const text = API.createElement({
|
||||||
type: "text",
|
type: "text",
|
||||||
@@ -221,7 +164,7 @@ describe("element locking", () => {
|
|||||||
API.setElements([rect, text]);
|
API.setElements([rect, text]);
|
||||||
API.setSelectedElements([rect, text]);
|
API.setSelectedElements([rect, text]);
|
||||||
|
|
||||||
expect(queryByTestId(document.body, `strokeWidth-medium`)).toBeChecked();
|
expect(queryByTestId(document.body, `strokeWidth-bold`)).toBeChecked();
|
||||||
expect(queryByTestId(document.body, `font-family-code`)).toHaveClass(
|
expect(queryByTestId(document.body, `font-family-code`)).toHaveClass(
|
||||||
"active",
|
"active",
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
DEFAULT_FONT_SIZE,
|
DEFAULT_FONT_SIZE,
|
||||||
FONT_FAMILY,
|
FONT_FAMILY,
|
||||||
ROUNDNESS,
|
ROUNDNESS,
|
||||||
STROKE_WIDTH_KEYS,
|
STROKE_WIDTH,
|
||||||
VERTICAL_ALIGN,
|
VERTICAL_ALIGN,
|
||||||
KEYS,
|
KEYS,
|
||||||
randomInteger,
|
randomInteger,
|
||||||
@@ -20,11 +20,9 @@ import {
|
|||||||
getFontFamilyString,
|
getFontFamilyString,
|
||||||
getLineHeight,
|
getLineHeight,
|
||||||
isTransparent,
|
isTransparent,
|
||||||
getStrokeWidthByKey,
|
|
||||||
reduceToCommonValue,
|
reduceToCommonValue,
|
||||||
invariant,
|
invariant,
|
||||||
FONT_SIZES,
|
FONT_SIZES,
|
||||||
type StrokeWidthKey,
|
|
||||||
} from "@excalidraw/common";
|
} from "@excalidraw/common";
|
||||||
|
|
||||||
import { canBecomePolygon, getNonDeletedElements } from "@excalidraw/element";
|
import { canBecomePolygon, getNonDeletedElements } from "@excalidraw/element";
|
||||||
@@ -72,11 +70,9 @@ import type {
|
|||||||
ElementsMap,
|
ElementsMap,
|
||||||
ExcalidrawBindableElement,
|
ExcalidrawBindableElement,
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
ExcalidrawFreeDrawElement,
|
|
||||||
ExcalidrawLinearElement,
|
ExcalidrawLinearElement,
|
||||||
ExcalidrawTextElement,
|
ExcalidrawTextElement,
|
||||||
FontFamilyValues,
|
FontFamilyValues,
|
||||||
StrokeVariability,
|
|
||||||
TextAlign,
|
TextAlign,
|
||||||
VerticalAlign,
|
VerticalAlign,
|
||||||
} from "@excalidraw/element/types";
|
} from "@excalidraw/element/types";
|
||||||
@@ -87,7 +83,6 @@ import type { CaptureUpdateActionType } from "@excalidraw/element";
|
|||||||
|
|
||||||
import { trackEvent } from "../analytics";
|
import { trackEvent } from "../analytics";
|
||||||
import { RadioSelection } from "../components/RadioSelection";
|
import { RadioSelection } from "../components/RadioSelection";
|
||||||
import { ToolButton } from "../components/ToolButton";
|
|
||||||
import { ColorPicker } from "../components/ColorPicker/ColorPicker";
|
import { ColorPicker } from "../components/ColorPicker/ColorPicker";
|
||||||
import { FontPicker } from "../components/FontPicker/FontPicker";
|
import { FontPicker } from "../components/FontPicker/FontPicker";
|
||||||
import { IconPicker } from "../components/IconPicker";
|
import { IconPicker } from "../components/IconPicker";
|
||||||
@@ -136,8 +131,6 @@ import {
|
|||||||
ArrowheadCardinalityOneOrManyIcon,
|
ArrowheadCardinalityOneOrManyIcon,
|
||||||
ArrowheadCardinalityZeroOrManyIcon,
|
ArrowheadCardinalityZeroOrManyIcon,
|
||||||
ArrowheadCardinalityZeroOrOneIcon,
|
ArrowheadCardinalityZeroOrOneIcon,
|
||||||
strokeVariabilityConstantIcon,
|
|
||||||
strokeVariabilityVariableIcon,
|
|
||||||
} from "../components/icons";
|
} from "../components/icons";
|
||||||
|
|
||||||
import { Fonts } from "../fonts";
|
import { Fonts } from "../fonts";
|
||||||
@@ -197,11 +190,7 @@ export const changeProperty = (
|
|||||||
export const getFormValue = function <T extends Primitive>(
|
export const getFormValue = function <T extends Primitive>(
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
app: AppClassProperties,
|
app: AppClassProperties,
|
||||||
/**
|
getAttribute: (element: ExcalidrawElement) => T,
|
||||||
* input value (usually the element attribute value,
|
|
||||||
* but depends on what the action's PanelComponent input expects)
|
|
||||||
*/
|
|
||||||
getValue: (element: ExcalidrawElement) => T,
|
|
||||||
elementPredicate: true | ((element: ExcalidrawElement) => boolean),
|
elementPredicate: true | ((element: ExcalidrawElement) => boolean),
|
||||||
defaultValue: T | ((isSomeElementSelected: boolean) => T),
|
defaultValue: T | ((isSomeElementSelected: boolean) => T),
|
||||||
): T {
|
): T {
|
||||||
@@ -211,7 +200,7 @@ export const getFormValue = function <T extends Primitive>(
|
|||||||
let ret: T | null = null;
|
let ret: T | null = null;
|
||||||
|
|
||||||
if (editingTextElement) {
|
if (editingTextElement) {
|
||||||
ret = getValue(editingTextElement);
|
ret = getAttribute(editingTextElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
@@ -225,7 +214,7 @@ export const getFormValue = function <T extends Primitive>(
|
|||||||
: selectedElements.filter((el) => elementPredicate(el));
|
: selectedElements.filter((el) => elementPredicate(el));
|
||||||
|
|
||||||
ret =
|
ret =
|
||||||
reduceToCommonValue(targetElements, getValue) ??
|
reduceToCommonValue(targetElements, getAttribute) ??
|
||||||
(typeof defaultValue === "function"
|
(typeof defaultValue === "function"
|
||||||
? defaultValue(true)
|
? defaultValue(true)
|
||||||
: defaultValue);
|
: defaultValue);
|
||||||
@@ -555,37 +544,20 @@ export const actionChangeFillStyle = register<ExcalidrawElement["fillStyle"]>({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const getStrokeWidthKeyForElement = (
|
export const actionChangeStrokeWidth = register<
|
||||||
element: ExcalidrawElement,
|
ExcalidrawElement["strokeWidth"]
|
||||||
): StrokeWidthKey | null => {
|
>({
|
||||||
return (
|
|
||||||
STROKE_WIDTH_KEYS.find(
|
|
||||||
(key) => getStrokeWidthByKey(element.type, key) === element.strokeWidth,
|
|
||||||
) ?? null
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getStrokeWidthForElement = (
|
|
||||||
element: ExcalidrawElement,
|
|
||||||
strokeWidthKey: StrokeWidthKey,
|
|
||||||
): ExcalidrawElement["strokeWidth"] => {
|
|
||||||
return getStrokeWidthByKey(element.type, strokeWidthKey);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const actionChangeStrokeWidth = register<StrokeWidthKey>({
|
|
||||||
name: "changeStrokeWidth",
|
name: "changeStrokeWidth",
|
||||||
label: "labels.strokeWidth",
|
label: "labels.strokeWidth",
|
||||||
trackEvent: false,
|
trackEvent: false,
|
||||||
perform: (elements, appState, value) => {
|
perform: (elements, appState, value) => {
|
||||||
invariant(value, "actionChangeStrokeWidth: value must be defined");
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
elements: changeProperty(elements, appState, (el) =>
|
elements: changeProperty(elements, appState, (el) =>
|
||||||
newElementWith(el, {
|
newElementWith(el, {
|
||||||
strokeWidth: getStrokeWidthForElement(el, value),
|
strokeWidth: value,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
appState: { ...appState, currentItemStrokeWidthKey: value },
|
appState: { ...appState, currentItemStrokeWidth: value },
|
||||||
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -593,35 +565,35 @@ export const actionChangeStrokeWidth = register<StrokeWidthKey>({
|
|||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>{t("labels.strokeWidth")}</legend>
|
<legend>{t("labels.strokeWidth")}</legend>
|
||||||
<div className="buttonList">
|
<div className="buttonList">
|
||||||
<RadioSelection<StrokeWidthKey>
|
<RadioSelection
|
||||||
group="stroke-width"
|
group="stroke-width"
|
||||||
options={[
|
options={[
|
||||||
{
|
{
|
||||||
value: "thin",
|
value: STROKE_WIDTH.thin,
|
||||||
text: t("labels.thin"),
|
text: t("labels.thin"),
|
||||||
icon: StrokeWidthBaseIcon,
|
icon: StrokeWidthBaseIcon,
|
||||||
testId: "strokeWidth-thin",
|
testId: "strokeWidth-thin",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "medium",
|
value: STROKE_WIDTH.bold,
|
||||||
text: t("labels.medium"),
|
text: t("labels.bold"),
|
||||||
icon: StrokeWidthBoldIcon,
|
icon: StrokeWidthBoldIcon,
|
||||||
testId: "strokeWidth-medium",
|
testId: "strokeWidth-bold",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "bold",
|
value: STROKE_WIDTH.extraBold,
|
||||||
text: t("labels.bold"),
|
text: t("labels.extraBold"),
|
||||||
icon: StrokeWidthExtraBoldIcon,
|
icon: StrokeWidthExtraBoldIcon,
|
||||||
testId: "strokeWidth-bold",
|
testId: "strokeWidth-extraBold",
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
value={getFormValue(
|
value={getFormValue(
|
||||||
elements,
|
elements,
|
||||||
app,
|
app,
|
||||||
getStrokeWidthKeyForElement,
|
(element) => element.strokeWidth,
|
||||||
(element) => element.hasOwnProperty("strokeWidth"),
|
(element) => element.hasOwnProperty("strokeWidth"),
|
||||||
(hasSelection) =>
|
(hasSelection) =>
|
||||||
hasSelection ? null : appState.currentItemStrokeWidthKey,
|
hasSelection ? null : appState.currentItemStrokeWidth,
|
||||||
)}
|
)}
|
||||||
onChange={(value) => updateData(value)}
|
onChange={(value) => updateData(value)}
|
||||||
/>
|
/>
|
||||||
@@ -684,87 +656,6 @@ export const actionChangeSloppiness = register<ExcalidrawElement["roughness"]>({
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const actionChangeFreedrawMode = register<StrokeVariability>({
|
|
||||||
name: "changeFreedrawMode",
|
|
||||||
label: "labels.pressure",
|
|
||||||
trackEvent: false,
|
|
||||||
perform: (elements, appState, value) => {
|
|
||||||
const variability = value || "constant";
|
|
||||||
|
|
||||||
return {
|
|
||||||
elements: changeProperty(elements, appState, (el) => {
|
|
||||||
if (el.type !== "freedraw") {
|
|
||||||
return el;
|
|
||||||
}
|
|
||||||
return newElementWith(el, {
|
|
||||||
strokeOptions: {
|
|
||||||
...el.strokeOptions,
|
|
||||||
variability,
|
|
||||||
},
|
|
||||||
}) as ExcalidrawElement;
|
|
||||||
}),
|
|
||||||
appState: { ...appState, currentItemStrokeVariability: variability },
|
|
||||||
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
PanelComponent: ({ elements, appState, updateData, app, data }) => {
|
|
||||||
const strokeVariability =
|
|
||||||
getFormValue(
|
|
||||||
elements,
|
|
||||||
app,
|
|
||||||
(element) =>
|
|
||||||
(element as ExcalidrawFreeDrawElement).strokeOptions?.variability,
|
|
||||||
(element) => element.type === "freedraw",
|
|
||||||
(hasSelection) =>
|
|
||||||
hasSelection ? null : appState.currentItemStrokeVariability,
|
|
||||||
) ?? appState.currentItemStrokeVariability;
|
|
||||||
|
|
||||||
// in the compact UI the pressure setting is rendered as a single button
|
|
||||||
// that cycles between the two variability modes on click
|
|
||||||
if (data?.cycle) {
|
|
||||||
const isVariable = strokeVariability === "variable";
|
|
||||||
return (
|
|
||||||
<ToolButton
|
|
||||||
type="button"
|
|
||||||
icon={
|
|
||||||
isVariable
|
|
||||||
? strokeVariabilityVariableIcon
|
|
||||||
: strokeVariabilityConstantIcon
|
|
||||||
}
|
|
||||||
title={t("labels.pressure")}
|
|
||||||
aria-label={t("labels.pressure")}
|
|
||||||
onClick={() => updateData(isVariable ? "constant" : "variable")}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<fieldset>
|
|
||||||
<legend>{t("labels.pressure")}</legend>
|
|
||||||
<div className="buttonList">
|
|
||||||
<RadioSelection<StrokeVariability>
|
|
||||||
group="strokeOptions.variability"
|
|
||||||
options={[
|
|
||||||
{
|
|
||||||
value: "constant",
|
|
||||||
text: t("labels.pressure_constant"),
|
|
||||||
icon: strokeVariabilityConstantIcon,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "variable",
|
|
||||||
text: t("labels.pressure_variable"),
|
|
||||||
icon: strokeVariabilityVariableIcon,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
value={strokeVariability}
|
|
||||||
onChange={(value) => updateData(value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const actionChangeStrokeStyle = register<
|
export const actionChangeStrokeStyle = register<
|
||||||
ExcalidrawElement["strokeStyle"]
|
ExcalidrawElement["strokeStyle"]
|
||||||
>({
|
>({
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ export {
|
|||||||
actionChangeStrokeWidth,
|
actionChangeStrokeWidth,
|
||||||
actionChangeFillStyle,
|
actionChangeFillStyle,
|
||||||
actionChangeSloppiness,
|
actionChangeSloppiness,
|
||||||
actionChangeFreedrawMode,
|
|
||||||
actionChangeOpacity,
|
actionChangeOpacity,
|
||||||
actionChangeFontSize,
|
actionChangeFontSize,
|
||||||
actionChangeFontFamily,
|
actionChangeFontFamily,
|
||||||
|
|||||||
@@ -68,7 +68,6 @@ export type ActionName =
|
|||||||
| "changeStrokeWidth"
|
| "changeStrokeWidth"
|
||||||
| "changeStrokeShape"
|
| "changeStrokeShape"
|
||||||
| "changeSloppiness"
|
| "changeSloppiness"
|
||||||
| "changeFreedrawMode"
|
|
||||||
| "changeStrokeStyle"
|
| "changeStrokeStyle"
|
||||||
| "changeArrowhead"
|
| "changeArrowhead"
|
||||||
| "changeArrowType"
|
| "changeArrowType"
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import {
|
|||||||
DEFAULT_ELEMENT_PROPS,
|
DEFAULT_ELEMENT_PROPS,
|
||||||
DEFAULT_FONT_FAMILY,
|
DEFAULT_FONT_FAMILY,
|
||||||
DEFAULT_FONT_SIZE,
|
DEFAULT_FONT_SIZE,
|
||||||
DEFAULT_ELEMENT_STROKE_WIDTH_KEY,
|
|
||||||
DEFAULT_TEXT_ALIGN,
|
DEFAULT_TEXT_ALIGN,
|
||||||
DEFAULT_GRID_SIZE,
|
DEFAULT_GRID_SIZE,
|
||||||
EXPORT_SCALES,
|
EXPORT_SCALES,
|
||||||
@@ -35,13 +34,12 @@ export const getDefaultAppState = (): Omit<
|
|||||||
currentItemFontSize: DEFAULT_FONT_SIZE,
|
currentItemFontSize: DEFAULT_FONT_SIZE,
|
||||||
currentItemOpacity: DEFAULT_ELEMENT_PROPS.opacity,
|
currentItemOpacity: DEFAULT_ELEMENT_PROPS.opacity,
|
||||||
currentItemRoughness: DEFAULT_ELEMENT_PROPS.roughness,
|
currentItemRoughness: DEFAULT_ELEMENT_PROPS.roughness,
|
||||||
currentItemStrokeVariability: "constant",
|
|
||||||
currentItemStartArrowhead: null,
|
currentItemStartArrowhead: null,
|
||||||
currentItemStrokeColor: DEFAULT_ELEMENT_PROPS.strokeColor,
|
currentItemStrokeColor: DEFAULT_ELEMENT_PROPS.strokeColor,
|
||||||
currentItemRoundness: isTestEnv() ? "sharp" : "round",
|
currentItemRoundness: isTestEnv() ? "sharp" : "round",
|
||||||
currentItemArrowType: ARROW_TYPE.round,
|
currentItemArrowType: ARROW_TYPE.round,
|
||||||
currentItemStrokeStyle: DEFAULT_ELEMENT_PROPS.strokeStyle,
|
currentItemStrokeStyle: DEFAULT_ELEMENT_PROPS.strokeStyle,
|
||||||
currentItemStrokeWidthKey: DEFAULT_ELEMENT_STROKE_WIDTH_KEY,
|
currentItemStrokeWidth: DEFAULT_ELEMENT_PROPS.strokeWidth,
|
||||||
currentItemTextAlign: DEFAULT_TEXT_ALIGN,
|
currentItemTextAlign: DEFAULT_TEXT_ALIGN,
|
||||||
currentHoveredFontFamily: null,
|
currentHoveredFontFamily: null,
|
||||||
cursorButton: "up",
|
cursorButton: "up",
|
||||||
@@ -169,15 +167,10 @@ const APP_STATE_STORAGE_CONF = (<
|
|||||||
},
|
},
|
||||||
currentItemOpacity: { browser: true, export: false, server: false },
|
currentItemOpacity: { browser: true, export: false, server: false },
|
||||||
currentItemRoughness: { browser: true, export: false, server: false },
|
currentItemRoughness: { browser: true, export: false, server: false },
|
||||||
currentItemStrokeVariability: {
|
|
||||||
browser: true,
|
|
||||||
export: false,
|
|
||||||
server: false,
|
|
||||||
},
|
|
||||||
currentItemStartArrowhead: { browser: true, export: false, server: false },
|
currentItemStartArrowhead: { browser: true, export: false, server: false },
|
||||||
currentItemStrokeColor: { browser: true, export: false, server: false },
|
currentItemStrokeColor: { browser: true, export: false, server: false },
|
||||||
currentItemStrokeStyle: { browser: true, export: false, server: false },
|
currentItemStrokeStyle: { browser: true, export: false, server: false },
|
||||||
currentItemStrokeWidthKey: { browser: true, export: false, server: false },
|
currentItemStrokeWidth: { browser: true, export: false, server: false },
|
||||||
currentItemTextAlign: { browser: true, export: false, server: false },
|
currentItemTextAlign: { browser: true, export: false, server: false },
|
||||||
currentHoveredFontFamily: { browser: false, export: false, server: false },
|
currentHoveredFontFamily: { browser: false, export: false, server: false },
|
||||||
cursorButton: { browser: true, export: false, server: false },
|
cursorButton: { browser: true, export: false, server: false },
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ import {
|
|||||||
canHaveArrowheads,
|
canHaveArrowheads,
|
||||||
getTargetElements,
|
getTargetElements,
|
||||||
hasBackground,
|
hasBackground,
|
||||||
hasFreedrawMode,
|
|
||||||
hasStrokeStyle,
|
hasStrokeStyle,
|
||||||
hasStrokeWidth,
|
hasStrokeWidth,
|
||||||
} from "../scene";
|
} from "../scene";
|
||||||
@@ -202,9 +201,9 @@ export const SelectedShapeActions = ({
|
|||||||
targetElements.some((element) => hasStrokeWidth(element.type))) &&
|
targetElements.some((element) => hasStrokeWidth(element.type))) &&
|
||||||
renderAction("changeStrokeWidth")}
|
renderAction("changeStrokeWidth")}
|
||||||
|
|
||||||
{(hasFreedrawMode(appState.activeTool.type) ||
|
{(appState.activeTool.type === "freedraw" ||
|
||||||
targetElements.some((element) => hasFreedrawMode(element.type))) &&
|
targetElements.some((element) => element.type === "freedraw")) &&
|
||||||
renderAction("changeFreedrawMode")}
|
renderAction("changeStrokeShape")}
|
||||||
|
|
||||||
{(hasStrokeStyle(appState.activeTool.type) ||
|
{(hasStrokeStyle(appState.activeTool.type) ||
|
||||||
targetElements.some((element) => hasStrokeStyle(element.type))) && (
|
targetElements.some((element) => hasStrokeStyle(element.type))) && (
|
||||||
@@ -395,17 +394,6 @@ const CombinedShapeProperties = ({
|
|||||||
hasStrokeWidth(element.type),
|
hasStrokeWidth(element.type),
|
||||||
)) &&
|
)) &&
|
||||||
renderAction("changeStrokeWidth")}
|
renderAction("changeStrokeWidth")}
|
||||||
{
|
|
||||||
/* in compact UI the freedraw pressure setting is rendered as a
|
|
||||||
standalone cycle button in the compact actions list; we render
|
|
||||||
it in the combined properties popup as well for clarity
|
|
||||||
*/
|
|
||||||
(hasFreedrawMode(appState.activeTool.type) ||
|
|
||||||
targetElements.some((element) =>
|
|
||||||
hasFreedrawMode(element.type),
|
|
||||||
)) &&
|
|
||||||
renderAction("changeFreedrawMode")
|
|
||||||
}
|
|
||||||
{(hasStrokeStyle(appState.activeTool.type) ||
|
{(hasStrokeStyle(appState.activeTool.type) ||
|
||||||
targetElements.some((element) =>
|
targetElements.some((element) =>
|
||||||
hasStrokeStyle(element.type),
|
hasStrokeStyle(element.type),
|
||||||
@@ -838,14 +826,6 @@ export const CompactShapeActions = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Freedraw pressure: standalone button cycling the variability mode */}
|
|
||||||
{(hasFreedrawMode(appState.activeTool.type) ||
|
|
||||||
targetElements.some((element) => hasFreedrawMode(element.type))) && (
|
|
||||||
<div className="compact-action-item">
|
|
||||||
{renderAction("changeFreedrawMode", { cycle: true })}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<CombinedShapeProperties
|
<CombinedShapeProperties
|
||||||
appState={appState}
|
appState={appState}
|
||||||
renderAction={renderAction}
|
renderAction={renderAction}
|
||||||
@@ -1074,11 +1054,6 @@ export const ShapesSwitcher = ({
|
|||||||
const isFullStylesPanel = stylesPanelMode === "full";
|
const isFullStylesPanel = stylesPanelMode === "full";
|
||||||
const isCompactStylesPanel = stylesPanelMode === "compact";
|
const isCompactStylesPanel = stylesPanelMode === "compact";
|
||||||
|
|
||||||
// a pen detected on a tool button's pointer-down, to be applied (enabling
|
|
||||||
// pen mode) only after the tap's `change` has committed — see the tool
|
|
||||||
// button handlers below
|
|
||||||
const pendingPenDetectionRef = useRef(false);
|
|
||||||
|
|
||||||
const SELECTION_TOOLS = [
|
const SELECTION_TOOLS = [
|
||||||
{
|
{
|
||||||
type: "selection",
|
type: "selection",
|
||||||
@@ -1177,13 +1152,8 @@ export const ShapesSwitcher = ({
|
|||||||
aria-keyshortcuts={shortcut}
|
aria-keyshortcuts={shortcut}
|
||||||
data-testid={`toolbar-${value}`}
|
data-testid={`toolbar-${value}`}
|
||||||
onPointerDown={({ pointerType }) => {
|
onPointerDown={({ pointerType }) => {
|
||||||
// Detect the pen here (pointerType is reliable on pointer-down)
|
|
||||||
// but DON'T enable pen mode yet: calling setState mid-gesture
|
|
||||||
// re-renders the controlled radio and, on iOS/iPadOS, aborts
|
|
||||||
// the ensuing click so the tool isn't selected on the first pen
|
|
||||||
// tap. Defer it until the tap's `change` has committed (below).
|
|
||||||
if (!app.state.penDetected && pointerType === "pen") {
|
if (!app.state.penDetected && pointerType === "pen") {
|
||||||
pendingPenDetectionRef.current = true;
|
app.togglePenMode(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value === "selection") {
|
if (value === "selection") {
|
||||||
@@ -1194,21 +1164,16 @@ export const ShapesSwitcher = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onChange={() => {
|
onChange={({ pointerType }) => {
|
||||||
if (app.state.activeTool.type !== value) {
|
if (app.state.activeTool.type !== value) {
|
||||||
trackEvent("toolbar", value, "ui");
|
trackEvent("toolbar", value, "ui");
|
||||||
}
|
}
|
||||||
app.setActiveTool({ type: value });
|
if (value === "image") {
|
||||||
|
app.setActiveTool({
|
||||||
// Apply the pen detection captured on pointer-down now that the
|
type: value,
|
||||||
// tool is selected. rAF keeps the resulting re-render out of the
|
});
|
||||||
// `change` event itself. We rely on the pointer-down detection
|
} else {
|
||||||
// rather than this handler's pointerType because the latter is
|
app.setActiveTool({ type: value });
|
||||||
// unreliable on iOS (its backing ref is cleared before the
|
|
||||||
// delayed click fires).
|
|
||||||
if (pendingPenDetectionRef.current) {
|
|
||||||
pendingPenDetectionRef.current = false;
|
|
||||||
requestAnimationFrame(() => app.togglePenMode(true));
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -27,8 +27,6 @@ import {
|
|||||||
KEYS,
|
KEYS,
|
||||||
APP_NAME,
|
APP_NAME,
|
||||||
CURSOR_TYPE,
|
CURSOR_TYPE,
|
||||||
DEFAULT_STROKE_STREAMLINE,
|
|
||||||
DEFAULT_STROKE_STREAMLINE_PRECISE,
|
|
||||||
DEFAULT_TRANSFORM_HANDLE_SPACING,
|
DEFAULT_TRANSFORM_HANDLE_SPACING,
|
||||||
DEFAULT_VERTICAL_ALIGN,
|
DEFAULT_VERTICAL_ALIGN,
|
||||||
DRAGGING_THRESHOLD,
|
DRAGGING_THRESHOLD,
|
||||||
@@ -111,7 +109,6 @@ import {
|
|||||||
setDesktopUIMode,
|
setDesktopUIMode,
|
||||||
isSelectionLikeTool,
|
isSelectionLikeTool,
|
||||||
oneOf,
|
oneOf,
|
||||||
getStrokeWidthByKey,
|
|
||||||
} from "@excalidraw/common";
|
} from "@excalidraw/common";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -263,7 +260,6 @@ import {
|
|||||||
getUncroppedWidthAndHeight,
|
getUncroppedWidthAndHeight,
|
||||||
getActiveTextElement,
|
getActiveTextElement,
|
||||||
isEligibleFrameChildType,
|
isEligibleFrameChildType,
|
||||||
getBindingStrategyForDraggingBindingElementEndpoints,
|
|
||||||
} from "@excalidraw/element";
|
} from "@excalidraw/element";
|
||||||
|
|
||||||
import type { GlobalPoint, LocalPoint, Radians } from "@excalidraw/math";
|
import type { GlobalPoint, LocalPoint, Radians } from "@excalidraw/math";
|
||||||
@@ -4137,7 +4133,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
strokeColor: this.state.currentItemStrokeColor,
|
strokeColor: this.state.currentItemStrokeColor,
|
||||||
backgroundColor: this.state.currentItemBackgroundColor,
|
backgroundColor: this.state.currentItemBackgroundColor,
|
||||||
fillStyle: this.state.currentItemFillStyle,
|
fillStyle: this.state.currentItemFillStyle,
|
||||||
strokeWidth: this.getCurrentItemStrokeWidth("text"),
|
strokeWidth: this.state.currentItemStrokeWidth,
|
||||||
strokeStyle: this.state.currentItemStrokeStyle,
|
strokeStyle: this.state.currentItemStrokeStyle,
|
||||||
roundness: null,
|
roundness: null,
|
||||||
roughness: this.state.currentItemRoughness,
|
roughness: this.state.currentItemRoughness,
|
||||||
@@ -4308,9 +4304,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
return {
|
return {
|
||||||
penMode: force ?? !prevState.penMode,
|
penMode: force ?? !prevState.penMode,
|
||||||
penDetected: true,
|
penDetected: true,
|
||||||
currentItemStrokeVariability: !prevState.penDetected
|
|
||||||
? "variable"
|
|
||||||
: prevState.currentItemStrokeVariability,
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -6310,7 +6303,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
strokeColor: this.state.currentItemStrokeColor,
|
strokeColor: this.state.currentItemStrokeColor,
|
||||||
backgroundColor: this.state.currentItemBackgroundColor,
|
backgroundColor: this.state.currentItemBackgroundColor,
|
||||||
fillStyle: this.state.currentItemFillStyle,
|
fillStyle: this.state.currentItemFillStyle,
|
||||||
strokeWidth: this.getCurrentItemStrokeWidth("text"),
|
strokeWidth: this.state.currentItemStrokeWidth,
|
||||||
strokeStyle: this.state.currentItemStrokeStyle,
|
strokeStyle: this.state.currentItemStrokeStyle,
|
||||||
roughness: this.state.currentItemRoughness,
|
roughness: this.state.currentItemRoughness,
|
||||||
opacity: this.state.currentItemOpacity,
|
opacity: this.state.currentItemOpacity,
|
||||||
@@ -7119,7 +7112,45 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
setCursorForShape(this.interactiveCanvas, this.state);
|
setCursorForShape(this.interactiveCanvas, this.state);
|
||||||
|
|
||||||
if (lastPoint === lastCommittedPoint) {
|
if (lastPoint === lastCommittedPoint) {
|
||||||
if (
|
const hoveredElement =
|
||||||
|
isArrowElement(this.state.newElement) &&
|
||||||
|
isBindingEnabled(this.state) &&
|
||||||
|
getHoveredElementForBinding(
|
||||||
|
pointFrom<GlobalPoint>(scenePointerX, scenePointerY),
|
||||||
|
this.scene.getNonDeletedElements(),
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
|
maxBindingDistance_simple(this.state.zoom),
|
||||||
|
);
|
||||||
|
if (hoveredElement) {
|
||||||
|
this.actionManager.executeAction(actionFinalize, "ui", {
|
||||||
|
event: event.nativeEvent,
|
||||||
|
sceneCoords: {
|
||||||
|
x: scenePointerX,
|
||||||
|
y: scenePointerY,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.setState({ suggestedBinding: null });
|
||||||
|
if (!this.state.activeTool.locked) {
|
||||||
|
resetCursor(this.interactiveCanvas);
|
||||||
|
this.setState((prevState) => ({
|
||||||
|
newElement: null,
|
||||||
|
activeTool: updateActiveTool(this.state, {
|
||||||
|
type: this.state.preferredSelectionTool.type,
|
||||||
|
}),
|
||||||
|
selectedElementIds: makeNextSelectedElementIds(
|
||||||
|
{
|
||||||
|
...prevState.selectedElementIds,
|
||||||
|
[multiElement.id]: true,
|
||||||
|
},
|
||||||
|
prevState,
|
||||||
|
),
|
||||||
|
selectedLinearElement: new LinearElementEditor(
|
||||||
|
multiElement,
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
// if we haven't yet created a temp point and we're beyond commit-zone
|
// if we haven't yet created a temp point and we're beyond commit-zone
|
||||||
// threshold, add a point
|
// threshold, add a point
|
||||||
pointDistance(
|
pointDistance(
|
||||||
@@ -7127,24 +7158,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
lastPoint,
|
lastPoint,
|
||||||
) >= LINE_CONFIRM_THRESHOLD
|
) >= LINE_CONFIRM_THRESHOLD
|
||||||
) {
|
) {
|
||||||
this.store.scheduleCapture();
|
|
||||||
flushSync(() => {
|
|
||||||
invariant(
|
|
||||||
this.state.selectedLinearElement?.initialState,
|
|
||||||
"initialState must be set",
|
|
||||||
);
|
|
||||||
this.setState({
|
|
||||||
selectedLinearElement: {
|
|
||||||
...this.state.selectedLinearElement,
|
|
||||||
lastCommittedPoint: points[points.length - 1],
|
|
||||||
selectedPointsIndices: [multiElement.points.length],
|
|
||||||
initialState: {
|
|
||||||
...this.state.selectedLinearElement.initialState,
|
|
||||||
lastClickedPoint: multiElement.points.length,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
this.scene.mutateElement(
|
this.scene.mutateElement(
|
||||||
multiElement,
|
multiElement,
|
||||||
{
|
{
|
||||||
@@ -7155,6 +7168,21 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
},
|
},
|
||||||
{ informMutation: false, isDragging: false },
|
{ informMutation: false, isDragging: false },
|
||||||
);
|
);
|
||||||
|
invariant(
|
||||||
|
this.state.selectedLinearElement?.initialState,
|
||||||
|
"initialState must be set",
|
||||||
|
);
|
||||||
|
this.setState({
|
||||||
|
selectedLinearElement: {
|
||||||
|
...this.state.selectedLinearElement,
|
||||||
|
lastCommittedPoint: points[points.length - 1],
|
||||||
|
selectedPointsIndices: [multiElement.points.length - 1],
|
||||||
|
initialState: {
|
||||||
|
...this.state.selectedLinearElement.initialState,
|
||||||
|
lastClickedPoint: multiElement.points.length - 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER);
|
setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER);
|
||||||
// in this branch, we're inside the commit zone, and no uncommitted
|
// in this branch, we're inside the commit zone, and no uncommitted
|
||||||
@@ -7780,7 +7808,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
return {
|
return {
|
||||||
penMode: true,
|
penMode: true,
|
||||||
penDetected: true,
|
penDetected: true,
|
||||||
currentItemStrokeVariability: "variable",
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -8999,8 +9026,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
|
|
||||||
const simulatePressure = event.pressure === 0.5;
|
const simulatePressure = event.pressure === 0.5;
|
||||||
|
|
||||||
const strokeVariability = this.state.currentItemStrokeVariability;
|
|
||||||
|
|
||||||
const element = newFreeDrawElement({
|
const element = newFreeDrawElement({
|
||||||
type: elementType,
|
type: elementType,
|
||||||
x: gridX,
|
x: gridX,
|
||||||
@@ -9008,24 +9033,15 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
strokeColor: this.state.currentItemStrokeColor,
|
strokeColor: this.state.currentItemStrokeColor,
|
||||||
backgroundColor: this.state.currentItemBackgroundColor,
|
backgroundColor: this.state.currentItemBackgroundColor,
|
||||||
fillStyle: this.state.currentItemFillStyle,
|
fillStyle: this.state.currentItemFillStyle,
|
||||||
strokeWidth: this.getCurrentItemStrokeWidth("freedraw"),
|
strokeWidth: this.state.currentItemStrokeWidth,
|
||||||
strokeStyle: this.state.currentItemStrokeStyle,
|
strokeStyle: this.state.currentItemStrokeStyle,
|
||||||
roughness: this.state.currentItemRoughness,
|
roughness: this.state.currentItemRoughness,
|
||||||
opacity: this.state.currentItemOpacity,
|
opacity: this.state.currentItemOpacity,
|
||||||
roundness: null,
|
roundness: null,
|
||||||
simulatePressure,
|
simulatePressure,
|
||||||
strokeOptions: {
|
|
||||||
variability: strokeVariability,
|
|
||||||
streamline:
|
|
||||||
event.pointerType !== "mouse"
|
|
||||||
? DEFAULT_STROKE_STREAMLINE_PRECISE
|
|
||||||
: DEFAULT_STROKE_STREAMLINE,
|
|
||||||
},
|
|
||||||
locked: false,
|
locked: false,
|
||||||
frameId: topLayerFrame ? topLayerFrame.id : null,
|
frameId: topLayerFrame ? topLayerFrame.id : null,
|
||||||
points: [pointFrom<LocalPoint>(0, 0)],
|
points: [pointFrom<LocalPoint>(0, 0)],
|
||||||
// pressures are only consumed when rendering a real-pressure stroke, so
|
|
||||||
// skip persisting them while pressure is being simulated
|
|
||||||
pressures: simulatePressure ? [] : [event.pressure],
|
pressures: simulatePressure ? [] : [event.pressure],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -9076,7 +9092,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
strokeColor: "transparent",
|
strokeColor: "transparent",
|
||||||
backgroundColor: "transparent",
|
backgroundColor: "transparent",
|
||||||
fillStyle: this.state.currentItemFillStyle,
|
fillStyle: this.state.currentItemFillStyle,
|
||||||
strokeWidth: this.getCurrentItemStrokeWidth("iframe"),
|
strokeWidth: this.state.currentItemStrokeWidth,
|
||||||
strokeStyle: this.state.currentItemStrokeStyle,
|
strokeStyle: this.state.currentItemStrokeStyle,
|
||||||
roughness: this.state.currentItemRoughness,
|
roughness: this.state.currentItemRoughness,
|
||||||
roundness: this.getCurrentItemRoundness("iframe"),
|
roundness: this.getCurrentItemRoundness("iframe"),
|
||||||
@@ -9129,7 +9145,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
strokeColor: "transparent",
|
strokeColor: "transparent",
|
||||||
backgroundColor: "transparent",
|
backgroundColor: "transparent",
|
||||||
fillStyle: this.state.currentItemFillStyle,
|
fillStyle: this.state.currentItemFillStyle,
|
||||||
strokeWidth: this.getCurrentItemStrokeWidth("embeddable"),
|
strokeWidth: this.state.currentItemStrokeWidth,
|
||||||
strokeStyle: this.state.currentItemStrokeStyle,
|
strokeStyle: this.state.currentItemStrokeStyle,
|
||||||
roughness: this.state.currentItemRoughness,
|
roughness: this.state.currentItemRoughness,
|
||||||
roundness: this.getCurrentItemRoundness("embeddable"),
|
roundness: this.getCurrentItemRoundness("embeddable"),
|
||||||
@@ -9176,7 +9192,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
strokeColor: this.state.currentItemStrokeColor,
|
strokeColor: this.state.currentItemStrokeColor,
|
||||||
backgroundColor: this.state.currentItemBackgroundColor,
|
backgroundColor: this.state.currentItemBackgroundColor,
|
||||||
fillStyle: this.state.currentItemFillStyle,
|
fillStyle: this.state.currentItemFillStyle,
|
||||||
strokeWidth: this.getCurrentItemStrokeWidth("image"),
|
strokeWidth: this.state.currentItemStrokeWidth,
|
||||||
strokeStyle: this.state.currentItemStrokeStyle,
|
strokeStyle: this.state.currentItemStrokeStyle,
|
||||||
roughness: this.state.currentItemRoughness,
|
roughness: this.state.currentItemRoughness,
|
||||||
roundness: null,
|
roundness: null,
|
||||||
@@ -9248,58 +9264,32 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
|
|
||||||
const { x: rx, y: ry } = multiElement;
|
const { x: rx, y: ry } = multiElement;
|
||||||
const { lastCommittedPoint } = selectedLinearElement;
|
const { lastCommittedPoint } = selectedLinearElement;
|
||||||
const sceneCoords = viewportCoordsToSceneCoords(event, this.state);
|
|
||||||
const { start, end } =
|
|
||||||
isBindingElement(multiElement) && isBindingEnabled(this.state)
|
|
||||||
? getBindingStrategyForDraggingBindingElementEndpoints(
|
|
||||||
multiElement,
|
|
||||||
new Map([
|
|
||||||
[
|
|
||||||
multiElement.points.length - 1,
|
|
||||||
{
|
|
||||||
point: multiElement.points[multiElement.points.length - 1],
|
|
||||||
isDragging: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
]),
|
|
||||||
sceneCoords.x,
|
|
||||||
sceneCoords.y,
|
|
||||||
this.scene.getNonDeletedElementsMap(),
|
|
||||||
this.scene.getNonDeletedElements(),
|
|
||||||
this.state,
|
|
||||||
{
|
|
||||||
newArrow: Boolean(this.state.newElement),
|
|
||||||
zoom: this.state.zoom,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
: { end: { mode: undefined } };
|
|
||||||
|
|
||||||
const elementsMap = this.scene.getNonDeletedElementsMap();
|
const hoveredElementForBinding =
|
||||||
// Auto-confirm when both ends bind to the SAME element and the end point
|
isBindingEnabled(this.state) &&
|
||||||
// lands on the outline rather than inside it
|
getHoveredElementForBinding(
|
||||||
const endOutsideSameElement =
|
pointFrom<GlobalPoint>(
|
||||||
start?.mode != null &&
|
this.lastPointerMoveCoords?.x ??
|
||||||
end.mode != null &&
|
rx + multiElement.points[multiElement.points.length - 1][0],
|
||||||
start.element.id === end.element.id &&
|
this.lastPointerMoveCoords?.y ??
|
||||||
!isPointInElement(end.focusPoint, end.element, elementsMap);
|
ry + multiElement.points[multiElement.points.length - 1][1],
|
||||||
const boundOutsideFromElsewhere =
|
|
||||||
end.mode === "orbit" &&
|
|
||||||
multiElement.startBinding?.elementId !== end.element?.id;
|
|
||||||
const lastCommittedPointIsInsideCommitZone =
|
|
||||||
lastCommittedPoint &&
|
|
||||||
pointDistance(
|
|
||||||
pointFrom(
|
|
||||||
pointerDownState.origin.x - rx,
|
|
||||||
pointerDownState.origin.y - ry,
|
|
||||||
),
|
),
|
||||||
lastCommittedPoint,
|
this.scene.getNonDeletedElements(),
|
||||||
) < LINE_CONFIRM_THRESHOLD;
|
this.scene.getNonDeletedElementsMap(),
|
||||||
|
);
|
||||||
|
|
||||||
// clicking inside commit zone → finalize arrow
|
// clicking inside commit zone → finalize arrow
|
||||||
if (
|
if (
|
||||||
boundOutsideFromElsewhere || // Outside -> orbit: Bind immediately
|
(isBindingElement(multiElement) && hoveredElementForBinding) ||
|
||||||
endOutsideSameElement || // End outside the start's element: Bind immediately
|
(multiElement.points.length > 1 &&
|
||||||
(multiElement.points.length > 1 && lastCommittedPointIsInsideCommitZone)
|
lastCommittedPoint &&
|
||||||
|
pointDistance(
|
||||||
|
pointFrom(
|
||||||
|
pointerDownState.origin.x - rx,
|
||||||
|
pointerDownState.origin.y - ry,
|
||||||
|
),
|
||||||
|
lastCommittedPoint,
|
||||||
|
) < LINE_CONFIRM_THRESHOLD)
|
||||||
) {
|
) {
|
||||||
this.actionManager.executeAction(actionFinalize, "ui", {
|
this.actionManager.executeAction(actionFinalize, "ui", {
|
||||||
event: event.nativeEvent,
|
event: event.nativeEvent,
|
||||||
@@ -9354,7 +9344,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
strokeColor: this.state.currentItemStrokeColor,
|
strokeColor: this.state.currentItemStrokeColor,
|
||||||
backgroundColor: this.state.currentItemBackgroundColor,
|
backgroundColor: this.state.currentItemBackgroundColor,
|
||||||
fillStyle: this.state.currentItemFillStyle,
|
fillStyle: this.state.currentItemFillStyle,
|
||||||
strokeWidth: this.getCurrentItemStrokeWidth(elementType),
|
strokeWidth: this.state.currentItemStrokeWidth,
|
||||||
strokeStyle: this.state.currentItemStrokeStyle,
|
strokeStyle: this.state.currentItemStrokeStyle,
|
||||||
roughness: this.state.currentItemRoughness,
|
roughness: this.state.currentItemRoughness,
|
||||||
opacity: this.state.currentItemOpacity,
|
opacity: this.state.currentItemOpacity,
|
||||||
@@ -9381,7 +9371,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
strokeColor: this.state.currentItemStrokeColor,
|
strokeColor: this.state.currentItemStrokeColor,
|
||||||
backgroundColor: this.state.currentItemBackgroundColor,
|
backgroundColor: this.state.currentItemBackgroundColor,
|
||||||
fillStyle: this.state.currentItemFillStyle,
|
fillStyle: this.state.currentItemFillStyle,
|
||||||
strokeWidth: this.getCurrentItemStrokeWidth(elementType),
|
strokeWidth: this.state.currentItemStrokeWidth,
|
||||||
strokeStyle: this.state.currentItemStrokeStyle,
|
strokeStyle: this.state.currentItemStrokeStyle,
|
||||||
roughness: this.state.currentItemRoughness,
|
roughness: this.state.currentItemRoughness,
|
||||||
opacity: this.state.currentItemOpacity,
|
opacity: this.state.currentItemOpacity,
|
||||||
@@ -9518,13 +9508,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getCurrentItemStrokeWidth(elementType: ExcalidrawElement["type"]) {
|
|
||||||
return getStrokeWidthByKey(
|
|
||||||
elementType,
|
|
||||||
this.state.currentItemStrokeWidthKey,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private createGenericElementOnPointerDown = (
|
private createGenericElementOnPointerDown = (
|
||||||
elementType: ExcalidrawGenericElement["type"] | "embeddable",
|
elementType: ExcalidrawGenericElement["type"] | "embeddable",
|
||||||
pointerDownState: PointerDownState,
|
pointerDownState: PointerDownState,
|
||||||
@@ -9548,7 +9531,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
strokeColor: this.state.currentItemStrokeColor,
|
strokeColor: this.state.currentItemStrokeColor,
|
||||||
backgroundColor: this.state.currentItemBackgroundColor,
|
backgroundColor: this.state.currentItemBackgroundColor,
|
||||||
fillStyle: this.state.currentItemFillStyle,
|
fillStyle: this.state.currentItemFillStyle,
|
||||||
strokeWidth: this.getCurrentItemStrokeWidth(elementType),
|
strokeWidth: this.state.currentItemStrokeWidth,
|
||||||
strokeStyle: this.state.currentItemStrokeStyle,
|
strokeStyle: this.state.currentItemStrokeStyle,
|
||||||
roughness: this.state.currentItemRoughness,
|
roughness: this.state.currentItemRoughness,
|
||||||
opacity: this.state.currentItemOpacity,
|
opacity: this.state.currentItemOpacity,
|
||||||
@@ -10881,6 +10864,13 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isLinearElement(newElement)) {
|
if (isLinearElement(newElement)) {
|
||||||
|
if (
|
||||||
|
newElement!.points.length > 1 &&
|
||||||
|
newElement.points[1][0] !== 0 &&
|
||||||
|
newElement.points[1][1] !== 0
|
||||||
|
) {
|
||||||
|
this.store.scheduleCapture();
|
||||||
|
}
|
||||||
const pointerCoords = viewportCoordsToSceneCoords(
|
const pointerCoords = viewportCoordsToSceneCoords(
|
||||||
childEvent,
|
childEvent,
|
||||||
this.state,
|
this.state,
|
||||||
@@ -10918,15 +10908,23 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
|
|
||||||
this.actionManager.executeAction(actionFinalize);
|
this.actionManager.executeAction(actionFinalize);
|
||||||
} else {
|
} else {
|
||||||
// Movement out of commit area will create the point
|
const dx = pointerCoords.x - newElement.x;
|
||||||
|
const dy = pointerCoords.y - newElement.y;
|
||||||
|
|
||||||
|
this.scene.mutateElement(
|
||||||
|
newElement,
|
||||||
|
{
|
||||||
|
points: [newElement.points[0], pointFrom<LocalPoint>(dx, dy)],
|
||||||
|
},
|
||||||
|
{ informMutation: false, isDragging: false },
|
||||||
|
);
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
multiElement: newElement,
|
multiElement: newElement,
|
||||||
newElement,
|
newElement,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (pointerDownState.drag.hasOccurred && !multiElement) {
|
} else if (pointerDownState.drag.hasOccurred && !multiElement) {
|
||||||
this.store.scheduleCapture();
|
|
||||||
|
|
||||||
if (isLinearElement(newElement)) {
|
if (isLinearElement(newElement)) {
|
||||||
this.actionManager.executeAction(actionFinalize, "ui", {
|
this.actionManager.executeAction(actionFinalize, "ui", {
|
||||||
event: childEvent,
|
event: childEvent,
|
||||||
|
|||||||
@@ -120,24 +120,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// on tablet, the pen mode button is rendered as a separate floating button
|
|
||||||
// below the compact actions menu (see LayerUI.tsx)
|
|
||||||
.App-menu_top__left > .ToolIcon__penMode {
|
|
||||||
justify-self: center;
|
|
||||||
|
|
||||||
.ToolIcon__icon {
|
|
||||||
width: var(--lg-button-size);
|
|
||||||
height: var(--lg-button-size);
|
|
||||||
background-color: var(--island-bg-color);
|
|
||||||
box-shadow: var(--shadow-island);
|
|
||||||
}
|
|
||||||
|
|
||||||
// no shadow while pen mode is active (the active fill is enough)
|
|
||||||
.ToolIcon_type_checkbox:checked + .ToolIcon__icon {
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.disable-view-mode {
|
.disable-view-mode {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|||||||
@@ -235,6 +235,8 @@ const LayerUI = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const renderSelectedShapeActions = () => {
|
const renderSelectedShapeActions = () => {
|
||||||
|
const isCompactMode = isCompactStylesPanel;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Section
|
<Section
|
||||||
heading="selectedShapeActions"
|
heading="selectedShapeActions"
|
||||||
@@ -242,7 +244,7 @@ const LayerUI = ({
|
|||||||
"transition-left": appState.zenModeEnabled,
|
"transition-left": appState.zenModeEnabled,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{isCompactStylesPanel ? (
|
{isCompactMode ? (
|
||||||
<Island
|
<Island
|
||||||
className={clsx("compact-shape-actions-island")}
|
className={clsx("compact-shape-actions-island")}
|
||||||
padding={0}
|
padding={0}
|
||||||
@@ -310,23 +312,6 @@ const LayerUI = ({
|
|||||||
>
|
>
|
||||||
{shouldRenderSelectedShapeActions && renderSelectedShapeActions()}
|
{shouldRenderSelectedShapeActions && renderSelectedShapeActions()}
|
||||||
</div>
|
</div>
|
||||||
{/* in compact UI the pen mode button lives outside the toolbar, as
|
|
||||||
a separate floating button below the compact actions menu
|
|
||||||
(same as we render it on mobile); shown alongside the compact
|
|
||||||
actions island, i.e. when a drawing tool or elements are
|
|
||||||
selected */}
|
|
||||||
{isCompactStylesPanel &&
|
|
||||||
!appState.viewModeEnabled &&
|
|
||||||
shouldRenderSelectedShapeActions && (
|
|
||||||
<PenModeButton
|
|
||||||
zenModeEnabled={appState.zenModeEnabled}
|
|
||||||
checked={appState.penMode}
|
|
||||||
onChange={() => onPenModeToggle(null)}
|
|
||||||
title={t("toolBar.penMode")}
|
|
||||||
isMobile
|
|
||||||
penDetected={appState.penDetected}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Stack.Col>
|
</Stack.Col>
|
||||||
{!appState.viewModeEnabled &&
|
{!appState.viewModeEnabled &&
|
||||||
appState.openDialog?.name !== "elementLinkSelector" && (
|
appState.openDialog?.name !== "elementLinkSelector" && (
|
||||||
@@ -358,18 +343,13 @@ const LayerUI = ({
|
|||||||
/>
|
/>
|
||||||
{heading}
|
{heading}
|
||||||
<Stack.Row gap={spacing.toolbarInnerRowGap}>
|
<Stack.Row gap={spacing.toolbarInnerRowGap}>
|
||||||
{/* in compact UI the pen mode button is rendered
|
<PenModeButton
|
||||||
as a separate floating button below the compact
|
zenModeEnabled={appState.zenModeEnabled}
|
||||||
actions menu */}
|
checked={appState.penMode}
|
||||||
{!isCompactStylesPanel && (
|
onChange={() => onPenModeToggle(null)}
|
||||||
<PenModeButton
|
title={t("toolBar.penMode")}
|
||||||
zenModeEnabled={appState.zenModeEnabled}
|
penDetected={appState.penDetected}
|
||||||
checked={appState.penMode}
|
/>
|
||||||
onChange={() => onPenModeToggle(null)}
|
|
||||||
title={t("toolBar.penMode")}
|
|
||||||
penDetected={appState.penDetected}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<LockButton
|
<LockButton
|
||||||
checked={appState.activeTool.locked}
|
checked={appState.activeTool.locked}
|
||||||
onChange={onLockToggle}
|
onChange={onLockToggle}
|
||||||
|
|||||||
@@ -1249,74 +1249,6 @@ export const SloppinessCartoonistIcon = createIcon(
|
|||||||
modifiedTablerIconProps,
|
modifiedTablerIconProps,
|
||||||
);
|
);
|
||||||
|
|
||||||
export const strokeVariabilityConstantIcon = createIcon(
|
|
||||||
<g>
|
|
||||||
<path
|
|
||||||
d="M4 12 C 5 8, 6 8, 8 12"
|
|
||||||
fill="none"
|
|
||||||
strokeWidth="1"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M8 12 C 9 16, 10 16, 12 12"
|
|
||||||
fill="none"
|
|
||||||
strokeWidth="1"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M12 12 C 14 8, 15 8, 16 12"
|
|
||||||
fill="none"
|
|
||||||
strokeWidth="1"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M16 12 C 17 16, 18 16, 19 12"
|
|
||||||
fill="none"
|
|
||||||
strokeWidth="1"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
/>
|
|
||||||
</g>,
|
|
||||||
tablerIconProps,
|
|
||||||
);
|
|
||||||
|
|
||||||
export const strokeVariabilityVariableIcon = createIcon(
|
|
||||||
<g>
|
|
||||||
<path
|
|
||||||
d="M4 12 C 5 8, 6 8, 8 12"
|
|
||||||
fill="none"
|
|
||||||
strokeWidth="1.5"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M8 12 C 9 16, 10 16, 12 12"
|
|
||||||
fill="none"
|
|
||||||
strokeWidth="2"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M12 12 C 14 8, 15 8, 16 12"
|
|
||||||
fill="none"
|
|
||||||
strokeWidth="2.75"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M16 12 C 17 16, 18 16, 19 12"
|
|
||||||
fill="none"
|
|
||||||
strokeWidth="3.25"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
/>
|
|
||||||
</g>,
|
|
||||||
tablerIconProps,
|
|
||||||
);
|
|
||||||
|
|
||||||
export const EdgeSharpIcon = createIcon(
|
export const EdgeSharpIcon = createIcon(
|
||||||
<svg strokeWidth="1.5">
|
<svg strokeWidth="1.5">
|
||||||
<path d="M3.33334 9.99998V6.66665C3.33334 6.04326 3.33403 4.9332 3.33539 3.33646C4.95233 3.33436 6.06276 3.33331 6.66668 3.33331H10" />
|
<path d="M3.33334 9.99998V6.66665C3.33334 6.04326 3.33403 4.9332 3.33539 3.33646C4.95233 3.33436 6.06276 3.33331 6.66668 3.33331H10" />
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { isFiniteNumber, isValidPoint, pointFrom } from "@excalidraw/math";
|
|||||||
import {
|
import {
|
||||||
type CombineBrandsIfNeeded,
|
type CombineBrandsIfNeeded,
|
||||||
DEFAULT_FONT_FAMILY,
|
DEFAULT_FONT_FAMILY,
|
||||||
DEFAULT_STROKE_STREAMLINE,
|
|
||||||
DEFAULT_TEXT_ALIGN,
|
DEFAULT_TEXT_ALIGN,
|
||||||
DEFAULT_VERTICAL_ALIGN,
|
DEFAULT_VERTICAL_ALIGN,
|
||||||
FONT_FAMILY,
|
FONT_FAMILY,
|
||||||
@@ -19,9 +18,6 @@ import {
|
|||||||
getSizeFromPoints,
|
getSizeFromPoints,
|
||||||
normalizeLink,
|
normalizeLink,
|
||||||
getLineHeight,
|
getLineHeight,
|
||||||
STROKE_WIDTH,
|
|
||||||
STROKE_WIDTH_KEYS,
|
|
||||||
type StrokeWidthKey,
|
|
||||||
} from "@excalidraw/common";
|
} from "@excalidraw/common";
|
||||||
import {
|
import {
|
||||||
calculateFixedPointForNonElbowArrowBinding,
|
calculateFixedPointForNonElbowArrowBinding,
|
||||||
@@ -58,6 +54,11 @@ import { getNormalizedDimensions } from "@excalidraw/element";
|
|||||||
|
|
||||||
import { isInvisiblySmallElement } from "@excalidraw/element";
|
import { isInvisiblySmallElement } from "@excalidraw/element";
|
||||||
|
|
||||||
|
import {
|
||||||
|
elementOverlapsWithFrame,
|
||||||
|
isFrameLikeElement,
|
||||||
|
} from "@excalidraw/element";
|
||||||
|
|
||||||
import type { LocalPoint, Radians } from "@excalidraw/math";
|
import type { LocalPoint, Radians } from "@excalidraw/math";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@@ -74,7 +75,6 @@ import type {
|
|||||||
FontFamilyValues,
|
FontFamilyValues,
|
||||||
NonDeletedSceneElementsMap,
|
NonDeletedSceneElementsMap,
|
||||||
OrderedExcalidrawElement,
|
OrderedExcalidrawElement,
|
||||||
StrokeVariability,
|
|
||||||
StrokeRoundness,
|
StrokeRoundness,
|
||||||
} from "@excalidraw/element/types";
|
} from "@excalidraw/element/types";
|
||||||
|
|
||||||
@@ -193,43 +193,6 @@ export type RestoredDataState = {
|
|||||||
files: BinaryFiles;
|
files: BinaryFiles;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ALLOWED_STROKE_VARIABILITIES = new Set<StrokeVariability>([
|
|
||||||
"constant",
|
|
||||||
"variable",
|
|
||||||
]);
|
|
||||||
|
|
||||||
const restoreStrokeVariability = (
|
|
||||||
variability: unknown,
|
|
||||||
defaultValue: StrokeVariability,
|
|
||||||
): StrokeVariability => {
|
|
||||||
return typeof variability === "string" &&
|
|
||||||
ALLOWED_STROKE_VARIABILITIES.has(variability as StrokeVariability)
|
|
||||||
? (variability as StrokeVariability)
|
|
||||||
: defaultValue;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getStrokeWidthKey = (strokeWidth: unknown): StrokeWidthKey | null => {
|
|
||||||
return isFiniteNumber(strokeWidth)
|
|
||||||
? STROKE_WIDTH_KEYS.find((key) => STROKE_WIDTH[key] === strokeWidth) ?? null
|
|
||||||
: null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const restoreFreedrawStrokeOptions = (
|
|
||||||
strokeOptions: unknown,
|
|
||||||
): { variability: StrokeVariability; streamline: number } => {
|
|
||||||
const options =
|
|
||||||
strokeOptions && typeof strokeOptions === "object"
|
|
||||||
? (strokeOptions as { variability?: unknown; streamline?: unknown })
|
|
||||||
: null;
|
|
||||||
|
|
||||||
return {
|
|
||||||
variability: restoreStrokeVariability(options?.variability, "variable"),
|
|
||||||
streamline: isFiniteNumber(options?.streamline)
|
|
||||||
? options?.streamline
|
|
||||||
: DEFAULT_STROKE_STREAMLINE,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const getFontFamilyByName = (fontFamilyName: string): FontFamilyValues => {
|
const getFontFamilyByName = (fontFamilyName: string): FontFamilyValues => {
|
||||||
if (Object.keys(FONT_FAMILY).includes(fontFamilyName)) {
|
if (Object.keys(FONT_FAMILY).includes(fontFamilyName)) {
|
||||||
return FONT_FAMILY[
|
return FONT_FAMILY[
|
||||||
@@ -525,7 +488,6 @@ export const restoreElement = (
|
|||||||
return restoreElementWithProperties(element, {
|
return restoreElementWithProperties(element, {
|
||||||
points,
|
points,
|
||||||
simulatePressure: element.simulatePressure,
|
simulatePressure: element.simulatePressure,
|
||||||
strokeOptions: restoreFreedrawStrokeOptions(element.strokeOptions),
|
|
||||||
pressures,
|
pressures,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -787,20 +749,78 @@ const repairBoundElement = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove an element's frameId if its containing frame is non-existent
|
* Remove an element's frameId if:
|
||||||
|
* - its containing frame is non-existent (or isn't actually a frame), or
|
||||||
|
* - the element no longer (precisely) overlaps its containing frame.
|
||||||
|
*
|
||||||
|
* Frame membership is treated atomically per group: a grouped element keeps its
|
||||||
|
* frameId as long as ANY element of its (outermost) group still overlaps the
|
||||||
|
* frame, so we never split a group across frame membership. Bound text inherits
|
||||||
|
* its container's geometry/membership.
|
||||||
*
|
*
|
||||||
* NOTE mutates elements.
|
* NOTE mutates elements.
|
||||||
*/
|
*/
|
||||||
const repairFrameMembership = (
|
const repairFrameMembership = (
|
||||||
element: Mutable<ExcalidrawElement>,
|
element: Mutable<ExcalidrawElement>,
|
||||||
elementsMap: Map<string, Mutable<ExcalidrawElement>>,
|
elementsMap: Map<string, Mutable<ExcalidrawElement>>,
|
||||||
|
/** elements bucketed by outermost groupId (see `restoreElements`) */
|
||||||
|
elementsByGroup: Map<string, Mutable<ExcalidrawElement>[]>,
|
||||||
|
/** memoizes the overlap decision per `${frameId}:${groupId}` */
|
||||||
|
checkedFrameGroups: Map<string, boolean>,
|
||||||
) => {
|
) => {
|
||||||
if (element.frameId) {
|
if (!element.frameId) {
|
||||||
const containingFrame = elementsMap.get(element.frameId);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!containingFrame) {
|
const containingFrame = elementsMap.get(element.frameId);
|
||||||
element.frameId = null;
|
|
||||||
|
// frame no longer exists (or the frameId doesn't point to a frame)
|
||||||
|
if (!containingFrame || !isFrameLikeElement(containingFrame)) {
|
||||||
|
element.frameId = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't recompute geometry for deleted elements
|
||||||
|
if (element.isDeleted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// a bound text follows its container's geometry / membership
|
||||||
|
const geometryElement =
|
||||||
|
isTextElement(element) && element.containerId
|
||||||
|
? elementsMap.get(element.containerId) ?? element
|
||||||
|
: element;
|
||||||
|
|
||||||
|
const outerGroupId =
|
||||||
|
geometryElement.groupIds[geometryElement.groupIds.length - 1];
|
||||||
|
|
||||||
|
let overlapsFrame: boolean;
|
||||||
|
if (outerGroupId) {
|
||||||
|
const cacheKey = `${containingFrame.id}:${outerGroupId}`;
|
||||||
|
const cached = checkedFrameGroups.get(cacheKey);
|
||||||
|
if (cached !== undefined) {
|
||||||
|
overlapsFrame = cached;
|
||||||
|
} else {
|
||||||
|
// group membership is atomic — keep the element as long as ANY element
|
||||||
|
// of its group still overlaps the frame, so we never split a group.
|
||||||
|
const groupElements = elementsByGroup.get(outerGroupId) ?? [
|
||||||
|
geometryElement,
|
||||||
|
];
|
||||||
|
overlapsFrame = groupElements.some((groupElement) =>
|
||||||
|
elementOverlapsWithFrame(groupElement, containingFrame, elementsMap),
|
||||||
|
);
|
||||||
|
checkedFrameGroups.set(cacheKey, overlapsFrame);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
overlapsFrame = elementOverlapsWithFrame(
|
||||||
|
geometryElement,
|
||||||
|
containingFrame,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!overlapsFrame) {
|
||||||
|
element.frameId = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -879,9 +899,32 @@ export const restoreElements = <T extends ExcalidrawElement>(
|
|||||||
|
|
||||||
// repair binding. Mutates elements.
|
// repair binding. Mutates elements.
|
||||||
const restoredElementsMap = arrayToMap(restoredElements);
|
const restoredElementsMap = arrayToMap(restoredElements);
|
||||||
|
|
||||||
|
// index elements by their outermost groupId once, so frame-membership repair
|
||||||
|
// can resolve a group's members in O(1) instead of scanning all elements per
|
||||||
|
// group (getElementsInGroup) — a big win on large, heavily-grouped scenes.
|
||||||
|
const elementsByGroup = new Map<string, Mutable<ExcalidrawElement>[]>();
|
||||||
|
for (const element of restoredElements) {
|
||||||
|
const outerGroupId = element.groupIds[element.groupIds.length - 1];
|
||||||
|
if (outerGroupId) {
|
||||||
|
const members = elementsByGroup.get(outerGroupId);
|
||||||
|
if (members) {
|
||||||
|
members.push(element);
|
||||||
|
} else {
|
||||||
|
elementsByGroup.set(outerGroupId, [element]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkedFrameGroups = new Map<string, boolean>();
|
||||||
for (const element of restoredElements) {
|
for (const element of restoredElements) {
|
||||||
if (element.frameId) {
|
if (element.frameId) {
|
||||||
repairFrameMembership(element, restoredElementsMap);
|
repairFrameMembership(
|
||||||
|
element,
|
||||||
|
restoredElementsMap,
|
||||||
|
elementsByGroup,
|
||||||
|
checkedFrameGroups,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isTextElement(element) && element.containerId) {
|
if (isTextElement(element) && element.containerId) {
|
||||||
@@ -1099,13 +1142,6 @@ export const restoreAppState = (
|
|||||||
nextAppState.boxSelectionMode = boxSelectionMode;
|
nextAppState.boxSelectionMode = boxSelectionMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
// legacy
|
|
||||||
if ((appState as any).currentItemStrokeWidth !== undefined) {
|
|
||||||
nextAppState.currentItemStrokeWidthKey =
|
|
||||||
getStrokeWidthKey((appState as any).currentItemStrokeWidth) ??
|
|
||||||
defaultAppState.currentItemStrokeWidthKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...nextAppState,
|
...nextAppState,
|
||||||
cursorButton: localAppState?.cursorButton || "up",
|
cursorButton: localAppState?.cursorButton || "up",
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import {
|
|||||||
applyDarkModeFilter,
|
applyDarkModeFilter,
|
||||||
DEFAULT_IMAGE_OPTIONS,
|
DEFAULT_IMAGE_OPTIONS,
|
||||||
DEFAULT_UI_OPTIONS,
|
DEFAULT_UI_OPTIONS,
|
||||||
getStrokeWidthByKey,
|
|
||||||
isShallowEqual,
|
isShallowEqual,
|
||||||
} from "@excalidraw/common";
|
} from "@excalidraw/common";
|
||||||
|
|
||||||
@@ -451,4 +450,4 @@ export function useExcalidrawStateValue(
|
|||||||
|
|
||||||
export { _useOnAppStateChange as useOnExcalidrawStateChange };
|
export { _useOnAppStateChange as useOnExcalidrawStateChange };
|
||||||
|
|
||||||
export { applyDarkModeFilter, getStrokeWidthByKey };
|
export { applyDarkModeFilter };
|
||||||
|
|||||||
@@ -35,9 +35,6 @@
|
|||||||
"strokeStyle_dashed": "Dashed",
|
"strokeStyle_dashed": "Dashed",
|
||||||
"strokeStyle_dotted": "Dotted",
|
"strokeStyle_dotted": "Dotted",
|
||||||
"sloppiness": "Sloppiness",
|
"sloppiness": "Sloppiness",
|
||||||
"pressure": "Pressure",
|
|
||||||
"pressure_constant": "Constant",
|
|
||||||
"pressure_variable": "Variable",
|
|
||||||
"opacity": "Opacity",
|
"opacity": "Opacity",
|
||||||
"textAlign": "Text align",
|
"textAlign": "Text align",
|
||||||
"edges": "Edges",
|
"edges": "Edges",
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ export {
|
|||||||
hasBackground,
|
hasBackground,
|
||||||
hasStrokeWidth,
|
hasStrokeWidth,
|
||||||
hasStrokeStyle,
|
hasStrokeStyle,
|
||||||
hasFreedrawMode,
|
|
||||||
canHaveArrowheads,
|
canHaveArrowheads,
|
||||||
canChangeRoundness,
|
canChangeRoundness,
|
||||||
} from "@excalidraw/element";
|
} from "@excalidraw/element";
|
||||||
|
|||||||
@@ -904,8 +904,7 @@ exports[`contextMenu element > right-clicking on a group should select whole gro
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -1104,8 +1103,7 @@ exports[`contextMenu element > selecting 'Add to library' in context menu adds e
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -1319,8 +1317,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -1651,8 +1648,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -1983,8 +1979,7 @@ exports[`contextMenu element > selecting 'Copy styles' in context menu copies st
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -2198,8 +2193,7 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -2440,8 +2434,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -2739,8 +2732,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -3112,8 +3104,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#e03131",
|
"currentItemStrokeColor": "#e03131",
|
||||||
"currentItemStrokeStyle": "dotted",
|
"currentItemStrokeStyle": "dotted",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "bold",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -3223,11 +3214,11 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
"seed": 1278240551,
|
"seed": 1278240551,
|
||||||
"strokeColor": "#e03131",
|
"strokeColor": "#e03131",
|
||||||
"strokeStyle": "dotted",
|
"strokeStyle": "dotted",
|
||||||
"strokeWidth": 4,
|
"strokeWidth": 2,
|
||||||
"type": "rectangle",
|
"type": "rectangle",
|
||||||
"updated": 1,
|
"updated": 1,
|
||||||
"version": 4,
|
"version": 4,
|
||||||
"versionNonce": 1349943049,
|
"versionNonce": 1402203177,
|
||||||
"width": 20,
|
"width": 20,
|
||||||
"x": -10,
|
"x": -10,
|
||||||
"y": 0,
|
"y": 0,
|
||||||
@@ -3252,14 +3243,14 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
"opacity": 60,
|
"opacity": 60,
|
||||||
"roughness": 2,
|
"roughness": 2,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"seed": 406373543,
|
"seed": 1898319239,
|
||||||
"strokeColor": "#e03131",
|
"strokeColor": "#e03131",
|
||||||
"strokeStyle": "dotted",
|
"strokeStyle": "dotted",
|
||||||
"strokeWidth": 4,
|
"strokeWidth": 2,
|
||||||
"type": "rectangle",
|
"type": "rectangle",
|
||||||
"updated": 1,
|
"updated": 1,
|
||||||
"version": 10,
|
"version": 9,
|
||||||
"versionNonce": 1402203177,
|
"versionNonce": 941653321,
|
||||||
"width": 20,
|
"width": 20,
|
||||||
"x": 20,
|
"x": 20,
|
||||||
"y": 30,
|
"y": 30,
|
||||||
@@ -3268,7 +3259,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
|
|
||||||
exports[`contextMenu element > selecting 'Paste styles' in context menu pastes styles > [end of test] number of elements 1`] = `2`;
|
exports[`contextMenu element > selecting 'Paste styles' in context menu pastes styles > [end of test] number of elements 1`] = `2`;
|
||||||
|
|
||||||
exports[`contextMenu element > selecting 'Paste styles' in context menu pastes styles > [end of test] number of renders 1`] = `17`;
|
exports[`contextMenu element > selecting 'Paste styles' in context menu pastes styles > [end of test] number of renders 1`] = `16`;
|
||||||
|
|
||||||
exports[`contextMenu element > selecting 'Paste styles' in context menu pastes styles > [end of test] redo stack 1`] = `[]`;
|
exports[`contextMenu element > selecting 'Paste styles' in context menu pastes styles > [end of test] redo stack 1`] = `[]`;
|
||||||
|
|
||||||
@@ -3468,11 +3459,11 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
"updated": {
|
"updated": {
|
||||||
"id3": {
|
"id3": {
|
||||||
"deleted": {
|
"deleted": {
|
||||||
"strokeWidth": 4,
|
"strokeStyle": "dotted",
|
||||||
"version": 7,
|
"version": 7,
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"strokeWidth": 2,
|
"strokeStyle": "solid",
|
||||||
"version": 6,
|
"version": 6,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -3493,11 +3484,11 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
"updated": {
|
"updated": {
|
||||||
"id3": {
|
"id3": {
|
||||||
"deleted": {
|
"deleted": {
|
||||||
"strokeStyle": "dotted",
|
"roughness": 2,
|
||||||
"version": 8,
|
"version": 8,
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"strokeStyle": "solid",
|
"roughness": 1,
|
||||||
"version": 7,
|
"version": 7,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -3518,11 +3509,11 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
"updated": {
|
"updated": {
|
||||||
"id3": {
|
"id3": {
|
||||||
"deleted": {
|
"deleted": {
|
||||||
"roughness": 2,
|
"opacity": 60,
|
||||||
"version": 9,
|
"version": 9,
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"roughness": 1,
|
"opacity": 100,
|
||||||
"version": 8,
|
"version": 8,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -3530,31 +3521,6 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
},
|
},
|
||||||
"id": "id17",
|
"id": "id17",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"appState": AppStateDelta {
|
|
||||||
"delta": Delta {
|
|
||||||
"deleted": {},
|
|
||||||
"inserted": {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"elements": {
|
|
||||||
"added": {},
|
|
||||||
"removed": {},
|
|
||||||
"updated": {
|
|
||||||
"id3": {
|
|
||||||
"deleted": {
|
|
||||||
"opacity": 60,
|
|
||||||
"version": 10,
|
|
||||||
},
|
|
||||||
"inserted": {
|
|
||||||
"opacity": 100,
|
|
||||||
"version": 9,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"id": "id19",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"appState": AppStateDelta {
|
"appState": AppStateDelta {
|
||||||
"delta": Delta {
|
"delta": Delta {
|
||||||
@@ -3582,7 +3548,6 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
"roughness": 2,
|
"roughness": 2,
|
||||||
"strokeColor": "#e03131",
|
"strokeColor": "#e03131",
|
||||||
"strokeStyle": "dotted",
|
"strokeStyle": "dotted",
|
||||||
"strokeWidth": 4,
|
|
||||||
"version": 4,
|
"version": 4,
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
@@ -3592,13 +3557,12 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 2,
|
|
||||||
"version": 3,
|
"version": 3,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"id": "id21",
|
"id": "id19",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
@@ -3633,8 +3597,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -3957,8 +3920,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -4281,8 +4243,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -5567,8 +5528,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -6785,8 +6745,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -7743,8 +7702,7 @@ exports[`contextMenu element > shows context menu for canvas > [end of test] app
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -8744,8 +8702,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -9736,8 +9693,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
|
|||||||
@@ -30,8 +30,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -665,8 +664,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -1228,8 +1226,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -1589,8 +1586,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -1952,8 +1948,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -2216,8 +2211,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -2704,8 +2698,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -3008,8 +3001,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -3328,8 +3320,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -3623,8 +3614,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -3910,8 +3900,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -4146,8 +4135,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -4404,8 +4392,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -4676,8 +4663,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -4906,8 +4892,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -5136,8 +5121,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -5384,8 +5368,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -5641,8 +5624,7 @@ exports[`history > multiplayer undo/redo > conflicts in frames and their childre
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -5900,8 +5882,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -6230,8 +6211,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -6658,8 +6638,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -7033,8 +7012,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -7346,8 +7324,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -7640,8 +7617,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -7871,8 +7847,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -8224,8 +8199,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -8577,8 +8551,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -8984,8 +8957,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -9111,12 +9083,8 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte
|
|||||||
"roundness": null,
|
"roundness": null,
|
||||||
"simulatePressure": false,
|
"simulatePressure": false,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeOptions": {
|
|
||||||
"streamline": "0.50000",
|
|
||||||
"variability": "constant",
|
|
||||||
},
|
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 1,
|
"strokeWidth": 2,
|
||||||
"type": "freedraw",
|
"type": "freedraw",
|
||||||
"updated": 1,
|
"updated": 1,
|
||||||
"version": 7,
|
"version": 7,
|
||||||
@@ -9217,12 +9185,8 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte
|
|||||||
"roundness": null,
|
"roundness": null,
|
||||||
"simulatePressure": false,
|
"simulatePressure": false,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeOptions": {
|
|
||||||
"streamline": "0.50000",
|
|
||||||
"variability": "constant",
|
|
||||||
},
|
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 1,
|
"strokeWidth": 2,
|
||||||
"type": "freedraw",
|
"type": "freedraw",
|
||||||
"version": 7,
|
"version": 7,
|
||||||
"width": 50,
|
"width": 50,
|
||||||
@@ -9272,8 +9236,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -9537,8 +9500,7 @@ exports[`history > multiplayer undo/redo > should not override remote changes on
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -9803,8 +9765,7 @@ exports[`history > multiplayer undo/redo > should not override remote changes on
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -10036,8 +9997,7 @@ exports[`history > multiplayer undo/redo > should override remotely added groups
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -10334,8 +10294,7 @@ exports[`history > multiplayer undo/redo > should override remotely added points
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -10653,8 +10612,7 @@ exports[`history > multiplayer undo/redo > should redistribute deltas when eleme
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -10890,8 +10848,7 @@ exports[`history > multiplayer undo/redo > should redraw arrows on undo > [end o
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -11332,7 +11289,7 @@ exports[`history > multiplayer undo/redo > should support undo and redo when esc
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeWidthKey": "medium",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -11816,8 +11773,7 @@ exports[`history > multiplayer undo/redo > should update history entries after r
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -12077,8 +12033,7 @@ exports[`history > singleplayer undo/redo > remounting undo/redo buttons should
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -12313,8 +12268,7 @@ exports[`history > singleplayer undo/redo > should clear the redo stack on eleme
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -12551,8 +12505,7 @@ exports[`history > singleplayer undo/redo > should create entry when selecting f
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#e03131",
|
"currentItemStrokeColor": "#e03131",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -12703,12 +12656,8 @@ exports[`history > singleplayer undo/redo > should create entry when selecting f
|
|||||||
"roundness": null,
|
"roundness": null,
|
||||||
"simulatePressure": false,
|
"simulatePressure": false,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeOptions": {
|
|
||||||
"streamline": "0.50000",
|
|
||||||
"variability": "constant",
|
|
||||||
},
|
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 1,
|
"strokeWidth": 2,
|
||||||
"type": "freedraw",
|
"type": "freedraw",
|
||||||
"updated": 1,
|
"updated": 1,
|
||||||
"version": 5,
|
"version": 5,
|
||||||
@@ -12757,12 +12706,8 @@ exports[`history > singleplayer undo/redo > should create entry when selecting f
|
|||||||
"roundness": null,
|
"roundness": null,
|
||||||
"simulatePressure": false,
|
"simulatePressure": false,
|
||||||
"strokeColor": "#e03131",
|
"strokeColor": "#e03131",
|
||||||
"strokeOptions": {
|
|
||||||
"streamline": "0.50000",
|
|
||||||
"variability": "constant",
|
|
||||||
},
|
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 1,
|
"strokeWidth": 2,
|
||||||
"type": "freedraw",
|
"type": "freedraw",
|
||||||
"updated": 1,
|
"updated": 1,
|
||||||
"version": 4,
|
"version": 4,
|
||||||
@@ -12900,12 +12845,8 @@ exports[`history > singleplayer undo/redo > should create entry when selecting f
|
|||||||
"roundness": null,
|
"roundness": null,
|
||||||
"simulatePressure": false,
|
"simulatePressure": false,
|
||||||
"strokeColor": "#e03131",
|
"strokeColor": "#e03131",
|
||||||
"strokeOptions": {
|
|
||||||
"streamline": "0.50000",
|
|
||||||
"variability": "constant",
|
|
||||||
},
|
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 1,
|
"strokeWidth": 2,
|
||||||
"type": "freedraw",
|
"type": "freedraw",
|
||||||
"version": 4,
|
"version": 4,
|
||||||
"width": 50,
|
"width": 50,
|
||||||
@@ -12955,8 +12896,7 @@ exports[`history > singleplayer undo/redo > should create new history entry on e
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -13166,8 +13106,7 @@ exports[`history > singleplayer undo/redo > should create new history entry on e
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -13374,8 +13313,7 @@ exports[`history > singleplayer undo/redo > should create new history entry on i
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -13676,8 +13614,7 @@ exports[`history > singleplayer undo/redo > should create new history entry on i
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -13975,8 +13912,7 @@ exports[`history > singleplayer undo/redo > should create new history entry on s
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -14221,8 +14157,7 @@ exports[`history > singleplayer undo/redo > should disable undo/redo buttons whe
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -14459,8 +14394,7 @@ exports[`history > singleplayer undo/redo > should end up with no history entry
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -14697,8 +14631,7 @@ exports[`history > singleplayer undo/redo > should iterate through the history w
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -14945,8 +14878,7 @@ exports[`history > singleplayer undo/redo > should not clear the redo stack on s
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -15277,8 +15209,7 @@ exports[`history > singleplayer undo/redo > should not collapse when applying co
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -15448,8 +15379,7 @@ exports[`history > singleplayer undo/redo > should not end up with history entry
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -15733,8 +15663,7 @@ exports[`history > singleplayer undo/redo > should not end up with history entry
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -15997,8 +15926,7 @@ exports[`history > singleplayer undo/redo > should not modify anything on unrela
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -16151,8 +16079,7 @@ exports[`history > singleplayer undo/redo > should not override appstate changes
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -16434,8 +16361,7 @@ exports[`history > singleplayer undo/redo > should support appstate name or view
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -16597,8 +16523,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -17347,8 +17272,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -17995,8 +17919,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -18643,8 +18566,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -19394,8 +19316,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -20164,8 +20085,7 @@ exports[`history > singleplayer undo/redo > should support changes in elements'
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -20645,8 +20565,7 @@ exports[`history > singleplayer undo/redo > should support duplication of groups
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -21157,8 +21076,7 @@ exports[`history > singleplayer undo/redo > should support element creation, del
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -21617,8 +21535,7 @@ exports[`history > singleplayer undo/redo > should support linear element creati
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
|
|||||||
@@ -30,8 +30,7 @@ exports[`given element A and group of elements B and given both are selected whe
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -457,8 +456,7 @@ exports[`given element A and group of elements B and given both are selected whe
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -874,8 +872,7 @@ exports[`regression tests > Cmd/Ctrl-click exclusively select element under poin
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -1441,8 +1438,7 @@ exports[`regression tests > Drags selected element when hitting only bounding bo
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -1649,8 +1645,7 @@ exports[`regression tests > adjusts z order when grouping > [end of test] appSta
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -2034,8 +2029,7 @@ exports[`regression tests > alt-drag duplicates an element > [end of test] appSt
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -2280,8 +2274,7 @@ exports[`regression tests > arrow keys > [end of test] appState 1`] = `
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -2461,8 +2454,7 @@ exports[`regression tests > can drag element that covers another element, while
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -2787,8 +2779,7 @@ exports[`regression tests > change the properties of a shape > [end of test] app
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1971c2",
|
"currentItemStrokeColor": "#1971c2",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -3043,8 +3034,7 @@ exports[`regression tests > click on an element and drag it > [dragged] appState
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -3285,8 +3275,7 @@ exports[`regression tests > click on an element and drag it > [end of test] appS
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -3522,8 +3511,7 @@ exports[`regression tests > click to select a shape > [end of test] appState 1`]
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -3781,8 +3769,7 @@ exports[`regression tests > click-drag to select a group > [end of test] appStat
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -4096,8 +4083,7 @@ exports[`regression tests > deleting last but one element in editing group shoul
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -4533,8 +4519,7 @@ exports[`regression tests > deselects group of selected elements on pointer down
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "down",
|
"cursorButton": "down",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -4817,8 +4802,7 @@ exports[`regression tests > deselects group of selected elements on pointer up w
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -5094,8 +5078,7 @@ exports[`regression tests > deselects selected element on pointer down when poin
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "down",
|
"cursorButton": "down",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -5303,8 +5286,7 @@ exports[`regression tests > deselects selected element, on pointer up, when clic
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -5504,8 +5486,7 @@ exports[`regression tests > double click to edit a group > [end of test] appStat
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -5898,8 +5879,7 @@ exports[`regression tests > drags selected elements from point inside common bou
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -6196,8 +6176,7 @@ exports[`regression tests > draw every type of shape > [end of test] appState 1`
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -6935,12 +6914,8 @@ exports[`regression tests > draw every type of shape > [end of test] undo stack
|
|||||||
"roundness": null,
|
"roundness": null,
|
||||||
"simulatePressure": false,
|
"simulatePressure": false,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeOptions": {
|
|
||||||
"streamline": "0.50000",
|
|
||||||
"variability": "constant",
|
|
||||||
},
|
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 1,
|
"strokeWidth": 2,
|
||||||
"type": "freedraw",
|
"type": "freedraw",
|
||||||
"version": 4,
|
"version": 4,
|
||||||
"width": 50,
|
"width": 50,
|
||||||
@@ -6990,8 +6965,7 @@ exports[`regression tests > given a group of selected elements with an element t
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -7325,8 +7299,7 @@ exports[`regression tests > given a selected element A and a not selected elemen
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -7605,8 +7578,7 @@ exports[`regression tests > given selected element A with lower z-index than uns
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -7841,8 +7813,7 @@ exports[`regression tests > given selected element A with lower z-index than uns
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -8082,8 +8053,7 @@ exports[`regression tests > key 2 selects rectangle tool > [end of test] appStat
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -8263,8 +8233,7 @@ exports[`regression tests > key 3 selects diamond tool > [end of test] appState
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -8444,8 +8413,7 @@ exports[`regression tests > key 4 selects ellipse tool > [end of test] appState
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -8625,8 +8593,7 @@ exports[`regression tests > key 5 selects arrow tool > [end of test] appState 1`
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -8859,8 +8826,7 @@ exports[`regression tests > key 6 selects line tool > [end of test] appState 1`]
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -9091,8 +9057,7 @@ exports[`regression tests > key 7 selects freedraw tool > [end of test] appState
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -9233,12 +9198,8 @@ exports[`regression tests > key 7 selects freedraw tool > [end of test] undo sta
|
|||||||
"roundness": null,
|
"roundness": null,
|
||||||
"simulatePressure": false,
|
"simulatePressure": false,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeOptions": {
|
|
||||||
"streamline": "0.50000",
|
|
||||||
"variability": "constant",
|
|
||||||
},
|
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 1,
|
"strokeWidth": 2,
|
||||||
"type": "freedraw",
|
"type": "freedraw",
|
||||||
"version": 4,
|
"version": 4,
|
||||||
"width": 30,
|
"width": 30,
|
||||||
@@ -9288,8 +9249,7 @@ exports[`regression tests > key a selects arrow tool > [end of test] appState 1`
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -9522,8 +9482,7 @@ exports[`regression tests > key d selects diamond tool > [end of test] appState
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -9703,8 +9662,7 @@ exports[`regression tests > key l selects line tool > [end of test] appState 1`]
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -9935,8 +9893,7 @@ exports[`regression tests > key o selects ellipse tool > [end of test] appState
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -10116,8 +10073,7 @@ exports[`regression tests > key p selects freedraw tool > [end of test] appState
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -10258,12 +10214,8 @@ exports[`regression tests > key p selects freedraw tool > [end of test] undo sta
|
|||||||
"roundness": null,
|
"roundness": null,
|
||||||
"simulatePressure": false,
|
"simulatePressure": false,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeOptions": {
|
|
||||||
"streamline": "0.50000",
|
|
||||||
"variability": "constant",
|
|
||||||
},
|
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 1,
|
"strokeWidth": 2,
|
||||||
"type": "freedraw",
|
"type": "freedraw",
|
||||||
"version": 4,
|
"version": 4,
|
||||||
"width": 30,
|
"width": 30,
|
||||||
@@ -10313,8 +10265,7 @@ exports[`regression tests > key r selects rectangle tool > [end of test] appStat
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -10494,8 +10445,7 @@ exports[`regression tests > make a group and duplicate it > [end of test] appSta
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -11026,8 +10976,7 @@ exports[`regression tests > noop interaction after undo shouldn't create history
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -11307,8 +11256,7 @@ exports[`regression tests > pinch-to-zoom works > [end of test] appState 1`] = `
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "down",
|
"cursorButton": "down",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -11431,8 +11379,7 @@ exports[`regression tests > shift click on selected element should deselect it o
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -11632,8 +11579,7 @@ exports[`regression tests > shift-click to multiselect, then drag > [end of test
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -11952,8 +11898,7 @@ exports[`regression tests > should group elements and ungroup them > [end of tes
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -12382,8 +12327,7 @@ exports[`regression tests > single-clicking on a subgroup of a selected group sh
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -13023,8 +12967,7 @@ exports[`regression tests > spacebar + drag scrolls the canvas > [end of test] a
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -13150,8 +13093,7 @@ exports[`regression tests > supports nested groups > [end of test] appState 1`]
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -13782,8 +13724,7 @@ exports[`regression tests > switches from group of selected elements to another
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "down",
|
"cursorButton": "down",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -14122,8 +14063,7 @@ exports[`regression tests > switches selected element on pointer down > [end of
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "down",
|
"cursorButton": "down",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -14387,8 +14327,7 @@ exports[`regression tests > two-finger scroll works > [end of test] appState 1`]
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "down",
|
"cursorButton": "down",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -14511,8 +14450,7 @@ exports[`regression tests > undo/redo drawing an element > [end of test] appStat
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -14877,8 +14815,7 @@ exports[`regression tests > updates fontSize & fontFamily appState > [end of tes
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
@@ -15001,8 +14938,7 @@ exports[`regression tests > zoom hotkeys > [end of test] appState 1`] = `
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { CODES, STROKE_WIDTH } from "@excalidraw/common";
|
import { CODES } from "@excalidraw/common";
|
||||||
|
|
||||||
import { copiedStyles } from "../actions/actionStyles";
|
import { copiedStyles } from "../actions/actionStyles";
|
||||||
import { Excalidraw } from "../index";
|
import { Excalidraw } from "../index";
|
||||||
@@ -78,7 +78,7 @@ describe("actionStyles", () => {
|
|||||||
expect(firstRect.strokeColor).toBe("#e03131");
|
expect(firstRect.strokeColor).toBe("#e03131");
|
||||||
expect(firstRect.backgroundColor).toBe("#a5d8ff");
|
expect(firstRect.backgroundColor).toBe("#a5d8ff");
|
||||||
expect(firstRect.fillStyle).toBe("cross-hatch");
|
expect(firstRect.fillStyle).toBe("cross-hatch");
|
||||||
expect(firstRect.strokeWidth).toBe(STROKE_WIDTH.bold);
|
expect(firstRect.strokeWidth).toBe(2); // Bold: 2
|
||||||
expect(firstRect.strokeStyle).toBe("dotted");
|
expect(firstRect.strokeStyle).toBe("dotted");
|
||||||
expect(firstRect.roughness).toBe(2); // Cartoonist: 2
|
expect(firstRect.roughness).toBe(2); // Cartoonist: 2
|
||||||
expect(firstRect.opacity).toBe(60);
|
expect(firstRect.opacity).toBe(60);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { vi } from "vitest";
|
import { vi } from "vitest";
|
||||||
|
|
||||||
import { KEYS, STROKE_WIDTH, reseed } from "@excalidraw/common";
|
import { KEYS, reseed } from "@excalidraw/common";
|
||||||
|
|
||||||
import { setDateTimeForTests } from "@excalidraw/common";
|
import { setDateTimeForTests } from "@excalidraw/common";
|
||||||
|
|
||||||
@@ -378,7 +378,7 @@ describe("contextMenu element", () => {
|
|||||||
expect(firstRect.strokeColor).toBe("#e03131");
|
expect(firstRect.strokeColor).toBe("#e03131");
|
||||||
expect(firstRect.backgroundColor).toBe("#a5d8ff");
|
expect(firstRect.backgroundColor).toBe("#a5d8ff");
|
||||||
expect(firstRect.fillStyle).toBe("cross-hatch");
|
expect(firstRect.fillStyle).toBe("cross-hatch");
|
||||||
expect(firstRect.strokeWidth).toBe(STROKE_WIDTH.bold);
|
expect(firstRect.strokeWidth).toBe(2); // Bold: 2
|
||||||
expect(firstRect.strokeStyle).toBe("dotted");
|
expect(firstRect.strokeStyle).toBe("dotted");
|
||||||
expect(firstRect.roughness).toBe(2); // Cartoonist: 2
|
expect(firstRect.roughness).toBe(2); // Cartoonist: 2
|
||||||
expect(firstRect.opacity).toBe(60);
|
expect(firstRect.opacity).toBe(60);
|
||||||
|
|||||||
@@ -240,12 +240,8 @@ exports[`restoreElements > should restore freedraw element correctly 1`] = `
|
|||||||
"seed": Any<Number>,
|
"seed": Any<Number>,
|
||||||
"simulatePressure": true,
|
"simulatePressure": true,
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
"strokeOptions": {
|
|
||||||
"streamline": "0.50000",
|
|
||||||
"variability": "variable",
|
|
||||||
},
|
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 1,
|
"strokeWidth": 2,
|
||||||
"type": "freedraw",
|
"type": "freedraw",
|
||||||
"updated": 1,
|
"updated": 1,
|
||||||
"version": 2,
|
"version": 2,
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
import { pointFrom } from "@excalidraw/math";
|
import { pointFrom } from "@excalidraw/math";
|
||||||
import { vi } from "vitest";
|
import { vi } from "vitest";
|
||||||
|
|
||||||
import { DEFAULT_SIDEBAR, FONT_FAMILY, ROUNDNESS } from "@excalidraw/common";
|
import {
|
||||||
|
arrayToMap,
|
||||||
|
DEFAULT_SIDEBAR,
|
||||||
|
FONT_FAMILY,
|
||||||
|
ROUNDNESS,
|
||||||
|
} from "@excalidraw/common";
|
||||||
|
|
||||||
import { newElementWith } from "@excalidraw/element";
|
import { newElementWith } from "@excalidraw/element";
|
||||||
import * as sizeHelpers from "@excalidraw/element";
|
import * as sizeHelpers from "@excalidraw/element";
|
||||||
@@ -193,53 +198,6 @@ describe("restoreElements", () => {
|
|||||||
expect(restoredFreedraw.pressures).toEqual([0.1, 0.4]);
|
expect(restoredFreedraw.pressures).toEqual([0.1, 0.4]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should restore freedraw stroke variability", () => {
|
|
||||||
const freedrawElement = API.createElement({
|
|
||||||
type: "freedraw",
|
|
||||||
id: "id-freedraw-mode",
|
|
||||||
points: [pointFrom(0, 0), pointFrom(10, 10)],
|
|
||||||
});
|
|
||||||
|
|
||||||
const [missing, bogusString, bogusNumber, valid, variable] =
|
|
||||||
restore.restoreElements(
|
|
||||||
[
|
|
||||||
{ ...freedrawElement, id: "missing", strokeOptions: undefined },
|
|
||||||
{
|
|
||||||
...freedrawElement,
|
|
||||||
id: "bogusString",
|
|
||||||
strokeOptions: { variability: "scribble" },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
...freedrawElement,
|
|
||||||
id: "bogusNumber",
|
|
||||||
strokeOptions: { variability: 42 },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
...freedrawElement,
|
|
||||||
id: "valid",
|
|
||||||
strokeOptions: { variability: "constant", streamline: 0.8 },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
...freedrawElement,
|
|
||||||
id: "variable",
|
|
||||||
strokeOptions: { variability: "variable", streamline: 0.8 },
|
|
||||||
},
|
|
||||||
] as any,
|
|
||||||
null,
|
|
||||||
) as ExcalidrawFreeDrawElement[];
|
|
||||||
|
|
||||||
expect(missing.strokeOptions?.variability).toBe("variable");
|
|
||||||
expect(bogusString.strokeOptions?.variability).toBe("variable");
|
|
||||||
expect(bogusNumber.strokeOptions?.variability).toBe("variable");
|
|
||||||
expect(valid.strokeOptions?.variability).toBe("constant");
|
|
||||||
expect(variable.strokeOptions?.variability).toBe("variable");
|
|
||||||
expect(missing.strokeOptions?.streamline).toBe(0.5);
|
|
||||||
expect(bogusString.strokeOptions?.streamline).toBe(0.5);
|
|
||||||
expect(bogusNumber.strokeOptions?.streamline).toBe(0.5);
|
|
||||||
expect(valid.strokeOptions?.streamline).toBe(0.8);
|
|
||||||
expect(variable.strokeOptions?.streamline).toBe(0.8);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should restore line and draw elements correctly", () => {
|
it("should restore line and draw elements correctly", () => {
|
||||||
const lineElement = API.createElement({ type: "line", id: "id-line01" });
|
const lineElement = API.createElement({ type: "line", id: "id-line01" });
|
||||||
|
|
||||||
@@ -687,21 +645,6 @@ describe("restoreElements", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("restoreAppState", () => {
|
describe("restoreAppState", () => {
|
||||||
it("should restore freedraw mode app state values", () => {
|
|
||||||
expect(
|
|
||||||
restore.restoreAppState(
|
|
||||||
{ currentItemStrokeVariability: "constant" } as any,
|
|
||||||
null,
|
|
||||||
).currentItemStrokeVariability,
|
|
||||||
).toBe("constant");
|
|
||||||
expect(
|
|
||||||
restore.restoreAppState(
|
|
||||||
{ currentItemStrokeVariability: "variable" } as any,
|
|
||||||
null,
|
|
||||||
).currentItemStrokeVariability,
|
|
||||||
).toBe("variable");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("when appState is null it should return the local app state property", () => {
|
it("when appState is null it should return the local app state property", () => {
|
||||||
const stubLocalAppState = getDefaultAppState();
|
const stubLocalAppState = getDefaultAppState();
|
||||||
stubLocalAppState.cursorButton = "down";
|
stubLocalAppState.cursorButton = "down";
|
||||||
@@ -750,21 +693,6 @@ describe("restoreAppState", () => {
|
|||||||
expect(restoredAppState.name).toBe(stubImportedAppState.name);
|
expect(restoredAppState.name).toBe(stubImportedAppState.name);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should migrate legacy current item stroke width to stroke width key", () => {
|
|
||||||
const stubImportedAppState = {
|
|
||||||
...getDefaultAppState(),
|
|
||||||
currentItemStrokeWidth: 4,
|
|
||||||
currentItemStrokeWidthKey: undefined,
|
|
||||||
} as any;
|
|
||||||
|
|
||||||
const restoredAppState = restore.restoreAppState(
|
|
||||||
stubImportedAppState,
|
|
||||||
null,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(restoredAppState.currentItemStrokeWidthKey).toBe("bold");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should restore with current app state when imported data state is undefined", () => {
|
it("should restore with current app state when imported data state is undefined", () => {
|
||||||
const stubImportedAppState = {
|
const stubImportedAppState = {
|
||||||
...getDefaultAppState(),
|
...getDefaultAppState(),
|
||||||
@@ -1217,3 +1145,201 @@ describe("repairing bindings", () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("repairing frame membership", () => {
|
||||||
|
const makeFrame = () =>
|
||||||
|
API.createElement({
|
||||||
|
type: "frame",
|
||||||
|
id: "frame",
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps frameId for an element fully inside the frame", () => {
|
||||||
|
const frame = makeFrame();
|
||||||
|
const inside = API.createElement({
|
||||||
|
type: "rectangle",
|
||||||
|
id: "inside",
|
||||||
|
x: 10,
|
||||||
|
y: 10,
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
frameId: frame.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const restoredElementsMap = arrayToMap(
|
||||||
|
restore.restoreElements([frame, inside], null, { repairBindings: true }),
|
||||||
|
);
|
||||||
|
expect(restoredElementsMap.get("inside")?.frameId).toBe(frame.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps frameId for an element intersecting the frame border", () => {
|
||||||
|
const frame = makeFrame();
|
||||||
|
// spans x:[90,130] — crosses the frame's right edge (x=100)
|
||||||
|
const straddling = API.createElement({
|
||||||
|
type: "rectangle",
|
||||||
|
id: "straddling",
|
||||||
|
x: 90,
|
||||||
|
y: 10,
|
||||||
|
width: 40,
|
||||||
|
height: 20,
|
||||||
|
frameId: frame.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const restoredElementsMap = arrayToMap(
|
||||||
|
restore.restoreElements([frame, straddling], null, {
|
||||||
|
repairBindings: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(restoredElementsMap.get("straddling")?.frameId).toBe(frame.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("removes frameId for an element fully outside the frame", () => {
|
||||||
|
const frame = makeFrame();
|
||||||
|
const outside = API.createElement({
|
||||||
|
type: "rectangle",
|
||||||
|
id: "outside",
|
||||||
|
x: 200,
|
||||||
|
y: 200,
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
frameId: frame.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const restoredElementsMap = arrayToMap(
|
||||||
|
restore.restoreElements([frame, outside], null, { repairBindings: true }),
|
||||||
|
);
|
||||||
|
expect(restoredElementsMap.get("outside")?.frameId).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("removes frameId when the containing frame no longer exists", () => {
|
||||||
|
const orphan = API.createElement({
|
||||||
|
type: "rectangle",
|
||||||
|
id: "orphan",
|
||||||
|
x: 10,
|
||||||
|
y: 10,
|
||||||
|
frameId: "non-existent-frame",
|
||||||
|
});
|
||||||
|
|
||||||
|
const restoredElementsMap = arrayToMap(
|
||||||
|
restore.restoreElements([orphan], null, { repairBindings: true }),
|
||||||
|
);
|
||||||
|
expect(restoredElementsMap.get("orphan")?.frameId).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does NOT touch frameId when repairBindings is off", () => {
|
||||||
|
const frame = makeFrame();
|
||||||
|
const outside = API.createElement({
|
||||||
|
type: "rectangle",
|
||||||
|
id: "outside",
|
||||||
|
x: 200,
|
||||||
|
y: 200,
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
frameId: frame.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const restoredElementsMap = arrayToMap(
|
||||||
|
restore.restoreElements([frame, outside], null, {
|
||||||
|
repairBindings: false,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(restoredElementsMap.get("outside")?.frameId).toBe(frame.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps a group atomic: members stay if ANY group member overlaps", () => {
|
||||||
|
const frame = makeFrame();
|
||||||
|
const insideMember = API.createElement({
|
||||||
|
type: "rectangle",
|
||||||
|
id: "insideMember",
|
||||||
|
x: 10,
|
||||||
|
y: 10,
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
groupIds: ["g1"],
|
||||||
|
frameId: frame.id,
|
||||||
|
});
|
||||||
|
const outsideMember = API.createElement({
|
||||||
|
type: "rectangle",
|
||||||
|
id: "outsideMember",
|
||||||
|
x: 300,
|
||||||
|
y: 300,
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
groupIds: ["g1"],
|
||||||
|
frameId: frame.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const restoredElementsMap = arrayToMap(
|
||||||
|
restore.restoreElements([frame, insideMember, outsideMember], null, {
|
||||||
|
repairBindings: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
// the whole group overlaps (via insideMember), so neither is evicted
|
||||||
|
expect(restoredElementsMap.get("insideMember")?.frameId).toBe(frame.id);
|
||||||
|
expect(restoredElementsMap.get("outsideMember")?.frameId).toBe(frame.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("removes frameId for a whole group when no member overlaps", () => {
|
||||||
|
const frame = makeFrame();
|
||||||
|
const a = API.createElement({
|
||||||
|
type: "rectangle",
|
||||||
|
id: "a",
|
||||||
|
x: 200,
|
||||||
|
y: 200,
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
groupIds: ["g1"],
|
||||||
|
frameId: frame.id,
|
||||||
|
});
|
||||||
|
const b = API.createElement({
|
||||||
|
type: "rectangle",
|
||||||
|
id: "b",
|
||||||
|
x: 300,
|
||||||
|
y: 300,
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
groupIds: ["g1"],
|
||||||
|
frameId: frame.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const restoredElementsMap = arrayToMap(
|
||||||
|
restore.restoreElements([frame, a, b], null, { repairBindings: true }),
|
||||||
|
);
|
||||||
|
expect(restoredElementsMap.get("a")?.frameId).toBe(null);
|
||||||
|
expect(restoredElementsMap.get("b")?.frameId).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("bound text follows its container's frame membership", () => {
|
||||||
|
const frameOutside = makeFrame();
|
||||||
|
const container = API.createElement({
|
||||||
|
type: "rectangle",
|
||||||
|
id: "container",
|
||||||
|
x: 300,
|
||||||
|
y: 300,
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
frameId: frameOutside.id,
|
||||||
|
boundElements: [{ type: "text", id: "label" }],
|
||||||
|
});
|
||||||
|
const label = API.createElement({
|
||||||
|
type: "text",
|
||||||
|
id: "label",
|
||||||
|
x: 305,
|
||||||
|
y: 305,
|
||||||
|
containerId: container.id,
|
||||||
|
frameId: frameOutside.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const restoredElementsMap = arrayToMap(
|
||||||
|
restore.restoreElements([frameOutside, container, label], null, {
|
||||||
|
repairBindings: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
// container is fully outside -> both it and its bound text leave the frame
|
||||||
|
expect(restoredElementsMap.get("container")?.frameId).toBe(null);
|
||||||
|
expect(restoredElementsMap.get("label")?.frameId).toBe(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
import type { ExcalidrawFreeDrawElement } from "@excalidraw/element/types";
|
|
||||||
|
|
||||||
import { Excalidraw } from "../index";
|
|
||||||
|
|
||||||
import { API } from "./helpers/api";
|
|
||||||
import { UI } from "./helpers/ui";
|
|
||||||
import { act, fireEvent, render, screen } from "./test-utils";
|
|
||||||
|
|
||||||
const { h } = window;
|
|
||||||
|
|
||||||
describe("freedraw mode action", () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
await render(<Excalidraw handleKeyboardGlobally={true} />);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(async () => {
|
|
||||||
// https://github.com/floating-ui/floating-ui/issues/1908#issuecomment-1301553793
|
|
||||||
await act(async () => {});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("applies currentItemStrokeVariability to newly drawn freedraw elements", () => {
|
|
||||||
// default app state draws constant-width strokes
|
|
||||||
expect(h.state.currentItemStrokeVariability).toBe("constant");
|
|
||||||
|
|
||||||
UI.createElement("freedraw", { x: 0, y: 0 });
|
|
||||||
|
|
||||||
expect(
|
|
||||||
(h.elements[0] as ExcalidrawFreeDrawElement).strokeOptions?.variability,
|
|
||||||
).toBe("constant");
|
|
||||||
expect(
|
|
||||||
(h.elements[0] as ExcalidrawFreeDrawElement).strokeOptions?.streamline,
|
|
||||||
).toBe(0.5);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("toggling the radio updates both the selected element and the default", () => {
|
|
||||||
const element = UI.createElement("freedraw", { x: 0, y: 0 });
|
|
||||||
API.setSelectedElements([element.get()]);
|
|
||||||
|
|
||||||
fireEvent.click(screen.getByTitle("Variable"));
|
|
||||||
expect(
|
|
||||||
(h.elements[0] as ExcalidrawFreeDrawElement).strokeOptions?.variability,
|
|
||||||
).toBe("variable");
|
|
||||||
expect(
|
|
||||||
(h.elements[0] as ExcalidrawFreeDrawElement).strokeOptions?.streamline,
|
|
||||||
).toBe(0.5);
|
|
||||||
expect(h.state.currentItemStrokeVariability).toBe("variable");
|
|
||||||
|
|
||||||
fireEvent.click(screen.getByTitle("Constant"));
|
|
||||||
expect(
|
|
||||||
(h.elements[0] as ExcalidrawFreeDrawElement).strokeOptions?.variability,
|
|
||||||
).toBe("constant");
|
|
||||||
expect(
|
|
||||||
(h.elements[0] as ExcalidrawFreeDrawElement).strokeOptions?.streamline,
|
|
||||||
).toBe(0.5);
|
|
||||||
expect(h.state.currentItemStrokeVariability).toBe("constant");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -4,12 +4,7 @@ import util from "util";
|
|||||||
|
|
||||||
import { pointFrom, type LocalPoint, type Radians } from "@excalidraw/math";
|
import { pointFrom, type LocalPoint, type Radians } from "@excalidraw/math";
|
||||||
|
|
||||||
import {
|
import { DEFAULT_VERTICAL_ALIGN, ROUNDNESS, assertNever } from "@excalidraw/common";
|
||||||
DEFAULT_VERTICAL_ALIGN,
|
|
||||||
ROUNDNESS,
|
|
||||||
assertNever,
|
|
||||||
getStrokeWidthByKey,
|
|
||||||
} from "@excalidraw/common";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
newArrowElement,
|
newArrowElement,
|
||||||
@@ -205,9 +200,6 @@ export class API {
|
|||||||
? ExcalidrawTextElement["containerId"]
|
? ExcalidrawTextElement["containerId"]
|
||||||
: never;
|
: never;
|
||||||
points?: T extends "arrow" | "line" | "freedraw" ? readonly LocalPoint[] : never;
|
points?: T extends "arrow" | "line" | "freedraw" ? readonly LocalPoint[] : never;
|
||||||
strokeOptions?: T extends "freedraw"
|
|
||||||
? ExcalidrawFreeDrawElement["strokeOptions"]
|
|
||||||
: never;
|
|
||||||
locked?: boolean;
|
locked?: boolean;
|
||||||
fileId?: T extends "image" ? string : never;
|
fileId?: T extends "image" ? string : never;
|
||||||
scale?: T extends "image" ? ExcalidrawImageElement["scale"] : never;
|
scale?: T extends "image" ? ExcalidrawImageElement["scale"] : never;
|
||||||
@@ -266,9 +258,7 @@ export class API {
|
|||||||
backgroundColor:
|
backgroundColor:
|
||||||
rest.backgroundColor ?? appState.currentItemBackgroundColor,
|
rest.backgroundColor ?? appState.currentItemBackgroundColor,
|
||||||
fillStyle: rest.fillStyle ?? appState.currentItemFillStyle,
|
fillStyle: rest.fillStyle ?? appState.currentItemFillStyle,
|
||||||
strokeWidth:
|
strokeWidth: rest.strokeWidth ?? appState.currentItemStrokeWidth,
|
||||||
rest.strokeWidth ??
|
|
||||||
getStrokeWidthByKey(type, appState.currentItemStrokeWidthKey),
|
|
||||||
strokeStyle: rest.strokeStyle ?? appState.currentItemStrokeStyle,
|
strokeStyle: rest.strokeStyle ?? appState.currentItemStrokeStyle,
|
||||||
roundness: (
|
roundness: (
|
||||||
rest.roundness === undefined
|
rest.roundness === undefined
|
||||||
@@ -327,7 +317,6 @@ export class API {
|
|||||||
type: type as "freedraw",
|
type: type as "freedraw",
|
||||||
simulatePressure: true,
|
simulatePressure: true,
|
||||||
points: rest.points,
|
points: rest.points,
|
||||||
strokeOptions: rest.strokeOptions,
|
|
||||||
...base,
|
...base,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import type {
|
||||||
|
ExcalidrawElement,
|
||||||
|
NonDeletedExcalidrawElement,
|
||||||
|
} from "@excalidraw/element/types";
|
||||||
|
|
||||||
|
import { exportToSvg } from "../../scene/export";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test-only helper: render the supplied elements to an SVG string for visual
|
||||||
|
* debugging (e.g. to eyeball frame-membership / overlap geometry).
|
||||||
|
*
|
||||||
|
* Frames are drawn with their outline but are NOT clipped, and every element's
|
||||||
|
* `frameId` is ignored, so elements that fall outside a frame stay visible and
|
||||||
|
* within the exported viewBox instead of being clipped or cropped away.
|
||||||
|
*
|
||||||
|
* Pass `outFile` to also write the SVG to disk — open it in a browser to view.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* await exportElementsToSVG([frame, diamond], {
|
||||||
|
* outFile: "/tmp/frame-debug.svg",
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export const exportElementsToSVG = async (
|
||||||
|
elements: readonly ExcalidrawElement[],
|
||||||
|
opts?: { outFile?: string; padding?: number; background?: string },
|
||||||
|
): Promise<string> => {
|
||||||
|
// ignore frame membership so nothing gets clipped/cropped out of view
|
||||||
|
const rootElements = elements.map(
|
||||||
|
(element) => ({ ...element, frameId: null } as NonDeletedExcalidrawElement),
|
||||||
|
);
|
||||||
|
|
||||||
|
const svg = await exportToSvg(
|
||||||
|
rootElements,
|
||||||
|
{
|
||||||
|
exportBackground: true,
|
||||||
|
viewBackgroundColor: opts?.background ?? "#ffffff",
|
||||||
|
exportPadding: opts?.padding ?? 24,
|
||||||
|
frameRendering: { enabled: true, name: true, outline: true, clip: false },
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
{ skipInliningFonts: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
const svgString = svg.outerHTML;
|
||||||
|
|
||||||
|
if (opts?.outFile) {
|
||||||
|
const { writeFileSync } = await import("fs");
|
||||||
|
writeFileSync(opts.outFile, svgString);
|
||||||
|
}
|
||||||
|
|
||||||
|
return svgString;
|
||||||
|
};
|
||||||
@@ -24,7 +24,7 @@ exports[`exportToSvg > with default arguments 1`] = `
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeWidthKey": "medium",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"extends": "../tsconfig.base.json",
|
"extends": "../tsconfig.base.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"rootDir": "../",
|
|
||||||
"outDir": "./dist/types"
|
"outDir": "./dist/types"
|
||||||
},
|
},
|
||||||
"include": ["**/*"],
|
"include": ["**/*"],
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import type {
|
|||||||
throttleRAF,
|
throttleRAF,
|
||||||
MIME_TYPES,
|
MIME_TYPES,
|
||||||
EditorInterface,
|
EditorInterface,
|
||||||
StrokeWidthKey,
|
|
||||||
} from "@excalidraw/common";
|
} from "@excalidraw/common";
|
||||||
|
|
||||||
import type { LinearElementEditor } from "@excalidraw/element";
|
import type { LinearElementEditor } from "@excalidraw/element";
|
||||||
@@ -34,7 +33,6 @@ import type {
|
|||||||
ExcalidrawNonSelectionElement,
|
ExcalidrawNonSelectionElement,
|
||||||
BindMode,
|
BindMode,
|
||||||
ExcalidrawTextElement,
|
ExcalidrawTextElement,
|
||||||
StrokeVariability,
|
|
||||||
} from "@excalidraw/element/types";
|
} from "@excalidraw/element/types";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@@ -364,10 +362,9 @@ export interface AppState {
|
|||||||
currentItemStrokeColor: string;
|
currentItemStrokeColor: string;
|
||||||
currentItemBackgroundColor: string;
|
currentItemBackgroundColor: string;
|
||||||
currentItemFillStyle: ExcalidrawElement["fillStyle"];
|
currentItemFillStyle: ExcalidrawElement["fillStyle"];
|
||||||
currentItemStrokeWidthKey: StrokeWidthKey;
|
currentItemStrokeWidth: number;
|
||||||
currentItemStrokeStyle: ExcalidrawElement["strokeStyle"];
|
currentItemStrokeStyle: ExcalidrawElement["strokeStyle"];
|
||||||
currentItemRoughness: number;
|
currentItemRoughness: number;
|
||||||
currentItemStrokeVariability: StrokeVariability;
|
|
||||||
currentItemOpacity: number;
|
currentItemOpacity: number;
|
||||||
currentItemFontFamily: FontFamilyValues;
|
currentItemFontFamily: FontFamilyValues;
|
||||||
currentItemFontSize: number;
|
currentItemFontSize: number;
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
{
|
{
|
||||||
"extends": "../tsconfig.base.json",
|
"extends": "../tsconfig.base.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "./dist/types",
|
"outDir": "./dist/types"
|
||||||
"rootDir": "../"
|
|
||||||
},
|
},
|
||||||
"include": ["src/**/*", "global.d.ts"],
|
"include": ["src/**/*", "global.d.ts"],
|
||||||
"exclude": ["**/*.test.*", "tests", "types", "examples", "dist"]
|
"exclude": ["**/*.test.*", "tests", "types", "examples", "dist"]
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2023 Excalidraw
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
# Laser Pointer
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
import { LaserPointer } from '@excalidraw/laser-pointer'
|
|
||||||
|
|
||||||
const stroke = new LaserPointer(options)
|
|
||||||
|
|
||||||
stroke.addPoint([100, 200, 1])
|
|
||||||
stroke.close()
|
|
||||||
|
|
||||||
const outline = stroke.getStrokeOutline()
|
|
||||||
|
|
||||||
## Options
|
|
||||||
|
|
||||||
| Property | Type | Default | Description |
|
|
||||||
| --- | --- | --- | --- |
|
|
||||||
| `size` | `number` | `2` | Radius of the stroke. |
|
|
||||||
| `streamline` | `number` | `0.42` | Interpolate input points to reduce jitter. |
|
|
||||||
| `simplify` | `number` | `0.1` | Reduce stroke size by sacrificing precision. |
|
|
||||||
| `simplifyPhase` | `"input" \| "output" \| "tail" ` | `"output"` | Decides when the simplification algorithm should be applied. |
|
|
||||||
| `sizeMapping` | `(details: SizeMappingDetails) => number` | `() => 1` | Maps each point to a value between `0.0` and `1.0`. |
|
|
||||||
| `keepHead` | `boolean` | `false` | Whether size mapping should influence the head of the stroke. |
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@excalidraw/laser-pointer",
|
|
||||||
"version": "1.3.1",
|
|
||||||
"description": "Generate outline for laser pointer tool",
|
|
||||||
"type": "module",
|
|
||||||
"types": "./dist/types/laser-pointer/src/index.d.ts",
|
|
||||||
"main": "./dist/prod/index.js",
|
|
||||||
"module": "./dist/prod/index.js",
|
|
||||||
"exports": {
|
|
||||||
".": {
|
|
||||||
"types": "./dist/types/laser-pointer/src/index.d.ts",
|
|
||||||
"development": "./dist/dev/index.js",
|
|
||||||
"production": "./dist/prod/index.js",
|
|
||||||
"default": "./dist/prod/index.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"dist/*"
|
|
||||||
],
|
|
||||||
"keywords": [
|
|
||||||
"excalidraw",
|
|
||||||
"laserpointer"
|
|
||||||
],
|
|
||||||
"bugs": "https://github.com/excalidraw/excalidraw/issues",
|
|
||||||
"repository": "https://github.com/excalidraw/excalidraw",
|
|
||||||
"license": "MIT",
|
|
||||||
"publishConfig": {
|
|
||||||
"access": "public"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"gen:types": "rimraf types && tsc",
|
|
||||||
"build:esm": "rimraf dist && node ../../scripts/buildBase.js && yarn gen:types"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
export * from "./state";
|
|
||||||
export type { Point } from "./math";
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
export type Point = [x: number, y: number, r: number];
|
|
||||||
|
|
||||||
export function add([ax, ay, ar]: Point, [bx, by, br]: Point): Point {
|
|
||||||
return [ax + bx, ay + by, ar + br];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function sub([ax, ay, ar]: Point, [bx, by, br]: Point): Point {
|
|
||||||
return [ax - bx, ay - by, ar - br];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function smul([x, y, r]: Point, s: number): Point {
|
|
||||||
return [x * s, y * s, r * s];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function norm([x, y, r]: Point): Point {
|
|
||||||
return [x / Math.sqrt(x ** 2 + y ** 2), y / Math.sqrt(x ** 2 + y ** 2), r];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function rot([x, y, r]: Point, rad: number): Point {
|
|
||||||
return [
|
|
||||||
Math.cos(rad) * x - Math.sin(rad) * y,
|
|
||||||
Math.sin(rad) * x + Math.cos(rad) * y,
|
|
||||||
r,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function plerp(a: Point, b: Point, t: number): Point {
|
|
||||||
return add(a, smul(sub(b, a), t));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function lerp(a: number, b: number, t: number): number {
|
|
||||||
return a + (b - a) * t;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function angle(p: Point, p1: Point, p2: Point) {
|
|
||||||
return (
|
|
||||||
Math.atan2(p2[1] - p[1], p2[0] - p[0]) -
|
|
||||||
Math.atan2(p1[1] - p[1], p1[0] - p[0])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function normAngle(a: number) {
|
|
||||||
return Math.atan2(Math.sin(a), Math.cos(a));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function mag([x, y]: Point) {
|
|
||||||
return Math.sqrt(x ** 2 + y ** 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function dist([ax, ay]: Point, [bx, by]: Point): number {
|
|
||||||
return Math.sqrt((bx - ax) ** 2 + (by - ay) ** 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCircleAndPerpendicularLineIntersectionsAtPoint(
|
|
||||||
point: Point,
|
|
||||||
direction: Point,
|
|
||||||
radius: number,
|
|
||||||
): [Point, Point] {
|
|
||||||
return [
|
|
||||||
add(point, smul(norm(rot(direction, Math.PI / 2)), radius)),
|
|
||||||
add(point, smul(norm(rot(direction, -Math.PI / 2)), radius)),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function runLength(ps: Point[]): number {
|
|
||||||
if (ps.length < 2) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
let len = 0;
|
|
||||||
|
|
||||||
for (let i = 1; i <= ps.length - 1; i++) {
|
|
||||||
len += dist(ps[i - 1], ps[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
len += dist(ps[ps.length - 2], ps[ps.length - 1]);
|
|
||||||
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const clamp = (v: number, min: number, max: number) =>
|
|
||||||
Math.max(min, Math.min(max, v));
|
|
||||||
|
|
||||||
export function distancePointToSegment(p3: Point, p1: Point, p2: Point) {
|
|
||||||
const sMag = dist(p1, p2);
|
|
||||||
|
|
||||||
if (sMag === 0) {
|
|
||||||
return dist(p3, p1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const u = clamp(
|
|
||||||
((p3[0] - p1[0]) * (p2[0] - p1[0]) + (p3[1] - p1[1]) * (p2[1] - p1[1])) /
|
|
||||||
sMag ** 2,
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
);
|
|
||||||
|
|
||||||
const pi: Point = [
|
|
||||||
p1[0] + u * (p2[0] - p1[0]),
|
|
||||||
p1[1] + u * (p2[1] - p1[1]),
|
|
||||||
p3[2],
|
|
||||||
];
|
|
||||||
|
|
||||||
return dist(pi, p3);
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
import { type Point, distancePointToSegment } from "./math";
|
|
||||||
|
|
||||||
export function douglasPeucker(points: Point[], epsilon: number): Point[] {
|
|
||||||
if (epsilon === 0) {
|
|
||||||
return points;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (points.length <= 2) {
|
|
||||||
return points;
|
|
||||||
}
|
|
||||||
|
|
||||||
const first = points[0];
|
|
||||||
const last = points[points.length - 1];
|
|
||||||
|
|
||||||
const [maxDistance, maxIndex] = points.reduce(
|
|
||||||
([maxDistance, maxIndex], point, index) => {
|
|
||||||
const distance = distancePointToSegment(point, first, last);
|
|
||||||
|
|
||||||
return distance > maxDistance
|
|
||||||
? [distance, index]
|
|
||||||
: [maxDistance, maxIndex];
|
|
||||||
},
|
|
||||||
[0, -1],
|
|
||||||
);
|
|
||||||
|
|
||||||
if (maxDistance >= epsilon) {
|
|
||||||
const maxIndexPoint = points[maxIndex];
|
|
||||||
|
|
||||||
return [
|
|
||||||
...douglasPeucker(
|
|
||||||
[first, ...points.slice(1, maxIndex), maxIndexPoint],
|
|
||||||
epsilon,
|
|
||||||
).slice(0, -1),
|
|
||||||
maxIndexPoint,
|
|
||||||
...douglasPeucker(
|
|
||||||
[maxIndexPoint, ...points.slice(maxIndex, -1), last],
|
|
||||||
epsilon,
|
|
||||||
).slice(1),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
return [first, last];
|
|
||||||
}
|
|
||||||
@@ -1,377 +0,0 @@
|
|||||||
import * as m from "./math";
|
|
||||||
import { douglasPeucker } from "./simplify";
|
|
||||||
|
|
||||||
import type { Point } from "./math";
|
|
||||||
|
|
||||||
export type SizeMappingDetails = {
|
|
||||||
pressure: number;
|
|
||||||
runningLength: number;
|
|
||||||
currentIndex: number;
|
|
||||||
totalLength: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type LaserPointerOptions = {
|
|
||||||
size: number;
|
|
||||||
|
|
||||||
streamline: number;
|
|
||||||
simplify: number;
|
|
||||||
simplifyPhase: "tail" | "output" | "input";
|
|
||||||
|
|
||||||
keepHead: boolean;
|
|
||||||
|
|
||||||
sizeMapping: (details: SizeMappingDetails) => number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class LaserPointer {
|
|
||||||
static defaults: LaserPointerOptions = {
|
|
||||||
size: 2,
|
|
||||||
streamline: 0.45,
|
|
||||||
simplify: 0.1,
|
|
||||||
simplifyPhase: "output",
|
|
||||||
keepHead: false,
|
|
||||||
|
|
||||||
sizeMapping: () => 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
static constants = {
|
|
||||||
cornerDetectionMaxAngle: 75,
|
|
||||||
cornerDetectionVariance: (s: number) => (s > 35 ? 0.5 : 1),
|
|
||||||
maxTailLength: 50,
|
|
||||||
};
|
|
||||||
|
|
||||||
options: LaserPointerOptions;
|
|
||||||
constructor(options: Partial<LaserPointerOptions>) {
|
|
||||||
this.options = Object.assign({}, LaserPointer.defaults, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
originalPoints: Point[] = [];
|
|
||||||
|
|
||||||
private stablePoints: Point[] = [];
|
|
||||||
private tailPoints: Point[] = [];
|
|
||||||
|
|
||||||
private isFresh = true;
|
|
||||||
|
|
||||||
private get lastPoint(): Point {
|
|
||||||
return (
|
|
||||||
this.tailPoints[this.tailPoints.length - 1] ??
|
|
||||||
this.stablePoints[this.stablePoints.length - 1]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
addPoint(point: Point) {
|
|
||||||
const lastPoint = this.originalPoints[this.originalPoints.length - 1];
|
|
||||||
|
|
||||||
if (lastPoint && lastPoint[0] === point[0] && lastPoint[1] === point[1]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.originalPoints.push(point);
|
|
||||||
|
|
||||||
if (this.isFresh) {
|
|
||||||
this.isFresh = false;
|
|
||||||
this.stablePoints.push(point);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.options.streamline > 0) {
|
|
||||||
point = m.plerp(this.lastPoint, point, 1 - this.options.streamline);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.tailPoints.push(point);
|
|
||||||
|
|
||||||
if (m.runLength(this.tailPoints) > LaserPointer.constants.maxTailLength) {
|
|
||||||
this.stabilizeTail();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
close() {
|
|
||||||
this.stabilizeTail();
|
|
||||||
}
|
|
||||||
|
|
||||||
stabilizeTail() {
|
|
||||||
if (this.options.simplify > 0 && this.options.simplifyPhase === "tail") {
|
|
||||||
throw new Error("Not implemented yet");
|
|
||||||
} else {
|
|
||||||
this.stablePoints.push(...this.tailPoints);
|
|
||||||
this.tailPoints = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private getSize(
|
|
||||||
sizeOverride: number | undefined,
|
|
||||||
pressure: number,
|
|
||||||
index: number,
|
|
||||||
totalLength: number,
|
|
||||||
runningLength: number,
|
|
||||||
) {
|
|
||||||
return (
|
|
||||||
(sizeOverride ?? this.options.size) *
|
|
||||||
this.options.sizeMapping({
|
|
||||||
pressure,
|
|
||||||
runningLength,
|
|
||||||
currentIndex: index,
|
|
||||||
totalLength,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getStrokeOutline(sizeOverride?: number | undefined): Point[] {
|
|
||||||
if (this.isFresh) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
let points = [...this.stablePoints, ...this.tailPoints];
|
|
||||||
|
|
||||||
if (this.options.simplify > 0 && this.options.simplifyPhase === "input") {
|
|
||||||
points = douglasPeucker(points, this.options.simplify);
|
|
||||||
}
|
|
||||||
|
|
||||||
const len = points.length;
|
|
||||||
|
|
||||||
if (len === 0) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (len === 1) {
|
|
||||||
const c = points[0];
|
|
||||||
|
|
||||||
const size = this.getSize(sizeOverride, c[2], 0, len, 0);
|
|
||||||
|
|
||||||
if (size < 0.5) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const ps: Point[] = [];
|
|
||||||
|
|
||||||
for (let theta = 0; theta <= Math.PI * 2; theta += Math.PI / 16) {
|
|
||||||
ps.push(m.add(c, m.smul(m.rot([1, 0, 0] as Point, theta), size)));
|
|
||||||
}
|
|
||||||
|
|
||||||
ps.push(
|
|
||||||
m.add(
|
|
||||||
c,
|
|
||||||
m.smul(
|
|
||||||
[1, 0, 0] as Point,
|
|
||||||
this.getSize(sizeOverride, c[2], 0, len, 0),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return ps;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (len === 2) {
|
|
||||||
const c = points[0];
|
|
||||||
const n = points[1];
|
|
||||||
|
|
||||||
const cSize = this.getSize(sizeOverride, c[2], 0, len, 0);
|
|
||||||
const nSize = this.getSize(sizeOverride, n[2], 0, len, 0);
|
|
||||||
|
|
||||||
if (cSize < 0.5 || nSize < 0.5) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const ps: Point[] = [];
|
|
||||||
|
|
||||||
const pAngle = m.angle(c, [c[0], c[1] - 100, c[2]] as Point, n);
|
|
||||||
|
|
||||||
for (
|
|
||||||
let theta = pAngle;
|
|
||||||
theta <= Math.PI + pAngle;
|
|
||||||
theta += Math.PI / 16
|
|
||||||
) {
|
|
||||||
ps.push(m.add(c, m.smul(m.rot([1, 0, 0] as Point, theta), cSize)));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (
|
|
||||||
let theta = Math.PI + pAngle;
|
|
||||||
theta <= Math.PI * 2 + pAngle;
|
|
||||||
theta += Math.PI / 16
|
|
||||||
) {
|
|
||||||
ps.push(m.add(n, m.smul(m.rot([1, 0, 0] as Point, theta), nSize)));
|
|
||||||
}
|
|
||||||
|
|
||||||
ps.push(ps[0]);
|
|
||||||
|
|
||||||
return ps;
|
|
||||||
}
|
|
||||||
|
|
||||||
const forwardPoints: Point[] = [];
|
|
||||||
const backwardPoints: Point[] = [];
|
|
||||||
|
|
||||||
let speed = 0;
|
|
||||||
let prevSpeed = 0;
|
|
||||||
|
|
||||||
let visibleStartIndex = 0;
|
|
||||||
let runningLength = 0;
|
|
||||||
|
|
||||||
for (let i = 1; i < len - 1; i++) {
|
|
||||||
const p = points[i - 1];
|
|
||||||
const c = points[i];
|
|
||||||
const n = points[i + 1];
|
|
||||||
|
|
||||||
const pressure = c[2];
|
|
||||||
|
|
||||||
const d = m.dist(p, c);
|
|
||||||
runningLength += d;
|
|
||||||
speed = prevSpeed + (d - prevSpeed) * 0.2;
|
|
||||||
|
|
||||||
const cSize = this.getSize(sizeOverride, pressure, i, len, runningLength);
|
|
||||||
|
|
||||||
if (cSize === 0) {
|
|
||||||
visibleStartIndex = i + 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const dirPC = m.norm(m.sub(p, c));
|
|
||||||
const dirNC = m.norm(m.sub(n, c));
|
|
||||||
const p1dirPC = m.rot(dirPC, Math.PI / 2);
|
|
||||||
const p2dirPC = m.rot(dirPC, -Math.PI / 2);
|
|
||||||
const p1dirNC = m.rot(dirNC, Math.PI / 2);
|
|
||||||
const p2dirNC = m.rot(dirNC, -Math.PI / 2);
|
|
||||||
|
|
||||||
const p1PC = m.add(c, m.smul(p1dirPC, cSize));
|
|
||||||
const p2PC = m.add(c, m.smul(p2dirPC, cSize));
|
|
||||||
const p1NC = m.add(c, m.smul(p1dirNC, cSize));
|
|
||||||
const p2NC = m.add(c, m.smul(p2dirNC, cSize));
|
|
||||||
|
|
||||||
const ftdir = m.add(p1dirPC, p2dirNC);
|
|
||||||
const btdir = m.add(p2dirPC, p1dirNC);
|
|
||||||
|
|
||||||
const paPC = m.add(
|
|
||||||
c,
|
|
||||||
m.smul(m.mag(ftdir) === 0 ? dirPC : m.norm(ftdir), cSize),
|
|
||||||
);
|
|
||||||
const paNC = m.add(
|
|
||||||
c,
|
|
||||||
m.smul(m.mag(btdir) === 0 ? dirNC : m.norm(btdir), cSize),
|
|
||||||
);
|
|
||||||
|
|
||||||
const cAngle = m.normAngle(m.angle(c, p, n));
|
|
||||||
const D_ANGLE =
|
|
||||||
(LaserPointer.constants.cornerDetectionMaxAngle / 180) *
|
|
||||||
Math.PI *
|
|
||||||
LaserPointer.constants.cornerDetectionVariance(speed);
|
|
||||||
|
|
||||||
if (Math.abs(cAngle) < D_ANGLE) {
|
|
||||||
const tAngle = Math.abs(m.normAngle(Math.PI - cAngle)); // turn angle
|
|
||||||
|
|
||||||
if (tAngle === 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cAngle < 0) {
|
|
||||||
backwardPoints.push(p2PC, paNC);
|
|
||||||
|
|
||||||
for (let theta = 0; theta <= tAngle; theta += tAngle / 4) {
|
|
||||||
forwardPoints.push(m.add(c, m.rot(m.smul(p1dirPC, cSize), theta)));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let theta = tAngle; theta >= 0; theta -= tAngle / 4) {
|
|
||||||
backwardPoints.push(m.add(c, m.rot(m.smul(p1dirPC, cSize), theta)));
|
|
||||||
}
|
|
||||||
|
|
||||||
backwardPoints.push(paNC, p1NC);
|
|
||||||
} else {
|
|
||||||
forwardPoints.push(p1PC, paPC);
|
|
||||||
|
|
||||||
for (let theta = 0; theta <= tAngle; theta += tAngle / 4) {
|
|
||||||
backwardPoints.push(
|
|
||||||
m.add(c, m.rot(m.smul(p1dirPC, -cSize), -theta)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let theta = tAngle; theta >= 0; theta -= tAngle / 4) {
|
|
||||||
forwardPoints.push(
|
|
||||||
m.add(c, m.rot(m.smul(p1dirPC, -cSize), -theta)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
forwardPoints.push(paPC, p2NC);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
forwardPoints.push(paPC);
|
|
||||||
backwardPoints.push(paNC);
|
|
||||||
}
|
|
||||||
|
|
||||||
prevSpeed = speed;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (visibleStartIndex >= len - 2) {
|
|
||||||
if (this.options.keepHead) {
|
|
||||||
const c = points[len - 1];
|
|
||||||
|
|
||||||
const ps: Point[] = [];
|
|
||||||
|
|
||||||
for (let theta = 0; theta <= Math.PI * 2; theta += Math.PI / 16) {
|
|
||||||
ps.push(
|
|
||||||
m.add(
|
|
||||||
c,
|
|
||||||
m.smul(m.rot([1, 0, 0] as Point, theta), this.options.size),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ps.push(m.add(c, m.smul([1, 0, 0] as Point, this.options.size)));
|
|
||||||
|
|
||||||
return ps;
|
|
||||||
}
|
|
||||||
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const first = points[visibleStartIndex];
|
|
||||||
const second = points[visibleStartIndex + 1];
|
|
||||||
const penultimate = points[len - 2];
|
|
||||||
const ultimate = points[len - 1];
|
|
||||||
|
|
||||||
const dirFS = m.norm(m.sub(second, first));
|
|
||||||
const dirPU = m.norm(m.sub(penultimate, ultimate));
|
|
||||||
|
|
||||||
const ppdirFS = m.rot(dirFS, -Math.PI / 2);
|
|
||||||
const ppdirPU = m.rot(dirPU, Math.PI / 2);
|
|
||||||
|
|
||||||
const startCapSize = this.getSize(sizeOverride, first[2], 0, len, 0);
|
|
||||||
const startCap: Point[] = [];
|
|
||||||
|
|
||||||
const endCapSize = this.options.keepHead
|
|
||||||
? this.options.size
|
|
||||||
: this.getSize(sizeOverride, penultimate[2], len - 2, len, runningLength);
|
|
||||||
|
|
||||||
const endCap: Point[] = [];
|
|
||||||
|
|
||||||
// Lowered threshold to 0.1,
|
|
||||||
// ensuring virtually all strokes get proper rounded caps for visual consistency.
|
|
||||||
if (startCapSize > 0.1) {
|
|
||||||
for (let theta = 0; theta <= Math.PI; theta += Math.PI / 16) {
|
|
||||||
startCap.unshift(
|
|
||||||
m.add(first, m.rot(m.smul(ppdirFS, startCapSize), -theta)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
startCap.unshift(m.add(first, m.smul(ppdirFS, -startCapSize)));
|
|
||||||
} else {
|
|
||||||
startCap.push(first);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let theta = 0; theta <= Math.PI * 3; theta += Math.PI / 16) {
|
|
||||||
endCap.push(m.add(ultimate, m.rot(m.smul(ppdirPU, -endCapSize), -theta)));
|
|
||||||
}
|
|
||||||
|
|
||||||
const strokeOutline = [
|
|
||||||
...startCap,
|
|
||||||
...forwardPoints,
|
|
||||||
...endCap.reverse(),
|
|
||||||
...backwardPoints.reverse(),
|
|
||||||
];
|
|
||||||
|
|
||||||
if (startCap.length > 0) {
|
|
||||||
strokeOutline.push(startCap[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.options.simplify > 0 && this.options.simplifyPhase === "output") {
|
|
||||||
return douglasPeucker(strokeOutline, this.options.simplify);
|
|
||||||
}
|
|
||||||
|
|
||||||
return strokeOutline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "../tsconfig.base.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"outDir": "./dist/types",
|
|
||||||
"rootDir": "../"
|
|
||||||
},
|
|
||||||
"include": ["src/**/*", "global.d.ts"],
|
|
||||||
"exclude": ["**/*.test.*", "tests", "types", "examples", "dist"]
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"extends": "../tsconfig.base.json",
|
"extends": "../tsconfig.base.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"rootDir": "../",
|
|
||||||
"outDir": "./dist/types"
|
"outDir": "./dist/types"
|
||||||
},
|
},
|
||||||
"include": ["src/**/*", "global.d.ts"],
|
"include": ["src/**/*", "global.d.ts"],
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
"declaration": true,
|
"declaration": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "Node",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
"emitDeclarationOnly": true,
|
"emitDeclarationOnly": true,
|
||||||
@@ -17,8 +17,6 @@
|
|||||||
"@excalidraw/element/*": ["./element/src/*"],
|
"@excalidraw/element/*": ["./element/src/*"],
|
||||||
"@excalidraw/excalidraw": ["./excalidraw/index.tsx"],
|
"@excalidraw/excalidraw": ["./excalidraw/index.tsx"],
|
||||||
"@excalidraw/excalidraw/*": ["./excalidraw/*"],
|
"@excalidraw/excalidraw/*": ["./excalidraw/*"],
|
||||||
"@excalidraw/laser-pointer": ["./laser-pointer/src/index.ts"],
|
|
||||||
"@excalidraw/laser-pointer/*": ["./laser-pointer/src/*"],
|
|
||||||
"@excalidraw/math": ["./math/src/index.ts"],
|
"@excalidraw/math": ["./math/src/index.ts"],
|
||||||
"@excalidraw/math/*": ["./math/src/*"],
|
"@excalidraw/math/*": ["./math/src/*"],
|
||||||
"@excalidraw/utils": ["./utils/src/index.ts"],
|
"@excalidraw/utils": ["./utils/src/index.ts"],
|
||||||
|
|||||||
@@ -30,8 +30,7 @@ exports[`exportToSvg > with default arguments 1`] = `
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeVariability": "constant",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemStrokeWidthKey": "medium",
|
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ exports[`exportToSvg > with default arguments 1`] = `
|
|||||||
"currentItemStartArrowhead": null,
|
"currentItemStartArrowhead": null,
|
||||||
"currentItemStrokeColor": "#1e1e1e",
|
"currentItemStrokeColor": "#1e1e1e",
|
||||||
"currentItemStrokeStyle": "solid",
|
"currentItemStrokeStyle": "solid",
|
||||||
"currentItemStrokeWidthKey": "medium",
|
"currentItemStrokeWidth": 2,
|
||||||
"currentItemTextAlign": "left",
|
"currentItemTextAlign": "left",
|
||||||
"cursorButton": "up",
|
"cursorButton": "up",
|
||||||
"defaultSidebarDockedPreference": false,
|
"defaultSidebarDockedPreference": false,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"extends": "../tsconfig.base.json",
|
"extends": "../tsconfig.base.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"rootDir": "../",
|
|
||||||
"outDir": "./dist/types"
|
"outDir": "./dist/types"
|
||||||
},
|
},
|
||||||
"include": ["src/**/*", "global.d.ts"],
|
"include": ["src/**/*", "global.d.ts"],
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import fs from "fs";
|
|||||||
// vitest.setup.ts
|
// vitest.setup.ts
|
||||||
import "vitest-canvas-mock";
|
import "vitest-canvas-mock";
|
||||||
import "@testing-library/jest-dom";
|
import "@testing-library/jest-dom";
|
||||||
import { configure } from "@testing-library/react";
|
|
||||||
import { vi } from "vitest";
|
import { vi } from "vitest";
|
||||||
|
|
||||||
import polyfill from "./packages/excalidraw/polyfill";
|
import polyfill from "./packages/excalidraw/polyfill";
|
||||||
@@ -17,21 +16,6 @@ import {
|
|||||||
Object.assign(globalThis, testPolyfills);
|
Object.assign(globalThis, testPolyfills);
|
||||||
PolyfillLocalStorage();
|
PolyfillLocalStorage();
|
||||||
|
|
||||||
// By default testing-library dumps the entire serialized DOM into the error
|
|
||||||
// message whenever a `waitFor`/`getBy*` fails, which floods the test output
|
|
||||||
// (often hundreds of lines of HTML per failure). Strip it out unless
|
|
||||||
// VITE_DEBUG_DOM is enabled (see .env.test), e.g. `VITE_DEBUG_DOM=true yarn test`.
|
|
||||||
const debugDom = ["true", "1"].includes(process.env.VITE_DEBUG_DOM ?? "");
|
|
||||||
if (!debugDom) {
|
|
||||||
configure({
|
|
||||||
getElementError: (message) => {
|
|
||||||
const error = new Error(message ?? undefined);
|
|
||||||
error.name = "TestingLibraryElementError";
|
|
||||||
return error;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
vi.mock("@excalidraw/common", async (importOriginal) => {
|
vi.mock("@excalidraw/common", async (importOriginal) => {
|
||||||
const module = await importOriginal<typeof import("@excalidraw/common")>();
|
const module = await importOriginal<typeof import("@excalidraw/common")>();
|
||||||
|
|
||||||
|
|||||||
+1
-3
@@ -12,7 +12,7 @@
|
|||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "node",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
@@ -27,8 +27,6 @@
|
|||||||
"@excalidraw/element/*": ["./packages/element/src/*"],
|
"@excalidraw/element/*": ["./packages/element/src/*"],
|
||||||
"@excalidraw/fractional-indexing": ["./packages/fractional-indexing/src/index.ts"],
|
"@excalidraw/fractional-indexing": ["./packages/fractional-indexing/src/index.ts"],
|
||||||
"@excalidraw/fractional-indexing/*": ["./packages/fractional-indexing/src/*"],
|
"@excalidraw/fractional-indexing/*": ["./packages/fractional-indexing/src/*"],
|
||||||
"@excalidraw/laser-pointer": ["./packages/laser-pointer/src/index.ts"],
|
|
||||||
"@excalidraw/laser-pointer/*": ["./packages/laser-pointer/src/*"],
|
|
||||||
"@excalidraw/math": ["./packages/math/src/index.ts"],
|
"@excalidraw/math": ["./packages/math/src/index.ts"],
|
||||||
"@excalidraw/math/*": ["./packages/math/src/*"],
|
"@excalidraw/math/*": ["./packages/math/src/*"],
|
||||||
"@excalidraw/utils": ["./packages/utils/src/index.ts"],
|
"@excalidraw/utils": ["./packages/utils/src/index.ts"],
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"public": true,
|
||||||
"headers": [
|
"headers": [
|
||||||
{
|
{
|
||||||
"source": "/(.*)",
|
"source": "/(.*)",
|
||||||
|
|||||||
@@ -59,17 +59,6 @@ export default defineConfig({
|
|||||||
"./packages/fractional-indexing/src/$1",
|
"./packages/fractional-indexing/src/$1",
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
find: /^@excalidraw\/laser-pointer$/,
|
|
||||||
replacement: path.resolve(
|
|
||||||
__dirname,
|
|
||||||
"./packages/laser-pointer/src/index.ts",
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
find: /^@excalidraw\/laser-pointer\/(.*?)/,
|
|
||||||
replacement: path.resolve(__dirname, "./packages/laser-pointer/src/$1"),
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
@@ -82,8 +71,6 @@ export default defineConfig({
|
|||||||
setupFiles: ["./setupTests.ts"],
|
setupFiles: ["./setupTests.ts"],
|
||||||
globals: true,
|
globals: true,
|
||||||
environment: "jsdom",
|
environment: "jsdom",
|
||||||
// don't list skipped tests in the failure tree — keeps output readable
|
|
||||||
hideSkippedTests: true,
|
|
||||||
coverage: {
|
coverage: {
|
||||||
reporter: ["text", "json-summary", "json", "html", "lcovonly"],
|
reporter: ["text", "json-summary", "json", "html", "lcovonly"],
|
||||||
// Since v2, it ignores empty lines by default and we need to disable it as it affects the coverage
|
// Since v2, it ignores empty lines by default and we need to disable it as it affects the coverage
|
||||||
|
|||||||
@@ -1521,6 +1521,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@excalidraw/eslint-config/-/eslint-config-1.0.3.tgz#2122ef7413ae77874ae9848ce0f1c6b3f0d8bbbd"
|
resolved "https://registry.yarnpkg.com/@excalidraw/eslint-config/-/eslint-config-1.0.3.tgz#2122ef7413ae77874ae9848ce0f1c6b3f0d8bbbd"
|
||||||
integrity sha512-GemHNF5Z6ga0BWBSX7GJaNBUchLu6RwTcAB84eX1MeckRNhNasAsPCdelDlFalz27iS4RuYEQh0bPE8SRxJgbQ==
|
integrity sha512-GemHNF5Z6ga0BWBSX7GJaNBUchLu6RwTcAB84eX1MeckRNhNasAsPCdelDlFalz27iS4RuYEQh0bPE8SRxJgbQ==
|
||||||
|
|
||||||
|
"@excalidraw/laser-pointer@1.3.1":
|
||||||
|
version "1.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@excalidraw/laser-pointer/-/laser-pointer-1.3.1.tgz#7c40836598e8e6ad91f01057883ed8b88fb9266c"
|
||||||
|
integrity sha512-psA1z1N2qeAfsORdXc9JmD2y4CmDwmuMRxnNdJHZexIcPwaNEyIpNcelw+QkL9rz9tosaN9krXuKaRqYpRAR6g==
|
||||||
|
|
||||||
"@excalidraw/markdown-to-text@0.1.2":
|
"@excalidraw/markdown-to-text@0.1.2":
|
||||||
version "0.1.2"
|
version "0.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/@excalidraw/markdown-to-text/-/markdown-to-text-0.1.2.tgz#1703705e7da608cf478f17bfe96fb295f55a23eb"
|
resolved "https://registry.yarnpkg.com/@excalidraw/markdown-to-text/-/markdown-to-text-0.1.2.tgz#1703705e7da608cf478f17bfe96fb295f55a23eb"
|
||||||
|
|||||||
Reference in New Issue
Block a user