Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c68c2be44c | |||
| be65ac7f22 | |||
| 09e249ae57 | |||
| f0c1e9707a | |||
| 7f4659339b | |||
| 0987c5b770 | |||
| 0a529bd2ed | |||
| 794b2b21a7 | |||
| a71bb63d1f | |||
| 661d6a4a75 | |||
| defd34923a | |||
| c540bd68aa | |||
| eddbe55f50 | |||
| 6e577d1308 | |||
| 80b9fd18b9 | |||
| dbc48cfee2 | |||
| 3fc89b716a | |||
| 30743ec726 | |||
| 86d49a273b | |||
| 92fe9b95d5 |
@@ -20,7 +20,7 @@
|
|||||||
name="description"
|
name="description"
|
||||||
content="Excalidraw is a virtual collaborative whiteboard tool that lets you easily sketch diagrams that have a hand-drawn feel to them."
|
content="Excalidraw is a virtual collaborative whiteboard tool that lets you easily sketch diagrams that have a hand-drawn feel to them."
|
||||||
/>
|
/>
|
||||||
<meta name="image" content="https://excalidraw.com/og-image-2.png" />
|
<meta name="image" content="https://excalidraw.com/og-image-3.png" />
|
||||||
|
|
||||||
<!-- Open Graph / Facebook -->
|
<!-- Open Graph / Facebook -->
|
||||||
<meta property="og:site_name" content="Excalidraw" />
|
<meta property="og:site_name" content="Excalidraw" />
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
property="og:description"
|
property="og:description"
|
||||||
content="Excalidraw is a virtual collaborative whiteboard tool that lets you easily sketch diagrams that have a hand-drawn feel to them."
|
content="Excalidraw is a virtual collaborative whiteboard tool that lets you easily sketch diagrams that have a hand-drawn feel to them."
|
||||||
/>
|
/>
|
||||||
<meta property="og:image" content="https://excalidraw.com/og-image-2.png" />
|
<meta property="og:image" content="https://excalidraw.com/og-image-3.png" />
|
||||||
|
|
||||||
<!-- Twitter -->
|
<!-- Twitter -->
|
||||||
<meta property="twitter:card" content="summary_large_image" />
|
<meta property="twitter:card" content="summary_large_image" />
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
/>
|
/>
|
||||||
<meta
|
<meta
|
||||||
property="twitter:image"
|
property="twitter:image"
|
||||||
content="https://excalidraw.com/og-twitter-v2.png"
|
content="https://excalidraw.com/og-image-3.png"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- General tags -->
|
<!-- General tags -->
|
||||||
|
|||||||
@@ -135,7 +135,8 @@ export type ActionName =
|
|||||||
| "createContainerFromText"
|
| "createContainerFromText"
|
||||||
| "wrapTextInContainer"
|
| "wrapTextInContainer"
|
||||||
| "commandPalette"
|
| "commandPalette"
|
||||||
| "autoResize";
|
| "autoResize"
|
||||||
|
| "elementStats";
|
||||||
|
|
||||||
export type PanelComponentProps = {
|
export type PanelComponentProps = {
|
||||||
elements: readonly ExcalidrawElement[];
|
elements: readonly ExcalidrawElement[];
|
||||||
|
|||||||
@@ -1477,19 +1477,28 @@ export class ElementsChange implements Change<SceneElementsMap> {
|
|||||||
return elements;
|
return elements;
|
||||||
}
|
}
|
||||||
|
|
||||||
const previous = Array.from(elements.values());
|
const unordered = Array.from(elements.values());
|
||||||
const reordered = orderByFractionalIndex([...previous]);
|
const ordered = orderByFractionalIndex([...unordered]);
|
||||||
|
const moved = Delta.getRightDifferences(unordered, ordered, true).reduce(
|
||||||
|
(acc, arrayIndex) => {
|
||||||
|
const candidate = unordered[Number(arrayIndex)];
|
||||||
|
if (candidate && changed.has(candidate.id)) {
|
||||||
|
acc.set(candidate.id, candidate);
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
return acc;
|
||||||
!flags.containsVisibleDifference &&
|
},
|
||||||
Delta.isRightDifferent(previous, reordered, true)
|
new Map(),
|
||||||
) {
|
);
|
||||||
|
|
||||||
|
if (!flags.containsVisibleDifference && moved.size) {
|
||||||
// we found a difference in order!
|
// we found a difference in order!
|
||||||
flags.containsVisibleDifference = true;
|
flags.containsVisibleDifference = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// let's synchronize all invalid indices of moved elements
|
// synchronize all elements that were actually moved
|
||||||
return arrayToMap(syncMovedIndices(reordered, changed)) as typeof elements;
|
// could fallback to synchronizing all invalid indices
|
||||||
|
return arrayToMap(syncMovedIndices(ordered, moved)) as typeof elements;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ import {
|
|||||||
isIOS,
|
isIOS,
|
||||||
supportsResizeObserver,
|
supportsResizeObserver,
|
||||||
DEFAULT_COLLISION_THRESHOLD,
|
DEFAULT_COLLISION_THRESHOLD,
|
||||||
|
DEFAULT_TEXT_ALIGN,
|
||||||
} from "../constants";
|
} from "../constants";
|
||||||
import type { ExportedElements } from "../data";
|
import type { ExportedElements } from "../data";
|
||||||
import { exportCanvas, loadFromBlob } from "../data";
|
import { exportCanvas, loadFromBlob } from "../data";
|
||||||
@@ -331,6 +332,8 @@ import {
|
|||||||
getLineHeightInPx,
|
getLineHeightInPx,
|
||||||
isMeasureTextSupported,
|
isMeasureTextSupported,
|
||||||
isValidTextContainer,
|
isValidTextContainer,
|
||||||
|
measureText,
|
||||||
|
wrapText,
|
||||||
} from "../element/textElement";
|
} from "../element/textElement";
|
||||||
import {
|
import {
|
||||||
showHyperlinkTooltip,
|
showHyperlinkTooltip,
|
||||||
@@ -430,6 +433,8 @@ import {
|
|||||||
} from "./hyperlink/helpers";
|
} from "./hyperlink/helpers";
|
||||||
import { getShortcutFromShortcutName } from "../actions/shortcuts";
|
import { getShortcutFromShortcutName } from "../actions/shortcuts";
|
||||||
import { actionTextAutoResize } from "../actions/actionTextAutoResize";
|
import { actionTextAutoResize } from "../actions/actionTextAutoResize";
|
||||||
|
import { getVisibleSceneBounds } from "../element/bounds";
|
||||||
|
import { Stats } from "./Stats";
|
||||||
|
|
||||||
const AppContext = React.createContext<AppClassProperties>(null!);
|
const AppContext = React.createContext<AppClassProperties>(null!);
|
||||||
const AppPropsContext = React.createContext<AppProps>(null!);
|
const AppPropsContext = React.createContext<AppProps>(null!);
|
||||||
@@ -541,7 +546,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
public library: AppClassProperties["library"];
|
public library: AppClassProperties["library"];
|
||||||
public libraryItemsFromStorage: LibraryItems | undefined;
|
public libraryItemsFromStorage: LibraryItems | undefined;
|
||||||
public id: string;
|
public id: string;
|
||||||
private store: Store;
|
store: Store;
|
||||||
private history: History;
|
private history: History;
|
||||||
private excalidrawContainerValue: {
|
private excalidrawContainerValue: {
|
||||||
container: HTMLDivElement | null;
|
container: HTMLDivElement | null;
|
||||||
@@ -1665,6 +1670,19 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{this.state.showStats && (
|
||||||
|
<Stats
|
||||||
|
appState={this.state}
|
||||||
|
setAppState={this.setState}
|
||||||
|
scene={this.scene}
|
||||||
|
onClose={() => {
|
||||||
|
this.actionManager.executeAction(
|
||||||
|
actionToggleStats,
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
renderCustomStats={renderCustomStats}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<StaticCanvas
|
<StaticCanvas
|
||||||
canvas={this.canvas}
|
canvas={this.canvas}
|
||||||
rc={this.rc}
|
rc={this.rc}
|
||||||
@@ -2565,7 +2583,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
addEventListener(document, EVENT.KEYUP, this.onKeyUp, { passive: true }),
|
addEventListener(document, EVENT.KEYUP, this.onKeyUp, { passive: true }),
|
||||||
addEventListener(
|
addEventListener(
|
||||||
document,
|
document,
|
||||||
EVENT.MOUSE_MOVE,
|
EVENT.POINTER_MOVE,
|
||||||
this.updateCurrentCursorPosition,
|
this.updateCurrentCursorPosition,
|
||||||
),
|
),
|
||||||
// rerender text elements on font load to fix #637 && #1553
|
// rerender text elements on font load to fix #637 && #1553
|
||||||
@@ -3341,32 +3359,53 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
text,
|
text,
|
||||||
fontSize: this.state.currentItemFontSize,
|
fontSize: this.state.currentItemFontSize,
|
||||||
fontFamily: this.state.currentItemFontFamily,
|
fontFamily: this.state.currentItemFontFamily,
|
||||||
textAlign: this.state.currentItemTextAlign,
|
textAlign: DEFAULT_TEXT_ALIGN,
|
||||||
verticalAlign: DEFAULT_VERTICAL_ALIGN,
|
verticalAlign: DEFAULT_VERTICAL_ALIGN,
|
||||||
locked: false,
|
locked: false,
|
||||||
};
|
};
|
||||||
|
const fontString = getFontString({
|
||||||
|
fontSize: textElementProps.fontSize,
|
||||||
|
fontFamily: textElementProps.fontFamily,
|
||||||
|
});
|
||||||
|
const lineHeight = getDefaultLineHeight(textElementProps.fontFamily);
|
||||||
|
const [x1, , x2] = getVisibleSceneBounds(this.state);
|
||||||
|
// long texts should not go beyond 800 pixels in width nor should it go below 200 px
|
||||||
|
const maxTextWidth = Math.max(Math.min((x2 - x1) * 0.5, 800), 200);
|
||||||
const LINE_GAP = 10;
|
const LINE_GAP = 10;
|
||||||
let currentY = y;
|
let currentY = y;
|
||||||
|
|
||||||
const lines = isPlainPaste ? [text] : text.split("\n");
|
const lines = isPlainPaste ? [text] : text.split("\n");
|
||||||
const textElements = lines.reduce(
|
const textElements = lines.reduce(
|
||||||
(acc: ExcalidrawTextElement[], line, idx) => {
|
(acc: ExcalidrawTextElement[], line, idx) => {
|
||||||
const text = line.trim();
|
const originalText = line.trim();
|
||||||
|
if (originalText.length) {
|
||||||
const lineHeight = getDefaultLineHeight(textElementProps.fontFamily);
|
|
||||||
if (text.length) {
|
|
||||||
const topLayerFrame = this.getTopLayerFrameAtSceneCoords({
|
const topLayerFrame = this.getTopLayerFrameAtSceneCoords({
|
||||||
x,
|
x,
|
||||||
y: currentY,
|
y: currentY,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let metrics = measureText(originalText, fontString, lineHeight);
|
||||||
|
const isTextWrapped = metrics.width > maxTextWidth;
|
||||||
|
|
||||||
|
const text = isTextWrapped
|
||||||
|
? wrapText(originalText, fontString, maxTextWidth)
|
||||||
|
: originalText;
|
||||||
|
|
||||||
|
metrics = isTextWrapped
|
||||||
|
? measureText(text, fontString, lineHeight)
|
||||||
|
: metrics;
|
||||||
|
|
||||||
|
const startX = x - metrics.width / 2;
|
||||||
|
const startY = currentY - metrics.height / 2;
|
||||||
|
|
||||||
const element = newTextElement({
|
const element = newTextElement({
|
||||||
...textElementProps,
|
...textElementProps,
|
||||||
x,
|
x: startX,
|
||||||
y: currentY,
|
y: startY,
|
||||||
text,
|
text,
|
||||||
|
originalText,
|
||||||
lineHeight,
|
lineHeight,
|
||||||
|
autoResize: !isTextWrapped,
|
||||||
frameId: topLayerFrame ? topLayerFrame.id : null,
|
frameId: topLayerFrame ? topLayerFrame.id : null,
|
||||||
});
|
});
|
||||||
acc.push(element);
|
acc.push(element);
|
||||||
@@ -3683,7 +3722,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
elements?: SceneData["elements"];
|
elements?: SceneData["elements"];
|
||||||
appState?: Pick<AppState, K> | null;
|
appState?: Pick<AppState, K> | null;
|
||||||
collaborators?: SceneData["collaborators"];
|
collaborators?: SceneData["collaborators"];
|
||||||
/** @default StoreAction.CAPTURE */
|
/** @default StoreAction.NONE */
|
||||||
storeAction?: SceneData["storeAction"];
|
storeAction?: SceneData["storeAction"];
|
||||||
}) => {
|
}) => {
|
||||||
const nextElements = syncInvalidIndices(sceneData.elements ?? []);
|
const nextElements = syncInvalidIndices(sceneData.elements ?? []);
|
||||||
|
|||||||
@@ -39,8 +39,6 @@ import { JSONExportDialog } from "./JSONExportDialog";
|
|||||||
import { PenModeButton } from "./PenModeButton";
|
import { PenModeButton } from "./PenModeButton";
|
||||||
import { trackEvent } from "../analytics";
|
import { trackEvent } from "../analytics";
|
||||||
import { useDevice } from "./App";
|
import { useDevice } from "./App";
|
||||||
import { Stats } from "./Stats";
|
|
||||||
import { actionToggleStats } from "../actions/actionToggleStats";
|
|
||||||
import Footer from "./footer/Footer";
|
import Footer from "./footer/Footer";
|
||||||
import { isSidebarDockedAtom } from "./Sidebar/Sidebar";
|
import { isSidebarDockedAtom } from "./Sidebar/Sidebar";
|
||||||
import { jotaiScope } from "../jotai";
|
import { jotaiScope } from "../jotai";
|
||||||
@@ -542,17 +540,6 @@ const LayerUI = ({
|
|||||||
showExitZenModeBtn={showExitZenModeBtn}
|
showExitZenModeBtn={showExitZenModeBtn}
|
||||||
renderWelcomeScreen={renderWelcomeScreen}
|
renderWelcomeScreen={renderWelcomeScreen}
|
||||||
/>
|
/>
|
||||||
{appState.showStats && (
|
|
||||||
<Stats
|
|
||||||
appState={appState}
|
|
||||||
setAppState={setAppState}
|
|
||||||
elements={elements}
|
|
||||||
onClose={() => {
|
|
||||||
actionManager.executeAction(actionToggleStats);
|
|
||||||
}}
|
|
||||||
renderCustomStats={renderCustomStats}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{appState.scrolledOutside && (
|
{appState.scrolledOutside && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -21,8 +21,6 @@ import { Section } from "./Section";
|
|||||||
import { SCROLLBAR_WIDTH, SCROLLBAR_MARGIN } from "../scene/scrollbars";
|
import { SCROLLBAR_WIDTH, SCROLLBAR_MARGIN } from "../scene/scrollbars";
|
||||||
import { LockButton } from "./LockButton";
|
import { LockButton } from "./LockButton";
|
||||||
import { PenModeButton } from "./PenModeButton";
|
import { PenModeButton } from "./PenModeButton";
|
||||||
import { Stats } from "./Stats";
|
|
||||||
import { actionToggleStats } from "../actions";
|
|
||||||
import { HandButton } from "./HandButton";
|
import { HandButton } from "./HandButton";
|
||||||
import { isHandToolActive } from "../appState";
|
import { isHandToolActive } from "../appState";
|
||||||
import { useTunnels } from "../context/tunnels";
|
import { useTunnels } from "../context/tunnels";
|
||||||
@@ -157,17 +155,6 @@ export const MobileMenu = ({
|
|||||||
<>
|
<>
|
||||||
{renderSidebars()}
|
{renderSidebars()}
|
||||||
{!appState.viewModeEnabled && renderToolbar()}
|
{!appState.viewModeEnabled && renderToolbar()}
|
||||||
{!appState.openMenu && appState.showStats && (
|
|
||||||
<Stats
|
|
||||||
appState={appState}
|
|
||||||
setAppState={setAppState}
|
|
||||||
elements={elements}
|
|
||||||
onClose={() => {
|
|
||||||
actionManager.executeAction(actionToggleStats);
|
|
||||||
}}
|
|
||||||
renderCustomStats={renderCustomStats}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<div
|
<div
|
||||||
className="App-bottom-bar"
|
className="App-bottom-bar"
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -1,108 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { getCommonBounds } from "../element/bounds";
|
|
||||||
import type { NonDeletedExcalidrawElement } from "../element/types";
|
|
||||||
import { t } from "../i18n";
|
|
||||||
import { getTargetElements } from "../scene";
|
|
||||||
import type { ExcalidrawProps, UIAppState } from "../types";
|
|
||||||
import { CloseIcon } from "./icons";
|
|
||||||
import { Island } from "./Island";
|
|
||||||
import "./Stats.scss";
|
|
||||||
|
|
||||||
export const Stats = (props: {
|
|
||||||
appState: UIAppState;
|
|
||||||
setAppState: React.Component<any, UIAppState>["setState"];
|
|
||||||
elements: readonly NonDeletedExcalidrawElement[];
|
|
||||||
onClose: () => void;
|
|
||||||
renderCustomStats: ExcalidrawProps["renderCustomStats"];
|
|
||||||
}) => {
|
|
||||||
const boundingBox = getCommonBounds(props.elements);
|
|
||||||
const selectedElements = getTargetElements(props.elements, props.appState);
|
|
||||||
const selectedBoundingBox = getCommonBounds(selectedElements);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="Stats">
|
|
||||||
<Island padding={2}>
|
|
||||||
<div className="close" onClick={props.onClose}>
|
|
||||||
{CloseIcon}
|
|
||||||
</div>
|
|
||||||
<h3>{t("stats.title")}</h3>
|
|
||||||
<table>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th colSpan={2}>{t("stats.scene")}</th>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>{t("stats.elements")}</td>
|
|
||||||
<td>{props.elements.length}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>{t("stats.width")}</td>
|
|
||||||
<td>{Math.round(boundingBox[2]) - Math.round(boundingBox[0])}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>{t("stats.height")}</td>
|
|
||||||
<td>{Math.round(boundingBox[3]) - Math.round(boundingBox[1])}</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
{selectedElements.length === 1 && (
|
|
||||||
<tr>
|
|
||||||
<th colSpan={2}>{t("stats.element")}</th>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{selectedElements.length > 1 && (
|
|
||||||
<>
|
|
||||||
<tr>
|
|
||||||
<th colSpan={2}>{t("stats.selected")}</th>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>{t("stats.elements")}</td>
|
|
||||||
<td>{selectedElements.length}</td>
|
|
||||||
</tr>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{selectedElements.length > 0 && (
|
|
||||||
<>
|
|
||||||
<tr>
|
|
||||||
<td>{"x"}</td>
|
|
||||||
<td>{Math.round(selectedBoundingBox[0])}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>{"y"}</td>
|
|
||||||
<td>{Math.round(selectedBoundingBox[1])}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>{t("stats.width")}</td>
|
|
||||||
<td>
|
|
||||||
{Math.round(
|
|
||||||
selectedBoundingBox[2] - selectedBoundingBox[0],
|
|
||||||
)}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>{t("stats.height")}</td>
|
|
||||||
<td>
|
|
||||||
{Math.round(
|
|
||||||
selectedBoundingBox[3] - selectedBoundingBox[1],
|
|
||||||
)}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{selectedElements.length === 1 && (
|
|
||||||
<tr>
|
|
||||||
<td>{t("stats.angle")}</td>
|
|
||||||
<td>
|
|
||||||
{`${Math.round(
|
|
||||||
(selectedElements[0].angle * 180) / Math.PI,
|
|
||||||
)}°`}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
{props.renderCustomStats?.(props.elements, props.appState)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</Island>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import { mutateElement } from "../../element/mutateElement";
|
||||||
|
import { getBoundTextElement } from "../../element/textElement";
|
||||||
|
import { isArrowElement } from "../../element/typeChecks";
|
||||||
|
import type { ElementsMap, ExcalidrawElement } from "../../element/types";
|
||||||
|
import { degreeToRadian, radianToDegree } from "../../math";
|
||||||
|
import DragInput from "./DragInput";
|
||||||
|
import type { DragInputCallbackType } from "./DragInput";
|
||||||
|
import { getStepSizedValue, isPropertyEditable } from "./utils";
|
||||||
|
|
||||||
|
interface AngleProps {
|
||||||
|
element: ExcalidrawElement;
|
||||||
|
elementsMap: ElementsMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
const STEP_SIZE = 15;
|
||||||
|
|
||||||
|
const Angle = ({ element, elementsMap }: AngleProps) => {
|
||||||
|
const handleDegreeChange: DragInputCallbackType = ({
|
||||||
|
accumulatedChange,
|
||||||
|
stateAtStart,
|
||||||
|
shouldChangeByStepSize,
|
||||||
|
nextValue,
|
||||||
|
}) => {
|
||||||
|
const _stateAtStart = stateAtStart[0];
|
||||||
|
if (_stateAtStart) {
|
||||||
|
if (nextValue !== undefined) {
|
||||||
|
const nextAngle = degreeToRadian(nextValue);
|
||||||
|
mutateElement(element, {
|
||||||
|
angle: nextAngle,
|
||||||
|
});
|
||||||
|
|
||||||
|
const boundTextElement = getBoundTextElement(element, elementsMap);
|
||||||
|
if (boundTextElement && !isArrowElement(element)) {
|
||||||
|
mutateElement(boundTextElement, { angle: nextAngle });
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const originalAngleInDegrees =
|
||||||
|
Math.round(radianToDegree(_stateAtStart.angle) * 100) / 100;
|
||||||
|
const changeInDegrees = Math.round(accumulatedChange);
|
||||||
|
let nextAngleInDegrees = (originalAngleInDegrees + changeInDegrees) % 360;
|
||||||
|
if (shouldChangeByStepSize) {
|
||||||
|
nextAngleInDegrees = getStepSizedValue(nextAngleInDegrees, STEP_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
nextAngleInDegrees =
|
||||||
|
nextAngleInDegrees < 0 ? nextAngleInDegrees + 360 : nextAngleInDegrees;
|
||||||
|
|
||||||
|
const nextAngle = degreeToRadian(nextAngleInDegrees);
|
||||||
|
|
||||||
|
mutateElement(element, {
|
||||||
|
angle: nextAngle,
|
||||||
|
});
|
||||||
|
|
||||||
|
const boundTextElement = getBoundTextElement(element, elementsMap);
|
||||||
|
if (boundTextElement && !isArrowElement(element)) {
|
||||||
|
mutateElement(boundTextElement, { angle: nextAngle });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DragInput
|
||||||
|
label="A"
|
||||||
|
value={Math.round(radianToDegree(element.angle) * 100) / 100}
|
||||||
|
elements={[element]}
|
||||||
|
dragInputCallback={handleDegreeChange}
|
||||||
|
editable={isPropertyEditable(element, "angle")}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Angle;
|
||||||
@@ -0,0 +1,247 @@
|
|||||||
|
import type { ElementsMap, ExcalidrawElement } from "../../element/types";
|
||||||
|
import DragInput from "./DragInput";
|
||||||
|
import type { DragInputCallbackType } from "./DragInput";
|
||||||
|
import { getStepSizedValue, isPropertyEditable } from "./utils";
|
||||||
|
import { mutateElement } from "../../element/mutateElement";
|
||||||
|
import {
|
||||||
|
measureFontSizeFromWidth,
|
||||||
|
rescalePointsInElement,
|
||||||
|
} from "../../element/resizeElements";
|
||||||
|
import {
|
||||||
|
getApproxMinLineHeight,
|
||||||
|
getApproxMinLineWidth,
|
||||||
|
getBoundTextElement,
|
||||||
|
getBoundTextMaxWidth,
|
||||||
|
handleBindTextResize,
|
||||||
|
} from "../../element/textElement";
|
||||||
|
import { getFontString } from "../../utils";
|
||||||
|
import { updateBoundElements } from "../../element/binding";
|
||||||
|
|
||||||
|
interface DimensionDragInputProps {
|
||||||
|
property: "width" | "height";
|
||||||
|
element: ExcalidrawElement;
|
||||||
|
elementsMap: ElementsMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
const STEP_SIZE = 10;
|
||||||
|
const _shouldKeepAspectRatio = (element: ExcalidrawElement) => {
|
||||||
|
return element.type === "image";
|
||||||
|
};
|
||||||
|
|
||||||
|
export const newOrigin = (
|
||||||
|
x1: number,
|
||||||
|
y1: number,
|
||||||
|
w1: number,
|
||||||
|
h1: number,
|
||||||
|
w2: number,
|
||||||
|
h2: number,
|
||||||
|
angle: number,
|
||||||
|
) => {
|
||||||
|
/**
|
||||||
|
* The formula below is the result of solving
|
||||||
|
* rotate(x1, y1, cx1, cy1, angle) = rotate(x2, y2, cx2, cy2, angle)
|
||||||
|
* where rotate is the function defined in math.ts
|
||||||
|
*
|
||||||
|
* This is so that the new origin (x2, y2),
|
||||||
|
* when rotated against the new center (cx2, cy2),
|
||||||
|
* coincides with (x1, y1) rotated against (cx1, cy1)
|
||||||
|
*
|
||||||
|
* The reason for doing this computation is so the element's top left corner
|
||||||
|
* on the canvas remains fixed after any changes in its dimension.
|
||||||
|
*/
|
||||||
|
|
||||||
|
return {
|
||||||
|
x:
|
||||||
|
x1 +
|
||||||
|
(w1 - w2) / 2 +
|
||||||
|
((w2 - w1) / 2) * Math.cos(angle) +
|
||||||
|
((h1 - h2) / 2) * Math.sin(angle),
|
||||||
|
y:
|
||||||
|
y1 +
|
||||||
|
(h1 - h2) / 2 +
|
||||||
|
((w2 - w1) / 2) * Math.sin(angle) +
|
||||||
|
((h2 - h1) / 2) * Math.cos(angle),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const resizeElement = (
|
||||||
|
nextWidth: number,
|
||||||
|
nextHeight: number,
|
||||||
|
keepAspectRatio: boolean,
|
||||||
|
latestState: ExcalidrawElement,
|
||||||
|
stateAtStart: ExcalidrawElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
|
originalElementsMap: Map<string, ExcalidrawElement>,
|
||||||
|
) => {
|
||||||
|
mutateElement(latestState, {
|
||||||
|
...newOrigin(
|
||||||
|
latestState.x,
|
||||||
|
latestState.y,
|
||||||
|
latestState.width,
|
||||||
|
latestState.height,
|
||||||
|
nextWidth,
|
||||||
|
nextHeight,
|
||||||
|
latestState.angle,
|
||||||
|
),
|
||||||
|
width: nextWidth,
|
||||||
|
height: nextHeight,
|
||||||
|
...rescalePointsInElement(stateAtStart, nextWidth, nextHeight, true),
|
||||||
|
});
|
||||||
|
|
||||||
|
let boundTextFont: { fontSize?: number } = {};
|
||||||
|
const boundTextElement = getBoundTextElement(latestState, elementsMap);
|
||||||
|
|
||||||
|
if (boundTextElement) {
|
||||||
|
boundTextFont = {
|
||||||
|
fontSize: boundTextElement.fontSize,
|
||||||
|
};
|
||||||
|
if (keepAspectRatio) {
|
||||||
|
const updatedElement = {
|
||||||
|
...latestState,
|
||||||
|
width: nextWidth,
|
||||||
|
height: nextHeight,
|
||||||
|
};
|
||||||
|
|
||||||
|
const nextFont = measureFontSizeFromWidth(
|
||||||
|
boundTextElement,
|
||||||
|
elementsMap,
|
||||||
|
getBoundTextMaxWidth(updatedElement, boundTextElement),
|
||||||
|
);
|
||||||
|
boundTextFont = {
|
||||||
|
fontSize: nextFont?.size ?? boundTextElement.fontSize,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const minWidth = getApproxMinLineWidth(
|
||||||
|
getFontString(boundTextElement),
|
||||||
|
boundTextElement.lineHeight,
|
||||||
|
);
|
||||||
|
const minHeight = getApproxMinLineHeight(
|
||||||
|
boundTextElement.fontSize,
|
||||||
|
boundTextElement.lineHeight,
|
||||||
|
);
|
||||||
|
nextWidth = Math.max(nextWidth, minWidth);
|
||||||
|
nextHeight = Math.max(nextHeight, minHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateBoundElements(latestState, elementsMap, {
|
||||||
|
newSize: {
|
||||||
|
width: nextWidth,
|
||||||
|
height: nextHeight,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (boundTextElement && boundTextFont) {
|
||||||
|
mutateElement(boundTextElement, {
|
||||||
|
fontSize: boundTextFont.fontSize,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
handleBindTextResize(latestState, elementsMap, "e", keepAspectRatio);
|
||||||
|
};
|
||||||
|
|
||||||
|
const DimensionDragInput = ({
|
||||||
|
property,
|
||||||
|
element,
|
||||||
|
elementsMap,
|
||||||
|
}: DimensionDragInputProps) => {
|
||||||
|
const handleDimensionChange: DragInputCallbackType = ({
|
||||||
|
accumulatedChange,
|
||||||
|
stateAtStart,
|
||||||
|
originalElementsMap,
|
||||||
|
shouldKeepAspectRatio,
|
||||||
|
shouldChangeByStepSize,
|
||||||
|
nextValue,
|
||||||
|
}) => {
|
||||||
|
const _stateAtStart = stateAtStart[0];
|
||||||
|
if (_stateAtStart) {
|
||||||
|
const keepAspectRatio =
|
||||||
|
shouldKeepAspectRatio || _shouldKeepAspectRatio(element);
|
||||||
|
const aspectRatio = _stateAtStart.width / _stateAtStart.height;
|
||||||
|
|
||||||
|
if (nextValue !== undefined) {
|
||||||
|
const nextWidth = Math.max(
|
||||||
|
property === "width"
|
||||||
|
? nextValue
|
||||||
|
: keepAspectRatio
|
||||||
|
? nextValue * aspectRatio
|
||||||
|
: _stateAtStart.width,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
const nextHeight = Math.max(
|
||||||
|
property === "height"
|
||||||
|
? nextValue
|
||||||
|
: keepAspectRatio
|
||||||
|
? nextValue / aspectRatio
|
||||||
|
: _stateAtStart.height,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
resizeElement(
|
||||||
|
nextWidth,
|
||||||
|
nextHeight,
|
||||||
|
keepAspectRatio,
|
||||||
|
element,
|
||||||
|
_stateAtStart,
|
||||||
|
elementsMap,
|
||||||
|
originalElementsMap,
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const changeInWidth = property === "width" ? accumulatedChange : 0;
|
||||||
|
const changeInHeight = property === "height" ? accumulatedChange : 0;
|
||||||
|
|
||||||
|
let nextWidth = Math.max(0, _stateAtStart.width + changeInWidth);
|
||||||
|
if (property === "width") {
|
||||||
|
if (shouldChangeByStepSize) {
|
||||||
|
nextWidth = getStepSizedValue(nextWidth, STEP_SIZE);
|
||||||
|
} else {
|
||||||
|
nextWidth = Math.round(nextWidth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let nextHeight = Math.max(0, _stateAtStart.height + changeInHeight);
|
||||||
|
if (property === "height") {
|
||||||
|
if (shouldChangeByStepSize) {
|
||||||
|
nextHeight = getStepSizedValue(nextHeight, STEP_SIZE);
|
||||||
|
} else {
|
||||||
|
nextHeight = Math.round(nextHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keepAspectRatio) {
|
||||||
|
if (property === "width") {
|
||||||
|
nextHeight = Math.round((nextWidth / aspectRatio) * 100) / 100;
|
||||||
|
} else {
|
||||||
|
nextWidth = Math.round(nextHeight * aspectRatio * 100) / 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resizeElement(
|
||||||
|
nextWidth,
|
||||||
|
nextHeight,
|
||||||
|
keepAspectRatio,
|
||||||
|
element,
|
||||||
|
_stateAtStart,
|
||||||
|
elementsMap,
|
||||||
|
originalElementsMap,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DragInput
|
||||||
|
label={property === "width" ? "W" : "H"}
|
||||||
|
elements={[element]}
|
||||||
|
dragInputCallback={handleDimensionChange}
|
||||||
|
value={
|
||||||
|
Math.round(
|
||||||
|
(property === "width" ? element.width : element.height) * 100,
|
||||||
|
) / 100
|
||||||
|
}
|
||||||
|
editable={isPropertyEditable(element, property)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DimensionDragInput;
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
.excalidraw {
|
||||||
|
.drag-input-container {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&:focus-within {
|
||||||
|
box-shadow: 0 0 0 1px var(--color-primary-darkest);
|
||||||
|
border-radius: var(--border-radius-lg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drag-input-label {
|
||||||
|
height: var(--default-button-size);
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 0.5rem 0.5rem 0.5rem 0.75rem;
|
||||||
|
border: 1px solid var(--default-border-color);
|
||||||
|
border-right: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
:root[dir="ltr"] & {
|
||||||
|
border-radius: var(--border-radius-lg) 0 0 var(--border-radius-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[dir="rtl"] & {
|
||||||
|
border-radius: 0 var(--border-radius-lg) var(--border-radius-lg) 0;
|
||||||
|
border-right: 1px solid var(--default-border-color);
|
||||||
|
border-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
color: var(--input-label-color);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drag-input {
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-family: inherit;
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--text-primary-color);
|
||||||
|
border: 0;
|
||||||
|
outline: none;
|
||||||
|
height: var(--default-button-size);
|
||||||
|
border: 1px solid var(--default-border-color);
|
||||||
|
border-left: 0;
|
||||||
|
letter-spacing: 0.4px;
|
||||||
|
|
||||||
|
:root[dir="ltr"] & {
|
||||||
|
border-radius: 0 var(--border-radius-lg) var(--border-radius-lg) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[dir="rtl"] & {
|
||||||
|
border-radius: var(--border-radius-lg) 0 0 var(--border-radius-lg);
|
||||||
|
border-left: 1px solid var(--default-border-color);
|
||||||
|
border-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
padding: 0.5rem;
|
||||||
|
padding-left: 0.25rem;
|
||||||
|
appearance: none;
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,208 @@
|
|||||||
|
import { useEffect, useMemo, useRef, useState } from "react";
|
||||||
|
import throttle from "lodash.throttle";
|
||||||
|
import { EVENT } from "../../constants";
|
||||||
|
import { KEYS } from "../../keys";
|
||||||
|
import type { ElementsMap, ExcalidrawElement } from "../../element/types";
|
||||||
|
import { deepCopyElement } from "../../element/newElement";
|
||||||
|
|
||||||
|
import "./DragInput.scss";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import { useApp } from "../App";
|
||||||
|
|
||||||
|
export type DragInputCallbackType = ({
|
||||||
|
accumulatedChange,
|
||||||
|
instantChange,
|
||||||
|
stateAtStart,
|
||||||
|
originalElementsMap,
|
||||||
|
shouldKeepAspectRatio,
|
||||||
|
shouldChangeByStepSize,
|
||||||
|
nextValue,
|
||||||
|
}: {
|
||||||
|
accumulatedChange: number;
|
||||||
|
instantChange: number;
|
||||||
|
stateAtStart: ExcalidrawElement[];
|
||||||
|
originalElementsMap: ElementsMap;
|
||||||
|
shouldKeepAspectRatio: boolean;
|
||||||
|
shouldChangeByStepSize: boolean;
|
||||||
|
nextValue?: number;
|
||||||
|
}) => void;
|
||||||
|
|
||||||
|
interface StatsDragInputProps {
|
||||||
|
label: string | React.ReactNode;
|
||||||
|
value: number;
|
||||||
|
elements: ExcalidrawElement[];
|
||||||
|
editable?: boolean;
|
||||||
|
shouldKeepAspectRatio?: boolean;
|
||||||
|
dragInputCallback: DragInputCallbackType;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StatsDragInput = ({
|
||||||
|
label,
|
||||||
|
dragInputCallback,
|
||||||
|
value,
|
||||||
|
elements,
|
||||||
|
editable = true,
|
||||||
|
shouldKeepAspectRatio,
|
||||||
|
}: StatsDragInputProps) => {
|
||||||
|
const app = useApp();
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const labelRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const cbThrottled = useMemo(() => {
|
||||||
|
return throttle(dragInputCallback, 16);
|
||||||
|
}, [dragInputCallback]);
|
||||||
|
|
||||||
|
const [inputValue, setInputValue] = useState(value.toString());
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setInputValue(value.toString());
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={clsx("drag-input-container", !editable && "disabled")}>
|
||||||
|
<div
|
||||||
|
className="drag-input-label"
|
||||||
|
ref={labelRef}
|
||||||
|
onPointerDown={(event) => {
|
||||||
|
if (inputRef.current && editable) {
|
||||||
|
let startValue = Number(inputRef.current.value);
|
||||||
|
if (isNaN(startValue)) {
|
||||||
|
startValue = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let lastPointer: {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
} | null = null;
|
||||||
|
|
||||||
|
let stateAtStart: ExcalidrawElement[] | null = null;
|
||||||
|
let originalElementsMap: Map<string, ExcalidrawElement> | null =
|
||||||
|
null;
|
||||||
|
|
||||||
|
let accumulatedChange: number | null = null;
|
||||||
|
|
||||||
|
document.body.classList.add("dragResize");
|
||||||
|
|
||||||
|
const onPointerMove = (event: PointerEvent) => {
|
||||||
|
if (!stateAtStart) {
|
||||||
|
stateAtStart = elements.map((element) =>
|
||||||
|
deepCopyElement(element),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!originalElementsMap) {
|
||||||
|
originalElementsMap = app.scene
|
||||||
|
.getNonDeletedElements()
|
||||||
|
.reduce((acc, element) => {
|
||||||
|
acc.set(element.id, deepCopyElement(element));
|
||||||
|
return acc;
|
||||||
|
}, new Map() as ElementsMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!accumulatedChange) {
|
||||||
|
accumulatedChange = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastPointer && stateAtStart && accumulatedChange !== null) {
|
||||||
|
const instantChange = event.clientX - lastPointer.x;
|
||||||
|
accumulatedChange += instantChange;
|
||||||
|
|
||||||
|
cbThrottled({
|
||||||
|
accumulatedChange,
|
||||||
|
instantChange,
|
||||||
|
stateAtStart,
|
||||||
|
originalElementsMap,
|
||||||
|
shouldKeepAspectRatio: shouldKeepAspectRatio!!,
|
||||||
|
shouldChangeByStepSize: event.shiftKey,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
lastPointer = {
|
||||||
|
x: event.clientX,
|
||||||
|
y: event.clientY,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener(EVENT.POINTER_MOVE, onPointerMove, false);
|
||||||
|
window.addEventListener(
|
||||||
|
EVENT.POINTER_UP,
|
||||||
|
() => {
|
||||||
|
window.removeEventListener(
|
||||||
|
EVENT.POINTER_MOVE,
|
||||||
|
onPointerMove,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
app.store.shouldCaptureIncrement();
|
||||||
|
|
||||||
|
lastPointer = null;
|
||||||
|
accumulatedChange = null;
|
||||||
|
stateAtStart = null;
|
||||||
|
originalElementsMap = null;
|
||||||
|
|
||||||
|
document.body.classList.remove("dragResize");
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onPointerEnter={() => {
|
||||||
|
if (labelRef.current) {
|
||||||
|
labelRef.current.style.cursor = "ew-resize";
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
className="drag-input"
|
||||||
|
autoComplete="off"
|
||||||
|
spellCheck="false"
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
if (editable) {
|
||||||
|
const eventTarget = event.target;
|
||||||
|
|
||||||
|
if (
|
||||||
|
eventTarget instanceof HTMLInputElement &&
|
||||||
|
event.key === KEYS.ENTER
|
||||||
|
) {
|
||||||
|
const v = Number(eventTarget.value);
|
||||||
|
if (isNaN(v)) {
|
||||||
|
setInputValue(value.toString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dragInputCallback({
|
||||||
|
accumulatedChange: 0,
|
||||||
|
instantChange: 0,
|
||||||
|
stateAtStart: elements,
|
||||||
|
originalElementsMap: app.scene.getNonDeletedElementsMap(),
|
||||||
|
shouldKeepAspectRatio: shouldKeepAspectRatio!!,
|
||||||
|
shouldChangeByStepSize: false,
|
||||||
|
nextValue: v,
|
||||||
|
});
|
||||||
|
app.store.shouldCaptureIncrement();
|
||||||
|
eventTarget.blur();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
ref={inputRef}
|
||||||
|
value={inputValue}
|
||||||
|
onChange={(event) => {
|
||||||
|
const eventTarget = event.target;
|
||||||
|
if (eventTarget instanceof HTMLInputElement) {
|
||||||
|
setInputValue(event.target.value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onBlur={() => {
|
||||||
|
if (!inputValue) {
|
||||||
|
setInputValue(value.toString());
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={!editable}
|
||||||
|
></input>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StatsDragInput;
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import type { ElementsMap, ExcalidrawTextElement } from "../../element/types";
|
||||||
|
import { refreshTextDimensions } from "../../element/newElement";
|
||||||
|
import StatsDragInput from "./DragInput";
|
||||||
|
import type { DragInputCallbackType } from "./DragInput";
|
||||||
|
import { mutateElement } from "../../element/mutateElement";
|
||||||
|
import { getStepSizedValue } from "./utils";
|
||||||
|
|
||||||
|
interface FontSizeProps {
|
||||||
|
element: ExcalidrawTextElement;
|
||||||
|
elementsMap: ElementsMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MIN_FONT_SIZE = 4;
|
||||||
|
const STEP_SIZE = 4;
|
||||||
|
|
||||||
|
const FontSize = ({ element, elementsMap }: FontSizeProps) => {
|
||||||
|
const handleFontSizeChange: DragInputCallbackType = ({
|
||||||
|
accumulatedChange,
|
||||||
|
stateAtStart,
|
||||||
|
shouldChangeByStepSize,
|
||||||
|
nextValue,
|
||||||
|
}) => {
|
||||||
|
const _stateAtStart = stateAtStart[0];
|
||||||
|
if (_stateAtStart) {
|
||||||
|
if (nextValue) {
|
||||||
|
const nextFontSize = Math.max(Math.round(nextValue), MIN_FONT_SIZE);
|
||||||
|
|
||||||
|
const newElement = {
|
||||||
|
...element,
|
||||||
|
fontSize: nextFontSize,
|
||||||
|
};
|
||||||
|
const updates = refreshTextDimensions(newElement, null, elementsMap);
|
||||||
|
mutateElement(element, {
|
||||||
|
...updates,
|
||||||
|
fontSize: nextFontSize,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_stateAtStart.type === "text") {
|
||||||
|
const originalFontSize = Math.round(_stateAtStart.fontSize);
|
||||||
|
const changeInFontSize = Math.round(accumulatedChange);
|
||||||
|
let nextFontSize = Math.max(
|
||||||
|
originalFontSize + changeInFontSize,
|
||||||
|
MIN_FONT_SIZE,
|
||||||
|
);
|
||||||
|
if (shouldChangeByStepSize) {
|
||||||
|
nextFontSize = getStepSizedValue(nextFontSize, STEP_SIZE);
|
||||||
|
}
|
||||||
|
const newElement = {
|
||||||
|
...element,
|
||||||
|
fontSize: nextFontSize,
|
||||||
|
};
|
||||||
|
const updates = refreshTextDimensions(newElement, null, elementsMap);
|
||||||
|
mutateElement(element, {
|
||||||
|
...updates,
|
||||||
|
fontSize: nextFontSize,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StatsDragInput
|
||||||
|
label="F"
|
||||||
|
value={Math.round(element.fontSize * 10) / 10}
|
||||||
|
elements={[element]}
|
||||||
|
dragInputCallback={handleFontSizeChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FontSize;
|
||||||
@@ -0,0 +1,210 @@
|
|||||||
|
import { getCommonBounds, isTextElement } from "../../element";
|
||||||
|
import { updateBoundElements } from "../../element/binding";
|
||||||
|
import { mutateElement } from "../../element/mutateElement";
|
||||||
|
import { rescalePointsInElement } from "../../element/resizeElements";
|
||||||
|
import {
|
||||||
|
getBoundTextElement,
|
||||||
|
handleBindTextResize,
|
||||||
|
} from "../../element/textElement";
|
||||||
|
import type { ElementsMap, ExcalidrawElement } from "../../element/types";
|
||||||
|
import DragInput from "./DragInput";
|
||||||
|
import type { DragInputCallbackType } from "./DragInput";
|
||||||
|
import { getStepSizedValue } from "./utils";
|
||||||
|
|
||||||
|
interface MultiDimensionProps {
|
||||||
|
property: "width" | "height";
|
||||||
|
elements: ExcalidrawElement[];
|
||||||
|
elementsMap: ElementsMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
const STEP_SIZE = 10;
|
||||||
|
|
||||||
|
const getResizedUpdates = (
|
||||||
|
anchorX: number,
|
||||||
|
anchorY: number,
|
||||||
|
scale: number,
|
||||||
|
stateAtStart: ExcalidrawElement,
|
||||||
|
) => {
|
||||||
|
const offsetX = stateAtStart.x - anchorX;
|
||||||
|
const offsetY = stateAtStart.y - anchorY;
|
||||||
|
const nextWidth = stateAtStart.width * scale;
|
||||||
|
const nextHeight = stateAtStart.height * scale;
|
||||||
|
const x = anchorX + offsetX * scale;
|
||||||
|
const y = anchorY + offsetY * scale;
|
||||||
|
|
||||||
|
return {
|
||||||
|
width: nextWidth,
|
||||||
|
height: nextHeight,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
...rescalePointsInElement(stateAtStart, nextWidth, nextHeight, false),
|
||||||
|
...(isTextElement(stateAtStart)
|
||||||
|
? { fontSize: stateAtStart.fontSize * scale }
|
||||||
|
: {}),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const resizeElement = (
|
||||||
|
anchorX: number,
|
||||||
|
anchorY: number,
|
||||||
|
property: MultiDimensionProps["property"],
|
||||||
|
scale: number,
|
||||||
|
latestElement: ExcalidrawElement,
|
||||||
|
origElement: ExcalidrawElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
|
originalElementsMap: ElementsMap,
|
||||||
|
shouldInformMutation: boolean,
|
||||||
|
) => {
|
||||||
|
const updates = getResizedUpdates(anchorX, anchorY, scale, origElement);
|
||||||
|
|
||||||
|
mutateElement(latestElement, updates, shouldInformMutation);
|
||||||
|
const boundTextElement = getBoundTextElement(
|
||||||
|
origElement,
|
||||||
|
originalElementsMap,
|
||||||
|
);
|
||||||
|
if (boundTextElement) {
|
||||||
|
const newFontSize = boundTextElement.fontSize * scale;
|
||||||
|
updateBoundElements(latestElement, elementsMap, {
|
||||||
|
newSize: { width: updates.width, height: updates.height },
|
||||||
|
});
|
||||||
|
const latestBoundTextElement = elementsMap.get(boundTextElement.id);
|
||||||
|
if (latestBoundTextElement && isTextElement(latestBoundTextElement)) {
|
||||||
|
mutateElement(
|
||||||
|
latestBoundTextElement,
|
||||||
|
{
|
||||||
|
fontSize: newFontSize,
|
||||||
|
},
|
||||||
|
shouldInformMutation,
|
||||||
|
);
|
||||||
|
handleBindTextResize(
|
||||||
|
latestElement,
|
||||||
|
elementsMap,
|
||||||
|
property === "width" ? "e" : "s",
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const MultiDimension = ({
|
||||||
|
property,
|
||||||
|
elements,
|
||||||
|
elementsMap,
|
||||||
|
}: MultiDimensionProps) => {
|
||||||
|
const handleDimensionChange: DragInputCallbackType = ({
|
||||||
|
accumulatedChange,
|
||||||
|
stateAtStart,
|
||||||
|
originalElementsMap,
|
||||||
|
shouldChangeByStepSize,
|
||||||
|
nextValue,
|
||||||
|
}) => {
|
||||||
|
const [x1, y1, x2, y2] = getCommonBounds(stateAtStart);
|
||||||
|
const initialWidth = x2 - x1;
|
||||||
|
const initialHeight = y2 - y1;
|
||||||
|
const keepAspectRatio = true;
|
||||||
|
const aspectRatio = initialWidth / initialHeight;
|
||||||
|
|
||||||
|
if (nextValue !== undefined) {
|
||||||
|
const nextHeight =
|
||||||
|
property === "height" ? nextValue : nextValue / aspectRatio;
|
||||||
|
|
||||||
|
const scale = nextHeight / initialHeight;
|
||||||
|
const anchorX = property === "width" ? x1 : x1 + width / 2;
|
||||||
|
const anchorY = property === "height" ? y1 : y1 + height / 2;
|
||||||
|
|
||||||
|
let i = 0;
|
||||||
|
while (i < stateAtStart.length) {
|
||||||
|
const latestElement = elements[i];
|
||||||
|
const origElement = stateAtStart[i];
|
||||||
|
|
||||||
|
// it should never happen that element and origElement are different
|
||||||
|
// but check just in case
|
||||||
|
if (latestElement.id === origElement.id) {
|
||||||
|
resizeElement(
|
||||||
|
anchorX,
|
||||||
|
anchorY,
|
||||||
|
property,
|
||||||
|
scale,
|
||||||
|
latestElement,
|
||||||
|
origElement,
|
||||||
|
elementsMap,
|
||||||
|
originalElementsMap,
|
||||||
|
i === stateAtStart.length - 1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const changeInWidth = property === "width" ? accumulatedChange : 0;
|
||||||
|
const changeInHeight = property === "height" ? accumulatedChange : 0;
|
||||||
|
|
||||||
|
let nextWidth = Math.max(0, initialWidth + changeInWidth);
|
||||||
|
if (property === "width") {
|
||||||
|
if (shouldChangeByStepSize) {
|
||||||
|
nextWidth = getStepSizedValue(nextWidth, STEP_SIZE);
|
||||||
|
} else {
|
||||||
|
nextWidth = Math.round(nextWidth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let nextHeight = Math.max(0, initialHeight + changeInHeight);
|
||||||
|
if (property === "height") {
|
||||||
|
if (shouldChangeByStepSize) {
|
||||||
|
nextHeight = getStepSizedValue(nextHeight, STEP_SIZE);
|
||||||
|
} else {
|
||||||
|
nextHeight = Math.round(nextHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keepAspectRatio) {
|
||||||
|
if (property === "width") {
|
||||||
|
nextHeight = Math.round((nextWidth / aspectRatio) * 100) / 100;
|
||||||
|
} else {
|
||||||
|
nextWidth = Math.round(nextHeight * aspectRatio * 100) / 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const scale = nextHeight / initialHeight;
|
||||||
|
const anchorX = property === "width" ? x1 : x1 + width / 2;
|
||||||
|
const anchorY = property === "height" ? y1 : y1 + height / 2;
|
||||||
|
|
||||||
|
let i = 0;
|
||||||
|
while (i < stateAtStart.length) {
|
||||||
|
const latestElement = elements[i];
|
||||||
|
const origElement = stateAtStart[i];
|
||||||
|
|
||||||
|
if (latestElement.id === origElement.id) {
|
||||||
|
resizeElement(
|
||||||
|
anchorX,
|
||||||
|
anchorY,
|
||||||
|
property,
|
||||||
|
scale,
|
||||||
|
latestElement,
|
||||||
|
origElement,
|
||||||
|
elementsMap,
|
||||||
|
originalElementsMap,
|
||||||
|
i === stateAtStart.length - 1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const [x1, y1, x2, y2] = getCommonBounds(elements);
|
||||||
|
const width = x2 - x1;
|
||||||
|
const height = y2 - y1;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DragInput
|
||||||
|
label={property === "width" ? "W" : "H"}
|
||||||
|
elements={elements}
|
||||||
|
dragInputCallback={handleDimensionChange}
|
||||||
|
value={Math.round((property === "width" ? width : height) * 100) / 100}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MultiDimension;
|
||||||
+40
-1
@@ -1,7 +1,8 @@
|
|||||||
@import "../css/variables.module.scss";
|
@import "../../css/variables.module.scss";
|
||||||
|
|
||||||
.excalidraw {
|
.excalidraw {
|
||||||
.Stats {
|
.Stats {
|
||||||
|
width: 204px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 64px;
|
top: 64px;
|
||||||
right: 12px;
|
right: 12px;
|
||||||
@@ -9,6 +10,38 @@
|
|||||||
z-index: 10;
|
z-index: 10;
|
||||||
pointer-events: var(--ui-pointerEvents);
|
pointer-events: var(--ui-pointerEvents);
|
||||||
|
|
||||||
|
.sectionContent {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.elementType {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.elementsCount {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 12px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statsItem {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
display: grid;
|
||||||
|
gap: 4px;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
margin: 0 24px 8px 0;
|
margin: 0 24px 8px 0;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
@@ -39,6 +72,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
width: 100%;
|
||||||
|
height: 1px;
|
||||||
|
background-color: var(--default-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
:root[dir="rtl"] & {
|
:root[dir="rtl"] & {
|
||||||
left: 12px;
|
left: 12px;
|
||||||
right: initial;
|
right: initial;
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
import React, { useEffect, useMemo, useState } from "react";
|
||||||
|
import { getCommonBounds } from "../../element/bounds";
|
||||||
|
import type { NonDeletedExcalidrawElement } from "../../element/types";
|
||||||
|
import { t } from "../../i18n";
|
||||||
|
import { getSelectedElements } from "../../scene";
|
||||||
|
import type Scene from "../../scene/Scene";
|
||||||
|
import type { AppState, ExcalidrawProps } from "../../types";
|
||||||
|
import { CloseIcon } from "../icons";
|
||||||
|
import { Island } from "../Island";
|
||||||
|
import { throttle } from "lodash";
|
||||||
|
import Dimension from "./Dimension";
|
||||||
|
import Angle from "./Angle";
|
||||||
|
|
||||||
|
import "./index.scss";
|
||||||
|
import FontSize from "./FontSize";
|
||||||
|
import MultiDimension from "./MultiDimension";
|
||||||
|
import { elementsAreInSameGroup } from "../../groups";
|
||||||
|
|
||||||
|
interface StatsProps {
|
||||||
|
appState: AppState;
|
||||||
|
scene: Scene;
|
||||||
|
setAppState: React.Component<any, AppState>["setState"];
|
||||||
|
onClose: () => void;
|
||||||
|
renderCustomStats: ExcalidrawProps["renderCustomStats"];
|
||||||
|
}
|
||||||
|
const STATS_TIMEOUT = 50;
|
||||||
|
|
||||||
|
export const Stats = (props: StatsProps) => {
|
||||||
|
const elements = props.scene.getNonDeletedElements();
|
||||||
|
const elementsMap = props.scene.getNonDeletedElementsMap();
|
||||||
|
const sceneNonce = props.scene.getSceneNonce();
|
||||||
|
// const selectedElements = getTargetElements(elements, props.appState);
|
||||||
|
const selectedElements = getSelectedElements(
|
||||||
|
props.scene.getNonDeletedElementsMap(),
|
||||||
|
props.appState,
|
||||||
|
{
|
||||||
|
includeBoundTextElement: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const singleElement =
|
||||||
|
selectedElements.length === 1 ? selectedElements[0] : null;
|
||||||
|
|
||||||
|
const multipleElements =
|
||||||
|
selectedElements.length > 1 ? selectedElements : null;
|
||||||
|
|
||||||
|
const [sceneDimension, setSceneDimension] = useState<{
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
}>({
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const throttledSetSceneDimension = useMemo(
|
||||||
|
() =>
|
||||||
|
throttle((elements: readonly NonDeletedExcalidrawElement[]) => {
|
||||||
|
const boundingBox = getCommonBounds(elements);
|
||||||
|
setSceneDimension({
|
||||||
|
width: Math.round(boundingBox[2]) - Math.round(boundingBox[0]),
|
||||||
|
height: Math.round(boundingBox[3]) - Math.round(boundingBox[1]),
|
||||||
|
});
|
||||||
|
}, STATS_TIMEOUT),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
throttledSetSceneDimension(elements);
|
||||||
|
}, [sceneNonce, elements, throttledSetSceneDimension]);
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() => () => throttledSetSceneDimension.cancel(),
|
||||||
|
[throttledSetSceneDimension],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="Stats">
|
||||||
|
<Island padding={3}>
|
||||||
|
<div className="section">
|
||||||
|
<div className="close" onClick={props.onClose}>
|
||||||
|
{CloseIcon}
|
||||||
|
</div>
|
||||||
|
<h3>{t("stats.generalStats")}</h3>
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th colSpan={2}>{t("stats.scene")}</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{t("stats.elements")}</td>
|
||||||
|
<td>{elements.length}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{t("stats.width")}</td>
|
||||||
|
<td>{sceneDimension.width}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{t("stats.height")}</td>
|
||||||
|
<td>{sceneDimension.height}</td>
|
||||||
|
</tr>
|
||||||
|
{props.renderCustomStats?.(elements, props.appState)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{selectedElements.length > 0 && (
|
||||||
|
<div
|
||||||
|
className="section"
|
||||||
|
style={{
|
||||||
|
marginTop: 12,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h3>{t("stats.elementStats")}</h3>
|
||||||
|
|
||||||
|
{singleElement && (
|
||||||
|
<div className="sectionContent">
|
||||||
|
<div className="elementType">
|
||||||
|
{t(`element.${singleElement.type}`)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="statsItem">
|
||||||
|
<Dimension
|
||||||
|
property="width"
|
||||||
|
element={singleElement}
|
||||||
|
elementsMap={elementsMap}
|
||||||
|
/>
|
||||||
|
<Dimension
|
||||||
|
property="height"
|
||||||
|
element={singleElement}
|
||||||
|
elementsMap={elementsMap}
|
||||||
|
/>
|
||||||
|
<Angle element={singleElement} elementsMap={elementsMap} />
|
||||||
|
{singleElement.type === "text" && (
|
||||||
|
<FontSize
|
||||||
|
element={singleElement}
|
||||||
|
elementsMap={elementsMap}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{singleElement.type === "text" && <div></div>}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{multipleElements && (
|
||||||
|
<div className="sectionContent">
|
||||||
|
{elementsAreInSameGroup(multipleElements) && (
|
||||||
|
<div className="elementType">{t("element.group")}</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="elementsCount">
|
||||||
|
<div>{t("stats.elements")}</div>
|
||||||
|
<div>{selectedElements.length}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="statsItem">
|
||||||
|
<MultiDimension
|
||||||
|
property="width"
|
||||||
|
elements={multipleElements}
|
||||||
|
elementsMap={elementsMap}
|
||||||
|
/>
|
||||||
|
<MultiDimension
|
||||||
|
property="height"
|
||||||
|
elements={multipleElements}
|
||||||
|
elementsMap={elementsMap}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Island>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { isFrameLikeElement, isTextElement } from "../../element/typeChecks";
|
||||||
|
import type { ExcalidrawElement } from "../../element/types";
|
||||||
|
|
||||||
|
export const isPropertyEditable = (
|
||||||
|
element: ExcalidrawElement,
|
||||||
|
property: keyof ExcalidrawElement,
|
||||||
|
) => {
|
||||||
|
if (property === "height" && isTextElement(element)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (property === "width" && isTextElement(element)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (property === "angle" && isFrameLikeElement(element)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getStepSizedValue = (value: number, stepSize: number) => {
|
||||||
|
const v = value + stepSize / 2;
|
||||||
|
return v - (v % stepSize);
|
||||||
|
};
|
||||||
@@ -22,6 +22,12 @@
|
|||||||
--sat: env(safe-area-inset-top);
|
--sat: env(safe-area-inset-top);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.dragResize,
|
||||||
|
body.dragResize a:hover,
|
||||||
|
body.dragResize * {
|
||||||
|
cursor: ew-resize;
|
||||||
|
}
|
||||||
|
|
||||||
.excalidraw {
|
.excalidraw {
|
||||||
--ui-font: Assistant, system-ui, BlinkMacSystemFont, -apple-system, Segoe UI,
|
--ui-font: Assistant, system-ui, BlinkMacSystemFont, -apple-system, Segoe UI,
|
||||||
Roboto, Helvetica, Arial, sans-serif;
|
Roboto, Helvetica, Arial, sans-serif;
|
||||||
|
|||||||
@@ -215,6 +215,7 @@ const getTextElementPositionOffsets = (
|
|||||||
export const newTextElement = (
|
export const newTextElement = (
|
||||||
opts: {
|
opts: {
|
||||||
text: string;
|
text: string;
|
||||||
|
originalText?: string;
|
||||||
fontSize?: number;
|
fontSize?: number;
|
||||||
fontFamily?: FontFamilyValues;
|
fontFamily?: FontFamilyValues;
|
||||||
textAlign?: TextAlign;
|
textAlign?: TextAlign;
|
||||||
@@ -222,6 +223,7 @@ export const newTextElement = (
|
|||||||
containerId?: ExcalidrawTextContainer["id"] | null;
|
containerId?: ExcalidrawTextContainer["id"] | null;
|
||||||
lineHeight?: ExcalidrawTextElement["lineHeight"];
|
lineHeight?: ExcalidrawTextElement["lineHeight"];
|
||||||
strokeWidth?: ExcalidrawTextElement["strokeWidth"];
|
strokeWidth?: ExcalidrawTextElement["strokeWidth"];
|
||||||
|
autoResize?: ExcalidrawTextElement["autoResize"];
|
||||||
} & ElementConstructorOpts,
|
} & ElementConstructorOpts,
|
||||||
): NonDeleted<ExcalidrawTextElement> => {
|
): NonDeleted<ExcalidrawTextElement> => {
|
||||||
const fontFamily = opts.fontFamily || DEFAULT_FONT_FAMILY;
|
const fontFamily = opts.fontFamily || DEFAULT_FONT_FAMILY;
|
||||||
@@ -252,8 +254,8 @@ export const newTextElement = (
|
|||||||
width: metrics.width,
|
width: metrics.width,
|
||||||
height: metrics.height,
|
height: metrics.height,
|
||||||
containerId: opts.containerId || null,
|
containerId: opts.containerId || null,
|
||||||
originalText: text,
|
originalText: opts.originalText ?? text,
|
||||||
autoResize: true,
|
autoResize: opts.autoResize ?? true,
|
||||||
lineHeight,
|
lineHeight,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ const rotateSingleElement = (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const rescalePointsInElement = (
|
export const rescalePointsInElement = (
|
||||||
element: NonDeletedExcalidrawElement,
|
element: NonDeletedExcalidrawElement,
|
||||||
width: number,
|
width: number,
|
||||||
height: number,
|
height: number,
|
||||||
@@ -199,7 +199,7 @@ const rescalePointsInElement = (
|
|||||||
}
|
}
|
||||||
: {};
|
: {};
|
||||||
|
|
||||||
const measureFontSizeFromWidth = (
|
export const measureFontSizeFromWidth = (
|
||||||
element: NonDeleted<ExcalidrawTextElement>,
|
element: NonDeleted<ExcalidrawTextElement>,
|
||||||
elementsMap: ElementsMap,
|
elementsMap: ElementsMap,
|
||||||
nextWidth: number,
|
nextWidth: number,
|
||||||
|
|||||||
@@ -1064,7 +1064,7 @@ describe("textWysiwyg", () => {
|
|||||||
expect([h.elements[1].x, h.elements[1].y]).toMatchInlineSnapshot(`
|
expect([h.elements[1].x, h.elements[1].y]).toMatchInlineSnapshot(`
|
||||||
[
|
[
|
||||||
85,
|
85,
|
||||||
4.999999999999986,
|
"5.00000",
|
||||||
]
|
]
|
||||||
`);
|
`);
|
||||||
|
|
||||||
@@ -1109,8 +1109,8 @@ describe("textWysiwyg", () => {
|
|||||||
UI.resize(rectangle, "ne", [rectangle.x + 100, rectangle.y - 100]);
|
UI.resize(rectangle, "ne", [rectangle.x + 100, rectangle.y - 100]);
|
||||||
expect([h.elements[1].x, h.elements[1].y]).toMatchInlineSnapshot(`
|
expect([h.elements[1].x, h.elements[1].y]).toMatchInlineSnapshot(`
|
||||||
[
|
[
|
||||||
374.99999999999994,
|
"375.00000",
|
||||||
-535.0000000000001,
|
"-535.00000",
|
||||||
]
|
]
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -133,27 +133,11 @@ const getMovedIndicesGroups = (
|
|||||||
let i = 0;
|
let i = 0;
|
||||||
|
|
||||||
while (i < elements.length) {
|
while (i < elements.length) {
|
||||||
if (
|
if (movedElements.has(elements[i].id)) {
|
||||||
movedElements.has(elements[i].id) &&
|
|
||||||
!isValidFractionalIndex(
|
|
||||||
elements[i]?.index,
|
|
||||||
elements[i - 1]?.index,
|
|
||||||
elements[i + 1]?.index,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
const indicesGroup = [i - 1, i]; // push the lower bound index as the first item
|
const indicesGroup = [i - 1, i]; // push the lower bound index as the first item
|
||||||
|
|
||||||
while (++i < elements.length) {
|
while (++i < elements.length) {
|
||||||
if (
|
if (!movedElements.has(elements[i].id)) {
|
||||||
!(
|
|
||||||
movedElements.has(elements[i].id) &&
|
|
||||||
!isValidFractionalIndex(
|
|
||||||
elements[i]?.index,
|
|
||||||
elements[i - 1]?.index,
|
|
||||||
elements[i + 1]?.index,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -270,6 +270,22 @@
|
|||||||
"mermaidToExcalidraw": "Mermaid to Excalidraw",
|
"mermaidToExcalidraw": "Mermaid to Excalidraw",
|
||||||
"magicSettings": "AI settings"
|
"magicSettings": "AI settings"
|
||||||
},
|
},
|
||||||
|
"element": {
|
||||||
|
"rectangle": "Rectangle",
|
||||||
|
"diamond": "Diamond",
|
||||||
|
"ellipse": "Ellipse",
|
||||||
|
"arrow": "Arrow",
|
||||||
|
"line": "Line",
|
||||||
|
"freedraw": "Freedraw",
|
||||||
|
"text": "Text",
|
||||||
|
"image": "Image",
|
||||||
|
"group": "Group",
|
||||||
|
"frame": "Frame",
|
||||||
|
"magicframe": "Wireframe to code",
|
||||||
|
"embeddable": "Web Embed",
|
||||||
|
"selection": "Selection",
|
||||||
|
"iframe": "IFrame"
|
||||||
|
},
|
||||||
"headings": {
|
"headings": {
|
||||||
"canvasActions": "Canvas actions",
|
"canvasActions": "Canvas actions",
|
||||||
"selectedShapeActions": "Selected shape actions",
|
"selectedShapeActions": "Selected shape actions",
|
||||||
@@ -443,7 +459,9 @@
|
|||||||
"scene": "Scene",
|
"scene": "Scene",
|
||||||
"selected": "Selected",
|
"selected": "Selected",
|
||||||
"storage": "Storage",
|
"storage": "Storage",
|
||||||
"title": "Stats for nerds",
|
"title": "Stats",
|
||||||
|
"generalStats": "General stats",
|
||||||
|
"elementStats": "Element stats",
|
||||||
"total": "Total",
|
"total": "Total",
|
||||||
"version": "Version",
|
"version": "Version",
|
||||||
"versionCopy": "Click to copy",
|
"versionCopy": "Click to copy",
|
||||||
|
|||||||
@@ -475,6 +475,14 @@ export const isRightAngle = (angle: number) => {
|
|||||||
return Math.round((angle / Math.PI) * 10000) % 5000 === 0;
|
return Math.round((angle / Math.PI) * 10000) % 5000 === 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const radianToDegree = (r: number) => {
|
||||||
|
return (r * 180) / Math.PI;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const degreeToRadian = (d: number) => {
|
||||||
|
return (d / 180) * Math.PI;
|
||||||
|
};
|
||||||
|
|
||||||
// Given two ranges, return if the two ranges overlap with each other
|
// Given two ranges, return if the two ranges overlap with each other
|
||||||
// e.g. [1, 3] overlaps with [2, 4] while [1, 3] does not overlap with [4, 5]
|
// e.g. [1, 3] overlaps with [2, 4] while [1, 3] does not overlap with [4, 5]
|
||||||
export const rangesOverlap = (
|
export const rangesOverlap = (
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { deepCopyElement } from "./element/newElement";
|
|||||||
import type { OrderedExcalidrawElement } from "./element/types";
|
import type { OrderedExcalidrawElement } from "./element/types";
|
||||||
import { Emitter } from "./emitter";
|
import { Emitter } from "./emitter";
|
||||||
import type { AppState, ObservedAppState } from "./types";
|
import type { AppState, ObservedAppState } from "./types";
|
||||||
|
import type { ValueOf } from "./utility-types";
|
||||||
import { isShallowEqual } from "./utils";
|
import { isShallowEqual } from "./utils";
|
||||||
|
|
||||||
// hidden non-enumerable property for runtime checks
|
// hidden non-enumerable property for runtime checks
|
||||||
@@ -35,16 +36,41 @@ const isObservedAppState = (
|
|||||||
): appState is ObservedAppState =>
|
): appState is ObservedAppState =>
|
||||||
!!Reflect.get(appState, hiddenObservedAppStateProp);
|
!!Reflect.get(appState, hiddenObservedAppStateProp);
|
||||||
|
|
||||||
export type StoreActionType = "capture" | "update" | "none";
|
export const StoreAction = {
|
||||||
|
/**
|
||||||
export const StoreAction: {
|
* Immediately undoable.
|
||||||
[K in Uppercase<StoreActionType>]: StoreActionType;
|
*
|
||||||
} = {
|
* Use for updates which should be captured.
|
||||||
|
* Should be used for most of the local updates.
|
||||||
|
*
|
||||||
|
* These updates will _immediately_ make it to the local undo / redo stacks.
|
||||||
|
*/
|
||||||
CAPTURE: "capture",
|
CAPTURE: "capture",
|
||||||
|
/**
|
||||||
|
* Never undoable.
|
||||||
|
*
|
||||||
|
* Use for updates which should never be recorded, such as remote updates
|
||||||
|
* or scene initialization.
|
||||||
|
*
|
||||||
|
* These updates will _never_ make it to the local undo / redo stacks.
|
||||||
|
*/
|
||||||
UPDATE: "update",
|
UPDATE: "update",
|
||||||
|
/**
|
||||||
|
* Eventually undoable.
|
||||||
|
*
|
||||||
|
* Use for updates which should not be captured immediately - likely
|
||||||
|
* exceptions which are part of some async multi-step process. Otherwise, all
|
||||||
|
* such updates would end up being captured with the next
|
||||||
|
* `StoreAction.CAPTURE` - triggered either by the next `updateScene`
|
||||||
|
* or internally by the editor.
|
||||||
|
*
|
||||||
|
* These updates will _eventually_ make it to the local undo / redo stacks.
|
||||||
|
*/
|
||||||
NONE: "none",
|
NONE: "none",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
export type StoreActionType = ValueOf<typeof StoreAction>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represent an increment to the Store.
|
* Represent an increment to the Store.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ exports[`contextMenu element > right-clicking on a group should select whole gro
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.25}
|
strokeWidth={"1.25000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -130,7 +130,7 @@ exports[`contextMenu element > right-clicking on a group should select whole gro
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.25}
|
strokeWidth={"1.25000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -182,7 +182,7 @@ exports[`contextMenu element > right-clicking on a group should select whole gro
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.25}
|
strokeWidth={"1.25000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -248,7 +248,7 @@ exports[`contextMenu element > right-clicking on a group should select whole gro
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.25}
|
strokeWidth={"1.25000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -288,7 +288,7 @@ exports[`contextMenu element > right-clicking on a group should select whole gro
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.25}
|
strokeWidth={"1.25000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -406,7 +406,7 @@ exports[`contextMenu element > right-clicking on a group should select whole gro
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.5}
|
strokeWidth={"1.50000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -453,7 +453,7 @@ exports[`contextMenu element > right-clicking on a group should select whole gro
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.5}
|
strokeWidth={"1.50000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -505,7 +505,7 @@ exports[`contextMenu element > right-clicking on a group should select whole gro
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.5}
|
strokeWidth={"1.50000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -554,7 +554,7 @@ exports[`contextMenu element > right-clicking on a group should select whole gro
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.5}
|
strokeWidth={"1.50000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -603,7 +603,7 @@ exports[`contextMenu element > right-clicking on a group should select whole gro
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.25}
|
strokeWidth={"1.25000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -643,7 +643,7 @@ exports[`contextMenu element > right-clicking on a group should select whole gro
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.25}
|
strokeWidth={"1.25000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -4448,7 +4448,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.25}
|
strokeWidth={"1.25000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -4551,7 +4551,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.25}
|
strokeWidth={"1.25000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -4603,7 +4603,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.25}
|
strokeWidth={"1.25000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -4669,7 +4669,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.25}
|
strokeWidth={"1.25000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -4709,7 +4709,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.25}
|
strokeWidth={"1.25000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -4827,7 +4827,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.5}
|
strokeWidth={"1.50000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -4874,7 +4874,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.5}
|
strokeWidth={"1.50000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -4926,7 +4926,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.5}
|
strokeWidth={"1.50000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -4975,7 +4975,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.5}
|
strokeWidth={"1.50000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -5024,7 +5024,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.25}
|
strokeWidth={"1.25000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -5064,7 +5064,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.25}
|
strokeWidth={"1.25000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -5567,7 +5567,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.25}
|
strokeWidth={"1.25000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -5670,7 +5670,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.25}
|
strokeWidth={"1.25000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -5722,7 +5722,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.25}
|
strokeWidth={"1.25000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -5788,7 +5788,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.25}
|
strokeWidth={"1.25000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -5828,7 +5828,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.25}
|
strokeWidth={"1.25000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -5946,7 +5946,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.5}
|
strokeWidth={"1.50000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -5993,7 +5993,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.5}
|
strokeWidth={"1.50000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -6045,7 +6045,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.5}
|
strokeWidth={"1.50000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -6094,7 +6094,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.5}
|
strokeWidth={"1.50000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -6143,7 +6143,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.25}
|
strokeWidth={"1.25000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -6183,7 +6183,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.25}
|
strokeWidth={"1.25000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -6741,7 +6741,7 @@ exports[`contextMenu element > shows context menu for canvas > [end of test] app
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.25}
|
strokeWidth={"1.25000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -6793,7 +6793,7 @@ exports[`contextMenu element > shows context menu for canvas > [end of test] app
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.25}
|
strokeWidth={"1.25000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -7005,7 +7005,7 @@ exports[`contextMenu element > shows context menu for canvas > [end of test] app
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.5}
|
strokeWidth={"1.50000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -7061,7 +7061,7 @@ exports[`contextMenu element > shows context menu for canvas > [end of test] app
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.25}
|
strokeWidth={"1.25000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -7105,7 +7105,7 @@ exports[`contextMenu element > shows context menu for canvas > [end of test] app
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.25}
|
strokeWidth={"1.25000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -7158,7 +7158,7 @@ exports[`contextMenu element > shows context menu for canvas > [end of test] app
|
|||||||
<g
|
<g
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
strokeWidth={1.5}
|
strokeWidth={"1.50000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -7200,7 +7200,7 @@ exports[`contextMenu element > shows context menu for canvas > [end of test] app
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.25}
|
strokeWidth={"1.25000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -7393,7 +7393,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.25}
|
strokeWidth={"1.25000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -7496,7 +7496,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.25}
|
strokeWidth={"1.25000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -7548,7 +7548,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.25}
|
strokeWidth={"1.25000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -7614,7 +7614,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.25}
|
strokeWidth={"1.25000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -7654,7 +7654,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.25}
|
strokeWidth={"1.25000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -7772,7 +7772,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.5}
|
strokeWidth={"1.50000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -7819,7 +7819,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.5}
|
strokeWidth={"1.50000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -7871,7 +7871,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.5}
|
strokeWidth={"1.50000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -7920,7 +7920,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.5}
|
strokeWidth={"1.50000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -7969,7 +7969,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.25}
|
strokeWidth={"1.25000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -8009,7 +8009,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.25}
|
strokeWidth={"1.25000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -8279,7 +8279,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.25}
|
strokeWidth={"1.25000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -8382,7 +8382,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.25}
|
strokeWidth={"1.25000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -8434,7 +8434,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.25}
|
strokeWidth={"1.25000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -8500,7 +8500,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.25}
|
strokeWidth={"1.25000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -8540,7 +8540,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.25}
|
strokeWidth={"1.25000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -8658,7 +8658,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.5}
|
strokeWidth={"1.50000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -8705,7 +8705,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.5}
|
strokeWidth={"1.50000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -8757,7 +8757,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.5}
|
strokeWidth={"1.50000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -8806,7 +8806,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.5}
|
strokeWidth={"1.50000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -8855,7 +8855,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.25}
|
strokeWidth={"1.25000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
@@ -8895,7 +8895,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g
|
<g
|
||||||
strokeWidth={1.25}
|
strokeWidth={"1.25000"}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M0 0h24v24H0z"
|
d="M0 0h24v24H0z"
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
"height": 99.19725525211979,
|
"height": "99.19726",
|
||||||
"id": "id163",
|
"id": "id163",
|
||||||
"index": "a2",
|
"index": "a2",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
@@ -195,8 +195,8 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
0,
|
0,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
98.40367721010284,
|
"98.40368",
|
||||||
99.19725525211979,
|
"99.19726",
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
@@ -211,7 +211,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
"type": "arrow",
|
"type": "arrow",
|
||||||
"updated": 1,
|
"updated": 1,
|
||||||
"version": 54,
|
"version": 54,
|
||||||
"width": 98.40367721010284,
|
"width": "98.40368",
|
||||||
"x": 1,
|
"x": 1,
|
||||||
"y": 0,
|
"y": 0,
|
||||||
}
|
}
|
||||||
@@ -278,10 +278,10 @@ History {
|
|||||||
"deleted": {
|
"deleted": {
|
||||||
"endBinding": {
|
"endBinding": {
|
||||||
"elementId": "id162",
|
"elementId": "id162",
|
||||||
"focus": 0.009900990099009901,
|
"focus": "0.00990",
|
||||||
"gap": 1,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"height": 0.9800031696987099,
|
"height": "0.98000",
|
||||||
"points": [
|
"points": [
|
||||||
[
|
[
|
||||||
0,
|
0,
|
||||||
@@ -289,22 +289,22 @@ History {
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
98,
|
98,
|
||||||
-0.9800031696987099,
|
"-0.98000",
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
"elementId": "id161",
|
"elementId": "id161",
|
||||||
"focus": 0.0297029702970297,
|
"focus": "0.02970",
|
||||||
"gap": 1,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"endBinding": {
|
"endBinding": {
|
||||||
"elementId": "id162",
|
"elementId": "id162",
|
||||||
"focus": -0.02,
|
"focus": "-0.02000",
|
||||||
"gap": 1,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"height": 0.0002487679019458344,
|
"height": "0.00025",
|
||||||
"points": [
|
"points": [
|
||||||
[
|
[
|
||||||
0,
|
0,
|
||||||
@@ -312,12 +312,12 @@ History {
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
98,
|
98,
|
||||||
0.0002487679019458344,
|
"0.00025",
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
"elementId": "id161",
|
"elementId": "id161",
|
||||||
"focus": 0.02,
|
"focus": "0.02000",
|
||||||
"gap": 1,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -369,15 +369,15 @@ History {
|
|||||||
"focus": 0,
|
"focus": 0,
|
||||||
"gap": 1,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"height": 99.19725525211979,
|
"height": "99.19726",
|
||||||
"points": [
|
"points": [
|
||||||
[
|
[
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
98.40367721010284,
|
"98.40368",
|
||||||
99.19725525211979,
|
"99.19726",
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
"startBinding": null,
|
"startBinding": null,
|
||||||
@@ -386,10 +386,10 @@ History {
|
|||||||
"inserted": {
|
"inserted": {
|
||||||
"endBinding": {
|
"endBinding": {
|
||||||
"elementId": "id162",
|
"elementId": "id162",
|
||||||
"focus": 0.009900990099009901,
|
"focus": "0.00990",
|
||||||
"gap": 1,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"height": 0.9802432787444684,
|
"height": "0.98024",
|
||||||
"points": [
|
"points": [
|
||||||
[
|
[
|
||||||
0,
|
0,
|
||||||
@@ -397,15 +397,15 @@ History {
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
98,
|
98,
|
||||||
-0.9802432787444684,
|
"-0.98024",
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
"elementId": "id161",
|
"elementId": "id161",
|
||||||
"focus": 0.0297029702970297,
|
"focus": "0.02970",
|
||||||
"gap": 1,
|
"gap": 1,
|
||||||
},
|
},
|
||||||
"y": 0.9903686540602428,
|
"y": "0.99037",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"id166" => Delta {
|
"id166" => Delta {
|
||||||
@@ -1188,7 +1188,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
"height": 0.03596020595764898,
|
"height": "0.03596",
|
||||||
"id": "id169",
|
"id": "id169",
|
||||||
"index": "Zz",
|
"index": "Zz",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
@@ -1203,7 +1203,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
98,
|
98,
|
||||||
-0.03596020595764898,
|
"-0.03596",
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
@@ -1224,7 +1224,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
"version": 15,
|
"version": 15,
|
||||||
"width": 98,
|
"width": 98,
|
||||||
"x": 1,
|
"x": 1,
|
||||||
"y": 0.05467419069071122,
|
"y": "0.05467",
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -1531,7 +1531,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
"height": 0.03596020595764898,
|
"height": "0.03596",
|
||||||
"id": "id172",
|
"id": "id172",
|
||||||
"index": "a0",
|
"index": "a0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
@@ -1546,7 +1546,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
98,
|
98,
|
||||||
-0.03596020595764898,
|
"-0.03596",
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
@@ -1567,7 +1567,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
"version": 15,
|
"version": 15,
|
||||||
"width": 98,
|
"width": 98,
|
||||||
"x": 1,
|
"x": 1,
|
||||||
"y": 0.05467419069071122,
|
"y": "0.05467",
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -1680,7 +1680,7 @@ History {
|
|||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
"height": 2.6199110083015196,
|
"height": "2.61991",
|
||||||
"index": "a0",
|
"index": "a0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"lastCommittedPoint": null,
|
"lastCommittedPoint": null,
|
||||||
@@ -1694,7 +1694,7 @@ History {
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
98,
|
98,
|
||||||
-2.6199110083015196,
|
"-2.61991",
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
@@ -1713,7 +1713,7 @@ History {
|
|||||||
"type": "arrow",
|
"type": "arrow",
|
||||||
"width": 98,
|
"width": 98,
|
||||||
"x": 1,
|
"x": 1,
|
||||||
"y": 3.98333408405027,
|
"y": "3.98333",
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"isDeleted": true,
|
"isDeleted": true,
|
||||||
@@ -2206,7 +2206,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
"height": 373.7994222717614,
|
"height": "373.79942",
|
||||||
"id": "id177",
|
"id": "id177",
|
||||||
"index": "a2",
|
"index": "a2",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
@@ -2221,7 +2221,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
498,
|
498,
|
||||||
-373.7994222717614,
|
"-373.79942",
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
@@ -2242,7 +2242,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
"version": 12,
|
"version": 12,
|
||||||
"width": 498,
|
"width": 498,
|
||||||
"x": 1,
|
"x": 1,
|
||||||
"y": -37.91991400161248,
|
"y": "-37.91991",
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -2598,7 +2598,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"id": "id143",
|
"id": "id143",
|
||||||
"index": "a1",
|
"index": "a1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"lineHeight": 1.25,
|
"lineHeight": "1.25000",
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false,
|
"locked": false,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
@@ -2639,7 +2639,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"id": "id144",
|
"id": "id144",
|
||||||
"index": "a2",
|
"index": "a2",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"lineHeight": 1.25,
|
"lineHeight": "1.25000",
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false,
|
"locked": false,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
@@ -2889,7 +2889,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"id": "id146",
|
"id": "id146",
|
||||||
"index": "a0",
|
"index": "a0",
|
||||||
"isDeleted": true,
|
"isDeleted": true,
|
||||||
"lineHeight": 1.25,
|
"lineHeight": "1.25000",
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false,
|
"locked": false,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
@@ -2930,7 +2930,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"id": "id147",
|
"id": "id147",
|
||||||
"index": "a1",
|
"index": "a1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"lineHeight": 1.25,
|
"lineHeight": "1.25000",
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false,
|
"locked": false,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
@@ -3165,7 +3165,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"id": "id134",
|
"id": "id134",
|
||||||
"index": "a0V",
|
"index": "a0V",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"lineHeight": 1.25,
|
"lineHeight": "1.25000",
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false,
|
"locked": false,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
@@ -3206,7 +3206,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"id": "id133",
|
"id": "id133",
|
||||||
"index": "a1",
|
"index": "a1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"lineHeight": 1.25,
|
"lineHeight": "1.25000",
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false,
|
"locked": false,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
@@ -3483,7 +3483,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"id": "id136",
|
"id": "id136",
|
||||||
"index": "a1",
|
"index": "a1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"lineHeight": 1.25,
|
"lineHeight": "1.25000",
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false,
|
"locked": false,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
@@ -3724,7 +3724,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"id": "id131",
|
"id": "id131",
|
||||||
"index": "a1",
|
"index": "a1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"lineHeight": 1.25,
|
"lineHeight": "1.25000",
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false,
|
"locked": false,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
@@ -3956,7 +3956,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"id": "id139",
|
"id": "id139",
|
||||||
"index": "a1",
|
"index": "a1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"lineHeight": 1.25,
|
"lineHeight": "1.25000",
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false,
|
"locked": false,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
@@ -4207,7 +4207,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"id": "id141",
|
"id": "id141",
|
||||||
"index": "a0",
|
"index": "a0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"lineHeight": 1.25,
|
"lineHeight": "1.25000",
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false,
|
"locked": false,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
@@ -4267,7 +4267,7 @@ History {
|
|||||||
"height": 25,
|
"height": 25,
|
||||||
"index": "a0",
|
"index": "a0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"lineHeight": 1.25,
|
"lineHeight": "1.25000",
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false,
|
"locked": false,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
@@ -4472,7 +4472,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"id": "id155",
|
"id": "id155",
|
||||||
"index": "a0",
|
"index": "a0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"lineHeight": 1.25,
|
"lineHeight": "1.25000",
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false,
|
"locked": false,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
@@ -4695,7 +4695,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"id": "id153",
|
"id": "id153",
|
||||||
"index": "a1",
|
"index": "a1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"lineHeight": 1.25,
|
"lineHeight": "1.25000",
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false,
|
"locked": false,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
@@ -4913,7 +4913,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"id": "id149",
|
"id": "id149",
|
||||||
"index": "a1",
|
"index": "a1",
|
||||||
"isDeleted": true,
|
"isDeleted": true,
|
||||||
"lineHeight": 1.25,
|
"lineHeight": "1.25000",
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false,
|
"locked": false,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
@@ -5139,7 +5139,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"id": "id151",
|
"id": "id151",
|
||||||
"index": "a0",
|
"index": "a0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"lineHeight": 1.25,
|
"lineHeight": "1.25000",
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false,
|
"locked": false,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
@@ -13995,7 +13995,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
"id": "id51",
|
"id": "id51",
|
||||||
"index": "a1",
|
"index": "a1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"lineHeight": 1.25,
|
"lineHeight": "1.25000",
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false,
|
"locked": false,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
@@ -14015,7 +14015,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
"verticalAlign": "middle",
|
"verticalAlign": "middle",
|
||||||
"width": 30,
|
"width": 30,
|
||||||
"x": -65,
|
"x": -65,
|
||||||
"y": -12.5,
|
"y": "-12.50000",
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -14254,7 +14254,7 @@ History {
|
|||||||
"height": 100,
|
"height": 100,
|
||||||
"index": "a1",
|
"index": "a1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"lineHeight": 1.25,
|
"lineHeight": "1.25000",
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false,
|
"locked": false,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
@@ -14389,7 +14389,7 @@ History {
|
|||||||
"verticalAlign": "middle",
|
"verticalAlign": "middle",
|
||||||
"width": 30,
|
"width": 30,
|
||||||
"x": -65,
|
"x": -65,
|
||||||
"y": -12.5,
|
"y": "-12.50000",
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"containerId": null,
|
"containerId": null,
|
||||||
@@ -14677,7 +14677,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
"id": "id45",
|
"id": "id45",
|
||||||
"index": "a1",
|
"index": "a1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"lineHeight": 1.25,
|
"lineHeight": "1.25000",
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false,
|
"locked": false,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
@@ -14697,7 +14697,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
"verticalAlign": "middle",
|
"verticalAlign": "middle",
|
||||||
"width": 30,
|
"width": 30,
|
||||||
"x": -65,
|
"x": -65,
|
||||||
"y": -12.5,
|
"y": "-12.50000",
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -14860,7 +14860,7 @@ History {
|
|||||||
"height": 100,
|
"height": 100,
|
||||||
"index": "a1",
|
"index": "a1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"lineHeight": 1.25,
|
"lineHeight": "1.25000",
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false,
|
"locked": false,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
@@ -14995,7 +14995,7 @@ History {
|
|||||||
"verticalAlign": "middle",
|
"verticalAlign": "middle",
|
||||||
"width": 30,
|
"width": 30,
|
||||||
"x": -65,
|
"x": -65,
|
||||||
"y": -12.5,
|
"y": "-12.50000",
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"containerId": null,
|
"containerId": null,
|
||||||
@@ -15283,7 +15283,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
"id": "id57",
|
"id": "id57",
|
||||||
"index": "a1",
|
"index": "a1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"lineHeight": 1.25,
|
"lineHeight": "1.25000",
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false,
|
"locked": false,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
@@ -15303,7 +15303,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
"verticalAlign": "middle",
|
"verticalAlign": "middle",
|
||||||
"width": 30,
|
"width": 30,
|
||||||
"x": -65,
|
"x": -65,
|
||||||
"y": -12.5,
|
"y": "-12.50000",
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -15466,7 +15466,7 @@ History {
|
|||||||
"height": 100,
|
"height": 100,
|
||||||
"index": "a1",
|
"index": "a1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"lineHeight": 1.25,
|
"lineHeight": "1.25000",
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false,
|
"locked": false,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
@@ -15601,7 +15601,7 @@ History {
|
|||||||
"verticalAlign": "middle",
|
"verticalAlign": "middle",
|
||||||
"width": 30,
|
"width": 30,
|
||||||
"x": -65,
|
"x": -65,
|
||||||
"y": -12.5,
|
"y": "-12.50000",
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"containerId": null,
|
"containerId": null,
|
||||||
@@ -15887,7 +15887,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
"id": "id63",
|
"id": "id63",
|
||||||
"index": "a1",
|
"index": "a1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"lineHeight": 1.25,
|
"lineHeight": "1.25000",
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false,
|
"locked": false,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
@@ -15907,7 +15907,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
"verticalAlign": "middle",
|
"verticalAlign": "middle",
|
||||||
"width": 30,
|
"width": 30,
|
||||||
"x": -65,
|
"x": -65,
|
||||||
"y": -12.5,
|
"y": "-12.50000",
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -16140,7 +16140,7 @@ History {
|
|||||||
"height": 100,
|
"height": 100,
|
||||||
"index": "a1",
|
"index": "a1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"lineHeight": 1.25,
|
"lineHeight": "1.25000",
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false,
|
"locked": false,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
@@ -16275,7 +16275,7 @@ History {
|
|||||||
"verticalAlign": "middle",
|
"verticalAlign": "middle",
|
||||||
"width": 30,
|
"width": 30,
|
||||||
"x": -65,
|
"x": -65,
|
||||||
"y": -12.5,
|
"y": "-12.50000",
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"containerId": null,
|
"containerId": null,
|
||||||
@@ -16587,7 +16587,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
"id": "id70",
|
"id": "id70",
|
||||||
"index": "a1",
|
"index": "a1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"lineHeight": 1.25,
|
"lineHeight": "1.25000",
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false,
|
"locked": false,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
@@ -16607,7 +16607,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
"verticalAlign": "middle",
|
"verticalAlign": "middle",
|
||||||
"width": 30,
|
"width": 30,
|
||||||
"x": -65,
|
"x": -65,
|
||||||
"y": -12.5,
|
"y": "-12.50000",
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -16855,7 +16855,7 @@ History {
|
|||||||
"height": 100,
|
"height": 100,
|
||||||
"index": "a1",
|
"index": "a1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"lineHeight": 1.25,
|
"lineHeight": "1.25000",
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false,
|
"locked": false,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
@@ -16990,7 +16990,7 @@ History {
|
|||||||
"verticalAlign": "middle",
|
"verticalAlign": "middle",
|
||||||
"width": 30,
|
"width": 30,
|
||||||
"x": -65,
|
"x": -65,
|
||||||
"y": -12.5,
|
"y": "-12.50000",
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"containerId": null,
|
"containerId": null,
|
||||||
@@ -17320,7 +17320,7 @@ exports[`history > singleplayer undo/redo > should support changes in elements'
|
|||||||
"strokeWidth": 2,
|
"strokeWidth": 2,
|
||||||
"type": "rectangle",
|
"type": "rectangle",
|
||||||
"updated": 1,
|
"updated": 1,
|
||||||
"version": 6,
|
"version": 7,
|
||||||
"width": 10,
|
"width": 10,
|
||||||
"x": 10,
|
"x": 10,
|
||||||
"y": 0,
|
"y": 0,
|
||||||
@@ -17352,7 +17352,7 @@ exports[`history > singleplayer undo/redo > should support changes in elements'
|
|||||||
"strokeWidth": 2,
|
"strokeWidth": 2,
|
||||||
"type": "rectangle",
|
"type": "rectangle",
|
||||||
"updated": 1,
|
"updated": 1,
|
||||||
"version": 9,
|
"version": 10,
|
||||||
"width": 10,
|
"width": 10,
|
||||||
"x": 40,
|
"x": 40,
|
||||||
"y": 40,
|
"y": 40,
|
||||||
@@ -17604,7 +17604,7 @@ History {
|
|||||||
"index": "a2",
|
"index": "a2",
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"index": "a0",
|
"index": "Zz",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"id41" => Delta {
|
"id41" => Delta {
|
||||||
@@ -17612,7 +17612,7 @@ History {
|
|||||||
"index": "a3",
|
"index": "a3",
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"index": "a0V",
|
"index": "a0",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -189,13 +189,13 @@ exports[`move element > rectangles with binding arrow 7`] = `
|
|||||||
"endArrowhead": "arrow",
|
"endArrowhead": "arrow",
|
||||||
"endBinding": {
|
"endBinding": {
|
||||||
"elementId": "id1",
|
"elementId": "id1",
|
||||||
"focus": -0.46666666666666673,
|
"focus": "-0.46667",
|
||||||
"gap": 10,
|
"gap": 10,
|
||||||
},
|
},
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
"height": 81.48231043525051,
|
"height": "81.48231",
|
||||||
"id": "id2",
|
"id": "id2",
|
||||||
"index": "a2",
|
"index": "a2",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
@@ -210,7 +210,7 @@ exports[`move element > rectangles with binding arrow 7`] = `
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
81,
|
81,
|
||||||
81.48231043525051,
|
"81.48231",
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
@@ -221,7 +221,7 @@ exports[`move element > rectangles with binding arrow 7`] = `
|
|||||||
"startArrowhead": null,
|
"startArrowhead": null,
|
||||||
"startBinding": {
|
"startBinding": {
|
||||||
"elementId": "id0",
|
"elementId": "id0",
|
||||||
"focus": -0.6000000000000001,
|
"focus": "-0.60000",
|
||||||
"gap": 10,
|
"gap": 10,
|
||||||
},
|
},
|
||||||
"strokeColor": "#1e1e1e",
|
"strokeColor": "#1e1e1e",
|
||||||
@@ -233,6 +233,6 @@ exports[`move element > rectangles with binding arrow 7`] = `
|
|||||||
"versionNonce": 2066753033,
|
"versionNonce": 2066753033,
|
||||||
"width": 81,
|
"width": 81,
|
||||||
"x": 110,
|
"x": 110,
|
||||||
"y": 49.981789081137734,
|
"y": "49.98179",
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -336,7 +336,7 @@ History {
|
|||||||
"groupIds": [
|
"groupIds": [
|
||||||
"id5",
|
"id5",
|
||||||
],
|
],
|
||||||
"index": "a1V",
|
"index": "a2",
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -348,9 +348,11 @@ History {
|
|||||||
"groupIds": [
|
"groupIds": [
|
||||||
"id5",
|
"id5",
|
||||||
],
|
],
|
||||||
|
"index": "a3",
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
|
"index": "a2",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -719,7 +721,7 @@ History {
|
|||||||
"groupIds": [
|
"groupIds": [
|
||||||
"id4",
|
"id4",
|
||||||
],
|
],
|
||||||
"index": "a1V",
|
"index": "a2",
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -731,9 +733,11 @@ History {
|
|||||||
"groupIds": [
|
"groupIds": [
|
||||||
"id4",
|
"id4",
|
||||||
],
|
],
|
||||||
|
"index": "a3",
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
|
"index": "a2",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -1856,7 +1860,7 @@ History {
|
|||||||
"groupIds": [
|
"groupIds": [
|
||||||
"id5",
|
"id5",
|
||||||
],
|
],
|
||||||
"index": "a1V",
|
"index": "a2",
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
@@ -1868,9 +1872,11 @@ History {
|
|||||||
"groupIds": [
|
"groupIds": [
|
||||||
"id5",
|
"id5",
|
||||||
],
|
],
|
||||||
|
"index": "a3",
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
|
"index": "a2",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -10752,7 +10758,7 @@ exports[`regression tests > pinch-to-zoom works > [end of test] appState 1`] = `
|
|||||||
"pendingImageElementId": null,
|
"pendingImageElementId": null,
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
"scrollX": -6.2500000000000036,
|
"scrollX": "-6.25000",
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
"selectedElementIds": {},
|
"selectedElementIds": {},
|
||||||
@@ -12810,7 +12816,7 @@ History {
|
|||||||
"id5",
|
"id5",
|
||||||
"id3",
|
"id3",
|
||||||
],
|
],
|
||||||
"index": "a1V",
|
"index": "a2",
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"groupIds": [
|
"groupIds": [
|
||||||
@@ -12825,11 +12831,13 @@ History {
|
|||||||
"id5",
|
"id5",
|
||||||
"id3",
|
"id3",
|
||||||
],
|
],
|
||||||
|
"index": "a3",
|
||||||
},
|
},
|
||||||
"inserted": {
|
"inserted": {
|
||||||
"groupIds": [
|
"groupIds": [
|
||||||
"id3",
|
"id3",
|
||||||
],
|
],
|
||||||
|
"index": "a2",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -13689,7 +13697,7 @@ exports[`regression tests > two-finger scroll works > [end of test] appState 1`]
|
|||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
"scrollX": 20,
|
"scrollX": 20,
|
||||||
"scrollY": -18.535533905932738,
|
"scrollY": "-18.53553",
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
"selectedElementIds": {},
|
"selectedElementIds": {},
|
||||||
"selectedElementsAreBeingDragged": false,
|
"selectedElementsAreBeingDragged": false,
|
||||||
|
|||||||
@@ -316,7 +316,7 @@ exports[`restoreElements > should restore text element correctly passing value f
|
|||||||
"id": "id-text01",
|
"id": "id-text01",
|
||||||
"index": "a0",
|
"index": "a0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"lineHeight": 1.25,
|
"lineHeight": "1.25000",
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false,
|
"locked": false,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
@@ -338,7 +338,7 @@ exports[`restoreElements > should restore text element correctly passing value f
|
|||||||
"verticalAlign": "middle",
|
"verticalAlign": "middle",
|
||||||
"width": 100,
|
"width": 100,
|
||||||
"x": -20,
|
"x": -20,
|
||||||
"y": -8.75,
|
"y": "-8.75000",
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -359,7 +359,7 @@ exports[`restoreElements > should restore text element correctly with unknown fo
|
|||||||
"id": "id-text01",
|
"id": "id-text01",
|
||||||
"index": "a0",
|
"index": "a0",
|
||||||
"isDeleted": true,
|
"isDeleted": true,
|
||||||
"lineHeight": 1.25,
|
"lineHeight": "1.25000",
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false,
|
"locked": false,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
|
|||||||
@@ -77,97 +77,6 @@ describe("sync invalid indices with array order", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("should NOT sync index when it is already valid", () => {
|
|
||||||
testMovedIndicesSync({
|
|
||||||
elements: [
|
|
||||||
{ id: "A", index: "a2" },
|
|
||||||
{ id: "B", index: "a4" },
|
|
||||||
],
|
|
||||||
movedElements: ["A"],
|
|
||||||
expect: {
|
|
||||||
validInput: true,
|
|
||||||
unchangedElements: ["A", "B"],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
testMovedIndicesSync({
|
|
||||||
elements: [
|
|
||||||
{ id: "A", index: "a2" },
|
|
||||||
{ id: "B", index: "a4" },
|
|
||||||
],
|
|
||||||
movedElements: ["B"],
|
|
||||||
expect: {
|
|
||||||
validInput: true,
|
|
||||||
unchangedElements: ["A", "B"],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("should NOT sync indices when they are already valid", () => {
|
|
||||||
{
|
|
||||||
testMovedIndicesSync({
|
|
||||||
elements: [
|
|
||||||
{ id: "A", index: "a1" },
|
|
||||||
{ id: "B", index: "a0" },
|
|
||||||
{ id: "C", index: "a2" },
|
|
||||||
],
|
|
||||||
movedElements: ["B", "C"],
|
|
||||||
expect: {
|
|
||||||
// this should not sync 'C'
|
|
||||||
unchangedElements: ["A", "C"],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
testMovedIndicesSync({
|
|
||||||
elements: [
|
|
||||||
{ id: "A", index: "a1" },
|
|
||||||
{ id: "B", index: "a0" },
|
|
||||||
{ id: "C", index: "a2" },
|
|
||||||
],
|
|
||||||
movedElements: ["A", "B"],
|
|
||||||
expect: {
|
|
||||||
// but this should sync 'A' as it's invalid!
|
|
||||||
unchangedElements: ["C"],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
testMovedIndicesSync({
|
|
||||||
elements: [
|
|
||||||
{ id: "A", index: "a0" },
|
|
||||||
{ id: "B", index: "a2" },
|
|
||||||
{ id: "C", index: "a1" },
|
|
||||||
{ id: "D", index: "a1" },
|
|
||||||
{ id: "E", index: "a2" },
|
|
||||||
],
|
|
||||||
movedElements: ["B", "D", "E"],
|
|
||||||
expect: {
|
|
||||||
// should not sync 'E'
|
|
||||||
unchangedElements: ["A", "C", "E"],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
testMovedIndicesSync({
|
|
||||||
elements: [
|
|
||||||
{ id: "A" },
|
|
||||||
{ id: "B" },
|
|
||||||
{ id: "C", index: "a0" },
|
|
||||||
{ id: "D", index: "a2" },
|
|
||||||
{ id: "E" },
|
|
||||||
{ id: "F", index: "a3" },
|
|
||||||
{ id: "G" },
|
|
||||||
{ id: "H", index: "a1" },
|
|
||||||
{ id: "I", index: "a2" },
|
|
||||||
{ id: "J" },
|
|
||||||
],
|
|
||||||
movedElements: ["A", "B", "D", "E", "F", "G", "J"],
|
|
||||||
expect: {
|
|
||||||
// should not sync 'D' and 'F'
|
|
||||||
unchangedElements: ["C", "D", "F"],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("should sync when fractional index is not defined", () => {
|
describe("should sync when fractional index is not defined", () => {
|
||||||
testMovedIndicesSync({
|
testMovedIndicesSync({
|
||||||
elements: [{ id: "A" }],
|
elements: [{ id: "A" }],
|
||||||
@@ -384,6 +293,122 @@ describe("sync invalid indices with array order", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("should sync all moved elements regardless of their validity", () => {
|
||||||
|
testMovedIndicesSync({
|
||||||
|
elements: [
|
||||||
|
{ id: "A", index: "a2" },
|
||||||
|
{ id: "B", index: "a4" },
|
||||||
|
],
|
||||||
|
movedElements: ["A"],
|
||||||
|
expect: {
|
||||||
|
validInput: true,
|
||||||
|
unchangedElements: ["B"],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
testMovedIndicesSync({
|
||||||
|
elements: [
|
||||||
|
{ id: "A", index: "a2" },
|
||||||
|
{ id: "B", index: "a4" },
|
||||||
|
],
|
||||||
|
movedElements: ["B"],
|
||||||
|
expect: {
|
||||||
|
validInput: true,
|
||||||
|
unchangedElements: ["A"],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
testMovedIndicesSync({
|
||||||
|
elements: [
|
||||||
|
{ id: "C", index: "a2" },
|
||||||
|
{ id: "D", index: "a3" },
|
||||||
|
{ id: "A", index: "a0" },
|
||||||
|
{ id: "B", index: "a1" },
|
||||||
|
],
|
||||||
|
movedElements: ["C", "D"],
|
||||||
|
expect: {
|
||||||
|
unchangedElements: ["A", "B"],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
testMovedIndicesSync({
|
||||||
|
elements: [
|
||||||
|
{ id: "A", index: "a1" },
|
||||||
|
{ id: "B", index: "a2" },
|
||||||
|
{ id: "D", index: "a4" },
|
||||||
|
{ id: "C", index: "a3" },
|
||||||
|
{ id: "F", index: "a6" },
|
||||||
|
{ id: "E", index: "a5" },
|
||||||
|
{ id: "H", index: "a8" },
|
||||||
|
{ id: "G", index: "a7" },
|
||||||
|
{ id: "I", index: "a9" },
|
||||||
|
],
|
||||||
|
movedElements: ["D", "F", "H"],
|
||||||
|
expect: {
|
||||||
|
unchangedElements: ["A", "B", "C", "E", "G", "I"],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
{
|
||||||
|
testMovedIndicesSync({
|
||||||
|
elements: [
|
||||||
|
{ id: "A", index: "a1" },
|
||||||
|
{ id: "B", index: "a0" },
|
||||||
|
{ id: "C", index: "a2" },
|
||||||
|
],
|
||||||
|
movedElements: ["B", "C"],
|
||||||
|
expect: {
|
||||||
|
unchangedElements: ["A"],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
testMovedIndicesSync({
|
||||||
|
elements: [
|
||||||
|
{ id: "A", index: "a1" },
|
||||||
|
{ id: "B", index: "a0" },
|
||||||
|
{ id: "C", index: "a2" },
|
||||||
|
],
|
||||||
|
movedElements: ["A", "B"],
|
||||||
|
expect: {
|
||||||
|
unchangedElements: ["C"],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
testMovedIndicesSync({
|
||||||
|
elements: [
|
||||||
|
{ id: "A", index: "a0" },
|
||||||
|
{ id: "B", index: "a2" },
|
||||||
|
{ id: "C", index: "a1" },
|
||||||
|
{ id: "D", index: "a1" },
|
||||||
|
{ id: "E", index: "a2" },
|
||||||
|
],
|
||||||
|
movedElements: ["B", "D", "E"],
|
||||||
|
expect: {
|
||||||
|
unchangedElements: ["A", "C"],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
testMovedIndicesSync({
|
||||||
|
elements: [
|
||||||
|
{ id: "A" },
|
||||||
|
{ id: "B" },
|
||||||
|
{ id: "C", index: "a0" },
|
||||||
|
{ id: "D", index: "a2" },
|
||||||
|
{ id: "E" },
|
||||||
|
{ id: "F", index: "a3" },
|
||||||
|
{ id: "G" },
|
||||||
|
{ id: "H", index: "a1" },
|
||||||
|
{ id: "I", index: "a2" },
|
||||||
|
{ id: "J" },
|
||||||
|
],
|
||||||
|
movedElements: ["A", "B", "D", "E", "F", "G", "J"],
|
||||||
|
expect: {
|
||||||
|
unchangedElements: ["C", "H", "I"],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("should generate fractions for explicitly moved elements", () => {
|
describe("should generate fractions for explicitly moved elements", () => {
|
||||||
describe("should generate a fraction between 'A' and 'C'", () => {
|
describe("should generate a fraction between 'A' and 'C'", () => {
|
||||||
testMovedIndicesSync({
|
testMovedIndicesSync({
|
||||||
|
|||||||
@@ -319,12 +319,12 @@ describe("Test Linear Elements", () => {
|
|||||||
expect(midPointsWithRoundEdge).toMatchInlineSnapshot(`
|
expect(midPointsWithRoundEdge).toMatchInlineSnapshot(`
|
||||||
[
|
[
|
||||||
[
|
[
|
||||||
55.9697848965255,
|
"55.96978",
|
||||||
47.442326230998205,
|
"47.44233",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
76.08587175006699,
|
"76.08587",
|
||||||
43.294165939653226,
|
"43.29417",
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
`);
|
`);
|
||||||
@@ -381,12 +381,12 @@ describe("Test Linear Elements", () => {
|
|||||||
expect(newMidPoints).toMatchInlineSnapshot(`
|
expect(newMidPoints).toMatchInlineSnapshot(`
|
||||||
[
|
[
|
||||||
[
|
[
|
||||||
105.96978489652551,
|
"105.96978",
|
||||||
67.4423262309982,
|
"67.44233",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
126.08587175006699,
|
"126.08587",
|
||||||
63.294165939653226,
|
"63.29417",
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
`);
|
`);
|
||||||
@@ -627,16 +627,16 @@ describe("Test Linear Elements", () => {
|
|||||||
0,
|
0,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
85.96978489652551,
|
"85.96978",
|
||||||
77.4423262309982,
|
"77.44233",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
70,
|
70,
|
||||||
50,
|
50,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
106.08587175006699,
|
"106.08587",
|
||||||
73.29416593965323,
|
"73.29417",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
40,
|
40,
|
||||||
@@ -683,12 +683,12 @@ describe("Test Linear Elements", () => {
|
|||||||
expect(newMidPoints).toMatchInlineSnapshot(`
|
expect(newMidPoints).toMatchInlineSnapshot(`
|
||||||
[
|
[
|
||||||
[
|
[
|
||||||
31.884084517616053,
|
"31.88408",
|
||||||
23.13275505472383,
|
"23.13276",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
77.74792546875662,
|
"77.74793",
|
||||||
44.57840982272327,
|
"44.57841",
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
`);
|
`);
|
||||||
@@ -769,12 +769,12 @@ describe("Test Linear Elements", () => {
|
|||||||
expect(newMidPoints).toMatchInlineSnapshot(`
|
expect(newMidPoints).toMatchInlineSnapshot(`
|
||||||
[
|
[
|
||||||
[
|
[
|
||||||
55.9697848965255,
|
"55.96978",
|
||||||
47.442326230998205,
|
"47.44233",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
76.08587175006699,
|
"76.08587",
|
||||||
43.294165939653226,
|
"43.29417",
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
`);
|
`);
|
||||||
@@ -928,8 +928,8 @@ describe("Test Linear Elements", () => {
|
|||||||
);
|
);
|
||||||
expect(position).toMatchInlineSnapshot(`
|
expect(position).toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
"x": 85.82201843191861,
|
"x": "85.82202",
|
||||||
"y": 75.63461309860818,
|
"y": "75.63461",
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
@@ -1068,15 +1068,15 @@ describe("Test Linear Elements", () => {
|
|||||||
true,
|
true,
|
||||||
),
|
),
|
||||||
).toMatchInlineSnapshot(`
|
).toMatchInlineSnapshot(`
|
||||||
[
|
[
|
||||||
20,
|
20,
|
||||||
20,
|
20,
|
||||||
105,
|
105,
|
||||||
80,
|
80,
|
||||||
55.45893770831013,
|
"55.45894",
|
||||||
45,
|
45,
|
||||||
]
|
]
|
||||||
`);
|
`);
|
||||||
|
|
||||||
UI.resize(container, "ne", [300, 200]);
|
UI.resize(container, "ne", [300, 200]);
|
||||||
|
|
||||||
@@ -1084,7 +1084,7 @@ describe("Test Linear Elements", () => {
|
|||||||
.toMatchInlineSnapshot(`
|
.toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
"height": 130,
|
"height": 130,
|
||||||
"width": 366.11716195150507,
|
"width": "366.11716",
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
@@ -1095,11 +1095,11 @@ describe("Test Linear Elements", () => {
|
|||||||
arrayToMap(h.elements),
|
arrayToMap(h.elements),
|
||||||
),
|
),
|
||||||
).toMatchInlineSnapshot(`
|
).toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
"x": 271.11716195150507,
|
"x": "271.11716",
|
||||||
"y": 45,
|
"y": 45,
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
expect((h.elements[1] as ExcalidrawTextElementWithContainer).text)
|
expect((h.elements[1] as ExcalidrawTextElementWithContainer).text)
|
||||||
.toMatchInlineSnapshot(`
|
.toMatchInlineSnapshot(`
|
||||||
"Online whiteboard
|
"Online whiteboard
|
||||||
@@ -1112,15 +1112,15 @@ describe("Test Linear Elements", () => {
|
|||||||
true,
|
true,
|
||||||
),
|
),
|
||||||
).toMatchInlineSnapshot(`
|
).toMatchInlineSnapshot(`
|
||||||
[
|
[
|
||||||
20,
|
20,
|
||||||
35,
|
35,
|
||||||
501.11716195150507,
|
"501.11716",
|
||||||
95,
|
95,
|
||||||
205.4589377083102,
|
"205.45894",
|
||||||
52.5,
|
"52.50000",
|
||||||
]
|
]
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should resize and position the bound text correctly when 2 pointer linear element resized", () => {
|
it("should resize and position the bound text correctly when 2 pointer linear element resized", () => {
|
||||||
|
|||||||
@@ -242,3 +242,20 @@ expect.extend({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializer for IEE754 float pointing numbers to avoid random failures due to tiny precision differences
|
||||||
|
*/
|
||||||
|
expect.addSnapshotSerializer({
|
||||||
|
serialize(val, config, indentation, depth, refs, printer) {
|
||||||
|
return printer(val.toFixed(5), config, indentation, depth, refs);
|
||||||
|
},
|
||||||
|
test(val) {
|
||||||
|
return (
|
||||||
|
typeof val === "number" &&
|
||||||
|
Number.isFinite(val) &&
|
||||||
|
!Number.isNaN(val) &&
|
||||||
|
!Number.isInteger(val)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -592,6 +592,7 @@ export type AppClassProperties = {
|
|||||||
files: BinaryFiles;
|
files: BinaryFiles;
|
||||||
device: App["device"];
|
device: App["device"];
|
||||||
scene: App["scene"];
|
scene: App["scene"];
|
||||||
|
store: App["store"];
|
||||||
pasteFromClipboard: App["pasteFromClipboard"];
|
pasteFromClipboard: App["pasteFromClipboard"];
|
||||||
id: App["id"];
|
id: App["id"];
|
||||||
onInsertElements: App["onInsertElements"];
|
onInsertElements: App["onInsertElements"];
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB |
Reference in New Issue
Block a user