Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9954e5f3c2 | |||
| 0cc70d07c7 | |||
| 359a2f23ae | |||
| 467a4a2a6a | |||
| a7b55da49e | |||
| 4f82c32861 | |||
| 8701a5f843 | |||
| c498247e0f | |||
| 81e1d61d42 | |||
| 741c8dec31 | |||
| da0481683a | |||
| 58307a96dc | |||
| 1a0755cd56 | |||
| c742b4847f | |||
| bcb50188b8 | |||
| bdb1ec387d | |||
| b0375fe5db | |||
| 940099f85d | |||
| e30fd9960d | |||
| 80ff1562b8 | |||
| 3f00762a77 | |||
| 7b012b1cad | |||
| a02c4cb140 | |||
| 3a01122093 | |||
| bccd2bf30d | |||
| 50e4a0b37d | |||
| 9e80c9e3dd | |||
| afdf38e47c | |||
| 06a7a51baa | |||
| 064bede0c5 | |||
| 997fec6c75 | |||
| 71ed96eabb | |||
| f4bebaaa50 | |||
| 3d92a3bf1f |
@@ -4,7 +4,6 @@ import { trackEvent } from "../packages/excalidraw/analytics";
|
|||||||
import { getDefaultAppState } from "../packages/excalidraw/appState";
|
import { getDefaultAppState } from "../packages/excalidraw/appState";
|
||||||
import { ErrorDialog } from "../packages/excalidraw/components/ErrorDialog";
|
import { ErrorDialog } from "../packages/excalidraw/components/ErrorDialog";
|
||||||
import { TopErrorBoundary } from "./components/TopErrorBoundary";
|
import { TopErrorBoundary } from "./components/TopErrorBoundary";
|
||||||
import { useMathSubtype } from "../packages/excalidraw/element/subtypes/mathjax";
|
|
||||||
import {
|
import {
|
||||||
APP_NAME,
|
APP_NAME,
|
||||||
EVENT,
|
EVENT,
|
||||||
@@ -356,8 +355,6 @@ const ExcalidrawWrapper = () => {
|
|||||||
const [excalidrawAPI, excalidrawRefCallback] =
|
const [excalidrawAPI, excalidrawRefCallback] =
|
||||||
useCallbackRefState<ExcalidrawImperativeAPI>();
|
useCallbackRefState<ExcalidrawImperativeAPI>();
|
||||||
|
|
||||||
useMathSubtype(excalidrawAPI);
|
|
||||||
|
|
||||||
const [, setShareDialogState] = useAtom(shareDialogStateAtom);
|
const [, setShareDialogState] = useAtom(shareDialogStateAtom);
|
||||||
const [collabAPI] = useAtom(collabAPIAtom);
|
const [collabAPI] = useAtom(collabAPIAtom);
|
||||||
const [isCollaborating] = useAtomWithInitialValue(isCollaboratingAtom, () => {
|
const [isCollaborating] = useAtomWithInitialValue(isCollaboratingAtom, () => {
|
||||||
|
|||||||
@@ -32,9 +32,7 @@
|
|||||||
"husky": "7.0.4",
|
"husky": "7.0.4",
|
||||||
"jsdom": "22.1.0",
|
"jsdom": "22.1.0",
|
||||||
"lint-staged": "12.3.7",
|
"lint-staged": "12.3.7",
|
||||||
"patch-package": "8.0.0",
|
|
||||||
"pepjs": "0.5.3",
|
"pepjs": "0.5.3",
|
||||||
"postinstall-postinstall": "2.1.0",
|
|
||||||
"prettier": "2.6.2",
|
"prettier": "2.6.2",
|
||||||
"rewire": "6.0.0",
|
"rewire": "6.0.0",
|
||||||
"typescript": "4.9.4",
|
"typescript": "4.9.4",
|
||||||
@@ -63,7 +61,6 @@
|
|||||||
"locales-coverage": "node scripts/build-locales-coverage.js",
|
"locales-coverage": "node scripts/build-locales-coverage.js",
|
||||||
"locales-coverage:description": "node scripts/locales-coverage-description.js",
|
"locales-coverage:description": "node scripts/locales-coverage-description.js",
|
||||||
"prepare": "husky install",
|
"prepare": "husky install",
|
||||||
"postinstall": "patch-package",
|
|
||||||
"prettier": "prettier \"**/*.{css,scss,json,md,html,yml}\" --ignore-path=.eslintignore",
|
"prettier": "prettier \"**/*.{css,scss,json,md,html,yml}\" --ignore-path=.eslintignore",
|
||||||
"start": "yarn --cwd ./excalidraw-app start",
|
"start": "yarn --cwd ./excalidraw-app start",
|
||||||
"start:app:production": "npm run build && npx http-server build -a localhost -p 5001 -o",
|
"start:app:production": "npm run build && npx http-server build -a localhost -p 5001 -o",
|
||||||
|
|||||||
@@ -305,7 +305,6 @@ define: {
|
|||||||
|
|
||||||
## 0.16.0 (2023-09-19)
|
## 0.16.0 (2023-09-19)
|
||||||
|
|
||||||
- Add a `subtype` attribute to `ExcalidrawElement` to allow self-contained extensions of any `ExcalidrawElement` type. Implement MathJax support on stem.excalidraw.com as a `math` subtype of `ExcalidrawTextElement`. Both standard Latex input and simplified AsciiMath input are supported. [#6037](https://github.com/excalidraw/excalidraw/pull/6037).
|
|
||||||
- Support creating containers, linear elements, text containers, labelled arrows and arrow bindings programatically [#6546](https://github.com/excalidraw/excalidraw/pull/6546)
|
- Support creating containers, linear elements, text containers, labelled arrows and arrow bindings programatically [#6546](https://github.com/excalidraw/excalidraw/pull/6546)
|
||||||
- Introducing Web-Embeds (alias iframe element)[#6691](https://github.com/excalidraw/excalidraw/pull/6691)
|
- Introducing Web-Embeds (alias iframe element)[#6691](https://github.com/excalidraw/excalidraw/pull/6691)
|
||||||
- Added [`props.validateEmbeddable`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props#validateembeddable) to customize embeddable src url validation. [#6691](https://github.com/excalidraw/excalidraw/pull/6691)
|
- Added [`props.validateEmbeddable`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/props#validateembeddable) to customize embeddable src url validation. [#6691](https://github.com/excalidraw/excalidraw/pull/6691)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
computeBoundTextPosition,
|
computeBoundTextPosition,
|
||||||
computeContainerDimensionForBoundText,
|
computeContainerDimensionForBoundText,
|
||||||
getBoundTextElement,
|
getBoundTextElement,
|
||||||
measureTextElement,
|
measureText,
|
||||||
redrawTextBoundingBox,
|
redrawTextBoundingBox,
|
||||||
} from "../element/textElement";
|
} from "../element/textElement";
|
||||||
import {
|
import {
|
||||||
@@ -31,7 +31,7 @@ import type {
|
|||||||
} from "../element/types";
|
} from "../element/types";
|
||||||
import type { AppState } from "../types";
|
import type { AppState } from "../types";
|
||||||
import type { Mutable } from "../utility-types";
|
import type { Mutable } from "../utility-types";
|
||||||
import { arrayToMap } from "../utils";
|
import { arrayToMap, getFontString } from "../utils";
|
||||||
import { register } from "./register";
|
import { register } from "./register";
|
||||||
import { syncMovedIndices } from "../fractionalIndex";
|
import { syncMovedIndices } from "../fractionalIndex";
|
||||||
import { StoreAction } from "../store";
|
import { StoreAction } from "../store";
|
||||||
@@ -51,9 +51,11 @@ export const actionUnbindText = register({
|
|||||||
selectedElements.forEach((element) => {
|
selectedElements.forEach((element) => {
|
||||||
const boundTextElement = getBoundTextElement(element, elementsMap);
|
const boundTextElement = getBoundTextElement(element, elementsMap);
|
||||||
if (boundTextElement) {
|
if (boundTextElement) {
|
||||||
const { width, height } = measureTextElement(boundTextElement, {
|
const { width, height } = measureText(
|
||||||
text: boundTextElement.originalText,
|
boundTextElement.originalText,
|
||||||
});
|
getFontString(boundTextElement),
|
||||||
|
boundTextElement.lineHeight,
|
||||||
|
);
|
||||||
const originalContainerHeight = getOriginalContainerHeightFromCache(
|
const originalContainerHeight = getOriginalContainerHeightFromCache(
|
||||||
element.id,
|
element.id,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import { register } from "./register";
|
||||||
|
import { cropIcon } from "../components/icons";
|
||||||
|
import { StoreAction } from "../store";
|
||||||
|
import { ToolButton } from "../components/ToolButton";
|
||||||
|
import { t } from "../i18n";
|
||||||
|
import { isImageElement } from "../element/typeChecks";
|
||||||
|
import type { ExcalidrawImageElement } from "../element/types";
|
||||||
|
|
||||||
|
export const actionToggleCropEditor = register({
|
||||||
|
name: "cropEditor",
|
||||||
|
label: "helpDialog.cropStart",
|
||||||
|
icon: cropIcon,
|
||||||
|
viewMode: true,
|
||||||
|
trackEvent: { category: "menu" },
|
||||||
|
keywords: ["image", "crop"],
|
||||||
|
perform(elements, appState, _, app) {
|
||||||
|
const selectedElement = app.scene.getSelectedElements({
|
||||||
|
selectedElementIds: appState.selectedElementIds,
|
||||||
|
includeBoundTextElement: true,
|
||||||
|
})[0] as ExcalidrawImageElement;
|
||||||
|
|
||||||
|
return {
|
||||||
|
appState: {
|
||||||
|
...appState,
|
||||||
|
isCropping: false,
|
||||||
|
croppingElementId: selectedElement.id,
|
||||||
|
},
|
||||||
|
storeAction: StoreAction.CAPTURE,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
predicate: (elements, appState, _, app) => {
|
||||||
|
const selectedElements = app.scene.getSelectedElements(appState);
|
||||||
|
if (
|
||||||
|
!appState.croppingElementId &&
|
||||||
|
selectedElements.length === 1 &&
|
||||||
|
isImageElement(selectedElements[0])
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
PanelComponent: ({ appState, updateData, app }) => {
|
||||||
|
const label = t("helpDialog.cropStart");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToolButton
|
||||||
|
type="button"
|
||||||
|
icon={cropIcon}
|
||||||
|
title={label}
|
||||||
|
aria-label={label}
|
||||||
|
onClick={() => updateData(null)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -88,3 +88,5 @@ export { actionToggleElementLock } from "./actionElementLock";
|
|||||||
export { actionToggleLinearEditor } from "./actionLinearEditor";
|
export { actionToggleLinearEditor } from "./actionLinearEditor";
|
||||||
|
|
||||||
export { actionToggleSearchMenu } from "./actionToggleSearchMenu";
|
export { actionToggleSearchMenu } from "./actionToggleSearchMenu";
|
||||||
|
|
||||||
|
export { actionToggleCropEditor } from "./actionCropEditor";
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import type {
|
|||||||
ActionResult,
|
ActionResult,
|
||||||
PanelComponentProps,
|
PanelComponentProps,
|
||||||
ActionSource,
|
ActionSource,
|
||||||
ActionPredicateFn,
|
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import type {
|
import type {
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
@@ -46,7 +45,6 @@ const trackAction = (
|
|||||||
|
|
||||||
export class ActionManager {
|
export class ActionManager {
|
||||||
actions = {} as Record<ActionName, Action>;
|
actions = {} as Record<ActionName, Action>;
|
||||||
actionPredicates = [] as ActionPredicateFn[];
|
|
||||||
|
|
||||||
updater: (actionResult: ActionResult | Promise<ActionResult>) => void;
|
updater: (actionResult: ActionResult | Promise<ActionResult>) => void;
|
||||||
|
|
||||||
@@ -74,37 +72,6 @@ export class ActionManager {
|
|||||||
this.app = app;
|
this.app = app;
|
||||||
}
|
}
|
||||||
|
|
||||||
registerActionPredicate(predicate: ActionPredicateFn) {
|
|
||||||
if (!this.actionPredicates.includes(predicate)) {
|
|
||||||
this.actionPredicates.push(predicate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
filterActions(
|
|
||||||
filter: ActionPredicateFn,
|
|
||||||
opts?: {
|
|
||||||
elements?: readonly ExcalidrawElement[];
|
|
||||||
data?: Record<string, any>;
|
|
||||||
},
|
|
||||||
): Action[] {
|
|
||||||
// For testing
|
|
||||||
if (this === undefined) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const elements = opts?.elements ?? this.getElementsIncludingDeleted();
|
|
||||||
const appState = this.getAppState();
|
|
||||||
const data = opts?.data;
|
|
||||||
|
|
||||||
const actions: Action[] = [];
|
|
||||||
for (const key in this.actions) {
|
|
||||||
const action = this.actions[key as ActionName];
|
|
||||||
if (filter(action, elements, appState, this.app, data)) {
|
|
||||||
actions.push(action);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return actions;
|
|
||||||
}
|
|
||||||
|
|
||||||
registerAction(action: Action) {
|
registerAction(action: Action) {
|
||||||
this.actions[action.name] = action;
|
this.actions[action.name] = action;
|
||||||
}
|
}
|
||||||
@@ -121,7 +88,7 @@ export class ActionManager {
|
|||||||
(action) =>
|
(action) =>
|
||||||
(action.name in canvasActions
|
(action.name in canvasActions
|
||||||
? canvasActions[action.name as keyof typeof canvasActions]
|
? canvasActions[action.name as keyof typeof canvasActions]
|
||||||
: this.isActionEnabled(action, { noPredicates: true })) &&
|
: true) &&
|
||||||
action.keyTest &&
|
action.keyTest &&
|
||||||
action.keyTest(
|
action.keyTest(
|
||||||
event,
|
event,
|
||||||
@@ -180,7 +147,7 @@ export class ActionManager {
|
|||||||
"PanelComponent" in this.actions[name] &&
|
"PanelComponent" in this.actions[name] &&
|
||||||
(name in canvasActions
|
(name in canvasActions
|
||||||
? canvasActions[name as keyof typeof canvasActions]
|
? canvasActions[name as keyof typeof canvasActions]
|
||||||
: this.isActionEnabled(this.actions[name], { noPredicates: true }))
|
: true)
|
||||||
) {
|
) {
|
||||||
const action = this.actions[name];
|
const action = this.actions[name];
|
||||||
const PanelComponent = action.PanelComponent!;
|
const PanelComponent = action.PanelComponent!;
|
||||||
@@ -202,7 +169,6 @@ export class ActionManager {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<PanelComponent
|
<PanelComponent
|
||||||
key={name}
|
|
||||||
elements={this.getElementsIncludingDeleted()}
|
elements={this.getElementsIncludingDeleted()}
|
||||||
appState={this.getAppState()}
|
appState={this.getAppState()}
|
||||||
updateData={updateData}
|
updateData={updateData}
|
||||||
@@ -216,31 +182,13 @@ export class ActionManager {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
isActionEnabled = (
|
isActionEnabled = (action: Action) => {
|
||||||
action: Action,
|
const elements = this.getElementsIncludingDeleted();
|
||||||
opts?: {
|
|
||||||
elements?: readonly ExcalidrawElement[];
|
|
||||||
data?: Record<string, any>;
|
|
||||||
noPredicates?: boolean;
|
|
||||||
},
|
|
||||||
): boolean => {
|
|
||||||
const elements = opts?.elements ?? this.getElementsIncludingDeleted();
|
|
||||||
const appState = this.getAppState();
|
const appState = this.getAppState();
|
||||||
const data = opts?.data;
|
|
||||||
|
|
||||||
if (
|
return (
|
||||||
!opts?.noPredicates &&
|
!action.predicate ||
|
||||||
action.predicate &&
|
action.predicate(elements, appState, this.app.props, this.app)
|
||||||
!action.predicate(elements, appState, this.app.props, this.app, data)
|
);
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
let enabled = true;
|
|
||||||
this.actionPredicates.forEach((fn) => {
|
|
||||||
if (!fn(action, elements, appState, this.app, data)) {
|
|
||||||
enabled = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return enabled;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,11 @@ import { isDarwin } from "../constants";
|
|||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import type { SubtypeOf } from "../utility-types";
|
import type { SubtypeOf } from "../utility-types";
|
||||||
import { getShortcutKey } from "../utils";
|
import { getShortcutKey } from "../utils";
|
||||||
import type { ActionName, CustomActionName } from "./types";
|
import type { ActionName } from "./types";
|
||||||
|
|
||||||
export type ShortcutName =
|
export type ShortcutName =
|
||||||
| SubtypeOf<
|
| SubtypeOf<
|
||||||
ActionName,
|
ActionName,
|
||||||
| CustomActionName
|
|
||||||
| "toggleTheme"
|
| "toggleTheme"
|
||||||
| "loadScene"
|
| "loadScene"
|
||||||
| "clearCanvas"
|
| "clearCanvas"
|
||||||
@@ -55,15 +54,6 @@ export type ShortcutName =
|
|||||||
| "commandPalette"
|
| "commandPalette"
|
||||||
| "searchMenu";
|
| "searchMenu";
|
||||||
|
|
||||||
export const registerCustomShortcuts = (
|
|
||||||
shortcuts: Record<CustomActionName, string[]>,
|
|
||||||
) => {
|
|
||||||
for (const key in shortcuts) {
|
|
||||||
const shortcut = key as CustomActionName;
|
|
||||||
shortcutMap[shortcut] = shortcuts[shortcut];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const shortcutMap: Record<ShortcutName, string[]> = {
|
const shortcutMap: Record<ShortcutName, string[]> = {
|
||||||
toggleTheme: [getShortcutKey("Shift+Alt+D")],
|
toggleTheme: [getShortcutKey("Shift+Alt+D")],
|
||||||
saveScene: [getShortcutKey("CtrlOrCmd+S")],
|
saveScene: [getShortcutKey("CtrlOrCmd+S")],
|
||||||
|
|||||||
@@ -41,24 +41,10 @@ type ActionFn = (
|
|||||||
app: AppClassProperties,
|
app: AppClassProperties,
|
||||||
) => ActionResult | Promise<ActionResult>;
|
) => ActionResult | Promise<ActionResult>;
|
||||||
|
|
||||||
// Return `true` *unless* `Action` should be disabled
|
|
||||||
// given `elements`, `appState`, and optionally `data`.
|
|
||||||
export type ActionPredicateFn = (
|
|
||||||
action: Action,
|
|
||||||
elements: readonly ExcalidrawElement[],
|
|
||||||
appState: AppState,
|
|
||||||
app: AppClassProperties,
|
|
||||||
data?: Record<string, any>,
|
|
||||||
) => boolean;
|
|
||||||
|
|
||||||
export type UpdaterFn = (res: ActionResult) => void;
|
export type UpdaterFn = (res: ActionResult) => void;
|
||||||
export type ActionFilterFn = (action: Action) => void;
|
export type ActionFilterFn = (action: Action) => void;
|
||||||
|
|
||||||
export const makeCustomActionName = (name: string) =>
|
|
||||||
`custom.${name}` as CustomActionName;
|
|
||||||
export type CustomActionName = `custom.${string}`;
|
|
||||||
export type ActionName =
|
export type ActionName =
|
||||||
| CustomActionName
|
|
||||||
| "copy"
|
| "copy"
|
||||||
| "cut"
|
| "cut"
|
||||||
| "paste"
|
| "paste"
|
||||||
@@ -152,7 +138,8 @@ export type ActionName =
|
|||||||
| "commandPalette"
|
| "commandPalette"
|
||||||
| "autoResize"
|
| "autoResize"
|
||||||
| "elementStats"
|
| "elementStats"
|
||||||
| "searchMenu";
|
| "searchMenu"
|
||||||
|
| "cropEditor";
|
||||||
|
|
||||||
export type PanelComponentProps = {
|
export type PanelComponentProps = {
|
||||||
elements: readonly ExcalidrawElement[];
|
elements: readonly ExcalidrawElement[];
|
||||||
@@ -193,7 +180,6 @@ export interface Action {
|
|||||||
appState: AppState,
|
appState: AppState,
|
||||||
appProps: ExcalidrawProps,
|
appProps: ExcalidrawProps,
|
||||||
app: AppClassProperties,
|
app: AppClassProperties,
|
||||||
data?: Record<string, any>,
|
|
||||||
) => boolean;
|
) => boolean;
|
||||||
checked?: (appState: Readonly<AppState>) => boolean;
|
checked?: (appState: Readonly<AppState>) => boolean;
|
||||||
trackEvent:
|
trackEvent:
|
||||||
|
|||||||
@@ -116,6 +116,8 @@ export const getDefaultAppState = (): Omit<
|
|||||||
objectsSnapModeEnabled: false,
|
objectsSnapModeEnabled: false,
|
||||||
userToFollow: null,
|
userToFollow: null,
|
||||||
followedBy: new Set(),
|
followedBy: new Set(),
|
||||||
|
isCropping: false,
|
||||||
|
croppingElementId: null,
|
||||||
searchMatches: [],
|
searchMatches: [],
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -170,8 +172,6 @@ const APP_STATE_STORAGE_CONF = (<
|
|||||||
editingGroupId: { browser: true, export: false, server: false },
|
editingGroupId: { browser: true, export: false, server: false },
|
||||||
editingLinearElement: { browser: false, export: false, server: false },
|
editingLinearElement: { browser: false, export: false, server: false },
|
||||||
activeTool: { browser: true, export: false, server: false },
|
activeTool: { browser: true, export: false, server: false },
|
||||||
activeSubtypes: { browser: true, export: false, server: false },
|
|
||||||
customData: { browser: true, export: false, server: false },
|
|
||||||
penMode: { browser: true, export: false, server: false },
|
penMode: { browser: true, export: false, server: false },
|
||||||
penDetected: { browser: true, export: false, server: false },
|
penDetected: { browser: true, export: false, server: false },
|
||||||
errorMessage: { browser: false, export: false, server: false },
|
errorMessage: { browser: false, export: false, server: false },
|
||||||
@@ -239,6 +239,8 @@ const APP_STATE_STORAGE_CONF = (<
|
|||||||
objectsSnapModeEnabled: { browser: true, export: false, server: false },
|
objectsSnapModeEnabled: { browser: true, export: false, server: false },
|
||||||
userToFollow: { browser: false, export: false, server: false },
|
userToFollow: { browser: false, export: false, server: false },
|
||||||
followedBy: { browser: false, export: false, server: false },
|
followedBy: { browser: false, export: false, server: false },
|
||||||
|
isCropping: { browser: false, export: false, server: false },
|
||||||
|
croppingElementId: { browser: false, export: false, server: false },
|
||||||
searchMatches: { browser: false, export: false, server: false },
|
searchMatches: { browser: false, export: false, server: false },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -17,13 +17,16 @@ import {
|
|||||||
hasBoundTextElement,
|
hasBoundTextElement,
|
||||||
isBindableElement,
|
isBindableElement,
|
||||||
isBoundToContainer,
|
isBoundToContainer,
|
||||||
|
isImageElement,
|
||||||
isTextElement,
|
isTextElement,
|
||||||
} from "./element/typeChecks";
|
} from "./element/typeChecks";
|
||||||
import type {
|
import type {
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
|
ExcalidrawImageElement,
|
||||||
ExcalidrawLinearElement,
|
ExcalidrawLinearElement,
|
||||||
ExcalidrawTextElement,
|
ExcalidrawTextElement,
|
||||||
NonDeleted,
|
NonDeleted,
|
||||||
|
Ordered,
|
||||||
OrderedExcalidrawElement,
|
OrderedExcalidrawElement,
|
||||||
SceneElementsMap,
|
SceneElementsMap,
|
||||||
} from "./element/types";
|
} from "./element/types";
|
||||||
@@ -626,6 +629,18 @@ export class AppStateChange implements Change<AppState> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
case "croppingElementId": {
|
||||||
|
const croppingElementId = nextAppState[key];
|
||||||
|
const element =
|
||||||
|
croppingElementId && nextElements.get(croppingElementId);
|
||||||
|
|
||||||
|
if (element && !element.isDeleted) {
|
||||||
|
visibleDifferenceFlag.value = true;
|
||||||
|
} else {
|
||||||
|
nextAppState[key] = null;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "editingGroupId":
|
case "editingGroupId":
|
||||||
const editingGroupId = nextAppState[key];
|
const editingGroupId = nextAppState[key];
|
||||||
|
|
||||||
@@ -756,6 +771,7 @@ export class AppStateChange implements Change<AppState> {
|
|||||||
selectedElementIds,
|
selectedElementIds,
|
||||||
editingLinearElementId,
|
editingLinearElementId,
|
||||||
selectedLinearElementId,
|
selectedLinearElementId,
|
||||||
|
croppingElementId,
|
||||||
...standaloneProps
|
...standaloneProps
|
||||||
} = delta as ObservedAppState;
|
} = delta as ObservedAppState;
|
||||||
|
|
||||||
@@ -779,7 +795,10 @@ export class AppStateChange implements Change<AppState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ElementPartial = Omit<ElementUpdate<OrderedExcalidrawElement>, "seed">;
|
type ElementPartial<T extends ExcalidrawElement = ExcalidrawElement> = Omit<
|
||||||
|
ElementUpdate<Ordered<T>>,
|
||||||
|
"seed"
|
||||||
|
>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Elements change is a low level primitive to capture a change between two sets of elements.
|
* Elements change is a low level primitive to capture a change between two sets of elements.
|
||||||
@@ -1216,6 +1235,18 @@ export class ElementsChange implements Change<SceneElementsMap> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isImageElement(element)) {
|
||||||
|
const _delta = delta as Delta<ElementPartial<ExcalidrawImageElement>>;
|
||||||
|
// we want to override `crop` only if modified so that we don't reset
|
||||||
|
// when undoing/redoing unrelated change
|
||||||
|
if (_delta.deleted.crop || _delta.inserted.crop) {
|
||||||
|
Object.assign(directlyApplicablePartial, {
|
||||||
|
// apply change verbatim
|
||||||
|
crop: _delta.inserted.crop ?? null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!flags.containsVisibleDifference) {
|
if (!flags.containsVisibleDifference) {
|
||||||
// strip away fractional as even if it would be different, it doesn't have to result in visible change
|
// strip away fractional as even if it would be different, it doesn't have to result in visible change
|
||||||
const { index, ...rest } = directlyApplicablePartial;
|
const { index, ...rest } = directlyApplicablePartial;
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ import {
|
|||||||
import { newElement, newLinearElement, newTextElement } from "./element";
|
import { newElement, newLinearElement, newTextElement } from "./element";
|
||||||
import type { NonDeletedExcalidrawElement } from "./element/types";
|
import type { NonDeletedExcalidrawElement } from "./element/types";
|
||||||
import { randomId } from "./random";
|
import { randomId } from "./random";
|
||||||
import type { AppState } from "./types";
|
|
||||||
import { selectSubtype } from "./element/subtypes";
|
|
||||||
|
|
||||||
export type ChartElements = readonly NonDeletedExcalidrawElement[];
|
export type ChartElements = readonly NonDeletedExcalidrawElement[];
|
||||||
|
|
||||||
@@ -27,8 +25,6 @@ export interface Spreadsheet {
|
|||||||
title: string | null;
|
title: string | null;
|
||||||
labels: string[] | null;
|
labels: string[] | null;
|
||||||
values: number[];
|
values: number[];
|
||||||
activeSubtypes?: AppState["activeSubtypes"];
|
|
||||||
customData?: AppState["customData"];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NOT_SPREADSHEET = "NOT_SPREADSHEET";
|
export const NOT_SPREADSHEET = "NOT_SPREADSHEET";
|
||||||
@@ -199,17 +195,13 @@ const chartXLabels = (
|
|||||||
groupId: string,
|
groupId: string,
|
||||||
backgroundColor: string,
|
backgroundColor: string,
|
||||||
): ChartElements => {
|
): ChartElements => {
|
||||||
const custom = selectSubtype(spreadsheet, "text");
|
|
||||||
return (
|
return (
|
||||||
spreadsheet.labels?.map((label, index) => {
|
spreadsheet.labels?.map((label, index) => {
|
||||||
return newTextElement({
|
return newTextElement({
|
||||||
groupIds: [groupId],
|
groupIds: [groupId],
|
||||||
backgroundColor,
|
backgroundColor,
|
||||||
...commonProps,
|
...commonProps,
|
||||||
text:
|
text: label.length > 8 ? `${label.slice(0, 5)}...` : label,
|
||||||
label.length > 8 && custom.subtype === undefined
|
|
||||||
? `${label.slice(0, 5)}...`
|
|
||||||
: label,
|
|
||||||
x: x + index * (BAR_WIDTH + BAR_GAP) + BAR_GAP * 2,
|
x: x + index * (BAR_WIDTH + BAR_GAP) + BAR_GAP * 2,
|
||||||
y: y + BAR_GAP / 2,
|
y: y + BAR_GAP / 2,
|
||||||
width: BAR_WIDTH,
|
width: BAR_WIDTH,
|
||||||
@@ -217,7 +209,6 @@ const chartXLabels = (
|
|||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
verticalAlign: "top",
|
verticalAlign: "top",
|
||||||
...custom,
|
|
||||||
});
|
});
|
||||||
}) || []
|
}) || []
|
||||||
);
|
);
|
||||||
@@ -238,7 +229,6 @@ const chartYLabels = (
|
|||||||
y: y - BAR_GAP,
|
y: y - BAR_GAP,
|
||||||
text: "0",
|
text: "0",
|
||||||
textAlign: "right",
|
textAlign: "right",
|
||||||
...selectSubtype(spreadsheet, "text"),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const maxYLabel = newTextElement({
|
const maxYLabel = newTextElement({
|
||||||
@@ -249,7 +239,6 @@ const chartYLabels = (
|
|||||||
y: y - BAR_HEIGHT - minYLabel.height / 2,
|
y: y - BAR_HEIGHT - minYLabel.height / 2,
|
||||||
text: Math.max(...spreadsheet.values).toLocaleString(),
|
text: Math.max(...spreadsheet.values).toLocaleString(),
|
||||||
textAlign: "right",
|
textAlign: "right",
|
||||||
...selectSubtype(spreadsheet, "text"),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return [minYLabel, maxYLabel];
|
return [minYLabel, maxYLabel];
|
||||||
@@ -272,7 +261,6 @@ const chartLines = (
|
|||||||
y,
|
y,
|
||||||
width: chartWidth,
|
width: chartWidth,
|
||||||
points: [pointFrom(0, 0), pointFrom(chartWidth, 0)],
|
points: [pointFrom(0, 0), pointFrom(chartWidth, 0)],
|
||||||
...selectSubtype(spreadsheet, "line"),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const yLine = newLinearElement({
|
const yLine = newLinearElement({
|
||||||
@@ -284,7 +272,6 @@ const chartLines = (
|
|||||||
y,
|
y,
|
||||||
height: chartHeight,
|
height: chartHeight,
|
||||||
points: [pointFrom(0, 0), pointFrom(0, -chartHeight)],
|
points: [pointFrom(0, 0), pointFrom(0, -chartHeight)],
|
||||||
...selectSubtype(spreadsheet, "line"),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const maxLine = newLinearElement({
|
const maxLine = newLinearElement({
|
||||||
@@ -298,7 +285,6 @@ const chartLines = (
|
|||||||
width: chartWidth,
|
width: chartWidth,
|
||||||
opacity: GRID_OPACITY,
|
opacity: GRID_OPACITY,
|
||||||
points: [pointFrom(0, 0), pointFrom(chartWidth, 0)],
|
points: [pointFrom(0, 0), pointFrom(chartWidth, 0)],
|
||||||
...selectSubtype(spreadsheet, "line"),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return [xLine, yLine, maxLine];
|
return [xLine, yLine, maxLine];
|
||||||
@@ -325,7 +311,6 @@ const chartBaseElements = (
|
|||||||
y: y - BAR_HEIGHT - BAR_GAP * 2 - DEFAULT_FONT_SIZE,
|
y: y - BAR_HEIGHT - BAR_GAP * 2 - DEFAULT_FONT_SIZE,
|
||||||
roundness: null,
|
roundness: null,
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
...selectSubtype(spreadsheet, "text"),
|
|
||||||
})
|
})
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
@@ -342,7 +327,6 @@ const chartBaseElements = (
|
|||||||
strokeColor: COLOR_PALETTE.black,
|
strokeColor: COLOR_PALETTE.black,
|
||||||
fillStyle: "solid",
|
fillStyle: "solid",
|
||||||
opacity: 6,
|
opacity: 6,
|
||||||
...selectSubtype(spreadsheet, "rectangle"),
|
|
||||||
})
|
})
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
@@ -375,7 +359,6 @@ const chartTypeBar = (
|
|||||||
y: y - barHeight - BAR_GAP,
|
y: y - barHeight - BAR_GAP,
|
||||||
width: BAR_WIDTH,
|
width: BAR_WIDTH,
|
||||||
height: barHeight,
|
height: barHeight,
|
||||||
...selectSubtype(spreadsheet, "rectangle"),
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -426,7 +409,6 @@ const chartTypeLine = (
|
|||||||
width: maxX - minX,
|
width: maxX - minX,
|
||||||
strokeWidth: 2,
|
strokeWidth: 2,
|
||||||
points: points as any,
|
points: points as any,
|
||||||
...selectSubtype(spreadsheet, "line"),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const dots = spreadsheet.values.map((value, index) => {
|
const dots = spreadsheet.values.map((value, index) => {
|
||||||
@@ -443,7 +425,6 @@ const chartTypeLine = (
|
|||||||
y: y + cy - BAR_GAP * 2,
|
y: y + cy - BAR_GAP * 2,
|
||||||
width: BAR_GAP,
|
width: BAR_GAP,
|
||||||
height: BAR_GAP,
|
height: BAR_GAP,
|
||||||
...selectSubtype(spreadsheet, "ellipse"),
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -461,7 +442,6 @@ const chartTypeLine = (
|
|||||||
strokeStyle: "dotted",
|
strokeStyle: "dotted",
|
||||||
opacity: GRID_OPACITY,
|
opacity: GRID_OPACITY,
|
||||||
points: [pointFrom(0, 0), pointFrom(0, cy)],
|
points: [pointFrom(0, 0), pointFrom(0, cy)],
|
||||||
...selectSubtype(spreadsheet, "line"),
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type {
|
|||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
NonDeletedExcalidrawElement,
|
NonDeletedExcalidrawElement,
|
||||||
} from "./element/types";
|
} from "./element/types";
|
||||||
import type { AppState, BinaryFiles } from "./types";
|
import type { BinaryFiles } from "./types";
|
||||||
import type { Spreadsheet } from "./charts";
|
import type { Spreadsheet } from "./charts";
|
||||||
import { tryParseSpreadsheet, VALID_SPREADSHEET } from "./charts";
|
import { tryParseSpreadsheet, VALID_SPREADSHEET } from "./charts";
|
||||||
import {
|
import {
|
||||||
@@ -333,7 +333,6 @@ const parseClipboardEvent = async (
|
|||||||
export const parseClipboard = async (
|
export const parseClipboard = async (
|
||||||
event: ClipboardEvent,
|
event: ClipboardEvent,
|
||||||
isPlainPaste = false,
|
isPlainPaste = false,
|
||||||
appState?: AppState,
|
|
||||||
): Promise<ClipboardData> => {
|
): Promise<ClipboardData> => {
|
||||||
const parsedEventData = await parseClipboardEvent(event, isPlainPaste);
|
const parsedEventData = await parseClipboardEvent(event, isPlainPaste);
|
||||||
|
|
||||||
@@ -350,10 +349,6 @@ export const parseClipboard = async (
|
|||||||
!isPlainPaste && parsePotentialSpreadsheet(parsedEventData.value);
|
!isPlainPaste && parsePotentialSpreadsheet(parsedEventData.value);
|
||||||
|
|
||||||
if (spreadsheetResult) {
|
if (spreadsheetResult) {
|
||||||
if ("spreadsheet" in spreadsheetResult) {
|
|
||||||
spreadsheetResult.spreadsheet.activeSubtypes = appState?.activeSubtypes;
|
|
||||||
spreadsheetResult.spreadsheet.customData = appState?.customData;
|
|
||||||
}
|
|
||||||
return spreadsheetResult;
|
return spreadsheetResult;
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
|||||||
@@ -21,12 +21,12 @@ import type { AppClassProperties, AppProps, UIAppState, Zoom } from "../types";
|
|||||||
import { capitalizeString, isTransparent } from "../utils";
|
import { capitalizeString, isTransparent } from "../utils";
|
||||||
import Stack from "./Stack";
|
import Stack from "./Stack";
|
||||||
import { ToolButton } from "./ToolButton";
|
import { ToolButton } from "./ToolButton";
|
||||||
import { SubtypeShapeActions } from "./Subtypes";
|
|
||||||
import { hasStrokeColor, toolIsArrow } from "../scene/comparisons";
|
import { hasStrokeColor, toolIsArrow } from "../scene/comparisons";
|
||||||
import { trackEvent } from "../analytics";
|
import { trackEvent } from "../analytics";
|
||||||
import {
|
import {
|
||||||
hasBoundTextElement,
|
hasBoundTextElement,
|
||||||
isElbowArrow,
|
isElbowArrow,
|
||||||
|
isImageElement,
|
||||||
isLinearElement,
|
isLinearElement,
|
||||||
isTextElement,
|
isTextElement,
|
||||||
} from "../element/typeChecks";
|
} from "../element/typeChecks";
|
||||||
@@ -128,6 +128,11 @@ export const SelectedShapeActions = ({
|
|||||||
isLinearElement(targetElements[0]) &&
|
isLinearElement(targetElements[0]) &&
|
||||||
!isElbowArrow(targetElements[0]);
|
!isElbowArrow(targetElements[0]);
|
||||||
|
|
||||||
|
const showCropEditorAction =
|
||||||
|
!appState.croppingElementId &&
|
||||||
|
targetElements.length === 1 &&
|
||||||
|
isImageElement(targetElements[0]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="panelColumn">
|
<div className="panelColumn">
|
||||||
<div>
|
<div>
|
||||||
@@ -137,7 +142,6 @@ export const SelectedShapeActions = ({
|
|||||||
{canChangeBackgroundColor(appState, targetElements) && (
|
{canChangeBackgroundColor(appState, targetElements) && (
|
||||||
<div>{renderAction("changeBackgroundColor")}</div>
|
<div>{renderAction("changeBackgroundColor")}</div>
|
||||||
)}
|
)}
|
||||||
<SubtypeShapeActions elements={targetElements} />
|
|
||||||
{showFillIcons && renderAction("changeFillStyle")}
|
{showFillIcons && renderAction("changeFillStyle")}
|
||||||
|
|
||||||
{(hasStrokeWidth(appState.activeTool.type) ||
|
{(hasStrokeWidth(appState.activeTool.type) ||
|
||||||
@@ -247,6 +251,7 @@ export const SelectedShapeActions = ({
|
|||||||
{renderAction("group")}
|
{renderAction("group")}
|
||||||
{renderAction("ungroup")}
|
{renderAction("ungroup")}
|
||||||
{showLinkIcon && renderAction("hyperlink")}
|
{showLinkIcon && renderAction("hyperlink")}
|
||||||
|
{showCropEditorAction && renderAction("cropEditor")}
|
||||||
{showLineEditorAction && renderAction("toggleLinearEditor")}
|
{showLineEditorAction && renderAction("toggleLinearEditor")}
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import {
|
|||||||
actionToggleElementLock,
|
actionToggleElementLock,
|
||||||
actionToggleLinearEditor,
|
actionToggleLinearEditor,
|
||||||
actionToggleObjectsSnapMode,
|
actionToggleObjectsSnapMode,
|
||||||
|
actionToggleCropEditor,
|
||||||
} from "../actions";
|
} from "../actions";
|
||||||
import { createRedoAction, createUndoAction } from "../actions/actionHistory";
|
import { createRedoAction, createUndoAction } from "../actions/actionHistory";
|
||||||
import { ActionManager } from "../actions/manager";
|
import { ActionManager } from "../actions/manager";
|
||||||
@@ -301,18 +302,6 @@ import { ContextMenu, CONTEXT_MENU_SEPARATOR } from "./ContextMenu";
|
|||||||
import LayerUI from "./LayerUI";
|
import LayerUI from "./LayerUI";
|
||||||
import { Toast } from "./Toast";
|
import { Toast } from "./Toast";
|
||||||
import { actionToggleViewMode } from "../actions/actionToggleViewMode";
|
import { actionToggleViewMode } from "../actions/actionToggleViewMode";
|
||||||
import type {
|
|
||||||
SubtypeLoadedCb,
|
|
||||||
SubtypeRecord,
|
|
||||||
SubtypePrepFn,
|
|
||||||
} from "../element/subtypes";
|
|
||||||
import {
|
|
||||||
checkRefreshOnSubtypeLoad,
|
|
||||||
isSubtypeAction,
|
|
||||||
prepareSubtype,
|
|
||||||
selectSubtype,
|
|
||||||
subtypeActionPredicate,
|
|
||||||
} from "../element/subtypes";
|
|
||||||
import {
|
import {
|
||||||
dataURLToFile,
|
dataURLToFile,
|
||||||
generateIdFromFile,
|
generateIdFromFile,
|
||||||
@@ -457,7 +446,19 @@ import {
|
|||||||
} from "../element/flowchart";
|
} from "../element/flowchart";
|
||||||
import { searchItemInFocusAtom } from "./SearchMenu";
|
import { searchItemInFocusAtom } from "./SearchMenu";
|
||||||
import type { LocalPoint, Radians } from "../../math";
|
import type { LocalPoint, Radians } from "../../math";
|
||||||
import { pointFrom, pointDistance, vector } from "../../math";
|
import {
|
||||||
|
clamp,
|
||||||
|
pointFrom,
|
||||||
|
pointDistance,
|
||||||
|
vector,
|
||||||
|
pointRotateRads,
|
||||||
|
vectorScale,
|
||||||
|
vectorFromPoint,
|
||||||
|
vectorSubtract,
|
||||||
|
vectorDot,
|
||||||
|
vectorNormalize,
|
||||||
|
} from "../../math";
|
||||||
|
import { cropElement } from "../element/cropElement";
|
||||||
|
|
||||||
const AppContext = React.createContext<AppClassProperties>(null!);
|
const AppContext = React.createContext<AppClassProperties>(null!);
|
||||||
const AppPropsContext = React.createContext<AppProps>(null!);
|
const AppPropsContext = React.createContext<AppProps>(null!);
|
||||||
@@ -601,6 +602,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
lastPointerUpEvent: React.PointerEvent<HTMLElement> | PointerEvent | null =
|
lastPointerUpEvent: React.PointerEvent<HTMLElement> | PointerEvent | null =
|
||||||
null;
|
null;
|
||||||
lastPointerMoveEvent: PointerEvent | null = null;
|
lastPointerMoveEvent: PointerEvent | null = null;
|
||||||
|
lastPointerMoveCoords: { x: number; y: number } | null = null;
|
||||||
lastViewportPosition = { x: 0, y: 0 };
|
lastViewportPosition = { x: 0, y: 0 };
|
||||||
|
|
||||||
animationFrameHandler = new AnimationFrameHandler();
|
animationFrameHandler = new AnimationFrameHandler();
|
||||||
@@ -722,7 +724,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
registerAction: (action: Action) => {
|
registerAction: (action: Action) => {
|
||||||
this.actionManager.registerAction(action);
|
this.actionManager.registerAction(action);
|
||||||
},
|
},
|
||||||
addSubtype: this.addSubtype,
|
|
||||||
refresh: this.refresh,
|
refresh: this.refresh,
|
||||||
setToast: this.setToast,
|
setToast: this.setToast,
|
||||||
id: this.id,
|
id: this.id,
|
||||||
@@ -759,19 +760,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.actionManager.registerAction(
|
this.actionManager.registerAction(
|
||||||
createRedoAction(this.history, this.store),
|
createRedoAction(this.history, this.store),
|
||||||
);
|
);
|
||||||
this.actionManager.registerActionPredicate(subtypeActionPredicate);
|
|
||||||
}
|
|
||||||
|
|
||||||
private addSubtype(record: SubtypeRecord, subtypePrepFn: SubtypePrepFn) {
|
|
||||||
const subtypeLoadedCb: SubtypeLoadedCb = (hasSubtype) => {
|
|
||||||
const elements = this.getSceneElementsIncludingDeleted();
|
|
||||||
// If there are any elements of the just-registered subtype,
|
|
||||||
// refresh the scene to re-render each such element.
|
|
||||||
if (checkRefreshOnSubtypeLoad(hasSubtype, elements)) {
|
|
||||||
this.refresh();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return prepareSubtype(record, subtypePrepFn, subtypeLoadedCb);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private onWindowMessage(event: MessageEvent) {
|
private onWindowMessage(event: MessageEvent) {
|
||||||
@@ -2977,7 +2965,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
// event else some browsers (FF...) will clear the clipboardData
|
// event else some browsers (FF...) will clear the clipboardData
|
||||||
// (something something security)
|
// (something something security)
|
||||||
let file = event?.clipboardData?.files[0];
|
let file = event?.clipboardData?.files[0];
|
||||||
const data = await parseClipboard(event, isPlainPaste, this.state);
|
const data = await parseClipboard(event, isPlainPaste);
|
||||||
if (!file && !isPlainPaste) {
|
if (!file && !isPlainPaste) {
|
||||||
if (data.mixedContent) {
|
if (data.mixedContent) {
|
||||||
return this.addElementsFromMixedContentPaste(data.mixedContent, {
|
return this.addElementsFromMixedContentPaste(data.mixedContent, {
|
||||||
@@ -3415,7 +3403,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
fontFamily: this.state.currentItemFontFamily,
|
fontFamily: this.state.currentItemFontFamily,
|
||||||
textAlign: DEFAULT_TEXT_ALIGN,
|
textAlign: DEFAULT_TEXT_ALIGN,
|
||||||
verticalAlign: DEFAULT_VERTICAL_ALIGN,
|
verticalAlign: DEFAULT_VERTICAL_ALIGN,
|
||||||
...selectSubtype(this.state, "text"),
|
|
||||||
locked: false,
|
locked: false,
|
||||||
};
|
};
|
||||||
const fontString = getFontString({
|
const fontString = getFontString({
|
||||||
@@ -3950,6 +3937,28 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!isInputLike(event.target)) {
|
if (!isInputLike(event.target)) {
|
||||||
|
if (
|
||||||
|
(event.key === KEYS.ESCAPE || event.key === KEYS.ENTER) &&
|
||||||
|
this.state.croppingElementId
|
||||||
|
) {
|
||||||
|
this.finishImageCropping();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedElements = getSelectedElements(
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
|
this.state,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
selectedElements.length === 1 &&
|
||||||
|
isImageElement(selectedElements[0]) &&
|
||||||
|
event.key === KEYS.ENTER
|
||||||
|
) {
|
||||||
|
this.startImageCropping(selectedElements[0]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
event.key === KEYS.ESCAPE &&
|
event.key === KEYS.ESCAPE &&
|
||||||
this.flowChartCreator.isCreatingChart
|
this.flowChartCreator.isCreatingChart
|
||||||
@@ -4934,7 +4943,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
const selectionShape = getSelectionBoxShape(
|
const selectionShape = getSelectionBoxShape(
|
||||||
element,
|
element,
|
||||||
this.scene.getNonDeletedElementsMap(),
|
this.scene.getNonDeletedElementsMap(),
|
||||||
this.getElementHitThreshold(),
|
isImageElement(element) ? 0 : this.getElementHitThreshold(),
|
||||||
);
|
);
|
||||||
|
|
||||||
return isPointInShape(pointFrom(x, y), selectionShape);
|
return isPointInShape(pointFrom(x, y), selectionShape);
|
||||||
@@ -5125,7 +5134,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
verticalAlign: parentCenterPosition
|
verticalAlign: parentCenterPosition
|
||||||
? VERTICAL_ALIGN.MIDDLE
|
? VERTICAL_ALIGN.MIDDLE
|
||||||
: DEFAULT_VERTICAL_ALIGN,
|
: DEFAULT_VERTICAL_ALIGN,
|
||||||
...selectSubtype(this.state, "text"),
|
|
||||||
containerId: shouldBindToContainer ? container?.id : undefined,
|
containerId: shouldBindToContainer ? container?.id : undefined,
|
||||||
groupIds: container?.groupIds ?? [],
|
groupIds: container?.groupIds ?? [],
|
||||||
lineHeight,
|
lineHeight,
|
||||||
@@ -5164,6 +5172,22 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private startImageCropping = (image: ExcalidrawImageElement) => {
|
||||||
|
this.store.shouldCaptureIncrement();
|
||||||
|
this.setState({
|
||||||
|
croppingElementId: image.id,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private finishImageCropping = () => {
|
||||||
|
if (this.state.croppingElementId) {
|
||||||
|
this.store.shouldCaptureIncrement();
|
||||||
|
this.setState({
|
||||||
|
croppingElementId: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private handleCanvasDoubleClick = (
|
private handleCanvasDoubleClick = (
|
||||||
event: React.MouseEvent<HTMLCanvasElement>,
|
event: React.MouseEvent<HTMLCanvasElement>,
|
||||||
) => {
|
) => {
|
||||||
@@ -5195,6 +5219,11 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (selectedElements.length === 1 && isImageElement(selectedElements[0])) {
|
||||||
|
this.startImageCropping(selectedElements[0]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
resetCursor(this.interactiveCanvas);
|
resetCursor(this.interactiveCanvas);
|
||||||
|
|
||||||
let { x: sceneX, y: sceneY } = viewportCoordsToSceneCoords(
|
let { x: sceneX, y: sceneY } = viewportCoordsToSceneCoords(
|
||||||
@@ -6764,11 +6793,24 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.device,
|
this.device,
|
||||||
);
|
);
|
||||||
if (elementWithTransformHandleType != null) {
|
if (elementWithTransformHandleType != null) {
|
||||||
this.setState({
|
if (
|
||||||
resizingElement: elementWithTransformHandleType.element,
|
elementWithTransformHandleType.transformHandleType === "rotation"
|
||||||
});
|
) {
|
||||||
pointerDownState.resize.handleType =
|
this.setState({
|
||||||
elementWithTransformHandleType.transformHandleType;
|
resizingElement: elementWithTransformHandleType.element,
|
||||||
|
});
|
||||||
|
pointerDownState.resize.handleType =
|
||||||
|
elementWithTransformHandleType.transformHandleType;
|
||||||
|
} else if (this.state.croppingElementId) {
|
||||||
|
pointerDownState.resize.handleType =
|
||||||
|
elementWithTransformHandleType.transformHandleType;
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
resizingElement: elementWithTransformHandleType.element,
|
||||||
|
});
|
||||||
|
pointerDownState.resize.handleType =
|
||||||
|
elementWithTransformHandleType.transformHandleType;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (selectedElements.length > 1) {
|
} else if (selectedElements.length > 1) {
|
||||||
pointerDownState.resize.handleType = getTransformHandleTypeFromCoords(
|
pointerDownState.resize.handleType = getTransformHandleTypeFromCoords(
|
||||||
@@ -6835,6 +6877,13 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
pointerDownState.origin.y,
|
pointerDownState.origin.y,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.state.croppingElementId &&
|
||||||
|
pointerDownState.hit.element?.id !== this.state.croppingElementId
|
||||||
|
) {
|
||||||
|
this.finishImageCropping();
|
||||||
|
}
|
||||||
|
|
||||||
if (pointerDownState.hit.element) {
|
if (pointerDownState.hit.element) {
|
||||||
// Early return if pointer is hitting link icon
|
// Early return if pointer is hitting link icon
|
||||||
const hitLinkElement = this.getElementLinkAtPosition(
|
const hitLinkElement = this.getElementLinkAtPosition(
|
||||||
@@ -7282,7 +7331,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
roughness: this.state.currentItemRoughness,
|
roughness: this.state.currentItemRoughness,
|
||||||
roundness: null,
|
roundness: null,
|
||||||
opacity: this.state.currentItemOpacity,
|
opacity: this.state.currentItemOpacity,
|
||||||
...selectSubtype(this.state, "image"),
|
|
||||||
locked: false,
|
locked: false,
|
||||||
frameId: topLayerFrame ? topLayerFrame.id : null,
|
frameId: topLayerFrame ? topLayerFrame.id : null,
|
||||||
});
|
});
|
||||||
@@ -7399,7 +7447,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
null,
|
null,
|
||||||
startArrowhead,
|
startArrowhead,
|
||||||
endArrowhead,
|
endArrowhead,
|
||||||
...selectSubtype(this.state, elementType),
|
|
||||||
locked: false,
|
locked: false,
|
||||||
frameId: topLayerFrame ? topLayerFrame.id : null,
|
frameId: topLayerFrame ? topLayerFrame.id : null,
|
||||||
elbowed: this.state.currentItemArrowType === ARROW_TYPE.elbow,
|
elbowed: this.state.currentItemArrowType === ARROW_TYPE.elbow,
|
||||||
@@ -7419,7 +7466,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.state.currentItemRoundness === "round"
|
this.state.currentItemRoundness === "round"
|
||||||
? { type: ROUNDNESS.PROPORTIONAL_RADIUS }
|
? { type: ROUNDNESS.PROPORTIONAL_RADIUS }
|
||||||
: null,
|
: null,
|
||||||
...selectSubtype(this.state, elementType),
|
|
||||||
locked: false,
|
locked: false,
|
||||||
frameId: topLayerFrame ? topLayerFrame.id : null,
|
frameId: topLayerFrame ? topLayerFrame.id : null,
|
||||||
});
|
});
|
||||||
@@ -7500,7 +7546,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
roughness: this.state.currentItemRoughness,
|
roughness: this.state.currentItemRoughness,
|
||||||
opacity: this.state.currentItemOpacity,
|
opacity: this.state.currentItemOpacity,
|
||||||
roundness: this.getCurrentItemRoundness(elementType),
|
roundness: this.getCurrentItemRoundness(elementType),
|
||||||
...selectSubtype(this.state, elementType),
|
|
||||||
locked: false,
|
locked: false,
|
||||||
frameId: topLayerFrame ? topLayerFrame.id : null,
|
frameId: topLayerFrame ? topLayerFrame.id : null,
|
||||||
} as const;
|
} as const;
|
||||||
@@ -7640,6 +7685,11 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
pointerDownState: PointerDownState,
|
pointerDownState: PointerDownState,
|
||||||
) {
|
) {
|
||||||
return withBatchedUpdatesThrottled((event: PointerEvent) => {
|
return withBatchedUpdatesThrottled((event: PointerEvent) => {
|
||||||
|
const pointerCoords = viewportCoordsToSceneCoords(event, this.state);
|
||||||
|
const lastPointerCoords =
|
||||||
|
this.lastPointerMoveCoords ?? pointerDownState.origin;
|
||||||
|
this.lastPointerMoveCoords = pointerCoords;
|
||||||
|
|
||||||
// We need to initialize dragOffsetXY only after we've updated
|
// We need to initialize dragOffsetXY only after we've updated
|
||||||
// `state.selectedElementIds` on pointerDown. Doing it here in pointerMove
|
// `state.selectedElementIds` on pointerDown. Doing it here in pointerMove
|
||||||
// event handler should hopefully ensure we're already working with
|
// event handler should hopefully ensure we're already working with
|
||||||
@@ -7662,8 +7712,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pointerCoords = viewportCoordsToSceneCoords(event, this.state);
|
|
||||||
|
|
||||||
if (isEraserActive(this.state)) {
|
if (isEraserActive(this.state)) {
|
||||||
this.handleEraser(event, pointerDownState, pointerCoords);
|
this.handleEraser(event, pointerDownState, pointerCoords);
|
||||||
return;
|
return;
|
||||||
@@ -7700,6 +7748,9 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
if (pointerDownState.resize.isResizing) {
|
if (pointerDownState.resize.isResizing) {
|
||||||
pointerDownState.lastCoords.x = pointerCoords.x;
|
pointerDownState.lastCoords.x = pointerCoords.x;
|
||||||
pointerDownState.lastCoords.y = pointerCoords.y;
|
pointerDownState.lastCoords.y = pointerCoords.y;
|
||||||
|
if (this.maybeHandleCrop(pointerDownState, event)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (this.maybeHandleResize(pointerDownState, event)) {
|
if (this.maybeHandleResize(pointerDownState, event)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -7873,6 +7924,96 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #region move crop region
|
||||||
|
if (this.state.croppingElementId) {
|
||||||
|
const croppingElement = this.scene
|
||||||
|
.getNonDeletedElementsMap()
|
||||||
|
.get(this.state.croppingElementId);
|
||||||
|
|
||||||
|
if (
|
||||||
|
croppingElement &&
|
||||||
|
isImageElement(croppingElement) &&
|
||||||
|
croppingElement.crop !== null &&
|
||||||
|
pointerDownState.hit.element === croppingElement
|
||||||
|
) {
|
||||||
|
const crop = croppingElement.crop;
|
||||||
|
const image =
|
||||||
|
isInitializedImageElement(croppingElement) &&
|
||||||
|
this.imageCache.get(croppingElement.fileId)?.image;
|
||||||
|
|
||||||
|
if (image && !(image instanceof Promise)) {
|
||||||
|
const instantDragOffset = vectorScale(
|
||||||
|
vector(
|
||||||
|
pointerCoords.x - lastPointerCoords.x,
|
||||||
|
pointerCoords.y - lastPointerCoords.y,
|
||||||
|
),
|
||||||
|
Math.max(this.state.zoom.value, 2),
|
||||||
|
);
|
||||||
|
|
||||||
|
const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(
|
||||||
|
croppingElement,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
|
||||||
|
const topLeft = vectorFromPoint(
|
||||||
|
pointRotateRads(
|
||||||
|
pointFrom(x1, y1),
|
||||||
|
pointFrom(cx, cy),
|
||||||
|
croppingElement.angle,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const topRight = vectorFromPoint(
|
||||||
|
pointRotateRads(
|
||||||
|
pointFrom(x2, y1),
|
||||||
|
pointFrom(cx, cy),
|
||||||
|
croppingElement.angle,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const bottomLeft = vectorFromPoint(
|
||||||
|
pointRotateRads(
|
||||||
|
pointFrom(x1, y2),
|
||||||
|
pointFrom(cx, cy),
|
||||||
|
croppingElement.angle,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const topEdge = vectorNormalize(
|
||||||
|
vectorSubtract(topRight, topLeft),
|
||||||
|
);
|
||||||
|
const leftEdge = vectorNormalize(
|
||||||
|
vectorSubtract(bottomLeft, topLeft),
|
||||||
|
);
|
||||||
|
|
||||||
|
// project instantDrafOffset onto leftEdge and topEdge to decompose
|
||||||
|
const offsetVector = vector(
|
||||||
|
vectorDot(instantDragOffset, topEdge),
|
||||||
|
vectorDot(instantDragOffset, leftEdge),
|
||||||
|
);
|
||||||
|
|
||||||
|
const nextCrop = {
|
||||||
|
...crop,
|
||||||
|
x: clamp(
|
||||||
|
crop.x -
|
||||||
|
offsetVector[0] * Math.sign(croppingElement.scale[0]),
|
||||||
|
0,
|
||||||
|
image.naturalWidth - crop.width,
|
||||||
|
),
|
||||||
|
y: clamp(
|
||||||
|
crop.y -
|
||||||
|
offsetVector[1] * Math.sign(croppingElement.scale[1]),
|
||||||
|
0,
|
||||||
|
image.naturalHeight - crop.height,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
mutateElement(croppingElement, {
|
||||||
|
crop: nextCrop,
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Snap cache *must* be synchronously popuplated before initial drag,
|
// Snap cache *must* be synchronously popuplated before initial drag,
|
||||||
// otherwise the first drag even will not snap, causing a jump before
|
// otherwise the first drag even will not snap, causing a jump before
|
||||||
// it snaps to its position if previously snapped already.
|
// it snaps to its position if previously snapped already.
|
||||||
@@ -8006,6 +8147,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.maybeCacheVisibleGaps(event, selectedElements, true);
|
this.maybeCacheVisibleGaps(event, selectedElements, true);
|
||||||
this.maybeCacheReferenceSnapPoints(event, selectedElements, true);
|
this.maybeCacheReferenceSnapPoints(event, selectedElements, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8254,15 +8396,18 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
const {
|
const {
|
||||||
newElement,
|
newElement,
|
||||||
resizingElement,
|
resizingElement,
|
||||||
|
croppingElementId,
|
||||||
multiElement,
|
multiElement,
|
||||||
activeTool,
|
activeTool,
|
||||||
isResizing,
|
isResizing,
|
||||||
isRotating,
|
isRotating,
|
||||||
|
isCropping,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
this.setState((prevState) => ({
|
this.setState((prevState) => ({
|
||||||
isResizing: false,
|
isResizing: false,
|
||||||
isRotating: false,
|
isRotating: false,
|
||||||
|
isCropping: false,
|
||||||
resizingElement: null,
|
resizingElement: null,
|
||||||
selectionElement: null,
|
selectionElement: null,
|
||||||
frameToHighlight: null,
|
frameToHighlight: null,
|
||||||
@@ -8272,6 +8417,8 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
originSnapOffset: null,
|
originSnapOffset: null,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
this.lastPointerMoveCoords = null;
|
||||||
|
|
||||||
SnapCache.setReferenceSnapPoints(null);
|
SnapCache.setReferenceSnapPoints(null);
|
||||||
SnapCache.setVisibleGaps(null);
|
SnapCache.setVisibleGaps(null);
|
||||||
|
|
||||||
@@ -8754,6 +8901,20 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// click outside the cropping region to exit
|
||||||
|
if (
|
||||||
|
// not in the cropping mode at all
|
||||||
|
!croppingElementId ||
|
||||||
|
// in the cropping mode
|
||||||
|
(croppingElementId &&
|
||||||
|
// not cropping and no hit element
|
||||||
|
((!hitElement && !isCropping) ||
|
||||||
|
// hitting something else
|
||||||
|
(hitElement && hitElement.id !== croppingElementId)))
|
||||||
|
) {
|
||||||
|
this.finishImageCropping();
|
||||||
|
}
|
||||||
|
|
||||||
const pointerStart = this.lastPointerDownEvent;
|
const pointerStart = this.lastPointerDownEvent;
|
||||||
const pointerEnd = this.lastPointerUpEvent || this.lastPointerMoveEvent;
|
const pointerEnd = this.lastPointerUpEvent || this.lastPointerMoveEvent;
|
||||||
|
|
||||||
@@ -9009,7 +9170,12 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.store.shouldCaptureIncrement();
|
this.store.shouldCaptureIncrement();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pointerDownState.drag.hasOccurred || isResizing || isRotating) {
|
if (
|
||||||
|
pointerDownState.drag.hasOccurred ||
|
||||||
|
isResizing ||
|
||||||
|
isRotating ||
|
||||||
|
isCropping
|
||||||
|
) {
|
||||||
// We only allow binding via linear elements, specifically via dragging
|
// We only allow binding via linear elements, specifically via dragging
|
||||||
// the endpoints ("start" or "end").
|
// the endpoints ("start" or "end").
|
||||||
const linearElements = this.scene
|
const linearElements = this.scene
|
||||||
@@ -9223,7 +9389,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
/**
|
/**
|
||||||
* inserts image into elements array and rerenders
|
* inserts image into elements array and rerenders
|
||||||
*/
|
*/
|
||||||
private insertImageElement = async (
|
insertImageElement = async (
|
||||||
imageElement: ExcalidrawImageElement,
|
imageElement: ExcalidrawImageElement,
|
||||||
imageFile: File,
|
imageFile: File,
|
||||||
showCursorImagePreview?: boolean,
|
showCursorImagePreview?: boolean,
|
||||||
@@ -9376,7 +9542,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private initializeImageDimensions = (
|
initializeImageDimensions = (
|
||||||
imageElement: ExcalidrawImageElement,
|
imageElement: ExcalidrawImageElement,
|
||||||
forceNaturalSize = false,
|
forceNaturalSize = false,
|
||||||
) => {
|
) => {
|
||||||
@@ -9424,7 +9590,13 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
const x = imageElement.x + imageElement.width / 2 - width / 2;
|
const x = imageElement.x + imageElement.width / 2 - width / 2;
|
||||||
const y = imageElement.y + imageElement.height / 2 - height / 2;
|
const y = imageElement.y + imageElement.height / 2 - height / 2;
|
||||||
|
|
||||||
mutateElement(imageElement, { x, y, width, height });
|
mutateElement(imageElement, {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
crop: null,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -9963,6 +10135,83 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private maybeHandleCrop = (
|
||||||
|
pointerDownState: PointerDownState,
|
||||||
|
event: MouseEvent | KeyboardEvent,
|
||||||
|
): boolean => {
|
||||||
|
// to crop, we must already be in the cropping mode, where croppingElement has been set
|
||||||
|
if (!this.state.croppingElementId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const transformHandleType = pointerDownState.resize.handleType;
|
||||||
|
const pointerCoords = pointerDownState.lastCoords;
|
||||||
|
const [x, y] = getGridPoint(
|
||||||
|
pointerCoords.x - pointerDownState.resize.offset.x,
|
||||||
|
pointerCoords.y - pointerDownState.resize.offset.y,
|
||||||
|
this.getEffectiveGridSize(),
|
||||||
|
);
|
||||||
|
|
||||||
|
const croppingElement = this.scene
|
||||||
|
.getNonDeletedElementsMap()
|
||||||
|
.get(this.state.croppingElementId);
|
||||||
|
|
||||||
|
if (
|
||||||
|
transformHandleType &&
|
||||||
|
croppingElement &&
|
||||||
|
isImageElement(croppingElement)
|
||||||
|
) {
|
||||||
|
const croppingAtStateStart = pointerDownState.originalElements.get(
|
||||||
|
croppingElement.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
const image =
|
||||||
|
isInitializedImageElement(croppingElement) &&
|
||||||
|
this.imageCache.get(croppingElement.fileId)?.image;
|
||||||
|
|
||||||
|
if (
|
||||||
|
croppingAtStateStart &&
|
||||||
|
isImageElement(croppingAtStateStart) &&
|
||||||
|
image &&
|
||||||
|
!(image instanceof Promise)
|
||||||
|
) {
|
||||||
|
mutateElement(
|
||||||
|
croppingElement,
|
||||||
|
cropElement(
|
||||||
|
croppingElement,
|
||||||
|
transformHandleType,
|
||||||
|
image.naturalWidth,
|
||||||
|
image.naturalHeight,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
event.shiftKey
|
||||||
|
? croppingAtStateStart.width / croppingAtStateStart.height
|
||||||
|
: undefined,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
updateBoundElements(
|
||||||
|
croppingElement,
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
|
{
|
||||||
|
oldSize: {
|
||||||
|
width: croppingElement.width,
|
||||||
|
height: croppingElement.height,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
isCropping: transformHandleType && transformHandleType !== "rotation",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
private maybeHandleResize = (
|
private maybeHandleResize = (
|
||||||
pointerDownState: PointerDownState,
|
pointerDownState: PointerDownState,
|
||||||
event: MouseEvent | KeyboardEvent,
|
event: MouseEvent | KeyboardEvent,
|
||||||
@@ -9979,7 +10228,9 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
// Frames cannot be rotated.
|
// Frames cannot be rotated.
|
||||||
(selectedFrames.length > 0 && transformHandleType === "rotation") ||
|
(selectedFrames.length > 0 && transformHandleType === "rotation") ||
|
||||||
// Elbow arrows cannot be transformed (resized or rotated).
|
// Elbow arrows cannot be transformed (resized or rotated).
|
||||||
(selectedElements.length === 1 && isElbowArrow(selectedElements[0]))
|
(selectedElements.length === 1 && isElbowArrow(selectedElements[0])) ||
|
||||||
|
// Do not resize when in crop mode
|
||||||
|
this.state.croppingElementId
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -10080,39 +10331,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
|
|
||||||
const elementsToHighlight = new Set<ExcalidrawElement>();
|
const elementsToHighlight = new Set<ExcalidrawElement>();
|
||||||
selectedFrames.forEach((frame) => {
|
selectedFrames.forEach((frame) => {
|
||||||
const elementsInFrame = getFrameChildren(
|
|
||||||
this.scene.getNonDeletedElements(),
|
|
||||||
frame.id,
|
|
||||||
);
|
|
||||||
|
|
||||||
// keep elements' positions relative to their frames on frames resizing
|
|
||||||
if (transformHandleType) {
|
|
||||||
if (transformHandleType.includes("w")) {
|
|
||||||
elementsInFrame.forEach((element) => {
|
|
||||||
mutateElement(element, {
|
|
||||||
x:
|
|
||||||
frame.x +
|
|
||||||
(frameElementsOffsetsMap.get(frame.id + element.id)?.x || 0),
|
|
||||||
y:
|
|
||||||
frame.y +
|
|
||||||
(frameElementsOffsetsMap.get(frame.id + element.id)?.y || 0),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (transformHandleType.includes("n")) {
|
|
||||||
elementsInFrame.forEach((element) => {
|
|
||||||
mutateElement(element, {
|
|
||||||
x:
|
|
||||||
frame.x +
|
|
||||||
(frameElementsOffsetsMap.get(frame.id + element.id)?.x || 0),
|
|
||||||
y:
|
|
||||||
frame.y +
|
|
||||||
(frameElementsOffsetsMap.get(frame.id + element.id)?.y || 0),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getElementsInResizingFrame(
|
getElementsInResizingFrame(
|
||||||
this.scene.getNonDeletedElements(),
|
this.scene.getNonDeletedElements(),
|
||||||
frame,
|
frame,
|
||||||
@@ -10133,29 +10351,6 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
|
|
||||||
private getContextMenuItems = (
|
private getContextMenuItems = (
|
||||||
type: "canvas" | "element",
|
type: "canvas" | "element",
|
||||||
): ContextMenuItems => {
|
|
||||||
const subtype: ContextMenuItems = [];
|
|
||||||
this.actionManager
|
|
||||||
.filterActions(isSubtypeAction)
|
|
||||||
.forEach(
|
|
||||||
(action) =>
|
|
||||||
this.actionManager.isActionEnabled(action, { data: {} }) &&
|
|
||||||
subtype.push(action),
|
|
||||||
);
|
|
||||||
if (subtype.length > 0) {
|
|
||||||
subtype.push(CONTEXT_MENU_SEPARATOR);
|
|
||||||
}
|
|
||||||
const standard: ContextMenuItems = this._getContextMenuItems(type).filter(
|
|
||||||
(item) =>
|
|
||||||
!item ||
|
|
||||||
item === CONTEXT_MENU_SEPARATOR ||
|
|
||||||
this.actionManager.isActionEnabled(item, { noPredicates: true }),
|
|
||||||
);
|
|
||||||
return [...subtype, ...standard];
|
|
||||||
};
|
|
||||||
|
|
||||||
private _getContextMenuItems = (
|
|
||||||
type: "canvas" | "element",
|
|
||||||
): ContextMenuItems => {
|
): ContextMenuItems => {
|
||||||
const options: ContextMenuItems = [];
|
const options: ContextMenuItems = [];
|
||||||
|
|
||||||
@@ -10210,6 +10405,8 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
actionSelectAllElementsInFrame,
|
actionSelectAllElementsInFrame,
|
||||||
actionRemoveAllElementsFromFrame,
|
actionRemoveAllElementsFromFrame,
|
||||||
CONTEXT_MENU_SEPARATOR,
|
CONTEXT_MENU_SEPARATOR,
|
||||||
|
actionToggleCropEditor,
|
||||||
|
CONTEXT_MENU_SEPARATOR,
|
||||||
...options,
|
...options,
|
||||||
CONTEXT_MENU_SEPARATOR,
|
CONTEXT_MENU_SEPARATOR,
|
||||||
actionCopyStyles,
|
actionCopyStyles,
|
||||||
|
|||||||
@@ -279,6 +279,7 @@ function CommandPaletteInner({
|
|||||||
actionManager.actions.increaseFontSize,
|
actionManager.actions.increaseFontSize,
|
||||||
actionManager.actions.decreaseFontSize,
|
actionManager.actions.decreaseFontSize,
|
||||||
actionManager.actions.toggleLinearEditor,
|
actionManager.actions.toggleLinearEditor,
|
||||||
|
actionManager.actions.cropEditor,
|
||||||
actionLink,
|
actionLink,
|
||||||
].map((action: Action) =>
|
].map((action: Action) =>
|
||||||
actionToCommand(
|
actionToCommand(
|
||||||
|
|||||||
@@ -222,6 +222,16 @@ export const HelpDialog = ({ onClose }: { onClose?: () => void }) => {
|
|||||||
]}
|
]}
|
||||||
isOr={false}
|
isOr={false}
|
||||||
/>
|
/>
|
||||||
|
<Shortcut
|
||||||
|
label={t("helpDialog.cropStart")}
|
||||||
|
shortcuts={[t("helpDialog.doubleClick"), getShortcutKey("Enter")]}
|
||||||
|
isOr={true}
|
||||||
|
/>
|
||||||
|
<Shortcut
|
||||||
|
label={t("helpDialog.cropFinish")}
|
||||||
|
shortcuts={[getShortcutKey("Enter"), getShortcutKey("Escape")]}
|
||||||
|
isOr={true}
|
||||||
|
/>
|
||||||
<Shortcut label={t("toolBar.lock")} shortcuts={[KEYS.Q]} />
|
<Shortcut label={t("toolBar.lock")} shortcuts={[KEYS.Q]} />
|
||||||
<Shortcut
|
<Shortcut
|
||||||
label={t("helpDialog.preventBinding")}
|
label={t("helpDialog.preventBinding")}
|
||||||
|
|||||||
@@ -100,6 +100,14 @@ const getHints = ({
|
|||||||
return t("hints.text_editing");
|
return t("hints.text_editing");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (appState.croppingElementId) {
|
||||||
|
return t("hints.leaveCropEditor");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedElements.length === 1 && isImageElement(selectedElements[0])) {
|
||||||
|
return t("hints.enterCropEditor");
|
||||||
|
}
|
||||||
|
|
||||||
if (activeTool.type === "selection") {
|
if (activeTool.type === "selection") {
|
||||||
if (
|
if (
|
||||||
appState.selectionElement &&
|
appState.selectionElement &&
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ import { EyeDropper, activeEyeDropperAtom } from "./EyeDropper";
|
|||||||
import { mutateElement } from "../element/mutateElement";
|
import { mutateElement } from "../element/mutateElement";
|
||||||
import { ShapeCache } from "../scene/ShapeCache";
|
import { ShapeCache } from "../scene/ShapeCache";
|
||||||
import Scene from "../scene/Scene";
|
import Scene from "../scene/Scene";
|
||||||
import { SubtypeToggles } from "./Subtypes";
|
|
||||||
import { LaserPointerButton } from "./LaserPointerButton";
|
import { LaserPointerButton } from "./LaserPointerButton";
|
||||||
import { TTDDialog } from "./TTDDialog/TTDDialog";
|
import { TTDDialog } from "./TTDDialog/TTDDialog";
|
||||||
import { Stats } from "./Stats";
|
import { Stats } from "./Stats";
|
||||||
@@ -300,7 +299,6 @@ const LayerUI = ({
|
|||||||
/>
|
/>
|
||||||
</Stack.Row>
|
</Stack.Row>
|
||||||
</Island>
|
</Island>
|
||||||
<SubtypeToggles />
|
|
||||||
{isCollaborating && (
|
{isCollaborating && (
|
||||||
<Island
|
<Island
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import { PenModeButton } from "./PenModeButton";
|
|||||||
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";
|
||||||
import { SubtypeToggles } from "./Subtypes";
|
|
||||||
|
|
||||||
type MobileMenuProps = {
|
type MobileMenuProps = {
|
||||||
appState: UIAppState;
|
appState: UIAppState;
|
||||||
@@ -90,7 +89,6 @@ export const MobileMenu = ({
|
|||||||
/>
|
/>
|
||||||
</Stack.Row>
|
</Stack.Row>
|
||||||
</Island>
|
</Island>
|
||||||
<SubtypeToggles />
|
|
||||||
{renderTopRightUI && renderTopRightUI(true, appState)}
|
{renderTopRightUI && renderTopRightUI(true, appState)}
|
||||||
<div className="mobile-misc-tools-container">
|
<div className="mobile-misc-tools-container">
|
||||||
{!appState.viewModeEnabled && (
|
{!appState.viewModeEnabled && (
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import React, { useLayoutEffect, useRef, useState } from "react";
|
|||||||
import { trackEvent } from "../analytics";
|
import { trackEvent } from "../analytics";
|
||||||
import type { ChartElements, Spreadsheet } from "../charts";
|
import type { ChartElements, Spreadsheet } from "../charts";
|
||||||
import { renderSpreadsheet } from "../charts";
|
import { renderSpreadsheet } from "../charts";
|
||||||
import type { ChartType, ElementsMap } from "../element/types";
|
import type { ChartType } from "../element/types";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import { exportToSvg } from "../scene/export";
|
import { exportToSvg } from "../scene/export";
|
||||||
import type { UIAppState } from "../types";
|
import type { UIAppState } from "../types";
|
||||||
@@ -11,12 +11,6 @@ import { useApp } from "./App";
|
|||||||
import { Dialog } from "./Dialog";
|
import { Dialog } from "./Dialog";
|
||||||
|
|
||||||
import "./PasteChartDialog.scss";
|
import "./PasteChartDialog.scss";
|
||||||
import { ensureSubtypesLoaded } from "../element/subtypes";
|
|
||||||
import { isTextElement } from "../element";
|
|
||||||
import {
|
|
||||||
getContainerElement,
|
|
||||||
redrawTextBoundingBox,
|
|
||||||
} from "../element/textElement";
|
|
||||||
|
|
||||||
type OnInsertChart = (chartType: ChartType, elements: ChartElements) => void;
|
type OnInsertChart = (chartType: ChartType, elements: ChartElements) => void;
|
||||||
|
|
||||||
@@ -32,64 +26,41 @@ const ChartPreviewBtn = (props: {
|
|||||||
);
|
);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
|
if (!props.spreadsheet) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const elements = renderSpreadsheet(
|
||||||
|
props.chartType,
|
||||||
|
props.spreadsheet,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
setChartElements(elements);
|
||||||
let svg: SVGSVGElement;
|
let svg: SVGSVGElement;
|
||||||
const previewNode = previewRef.current!;
|
const previewNode = previewRef.current!;
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
(async () => {
|
svg = await exportToSvg(
|
||||||
let elements: ChartElements;
|
elements,
|
||||||
await ensureSubtypesLoaded(
|
{
|
||||||
props.spreadsheet?.activeSubtypes ?? [],
|
exportBackground: false,
|
||||||
() => {
|
viewBackgroundColor: oc.white,
|
||||||
if (!props.spreadsheet) {
|
},
|
||||||
return;
|
null, // files
|
||||||
}
|
);
|
||||||
|
svg.querySelector(".style-fonts")?.remove();
|
||||||
|
previewNode.replaceChildren();
|
||||||
|
previewNode.appendChild(svg);
|
||||||
|
|
||||||
elements = renderSpreadsheet(
|
if (props.selected) {
|
||||||
props.chartType,
|
(previewNode.parentNode as HTMLDivElement).focus();
|
||||||
props.spreadsheet,
|
}
|
||||||
0,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
const elementsMap = new Map() as ElementsMap;
|
|
||||||
for (const element of elements) {
|
|
||||||
if (!element.isDeleted) {
|
|
||||||
elementsMap.set(element.id, element);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
elements.forEach(
|
|
||||||
(el) =>
|
|
||||||
isTextElement(el) &&
|
|
||||||
redrawTextBoundingBox(
|
|
||||||
el,
|
|
||||||
getContainerElement(el, elementsMap),
|
|
||||||
elementsMap,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
setChartElements(elements);
|
|
||||||
},
|
|
||||||
).then(async () => {
|
|
||||||
svg = await exportToSvg(
|
|
||||||
elements,
|
|
||||||
{
|
|
||||||
exportBackground: false,
|
|
||||||
viewBackgroundColor: oc.white,
|
|
||||||
},
|
|
||||||
null, // files
|
|
||||||
);
|
|
||||||
svg.querySelector(".style-fonts")?.remove();
|
|
||||||
previewNode.replaceChildren();
|
|
||||||
previewNode.appendChild(svg);
|
|
||||||
|
|
||||||
if (props.selected) {
|
|
||||||
(previewNode.parentNode as HTMLDivElement).focus();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
previewNode.replaceChildren();
|
|
||||||
};
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
previewNode.replaceChildren();
|
||||||
|
};
|
||||||
}, [props.spreadsheet, props.chartType, props.selected]);
|
}, [props.spreadsheet, props.chartType, props.selected]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,188 +0,0 @@
|
|||||||
import { getShortcutKey, updateActiveTool } from "../utils";
|
|
||||||
import { t } from "../i18n";
|
|
||||||
import type { Action } from "../actions/types";
|
|
||||||
import { makeCustomActionName } from "../actions/types";
|
|
||||||
import clsx from "clsx";
|
|
||||||
import type { Subtype, SubtypeRecord } from "../element/subtypes";
|
|
||||||
import {
|
|
||||||
getSubtypeNames,
|
|
||||||
hasAlwaysEnabledActions,
|
|
||||||
isSubtypeAction,
|
|
||||||
isValidSubtype,
|
|
||||||
subtypeCollides,
|
|
||||||
} from "../element/subtypes";
|
|
||||||
import type { ExcalidrawElement, Theme } from "../element/types";
|
|
||||||
import {
|
|
||||||
useExcalidrawActionManager,
|
|
||||||
useExcalidrawContainer,
|
|
||||||
useExcalidrawSetAppState,
|
|
||||||
} from "./App";
|
|
||||||
import type { ContextMenuItems } from "./ContextMenu";
|
|
||||||
import { Island } from "./Island";
|
|
||||||
|
|
||||||
export const SubtypeButton = (
|
|
||||||
subtype: Subtype,
|
|
||||||
parentType: SubtypeRecord["parents"][number],
|
|
||||||
icon: ({ theme }: { theme: Theme }) => JSX.Element,
|
|
||||||
key?: string,
|
|
||||||
) => {
|
|
||||||
const title = key !== undefined ? ` - ${getShortcutKey(key)}` : "";
|
|
||||||
const keyTest: Action["keyTest"] =
|
|
||||||
key !== undefined ? (event) => event.code === `Key${key}` : undefined;
|
|
||||||
const subtypeAction: Action = {
|
|
||||||
name: makeCustomActionName(subtype),
|
|
||||||
label: t(`toolBar.${subtype}`),
|
|
||||||
trackEvent: false,
|
|
||||||
predicate: (...rest) => rest[4]?.subtype === subtype,
|
|
||||||
perform: (elements, appState) => {
|
|
||||||
const inactive = !appState.activeSubtypes?.includes(subtype) ?? true;
|
|
||||||
const activeSubtypes: Subtype[] = [];
|
|
||||||
if (appState.activeSubtypes) {
|
|
||||||
activeSubtypes.push(...appState.activeSubtypes);
|
|
||||||
}
|
|
||||||
let activated = false;
|
|
||||||
if (inactive) {
|
|
||||||
// Ensure `element.subtype` is well-defined
|
|
||||||
if (!subtypeCollides(subtype, activeSubtypes)) {
|
|
||||||
activeSubtypes.push(subtype);
|
|
||||||
activated = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Can only be active if appState.activeSubtypes is defined
|
|
||||||
// and contains subtype.
|
|
||||||
activeSubtypes.splice(activeSubtypes.indexOf(subtype), 1);
|
|
||||||
}
|
|
||||||
const type =
|
|
||||||
appState.activeTool.type !== "custom" &&
|
|
||||||
isValidSubtype(subtype, appState.activeTool.type)
|
|
||||||
? appState.activeTool.type
|
|
||||||
: parentType;
|
|
||||||
const activeTool = !inactive
|
|
||||||
? appState.activeTool
|
|
||||||
: updateActiveTool(appState, { type });
|
|
||||||
const selectedElementIds = activated ? {} : appState.selectedElementIds;
|
|
||||||
const selectedGroupIds = activated ? {} : appState.selectedGroupIds;
|
|
||||||
|
|
||||||
return {
|
|
||||||
appState: {
|
|
||||||
...appState,
|
|
||||||
activeSubtypes,
|
|
||||||
selectedElementIds,
|
|
||||||
selectedGroupIds,
|
|
||||||
activeTool,
|
|
||||||
},
|
|
||||||
storeAction: "capture",
|
|
||||||
};
|
|
||||||
},
|
|
||||||
keyTest,
|
|
||||||
PanelComponent: ({ elements, appState, updateData, data }) => (
|
|
||||||
<button
|
|
||||||
className={clsx("ToolIcon_type_button", "ToolIcon_type_button--show", {
|
|
||||||
ToolIcon: true,
|
|
||||||
"ToolIcon--selected":
|
|
||||||
appState.activeSubtypes !== undefined &&
|
|
||||||
appState.activeSubtypes.includes(subtype),
|
|
||||||
"ToolIcon--plain": true,
|
|
||||||
})}
|
|
||||||
title={`${t(`toolBar.${subtype}`)}${title}`}
|
|
||||||
aria-label={t(`toolBar.${subtype}`)}
|
|
||||||
onClick={() => {
|
|
||||||
updateData(null);
|
|
||||||
}}
|
|
||||||
onContextMenu={
|
|
||||||
data && "onContextMenu" in data
|
|
||||||
? (event: React.MouseEvent) => {
|
|
||||||
if (
|
|
||||||
appState.activeSubtypes === undefined ||
|
|
||||||
(appState.activeSubtypes !== undefined &&
|
|
||||||
!appState.activeSubtypes.includes(subtype))
|
|
||||||
) {
|
|
||||||
updateData(null);
|
|
||||||
}
|
|
||||||
data.onContextMenu(event, subtype);
|
|
||||||
}
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{
|
|
||||||
<div className="ToolIcon__icon" aria-hidden="true">
|
|
||||||
{icon.call(this, { theme: appState.theme })}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</button>
|
|
||||||
),
|
|
||||||
};
|
|
||||||
if (key === "") {
|
|
||||||
delete subtypeAction.keyTest;
|
|
||||||
}
|
|
||||||
return subtypeAction;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SubtypeToggles = () => {
|
|
||||||
const am = useExcalidrawActionManager();
|
|
||||||
const { container } = useExcalidrawContainer();
|
|
||||||
const setAppState = useExcalidrawSetAppState();
|
|
||||||
|
|
||||||
const onContextMenu = (
|
|
||||||
event: React.MouseEvent<HTMLButtonElement>,
|
|
||||||
subtype: string,
|
|
||||||
) => {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
const { top: offsetTop, left: offsetLeft } =
|
|
||||||
container!.getBoundingClientRect();
|
|
||||||
const left = event.clientX - offsetLeft;
|
|
||||||
const top = event.clientY - offsetTop;
|
|
||||||
|
|
||||||
const items: ContextMenuItems = [];
|
|
||||||
am.filterActions(isSubtypeAction).forEach(
|
|
||||||
(action) =>
|
|
||||||
am.isActionEnabled(action, { data: { subtype } }) && items.push(action),
|
|
||||||
);
|
|
||||||
setAppState({}, () => {
|
|
||||||
setAppState({
|
|
||||||
contextMenu: { top, left, items },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Only render if one or more subtypes are registered
|
|
||||||
if (getSubtypeNames().length === 0) {
|
|
||||||
return <></>;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Island
|
|
||||||
style={{
|
|
||||||
marginLeft: 8,
|
|
||||||
alignSelf: "center",
|
|
||||||
height: "fit-content",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{getSubtypeNames().map((subtype) =>
|
|
||||||
am.renderAction(
|
|
||||||
makeCustomActionName(subtype),
|
|
||||||
hasAlwaysEnabledActions(subtype) ? { onContextMenu } : {},
|
|
||||||
),
|
|
||||||
)}
|
|
||||||
</Island>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
SubtypeToggles.displayName = "SubtypeToggles";
|
|
||||||
|
|
||||||
export const SubtypeShapeActions = (props: {
|
|
||||||
elements: readonly ExcalidrawElement[];
|
|
||||||
}) => {
|
|
||||||
const am = useExcalidrawActionManager();
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{am
|
|
||||||
.filterActions(isSubtypeAction, { elements: props.elements })
|
|
||||||
.map((action) => am.renderAction(action.name))}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
SubtypeShapeActions.displayName = "SubtypeShapeActions";
|
|
||||||
@@ -203,6 +203,8 @@ const getRelevantAppStateProps = (
|
|||||||
snapLines: appState.snapLines,
|
snapLines: appState.snapLines,
|
||||||
zenModeEnabled: appState.zenModeEnabled,
|
zenModeEnabled: appState.zenModeEnabled,
|
||||||
editingTextElement: appState.editingTextElement,
|
editingTextElement: appState.editingTextElement,
|
||||||
|
isCropping: appState.isCropping,
|
||||||
|
croppingElementId: appState.croppingElementId,
|
||||||
searchMatches: appState.searchMatches,
|
searchMatches: appState.searchMatches,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ const getRelevantAppStateProps = (
|
|||||||
frameToHighlight: appState.frameToHighlight,
|
frameToHighlight: appState.frameToHighlight,
|
||||||
editingGroupId: appState.editingGroupId,
|
editingGroupId: appState.editingGroupId,
|
||||||
currentHoveredFontFamily: appState.currentHoveredFontFamily,
|
currentHoveredFontFamily: appState.currentHoveredFontFamily,
|
||||||
|
croppingElementId: appState.croppingElementId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const areEqual = (
|
const areEqual = (
|
||||||
|
|||||||
@@ -2147,3 +2147,12 @@ export const upIcon = createIcon(
|
|||||||
</g>,
|
</g>,
|
||||||
tablerIconProps,
|
tablerIconProps,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const cropIcon = createIcon(
|
||||||
|
<g strokeWidth="1.25">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M8 5v10a1 1 0 0 0 1 1h10" />
|
||||||
|
<path d="M5 8h10a1 1 0 0 1 1 1v10" />
|
||||||
|
</g>,
|
||||||
|
tablerIconProps,
|
||||||
|
);
|
||||||
|
|||||||
@@ -121,8 +121,7 @@ const repairBinding = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const restoreElementWithProperties = <
|
const restoreElementWithProperties = <
|
||||||
T extends Required<Omit<ExcalidrawElement, "subtype" | "customData">> & {
|
T extends Required<Omit<ExcalidrawElement, "customData">> & {
|
||||||
subtype?: ExcalidrawElement["subtype"];
|
|
||||||
customData?: ExcalidrawElement["customData"];
|
customData?: ExcalidrawElement["customData"];
|
||||||
/** @deprecated */
|
/** @deprecated */
|
||||||
boundElementIds?: readonly ExcalidrawElement["id"][];
|
boundElementIds?: readonly ExcalidrawElement["id"][];
|
||||||
@@ -185,9 +184,6 @@ const restoreElementWithProperties = <
|
|||||||
locked: element.locked ?? false,
|
locked: element.locked ?? false,
|
||||||
};
|
};
|
||||||
|
|
||||||
if ("subtype" in element) {
|
|
||||||
base.subtype = element.subtype;
|
|
||||||
}
|
|
||||||
if ("customData" in element || "customData" in extra) {
|
if ("customData" in element || "customData" in extra) {
|
||||||
base.customData =
|
base.customData =
|
||||||
"customData" in extra ? extra.customData : element.customData;
|
"customData" in extra ? extra.customData : element.customData;
|
||||||
@@ -262,6 +258,7 @@ const restoreElement = (
|
|||||||
status: element.status || "pending",
|
status: element.status || "pending",
|
||||||
fileId: element.fileId,
|
fileId: element.fileId,
|
||||||
scale: element.scale || [1, 1],
|
scale: element.scale || [1, 1],
|
||||||
|
crop: element.crop ?? null,
|
||||||
});
|
});
|
||||||
case "line":
|
case "line":
|
||||||
// @ts-ignore LEGACY type
|
// @ts-ignore LEGACY type
|
||||||
@@ -601,12 +598,6 @@ export const restoreAppState = (
|
|||||||
: defaultValue;
|
: defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("activeSubtypes" in appState) {
|
|
||||||
nextAppState.activeSubtypes = appState.activeSubtypes;
|
|
||||||
}
|
|
||||||
if ("customData" in appState) {
|
|
||||||
nextAppState.customData = appState.customData;
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
...nextAppState,
|
...nextAppState,
|
||||||
cursorButton: localAppState?.cursorButton || "up",
|
cursorButton: localAppState?.cursorButton || "up",
|
||||||
|
|||||||
@@ -0,0 +1,587 @@
|
|||||||
|
import { type Point } from "points-on-curve";
|
||||||
|
import {
|
||||||
|
type Radians,
|
||||||
|
pointFrom,
|
||||||
|
pointCenter,
|
||||||
|
pointRotateRads,
|
||||||
|
vectorFromPoint,
|
||||||
|
vectorNormalize,
|
||||||
|
vectorSubtract,
|
||||||
|
vectorAdd,
|
||||||
|
vectorScale,
|
||||||
|
pointFromVector,
|
||||||
|
clamp,
|
||||||
|
isCloseTo,
|
||||||
|
} from "../../math";
|
||||||
|
import type { TransformHandleType } from "./transformHandles";
|
||||||
|
import type {
|
||||||
|
ElementsMap,
|
||||||
|
ExcalidrawElement,
|
||||||
|
ExcalidrawImageElement,
|
||||||
|
ImageCrop,
|
||||||
|
NonDeleted,
|
||||||
|
} from "./types";
|
||||||
|
import {
|
||||||
|
getElementAbsoluteCoords,
|
||||||
|
getResizedElementAbsoluteCoords,
|
||||||
|
} from "./bounds";
|
||||||
|
|
||||||
|
const MINIMAL_CROP_SIZE = 10;
|
||||||
|
|
||||||
|
export const cropElement = (
|
||||||
|
element: ExcalidrawImageElement,
|
||||||
|
transformHandle: TransformHandleType,
|
||||||
|
naturalWidth: number,
|
||||||
|
naturalHeight: number,
|
||||||
|
pointerX: number,
|
||||||
|
pointerY: number,
|
||||||
|
widthAspectRatio?: number,
|
||||||
|
) => {
|
||||||
|
const { width: uncroppedWidth, height: uncroppedHeight } =
|
||||||
|
getUncroppedWidthAndHeight(element);
|
||||||
|
|
||||||
|
const naturalWidthToUncropped = naturalWidth / uncroppedWidth;
|
||||||
|
const naturalHeightToUncropped = naturalHeight / uncroppedHeight;
|
||||||
|
|
||||||
|
const croppedLeft = (element.crop?.x ?? 0) / naturalWidthToUncropped;
|
||||||
|
const croppedTop = (element.crop?.y ?? 0) / naturalHeightToUncropped;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* uncropped width
|
||||||
|
* *––––––––––––––––––––––––*
|
||||||
|
* | (x,y) (natural) |
|
||||||
|
* | *–––––––* |
|
||||||
|
* | |///////| height | uncropped height
|
||||||
|
* | *–––––––* |
|
||||||
|
* | width (natural) |
|
||||||
|
* *––––––––––––––––––––––––*
|
||||||
|
*/
|
||||||
|
|
||||||
|
const rotatedPointer = pointRotateRads(
|
||||||
|
pointFrom(pointerX, pointerY),
|
||||||
|
pointFrom(element.x + element.width / 2, element.y + element.height / 2),
|
||||||
|
-element.angle as Radians,
|
||||||
|
);
|
||||||
|
|
||||||
|
pointerX = rotatedPointer[0];
|
||||||
|
pointerY = rotatedPointer[1];
|
||||||
|
|
||||||
|
let nextWidth = element.width;
|
||||||
|
let nextHeight = element.height;
|
||||||
|
|
||||||
|
let crop: ImageCrop | null = element.crop ?? {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: naturalWidth,
|
||||||
|
height: naturalHeight,
|
||||||
|
naturalWidth,
|
||||||
|
naturalHeight,
|
||||||
|
};
|
||||||
|
|
||||||
|
const previousCropHeight = crop.height;
|
||||||
|
const previousCropWidth = crop.width;
|
||||||
|
|
||||||
|
const isFlippedByX = element.scale[0] === -1;
|
||||||
|
const isFlippedByY = element.scale[1] === -1;
|
||||||
|
|
||||||
|
let changeInHeight = pointerY - element.y;
|
||||||
|
let changeInWidth = pointerX - element.x;
|
||||||
|
|
||||||
|
if (transformHandle.includes("n")) {
|
||||||
|
nextHeight = clamp(
|
||||||
|
element.height - changeInHeight,
|
||||||
|
MINIMAL_CROP_SIZE,
|
||||||
|
isFlippedByY ? uncroppedHeight - croppedTop : element.height + croppedTop,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transformHandle.includes("s")) {
|
||||||
|
changeInHeight = pointerY - element.y - element.height;
|
||||||
|
nextHeight = clamp(
|
||||||
|
element.height + changeInHeight,
|
||||||
|
MINIMAL_CROP_SIZE,
|
||||||
|
isFlippedByY ? element.height + croppedTop : uncroppedHeight - croppedTop,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transformHandle.includes("e")) {
|
||||||
|
changeInWidth = pointerX - element.x - element.width;
|
||||||
|
|
||||||
|
nextWidth = clamp(
|
||||||
|
element.width + changeInWidth,
|
||||||
|
MINIMAL_CROP_SIZE,
|
||||||
|
isFlippedByX ? element.width + croppedLeft : uncroppedWidth - croppedLeft,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transformHandle.includes("w")) {
|
||||||
|
nextWidth = clamp(
|
||||||
|
element.width - changeInWidth,
|
||||||
|
MINIMAL_CROP_SIZE,
|
||||||
|
isFlippedByX ? uncroppedWidth - croppedLeft : element.width + croppedLeft,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateCropWidthAndHeight = (crop: ImageCrop) => {
|
||||||
|
crop.height = nextHeight * naturalHeightToUncropped;
|
||||||
|
crop.width = nextWidth * naturalWidthToUncropped;
|
||||||
|
};
|
||||||
|
|
||||||
|
updateCropWidthAndHeight(crop);
|
||||||
|
|
||||||
|
const adjustFlipForHandle = (
|
||||||
|
handle: TransformHandleType,
|
||||||
|
crop: ImageCrop,
|
||||||
|
) => {
|
||||||
|
updateCropWidthAndHeight(crop);
|
||||||
|
if (handle.includes("n")) {
|
||||||
|
if (!isFlippedByY) {
|
||||||
|
crop.y += previousCropHeight - crop.height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (handle.includes("s")) {
|
||||||
|
if (isFlippedByY) {
|
||||||
|
crop.y += previousCropHeight - crop.height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (handle.includes("e")) {
|
||||||
|
if (isFlippedByX) {
|
||||||
|
crop.x += previousCropWidth - crop.width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (handle.includes("w")) {
|
||||||
|
if (!isFlippedByX) {
|
||||||
|
crop.x += previousCropWidth - crop.width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (transformHandle) {
|
||||||
|
case "n": {
|
||||||
|
if (widthAspectRatio) {
|
||||||
|
const distanceToLeft = croppedLeft + element.width / 2;
|
||||||
|
const distanceToRight =
|
||||||
|
uncroppedWidth - croppedLeft - element.width / 2;
|
||||||
|
|
||||||
|
const MAX_WIDTH = Math.min(distanceToLeft, distanceToRight) * 2;
|
||||||
|
|
||||||
|
nextWidth = clamp(
|
||||||
|
nextHeight * widthAspectRatio,
|
||||||
|
MINIMAL_CROP_SIZE,
|
||||||
|
MAX_WIDTH,
|
||||||
|
);
|
||||||
|
nextHeight = nextWidth / widthAspectRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
adjustFlipForHandle(transformHandle, crop);
|
||||||
|
|
||||||
|
if (widthAspectRatio) {
|
||||||
|
crop.x += (previousCropWidth - crop.width) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "s": {
|
||||||
|
if (widthAspectRatio) {
|
||||||
|
const distanceToLeft = croppedLeft + element.width / 2;
|
||||||
|
const distanceToRight =
|
||||||
|
uncroppedWidth - croppedLeft - element.width / 2;
|
||||||
|
|
||||||
|
const MAX_WIDTH = Math.min(distanceToLeft, distanceToRight) * 2;
|
||||||
|
|
||||||
|
nextWidth = clamp(
|
||||||
|
nextHeight * widthAspectRatio,
|
||||||
|
MINIMAL_CROP_SIZE,
|
||||||
|
MAX_WIDTH,
|
||||||
|
);
|
||||||
|
nextHeight = nextWidth / widthAspectRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
adjustFlipForHandle(transformHandle, crop);
|
||||||
|
|
||||||
|
if (widthAspectRatio) {
|
||||||
|
crop.x += (previousCropWidth - crop.width) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "w": {
|
||||||
|
if (widthAspectRatio) {
|
||||||
|
const distanceToTop = croppedTop + element.height / 2;
|
||||||
|
const distanceToBottom =
|
||||||
|
uncroppedHeight - croppedTop - element.height / 2;
|
||||||
|
|
||||||
|
const MAX_HEIGHT = Math.min(distanceToTop, distanceToBottom) * 2;
|
||||||
|
|
||||||
|
nextHeight = clamp(
|
||||||
|
nextWidth / widthAspectRatio,
|
||||||
|
MINIMAL_CROP_SIZE,
|
||||||
|
MAX_HEIGHT,
|
||||||
|
);
|
||||||
|
nextWidth = nextHeight * widthAspectRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
adjustFlipForHandle(transformHandle, crop);
|
||||||
|
|
||||||
|
if (widthAspectRatio) {
|
||||||
|
crop.y += (previousCropHeight - crop.height) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "e": {
|
||||||
|
if (widthAspectRatio) {
|
||||||
|
const distanceToTop = croppedTop + element.height / 2;
|
||||||
|
const distanceToBottom =
|
||||||
|
uncroppedHeight - croppedTop - element.height / 2;
|
||||||
|
|
||||||
|
const MAX_HEIGHT = Math.min(distanceToTop, distanceToBottom) * 2;
|
||||||
|
|
||||||
|
nextHeight = clamp(
|
||||||
|
nextWidth / widthAspectRatio,
|
||||||
|
MINIMAL_CROP_SIZE,
|
||||||
|
MAX_HEIGHT,
|
||||||
|
);
|
||||||
|
nextWidth = nextHeight * widthAspectRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
adjustFlipForHandle(transformHandle, crop);
|
||||||
|
|
||||||
|
if (widthAspectRatio) {
|
||||||
|
crop.y += (previousCropHeight - crop.height) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "ne": {
|
||||||
|
if (widthAspectRatio) {
|
||||||
|
if (changeInWidth > -changeInHeight) {
|
||||||
|
const MAX_HEIGHT = isFlippedByY
|
||||||
|
? uncroppedHeight - croppedTop
|
||||||
|
: croppedTop + element.height;
|
||||||
|
|
||||||
|
nextHeight = clamp(
|
||||||
|
nextWidth / widthAspectRatio,
|
||||||
|
MINIMAL_CROP_SIZE,
|
||||||
|
MAX_HEIGHT,
|
||||||
|
);
|
||||||
|
nextWidth = nextHeight * widthAspectRatio;
|
||||||
|
} else {
|
||||||
|
const MAX_WIDTH = isFlippedByX
|
||||||
|
? croppedLeft + element.width
|
||||||
|
: uncroppedWidth - croppedLeft;
|
||||||
|
|
||||||
|
nextWidth = clamp(
|
||||||
|
nextHeight * widthAspectRatio,
|
||||||
|
MINIMAL_CROP_SIZE,
|
||||||
|
MAX_WIDTH,
|
||||||
|
);
|
||||||
|
nextHeight = nextWidth / widthAspectRatio;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
adjustFlipForHandle(transformHandle, crop);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "nw": {
|
||||||
|
if (widthAspectRatio) {
|
||||||
|
if (changeInWidth < changeInHeight) {
|
||||||
|
const MAX_HEIGHT = isFlippedByY
|
||||||
|
? uncroppedHeight - croppedTop
|
||||||
|
: croppedTop + element.height;
|
||||||
|
nextHeight = clamp(
|
||||||
|
nextWidth / widthAspectRatio,
|
||||||
|
MINIMAL_CROP_SIZE,
|
||||||
|
MAX_HEIGHT,
|
||||||
|
);
|
||||||
|
nextWidth = nextHeight * widthAspectRatio;
|
||||||
|
} else {
|
||||||
|
const MAX_WIDTH = isFlippedByX
|
||||||
|
? uncroppedWidth - croppedLeft
|
||||||
|
: croppedLeft + element.width;
|
||||||
|
|
||||||
|
nextWidth = clamp(
|
||||||
|
nextHeight * widthAspectRatio,
|
||||||
|
MINIMAL_CROP_SIZE,
|
||||||
|
MAX_WIDTH,
|
||||||
|
);
|
||||||
|
nextHeight = nextWidth / widthAspectRatio;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
adjustFlipForHandle(transformHandle, crop);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "se": {
|
||||||
|
if (widthAspectRatio) {
|
||||||
|
if (changeInWidth > changeInHeight) {
|
||||||
|
const MAX_HEIGHT = isFlippedByY
|
||||||
|
? croppedTop + element.height
|
||||||
|
: uncroppedHeight - croppedTop;
|
||||||
|
|
||||||
|
nextHeight = clamp(
|
||||||
|
nextWidth / widthAspectRatio,
|
||||||
|
MINIMAL_CROP_SIZE,
|
||||||
|
MAX_HEIGHT,
|
||||||
|
);
|
||||||
|
nextWidth = nextHeight * widthAspectRatio;
|
||||||
|
} else {
|
||||||
|
const MAX_WIDTH = isFlippedByX
|
||||||
|
? croppedLeft + element.width
|
||||||
|
: uncroppedWidth - croppedLeft;
|
||||||
|
|
||||||
|
nextWidth = clamp(
|
||||||
|
nextHeight * widthAspectRatio,
|
||||||
|
MINIMAL_CROP_SIZE,
|
||||||
|
MAX_WIDTH,
|
||||||
|
);
|
||||||
|
nextHeight = nextWidth / widthAspectRatio;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
adjustFlipForHandle(transformHandle, crop);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "sw": {
|
||||||
|
if (widthAspectRatio) {
|
||||||
|
if (-changeInWidth > changeInHeight) {
|
||||||
|
const MAX_HEIGHT = isFlippedByY
|
||||||
|
? croppedTop + element.height
|
||||||
|
: uncroppedHeight - croppedTop;
|
||||||
|
|
||||||
|
nextHeight = clamp(
|
||||||
|
nextWidth / widthAspectRatio,
|
||||||
|
MINIMAL_CROP_SIZE,
|
||||||
|
MAX_HEIGHT,
|
||||||
|
);
|
||||||
|
nextWidth = nextHeight * widthAspectRatio;
|
||||||
|
} else {
|
||||||
|
const MAX_WIDTH = isFlippedByX
|
||||||
|
? uncroppedWidth - croppedLeft
|
||||||
|
: croppedLeft + element.width;
|
||||||
|
|
||||||
|
nextWidth = clamp(
|
||||||
|
nextHeight * widthAspectRatio,
|
||||||
|
MINIMAL_CROP_SIZE,
|
||||||
|
MAX_WIDTH,
|
||||||
|
);
|
||||||
|
nextHeight = nextWidth / widthAspectRatio;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
adjustFlipForHandle(transformHandle, crop);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newOrigin = recomputeOrigin(
|
||||||
|
element,
|
||||||
|
transformHandle,
|
||||||
|
nextWidth,
|
||||||
|
nextHeight,
|
||||||
|
!!widthAspectRatio,
|
||||||
|
);
|
||||||
|
|
||||||
|
// reset crop to null if we're back to orig size
|
||||||
|
if (
|
||||||
|
isCloseTo(crop.width, crop.naturalWidth) &&
|
||||||
|
isCloseTo(crop.height, crop.naturalHeight)
|
||||||
|
) {
|
||||||
|
crop = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: newOrigin[0],
|
||||||
|
y: newOrigin[1],
|
||||||
|
width: nextWidth,
|
||||||
|
height: nextHeight,
|
||||||
|
crop,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const recomputeOrigin = (
|
||||||
|
stateAtCropStart: NonDeleted<ExcalidrawElement>,
|
||||||
|
transformHandle: TransformHandleType,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
shouldMaintainAspectRatio?: boolean,
|
||||||
|
) => {
|
||||||
|
const [x1, y1, x2, y2] = getResizedElementAbsoluteCoords(
|
||||||
|
stateAtCropStart,
|
||||||
|
stateAtCropStart.width,
|
||||||
|
stateAtCropStart.height,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
const startTopLeft = pointFrom(x1, y1);
|
||||||
|
const startBottomRight = pointFrom(x2, y2);
|
||||||
|
const startCenter: any = pointCenter(startTopLeft, startBottomRight);
|
||||||
|
|
||||||
|
const [newBoundsX1, newBoundsY1, newBoundsX2, newBoundsY2] =
|
||||||
|
getResizedElementAbsoluteCoords(stateAtCropStart, width, height, true);
|
||||||
|
const newBoundsWidth = newBoundsX2 - newBoundsX1;
|
||||||
|
const newBoundsHeight = newBoundsY2 - newBoundsY1;
|
||||||
|
|
||||||
|
// Calculate new topLeft based on fixed corner during resize
|
||||||
|
let newTopLeft = [...startTopLeft] as [number, number];
|
||||||
|
|
||||||
|
if (["n", "w", "nw"].includes(transformHandle)) {
|
||||||
|
newTopLeft = [
|
||||||
|
startBottomRight[0] - Math.abs(newBoundsWidth),
|
||||||
|
startBottomRight[1] - Math.abs(newBoundsHeight),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (transformHandle === "ne") {
|
||||||
|
const bottomLeft = [startTopLeft[0], startBottomRight[1]];
|
||||||
|
newTopLeft = [bottomLeft[0], bottomLeft[1] - Math.abs(newBoundsHeight)];
|
||||||
|
}
|
||||||
|
if (transformHandle === "sw") {
|
||||||
|
const topRight = [startBottomRight[0], startTopLeft[1]];
|
||||||
|
newTopLeft = [topRight[0] - Math.abs(newBoundsWidth), topRight[1]];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldMaintainAspectRatio) {
|
||||||
|
if (["s", "n"].includes(transformHandle)) {
|
||||||
|
newTopLeft[0] = startCenter[0] - newBoundsWidth / 2;
|
||||||
|
}
|
||||||
|
if (["e", "w"].includes(transformHandle)) {
|
||||||
|
newTopLeft[1] = startCenter[1] - newBoundsHeight / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// adjust topLeft to new rotation point
|
||||||
|
const angle = stateAtCropStart.angle;
|
||||||
|
const rotatedTopLeft = pointRotateRads(newTopLeft, startCenter, angle);
|
||||||
|
const newCenter: Point = [
|
||||||
|
newTopLeft[0] + Math.abs(newBoundsWidth) / 2,
|
||||||
|
newTopLeft[1] + Math.abs(newBoundsHeight) / 2,
|
||||||
|
];
|
||||||
|
const rotatedNewCenter = pointRotateRads(newCenter, startCenter, angle);
|
||||||
|
newTopLeft = pointRotateRads(
|
||||||
|
rotatedTopLeft,
|
||||||
|
rotatedNewCenter,
|
||||||
|
-angle as Radians,
|
||||||
|
);
|
||||||
|
|
||||||
|
const newOrigin = [...newTopLeft];
|
||||||
|
newOrigin[0] += stateAtCropStart.x - newBoundsX1;
|
||||||
|
newOrigin[1] += stateAtCropStart.y - newBoundsY1;
|
||||||
|
|
||||||
|
return newOrigin;
|
||||||
|
};
|
||||||
|
|
||||||
|
// refer to https://link.excalidraw.com/l/6rfy1007QOo/6stx5PmRn0k
|
||||||
|
export const getUncroppedImageElement = (
|
||||||
|
element: ExcalidrawImageElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
|
) => {
|
||||||
|
if (element.crop) {
|
||||||
|
const { width, height } = getUncroppedWidthAndHeight(element);
|
||||||
|
|
||||||
|
const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(
|
||||||
|
element,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
|
||||||
|
const topLeftVector = vectorFromPoint(
|
||||||
|
pointRotateRads(pointFrom(x1, y1), pointFrom(cx, cy), element.angle),
|
||||||
|
);
|
||||||
|
const topRightVector = vectorFromPoint(
|
||||||
|
pointRotateRads(pointFrom(x2, y1), pointFrom(cx, cy), element.angle),
|
||||||
|
);
|
||||||
|
const topEdgeNormalized = vectorNormalize(
|
||||||
|
vectorSubtract(topRightVector, topLeftVector),
|
||||||
|
);
|
||||||
|
const bottomLeftVector = vectorFromPoint(
|
||||||
|
pointRotateRads(pointFrom(x1, y2), pointFrom(cx, cy), element.angle),
|
||||||
|
);
|
||||||
|
const leftEdgeVector = vectorSubtract(bottomLeftVector, topLeftVector);
|
||||||
|
const leftEdgeNormalized = vectorNormalize(leftEdgeVector);
|
||||||
|
|
||||||
|
const { cropX, cropY } = adjustCropPosition(element.crop, element.scale);
|
||||||
|
|
||||||
|
const rotatedTopLeft = vectorAdd(
|
||||||
|
vectorAdd(
|
||||||
|
topLeftVector,
|
||||||
|
vectorScale(
|
||||||
|
topEdgeNormalized,
|
||||||
|
(-cropX * width) / element.crop.naturalWidth,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
vectorScale(
|
||||||
|
leftEdgeNormalized,
|
||||||
|
(-cropY * height) / element.crop.naturalHeight,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const center = pointFromVector(
|
||||||
|
vectorAdd(
|
||||||
|
vectorAdd(rotatedTopLeft, vectorScale(topEdgeNormalized, width / 2)),
|
||||||
|
vectorScale(leftEdgeNormalized, height / 2),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const unrotatedTopLeft = pointRotateRads(
|
||||||
|
pointFromVector(rotatedTopLeft),
|
||||||
|
center,
|
||||||
|
-element.angle as Radians,
|
||||||
|
);
|
||||||
|
|
||||||
|
const uncroppedElement: ExcalidrawImageElement = {
|
||||||
|
...element,
|
||||||
|
x: unrotatedTopLeft[0],
|
||||||
|
y: unrotatedTopLeft[1],
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
crop: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
return uncroppedElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
return element;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getUncroppedWidthAndHeight = (element: ExcalidrawImageElement) => {
|
||||||
|
if (element.crop) {
|
||||||
|
const width =
|
||||||
|
element.width / (element.crop.width / element.crop.naturalWidth);
|
||||||
|
const height =
|
||||||
|
element.height / (element.crop.height / element.crop.naturalHeight);
|
||||||
|
|
||||||
|
return {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
width: element.width,
|
||||||
|
height: element.height,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const adjustCropPosition = (
|
||||||
|
crop: ImageCrop,
|
||||||
|
scale: ExcalidrawImageElement["scale"],
|
||||||
|
) => {
|
||||||
|
let cropX = crop.x;
|
||||||
|
let cropY = crop.y;
|
||||||
|
|
||||||
|
const flipX = scale[0] === -1;
|
||||||
|
const flipY = scale[1] === -1;
|
||||||
|
|
||||||
|
if (flipX) {
|
||||||
|
cropX = crop.naturalWidth - Math.abs(cropX) - crop.width;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flipY) {
|
||||||
|
cropY = crop.naturalHeight - Math.abs(cropY) - crop.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
cropX,
|
||||||
|
cropY,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
isArrowElement,
|
isArrowElement,
|
||||||
isElbowArrow,
|
isElbowArrow,
|
||||||
isFrameLikeElement,
|
isFrameLikeElement,
|
||||||
|
isImageElement,
|
||||||
isTextElement,
|
isTextElement,
|
||||||
} from "./typeChecks";
|
} from "./typeChecks";
|
||||||
import { getFontString } from "../utils";
|
import { getFontString } from "../utils";
|
||||||
@@ -251,6 +252,14 @@ export const dragNewElement = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (width !== 0 && height !== 0) {
|
if (width !== 0 && height !== 0) {
|
||||||
|
let imageInitialDimension = null;
|
||||||
|
if (isImageElement(newElement)) {
|
||||||
|
imageInitialDimension = {
|
||||||
|
initialWidth: width,
|
||||||
|
initialHeight: height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
mutateElement(
|
mutateElement(
|
||||||
newElement,
|
newElement,
|
||||||
{
|
{
|
||||||
@@ -259,6 +268,7 @@ export const dragNewElement = ({
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
...textAutoResize,
|
...textAutoResize,
|
||||||
|
...imageInitialDimension,
|
||||||
},
|
},
|
||||||
informMutation,
|
informMutation,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,23 +5,12 @@ import { randomInteger } from "../random";
|
|||||||
import { getUpdatedTimestamp } from "../utils";
|
import { getUpdatedTimestamp } from "../utils";
|
||||||
import type { Mutable } from "../utility-types";
|
import type { Mutable } from "../utility-types";
|
||||||
import { ShapeCache } from "../scene/ShapeCache";
|
import { ShapeCache } from "../scene/ShapeCache";
|
||||||
import { maybeGetSubtypeProps } from "./newElement";
|
|
||||||
import { getSubtypeMethods } from "./subtypes";
|
|
||||||
|
|
||||||
export type ElementUpdate<TElement extends ExcalidrawElement> = Omit<
|
export type ElementUpdate<TElement extends ExcalidrawElement> = Omit<
|
||||||
Partial<TElement>,
|
Partial<TElement>,
|
||||||
"id" | "version" | "versionNonce" | "updated"
|
"id" | "version" | "versionNonce" | "updated"
|
||||||
>;
|
>;
|
||||||
|
|
||||||
const cleanUpdates = <TElement extends Mutable<ExcalidrawElement>>(
|
|
||||||
element: TElement,
|
|
||||||
updates: ElementUpdate<TElement>,
|
|
||||||
): ElementUpdate<TElement> => {
|
|
||||||
const subtype = maybeGetSubtypeProps(element, element.type).subtype;
|
|
||||||
const map = getSubtypeMethods(subtype);
|
|
||||||
return map?.clean ? (map.clean(updates) as typeof updates) : updates;
|
|
||||||
};
|
|
||||||
|
|
||||||
// This function tracks updates of text elements for the purposes for collaboration.
|
// This function tracks updates of text elements for the purposes for collaboration.
|
||||||
// The version is used to compare updates when more than one user is working in
|
// The version is used to compare updates when more than one user is working in
|
||||||
// the same drawing. Note: this will trigger the component to update. Make sure you
|
// the same drawing. Note: this will trigger the component to update. Make sure you
|
||||||
@@ -32,8 +21,6 @@ export const mutateElement = <TElement extends Mutable<ExcalidrawElement>>(
|
|||||||
informMutation = true,
|
informMutation = true,
|
||||||
): TElement => {
|
): TElement => {
|
||||||
let didChange = false;
|
let didChange = false;
|
||||||
let increment = false;
|
|
||||||
const oldUpdates = cleanUpdates(element, updates);
|
|
||||||
|
|
||||||
// casting to any because can't use `in` operator
|
// casting to any because can't use `in` operator
|
||||||
// (see https://github.com/microsoft/TypeScript/issues/21732)
|
// (see https://github.com/microsoft/TypeScript/issues/21732)
|
||||||
@@ -82,7 +69,6 @@ export const mutateElement = <TElement extends Mutable<ExcalidrawElement>>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!didChangePoints) {
|
if (!didChangePoints) {
|
||||||
key in oldUpdates && (increment = true);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,7 +76,6 @@ export const mutateElement = <TElement extends Mutable<ExcalidrawElement>>(
|
|||||||
|
|
||||||
(element as any)[key] = value;
|
(element as any)[key] = value;
|
||||||
didChange = true;
|
didChange = true;
|
||||||
key in oldUpdates && (increment = true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,11 +92,9 @@ export const mutateElement = <TElement extends Mutable<ExcalidrawElement>>(
|
|||||||
ShapeCache.delete(element);
|
ShapeCache.delete(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (increment) {
|
element.version++;
|
||||||
element.version++;
|
element.versionNonce = randomInteger();
|
||||||
element.versionNonce = randomInteger();
|
element.updated = getUpdatedTimestamp();
|
||||||
element.updated = getUpdatedTimestamp();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (informMutation) {
|
if (informMutation) {
|
||||||
Scene.getScene(element)?.triggerUpdate();
|
Scene.getScene(element)?.triggerUpdate();
|
||||||
@@ -127,8 +110,6 @@ export const newElementWith = <TElement extends ExcalidrawElement>(
|
|||||||
force = false,
|
force = false,
|
||||||
): TElement => {
|
): TElement => {
|
||||||
let didChange = false;
|
let didChange = false;
|
||||||
let increment = false;
|
|
||||||
const oldUpdates = cleanUpdates(element, updates);
|
|
||||||
for (const key in updates) {
|
for (const key in updates) {
|
||||||
const value = (updates as any)[key];
|
const value = (updates as any)[key];
|
||||||
if (typeof value !== "undefined") {
|
if (typeof value !== "undefined") {
|
||||||
@@ -140,7 +121,6 @@ export const newElementWith = <TElement extends ExcalidrawElement>(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
didChange = true;
|
didChange = true;
|
||||||
key in oldUpdates && (increment = true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,9 +128,6 @@ export const newElementWith = <TElement extends ExcalidrawElement>(
|
|||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!increment) {
|
|
||||||
return { ...element, ...updates };
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
...element,
|
...element,
|
||||||
...updates,
|
...updates,
|
||||||
|
|||||||
@@ -19,7 +19,12 @@ import type {
|
|||||||
ElementsMap,
|
ElementsMap,
|
||||||
ExcalidrawArrowElement,
|
ExcalidrawArrowElement,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import { arrayToMap, getUpdatedTimestamp, isTestEnv } from "../utils";
|
import {
|
||||||
|
arrayToMap,
|
||||||
|
getFontString,
|
||||||
|
getUpdatedTimestamp,
|
||||||
|
isTestEnv,
|
||||||
|
} from "../utils";
|
||||||
import { randomInteger, randomId } from "../random";
|
import { randomInteger, randomId } from "../random";
|
||||||
import { bumpVersion, newElementWith } from "./mutateElement";
|
import { bumpVersion, newElementWith } from "./mutateElement";
|
||||||
import { getNewGroupIdsForDuplication } from "../groups";
|
import { getNewGroupIdsForDuplication } from "../groups";
|
||||||
@@ -27,9 +32,9 @@ import type { AppState } from "../types";
|
|||||||
import { getElementAbsoluteCoords } from ".";
|
import { getElementAbsoluteCoords } from ".";
|
||||||
import { getResizedElementAbsoluteCoords } from "./bounds";
|
import { getResizedElementAbsoluteCoords } from "./bounds";
|
||||||
import {
|
import {
|
||||||
measureTextElement,
|
measureText,
|
||||||
normalizeText,
|
normalizeText,
|
||||||
wrapTextElement,
|
wrapText,
|
||||||
getBoundTextMaxWidth,
|
getBoundTextMaxWidth,
|
||||||
} from "./textElement";
|
} from "./textElement";
|
||||||
import {
|
import {
|
||||||
@@ -43,30 +48,6 @@ import {
|
|||||||
import type { MarkOptional, Merge, Mutable } from "../utility-types";
|
import type { MarkOptional, Merge, Mutable } from "../utility-types";
|
||||||
import { getLineHeight } from "../fonts";
|
import { getLineHeight } from "../fonts";
|
||||||
import type { Radians } from "../../math";
|
import type { Radians } from "../../math";
|
||||||
import { getSubtypeMethods, isValidSubtype } from "./subtypes";
|
|
||||||
|
|
||||||
export const maybeGetSubtypeProps = (
|
|
||||||
obj: {
|
|
||||||
subtype?: ExcalidrawElement["subtype"];
|
|
||||||
customData?: ExcalidrawElement["customData"];
|
|
||||||
},
|
|
||||||
type: ExcalidrawElement["type"],
|
|
||||||
) => {
|
|
||||||
const data: typeof obj = {};
|
|
||||||
if ("subtype" in obj) {
|
|
||||||
data.subtype = obj.subtype;
|
|
||||||
}
|
|
||||||
if ("customData" in obj) {
|
|
||||||
data.customData = obj.customData;
|
|
||||||
}
|
|
||||||
if ("subtype" in data && !isValidSubtype(data.subtype, type)) {
|
|
||||||
delete data.subtype;
|
|
||||||
}
|
|
||||||
if (!("subtype" in data) && "customData" in data) {
|
|
||||||
delete data.customData;
|
|
||||||
}
|
|
||||||
return data as typeof obj;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ElementConstructorOpts = MarkOptional<
|
export type ElementConstructorOpts = MarkOptional<
|
||||||
Omit<ExcalidrawGenericElement, "id" | "type" | "isDeleted" | "updated">,
|
Omit<ExcalidrawGenericElement, "id" | "type" | "isDeleted" | "updated">,
|
||||||
@@ -81,8 +62,6 @@ export type ElementConstructorOpts = MarkOptional<
|
|||||||
| "version"
|
| "version"
|
||||||
| "versionNonce"
|
| "versionNonce"
|
||||||
| "link"
|
| "link"
|
||||||
| "subtype"
|
|
||||||
| "customData"
|
|
||||||
| "strokeStyle"
|
| "strokeStyle"
|
||||||
| "fillStyle"
|
| "fillStyle"
|
||||||
| "strokeColor"
|
| "strokeColor"
|
||||||
@@ -120,10 +99,8 @@ const _newElementBase = <T extends ExcalidrawElement>(
|
|||||||
...rest
|
...rest
|
||||||
}: ElementConstructorOpts & Omit<Partial<ExcalidrawGenericElement>, "type">,
|
}: ElementConstructorOpts & Omit<Partial<ExcalidrawGenericElement>, "type">,
|
||||||
) => {
|
) => {
|
||||||
const { subtype, customData } = rest;
|
|
||||||
// assign type to guard against excess properties
|
// assign type to guard against excess properties
|
||||||
const element: Merge<ExcalidrawGenericElement, { type: T["type"] }> = {
|
const element: Merge<ExcalidrawGenericElement, { type: T["type"] }> = {
|
||||||
...maybeGetSubtypeProps({ subtype, customData }, type),
|
|
||||||
id: rest.id || randomId(),
|
id: rest.id || randomId(),
|
||||||
type,
|
type,
|
||||||
x,
|
x,
|
||||||
@@ -159,11 +136,8 @@ export const newElement = (
|
|||||||
opts: {
|
opts: {
|
||||||
type: ExcalidrawGenericElement["type"];
|
type: ExcalidrawGenericElement["type"];
|
||||||
} & ElementConstructorOpts,
|
} & ElementConstructorOpts,
|
||||||
): NonDeleted<ExcalidrawGenericElement> => {
|
): NonDeleted<ExcalidrawGenericElement> =>
|
||||||
const map = getSubtypeMethods(opts?.subtype);
|
_newElementBase<ExcalidrawGenericElement>(opts.type, opts);
|
||||||
map?.clean && map.clean(opts);
|
|
||||||
return _newElementBase<ExcalidrawGenericElement>(opts.type, opts);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const newEmbeddableElement = (
|
export const newEmbeddableElement = (
|
||||||
opts: {
|
opts: {
|
||||||
@@ -256,12 +230,10 @@ export const newTextElement = (
|
|||||||
const fontSize = opts.fontSize || DEFAULT_FONT_SIZE;
|
const fontSize = opts.fontSize || DEFAULT_FONT_SIZE;
|
||||||
const lineHeight = opts.lineHeight || getLineHeight(fontFamily);
|
const lineHeight = opts.lineHeight || getLineHeight(fontFamily);
|
||||||
const text = normalizeText(opts.text);
|
const text = normalizeText(opts.text);
|
||||||
const metrics = measureTextElement(
|
const metrics = measureText(
|
||||||
{ ...opts, fontSize, fontFamily, lineHeight },
|
text,
|
||||||
{
|
getFontString({ fontFamily, fontSize }),
|
||||||
text,
|
lineHeight,
|
||||||
customData: opts.customData,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
const textAlign = opts.textAlign || DEFAULT_TEXT_ALIGN;
|
const textAlign = opts.textAlign || DEFAULT_TEXT_ALIGN;
|
||||||
const verticalAlign = opts.verticalAlign || DEFAULT_VERTICAL_ALIGN;
|
const verticalAlign = opts.verticalAlign || DEFAULT_VERTICAL_ALIGN;
|
||||||
@@ -305,9 +277,11 @@ const getAdjustedDimensions = (
|
|||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
} => {
|
} => {
|
||||||
let { width: nextWidth, height: nextHeight } = measureTextElement(element, {
|
let { width: nextWidth, height: nextHeight } = measureText(
|
||||||
text: nextText,
|
nextText,
|
||||||
});
|
getFontString(element),
|
||||||
|
element.lineHeight,
|
||||||
|
);
|
||||||
|
|
||||||
// wrapped text
|
// wrapped text
|
||||||
if (!element.autoResize) {
|
if (!element.autoResize) {
|
||||||
@@ -323,7 +297,11 @@ const getAdjustedDimensions = (
|
|||||||
!element.containerId &&
|
!element.containerId &&
|
||||||
element.autoResize
|
element.autoResize
|
||||||
) {
|
) {
|
||||||
const prevMetrics = measureTextElement(element);
|
const prevMetrics = measureText(
|
||||||
|
element.text,
|
||||||
|
getFontString(element),
|
||||||
|
element.lineHeight,
|
||||||
|
);
|
||||||
const offsets = getTextElementPositionOffsets(element, {
|
const offsets = getTextElementPositionOffsets(element, {
|
||||||
width: nextWidth - prevMetrics.width,
|
width: nextWidth - prevMetrics.width,
|
||||||
height: nextHeight - prevMetrics.height,
|
height: nextHeight - prevMetrics.height,
|
||||||
@@ -426,14 +404,12 @@ export const refreshTextDimensions = (
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (container || !textElement.autoResize) {
|
if (container || !textElement.autoResize) {
|
||||||
text = wrapTextElement(
|
text = wrapText(
|
||||||
textElement,
|
text,
|
||||||
|
getFontString(textElement),
|
||||||
container
|
container
|
||||||
? getBoundTextMaxWidth(container, textElement)
|
? getBoundTextMaxWidth(container, textElement)
|
||||||
: textElement.width,
|
: textElement.width,
|
||||||
{
|
|
||||||
text,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const dimensions = getAdjustedDimensions(textElement, elementsMap, text);
|
const dimensions = getAdjustedDimensions(textElement, elementsMap, text);
|
||||||
@@ -448,8 +424,6 @@ export const newFreeDrawElement = (
|
|||||||
pressures?: ExcalidrawFreeDrawElement["pressures"];
|
pressures?: ExcalidrawFreeDrawElement["pressures"];
|
||||||
} & ElementConstructorOpts,
|
} & ElementConstructorOpts,
|
||||||
): NonDeleted<ExcalidrawFreeDrawElement> => {
|
): NonDeleted<ExcalidrawFreeDrawElement> => {
|
||||||
const map = getSubtypeMethods(opts?.subtype);
|
|
||||||
map?.clean && map.clean(opts);
|
|
||||||
return {
|
return {
|
||||||
..._newElementBase<ExcalidrawFreeDrawElement>(opts.type, opts),
|
..._newElementBase<ExcalidrawFreeDrawElement>(opts.type, opts),
|
||||||
points: opts.points || [],
|
points: opts.points || [],
|
||||||
@@ -465,8 +439,6 @@ export const newLinearElement = (
|
|||||||
points?: ExcalidrawLinearElement["points"];
|
points?: ExcalidrawLinearElement["points"];
|
||||||
} & ElementConstructorOpts,
|
} & ElementConstructorOpts,
|
||||||
): NonDeleted<ExcalidrawLinearElement> => {
|
): NonDeleted<ExcalidrawLinearElement> => {
|
||||||
const map = getSubtypeMethods(opts?.subtype);
|
|
||||||
map?.clean && map.clean(opts);
|
|
||||||
return {
|
return {
|
||||||
..._newElementBase<ExcalidrawLinearElement>(opts.type, opts),
|
..._newElementBase<ExcalidrawLinearElement>(opts.type, opts),
|
||||||
points: opts.points || [],
|
points: opts.points || [],
|
||||||
@@ -487,8 +459,6 @@ export const newArrowElement = (
|
|||||||
elbowed?: boolean;
|
elbowed?: boolean;
|
||||||
} & ElementConstructorOpts,
|
} & ElementConstructorOpts,
|
||||||
): NonDeleted<ExcalidrawArrowElement> => {
|
): NonDeleted<ExcalidrawArrowElement> => {
|
||||||
const map = getSubtypeMethods(opts?.subtype);
|
|
||||||
map?.clean && map.clean(opts);
|
|
||||||
return {
|
return {
|
||||||
..._newElementBase<ExcalidrawArrowElement>(opts.type, opts),
|
..._newElementBase<ExcalidrawArrowElement>(opts.type, opts),
|
||||||
points: opts.points || [],
|
points: opts.points || [],
|
||||||
@@ -507,10 +477,9 @@ export const newImageElement = (
|
|||||||
status?: ExcalidrawImageElement["status"];
|
status?: ExcalidrawImageElement["status"];
|
||||||
fileId?: ExcalidrawImageElement["fileId"];
|
fileId?: ExcalidrawImageElement["fileId"];
|
||||||
scale?: ExcalidrawImageElement["scale"];
|
scale?: ExcalidrawImageElement["scale"];
|
||||||
|
crop?: ExcalidrawImageElement["crop"];
|
||||||
} & ElementConstructorOpts,
|
} & ElementConstructorOpts,
|
||||||
): NonDeleted<ExcalidrawImageElement> => {
|
): NonDeleted<ExcalidrawImageElement> => {
|
||||||
const map = getSubtypeMethods(opts?.subtype);
|
|
||||||
map?.clean && map.clean(opts);
|
|
||||||
return {
|
return {
|
||||||
..._newElementBase<ExcalidrawImageElement>("image", opts),
|
..._newElementBase<ExcalidrawImageElement>("image", opts),
|
||||||
// in the future we'll support changing stroke color for some SVG elements,
|
// in the future we'll support changing stroke color for some SVG elements,
|
||||||
@@ -519,6 +488,7 @@ export const newImageElement = (
|
|||||||
status: opts.status ?? "pending",
|
status: opts.status ?? "pending",
|
||||||
fileId: opts.fileId ?? null,
|
fileId: opts.fileId ?? null,
|
||||||
scale: opts.scale ?? [1, 1],
|
scale: opts.scale ?? [1, 1],
|
||||||
|
crop: opts.crop ?? null,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import type { AppState, Device, Zoom } from "../types";
|
|||||||
import type { Bounds } from "./bounds";
|
import type { Bounds } from "./bounds";
|
||||||
import { getElementAbsoluteCoords } from "./bounds";
|
import { getElementAbsoluteCoords } from "./bounds";
|
||||||
import { SIDE_RESIZING_THRESHOLD } from "../constants";
|
import { SIDE_RESIZING_THRESHOLD } from "../constants";
|
||||||
import { isLinearElement } from "./typeChecks";
|
import { isImageElement, isLinearElement } from "./typeChecks";
|
||||||
import type { GlobalPoint, LineSegment, LocalPoint } from "../../math";
|
import type { GlobalPoint, LineSegment, LocalPoint } from "../../math";
|
||||||
import {
|
import {
|
||||||
pointFrom,
|
pointFrom,
|
||||||
@@ -90,7 +90,11 @@ export const resizeTest = <Point extends GlobalPoint | LocalPoint>(
|
|||||||
|
|
||||||
// do not resize from the sides for linear elements with only two points
|
// do not resize from the sides for linear elements with only two points
|
||||||
if (!(isLinearElement(element) && element.points.length <= 2)) {
|
if (!(isLinearElement(element) && element.points.length <= 2)) {
|
||||||
const SPACING = SIDE_RESIZING_THRESHOLD / zoom.value;
|
const SPACING = isImageElement(element)
|
||||||
|
? 0
|
||||||
|
: SIDE_RESIZING_THRESHOLD / zoom.value;
|
||||||
|
const ZOOMED_SIDE_RESIZING_THRESHOLD =
|
||||||
|
SIDE_RESIZING_THRESHOLD / zoom.value;
|
||||||
const sides = getSelectionBorders(
|
const sides = getSelectionBorders(
|
||||||
pointFrom(x1 - SPACING, y1 - SPACING),
|
pointFrom(x1 - SPACING, y1 - SPACING),
|
||||||
pointFrom(x2 + SPACING, y2 + SPACING),
|
pointFrom(x2 + SPACING, y2 + SPACING),
|
||||||
@@ -104,7 +108,7 @@ export const resizeTest = <Point extends GlobalPoint | LocalPoint>(
|
|||||||
pointOnLineSegment(
|
pointOnLineSegment(
|
||||||
pointFrom(x, y),
|
pointFrom(x, y),
|
||||||
side as LineSegment<Point>,
|
side as LineSegment<Point>,
|
||||||
SPACING,
|
ZOOMED_SIDE_RESIZING_THRESHOLD,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return dir as TransformHandleType;
|
return dir as TransformHandleType;
|
||||||
|
|||||||
@@ -1,541 +0,0 @@
|
|||||||
import { useEffect } from "react";
|
|
||||||
import type {
|
|
||||||
ElementsMap,
|
|
||||||
ExcalidrawElement,
|
|
||||||
ExcalidrawTextElement,
|
|
||||||
NonDeleted,
|
|
||||||
} from "../types";
|
|
||||||
import { getNonDeletedElements } from "../";
|
|
||||||
import { getSelectedElements } from "../../scene";
|
|
||||||
import type { AppState, ExcalidrawImperativeAPI, ToolType } from "../../types";
|
|
||||||
import type { LangLdr } from "../../i18n";
|
|
||||||
import { registerCustomLangData } from "../../i18n";
|
|
||||||
|
|
||||||
import type {
|
|
||||||
Action,
|
|
||||||
ActionName,
|
|
||||||
ActionPredicateFn,
|
|
||||||
CustomActionName,
|
|
||||||
} from "../../actions/types";
|
|
||||||
import { makeCustomActionName } from "../../actions/types";
|
|
||||||
import { registerCustomShortcuts } from "../../actions/shortcuts";
|
|
||||||
import { register } from "../../actions/register";
|
|
||||||
import { hasBoundTextElement, isTextElement } from "../typeChecks";
|
|
||||||
import {
|
|
||||||
getBoundTextElement,
|
|
||||||
getContainerElement,
|
|
||||||
redrawTextBoundingBox,
|
|
||||||
} from "../textElement";
|
|
||||||
import { ShapeCache } from "../../scene/ShapeCache";
|
|
||||||
import Scene from "../../scene/Scene";
|
|
||||||
|
|
||||||
// Use "let" instead of "const" so we can dynamically add subtypes
|
|
||||||
let subtypeNames: readonly Subtype[] = [];
|
|
||||||
let parentTypeMap: readonly {
|
|
||||||
subtype: Subtype;
|
|
||||||
parentType: ExcalidrawElement["type"];
|
|
||||||
}[] = [];
|
|
||||||
let subtypeActionMap: readonly {
|
|
||||||
subtype: Subtype;
|
|
||||||
actions: readonly ActionName[];
|
|
||||||
}[] = [];
|
|
||||||
let disabledActionMap: readonly {
|
|
||||||
subtype: Subtype;
|
|
||||||
actions: readonly DisabledActionName[];
|
|
||||||
}[] = [];
|
|
||||||
let alwaysEnabledMap: readonly {
|
|
||||||
subtype: Subtype;
|
|
||||||
actions: readonly SubtypeActionName[];
|
|
||||||
}[] = [];
|
|
||||||
|
|
||||||
export type SubtypeRecord = Readonly<{
|
|
||||||
subtype: Subtype;
|
|
||||||
parents: readonly (ExcalidrawElement["type"] & ToolType)[];
|
|
||||||
actionNames?: readonly SubtypeActionName[];
|
|
||||||
disabledNames?: readonly DisabledActionName[];
|
|
||||||
shortcutMap?: Record<string, string[]>;
|
|
||||||
alwaysEnabledNames?: readonly SubtypeActionName[];
|
|
||||||
}>;
|
|
||||||
|
|
||||||
// Subtype Names
|
|
||||||
export type Subtype = Required<ExcalidrawElement>["subtype"];
|
|
||||||
export const getSubtypeNames = (): readonly Subtype[] => {
|
|
||||||
return subtypeNames;
|
|
||||||
};
|
|
||||||
export const isValidSubtype = (s: any, t: any): s is Subtype =>
|
|
||||||
parentTypeMap.find(
|
|
||||||
(val) => (val.subtype as any) === s && (val.parentType as any) === t,
|
|
||||||
) !== undefined;
|
|
||||||
const isSubtypeName = (s: any): s is Subtype => subtypeNames.includes(s);
|
|
||||||
|
|
||||||
// Subtype Actions
|
|
||||||
|
|
||||||
// Used for context menus in the shape chooser
|
|
||||||
export const hasAlwaysEnabledActions = (s: any): boolean => {
|
|
||||||
if (!isSubtypeName(s)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return alwaysEnabledMap.some((value) => value.subtype === s);
|
|
||||||
};
|
|
||||||
|
|
||||||
type SubtypeActionName = string;
|
|
||||||
|
|
||||||
const isSubtypeActionName = (s: any): s is SubtypeActionName =>
|
|
||||||
subtypeActionMap.some((val) => val.actions.includes(s));
|
|
||||||
|
|
||||||
const addSubtypeAction = (action: Action) => {
|
|
||||||
if (isSubtypeActionName(action.name) || isSubtypeName(action.name)) {
|
|
||||||
register(action);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Standard actions disabled by subtypes
|
|
||||||
type DisabledActionName = ActionName;
|
|
||||||
|
|
||||||
const isDisabledActionName = (s: any): s is DisabledActionName =>
|
|
||||||
disabledActionMap.some((val) => val.actions.includes(s));
|
|
||||||
|
|
||||||
// Is the `actionName` one of the subtype actions for `subtype`
|
|
||||||
// (if `isAdded` is true) or one of the standard actions disabled
|
|
||||||
// by `subtype` (if `isAdded` is false)?
|
|
||||||
const isForSubtype = (
|
|
||||||
subtype: ExcalidrawElement["subtype"],
|
|
||||||
actionName: ActionName,
|
|
||||||
isAdded: boolean,
|
|
||||||
) => {
|
|
||||||
const actions = isAdded ? subtypeActionMap : disabledActionMap;
|
|
||||||
const map = actions.find((value) => value.subtype === subtype);
|
|
||||||
if (map) {
|
|
||||||
return map.actions.includes(actionName);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const isSubtypeAction: ActionPredicateFn = function (action) {
|
|
||||||
return isSubtypeActionName(action.name) && !isSubtypeName(action.name);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const subtypeActionPredicate: ActionPredicateFn = function (
|
|
||||||
action,
|
|
||||||
elements,
|
|
||||||
appState,
|
|
||||||
app,
|
|
||||||
) {
|
|
||||||
// We always enable subtype actions. Also let through standard actions
|
|
||||||
// which no subtypes might have disabled.
|
|
||||||
if (
|
|
||||||
isSubtypeName(action.name) ||
|
|
||||||
(!isSubtypeActionName(action.name) && !isDisabledActionName(action.name))
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const selectedElements = getSelectedElements(
|
|
||||||
getNonDeletedElements(elements),
|
|
||||||
appState,
|
|
||||||
);
|
|
||||||
const chosen = appState.editingTextElement
|
|
||||||
? [appState.editingTextElement, ...selectedElements]
|
|
||||||
: selectedElements;
|
|
||||||
// Now handle actions added by subtypes
|
|
||||||
if (isSubtypeActionName(action.name)) {
|
|
||||||
// Has any ExcalidrawElement enabled this actionName through having
|
|
||||||
// its subtype?
|
|
||||||
return (
|
|
||||||
chosen.some((el) => {
|
|
||||||
const e = hasBoundTextElement(el)
|
|
||||||
? getBoundTextElement(el, app.scene.getElementsMapIncludingDeleted())!
|
|
||||||
: el;
|
|
||||||
return isForSubtype(e.subtype, action.name, true);
|
|
||||||
}) ||
|
|
||||||
// Or has any active subtype enabled this actionName?
|
|
||||||
(appState.activeSubtypes !== undefined &&
|
|
||||||
appState.activeSubtypes?.some((subtype) => {
|
|
||||||
if (!isValidSubtype(subtype, appState.activeTool.type)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return isForSubtype(subtype, action.name, true);
|
|
||||||
})) ||
|
|
||||||
alwaysEnabledMap.some((value) => {
|
|
||||||
return value.actions.includes(action.name);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// Now handle standard actions disabled by subtypes
|
|
||||||
if (isDisabledActionName(action.name)) {
|
|
||||||
return (
|
|
||||||
// Has every ExcalidrawElement not disabled this actionName?
|
|
||||||
(chosen.every((el) => {
|
|
||||||
const e = hasBoundTextElement(el)
|
|
||||||
? getBoundTextElement(el, app.scene.getElementsMapIncludingDeleted())!
|
|
||||||
: el;
|
|
||||||
return !isForSubtype(e.subtype, action.name, false);
|
|
||||||
}) &&
|
|
||||||
// And has every active subtype not disabled this actionName?
|
|
||||||
(appState.activeSubtypes === undefined ||
|
|
||||||
appState.activeSubtypes?.every((subtype) => {
|
|
||||||
if (!isValidSubtype(subtype, appState.activeTool.type)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return !isForSubtype(subtype, action.name, false);
|
|
||||||
}))) ||
|
|
||||||
// Or can we find an ExcalidrawElement without a valid subtype
|
|
||||||
// which would disable this action if it had a valid subtype?
|
|
||||||
chosen.some((el) => {
|
|
||||||
const e = hasBoundTextElement(el)
|
|
||||||
? getBoundTextElement(el, app.scene.getElementsMapIncludingDeleted())!
|
|
||||||
: el;
|
|
||||||
return parentTypeMap.some(
|
|
||||||
(value) =>
|
|
||||||
value.parentType === e.type &&
|
|
||||||
!isValidSubtype(e.subtype, e.type) &&
|
|
||||||
isForSubtype(value.subtype, action.name, false),
|
|
||||||
);
|
|
||||||
}) ||
|
|
||||||
chosen.some((el) => {
|
|
||||||
const e = hasBoundTextElement(el)
|
|
||||||
? getBoundTextElement(el, app.scene.getElementsMapIncludingDeleted())!
|
|
||||||
: el;
|
|
||||||
return (
|
|
||||||
// Would the subtype of e by inself disable this action?
|
|
||||||
isForSubtype(e.subtype, action.name, false) &&
|
|
||||||
// Can we find an ExcalidrawElement which could have the same subtype
|
|
||||||
// as e but whose subtype does not disable this action?
|
|
||||||
chosen.some((el) => {
|
|
||||||
const e2 = hasBoundTextElement(el)
|
|
||||||
? getBoundTextElement(
|
|
||||||
el,
|
|
||||||
app.scene.getElementsMapIncludingDeleted(),
|
|
||||||
)!
|
|
||||||
: el;
|
|
||||||
return (
|
|
||||||
// Does e have a valid subtype whose parent types include the
|
|
||||||
// type of e2, and does the subtype of e2 not disable this action?
|
|
||||||
parentTypeMap
|
|
||||||
.filter((val) => val.subtype === e.subtype)
|
|
||||||
.some((val) => val.parentType === e2.type) &&
|
|
||||||
!isForSubtype(e2.subtype, action.name, false)
|
|
||||||
);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// Shouldn't happen
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Are any of the parent types of `subtype` shared by any subtype
|
|
||||||
// in the array?
|
|
||||||
export const subtypeCollides = (subtype: Subtype, subtypeArray: Subtype[]) => {
|
|
||||||
const subtypeParents = parentTypeMap
|
|
||||||
.filter((value) => value.subtype === subtype)
|
|
||||||
.map((value) => value.parentType);
|
|
||||||
const subtypeArrayParents = subtypeArray.flatMap((s) =>
|
|
||||||
parentTypeMap
|
|
||||||
.filter((value) => value.subtype === s)
|
|
||||||
.map((value) => value.parentType),
|
|
||||||
);
|
|
||||||
return subtypeParents.some((t) => subtypeArrayParents.includes(t));
|
|
||||||
};
|
|
||||||
|
|
||||||
// Subtype Methods
|
|
||||||
export type SubtypeMethods = {
|
|
||||||
clean: (
|
|
||||||
updates: Omit<
|
|
||||||
Partial<ExcalidrawElement>,
|
|
||||||
"id" | "version" | "versionNonce"
|
|
||||||
>,
|
|
||||||
) => Omit<Partial<ExcalidrawElement>, "id" | "version" | "versionNonce">;
|
|
||||||
getEditorStyle: (element: ExcalidrawTextElement) => Record<string, any>;
|
|
||||||
ensureLoaded: (callback?: () => void) => Promise<void>;
|
|
||||||
measureText: (
|
|
||||||
element: Pick<
|
|
||||||
ExcalidrawTextElement,
|
|
||||||
| "subtype"
|
|
||||||
| "customData"
|
|
||||||
| "fontSize"
|
|
||||||
| "fontFamily"
|
|
||||||
| "text"
|
|
||||||
| "lineHeight"
|
|
||||||
>,
|
|
||||||
next?: {
|
|
||||||
fontSize?: number;
|
|
||||||
text?: string;
|
|
||||||
customData?: ExcalidrawElement["customData"];
|
|
||||||
},
|
|
||||||
) => { width: number; height: number };
|
|
||||||
render: (
|
|
||||||
element: NonDeleted<ExcalidrawElement>,
|
|
||||||
elementsMap: ElementsMap,
|
|
||||||
context: CanvasRenderingContext2D,
|
|
||||||
) => void;
|
|
||||||
renderSvg: (
|
|
||||||
svgRoot: SVGElement,
|
|
||||||
addToRoot: (node: SVGElement, element: ExcalidrawElement) => void,
|
|
||||||
element: NonDeleted<ExcalidrawElement>,
|
|
||||||
elementsMap: ElementsMap,
|
|
||||||
opt?: { offsetX?: number; offsetY?: number },
|
|
||||||
) => void;
|
|
||||||
wrapText: (
|
|
||||||
element: Pick<
|
|
||||||
ExcalidrawTextElement,
|
|
||||||
| "subtype"
|
|
||||||
| "customData"
|
|
||||||
| "fontSize"
|
|
||||||
| "fontFamily"
|
|
||||||
| "originalText"
|
|
||||||
| "lineHeight"
|
|
||||||
>,
|
|
||||||
containerWidth: number,
|
|
||||||
next?: {
|
|
||||||
fontSize?: number;
|
|
||||||
text?: string;
|
|
||||||
customData?: ExcalidrawElement["customData"];
|
|
||||||
},
|
|
||||||
) => string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type MethodMap = { subtype: Subtype; methods: Partial<SubtypeMethods> };
|
|
||||||
const methodMaps = [] as Array<MethodMap>;
|
|
||||||
|
|
||||||
// Use `getSubtypeMethods` to call subtype-specialized methods, like `render`.
|
|
||||||
export const getSubtypeMethods = (
|
|
||||||
subtype: Subtype | undefined,
|
|
||||||
): Partial<SubtypeMethods> | undefined => {
|
|
||||||
const map = methodMaps.find((method) => method.subtype === subtype);
|
|
||||||
return map?.methods;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const addSubtypeMethods = (
|
|
||||||
subtype: Subtype,
|
|
||||||
methods: Partial<SubtypeMethods>,
|
|
||||||
) => {
|
|
||||||
if (!methodMaps.find((method) => method.subtype === subtype)) {
|
|
||||||
methodMaps.push({ subtype, methods });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// For a given `ExcalidrawElement` type, return the active subtype
|
|
||||||
// and associated customData (if any) from the AppState. Assume
|
|
||||||
// only one subtype is active for a given `ExcalidrawElement` type
|
|
||||||
// at any given time.
|
|
||||||
export const selectSubtype = (
|
|
||||||
appState: {
|
|
||||||
activeSubtypes?: AppState["activeSubtypes"];
|
|
||||||
customData?: AppState["customData"];
|
|
||||||
},
|
|
||||||
type: ExcalidrawElement["type"],
|
|
||||||
): {
|
|
||||||
subtype?: ExcalidrawElement["subtype"];
|
|
||||||
customData?: ExcalidrawElement["customData"];
|
|
||||||
} => {
|
|
||||||
if (appState.activeSubtypes === undefined) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
const subtype = appState.activeSubtypes.find((subtype) =>
|
|
||||||
isValidSubtype(subtype, type),
|
|
||||||
);
|
|
||||||
if (subtype === undefined) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
if (appState.customData === undefined || !(subtype in appState.customData)) {
|
|
||||||
return { subtype };
|
|
||||||
}
|
|
||||||
const customData = appState.customData[subtype];
|
|
||||||
return { subtype, customData };
|
|
||||||
};
|
|
||||||
|
|
||||||
// Callback to re-render subtyped `ExcalidrawElement`s after completing
|
|
||||||
// async loading of the subtype.
|
|
||||||
export type SubtypeLoadedCb = (hasSubtype: SubtypeCheckFn) => void;
|
|
||||||
export type SubtypeCheckFn = (element: ExcalidrawElement) => boolean;
|
|
||||||
|
|
||||||
// Functions to prepare subtypes for use
|
|
||||||
export type SubtypePrepFn = (
|
|
||||||
addSubtypeAction: (action: Action) => void,
|
|
||||||
addLangData: (fallbackLangData: {}, setLanguageAux: LangLdr) => void,
|
|
||||||
onSubtypeLoaded?: SubtypeLoadedCb,
|
|
||||||
) => {
|
|
||||||
actions: Action[];
|
|
||||||
methods: Partial<SubtypeMethods>;
|
|
||||||
};
|
|
||||||
|
|
||||||
// This is the main method to set up the subtype. The optional
|
|
||||||
// `onSubtypeLoaded` callback may be used to re-render subtyped
|
|
||||||
// `ExcalidrawElement`s after the subtype has finished async loading.
|
|
||||||
// See the MathJax extension in `@excalidraw/extensions` for example.
|
|
||||||
export const prepareSubtype = (
|
|
||||||
record: SubtypeRecord,
|
|
||||||
subtypePrepFn: SubtypePrepFn,
|
|
||||||
onSubtypeLoaded?: SubtypeLoadedCb,
|
|
||||||
): { actions: readonly Action[] | null; methods: Partial<SubtypeMethods> } => {
|
|
||||||
const map = getSubtypeMethods(record.subtype);
|
|
||||||
if (map) {
|
|
||||||
return { actions: null, methods: map };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for undefined/null subtypes and parentTypes
|
|
||||||
if (
|
|
||||||
record.subtype === undefined ||
|
|
||||||
record.subtype === "" ||
|
|
||||||
record.parents === undefined ||
|
|
||||||
record.parents.length === 0
|
|
||||||
) {
|
|
||||||
return { actions: null, methods: {} };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register the types
|
|
||||||
const subtype = record.subtype;
|
|
||||||
subtypeNames = [...subtypeNames, subtype];
|
|
||||||
record.parents.forEach((parentType) => {
|
|
||||||
parentTypeMap = [...parentTypeMap, { subtype, parentType }];
|
|
||||||
});
|
|
||||||
if (record.actionNames) {
|
|
||||||
subtypeActionMap = [
|
|
||||||
...subtypeActionMap,
|
|
||||||
{
|
|
||||||
subtype,
|
|
||||||
actions: record.actionNames.map((actionName) =>
|
|
||||||
makeCustomActionName(actionName),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
if (record.disabledNames) {
|
|
||||||
disabledActionMap = [
|
|
||||||
...disabledActionMap,
|
|
||||||
{ subtype, actions: record.disabledNames },
|
|
||||||
];
|
|
||||||
}
|
|
||||||
if (record.alwaysEnabledNames) {
|
|
||||||
alwaysEnabledMap = [
|
|
||||||
...alwaysEnabledMap,
|
|
||||||
{
|
|
||||||
subtype,
|
|
||||||
actions: record.alwaysEnabledNames.map((actionName) =>
|
|
||||||
makeCustomActionName(actionName),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
const customShortcutMap = record.shortcutMap;
|
|
||||||
if (customShortcutMap) {
|
|
||||||
const shortcutMap: Record<CustomActionName, string[]> = {};
|
|
||||||
for (const key in customShortcutMap) {
|
|
||||||
shortcutMap[makeCustomActionName(key)] = customShortcutMap[key];
|
|
||||||
}
|
|
||||||
registerCustomShortcuts(shortcutMap);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare the subtype
|
|
||||||
const { actions, methods } = subtypePrepFn(
|
|
||||||
addSubtypeAction,
|
|
||||||
registerCustomLangData,
|
|
||||||
onSubtypeLoaded,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Register the subtype's methods
|
|
||||||
addSubtypeMethods(record.subtype, methods);
|
|
||||||
return { actions, methods };
|
|
||||||
};
|
|
||||||
|
|
||||||
// Ensure all subtypes are loaded before continuing, eg to
|
|
||||||
// render SVG previews of new charts. Chart-relevant subtypes
|
|
||||||
// include math equations in titles or non hand-drawn line styles.
|
|
||||||
export const ensureSubtypesLoadedForElements = async (
|
|
||||||
elements: readonly ExcalidrawElement[],
|
|
||||||
callback?: () => void,
|
|
||||||
) => {
|
|
||||||
// Only ensure the loading of subtypes which are actually needed.
|
|
||||||
// We don't want to be held up by eg downloading the MathJax SVG fonts
|
|
||||||
// if we don't actually need them yet.
|
|
||||||
const subtypesUsed = [] as Subtype[];
|
|
||||||
elements.forEach((el) => {
|
|
||||||
if (
|
|
||||||
"subtype" in el &&
|
|
||||||
isValidSubtype(el.subtype, el.type) &&
|
|
||||||
!subtypesUsed.includes(el.subtype)
|
|
||||||
) {
|
|
||||||
subtypesUsed.push(el.subtype);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
await ensureSubtypesLoaded(subtypesUsed, callback);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ensureSubtypesLoaded = async (
|
|
||||||
subtypes: Subtype[],
|
|
||||||
callback?: () => void,
|
|
||||||
) => {
|
|
||||||
// Use a for loop so we can do `await map.ensureLoaded()`
|
|
||||||
for (let i = 0; i < subtypes.length; i++) {
|
|
||||||
const subtype = subtypes[i];
|
|
||||||
// Should be defined if prepareSubtype() has run
|
|
||||||
const map = getSubtypeMethods(subtype);
|
|
||||||
if (map?.ensureLoaded) {
|
|
||||||
await map.ensureLoaded();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (callback) {
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Call this method after finishing any async loading for
|
|
||||||
// subtypes of ExcalidrawElement if the newly loaded code
|
|
||||||
// would change the rendering.
|
|
||||||
export const checkRefreshOnSubtypeLoad = (
|
|
||||||
hasSubtype: SubtypeCheckFn,
|
|
||||||
elements: readonly ExcalidrawElement[],
|
|
||||||
) => {
|
|
||||||
const elementsMap = new Map() as ElementsMap;
|
|
||||||
for (const element of elements) {
|
|
||||||
if (!element.isDeleted) {
|
|
||||||
elementsMap.set(element.id, element);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let refreshNeeded = false;
|
|
||||||
const scenes: Scene[] = [];
|
|
||||||
getNonDeletedElements(elements).forEach((element) => {
|
|
||||||
// If the element is of the subtype that was just
|
|
||||||
// registered, update the element's dimensions, mark the
|
|
||||||
// element for a re-render, and indicate the scene needs a refresh.
|
|
||||||
if (hasSubtype(element)) {
|
|
||||||
ShapeCache.delete(element);
|
|
||||||
if (isTextElement(element)) {
|
|
||||||
redrawTextBoundingBox(
|
|
||||||
element,
|
|
||||||
getContainerElement(element, elementsMap),
|
|
||||||
elementsMap,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
refreshNeeded = true;
|
|
||||||
const scene = Scene.getScene(element);
|
|
||||||
if (scene && !scenes.includes(scene)) {
|
|
||||||
// Store in case we have multiple scenes
|
|
||||||
scenes.push(scene);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Only inform each scene once
|
|
||||||
scenes.forEach((scene) => scene.triggerUpdate());
|
|
||||||
return refreshNeeded;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useSubtype = (
|
|
||||||
api: ExcalidrawImperativeAPI | null,
|
|
||||||
record: SubtypeRecord,
|
|
||||||
subtypePrepFn: SubtypePrepFn,
|
|
||||||
) => {
|
|
||||||
useEffect(() => {
|
|
||||||
if (api) {
|
|
||||||
const prep = api.addSubtype(record, subtypePrepFn);
|
|
||||||
if (prep) {
|
|
||||||
addSubtypeMethods(record.subtype, prep.methods);
|
|
||||||
if (prep.actions) {
|
|
||||||
prep.actions.forEach((action) => api.registerAction(action));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [api, record, subtypePrepFn]);
|
|
||||||
};
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import type { Theme } from "../../../element/types";
|
|
||||||
import { createIcon, iconFillColor } from "../../../components/icons";
|
|
||||||
|
|
||||||
// We inline font-awesome icons in order to save on js size rather than including the font awesome react library
|
|
||||||
export const mathSubtypeIcon = ({ theme }: { theme: Theme }) =>
|
|
||||||
createIcon(
|
|
||||||
<path
|
|
||||||
fill={iconFillColor(theme)}
|
|
||||||
// fa-square-root-variable-solid
|
|
||||||
d="M289 24.2C292.5 10 305.3 0 320 0H544c17.7 0 32 14.3 32 32s-14.3 32-32 32H345L239 487.8c-3.2 13-14.2 22.6-27.6 24s-26.1-5.5-32.1-17.5L76.2 288H32c-17.7 0-32-14.3-32-32s14.3-32 32-32H96c12.1 0 23.2 6.8 28.6 17.7l73.3 146.6L289 24.2zM393.4 233.4c12.5-12.5 32.8-12.5 45.3 0L480 274.7l41.4-41.4c12.5-12.5 32.8-12.5 45.3 0s12.5 32.8 0 45.3L525.3 320l41.4 41.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L480 365.3l-41.4 41.4c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L434.7 320l-41.4-41.4c-12.5-12.5-12.5-32.8 0-45.3z"
|
|
||||||
/>,
|
|
||||||
{ width: 576, height: 512, mirror: true, strokeWidth: 1.25 },
|
|
||||||
);
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,15 +0,0 @@
|
|||||||
import type { ExcalidrawImperativeAPI } from "../../../types";
|
|
||||||
import { useSubtype } from "../";
|
|
||||||
import { getMathSubtypeRecord } from "./types";
|
|
||||||
import { prepareMathSubtype } from "./implementation";
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
module SREfeature {
|
|
||||||
function custom(locale: string): Promise<string>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The main hook to use the MathJax subtype
|
|
||||||
export const useMathSubtype = (api: ExcalidrawImperativeAPI | null) => {
|
|
||||||
useSubtype(api, getMathSubtypeRecord(), prepareMathSubtype);
|
|
||||||
};
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"labels": {
|
|
||||||
"changeMathOnly": "Math display",
|
|
||||||
"mathOnlyTrue": "Math only",
|
|
||||||
"mathOnlyFalse": "Mixed text",
|
|
||||||
"resetUseTex": "Reset math input type",
|
|
||||||
"useTexTrueActive": "✔ Standard input",
|
|
||||||
"useTexTrueInactive": "Standard input",
|
|
||||||
"useTexFalseActive": "✔ Simplified input",
|
|
||||||
"useTexFalseInactive": "Simplified input"
|
|
||||||
},
|
|
||||||
"toolBar": {
|
|
||||||
"math": "Math"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
import { vi } from "vitest";
|
|
||||||
import { render } from "../../../../tests/test-utils";
|
|
||||||
import { API } from "../../../../tests/helpers/api";
|
|
||||||
import { Excalidraw } from "../../../../index";
|
|
||||||
|
|
||||||
import { measureTextElement } from "../../../textElement";
|
|
||||||
import { ensureSubtypesLoaded } from "../../";
|
|
||||||
import { getMathSubtypeRecord } from "../types";
|
|
||||||
import { prepareMathSubtype } from "../implementation";
|
|
||||||
|
|
||||||
describe("mathjax loaded", () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
await render(<Excalidraw />);
|
|
||||||
API.addSubtype(getMathSubtypeRecord(), prepareMathSubtype);
|
|
||||||
await ensureSubtypesLoaded(["math"]);
|
|
||||||
});
|
|
||||||
it("text-only measurements match", async () => {
|
|
||||||
const text = "A quick brown fox jumps over the lazy dog.";
|
|
||||||
const elements = [
|
|
||||||
API.createElement({ type: "text", id: "A", text, subtype: "math" }),
|
|
||||||
API.createElement({ type: "text", id: "B", text }),
|
|
||||||
];
|
|
||||||
const metrics1 = measureTextElement(elements[0]);
|
|
||||||
const metrics2 = measureTextElement(elements[1]);
|
|
||||||
expect(metrics1).toStrictEqual(metrics2);
|
|
||||||
});
|
|
||||||
it("minimum height remains", async () => {
|
|
||||||
const elements = [
|
|
||||||
API.createElement({ type: "text", id: "A", text: "a" }),
|
|
||||||
API.createElement({
|
|
||||||
type: "text",
|
|
||||||
id: "B",
|
|
||||||
text: "\\(\\alpha\\)",
|
|
||||||
subtype: "math",
|
|
||||||
customData: { useTex: true },
|
|
||||||
}),
|
|
||||||
API.createElement({
|
|
||||||
type: "text",
|
|
||||||
id: "C",
|
|
||||||
text: "`beta`",
|
|
||||||
subtype: "math",
|
|
||||||
customData: { useTex: false },
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
const height = measureTextElement(elements[0]).height;
|
|
||||||
const height1 = measureTextElement(elements[1]).height;
|
|
||||||
const height2 = measureTextElement(elements[2]).height;
|
|
||||||
expect(height).toEqual(height1);
|
|
||||||
expect(height).toEqual(height2);
|
|
||||||
});
|
|
||||||
it("converts math to svgs", async () => {
|
|
||||||
const svgDim = 42;
|
|
||||||
vi.spyOn(SVGElement.prototype, "getBoundingClientRect").mockImplementation(
|
|
||||||
() => new DOMRect(0, 0, svgDim, svgDim),
|
|
||||||
);
|
|
||||||
const elements = [];
|
|
||||||
const type = "text";
|
|
||||||
const subtype = "math";
|
|
||||||
let text = "Math ";
|
|
||||||
elements.push(API.createElement({ type, text }));
|
|
||||||
text = "Math \\(\\alpha\\)";
|
|
||||||
elements.push(
|
|
||||||
API.createElement({ type, subtype, text, customData: { useTex: true } }),
|
|
||||||
);
|
|
||||||
text = "Math `beta`";
|
|
||||||
elements.push(
|
|
||||||
API.createElement({ type, subtype, text, customData: { useTex: false } }),
|
|
||||||
);
|
|
||||||
const metrics = {
|
|
||||||
width: measureTextElement(elements[0]).width + svgDim,
|
|
||||||
height: svgDim,
|
|
||||||
};
|
|
||||||
expect(measureTextElement(elements[1])).toStrictEqual(metrics);
|
|
||||||
expect(measureTextElement(elements[2])).toStrictEqual(metrics);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import { getShortcutKey } from "../../../utils";
|
|
||||||
import type { SubtypeRecord } from "../";
|
|
||||||
|
|
||||||
// Exports
|
|
||||||
export const getMathSubtypeRecord = () => mathSubtype;
|
|
||||||
|
|
||||||
// Use `getMathSubtype` so we don't have to export this
|
|
||||||
const mathSubtype: SubtypeRecord = {
|
|
||||||
subtype: "math",
|
|
||||||
parents: ["text"],
|
|
||||||
actionNames: ["useTexTrue", "useTexFalse", "resetUseTex", "changeMathOnly"],
|
|
||||||
disabledNames: ["changeFontFamily"],
|
|
||||||
shortcutMap: {
|
|
||||||
resetUseTex: [getShortcutKey("Shift+R")],
|
|
||||||
},
|
|
||||||
alwaysEnabledNames: ["useTexTrue", "useTexFalse"],
|
|
||||||
};
|
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
import type { SubtypeMethods } from "./subtypes";
|
|
||||||
import { getSubtypeMethods } from "./subtypes";
|
|
||||||
import { getFontString, arrayToMap, isTestEnv, normalizeEOL } from "../utils";
|
import { getFontString, arrayToMap, isTestEnv, normalizeEOL } from "../utils";
|
||||||
import type {
|
import type {
|
||||||
ElementsMap,
|
ElementsMap,
|
||||||
@@ -32,30 +30,6 @@ import {
|
|||||||
} from "./containerCache";
|
} from "./containerCache";
|
||||||
import type { ExtractSetType } from "../utility-types";
|
import type { ExtractSetType } from "../utility-types";
|
||||||
|
|
||||||
export const measureTextElement = function (element, next) {
|
|
||||||
const map = getSubtypeMethods(element.subtype);
|
|
||||||
if (map?.measureText) {
|
|
||||||
return map.measureText(element, next);
|
|
||||||
}
|
|
||||||
|
|
||||||
const fontSize = next?.fontSize ?? element.fontSize;
|
|
||||||
const font = getFontString({ fontSize, fontFamily: element.fontFamily });
|
|
||||||
const text = next?.text ?? element.text;
|
|
||||||
return measureText(text, font, element.lineHeight);
|
|
||||||
} as SubtypeMethods["measureText"];
|
|
||||||
|
|
||||||
export const wrapTextElement = function (element, containerWidth, next) {
|
|
||||||
const map = getSubtypeMethods(element.subtype);
|
|
||||||
if (map?.wrapText) {
|
|
||||||
return map.wrapText(element, containerWidth, next);
|
|
||||||
}
|
|
||||||
|
|
||||||
const fontSize = next?.fontSize ?? element.fontSize;
|
|
||||||
const font = getFontString({ fontSize, fontFamily: element.fontFamily });
|
|
||||||
const text = next?.text ?? element.originalText;
|
|
||||||
return wrapText(text, font, containerWidth);
|
|
||||||
} as SubtypeMethods["wrapText"];
|
|
||||||
|
|
||||||
export const normalizeText = (text: string) => {
|
export const normalizeText = (text: string) => {
|
||||||
return (
|
return (
|
||||||
normalizeEOL(text)
|
normalizeEOL(text)
|
||||||
@@ -90,12 +64,18 @@ export const redrawTextBoundingBox = (
|
|||||||
maxWidth = container
|
maxWidth = container
|
||||||
? getBoundTextMaxWidth(container, textElement)
|
? getBoundTextMaxWidth(container, textElement)
|
||||||
: textElement.width;
|
: textElement.width;
|
||||||
boundTextUpdates.text = wrapTextElement(textElement, maxWidth);
|
boundTextUpdates.text = wrapText(
|
||||||
|
textElement.originalText,
|
||||||
|
getFontString(textElement),
|
||||||
|
maxWidth,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const metrics = measureTextElement(textElement, {
|
const metrics = measureText(
|
||||||
text: boundTextUpdates.text,
|
boundTextUpdates.text,
|
||||||
});
|
getFontString(textElement),
|
||||||
|
textElement.lineHeight,
|
||||||
|
);
|
||||||
|
|
||||||
// Note: only update width for unwrapped text and bound texts (which always have autoResize set to true)
|
// Note: only update width for unwrapped text and bound texts (which always have autoResize set to true)
|
||||||
if (textElement.autoResize) {
|
if (textElement.autoResize) {
|
||||||
@@ -103,14 +83,6 @@ export const redrawTextBoundingBox = (
|
|||||||
}
|
}
|
||||||
boundTextUpdates.height = metrics.height;
|
boundTextUpdates.height = metrics.height;
|
||||||
|
|
||||||
// Maintain coordX for non left-aligned text in case the width has changed
|
|
||||||
if (!container) {
|
|
||||||
if (textElement.textAlign === TEXT_ALIGN.RIGHT) {
|
|
||||||
boundTextUpdates.x += textElement.width - metrics.width;
|
|
||||||
} else if (textElement.textAlign === TEXT_ALIGN.CENTER) {
|
|
||||||
boundTextUpdates.x += textElement.width / 2 - metrics.width / 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (container) {
|
if (container) {
|
||||||
const maxContainerHeight = getBoundTextMaxHeight(
|
const maxContainerHeight = getBoundTextMaxHeight(
|
||||||
container,
|
container,
|
||||||
@@ -219,9 +191,17 @@ export const handleBindTextResize = (
|
|||||||
(transformHandleType !== "n" && transformHandleType !== "s")
|
(transformHandleType !== "n" && transformHandleType !== "s")
|
||||||
) {
|
) {
|
||||||
if (text) {
|
if (text) {
|
||||||
text = wrapTextElement(textElement, maxWidth);
|
text = wrapText(
|
||||||
|
textElement.originalText,
|
||||||
|
getFontString(textElement),
|
||||||
|
maxWidth,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const metrics = measureTextElement(textElement, { text });
|
const metrics = measureText(
|
||||||
|
text,
|
||||||
|
getFontString(textElement),
|
||||||
|
textElement.lineHeight,
|
||||||
|
);
|
||||||
nextHeight = metrics.height;
|
nextHeight = metrics.height;
|
||||||
nextWidth = metrics.width;
|
nextWidth = metrics.width;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import {
|
|||||||
getContainerElement,
|
getContainerElement,
|
||||||
getTextElementAngle,
|
getTextElementAngle,
|
||||||
getTextWidth,
|
getTextWidth,
|
||||||
measureText,
|
|
||||||
normalizeText,
|
normalizeText,
|
||||||
redrawTextBoundingBox,
|
redrawTextBoundingBox,
|
||||||
wrapText,
|
wrapText,
|
||||||
@@ -47,15 +46,12 @@ import {
|
|||||||
import type App from "../components/App";
|
import type App from "../components/App";
|
||||||
import { LinearElementEditor } from "./linearElementEditor";
|
import { LinearElementEditor } from "./linearElementEditor";
|
||||||
import { parseClipboard } from "../clipboard";
|
import { parseClipboard } from "../clipboard";
|
||||||
import type { SubtypeMethods } from "./subtypes";
|
|
||||||
import { getSubtypeMethods } from "./subtypes";
|
|
||||||
import {
|
import {
|
||||||
originalContainerCache,
|
originalContainerCache,
|
||||||
updateOriginalContainerCache,
|
updateOriginalContainerCache,
|
||||||
} from "./containerCache";
|
} from "./containerCache";
|
||||||
|
|
||||||
const getTransform = (
|
const getTransform = (
|
||||||
offsetX: number,
|
|
||||||
width: number,
|
width: number,
|
||||||
height: number,
|
height: number,
|
||||||
angle: number,
|
angle: number,
|
||||||
@@ -73,18 +69,9 @@ const getTransform = (
|
|||||||
if (height > maxHeight && zoom.value !== 1) {
|
if (height > maxHeight && zoom.value !== 1) {
|
||||||
translateY = (maxHeight * (zoom.value - 1)) / 2;
|
translateY = (maxHeight * (zoom.value - 1)) / 2;
|
||||||
}
|
}
|
||||||
const offset = offsetX !== 0 ? ` translate(${offsetX}px, 0px)` : "";
|
return `translate(${translateX}px, ${translateY}px) scale(${zoom.value}) rotate(${degree}deg)`;
|
||||||
return `translate(${translateX}px, ${translateY}px) scale(${zoom.value}) rotate(${degree}deg)${offset}`;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getEditorStyle = function (element) {
|
|
||||||
const map = getSubtypeMethods(element.subtype);
|
|
||||||
if (map?.getEditorStyle) {
|
|
||||||
return map.getEditorStyle(element);
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
} as SubtypeMethods["getEditorStyle"];
|
|
||||||
|
|
||||||
export const textWysiwyg = ({
|
export const textWysiwyg = ({
|
||||||
id,
|
id,
|
||||||
onChange,
|
onChange,
|
||||||
@@ -150,27 +137,14 @@ export const textWysiwyg = ({
|
|||||||
app.scene.getNonDeletedElementsMap(),
|
app.scene.getNonDeletedElementsMap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Editing metrics
|
let width = updatedTextElement.width;
|
||||||
const eMetrics = measureText(
|
|
||||||
container && updatedTextElement.containerId
|
|
||||||
? wrapText(
|
|
||||||
updatedTextElement.originalText,
|
|
||||||
getFontString(updatedTextElement),
|
|
||||||
getBoundTextMaxWidth(container, updatedTextElement),
|
|
||||||
)
|
|
||||||
: updatedTextElement.originalText,
|
|
||||||
getFontString(updatedTextElement),
|
|
||||||
updatedTextElement.lineHeight,
|
|
||||||
);
|
|
||||||
|
|
||||||
let width = Math.max(updatedTextElement.width, eMetrics.width);
|
// set to element height by default since that's
|
||||||
|
|
||||||
// Set to element height by default since that's
|
|
||||||
// what is going to be used for unbounded text
|
// what is going to be used for unbounded text
|
||||||
let height = Math.max(updatedTextElement.height, eMetrics.height);
|
let height = updatedTextElement.height;
|
||||||
|
|
||||||
let maxWidth = width;
|
let maxWidth = updatedTextElement.width;
|
||||||
let maxHeight = height;
|
let maxHeight = updatedTextElement.height;
|
||||||
|
|
||||||
if (container && updatedTextElement.containerId) {
|
if (container && updatedTextElement.containerId) {
|
||||||
if (isArrowElement(container)) {
|
if (isArrowElement(container)) {
|
||||||
@@ -266,31 +240,8 @@ export const textWysiwyg = ({
|
|||||||
width += 0.5;
|
width += 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Horizontal offset in case updatedTextElement has a non-WYSIWYG subtype
|
|
||||||
const offWidth = container
|
|
||||||
? Math.min(
|
|
||||||
0,
|
|
||||||
updatedTextElement.width - Math.min(maxWidth, eMetrics.width),
|
|
||||||
)
|
|
||||||
: Math.min(maxWidth, updatedTextElement.width) -
|
|
||||||
Math.min(maxWidth, eMetrics.width);
|
|
||||||
const offsetX =
|
|
||||||
textAlign === "right"
|
|
||||||
? offWidth
|
|
||||||
: textAlign === "center"
|
|
||||||
? offWidth / 2
|
|
||||||
: 0;
|
|
||||||
let { width: w, height: h } = updatedTextElement;
|
|
||||||
|
|
||||||
// add 5% buffer otherwise it causes wysiwyg to jump
|
// add 5% buffer otherwise it causes wysiwyg to jump
|
||||||
height *= 1.05;
|
height *= 1.05;
|
||||||
h *= 1.05;
|
|
||||||
|
|
||||||
const transformOrigin =
|
|
||||||
updatedTextElement.width !== eMetrics.width ||
|
|
||||||
updatedTextElement.height !== eMetrics.height
|
|
||||||
? { transformOrigin: `${w / 2}px ${h / 2}px` }
|
|
||||||
: {};
|
|
||||||
|
|
||||||
const font = getFontString(updatedTextElement);
|
const font = getFontString(updatedTextElement);
|
||||||
|
|
||||||
@@ -310,9 +261,7 @@ export const textWysiwyg = ({
|
|||||||
height: `${height}px`,
|
height: `${height}px`,
|
||||||
left: `${viewportX - padding}px`,
|
left: `${viewportX - padding}px`,
|
||||||
top: `${viewportY}px`,
|
top: `${viewportY}px`,
|
||||||
...transformOrigin,
|
|
||||||
transform: getTransform(
|
transform: getTransform(
|
||||||
offsetX,
|
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
getTextElementAngle(updatedTextElement, container),
|
getTextElementAngle(updatedTextElement, container),
|
||||||
@@ -373,7 +322,6 @@ export const textWysiwyg = ({
|
|||||||
whiteSpace,
|
whiteSpace,
|
||||||
overflowWrap: "break-word",
|
overflowWrap: "break-word",
|
||||||
boxSizing: "content-box",
|
boxSizing: "content-box",
|
||||||
...getEditorStyle(element),
|
|
||||||
});
|
});
|
||||||
editable.value = element.originalText;
|
editable.value = element.originalText;
|
||||||
updateWysiwygStyle();
|
updateWysiwygStyle();
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import type { Device, InteractiveCanvasAppState, Zoom } from "../types";
|
|||||||
import {
|
import {
|
||||||
isElbowArrow,
|
isElbowArrow,
|
||||||
isFrameLikeElement,
|
isFrameLikeElement,
|
||||||
|
isImageElement,
|
||||||
isLinearElement,
|
isLinearElement,
|
||||||
} from "./typeChecks";
|
} from "./typeChecks";
|
||||||
import {
|
import {
|
||||||
@@ -129,6 +130,7 @@ export const getTransformHandlesFromCoords = (
|
|||||||
pointerType: PointerType,
|
pointerType: PointerType,
|
||||||
omitSides: { [T in TransformHandleType]?: boolean } = {},
|
omitSides: { [T in TransformHandleType]?: boolean } = {},
|
||||||
margin = 4,
|
margin = 4,
|
||||||
|
spacing = DEFAULT_TRANSFORM_HANDLE_SPACING,
|
||||||
): TransformHandles => {
|
): TransformHandles => {
|
||||||
const size = transformHandleSizes[pointerType];
|
const size = transformHandleSizes[pointerType];
|
||||||
const handleWidth = size / zoom.value;
|
const handleWidth = size / zoom.value;
|
||||||
@@ -140,8 +142,7 @@ export const getTransformHandlesFromCoords = (
|
|||||||
const width = x2 - x1;
|
const width = x2 - x1;
|
||||||
const height = y2 - y1;
|
const height = y2 - y1;
|
||||||
const dashedLineMargin = margin / zoom.value;
|
const dashedLineMargin = margin / zoom.value;
|
||||||
const centeringOffset =
|
const centeringOffset = (size - spacing * 2) / (2 * zoom.value);
|
||||||
(size - DEFAULT_TRANSFORM_HANDLE_SPACING * 2) / (2 * zoom.value);
|
|
||||||
|
|
||||||
const transformHandles: TransformHandles = {
|
const transformHandles: TransformHandles = {
|
||||||
nw: omitSides.nw
|
nw: omitSides.nw
|
||||||
@@ -301,8 +302,10 @@ export const getTransformHandles = (
|
|||||||
rotation: true,
|
rotation: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const dashedLineMargin = isLinearElement(element)
|
const margin = isLinearElement(element)
|
||||||
? DEFAULT_TRANSFORM_HANDLE_SPACING + 8
|
? DEFAULT_TRANSFORM_HANDLE_SPACING + 8
|
||||||
|
: isImageElement(element)
|
||||||
|
? 0
|
||||||
: DEFAULT_TRANSFORM_HANDLE_SPACING;
|
: DEFAULT_TRANSFORM_HANDLE_SPACING;
|
||||||
return getTransformHandlesFromCoords(
|
return getTransformHandlesFromCoords(
|
||||||
getElementAbsoluteCoords(element, elementsMap, true),
|
getElementAbsoluteCoords(element, elementsMap, true),
|
||||||
@@ -310,7 +313,8 @@ export const getTransformHandles = (
|
|||||||
zoom,
|
zoom,
|
||||||
pointerType,
|
pointerType,
|
||||||
omitSides,
|
omitSides,
|
||||||
dashedLineMargin,
|
margin,
|
||||||
|
isImageElement(element) ? 0 : undefined,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -76,7 +76,6 @@ type _ExcalidrawElementBase = Readonly<{
|
|||||||
updated: number;
|
updated: number;
|
||||||
link: string | null;
|
link: string | null;
|
||||||
locked: boolean;
|
locked: boolean;
|
||||||
subtype?: string;
|
|
||||||
customData?: Record<string, any>;
|
customData?: Record<string, any>;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
@@ -133,6 +132,15 @@ export type IframeData =
|
|||||||
| { type: "document"; srcdoc: (theme: Theme) => string }
|
| { type: "document"; srcdoc: (theme: Theme) => string }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export type ImageCrop = {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
naturalWidth: number;
|
||||||
|
naturalHeight: number;
|
||||||
|
};
|
||||||
|
|
||||||
export type ExcalidrawImageElement = _ExcalidrawElementBase &
|
export type ExcalidrawImageElement = _ExcalidrawElementBase &
|
||||||
Readonly<{
|
Readonly<{
|
||||||
type: "image";
|
type: "image";
|
||||||
@@ -141,6 +149,8 @@ export type ExcalidrawImageElement = _ExcalidrawElementBase &
|
|||||||
status: "pending" | "saved" | "error";
|
status: "pending" | "saved" | "error";
|
||||||
/** X and Y scale factors <-1, 1>, used for image axis flipping */
|
/** X and Y scale factors <-1, 1>, used for image axis flipping */
|
||||||
scale: [number, number];
|
scale: [number, number];
|
||||||
|
/** whether an element is cropped */
|
||||||
|
crop: ImageCrop | null;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type InitializedExcalidrawImageElement = MarkNonNullable<
|
export type InitializedExcalidrawImageElement = MarkNonNullable<
|
||||||
|
|||||||
Vendored
-2
@@ -104,5 +104,3 @@ declare namespace jest {
|
|||||||
toBeNonNaNNumber(): void;
|
toBeNonNaNNumber(): void;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module "mathjax-full/js/input/asciimath/mathjax2/legacy/MathJax";
|
|
||||||
|
|||||||
@@ -87,17 +87,6 @@ if (import.meta.env.DEV) {
|
|||||||
let currentLang: Language = defaultLang;
|
let currentLang: Language = defaultLang;
|
||||||
let currentLangData = {};
|
let currentLangData = {};
|
||||||
|
|
||||||
let fallbackCustomLangData = {};
|
|
||||||
const langLoaders: LangLdr[] = [];
|
|
||||||
export type LangLdr = (langCode: string) => Promise<{}>;
|
|
||||||
|
|
||||||
export const registerCustomLangData = (fallbackLangData: {}, ldr: LangLdr) => {
|
|
||||||
if (!langLoaders.includes(ldr)) {
|
|
||||||
fallbackCustomLangData = { ...fallbackLangData, ...fallbackCustomLangData };
|
|
||||||
langLoaders.push(ldr);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const setLanguage = async (lang: Language) => {
|
export const setLanguage = async (lang: Language) => {
|
||||||
currentLang = lang;
|
currentLang = lang;
|
||||||
document.documentElement.dir = currentLang.rtl ? "rtl" : "ltr";
|
document.documentElement.dir = currentLang.rtl ? "rtl" : "ltr";
|
||||||
@@ -112,14 +101,6 @@ export const setLanguage = async (lang: Language) => {
|
|||||||
console.error(`Failed to load language ${lang.code}:`, error.message);
|
console.error(`Failed to load language ${lang.code}:`, error.message);
|
||||||
currentLangData = fallbackLangData;
|
currentLangData = fallbackLangData;
|
||||||
}
|
}
|
||||||
const auxData = langLoaders.map((fn) => fn(currentLang.code));
|
|
||||||
while (auxData.length > 0) {
|
|
||||||
try {
|
|
||||||
currentLangData = { ...(await auxData.pop()), ...currentLangData };
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error(`Error loading ${lang.code} extra data:`, error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
jotaiStore.set(editorLangCodeAtom, lang.code);
|
jotaiStore.set(editorLangCodeAtom, lang.code);
|
||||||
@@ -142,9 +123,7 @@ const findPartsForData = (data: any, parts: string[]) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const t = (
|
export const t = (
|
||||||
path:
|
path: NestedKeyOf<typeof fallbackLangData>,
|
||||||
| NestedKeyOf<typeof fallbackLangData>
|
|
||||||
| `${NestedKeyOf<typeof fallbackLangData>}.${string}`,
|
|
||||||
replacement?: { [key: string]: string | number } | null,
|
replacement?: { [key: string]: string | number } | null,
|
||||||
fallback?: string,
|
fallback?: string,
|
||||||
) => {
|
) => {
|
||||||
@@ -159,7 +138,6 @@ export const t = (
|
|||||||
let translation =
|
let translation =
|
||||||
findPartsForData(currentLangData, parts) ||
|
findPartsForData(currentLangData, parts) ||
|
||||||
findPartsForData(fallbackLangData, parts) ||
|
findPartsForData(fallbackLangData, parts) ||
|
||||||
findPartsForData(fallbackCustomLangData, parts) ||
|
|
||||||
fallback;
|
fallback;
|
||||||
if (translation === undefined) {
|
if (translation === undefined) {
|
||||||
const errorMessage = `Can't find translation for ${path}`;
|
const errorMessage = `Can't find translation for ${path}`;
|
||||||
|
|||||||
@@ -328,7 +328,9 @@
|
|||||||
"deepBoxSelect": "Hold CtrlOrCmd to deep select, and to prevent dragging",
|
"deepBoxSelect": "Hold CtrlOrCmd to deep select, and to prevent dragging",
|
||||||
"eraserRevert": "Hold Alt to revert the elements marked for deletion",
|
"eraserRevert": "Hold Alt to revert the elements marked for deletion",
|
||||||
"firefox_clipboard_write": "This feature can likely be enabled by setting the \"dom.events.asyncClipboard.clipboardItem\" flag to \"true\". To change the browser flags in Firefox, visit the \"about:config\" page.",
|
"firefox_clipboard_write": "This feature can likely be enabled by setting the \"dom.events.asyncClipboard.clipboardItem\" flag to \"true\". To change the browser flags in Firefox, visit the \"about:config\" page.",
|
||||||
"disableSnapping": "Hold CtrlOrCmd to disable snapping"
|
"disableSnapping": "Hold CtrlOrCmd to disable snapping",
|
||||||
|
"enterCropEditor": "Double click the image or press ENTER to crop the image",
|
||||||
|
"leaveCropEditor": "Click outside the image or press ENTER or ESCAPE to finish cropping"
|
||||||
},
|
},
|
||||||
"canvasError": {
|
"canvasError": {
|
||||||
"cannotShowPreview": "Cannot show preview",
|
"cannotShowPreview": "Cannot show preview",
|
||||||
@@ -399,7 +401,9 @@
|
|||||||
"zoomToSelection": "Zoom to selection",
|
"zoomToSelection": "Zoom to selection",
|
||||||
"toggleElementLock": "Lock/unlock selection",
|
"toggleElementLock": "Lock/unlock selection",
|
||||||
"movePageUpDown": "Move page up/down",
|
"movePageUpDown": "Move page up/down",
|
||||||
"movePageLeftRight": "Move page left/right"
|
"movePageLeftRight": "Move page left/right",
|
||||||
|
"cropStart": "Crop image",
|
||||||
|
"cropFinish": "Finish image cropping"
|
||||||
},
|
},
|
||||||
"clearCanvasDialog": {
|
"clearCanvasDialog": {
|
||||||
"title": "Clear canvas"
|
"title": "Clear canvas"
|
||||||
|
|||||||
@@ -72,7 +72,6 @@
|
|||||||
"image-blob-reduce": "3.0.1",
|
"image-blob-reduce": "3.0.1",
|
||||||
"jotai": "1.13.1",
|
"jotai": "1.13.1",
|
||||||
"lodash.throttle": "4.1.1",
|
"lodash.throttle": "4.1.1",
|
||||||
"mathjax-full": "3.2.2",
|
|
||||||
"nanoid": "3.3.3",
|
"nanoid": "3.3.3",
|
||||||
"open-color": "1.9.1",
|
"open-color": "1.9.1",
|
||||||
"pako": "1.0.11",
|
"pako": "1.0.11",
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ import oc from "open-color";
|
|||||||
import {
|
import {
|
||||||
isElbowArrow,
|
isElbowArrow,
|
||||||
isFrameLikeElement,
|
isFrameLikeElement,
|
||||||
|
isImageElement,
|
||||||
isLinearElement,
|
isLinearElement,
|
||||||
isTextElement,
|
isTextElement,
|
||||||
} from "../element/typeChecks";
|
} from "../element/typeChecks";
|
||||||
@@ -62,6 +63,7 @@ import type {
|
|||||||
ExcalidrawBindableElement,
|
ExcalidrawBindableElement,
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
ExcalidrawFrameLikeElement,
|
ExcalidrawFrameLikeElement,
|
||||||
|
ExcalidrawImageElement,
|
||||||
ExcalidrawLinearElement,
|
ExcalidrawLinearElement,
|
||||||
ExcalidrawTextElement,
|
ExcalidrawTextElement,
|
||||||
GroupId,
|
GroupId,
|
||||||
@@ -307,38 +309,42 @@ const renderBindingHighlightForSuggestedPointBinding = (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ElementSelectionBorder = {
|
||||||
|
angle: number;
|
||||||
|
x1: number;
|
||||||
|
y1: number;
|
||||||
|
x2: number;
|
||||||
|
y2: number;
|
||||||
|
selectionColors: string[];
|
||||||
|
dashed?: boolean;
|
||||||
|
cx: number;
|
||||||
|
cy: number;
|
||||||
|
activeEmbeddable: boolean;
|
||||||
|
padding?: number;
|
||||||
|
};
|
||||||
|
|
||||||
const renderSelectionBorder = (
|
const renderSelectionBorder = (
|
||||||
context: CanvasRenderingContext2D,
|
context: CanvasRenderingContext2D,
|
||||||
appState: InteractiveCanvasAppState,
|
appState: InteractiveCanvasAppState,
|
||||||
elementProperties: {
|
elementProperties: ElementSelectionBorder,
|
||||||
angle: number;
|
|
||||||
elementX1: number;
|
|
||||||
elementY1: number;
|
|
||||||
elementX2: number;
|
|
||||||
elementY2: number;
|
|
||||||
selectionColors: string[];
|
|
||||||
dashed?: boolean;
|
|
||||||
cx: number;
|
|
||||||
cy: number;
|
|
||||||
activeEmbeddable: boolean;
|
|
||||||
},
|
|
||||||
) => {
|
) => {
|
||||||
const {
|
const {
|
||||||
angle,
|
angle,
|
||||||
elementX1,
|
x1,
|
||||||
elementY1,
|
y1,
|
||||||
elementX2,
|
x2,
|
||||||
elementY2,
|
y2,
|
||||||
selectionColors,
|
selectionColors,
|
||||||
cx,
|
cx,
|
||||||
cy,
|
cy,
|
||||||
dashed,
|
dashed,
|
||||||
activeEmbeddable,
|
activeEmbeddable,
|
||||||
} = elementProperties;
|
} = elementProperties;
|
||||||
const elementWidth = elementX2 - elementX1;
|
const elementWidth = x2 - x1;
|
||||||
const elementHeight = elementY2 - elementY1;
|
const elementHeight = y2 - y1;
|
||||||
|
|
||||||
const padding = DEFAULT_TRANSFORM_HANDLE_SPACING * 2;
|
const padding =
|
||||||
|
elementProperties.padding ?? DEFAULT_TRANSFORM_HANDLE_SPACING * 2;
|
||||||
|
|
||||||
const linePadding = padding / appState.zoom.value;
|
const linePadding = padding / appState.zoom.value;
|
||||||
const lineWidth = 8 / appState.zoom.value;
|
const lineWidth = 8 / appState.zoom.value;
|
||||||
@@ -360,8 +366,8 @@ const renderSelectionBorder = (
|
|||||||
context.lineDashOffset = (lineWidth + spaceWidth) * index;
|
context.lineDashOffset = (lineWidth + spaceWidth) * index;
|
||||||
strokeRectWithRotation(
|
strokeRectWithRotation(
|
||||||
context,
|
context,
|
||||||
elementX1 - linePadding,
|
x1 - linePadding,
|
||||||
elementY1 - linePadding,
|
y1 - linePadding,
|
||||||
elementWidth + linePadding * 2,
|
elementWidth + linePadding * 2,
|
||||||
elementHeight + linePadding * 2,
|
elementHeight + linePadding * 2,
|
||||||
cx,
|
cx,
|
||||||
@@ -433,18 +439,17 @@ const renderElementsBoxHighlight = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
const getSelectionFromElements = (elements: ExcalidrawElement[]) => {
|
const getSelectionFromElements = (elements: ExcalidrawElement[]) => {
|
||||||
const [elementX1, elementY1, elementX2, elementY2] =
|
const [x1, y1, x2, y2] = getCommonBounds(elements);
|
||||||
getCommonBounds(elements);
|
|
||||||
return {
|
return {
|
||||||
angle: 0,
|
angle: 0,
|
||||||
elementX1,
|
x1,
|
||||||
elementX2,
|
x2,
|
||||||
elementY1,
|
y1,
|
||||||
elementY2,
|
y2,
|
||||||
selectionColors: ["rgb(0,118,255)"],
|
selectionColors: ["rgb(0,118,255)"],
|
||||||
dashed: false,
|
dashed: false,
|
||||||
cx: elementX1 + (elementX2 - elementX1) / 2,
|
cx: x1 + (x2 - x1) / 2,
|
||||||
cy: elementY1 + (elementY2 - elementY1) / 2,
|
cy: y1 + (y2 - y1) / 2,
|
||||||
activeEmbeddable: false,
|
activeEmbeddable: false,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -594,6 +599,111 @@ const renderTransformHandles = (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderCropHandles = (
|
||||||
|
context: CanvasRenderingContext2D,
|
||||||
|
renderConfig: InteractiveCanvasRenderConfig,
|
||||||
|
appState: InteractiveCanvasAppState,
|
||||||
|
croppingElement: ExcalidrawImageElement,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
|
): void => {
|
||||||
|
const [x1, y1, , , cx, cy] = getElementAbsoluteCoords(
|
||||||
|
croppingElement,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
|
||||||
|
const LINE_WIDTH = 3;
|
||||||
|
const LINE_LENGTH = 20;
|
||||||
|
|
||||||
|
const ZOOMED_LINE_WIDTH = LINE_WIDTH / appState.zoom.value;
|
||||||
|
const ZOOMED_HALF_LINE_WIDTH = ZOOMED_LINE_WIDTH / 2;
|
||||||
|
|
||||||
|
const HALF_WIDTH = cx - x1 + ZOOMED_LINE_WIDTH;
|
||||||
|
const HALF_HEIGHT = cy - y1 + ZOOMED_LINE_WIDTH;
|
||||||
|
|
||||||
|
const HORIZONTAL_LINE_LENGTH = Math.min(
|
||||||
|
LINE_LENGTH / appState.zoom.value,
|
||||||
|
HALF_WIDTH,
|
||||||
|
);
|
||||||
|
const VERTICAL_LINE_LENGTH = Math.min(
|
||||||
|
LINE_LENGTH / appState.zoom.value,
|
||||||
|
HALF_HEIGHT,
|
||||||
|
);
|
||||||
|
|
||||||
|
context.save();
|
||||||
|
context.fillStyle = renderConfig.selectionColor;
|
||||||
|
context.strokeStyle = renderConfig.selectionColor;
|
||||||
|
context.lineWidth = ZOOMED_LINE_WIDTH;
|
||||||
|
|
||||||
|
const handles: Array<
|
||||||
|
[
|
||||||
|
[number, number],
|
||||||
|
[number, number],
|
||||||
|
[number, number],
|
||||||
|
[number, number],
|
||||||
|
[number, number],
|
||||||
|
]
|
||||||
|
> = [
|
||||||
|
[
|
||||||
|
// x, y
|
||||||
|
[-HALF_WIDTH, -HALF_HEIGHT],
|
||||||
|
// horizontal line: first start and to
|
||||||
|
[0, ZOOMED_HALF_LINE_WIDTH],
|
||||||
|
[HORIZONTAL_LINE_LENGTH, ZOOMED_HALF_LINE_WIDTH],
|
||||||
|
// vertical line: second start and to
|
||||||
|
[ZOOMED_HALF_LINE_WIDTH, 0],
|
||||||
|
[ZOOMED_HALF_LINE_WIDTH, VERTICAL_LINE_LENGTH],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[HALF_WIDTH - ZOOMED_HALF_LINE_WIDTH, -HALF_HEIGHT],
|
||||||
|
[ZOOMED_HALF_LINE_WIDTH, ZOOMED_HALF_LINE_WIDTH],
|
||||||
|
[
|
||||||
|
-HORIZONTAL_LINE_LENGTH + ZOOMED_HALF_LINE_WIDTH,
|
||||||
|
ZOOMED_HALF_LINE_WIDTH,
|
||||||
|
],
|
||||||
|
[0, 0],
|
||||||
|
[0, VERTICAL_LINE_LENGTH],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[-HALF_WIDTH, HALF_HEIGHT],
|
||||||
|
[0, -ZOOMED_HALF_LINE_WIDTH],
|
||||||
|
[HORIZONTAL_LINE_LENGTH, -ZOOMED_HALF_LINE_WIDTH],
|
||||||
|
[ZOOMED_HALF_LINE_WIDTH, 0],
|
||||||
|
[ZOOMED_HALF_LINE_WIDTH, -VERTICAL_LINE_LENGTH],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[HALF_WIDTH - ZOOMED_HALF_LINE_WIDTH, HALF_HEIGHT],
|
||||||
|
[ZOOMED_HALF_LINE_WIDTH, -ZOOMED_HALF_LINE_WIDTH],
|
||||||
|
[
|
||||||
|
-HORIZONTAL_LINE_LENGTH + ZOOMED_HALF_LINE_WIDTH,
|
||||||
|
-ZOOMED_HALF_LINE_WIDTH,
|
||||||
|
],
|
||||||
|
[0, 0],
|
||||||
|
[0, -VERTICAL_LINE_LENGTH],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
handles.forEach((handle) => {
|
||||||
|
const [[x, y], [x1s, y1s], [x1t, y1t], [x2s, y2s], [x2t, y2t]] = handle;
|
||||||
|
|
||||||
|
context.save();
|
||||||
|
context.translate(cx, cy);
|
||||||
|
context.rotate(croppingElement.angle);
|
||||||
|
|
||||||
|
context.beginPath();
|
||||||
|
context.moveTo(x + x1s, y + y1s);
|
||||||
|
context.lineTo(x + x1t, y + y1t);
|
||||||
|
context.stroke();
|
||||||
|
|
||||||
|
context.beginPath();
|
||||||
|
context.moveTo(x + x2s, y + y2s);
|
||||||
|
context.lineTo(x + x2t, y + y2t);
|
||||||
|
context.stroke();
|
||||||
|
context.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
context.restore();
|
||||||
|
};
|
||||||
|
|
||||||
const renderTextBox = (
|
const renderTextBox = (
|
||||||
text: NonDeleted<ExcalidrawTextElement>,
|
text: NonDeleted<ExcalidrawTextElement>,
|
||||||
context: CanvasRenderingContext2D,
|
context: CanvasRenderingContext2D,
|
||||||
@@ -671,7 +781,7 @@ const _renderInteractiveScene = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Paint selection element
|
// Paint selection element
|
||||||
if (appState.selectionElement) {
|
if (appState.selectionElement && !appState.isCropping) {
|
||||||
try {
|
try {
|
||||||
renderSelectionElement(
|
renderSelectionElement(
|
||||||
appState.selectionElement,
|
appState.selectionElement,
|
||||||
@@ -783,18 +893,7 @@ const _renderInteractiveScene = ({
|
|||||||
// Optimisation for finding quickly relevant element ids
|
// Optimisation for finding quickly relevant element ids
|
||||||
const locallySelectedIds = arrayToMap(selectedElements);
|
const locallySelectedIds = arrayToMap(selectedElements);
|
||||||
|
|
||||||
const selections: {
|
const selections: ElementSelectionBorder[] = [];
|
||||||
angle: number;
|
|
||||||
elementX1: number;
|
|
||||||
elementY1: number;
|
|
||||||
elementX2: number;
|
|
||||||
elementY2: number;
|
|
||||||
selectionColors: string[];
|
|
||||||
dashed?: boolean;
|
|
||||||
cx: number;
|
|
||||||
cy: number;
|
|
||||||
activeEmbeddable: boolean;
|
|
||||||
}[] = [];
|
|
||||||
|
|
||||||
for (const element of elementsMap.values()) {
|
for (const element of elementsMap.values()) {
|
||||||
const selectionColors = [];
|
const selectionColors = [];
|
||||||
@@ -833,14 +932,17 @@ const _renderInteractiveScene = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (selectionColors.length) {
|
if (selectionColors.length) {
|
||||||
const [elementX1, elementY1, elementX2, elementY2, cx, cy] =
|
const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(
|
||||||
getElementAbsoluteCoords(element, elementsMap, true);
|
element,
|
||||||
|
elementsMap,
|
||||||
|
true,
|
||||||
|
);
|
||||||
selections.push({
|
selections.push({
|
||||||
angle: element.angle,
|
angle: element.angle,
|
||||||
elementX1,
|
x1,
|
||||||
elementY1,
|
y1,
|
||||||
elementX2,
|
x2,
|
||||||
elementY2,
|
y2,
|
||||||
selectionColors,
|
selectionColors,
|
||||||
dashed: !!remoteClients,
|
dashed: !!remoteClients,
|
||||||
cx,
|
cx,
|
||||||
@@ -848,24 +950,28 @@ const _renderInteractiveScene = ({
|
|||||||
activeEmbeddable:
|
activeEmbeddable:
|
||||||
appState.activeEmbeddable?.element === element &&
|
appState.activeEmbeddable?.element === element &&
|
||||||
appState.activeEmbeddable.state === "active",
|
appState.activeEmbeddable.state === "active",
|
||||||
|
padding:
|
||||||
|
element.id === appState.croppingElementId ||
|
||||||
|
isImageElement(element)
|
||||||
|
? 0
|
||||||
|
: undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const addSelectionForGroupId = (groupId: GroupId) => {
|
const addSelectionForGroupId = (groupId: GroupId) => {
|
||||||
const groupElements = getElementsInGroup(elementsMap, groupId);
|
const groupElements = getElementsInGroup(elementsMap, groupId);
|
||||||
const [elementX1, elementY1, elementX2, elementY2] =
|
const [x1, y1, x2, y2] = getCommonBounds(groupElements);
|
||||||
getCommonBounds(groupElements);
|
|
||||||
selections.push({
|
selections.push({
|
||||||
angle: 0,
|
angle: 0,
|
||||||
elementX1,
|
x1,
|
||||||
elementX2,
|
x2,
|
||||||
elementY1,
|
y1,
|
||||||
elementY2,
|
y2,
|
||||||
selectionColors: [oc.black],
|
selectionColors: [oc.black],
|
||||||
dashed: true,
|
dashed: true,
|
||||||
cx: elementX1 + (elementX2 - elementX1) / 2,
|
cx: x1 + (x2 - x1) / 2,
|
||||||
cy: elementY1 + (elementY2 - elementY1) / 2,
|
cy: y1 + (y2 - y1) / 2,
|
||||||
activeEmbeddable: false,
|
activeEmbeddable: false,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -900,7 +1006,9 @@ const _renderInteractiveScene = ({
|
|||||||
!appState.viewModeEnabled &&
|
!appState.viewModeEnabled &&
|
||||||
showBoundingBox &&
|
showBoundingBox &&
|
||||||
// do not show transform handles when text is being edited
|
// do not show transform handles when text is being edited
|
||||||
!isTextElement(appState.editingTextElement)
|
!isTextElement(appState.editingTextElement) &&
|
||||||
|
// do not show transform handles when image is being cropped
|
||||||
|
!appState.croppingElementId
|
||||||
) {
|
) {
|
||||||
renderTransformHandles(
|
renderTransformHandles(
|
||||||
context,
|
context,
|
||||||
@@ -910,6 +1018,20 @@ const _renderInteractiveScene = ({
|
|||||||
selectedElements[0].angle,
|
selectedElements[0].angle,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (appState.croppingElementId && !appState.isCropping) {
|
||||||
|
const croppingElement = elementsMap.get(appState.croppingElementId);
|
||||||
|
|
||||||
|
if (croppingElement && isImageElement(croppingElement)) {
|
||||||
|
renderCropHandles(
|
||||||
|
context,
|
||||||
|
renderConfig,
|
||||||
|
appState,
|
||||||
|
croppingElement,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if (selectedElements.length > 1 && !appState.isRotating) {
|
} else if (selectedElements.length > 1 && !appState.isRotating) {
|
||||||
const dashedLinePadding =
|
const dashedLinePadding =
|
||||||
(DEFAULT_TRANSFORM_HANDLE_SPACING * 2) / appState.zoom.value;
|
(DEFAULT_TRANSFORM_HANDLE_SPACING * 2) / appState.zoom.value;
|
||||||
|
|||||||
@@ -17,12 +17,14 @@ import {
|
|||||||
isArrowElement,
|
isArrowElement,
|
||||||
hasBoundTextElement,
|
hasBoundTextElement,
|
||||||
isMagicFrameElement,
|
isMagicFrameElement,
|
||||||
|
isImageElement,
|
||||||
} from "../element/typeChecks";
|
} from "../element/typeChecks";
|
||||||
import { getElementAbsoluteCoords } from "../element/bounds";
|
import { getElementAbsoluteCoords } from "../element/bounds";
|
||||||
import type { RoughCanvas } from "roughjs/bin/canvas";
|
import type { RoughCanvas } from "roughjs/bin/canvas";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
StaticCanvasRenderConfig,
|
StaticCanvasRenderConfig,
|
||||||
|
RenderableElementsMap,
|
||||||
InteractiveCanvasRenderConfig,
|
InteractiveCanvasRenderConfig,
|
||||||
} from "../scene/types";
|
} from "../scene/types";
|
||||||
import { distance, getFontString, isRTL } from "../utils";
|
import { distance, getFontString, isRTL } from "../utils";
|
||||||
@@ -36,7 +38,6 @@ import type {
|
|||||||
PendingExcalidrawElements,
|
PendingExcalidrawElements,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
import { getDefaultAppState } from "../appState";
|
import { getDefaultAppState } from "../appState";
|
||||||
import { getSubtypeMethods } from "../element/subtypes";
|
|
||||||
import {
|
import {
|
||||||
BOUND_TEXT_PADDING,
|
BOUND_TEXT_PADDING,
|
||||||
ELEMENT_READY_TO_ERASE_OPACITY,
|
ELEMENT_READY_TO_ERASE_OPACITY,
|
||||||
@@ -61,6 +62,7 @@ import { ShapeCache } from "../scene/ShapeCache";
|
|||||||
import { getVerticalOffset } from "../fonts";
|
import { getVerticalOffset } from "../fonts";
|
||||||
import { isRightAngleRads } from "../../math";
|
import { isRightAngleRads } from "../../math";
|
||||||
import { getCornerRadius } from "../shapes";
|
import { getCornerRadius } from "../shapes";
|
||||||
|
import { getUncroppedImageElement } from "../element/cropElement";
|
||||||
|
|
||||||
// using a stronger invert (100% vs our regular 93%) and saturate
|
// using a stronger invert (100% vs our regular 93%) and saturate
|
||||||
// as a temp hack to make images in dark theme look closer to original
|
// as a temp hack to make images in dark theme look closer to original
|
||||||
@@ -251,14 +253,7 @@ const generateElementCanvas = (
|
|||||||
context.filter = IMAGE_INVERT_FILTER;
|
context.filter = IMAGE_INVERT_FILTER;
|
||||||
}
|
}
|
||||||
|
|
||||||
drawElementOnCanvas(
|
drawElementOnCanvas(element, rc, context, renderConfig, appState);
|
||||||
element,
|
|
||||||
elementsMap,
|
|
||||||
rc,
|
|
||||||
context,
|
|
||||||
renderConfig,
|
|
||||||
appState,
|
|
||||||
);
|
|
||||||
|
|
||||||
context.restore();
|
context.restore();
|
||||||
|
|
||||||
@@ -381,22 +376,11 @@ const drawImagePlaceholder = (
|
|||||||
|
|
||||||
const drawElementOnCanvas = (
|
const drawElementOnCanvas = (
|
||||||
element: NonDeletedExcalidrawElement,
|
element: NonDeletedExcalidrawElement,
|
||||||
elementsMap: ElementsMap,
|
|
||||||
rc: RoughCanvas,
|
rc: RoughCanvas,
|
||||||
context: CanvasRenderingContext2D,
|
context: CanvasRenderingContext2D,
|
||||||
renderConfig: StaticCanvasRenderConfig,
|
renderConfig: StaticCanvasRenderConfig,
|
||||||
appState: StaticCanvasAppState,
|
appState: StaticCanvasAppState,
|
||||||
) => {
|
) => {
|
||||||
context.globalAlpha =
|
|
||||||
((getContainingFrame(element, elementsMap)?.opacity ?? 100) *
|
|
||||||
element.opacity) /
|
|
||||||
10000;
|
|
||||||
const map = getSubtypeMethods(element.subtype);
|
|
||||||
if (map?.render) {
|
|
||||||
map.render(element, elementsMap, context);
|
|
||||||
context.globalAlpha = 1;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
switch (element.type) {
|
switch (element.type) {
|
||||||
case "rectangle":
|
case "rectangle":
|
||||||
case "iframe":
|
case "iframe":
|
||||||
@@ -452,8 +436,22 @@ const drawElementOnCanvas = (
|
|||||||
);
|
);
|
||||||
context.clip();
|
context.clip();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { x, y, width, height } = element.crop
|
||||||
|
? element.crop
|
||||||
|
: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: img.naturalWidth,
|
||||||
|
height: img.naturalHeight,
|
||||||
|
};
|
||||||
|
|
||||||
context.drawImage(
|
context.drawImage(
|
||||||
img,
|
img,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
0 /* hardcoded for the selection box*/,
|
0 /* hardcoded for the selection box*/,
|
||||||
0,
|
0,
|
||||||
element.width,
|
element.width,
|
||||||
@@ -691,7 +689,7 @@ export const renderSelectionElement = (
|
|||||||
|
|
||||||
export const renderElement = (
|
export const renderElement = (
|
||||||
element: NonDeletedExcalidrawElement,
|
element: NonDeletedExcalidrawElement,
|
||||||
elementsMap: ElementsMap,
|
elementsMap: RenderableElementsMap,
|
||||||
allElementsMap: NonDeletedSceneElementsMap,
|
allElementsMap: NonDeletedSceneElementsMap,
|
||||||
rc: RoughCanvas,
|
rc: RoughCanvas,
|
||||||
context: CanvasRenderingContext2D,
|
context: CanvasRenderingContext2D,
|
||||||
@@ -760,14 +758,7 @@ export const renderElement = (
|
|||||||
context.translate(cx, cy);
|
context.translate(cx, cy);
|
||||||
context.rotate(element.angle);
|
context.rotate(element.angle);
|
||||||
context.translate(-shiftX, -shiftY);
|
context.translate(-shiftX, -shiftY);
|
||||||
drawElementOnCanvas(
|
drawElementOnCanvas(element, rc, context, renderConfig, appState);
|
||||||
element,
|
|
||||||
elementsMap,
|
|
||||||
rc,
|
|
||||||
context,
|
|
||||||
renderConfig,
|
|
||||||
appState,
|
|
||||||
);
|
|
||||||
context.restore();
|
context.restore();
|
||||||
} else {
|
} else {
|
||||||
const elementWithCanvas = generateElementWithCanvas(
|
const elementWithCanvas = generateElementWithCanvas(
|
||||||
@@ -862,7 +853,6 @@ export const renderElement = (
|
|||||||
|
|
||||||
drawElementOnCanvas(
|
drawElementOnCanvas(
|
||||||
element,
|
element,
|
||||||
elementsMap,
|
|
||||||
tempRc,
|
tempRc,
|
||||||
tempCanvasContext,
|
tempCanvasContext,
|
||||||
renderConfig,
|
renderConfig,
|
||||||
@@ -906,14 +896,7 @@ export const renderElement = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
context.translate(-shiftX, -shiftY);
|
context.translate(-shiftX, -shiftY);
|
||||||
drawElementOnCanvas(
|
drawElementOnCanvas(element, rc, context, renderConfig, appState);
|
||||||
element,
|
|
||||||
elementsMap,
|
|
||||||
rc,
|
|
||||||
context,
|
|
||||||
renderConfig,
|
|
||||||
appState,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
context.restore();
|
context.restore();
|
||||||
@@ -954,14 +937,53 @@ export const renderElement = (
|
|||||||
context.imageSmoothingEnabled = false;
|
context.imageSmoothingEnabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
drawElementFromCanvas(
|
if (
|
||||||
elementWithCanvas,
|
element.id === appState.croppingElementId &&
|
||||||
context,
|
isImageElement(elementWithCanvas.element) &&
|
||||||
|
elementWithCanvas.element.crop !== null
|
||||||
|
) {
|
||||||
|
context.save();
|
||||||
|
context.globalAlpha = 0.1;
|
||||||
|
|
||||||
|
const uncroppedElementCanvas = generateElementCanvas(
|
||||||
|
getUncroppedImageElement(elementWithCanvas.element, elementsMap),
|
||||||
|
allElementsMap,
|
||||||
|
appState.zoom,
|
||||||
|
renderConfig,
|
||||||
|
appState,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (uncroppedElementCanvas) {
|
||||||
|
drawElementFromCanvas(
|
||||||
|
uncroppedElementCanvas,
|
||||||
|
context,
|
||||||
|
renderConfig,
|
||||||
|
appState,
|
||||||
|
allElementsMap,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
const _elementWithCanvas = generateElementCanvas(
|
||||||
|
elementWithCanvas.element,
|
||||||
|
allElementsMap,
|
||||||
|
appState.zoom,
|
||||||
renderConfig,
|
renderConfig,
|
||||||
appState,
|
appState,
|
||||||
allElementsMap,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (_elementWithCanvas) {
|
||||||
|
drawElementFromCanvas(
|
||||||
|
_elementWithCanvas,
|
||||||
|
context,
|
||||||
|
renderConfig,
|
||||||
|
appState,
|
||||||
|
allElementsMap,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// reset
|
// reset
|
||||||
context.imageSmoothingEnabled = currentImageSmoothingStatus;
|
context.imageSmoothingEnabled = currentImageSmoothingStatus;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
SVG_NS,
|
SVG_NS,
|
||||||
} from "../constants";
|
} from "../constants";
|
||||||
import { normalizeLink, toValidURL } from "../data/url";
|
import { normalizeLink, toValidURL } from "../data/url";
|
||||||
import { getElementAbsoluteCoords } from "../element";
|
import { getElementAbsoluteCoords, hashString } from "../element";
|
||||||
import {
|
import {
|
||||||
createPlaceholderEmbeddableLabel,
|
createPlaceholderEmbeddableLabel,
|
||||||
getEmbedLink,
|
getEmbedLink,
|
||||||
@@ -35,9 +35,9 @@ import type { RenderableElementsMap, SVGRenderConfig } from "../scene/types";
|
|||||||
import type { AppState, BinaryFiles } from "../types";
|
import type { AppState, BinaryFiles } from "../types";
|
||||||
import { getFontFamilyString, isRTL, isTestEnv } from "../utils";
|
import { getFontFamilyString, isRTL, isTestEnv } from "../utils";
|
||||||
import { getFreeDrawSvgPath, IMAGE_INVERT_FILTER } from "./renderElement";
|
import { getFreeDrawSvgPath, IMAGE_INVERT_FILTER } from "./renderElement";
|
||||||
import { getSubtypeMethods } from "../element/subtypes";
|
|
||||||
import { getVerticalOffset } from "../fonts";
|
import { getVerticalOffset } from "../fonts";
|
||||||
import { getCornerRadius, isPathALoop } from "../shapes";
|
import { getCornerRadius, isPathALoop } from "../shapes";
|
||||||
|
import { getUncroppedWidthAndHeight } from "../element/cropElement";
|
||||||
|
|
||||||
const roughSVGDrawWithPrecision = (
|
const roughSVGDrawWithPrecision = (
|
||||||
rsvg: RoughSVG,
|
rsvg: RoughSVG,
|
||||||
@@ -126,15 +126,6 @@ const renderElementToSvg = (
|
|||||||
root.appendChild(node);
|
root.appendChild(node);
|
||||||
};
|
};
|
||||||
|
|
||||||
const map = getSubtypeMethods(element.subtype);
|
|
||||||
if (map?.renderSvg) {
|
|
||||||
map.renderSvg(svgRoot, addToRoot, element, elementsMap, {
|
|
||||||
offsetX,
|
|
||||||
offsetY,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const opacity =
|
const opacity =
|
||||||
((getContainingFrame(element, elementsMap)?.opacity ?? 100) *
|
((getContainingFrame(element, elementsMap)?.opacity ?? 100) *
|
||||||
element.opacity) /
|
element.opacity) /
|
||||||
@@ -420,19 +411,36 @@ const renderElementToSvg = (
|
|||||||
const fileData =
|
const fileData =
|
||||||
isInitializedImageElement(element) && files[element.fileId];
|
isInitializedImageElement(element) && files[element.fileId];
|
||||||
if (fileData) {
|
if (fileData) {
|
||||||
const symbolId = `image-${fileData.id}`;
|
const cropHash = hashString(JSON.stringify(element.crop));
|
||||||
|
const symbolId = `image-${fileData.id}-${cropHash}`;
|
||||||
let symbol = svgRoot.querySelector(`#${symbolId}`);
|
let symbol = svgRoot.querySelector(`#${symbolId}`);
|
||||||
if (!symbol) {
|
if (!symbol) {
|
||||||
symbol = svgRoot.ownerDocument!.createElementNS(SVG_NS, "symbol");
|
symbol = svgRoot.ownerDocument!.createElementNS(SVG_NS, "symbol");
|
||||||
symbol.id = symbolId;
|
symbol.id = symbolId;
|
||||||
|
|
||||||
const image = svgRoot.ownerDocument!.createElementNS(SVG_NS, "image");
|
const image = svgRoot.ownerDocument!.createElementNS(SVG_NS, "image");
|
||||||
|
|
||||||
image.setAttribute("width", "100%");
|
|
||||||
image.setAttribute("height", "100%");
|
|
||||||
image.setAttribute("href", fileData.dataURL);
|
image.setAttribute("href", fileData.dataURL);
|
||||||
image.setAttribute("preserveAspectRatio", "none");
|
image.setAttribute("preserveAspectRatio", "none");
|
||||||
|
|
||||||
|
if (element.crop) {
|
||||||
|
const { width: uncroppedWidth, height: uncroppedHeight } =
|
||||||
|
getUncroppedWidthAndHeight(element);
|
||||||
|
|
||||||
|
symbol.setAttribute(
|
||||||
|
"viewBox",
|
||||||
|
`${
|
||||||
|
element.crop.x / (element.crop.naturalWidth / uncroppedWidth)
|
||||||
|
} ${
|
||||||
|
element.crop.y / (element.crop.naturalHeight / uncroppedHeight)
|
||||||
|
} ${width} ${height}`,
|
||||||
|
);
|
||||||
|
image.setAttribute("width", `${uncroppedWidth}`);
|
||||||
|
image.setAttribute("height", `${uncroppedHeight}`);
|
||||||
|
} else {
|
||||||
|
image.setAttribute("width", "100%");
|
||||||
|
image.setAttribute("height", "100%");
|
||||||
|
}
|
||||||
|
|
||||||
symbol.appendChild(image);
|
symbol.appendChild(image);
|
||||||
|
|
||||||
root.prepend(symbol);
|
root.prepend(symbol);
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export const getObservedAppState = (appState: AppState): ObservedAppState => {
|
|||||||
selectedGroupIds: appState.selectedGroupIds,
|
selectedGroupIds: appState.selectedGroupIds,
|
||||||
editingLinearElementId: appState.editingLinearElement?.elementId || null,
|
editingLinearElementId: appState.editingLinearElement?.elementId || null,
|
||||||
selectedLinearElementId: appState.selectedLinearElement?.elementId || null,
|
selectedLinearElementId: appState.selectedLinearElement?.elementId || null,
|
||||||
|
croppingElementId: appState.croppingElementId,
|
||||||
};
|
};
|
||||||
|
|
||||||
Reflect.defineProperty(observedAppState, hiddenObservedAppStateProp, {
|
Reflect.defineProperty(observedAppState, hiddenObservedAppStateProp, {
|
||||||
|
|||||||
@@ -116,6 +116,50 @@ exports[`contextMenu element > right-clicking on a group should select whole gro
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
"separator",
|
"separator",
|
||||||
|
{
|
||||||
|
"PanelComponent": [Function],
|
||||||
|
"icon": <svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className=""
|
||||||
|
fill="none"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<g
|
||||||
|
strokeWidth="1.25"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M0 0h24v24H0z"
|
||||||
|
fill="none"
|
||||||
|
stroke="none"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M8 5v10a1 1 0 0 0 1 1h10"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M5 8h10a1 1 0 0 1 1 1v10"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>,
|
||||||
|
"keywords": [
|
||||||
|
"image",
|
||||||
|
"crop",
|
||||||
|
],
|
||||||
|
"label": "helpDialog.cropStart",
|
||||||
|
"name": "cropEditor",
|
||||||
|
"perform": [Function],
|
||||||
|
"predicate": [Function],
|
||||||
|
"trackEvent": {
|
||||||
|
"category": "menu",
|
||||||
|
},
|
||||||
|
"viewMode": true,
|
||||||
|
},
|
||||||
|
"separator",
|
||||||
{
|
{
|
||||||
"icon": <svg
|
"icon": <svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
@@ -794,6 +838,7 @@ exports[`contextMenu element > right-clicking on a group should select whole gro
|
|||||||
"left": 30,
|
"left": 30,
|
||||||
"top": 40,
|
"top": 40,
|
||||||
},
|
},
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -836,6 +881,7 @@ exports[`contextMenu element > right-clicking on a group should select whole gro
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -1000,6 +1046,7 @@ exports[`contextMenu element > selecting 'Add to library' in context menu adds e
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -1042,6 +1089,7 @@ exports[`contextMenu element > selecting 'Add to library' in context menu adds e
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -1216,6 +1264,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -1258,6 +1307,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -1547,6 +1597,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -1589,6 +1640,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -1878,6 +1930,7 @@ exports[`contextMenu element > selecting 'Copy styles' in context menu copies st
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -1920,6 +1973,7 @@ exports[`contextMenu element > selecting 'Copy styles' in context menu copies st
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -2094,6 +2148,7 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -2136,6 +2191,7 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -2334,6 +2390,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -2376,6 +2433,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -2635,6 +2693,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -2677,6 +2736,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -3004,6 +3064,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -3046,6 +3107,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -3479,6 +3541,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -3521,6 +3584,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -3802,6 +3866,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -3844,6 +3909,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -4125,6 +4191,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -4167,6 +4234,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -4633,6 +4701,50 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
"separator",
|
"separator",
|
||||||
|
{
|
||||||
|
"PanelComponent": [Function],
|
||||||
|
"icon": <svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className=""
|
||||||
|
fill="none"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<g
|
||||||
|
strokeWidth="1.25"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M0 0h24v24H0z"
|
||||||
|
fill="none"
|
||||||
|
stroke="none"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M8 5v10a1 1 0 0 0 1 1h10"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M5 8h10a1 1 0 0 1 1 1v10"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>,
|
||||||
|
"keywords": [
|
||||||
|
"image",
|
||||||
|
"crop",
|
||||||
|
],
|
||||||
|
"label": "helpDialog.cropStart",
|
||||||
|
"name": "cropEditor",
|
||||||
|
"perform": [Function],
|
||||||
|
"predicate": [Function],
|
||||||
|
"trackEvent": {
|
||||||
|
"category": "menu",
|
||||||
|
},
|
||||||
|
"viewMode": true,
|
||||||
|
},
|
||||||
|
"separator",
|
||||||
{
|
{
|
||||||
"icon": <svg
|
"icon": <svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
@@ -5311,6 +5423,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
|||||||
"left": -17,
|
"left": -17,
|
||||||
"top": -7,
|
"top": -7,
|
||||||
},
|
},
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -5353,6 +5466,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -5760,6 +5874,50 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
"separator",
|
"separator",
|
||||||
|
{
|
||||||
|
"PanelComponent": [Function],
|
||||||
|
"icon": <svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className=""
|
||||||
|
fill="none"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<g
|
||||||
|
strokeWidth="1.25"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M0 0h24v24H0z"
|
||||||
|
fill="none"
|
||||||
|
stroke="none"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M8 5v10a1 1 0 0 0 1 1h10"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M5 8h10a1 1 0 0 1 1 1v10"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>,
|
||||||
|
"keywords": [
|
||||||
|
"image",
|
||||||
|
"crop",
|
||||||
|
],
|
||||||
|
"label": "helpDialog.cropStart",
|
||||||
|
"name": "cropEditor",
|
||||||
|
"perform": [Function],
|
||||||
|
"predicate": [Function],
|
||||||
|
"trackEvent": {
|
||||||
|
"category": "menu",
|
||||||
|
},
|
||||||
|
"viewMode": true,
|
||||||
|
},
|
||||||
|
"separator",
|
||||||
{
|
{
|
||||||
"icon": <svg
|
"icon": <svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
@@ -6438,6 +6596,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
|||||||
"left": -17,
|
"left": -17,
|
||||||
"top": -7,
|
"top": -7,
|
||||||
},
|
},
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -6480,6 +6639,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -7373,6 +7533,7 @@ exports[`contextMenu element > shows context menu for canvas > [end of test] app
|
|||||||
"left": -19,
|
"left": -19,
|
||||||
"top": -9,
|
"top": -9,
|
||||||
},
|
},
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -7415,6 +7576,7 @@ exports[`contextMenu element > shows context menu for canvas > [end of test] app
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -7607,6 +7769,50 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
"separator",
|
"separator",
|
||||||
|
{
|
||||||
|
"PanelComponent": [Function],
|
||||||
|
"icon": <svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className=""
|
||||||
|
fill="none"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<g
|
||||||
|
strokeWidth="1.25"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M0 0h24v24H0z"
|
||||||
|
fill="none"
|
||||||
|
stroke="none"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M8 5v10a1 1 0 0 0 1 1h10"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M5 8h10a1 1 0 0 1 1 1v10"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>,
|
||||||
|
"keywords": [
|
||||||
|
"image",
|
||||||
|
"crop",
|
||||||
|
],
|
||||||
|
"label": "helpDialog.cropStart",
|
||||||
|
"name": "cropEditor",
|
||||||
|
"perform": [Function],
|
||||||
|
"predicate": [Function],
|
||||||
|
"trackEvent": {
|
||||||
|
"category": "menu",
|
||||||
|
},
|
||||||
|
"viewMode": true,
|
||||||
|
},
|
||||||
|
"separator",
|
||||||
{
|
{
|
||||||
"icon": <svg
|
"icon": <svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
@@ -8285,6 +8491,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
"left": -17,
|
"left": -17,
|
||||||
"top": -7,
|
"top": -7,
|
||||||
},
|
},
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -8327,6 +8534,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -8501,6 +8709,50 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
"separator",
|
"separator",
|
||||||
|
{
|
||||||
|
"PanelComponent": [Function],
|
||||||
|
"icon": <svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className=""
|
||||||
|
fill="none"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<g
|
||||||
|
strokeWidth="1.25"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M0 0h24v24H0z"
|
||||||
|
fill="none"
|
||||||
|
stroke="none"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M8 5v10a1 1 0 0 0 1 1h10"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M5 8h10a1 1 0 0 1 1 1v10"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>,
|
||||||
|
"keywords": [
|
||||||
|
"image",
|
||||||
|
"crop",
|
||||||
|
],
|
||||||
|
"label": "helpDialog.cropStart",
|
||||||
|
"name": "cropEditor",
|
||||||
|
"perform": [Function],
|
||||||
|
"predicate": [Function],
|
||||||
|
"trackEvent": {
|
||||||
|
"category": "menu",
|
||||||
|
},
|
||||||
|
"viewMode": true,
|
||||||
|
},
|
||||||
|
"separator",
|
||||||
{
|
{
|
||||||
"icon": <svg
|
"icon": <svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
@@ -9179,6 +9431,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
"left": 80,
|
"left": 80,
|
||||||
"top": 90,
|
"top": 90,
|
||||||
},
|
},
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -9221,6 +9474,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 100,
|
"height": 100,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -11,6 +11,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -53,6 +54,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -613,6 +615,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -655,6 +658,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -1119,6 +1123,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -1161,6 +1166,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -1487,6 +1493,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -1529,6 +1536,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -1856,6 +1864,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -1898,6 +1907,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -2123,6 +2133,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -2165,6 +2176,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -2563,6 +2575,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -2605,6 +2618,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -2862,6 +2876,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -2904,6 +2919,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -3146,6 +3162,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -3188,6 +3205,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -3440,6 +3458,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -3482,6 +3501,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -3726,6 +3746,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -3768,6 +3789,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -3961,6 +3983,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -4003,6 +4026,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -4220,6 +4244,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -4262,6 +4287,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -4493,6 +4519,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -4535,6 +4562,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -4724,6 +4752,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -4766,6 +4795,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -4955,6 +4985,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -4997,6 +5028,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -5184,6 +5216,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -5226,6 +5259,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -5413,6 +5447,7 @@ exports[`history > multiplayer undo/redo > conflicts in frames and their childre
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -5455,6 +5490,7 @@ exports[`history > multiplayer undo/redo > conflicts in frames and their childre
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -5672,6 +5708,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -5714,6 +5751,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -6003,6 +6041,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -6045,6 +6084,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -6428,6 +6468,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -6470,6 +6511,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -6806,6 +6848,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -6848,6 +6891,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -7125,6 +7169,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -7167,6 +7212,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -7423,6 +7469,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -7465,6 +7512,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -7652,6 +7700,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -7694,6 +7743,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -8007,6 +8057,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -8049,6 +8100,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -8362,6 +8414,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -8404,6 +8457,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -8766,6 +8820,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -8808,6 +8863,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -9053,6 +9109,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -9095,6 +9152,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -9318,6 +9376,7 @@ exports[`history > multiplayer undo/redo > should not override remote changes on
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -9360,6 +9419,7 @@ exports[`history > multiplayer undo/redo > should not override remote changes on
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -9582,6 +9642,7 @@ exports[`history > multiplayer undo/redo > should not override remote changes on
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -9624,6 +9685,7 @@ exports[`history > multiplayer undo/redo > should not override remote changes on
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -9813,6 +9875,7 @@ exports[`history > multiplayer undo/redo > should override remotely added groups
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -9855,6 +9918,7 @@ exports[`history > multiplayer undo/redo > should override remotely added groups
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -10114,6 +10178,7 @@ exports[`history > multiplayer undo/redo > should override remotely added points
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -10156,6 +10221,7 @@ exports[`history > multiplayer undo/redo > should override remotely added points
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -10454,6 +10520,7 @@ exports[`history > multiplayer undo/redo > should redistribute deltas when eleme
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -10496,6 +10563,7 @@ exports[`history > multiplayer undo/redo > should redistribute deltas when eleme
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -10689,6 +10757,7 @@ exports[`history > multiplayer undo/redo > should redraw arrows on undo > [end o
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -10731,6 +10800,7 @@ exports[`history > multiplayer undo/redo > should redraw arrows on undo > [end o
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -11142,6 +11212,7 @@ exports[`history > multiplayer undo/redo > should update history entries after r
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -11184,6 +11255,7 @@ exports[`history > multiplayer undo/redo > should update history entries after r
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -11396,6 +11468,7 @@ exports[`history > singleplayer undo/redo > remounting undo/redo buttons should
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -11438,6 +11511,7 @@ exports[`history > singleplayer undo/redo > remounting undo/redo buttons should
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -11635,6 +11709,7 @@ exports[`history > singleplayer undo/redo > should clear the redo stack on eleme
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -11677,6 +11752,7 @@ exports[`history > singleplayer undo/redo > should clear the redo stack on eleme
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -11876,6 +11952,7 @@ exports[`history > singleplayer undo/redo > should create entry when selecting f
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -11918,6 +11995,7 @@ exports[`history > singleplayer undo/redo > should create entry when selecting f
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -12277,6 +12355,7 @@ exports[`history > singleplayer undo/redo > should create new history entry on s
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -12319,6 +12398,7 @@ exports[`history > singleplayer undo/redo > should create new history entry on s
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -12524,6 +12604,7 @@ exports[`history > singleplayer undo/redo > should disable undo/redo buttons whe
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -12566,6 +12647,7 @@ exports[`history > singleplayer undo/redo > should disable undo/redo buttons whe
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -12765,6 +12847,7 @@ exports[`history > singleplayer undo/redo > should end up with no history entry
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -12807,6 +12890,7 @@ exports[`history > singleplayer undo/redo > should end up with no history entry
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -13006,6 +13090,7 @@ exports[`history > singleplayer undo/redo > should iterate through the history w
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -13048,6 +13133,7 @@ exports[`history > singleplayer undo/redo > should iterate through the history w
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -13253,6 +13339,7 @@ exports[`history > singleplayer undo/redo > should not clear the redo stack on s
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -13295,6 +13382,7 @@ exports[`history > singleplayer undo/redo > should not clear the redo stack on s
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -13585,6 +13673,7 @@ exports[`history > singleplayer undo/redo > should not collapse when applying co
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -13627,6 +13716,7 @@ exports[`history > singleplayer undo/redo > should not collapse when applying co
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -13757,6 +13847,7 @@ exports[`history > singleplayer undo/redo > should not end up with history entry
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -13799,6 +13890,7 @@ exports[`history > singleplayer undo/redo > should not end up with history entry
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -14045,6 +14137,7 @@ exports[`history > singleplayer undo/redo > should not end up with history entry
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -14087,6 +14180,7 @@ exports[`history > singleplayer undo/redo > should not end up with history entry
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -14312,6 +14406,7 @@ exports[`history > singleplayer undo/redo > should not override appstate changes
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -14354,6 +14449,7 @@ exports[`history > singleplayer undo/redo > should not override appstate changes
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -14587,6 +14683,7 @@ exports[`history > singleplayer undo/redo > should support appstate name or view
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -14629,6 +14726,7 @@ exports[`history > singleplayer undo/redo > should support appstate name or view
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -14748,6 +14846,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -14790,6 +14889,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -15444,6 +15544,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -15486,6 +15587,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -16064,6 +16166,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -16106,6 +16209,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -16684,6 +16788,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -16726,6 +16831,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -17396,6 +17502,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -17438,6 +17545,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -18146,6 +18254,7 @@ exports[`history > singleplayer undo/redo > should support changes in elements'
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -18188,6 +18297,7 @@ exports[`history > singleplayer undo/redo > should support changes in elements'
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -18620,6 +18730,7 @@ exports[`history > singleplayer undo/redo > should support duplication of groups
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -18662,6 +18773,7 @@ exports[`history > singleplayer undo/redo > should support duplication of groups
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -19142,6 +19254,7 @@ exports[`history > singleplayer undo/redo > should support element creation, del
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -19184,6 +19297,7 @@ exports[`history > singleplayer undo/redo > should support element creation, del
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -19598,6 +19712,7 @@ exports[`history > singleplayer undo/redo > should support linear element creati
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -19640,6 +19755,7 @@ exports[`history > singleplayer undo/redo > should support linear element creati
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 0,
|
"height": 0,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ exports[`given element A and group of elements B and given both are selected whe
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -53,6 +54,7 @@ exports[`given element A and group of elements B and given both are selected whe
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -423,6 +425,7 @@ exports[`given element A and group of elements B and given both are selected whe
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -465,6 +468,7 @@ exports[`given element A and group of elements B and given both are selected whe
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -826,6 +830,7 @@ exports[`regression tests > Cmd/Ctrl-click exclusively select element under poin
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -868,6 +873,7 @@ exports[`regression tests > Cmd/Ctrl-click exclusively select element under poin
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": false,
|
"isBindingEnabled": false,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -1368,6 +1374,7 @@ exports[`regression tests > Drags selected element when hitting only bounding bo
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -1410,6 +1417,7 @@ exports[`regression tests > Drags selected element when hitting only bounding bo
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -1569,6 +1577,7 @@ exports[`regression tests > adjusts z order when grouping > [end of test] appSta
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -1611,6 +1620,7 @@ exports[`regression tests > adjusts z order when grouping > [end of test] appSta
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -1941,6 +1951,7 @@ exports[`regression tests > alt-drag duplicates an element > [end of test] appSt
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -1983,6 +1994,7 @@ exports[`regression tests > alt-drag duplicates an element > [end of test] appSt
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -2178,6 +2190,7 @@ exports[`regression tests > arrow keys > [end of test] appState 1`] = `
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -2220,6 +2233,7 @@ exports[`regression tests > arrow keys > [end of test] appState 1`] = `
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -2355,6 +2369,7 @@ exports[`regression tests > can drag element that covers another element, while
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -2397,6 +2412,7 @@ exports[`regression tests > can drag element that covers another element, while
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -2672,6 +2688,7 @@ exports[`regression tests > change the properties of a shape > [end of test] app
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -2714,6 +2731,7 @@ exports[`regression tests > change the properties of a shape > [end of test] app
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -2915,6 +2933,7 @@ exports[`regression tests > click on an element and drag it > [dragged] appState
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -2957,6 +2976,7 @@ exports[`regression tests > click on an element and drag it > [dragged] appState
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -3155,6 +3175,7 @@ exports[`regression tests > click on an element and drag it > [end of test] appS
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -3197,6 +3218,7 @@ exports[`regression tests > click on an element and drag it > [end of test] appS
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -3382,6 +3404,7 @@ exports[`regression tests > click to select a shape > [end of test] appState 1`]
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -3424,6 +3447,7 @@ exports[`regression tests > click to select a shape > [end of test] appState 1`]
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -3635,6 +3659,7 @@ exports[`regression tests > click-drag to select a group > [end of test] appStat
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -3677,6 +3702,7 @@ exports[`regression tests > click-drag to select a group > [end of test] appStat
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -3943,6 +3969,7 @@ exports[`regression tests > deleting last but one element in editing group shoul
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -3985,6 +4012,7 @@ exports[`regression tests > deleting last but one element in editing group shoul
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -4354,6 +4382,7 @@ exports[`regression tests > deselects group of selected elements on pointer down
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -4396,6 +4425,7 @@ exports[`regression tests > deselects group of selected elements on pointer down
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -4634,6 +4664,7 @@ exports[`regression tests > deselects group of selected elements on pointer up w
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -4676,6 +4707,7 @@ exports[`regression tests > deselects group of selected elements on pointer up w
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -4884,6 +4916,7 @@ exports[`regression tests > deselects selected element on pointer down when poin
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -4926,6 +4959,7 @@ exports[`regression tests > deselects selected element on pointer down when poin
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -5091,6 +5125,7 @@ exports[`regression tests > deselects selected element, on pointer up, when clic
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -5133,6 +5168,7 @@ exports[`regression tests > deselects selected element, on pointer up, when clic
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -5287,6 +5323,7 @@ exports[`regression tests > double click to edit a group > [end of test] appStat
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -5329,6 +5366,7 @@ exports[`regression tests > double click to edit a group > [end of test] appStat
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -5666,6 +5704,7 @@ exports[`regression tests > drags selected elements from point inside common bou
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -5708,6 +5747,7 @@ exports[`regression tests > drags selected elements from point inside common bou
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -5953,6 +5993,7 @@ exports[`regression tests > draw every type of shape > [end of test] appState 1`
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -5995,6 +6036,7 @@ exports[`regression tests > draw every type of shape > [end of test] appState 1`
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -6758,6 +6800,7 @@ exports[`regression tests > given a group of selected elements with an element t
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -6800,6 +6843,7 @@ exports[`regression tests > given a group of selected elements with an element t
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -7085,6 +7129,7 @@ exports[`regression tests > given a selected element A and a not selected elemen
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -7127,6 +7172,7 @@ exports[`regression tests > given a selected element A and a not selected elemen
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -7358,6 +7404,7 @@ exports[`regression tests > given selected element A with lower z-index than uns
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -7400,6 +7447,7 @@ exports[`regression tests > given selected element A with lower z-index than uns
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -7589,6 +7637,7 @@ exports[`regression tests > given selected element A with lower z-index than uns
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -7631,6 +7680,7 @@ exports[`regression tests > given selected element A with lower z-index than uns
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -7823,6 +7873,7 @@ exports[`regression tests > key 2 selects rectangle tool > [end of test] appStat
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -7865,6 +7916,7 @@ exports[`regression tests > key 2 selects rectangle tool > [end of test] appStat
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -8000,6 +8052,7 @@ exports[`regression tests > key 3 selects diamond tool > [end of test] appState
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -8042,6 +8095,7 @@ exports[`regression tests > key 3 selects diamond tool > [end of test] appState
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -8177,6 +8231,7 @@ exports[`regression tests > key 4 selects ellipse tool > [end of test] appState
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -8219,6 +8274,7 @@ exports[`regression tests > key 4 selects ellipse tool > [end of test] appState
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -8354,6 +8410,7 @@ exports[`regression tests > key 5 selects arrow tool > [end of test] appState 1`
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -8396,6 +8453,7 @@ exports[`regression tests > key 5 selects arrow tool > [end of test] appState 1`
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -8574,6 +8632,7 @@ exports[`regression tests > key 6 selects line tool > [end of test] appState 1`]
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -8616,6 +8675,7 @@ exports[`regression tests > key 6 selects line tool > [end of test] appState 1`]
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -8793,6 +8853,7 @@ exports[`regression tests > key 7 selects freedraw tool > [end of test] appState
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -8835,6 +8896,7 @@ exports[`regression tests > key 7 selects freedraw tool > [end of test] appState
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -8984,6 +9046,7 @@ exports[`regression tests > key a selects arrow tool > [end of test] appState 1`
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -9026,6 +9089,7 @@ exports[`regression tests > key a selects arrow tool > [end of test] appState 1`
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -9204,6 +9268,7 @@ exports[`regression tests > key d selects diamond tool > [end of test] appState
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -9246,6 +9311,7 @@ exports[`regression tests > key d selects diamond tool > [end of test] appState
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -9381,6 +9447,7 @@ exports[`regression tests > key l selects line tool > [end of test] appState 1`]
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -9423,6 +9490,7 @@ exports[`regression tests > key l selects line tool > [end of test] appState 1`]
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -9600,6 +9668,7 @@ exports[`regression tests > key o selects ellipse tool > [end of test] appState
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -9642,6 +9711,7 @@ exports[`regression tests > key o selects ellipse tool > [end of test] appState
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -9777,6 +9847,7 @@ exports[`regression tests > key p selects freedraw tool > [end of test] appState
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -9819,6 +9890,7 @@ exports[`regression tests > key p selects freedraw tool > [end of test] appState
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -9968,6 +10040,7 @@ exports[`regression tests > key r selects rectangle tool > [end of test] appStat
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -10010,6 +10083,7 @@ exports[`regression tests > key r selects rectangle tool > [end of test] appStat
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -10145,6 +10219,7 @@ exports[`regression tests > make a group and duplicate it > [end of test] appSta
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -10187,6 +10262,7 @@ exports[`regression tests > make a group and duplicate it > [end of test] appSta
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -10656,6 +10732,7 @@ exports[`regression tests > noop interaction after undo shouldn't create history
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -10698,6 +10775,7 @@ exports[`regression tests > noop interaction after undo shouldn't create history
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -10930,6 +11008,7 @@ exports[`regression tests > pinch-to-zoom works > [end of test] appState 1`] = `
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -10972,6 +11051,7 @@ exports[`regression tests > pinch-to-zoom works > [end of test] appState 1`] = `
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -11053,6 +11133,7 @@ exports[`regression tests > shift click on selected element should deselect it o
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -11095,6 +11176,7 @@ exports[`regression tests > shift click on selected element should deselect it o
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -11249,6 +11331,7 @@ exports[`regression tests > shift-click to multiselect, then drag > [end of test
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -11291,6 +11374,7 @@ exports[`regression tests > shift-click to multiselect, then drag > [end of test
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -11557,6 +11641,7 @@ exports[`regression tests > should group elements and ungroup them > [end of tes
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -11599,6 +11684,7 @@ exports[`regression tests > should group elements and ungroup them > [end of tes
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -11966,6 +12052,7 @@ exports[`regression tests > single-clicking on a subgroup of a selected group sh
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -12008,6 +12095,7 @@ exports[`regression tests > single-clicking on a subgroup of a selected group sh
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -12576,6 +12664,7 @@ exports[`regression tests > spacebar + drag scrolls the canvas > [end of test] a
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -12618,6 +12707,7 @@ exports[`regression tests > spacebar + drag scrolls the canvas > [end of test] a
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -12702,6 +12792,7 @@ exports[`regression tests > supports nested groups > [end of test] appState 1`]
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -12744,6 +12835,7 @@ exports[`regression tests > supports nested groups > [end of test] appState 1`]
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -13283,6 +13375,7 @@ exports[`regression tests > switches from group of selected elements to another
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -13325,6 +13418,7 @@ exports[`regression tests > switches from group of selected elements to another
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -13618,6 +13712,7 @@ exports[`regression tests > switches selected element on pointer down > [end of
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -13660,6 +13755,7 @@ exports[`regression tests > switches selected element on pointer down > [end of
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -13880,6 +13976,7 @@ exports[`regression tests > two-finger scroll works > [end of test] appState 1`]
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -13922,6 +14019,7 @@ exports[`regression tests > two-finger scroll works > [end of test] appState 1`]
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -14003,6 +14101,7 @@ exports[`regression tests > undo/redo drawing an element > [end of test] appStat
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -14045,6 +14144,7 @@ exports[`regression tests > undo/redo drawing an element > [end of test] appStat
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -14379,6 +14479,7 @@ exports[`regression tests > updates fontSize & fontFamily appState > [end of tes
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -14421,6 +14522,7 @@ exports[`regression tests > updates fontSize & fontFamily appState > [end of tes
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
@@ -14502,6 +14604,7 @@ exports[`regression tests > zoom hotkeys > [end of test] appState 1`] = `
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -14544,6 +14647,7 @@ exports[`regression tests > zoom hotkeys > [end of test] appState 1`] = `
|
|||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"height": 768,
|
"height": 768,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
|
|||||||
@@ -0,0 +1,342 @@
|
|||||||
|
import React from "react";
|
||||||
|
import ReactDOM from "react-dom";
|
||||||
|
import { vi } from "vitest";
|
||||||
|
import { Keyboard, Pointer, UI } from "./helpers/ui";
|
||||||
|
import type { ExcalidrawImageElement, ImageCrop } from "../element/types";
|
||||||
|
import { act, GlobalTestState, render } from "./test-utils";
|
||||||
|
import { Excalidraw, exportToCanvas, exportToSvg } from "..";
|
||||||
|
import { API } from "./helpers/api";
|
||||||
|
import type { NormalizedZoomValue } from "../types";
|
||||||
|
import { KEYS } from "../keys";
|
||||||
|
import { duplicateElement } from "../element";
|
||||||
|
import { cloneJSON } from "../utils";
|
||||||
|
import { actionFlipHorizontal, actionFlipVertical } from "../actions";
|
||||||
|
|
||||||
|
const { h } = window;
|
||||||
|
const mouse = new Pointer("mouse");
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
// Unmount ReactDOM from root
|
||||||
|
ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
|
||||||
|
|
||||||
|
mouse.reset();
|
||||||
|
localStorage.clear();
|
||||||
|
sessionStorage.clear();
|
||||||
|
vi.clearAllMocks();
|
||||||
|
|
||||||
|
Object.assign(document, {
|
||||||
|
elementFromPoint: () => GlobalTestState.canvas,
|
||||||
|
});
|
||||||
|
await render(<Excalidraw autoFocus={true} handleKeyboardGlobally={true} />);
|
||||||
|
API.setAppState({
|
||||||
|
zoom: {
|
||||||
|
value: 1 as NormalizedZoomValue,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const image = API.createElement({ type: "image", width: 200, height: 100 });
|
||||||
|
API.setElements([image]);
|
||||||
|
API.setAppState({
|
||||||
|
selectedElementIds: {
|
||||||
|
[image.id]: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const generateRandomNaturalWidthAndHeight = (image: ExcalidrawImageElement) => {
|
||||||
|
const initialWidth = image.width;
|
||||||
|
const initialHeight = image.height;
|
||||||
|
|
||||||
|
const scale = 1 + Math.random() * 5;
|
||||||
|
|
||||||
|
return {
|
||||||
|
naturalWidth: initialWidth * scale,
|
||||||
|
naturalHeight: initialHeight * scale,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const compareCrops = (cropA: ImageCrop, cropB: ImageCrop) => {
|
||||||
|
(Object.keys(cropA) as [keyof ImageCrop]).forEach((key) => {
|
||||||
|
const propA = cropA[key];
|
||||||
|
const propB = cropB[key];
|
||||||
|
|
||||||
|
expect(propA as number).toBeCloseTo(propB as number);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("Enter and leave the crop editor", () => {
|
||||||
|
it("enter the editor by double clicking", () => {
|
||||||
|
const image = h.elements[0];
|
||||||
|
expect(h.state.croppingElementId).toBe(null);
|
||||||
|
mouse.doubleClickOn(image);
|
||||||
|
expect(h.state.croppingElementId).not.toBe(null);
|
||||||
|
expect(h.state.croppingElementId).toBe(image.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("enter the editor by pressing enter", () => {
|
||||||
|
const image = h.elements[0];
|
||||||
|
expect(h.state.croppingElementId).toBe(null);
|
||||||
|
Keyboard.keyDown(KEYS.ENTER);
|
||||||
|
expect(h.state.croppingElementId).not.toBe(null);
|
||||||
|
expect(h.state.croppingElementId).toBe(image.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("leave the editor by clicking outside", () => {
|
||||||
|
const image = h.elements[0];
|
||||||
|
Keyboard.keyDown(KEYS.ENTER);
|
||||||
|
expect(h.state.croppingElementId).not.toBe(null);
|
||||||
|
|
||||||
|
mouse.click(image.x - 20, image.y - 20);
|
||||||
|
expect(h.state.croppingElementId).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("leave the editor by pressing escape", () => {
|
||||||
|
const image = h.elements[0];
|
||||||
|
mouse.doubleClickOn(image);
|
||||||
|
expect(h.state.croppingElementId).not.toBe(null);
|
||||||
|
|
||||||
|
Keyboard.keyDown(KEYS.ESCAPE);
|
||||||
|
expect(h.state.croppingElementId).toBe(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Crop an image", () => {
|
||||||
|
it("Cropping changes the dimension", async () => {
|
||||||
|
const image = h.elements[0] as ExcalidrawImageElement;
|
||||||
|
|
||||||
|
const initialWidth = image.width;
|
||||||
|
const initialHeight = image.height;
|
||||||
|
|
||||||
|
const { naturalWidth, naturalHeight } =
|
||||||
|
generateRandomNaturalWidthAndHeight(image);
|
||||||
|
|
||||||
|
UI.crop(image, "w", naturalWidth, naturalHeight, [initialWidth / 2, 0]);
|
||||||
|
|
||||||
|
expect(image.width).toBeLessThan(initialWidth);
|
||||||
|
UI.crop(image, "n", naturalWidth, naturalHeight, [0, initialHeight / 2]);
|
||||||
|
expect(image.height).toBeLessThan(initialHeight);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Cropping has minimal sizes", async () => {
|
||||||
|
const image = h.elements[0] as ExcalidrawImageElement;
|
||||||
|
const initialWidth = image.width;
|
||||||
|
const initialHeight = image.height;
|
||||||
|
|
||||||
|
const { naturalWidth, naturalHeight } =
|
||||||
|
generateRandomNaturalWidthAndHeight(image);
|
||||||
|
|
||||||
|
UI.crop(image, "w", naturalWidth, naturalHeight, [initialWidth, 0]);
|
||||||
|
expect(image.width).toBeLessThan(initialWidth);
|
||||||
|
expect(image.width).toBeGreaterThan(0);
|
||||||
|
UI.crop(image, "w", naturalWidth, naturalHeight, [-initialWidth, 0]);
|
||||||
|
UI.crop(image, "n", naturalWidth, naturalHeight, [0, initialHeight]);
|
||||||
|
expect(image.height).toBeLessThan(initialHeight);
|
||||||
|
expect(image.height).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Preserve aspect ratio", async () => {
|
||||||
|
let image = h.elements[0] as ExcalidrawImageElement;
|
||||||
|
const initialWidth = image.width;
|
||||||
|
const initialHeight = image.height;
|
||||||
|
|
||||||
|
const { naturalWidth, naturalHeight } =
|
||||||
|
generateRandomNaturalWidthAndHeight(image);
|
||||||
|
|
||||||
|
UI.crop(image, "w", naturalWidth, naturalHeight, [initialWidth / 3, 0]);
|
||||||
|
|
||||||
|
let resizedWidth = image.width;
|
||||||
|
let resizedHeight = image.height;
|
||||||
|
|
||||||
|
// max height, cropping should not change anything
|
||||||
|
UI.crop(
|
||||||
|
image,
|
||||||
|
"w",
|
||||||
|
naturalWidth,
|
||||||
|
naturalHeight,
|
||||||
|
[-initialWidth / 3, 0],
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
expect(image.width).toBe(resizedWidth);
|
||||||
|
expect(image.height).toBe(resizedHeight);
|
||||||
|
|
||||||
|
// re-crop to initial state
|
||||||
|
UI.crop(image, "w", naturalWidth, naturalHeight, [-initialWidth / 3, 0]);
|
||||||
|
// change crop height and width
|
||||||
|
UI.crop(image, "s", naturalWidth, naturalHeight, [0, -initialHeight / 2]);
|
||||||
|
UI.crop(image, "e", naturalWidth, naturalHeight, [-initialWidth / 3, 0]);
|
||||||
|
|
||||||
|
resizedWidth = image.width;
|
||||||
|
resizedHeight = image.height;
|
||||||
|
|
||||||
|
// test corner handle aspect ratio preserving
|
||||||
|
UI.crop(image, "se", naturalWidth, naturalHeight, [initialWidth, 0], true);
|
||||||
|
expect(image.width / image.height).toBe(resizedWidth / resizedHeight);
|
||||||
|
expect(image.width).toBeLessThanOrEqual(initialWidth);
|
||||||
|
expect(image.height).toBeLessThanOrEqual(initialHeight);
|
||||||
|
|
||||||
|
// reset
|
||||||
|
image = API.createElement({ type: "image", width: 200, height: 100 });
|
||||||
|
API.setElements([image]);
|
||||||
|
API.setAppState({
|
||||||
|
selectedElementIds: {
|
||||||
|
[image.id]: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 50 x 50 square
|
||||||
|
UI.crop(image, "nw", naturalWidth, naturalHeight, [150, 50]);
|
||||||
|
UI.crop(image, "n", naturalWidth, naturalHeight, [0, -100], true);
|
||||||
|
expect(image.width).toBeCloseTo(image.height);
|
||||||
|
// image is at the corner, not space to its right to expand, should not be able to resize
|
||||||
|
expect(image.height).toBeCloseTo(50);
|
||||||
|
|
||||||
|
UI.crop(image, "nw", naturalWidth, naturalHeight, [-150, -100], true);
|
||||||
|
expect(image.width).toBeCloseTo(image.height);
|
||||||
|
// max height should be reached
|
||||||
|
expect(image.height).toBeCloseTo(initialHeight);
|
||||||
|
expect(image.width).toBe(initialHeight);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Cropping and other features", async () => {
|
||||||
|
it("Cropping works independently of duplication", async () => {
|
||||||
|
const image = h.elements[0] as ExcalidrawImageElement;
|
||||||
|
const initialWidth = image.width;
|
||||||
|
const initialHeight = image.height;
|
||||||
|
|
||||||
|
const { naturalWidth, naturalHeight } =
|
||||||
|
generateRandomNaturalWidthAndHeight(image);
|
||||||
|
|
||||||
|
UI.crop(image, "nw", naturalWidth, naturalHeight, [
|
||||||
|
initialWidth / 2,
|
||||||
|
initialHeight / 2,
|
||||||
|
]);
|
||||||
|
Keyboard.keyDown(KEYS.ESCAPE);
|
||||||
|
const duplicatedImage = duplicateElement(null, new Map(), image, {});
|
||||||
|
act(() => {
|
||||||
|
h.app.scene.insertElement(duplicatedImage);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(duplicatedImage.width).toBe(image.width);
|
||||||
|
expect(duplicatedImage.height).toBe(image.height);
|
||||||
|
|
||||||
|
UI.crop(duplicatedImage, "nw", naturalWidth, naturalHeight, [
|
||||||
|
-initialWidth / 2,
|
||||||
|
-initialHeight / 2,
|
||||||
|
]);
|
||||||
|
expect(duplicatedImage.width).toBe(initialWidth);
|
||||||
|
expect(duplicatedImage.height).toBe(initialHeight);
|
||||||
|
const resizedWidth = image.width;
|
||||||
|
const resizedHeight = image.height;
|
||||||
|
|
||||||
|
expect(image.width).not.toBe(duplicatedImage.width);
|
||||||
|
expect(image.height).not.toBe(duplicatedImage.height);
|
||||||
|
UI.crop(duplicatedImage, "se", naturalWidth, naturalHeight, [
|
||||||
|
-initialWidth / 1.5,
|
||||||
|
-initialHeight / 1.5,
|
||||||
|
]);
|
||||||
|
expect(duplicatedImage.width).not.toBe(initialWidth);
|
||||||
|
expect(image.width).toBe(resizedWidth);
|
||||||
|
expect(duplicatedImage.height).not.toBe(initialHeight);
|
||||||
|
expect(image.height).toBe(resizedHeight);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Resizing should not affect crop", async () => {
|
||||||
|
const image = h.elements[0] as ExcalidrawImageElement;
|
||||||
|
const initialWidth = image.width;
|
||||||
|
const initialHeight = image.height;
|
||||||
|
|
||||||
|
const { naturalWidth, naturalHeight } =
|
||||||
|
generateRandomNaturalWidthAndHeight(image);
|
||||||
|
|
||||||
|
UI.crop(image, "nw", naturalWidth, naturalHeight, [
|
||||||
|
initialWidth / 2,
|
||||||
|
initialHeight / 2,
|
||||||
|
]);
|
||||||
|
const cropBeforeResizing = image.crop;
|
||||||
|
const cropBeforeResizingCloned = cloneJSON(image.crop) as ImageCrop;
|
||||||
|
expect(cropBeforeResizing).not.toBe(null);
|
||||||
|
|
||||||
|
UI.crop(image, "e", naturalWidth, naturalHeight, [200, 0]);
|
||||||
|
expect(cropBeforeResizing).toBe(image.crop);
|
||||||
|
compareCrops(cropBeforeResizingCloned, image.crop!);
|
||||||
|
|
||||||
|
UI.resize(image, "s", [0, -100]);
|
||||||
|
expect(cropBeforeResizing).toBe(image.crop);
|
||||||
|
compareCrops(cropBeforeResizingCloned, image.crop!);
|
||||||
|
|
||||||
|
UI.resize(image, "ne", [-50, -50]);
|
||||||
|
expect(cropBeforeResizing).toBe(image.crop);
|
||||||
|
compareCrops(cropBeforeResizingCloned, image.crop!);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Flipping does not change crop", async () => {
|
||||||
|
const image = h.elements[0] as ExcalidrawImageElement;
|
||||||
|
const initialWidth = image.width;
|
||||||
|
const initialHeight = image.height;
|
||||||
|
|
||||||
|
const { naturalWidth, naturalHeight } =
|
||||||
|
generateRandomNaturalWidthAndHeight(image);
|
||||||
|
|
||||||
|
mouse.doubleClickOn(image);
|
||||||
|
expect(h.state.croppingElementId).not.toBe(null);
|
||||||
|
UI.crop(image, "nw", naturalWidth, naturalHeight, [
|
||||||
|
initialWidth / 2,
|
||||||
|
initialHeight / 2,
|
||||||
|
]);
|
||||||
|
Keyboard.keyDown(KEYS.ESCAPE);
|
||||||
|
const cropBeforeResizing = image.crop;
|
||||||
|
const cropBeforeResizingCloned = cloneJSON(image.crop) as ImageCrop;
|
||||||
|
|
||||||
|
API.executeAction(actionFlipHorizontal);
|
||||||
|
expect(image.crop).toBe(cropBeforeResizing);
|
||||||
|
compareCrops(cropBeforeResizingCloned, image.crop!);
|
||||||
|
|
||||||
|
API.executeAction(actionFlipVertical);
|
||||||
|
expect(image.crop).toBe(cropBeforeResizing);
|
||||||
|
compareCrops(cropBeforeResizingCloned, image.crop!);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Exports should preserve crops", async () => {
|
||||||
|
const image = h.elements[0] as ExcalidrawImageElement;
|
||||||
|
const initialWidth = image.width;
|
||||||
|
const initialHeight = image.height;
|
||||||
|
|
||||||
|
const { naturalWidth, naturalHeight } =
|
||||||
|
generateRandomNaturalWidthAndHeight(image);
|
||||||
|
|
||||||
|
mouse.doubleClickOn(image);
|
||||||
|
expect(h.state.croppingElementId).not.toBe(null);
|
||||||
|
UI.crop(image, "nw", naturalWidth, naturalHeight, [
|
||||||
|
initialWidth / 2,
|
||||||
|
initialHeight / 4,
|
||||||
|
]);
|
||||||
|
Keyboard.keyDown(KEYS.ESCAPE);
|
||||||
|
const widthToHeightRatio = image.width / image.height;
|
||||||
|
|
||||||
|
const canvas = await exportToCanvas({
|
||||||
|
elements: [image],
|
||||||
|
appState: h.state,
|
||||||
|
files: h.app.files,
|
||||||
|
exportPadding: 0,
|
||||||
|
});
|
||||||
|
const exportedCanvasRatio = canvas.width / canvas.height;
|
||||||
|
|
||||||
|
expect(widthToHeightRatio).toBeCloseTo(exportedCanvasRatio);
|
||||||
|
|
||||||
|
const svg = await exportToSvg({
|
||||||
|
elements: [image],
|
||||||
|
appState: h.state,
|
||||||
|
files: h.app.files,
|
||||||
|
exportPadding: 0,
|
||||||
|
});
|
||||||
|
const svgWidth = svg.getAttribute("width");
|
||||||
|
const svgHeight = svg.getAttribute("height");
|
||||||
|
|
||||||
|
expect(svgWidth).toBeDefined();
|
||||||
|
expect(svgHeight).toBeDefined();
|
||||||
|
|
||||||
|
const exportedSvgRatio = Number(svgWidth) / Number(svgHeight);
|
||||||
|
expect(widthToHeightRatio).toBeCloseTo(exportedSvgRatio);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
import type { ExcalidrawElement } from "../element/types";
|
|
||||||
import { getShortcutKey } from "../utils";
|
|
||||||
import { API } from "./helpers/api";
|
|
||||||
import { render } from "./test-utils";
|
|
||||||
import { Excalidraw } from "../index";
|
|
||||||
import {
|
|
||||||
getShortcutFromShortcutName,
|
|
||||||
registerCustomShortcuts,
|
|
||||||
} from "../actions/shortcuts";
|
|
||||||
import type {
|
|
||||||
Action,
|
|
||||||
ActionPredicateFn,
|
|
||||||
ActionResult,
|
|
||||||
CustomActionName,
|
|
||||||
} from "../actions/types";
|
|
||||||
import { makeCustomActionName } from "../actions/types";
|
|
||||||
import {
|
|
||||||
actionChangeFontFamily,
|
|
||||||
actionChangeFontSize,
|
|
||||||
} from "../actions/actionProperties";
|
|
||||||
import { isTextElement } from "../element";
|
|
||||||
|
|
||||||
const { h } = window;
|
|
||||||
|
|
||||||
describe("regression tests", () => {
|
|
||||||
it("should retrieve custom shortcuts", () => {
|
|
||||||
const shortcutName = makeCustomActionName("test");
|
|
||||||
const shortcuts: Record<CustomActionName, string[]> = {};
|
|
||||||
shortcuts[shortcutName] = [
|
|
||||||
getShortcutKey("CtrlOrCmd+1"),
|
|
||||||
getShortcutKey("CtrlOrCmd+2"),
|
|
||||||
];
|
|
||||||
registerCustomShortcuts(shortcuts);
|
|
||||||
expect(getShortcutFromShortcutName(shortcutName)).toBe("Ctrl+1");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should apply universal action predicates", async () => {
|
|
||||||
await render(<Excalidraw />);
|
|
||||||
// Create the test elements
|
|
||||||
const el1 = API.createElement({ type: "rectangle", id: "A", y: 0 });
|
|
||||||
const el2 = API.createElement({ type: "rectangle", id: "B", y: 30 });
|
|
||||||
const el3 = API.createElement({ type: "text", id: "C", y: 60 });
|
|
||||||
const el12: ExcalidrawElement[] = [el1, el2];
|
|
||||||
const el13: ExcalidrawElement[] = [el1, el3];
|
|
||||||
const el23: ExcalidrawElement[] = [el2, el3];
|
|
||||||
const el123: ExcalidrawElement[] = [el1, el2, el3];
|
|
||||||
// Set up the custom Action enablers
|
|
||||||
const enableName = "custom.enable";
|
|
||||||
const enableAction: Action = {
|
|
||||||
name: enableName,
|
|
||||||
label: "",
|
|
||||||
perform: (): ActionResult => {
|
|
||||||
return {} as ActionResult;
|
|
||||||
},
|
|
||||||
trackEvent: false,
|
|
||||||
};
|
|
||||||
const enabler: ActionPredicateFn = function (action, elements) {
|
|
||||||
if (action.name !== enableName || elements.some((el) => el.y === 30)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
// Set up the standard Action disablers
|
|
||||||
const disabled1 = actionChangeFontFamily;
|
|
||||||
const disabled2 = actionChangeFontSize;
|
|
||||||
const disabler: ActionPredicateFn = function (action, elements) {
|
|
||||||
if (
|
|
||||||
action.name === disabled2.name &&
|
|
||||||
elements.some((el) => el.y === 0 || isTextElement(el))
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
// Test the custom Action enablers
|
|
||||||
const am = h.app.actionManager;
|
|
||||||
am.registerActionPredicate(enabler);
|
|
||||||
expect(am.isActionEnabled(enableAction, { elements: el12 })).toBe(true);
|
|
||||||
expect(am.isActionEnabled(enableAction, { elements: el13 })).toBe(false);
|
|
||||||
expect(am.isActionEnabled(enableAction, { elements: el23 })).toBe(true);
|
|
||||||
expect(am.isActionEnabled(disabled1, { elements: el12 })).toBe(true);
|
|
||||||
expect(am.isActionEnabled(disabled1, { elements: el13 })).toBe(true);
|
|
||||||
expect(am.isActionEnabled(disabled1, { elements: el23 })).toBe(true);
|
|
||||||
// Test the standard Action disablers
|
|
||||||
am.registerActionPredicate(disabler);
|
|
||||||
expect(am.isActionEnabled(disabled1, { elements: el123 })).toBe(true);
|
|
||||||
expect(am.isActionEnabled(disabled2, { elements: [el1] })).toBe(false);
|
|
||||||
expect(am.isActionEnabled(disabled2, { elements: [el2] })).toBe(true);
|
|
||||||
expect(am.isActionEnabled(disabled2, { elements: [el3] })).toBe(false);
|
|
||||||
expect(am.isActionEnabled(disabled2, { elements: el12 })).toBe(false);
|
|
||||||
expect(am.isActionEnabled(disabled2, { elements: el23 })).toBe(false);
|
|
||||||
expect(am.isActionEnabled(disabled2, { elements: el13 })).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -20,18 +20,7 @@ import fs from "fs";
|
|||||||
import util from "util";
|
import util from "util";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { getMimeType } from "../../data/blob";
|
import { getMimeType } from "../../data/blob";
|
||||||
import type {
|
|
||||||
SubtypeLoadedCb,
|
|
||||||
SubtypePrepFn,
|
|
||||||
SubtypeRecord,
|
|
||||||
} from "../../element/subtypes";
|
|
||||||
import {
|
import {
|
||||||
checkRefreshOnSubtypeLoad,
|
|
||||||
prepareSubtype,
|
|
||||||
selectSubtype,
|
|
||||||
} from "../../element/subtypes";
|
|
||||||
import {
|
|
||||||
maybeGetSubtypeProps,
|
|
||||||
newArrowElement,
|
newArrowElement,
|
||||||
newEmbeddableElement,
|
newEmbeddableElement,
|
||||||
newFrameElement,
|
newFrameElement,
|
||||||
@@ -58,19 +47,6 @@ createTestHook();
|
|||||||
const { h } = window;
|
const { h } = window;
|
||||||
|
|
||||||
export class API {
|
export class API {
|
||||||
static addSubtype = (record: SubtypeRecord, subtypePrepFn: SubtypePrepFn) => {
|
|
||||||
const subtypeLoadedCb: SubtypeLoadedCb = (hasSubtype) => {
|
|
||||||
if (checkRefreshOnSubtypeLoad(hasSubtype, h.elements)) {
|
|
||||||
h.app.refresh();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const prep = prepareSubtype(record, subtypePrepFn, subtypeLoadedCb);
|
|
||||||
if (prep.actions) {
|
|
||||||
h.app.actionManager.registerAll(prep.actions);
|
|
||||||
}
|
|
||||||
return prep;
|
|
||||||
};
|
|
||||||
|
|
||||||
static updateScene: InstanceType<typeof App>["updateScene"] = (...args) => {
|
static updateScene: InstanceType<typeof App>["updateScene"] = (...args) => {
|
||||||
act(() => {
|
act(() => {
|
||||||
h.app.updateScene(...args);
|
h.app.updateScene(...args);
|
||||||
@@ -199,8 +175,6 @@ export class API {
|
|||||||
verticalAlign?: T extends "text"
|
verticalAlign?: T extends "text"
|
||||||
? ExcalidrawTextElement["verticalAlign"]
|
? ExcalidrawTextElement["verticalAlign"]
|
||||||
: never;
|
: never;
|
||||||
subtype?: ExcalidrawElement["subtype"];
|
|
||||||
customData?: ExcalidrawElement["customData"];
|
|
||||||
boundElements?: ExcalidrawGenericElement["boundElements"];
|
boundElements?: ExcalidrawGenericElement["boundElements"];
|
||||||
containerId?: T extends "text"
|
containerId?: T extends "text"
|
||||||
? ExcalidrawTextElement["containerId"]
|
? ExcalidrawTextElement["containerId"]
|
||||||
@@ -240,14 +214,6 @@ export class API {
|
|||||||
|
|
||||||
const appState = h?.state || getDefaultAppState();
|
const appState = h?.state || getDefaultAppState();
|
||||||
|
|
||||||
const custom = maybeGetSubtypeProps(
|
|
||||||
{
|
|
||||||
subtype: rest.subtype ?? selectSubtype(appState, type)?.subtype,
|
|
||||||
customData:
|
|
||||||
rest.customData ?? selectSubtype(appState, type)?.customData,
|
|
||||||
},
|
|
||||||
type,
|
|
||||||
);
|
|
||||||
const base: Omit<
|
const base: Omit<
|
||||||
ExcalidrawGenericElement,
|
ExcalidrawGenericElement,
|
||||||
| "id"
|
| "id"
|
||||||
@@ -262,7 +228,6 @@ export class API {
|
|||||||
| "link"
|
| "link"
|
||||||
| "updated"
|
| "updated"
|
||||||
> = {
|
> = {
|
||||||
...custom,
|
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
frameId: rest.frameId ?? null,
|
frameId: rest.frameId ?? null,
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"toolBar": {
|
|
||||||
"test": "Test",
|
|
||||||
"test2": "Test 2",
|
|
||||||
"test3": "Test 3"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import type { ToolType } from "../../types";
|
|
||||||
import type {
|
import type {
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
ExcalidrawLinearElement,
|
ExcalidrawLinearElement,
|
||||||
@@ -9,6 +8,7 @@ import type {
|
|||||||
ExcalidrawDiamondElement,
|
ExcalidrawDiamondElement,
|
||||||
ExcalidrawTextContainer,
|
ExcalidrawTextContainer,
|
||||||
ExcalidrawTextElementWithContainer,
|
ExcalidrawTextElementWithContainer,
|
||||||
|
ExcalidrawImageElement,
|
||||||
} from "../../element/types";
|
} from "../../element/types";
|
||||||
import type { TransformHandleType } from "../../element/transformHandles";
|
import type { TransformHandleType } from "../../element/transformHandles";
|
||||||
import {
|
import {
|
||||||
@@ -35,6 +35,8 @@ import { arrayToMap } from "../../utils";
|
|||||||
import { createTestHook } from "../../components/App";
|
import { createTestHook } from "../../components/App";
|
||||||
import type { GlobalPoint, LocalPoint, Radians } from "../../../math";
|
import type { GlobalPoint, LocalPoint, Radians } from "../../../math";
|
||||||
import { pointFrom, pointRotateRads } from "../../../math";
|
import { pointFrom, pointRotateRads } from "../../../math";
|
||||||
|
import { cropElement } from "../../element/cropElement";
|
||||||
|
import type { ToolType } from "../../types";
|
||||||
|
|
||||||
// so that window.h is available when App.tsx is not imported as well.
|
// so that window.h is available when App.tsx is not imported as well.
|
||||||
createTestHook();
|
createTestHook();
|
||||||
@@ -561,6 +563,38 @@ export class UI {
|
|||||||
return transform(element, handle, mouseMove, keyboardModifiers);
|
return transform(element, handle, mouseMove, keyboardModifiers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static crop(
|
||||||
|
element: ExcalidrawImageElement,
|
||||||
|
handle: TransformHandleDirection,
|
||||||
|
naturalWidth: number,
|
||||||
|
naturalHeight: number,
|
||||||
|
mouseMove: [deltaX: number, deltaY: number],
|
||||||
|
keepAspectRatio = false,
|
||||||
|
) {
|
||||||
|
const handleCoords = getTransformHandles(
|
||||||
|
element,
|
||||||
|
h.state.zoom,
|
||||||
|
arrayToMap(h.elements),
|
||||||
|
"mouse",
|
||||||
|
{},
|
||||||
|
)[handle]!;
|
||||||
|
|
||||||
|
const clientX = handleCoords[0] + handleCoords[2] / 2;
|
||||||
|
const clientY = handleCoords[1] + handleCoords[3] / 2;
|
||||||
|
|
||||||
|
const mutations = cropElement(
|
||||||
|
element,
|
||||||
|
handle,
|
||||||
|
naturalWidth,
|
||||||
|
naturalHeight,
|
||||||
|
clientX + mouseMove[0],
|
||||||
|
clientY + mouseMove[1],
|
||||||
|
keepAspectRatio ? element.width / element.height : undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
API.updateElement(element, mutations);
|
||||||
|
}
|
||||||
|
|
||||||
static rotate(
|
static rotate(
|
||||||
element: ExcalidrawElement | ExcalidrawElement[],
|
element: ExcalidrawElement | ExcalidrawElement[],
|
||||||
mouseMove: [deltaX: number, deltaY: number],
|
mouseMove: [deltaX: number, deltaY: number],
|
||||||
|
|||||||
@@ -1,679 +0,0 @@
|
|||||||
import { vi } from "vitest";
|
|
||||||
import fallbackLangData from "./helpers/locales/en.json";
|
|
||||||
import type {
|
|
||||||
SubtypeLoadedCb,
|
|
||||||
SubtypeRecord,
|
|
||||||
SubtypeMethods,
|
|
||||||
SubtypePrepFn,
|
|
||||||
} from "../element/subtypes";
|
|
||||||
import {
|
|
||||||
addSubtypeMethods,
|
|
||||||
ensureSubtypesLoadedForElements,
|
|
||||||
getSubtypeMethods,
|
|
||||||
getSubtypeNames,
|
|
||||||
hasAlwaysEnabledActions,
|
|
||||||
isValidSubtype,
|
|
||||||
selectSubtype,
|
|
||||||
subtypeCollides,
|
|
||||||
} from "../element/subtypes";
|
|
||||||
|
|
||||||
import { render } from "./test-utils";
|
|
||||||
import { API } from "./helpers/api";
|
|
||||||
import { Excalidraw } from "../index";
|
|
||||||
|
|
||||||
import type {
|
|
||||||
ExcalidrawElement,
|
|
||||||
ExcalidrawTextElement,
|
|
||||||
FontString,
|
|
||||||
Theme,
|
|
||||||
} from "../element/types";
|
|
||||||
import { createIcon, iconFillColor } from "../components/icons";
|
|
||||||
import { SubtypeButton } from "../components/Subtypes";
|
|
||||||
import type { LangLdr } from "../i18n";
|
|
||||||
import { registerCustomLangData, t } from "../i18n";
|
|
||||||
import { getFontString, getShortcutKey } from "../utils";
|
|
||||||
import * as textElementUtils from "../element/textElement";
|
|
||||||
import { isTextElement } from "../element";
|
|
||||||
import { mutateElement, newElementWith } from "../element/mutateElement";
|
|
||||||
import type { Action, ActionName } from "../actions/types";
|
|
||||||
import { makeCustomActionName } from "../actions/types";
|
|
||||||
import type { AppState } from "../types";
|
|
||||||
import { getShortcutFromShortcutName } from "../actions/shortcuts";
|
|
||||||
import { actionChangeSloppiness } from "../actions";
|
|
||||||
import { actionChangeRoundness } from "../actions/actionProperties";
|
|
||||||
|
|
||||||
const MW = 200;
|
|
||||||
const TWIDTH = 200;
|
|
||||||
const THEIGHT = 20;
|
|
||||||
const FONTSIZE = 20;
|
|
||||||
const DBFONTSIZE = 40;
|
|
||||||
const TRFONTSIZE = 60;
|
|
||||||
|
|
||||||
const getLangData: LangLdr = (langCode) =>
|
|
||||||
import(`./helpers/locales/${langCode}.json`);
|
|
||||||
|
|
||||||
const testSubtypeIcon = ({ theme }: { theme: Theme }) =>
|
|
||||||
createIcon(
|
|
||||||
<path
|
|
||||||
stroke={iconFillColor(theme)}
|
|
||||||
strokeWidth={2}
|
|
||||||
strokeLinecap="round"
|
|
||||||
fill="none"
|
|
||||||
/>,
|
|
||||||
{ width: 40, height: 20, mirror: true },
|
|
||||||
);
|
|
||||||
|
|
||||||
const TEST_ACTION = "testAction";
|
|
||||||
const TEST_DISABLE1 = actionChangeSloppiness;
|
|
||||||
const TEST_DISABLE3 = actionChangeRoundness;
|
|
||||||
|
|
||||||
const test1: SubtypeRecord = {
|
|
||||||
subtype: "test",
|
|
||||||
parents: ["line", "arrow", "rectangle", "diamond", "ellipse"],
|
|
||||||
disabledNames: [TEST_DISABLE1.name as ActionName],
|
|
||||||
actionNames: [TEST_ACTION],
|
|
||||||
};
|
|
||||||
const test1NonParent = "text" as const;
|
|
||||||
|
|
||||||
const test2: SubtypeRecord = {
|
|
||||||
subtype: "test2",
|
|
||||||
parents: ["text"],
|
|
||||||
};
|
|
||||||
|
|
||||||
const test3: SubtypeRecord = {
|
|
||||||
subtype: "test3",
|
|
||||||
parents: ["text", "line"],
|
|
||||||
shortcutMap: {
|
|
||||||
testShortcut: [getShortcutKey("Shift+T")],
|
|
||||||
},
|
|
||||||
alwaysEnabledNames: ["test3Always"],
|
|
||||||
disabledNames: [TEST_DISABLE3.name as ActionName],
|
|
||||||
};
|
|
||||||
|
|
||||||
let testActions: Action[] | null = null;
|
|
||||||
|
|
||||||
const makeTestActions = () => {
|
|
||||||
if (testActions) {
|
|
||||||
return testActions;
|
|
||||||
}
|
|
||||||
const testAction: Action = {
|
|
||||||
name: makeCustomActionName(TEST_ACTION),
|
|
||||||
label: t("toolBar.test"),
|
|
||||||
trackEvent: false,
|
|
||||||
perform: (elements, appState) => {
|
|
||||||
return {
|
|
||||||
elements,
|
|
||||||
storeAction: "none",
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
testActions = [
|
|
||||||
testAction,
|
|
||||||
SubtypeButton(test1.subtype, test1.parents[0], testSubtypeIcon),
|
|
||||||
SubtypeButton(test2.subtype, test2.parents[0], testSubtypeIcon),
|
|
||||||
SubtypeButton(test3.subtype, test3.parents[0], testSubtypeIcon),
|
|
||||||
];
|
|
||||||
return testActions;
|
|
||||||
};
|
|
||||||
|
|
||||||
const cleanTestElementUpdate = function (updates) {
|
|
||||||
const oldUpdates = {};
|
|
||||||
for (const key in updates) {
|
|
||||||
if (key !== "roughness") {
|
|
||||||
(oldUpdates as any)[key] = (updates as any)[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(updates as any).roughness = 0;
|
|
||||||
return oldUpdates;
|
|
||||||
} as SubtypeMethods["clean"];
|
|
||||||
|
|
||||||
const prepareNullSubtype = function () {
|
|
||||||
const methods = {} as SubtypeMethods;
|
|
||||||
methods.clean = cleanTestElementUpdate;
|
|
||||||
methods.measureText = measureTest2;
|
|
||||||
methods.wrapText = wrapTest2;
|
|
||||||
|
|
||||||
const actions = makeTestActions().filter((_, index) => index > 0);
|
|
||||||
return { actions, methods };
|
|
||||||
} as SubtypePrepFn;
|
|
||||||
|
|
||||||
const prepareTest1Subtype = function (
|
|
||||||
addSubtypeAction,
|
|
||||||
addLangData,
|
|
||||||
onSubtypeLoaded,
|
|
||||||
) {
|
|
||||||
const methods = {} as SubtypeMethods;
|
|
||||||
methods.clean = cleanTestElementUpdate;
|
|
||||||
|
|
||||||
addLangData(fallbackLangData, getLangData);
|
|
||||||
registerCustomLangData(fallbackLangData, getLangData);
|
|
||||||
|
|
||||||
const actions = makeTestActions().filter((_, index) => index < 2);
|
|
||||||
actions.forEach((action) => addSubtypeAction(action));
|
|
||||||
|
|
||||||
return { actions, methods };
|
|
||||||
} as SubtypePrepFn;
|
|
||||||
|
|
||||||
let test2Loaded = false;
|
|
||||||
|
|
||||||
const ensureLoadedTest2: SubtypeMethods["ensureLoaded"] = async (callback) => {
|
|
||||||
test2Loaded = true;
|
|
||||||
if (onTest2Loaded) {
|
|
||||||
onTest2Loaded((el) => isTextElement(el) && el.subtype === test2.subtype);
|
|
||||||
}
|
|
||||||
if (callback) {
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const measureTest2: SubtypeMethods["measureText"] = function (element, next) {
|
|
||||||
const text = next?.text ?? element.text;
|
|
||||||
const customData = next?.customData ?? {};
|
|
||||||
const fontSize = customData.triple
|
|
||||||
? TRFONTSIZE
|
|
||||||
: next?.fontSize ?? element.fontSize;
|
|
||||||
const fontFamily = element.fontFamily;
|
|
||||||
const fontString = getFontString({ fontSize, fontFamily });
|
|
||||||
const lineHeight = element.lineHeight;
|
|
||||||
const metrics = textElementUtils.measureText(text, fontString, lineHeight);
|
|
||||||
const width = test2Loaded
|
|
||||||
? metrics.width * 2
|
|
||||||
: Math.max(metrics.width - 10, 0);
|
|
||||||
const height = test2Loaded
|
|
||||||
? metrics.height * 2
|
|
||||||
: Math.max(metrics.height - 5, 0);
|
|
||||||
return { width, height };
|
|
||||||
};
|
|
||||||
|
|
||||||
const wrapTest2: SubtypeMethods["wrapText"] = function (
|
|
||||||
element,
|
|
||||||
maxWidth,
|
|
||||||
next,
|
|
||||||
) {
|
|
||||||
const text = next?.text ?? element.originalText;
|
|
||||||
if (next?.customData && next?.customData.triple === true) {
|
|
||||||
return `${text.split(" ").join("\n")}\nHELLO WORLD.`;
|
|
||||||
}
|
|
||||||
if (next?.fontSize === DBFONTSIZE) {
|
|
||||||
return `${text.split(" ").join("\n")}\nHELLO World.`;
|
|
||||||
}
|
|
||||||
return `${text.split(" ").join("\n")}\nHello world.`;
|
|
||||||
};
|
|
||||||
|
|
||||||
let onTest2Loaded: SubtypeLoadedCb | undefined;
|
|
||||||
|
|
||||||
const prepareTest2Subtype = function (
|
|
||||||
addSubtypeAction,
|
|
||||||
addLangData,
|
|
||||||
onSubtypeLoaded,
|
|
||||||
) {
|
|
||||||
const methods = {
|
|
||||||
ensureLoaded: ensureLoadedTest2,
|
|
||||||
measureText: measureTest2,
|
|
||||||
wrapText: wrapTest2,
|
|
||||||
} as SubtypeMethods;
|
|
||||||
|
|
||||||
addLangData(fallbackLangData, getLangData);
|
|
||||||
registerCustomLangData(fallbackLangData, getLangData);
|
|
||||||
|
|
||||||
const actions = [makeTestActions()[2]];
|
|
||||||
actions.forEach((action) => addSubtypeAction(action));
|
|
||||||
|
|
||||||
onTest2Loaded = onSubtypeLoaded;
|
|
||||||
|
|
||||||
return { actions, methods };
|
|
||||||
} as SubtypePrepFn;
|
|
||||||
|
|
||||||
const prepareTest3Subtype = function (
|
|
||||||
addSubtypeAction,
|
|
||||||
addLangData,
|
|
||||||
onSubtypeLoaded,
|
|
||||||
) {
|
|
||||||
const methods = {} as SubtypeMethods;
|
|
||||||
|
|
||||||
addLangData(fallbackLangData, getLangData);
|
|
||||||
registerCustomLangData(fallbackLangData, getLangData);
|
|
||||||
|
|
||||||
const actions = [makeTestActions()[3]];
|
|
||||||
actions.forEach((action) => addSubtypeAction(action));
|
|
||||||
|
|
||||||
return { actions, methods };
|
|
||||||
} as SubtypePrepFn;
|
|
||||||
|
|
||||||
const { h } = window;
|
|
||||||
|
|
||||||
describe("subtype registration", () => {
|
|
||||||
it("should check for invalid subtype or parents", async () => {
|
|
||||||
await render(<Excalidraw />, {});
|
|
||||||
// Define invalid subtype records
|
|
||||||
const null1 = {} as SubtypeRecord;
|
|
||||||
const null2 = { subtype: "" } as SubtypeRecord;
|
|
||||||
const null3 = { subtype: "null" } as SubtypeRecord;
|
|
||||||
const null4 = { subtype: "null", parents: [] } as SubtypeRecord;
|
|
||||||
// Try registering the invalid subtypes
|
|
||||||
const prepN1 = API.addSubtype(null1, prepareNullSubtype);
|
|
||||||
const prepN2 = API.addSubtype(null2, prepareNullSubtype);
|
|
||||||
const prepN3 = API.addSubtype(null3, prepareNullSubtype);
|
|
||||||
const prepN4 = API.addSubtype(null4, prepareNullSubtype);
|
|
||||||
// Verify the guards in `prepareSubtype` worked
|
|
||||||
expect(prepN1).toStrictEqual({ actions: null, methods: {} });
|
|
||||||
expect(prepN2).toStrictEqual({ actions: null, methods: {} });
|
|
||||||
expect(prepN3).toStrictEqual({ actions: null, methods: {} });
|
|
||||||
expect(prepN4).toStrictEqual({ actions: null, methods: {} });
|
|
||||||
});
|
|
||||||
it("should return subtype actions and methods correctly", async () => {
|
|
||||||
// Check initial registration works
|
|
||||||
let prep1 = API.addSubtype(test1, prepareTest1Subtype);
|
|
||||||
const actions = makeTestActions().filter((_, index) => index < 2);
|
|
||||||
expect(prep1.actions).toStrictEqual(actions);
|
|
||||||
expect(prep1.methods).toStrictEqual({ clean: cleanTestElementUpdate });
|
|
||||||
// Check repeat registration fails
|
|
||||||
prep1 = API.addSubtype(test1, prepareNullSubtype);
|
|
||||||
expect(prep1.actions).toBeNull();
|
|
||||||
expect(prep1.methods).toStrictEqual({ clean: cleanTestElementUpdate });
|
|
||||||
|
|
||||||
// Check initial registration works
|
|
||||||
let prep2 = API.addSubtype(test2, prepareTest2Subtype);
|
|
||||||
expect(prep2.actions).toStrictEqual([makeTestActions()[2]]);
|
|
||||||
expect(prep2.methods).toStrictEqual({
|
|
||||||
ensureLoaded: ensureLoadedTest2,
|
|
||||||
measureText: measureTest2,
|
|
||||||
wrapText: wrapTest2,
|
|
||||||
});
|
|
||||||
// Check repeat registration fails
|
|
||||||
prep2 = API.addSubtype(test2, prepareNullSubtype);
|
|
||||||
expect(prep2.actions).toBeNull();
|
|
||||||
expect(prep2.methods).toStrictEqual({
|
|
||||||
ensureLoaded: ensureLoadedTest2,
|
|
||||||
measureText: measureTest2,
|
|
||||||
wrapText: wrapTest2,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check initial registration works
|
|
||||||
let prep3 = API.addSubtype(test3, prepareTest3Subtype);
|
|
||||||
expect(prep3.actions).toStrictEqual([makeTestActions()[3]]);
|
|
||||||
expect(prep3.methods).toStrictEqual({});
|
|
||||||
// Check repeat registration fails
|
|
||||||
prep3 = API.addSubtype(test3, prepareNullSubtype);
|
|
||||||
expect(prep3.actions).toBeNull();
|
|
||||||
expect(prep3.methods).toStrictEqual({});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("subtypes", () => {
|
|
||||||
it("should correctly register", async () => {
|
|
||||||
const subtypes = getSubtypeNames();
|
|
||||||
expect(subtypes).toContain(test1.subtype);
|
|
||||||
expect(subtypes).toContain(test2.subtype);
|
|
||||||
expect(subtypes).toContain(test3.subtype);
|
|
||||||
});
|
|
||||||
it("should return subtype methods", async () => {
|
|
||||||
expect(getSubtypeMethods(undefined)).toBeUndefined();
|
|
||||||
const test1Methods = getSubtypeMethods(test1.subtype);
|
|
||||||
expect(test1Methods?.clean).toBeDefined();
|
|
||||||
expect(test1Methods?.render).toBeUndefined();
|
|
||||||
expect(test1Methods?.wrapText).toBeUndefined();
|
|
||||||
expect(test1Methods?.renderSvg).toBeUndefined();
|
|
||||||
expect(test1Methods?.measureText).toBeUndefined();
|
|
||||||
expect(test1Methods?.ensureLoaded).toBeUndefined();
|
|
||||||
});
|
|
||||||
it("should not overwrite subtype methods", async () => {
|
|
||||||
addSubtypeMethods(test1.subtype, {});
|
|
||||||
addSubtypeMethods(test2.subtype, {});
|
|
||||||
addSubtypeMethods(test3.subtype, { clean: cleanTestElementUpdate });
|
|
||||||
const test1Methods = getSubtypeMethods(test1.subtype);
|
|
||||||
expect(test1Methods?.clean).toBeDefined();
|
|
||||||
const test2Methods = getSubtypeMethods(test2.subtype);
|
|
||||||
expect(test2Methods?.measureText).toBeDefined();
|
|
||||||
expect(test2Methods?.wrapText).toBeDefined();
|
|
||||||
const test3Methods = getSubtypeMethods(test3.subtype);
|
|
||||||
expect(test3Methods?.clean).toBeUndefined();
|
|
||||||
});
|
|
||||||
it("should register custom shortcuts", async () => {
|
|
||||||
expect(
|
|
||||||
getShortcutFromShortcutName(makeCustomActionName("testShortcut")),
|
|
||||||
).toBe("Shift+T");
|
|
||||||
});
|
|
||||||
it("should correctly validate", async () => {
|
|
||||||
test1.parents.forEach((p) => {
|
|
||||||
expect(isValidSubtype(test1.subtype, p)).toBe(true);
|
|
||||||
expect(isValidSubtype(undefined, p)).toBe(false);
|
|
||||||
});
|
|
||||||
expect(isValidSubtype(test1.subtype, test1NonParent)).toBe(false);
|
|
||||||
expect(isValidSubtype(test1.subtype, undefined)).toBe(false);
|
|
||||||
expect(isValidSubtype(undefined, undefined)).toBe(false);
|
|
||||||
});
|
|
||||||
it("should collide with themselves", async () => {
|
|
||||||
expect(subtypeCollides(test1.subtype, [test1.subtype])).toBe(true);
|
|
||||||
expect(subtypeCollides(test1.subtype, [test1.subtype, test2.subtype])).toBe(
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
it("should not collide without type overlap", async () => {
|
|
||||||
expect(subtypeCollides(test1.subtype, [test2.subtype])).toBe(false);
|
|
||||||
});
|
|
||||||
it("should collide with type overlap", async () => {
|
|
||||||
expect(subtypeCollides(test1.subtype, [test3.subtype])).toBe(true);
|
|
||||||
});
|
|
||||||
it("should apply to ExcalidrawElements", async () => {
|
|
||||||
const elements = [
|
|
||||||
API.createElement({ type: "line", id: "A", subtype: test1.subtype }),
|
|
||||||
API.createElement({ type: "arrow", id: "B", subtype: test1.subtype }),
|
|
||||||
API.createElement({ type: "rectangle", id: "C", subtype: test1.subtype }),
|
|
||||||
API.createElement({ type: "diamond", id: "D", subtype: test1.subtype }),
|
|
||||||
API.createElement({ type: "ellipse", id: "E", subtype: test1.subtype }),
|
|
||||||
];
|
|
||||||
await render(<Excalidraw />, { localStorageData: { elements } });
|
|
||||||
elements.forEach((el) => expect(el.subtype).toBe(test1.subtype));
|
|
||||||
});
|
|
||||||
it("should enforce prop value restrictions", async () => {
|
|
||||||
const elements = [
|
|
||||||
API.createElement({
|
|
||||||
type: "line",
|
|
||||||
id: "A",
|
|
||||||
subtype: test1.subtype,
|
|
||||||
roughness: 1,
|
|
||||||
}),
|
|
||||||
API.createElement({ type: "line", id: "B", roughness: 1 }),
|
|
||||||
];
|
|
||||||
await render(<Excalidraw />, { localStorageData: { elements } });
|
|
||||||
elements.forEach((el) => {
|
|
||||||
if (el.subtype === test1.subtype) {
|
|
||||||
expect(el.roughness).toBe(0);
|
|
||||||
} else {
|
|
||||||
expect(el.roughness).toBe(1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it("should consider enforced prop values in version increments", async () => {
|
|
||||||
const rectA = API.createElement({
|
|
||||||
type: "line",
|
|
||||||
id: "A",
|
|
||||||
subtype: test1.subtype,
|
|
||||||
roughness: 1,
|
|
||||||
strokeWidth: 1,
|
|
||||||
});
|
|
||||||
const rectB = API.createElement({
|
|
||||||
type: "line",
|
|
||||||
id: "B",
|
|
||||||
subtype: test1.subtype,
|
|
||||||
roughness: 1,
|
|
||||||
strokeWidth: 1,
|
|
||||||
});
|
|
||||||
// Initial element creation checks
|
|
||||||
expect(rectA.roughness).toBe(0);
|
|
||||||
expect(rectB.roughness).toBe(0);
|
|
||||||
expect(rectA.version).toBe(1);
|
|
||||||
expect(rectB.version).toBe(1);
|
|
||||||
// Check that attempting to set prop values not permitted by the subtype
|
|
||||||
// doesn't increment element versions
|
|
||||||
mutateElement(rectA, { roughness: 2 });
|
|
||||||
mutateElement(rectB, { roughness: 2, strokeWidth: 2 });
|
|
||||||
expect(rectA.version).toBe(1);
|
|
||||||
expect(rectB.version).toBe(2);
|
|
||||||
// Check that element versions don't increment when creating new elements
|
|
||||||
// while attempting to use prop values not permitted by the subtype
|
|
||||||
// First check based on `rectA` (unsuccessfully mutated)
|
|
||||||
const rectC = newElementWith(rectA, { roughness: 1 });
|
|
||||||
const rectD = newElementWith(rectA, { roughness: 1, strokeWidth: 1.5 });
|
|
||||||
expect(rectC.version).toBe(1);
|
|
||||||
expect(rectD.version).toBe(2);
|
|
||||||
// Then check based on `rectB` (successfully mutated)
|
|
||||||
const rectE = newElementWith(rectB, { roughness: 1 });
|
|
||||||
const rectF = newElementWith(rectB, { roughness: 1, strokeWidth: 1.5 });
|
|
||||||
expect(rectE.version).toBe(2);
|
|
||||||
expect(rectF.version).toBe(3);
|
|
||||||
});
|
|
||||||
it("should call custom text methods", async () => {
|
|
||||||
const testString = "A quick brown fox jumps over the lazy dog.";
|
|
||||||
const elements = [
|
|
||||||
API.createElement({
|
|
||||||
type: "text",
|
|
||||||
id: "A",
|
|
||||||
subtype: test2.subtype,
|
|
||||||
text: testString,
|
|
||||||
fontSize: FONTSIZE,
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
await render(<Excalidraw />, { localStorageData: { elements } });
|
|
||||||
const mockMeasureText = (text: string, font: FontString) => {
|
|
||||||
if (text === testString) {
|
|
||||||
let multiplier = 1;
|
|
||||||
if (font.includes(`${DBFONTSIZE}`)) {
|
|
||||||
multiplier = 2;
|
|
||||||
}
|
|
||||||
if (font.includes(`${TRFONTSIZE}`)) {
|
|
||||||
multiplier = 3;
|
|
||||||
}
|
|
||||||
const width = multiplier * TWIDTH;
|
|
||||||
const height = multiplier * THEIGHT;
|
|
||||||
return { width, height };
|
|
||||||
}
|
|
||||||
return { width: 1, height: 0 };
|
|
||||||
};
|
|
||||||
|
|
||||||
vi.spyOn(textElementUtils, "measureText").mockImplementation(
|
|
||||||
mockMeasureText,
|
|
||||||
);
|
|
||||||
|
|
||||||
elements.forEach((el) => {
|
|
||||||
if (isTextElement(el)) {
|
|
||||||
// First test with `ExcalidrawTextElement.text`
|
|
||||||
const metrics = textElementUtils.measureTextElement(el);
|
|
||||||
expect(metrics).toStrictEqual({
|
|
||||||
width: TWIDTH - 10,
|
|
||||||
height: THEIGHT - 5,
|
|
||||||
});
|
|
||||||
const wrappedText = textElementUtils.wrapTextElement(el, MW);
|
|
||||||
expect(wrappedText).toEqual(
|
|
||||||
`${testString.split(" ").join("\n")}\nHello world.`,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Now test with modified text in `next`
|
|
||||||
let next: {
|
|
||||||
text?: string;
|
|
||||||
fontSize?: number;
|
|
||||||
customData?: Record<string, any>;
|
|
||||||
} = {
|
|
||||||
text: "Hello world.",
|
|
||||||
};
|
|
||||||
const nextMetrics = textElementUtils.measureTextElement(el, next);
|
|
||||||
expect(nextMetrics).toStrictEqual({ width: 0, height: 0 });
|
|
||||||
const nextWrappedText = textElementUtils.wrapTextElement(el, MW, next);
|
|
||||||
expect(nextWrappedText).toEqual("Hello\nworld.\nHello world.");
|
|
||||||
|
|
||||||
// Now test modified fontSizes in `next`
|
|
||||||
next = { fontSize: DBFONTSIZE };
|
|
||||||
const nextFM = textElementUtils.measureTextElement(el, next);
|
|
||||||
expect(nextFM).toStrictEqual({
|
|
||||||
width: 2 * TWIDTH - 10,
|
|
||||||
height: 2 * THEIGHT - 5,
|
|
||||||
});
|
|
||||||
const nextFWrText = textElementUtils.wrapTextElement(el, MW, next);
|
|
||||||
expect(nextFWrText).toEqual(
|
|
||||||
`${testString.split(" ").join("\n")}\nHELLO World.`,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Now test customData in `next`
|
|
||||||
next = { customData: { triple: true } };
|
|
||||||
const nextCD = textElementUtils.measureTextElement(el, next);
|
|
||||||
expect(nextCD).toStrictEqual({
|
|
||||||
width: 3 * TWIDTH - 10,
|
|
||||||
height: 3 * THEIGHT - 5,
|
|
||||||
});
|
|
||||||
const nextCDWrText = textElementUtils.wrapTextElement(el, MW, next);
|
|
||||||
expect(nextCDWrText).toEqual(
|
|
||||||
`${testString.split(" ").join("\n")}\nHELLO WORLD.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it("should recognize subtypes with always-enabled actions", async () => {
|
|
||||||
expect(hasAlwaysEnabledActions(test1.subtype)).toBe(false);
|
|
||||||
expect(hasAlwaysEnabledActions(test2.subtype)).toBe(false);
|
|
||||||
expect(hasAlwaysEnabledActions(test3.subtype)).toBe(true);
|
|
||||||
});
|
|
||||||
it("should select active subtypes and customData", async () => {
|
|
||||||
const appState = {} as {
|
|
||||||
activeSubtypes: AppState["activeSubtypes"];
|
|
||||||
customData: AppState["customData"];
|
|
||||||
};
|
|
||||||
|
|
||||||
// No active subtypes
|
|
||||||
let subtypes = selectSubtype(appState, "text");
|
|
||||||
expect(subtypes.subtype).toBeUndefined();
|
|
||||||
expect(subtypes.customData).toBeUndefined();
|
|
||||||
// Subtype for both "text" and "line" types
|
|
||||||
appState.activeSubtypes = [test3.subtype];
|
|
||||||
subtypes = selectSubtype(appState, "text");
|
|
||||||
expect(subtypes.subtype).toBe(test3.subtype);
|
|
||||||
subtypes = selectSubtype(appState, "line");
|
|
||||||
expect(subtypes.subtype).toBe(test3.subtype);
|
|
||||||
subtypes = selectSubtype(appState, "arrow");
|
|
||||||
expect(subtypes.subtype).toBeUndefined();
|
|
||||||
// Subtype for multiple linear types
|
|
||||||
appState.activeSubtypes = [test1.subtype];
|
|
||||||
subtypes = selectSubtype(appState, "text");
|
|
||||||
expect(subtypes.subtype).toBeUndefined();
|
|
||||||
subtypes = selectSubtype(appState, "line");
|
|
||||||
expect(subtypes.subtype).toBe(test1.subtype);
|
|
||||||
subtypes = selectSubtype(appState, "arrow");
|
|
||||||
expect(subtypes.subtype).toBe(test1.subtype);
|
|
||||||
// Subtype for "text" only
|
|
||||||
appState.activeSubtypes = [test2.subtype];
|
|
||||||
subtypes = selectSubtype(appState, "text");
|
|
||||||
expect(subtypes.subtype).toBe(test2.subtype);
|
|
||||||
subtypes = selectSubtype(appState, "line");
|
|
||||||
expect(subtypes.subtype).toBeUndefined();
|
|
||||||
subtypes = selectSubtype(appState, "arrow");
|
|
||||||
expect(subtypes.subtype).toBeUndefined();
|
|
||||||
|
|
||||||
// Test customData
|
|
||||||
appState.customData = {};
|
|
||||||
appState.customData[test1.subtype] = { test: true };
|
|
||||||
appState.customData[test2.subtype] = { test2: true };
|
|
||||||
appState.customData[test3.subtype] = { test3: true };
|
|
||||||
// Subtype for both "text" and "line" types
|
|
||||||
appState.activeSubtypes = [test3.subtype];
|
|
||||||
subtypes = selectSubtype(appState, "text");
|
|
||||||
expect(subtypes.customData).toBeDefined();
|
|
||||||
expect(subtypes.customData![test1.subtype]).toBeUndefined();
|
|
||||||
expect(subtypes.customData![test2.subtype]).toBeUndefined();
|
|
||||||
expect(subtypes.customData![test3.subtype]).toBe(true);
|
|
||||||
subtypes = selectSubtype(appState, "line");
|
|
||||||
expect(subtypes.customData).toBeDefined();
|
|
||||||
expect(subtypes.customData![test1.subtype]).toBeUndefined();
|
|
||||||
expect(subtypes.customData![test2.subtype]).toBeUndefined();
|
|
||||||
expect(subtypes.customData![test3.subtype]).toBe(true);
|
|
||||||
subtypes = selectSubtype(appState, "arrow");
|
|
||||||
expect(subtypes.customData).toBeUndefined();
|
|
||||||
// Subtype for multiple linear types
|
|
||||||
appState.activeSubtypes = [test1.subtype];
|
|
||||||
subtypes = selectSubtype(appState, "text");
|
|
||||||
expect(subtypes.customData).toBeUndefined();
|
|
||||||
subtypes = selectSubtype(appState, "line");
|
|
||||||
expect(subtypes.customData).toBeDefined();
|
|
||||||
expect(subtypes.customData![test1.subtype]).toBe(true);
|
|
||||||
expect(subtypes.customData![test2.subtype]).toBeUndefined();
|
|
||||||
expect(subtypes.customData![test3.subtype]).toBeUndefined();
|
|
||||||
// Multiple, non-colliding subtypes
|
|
||||||
appState.activeSubtypes = [test1.subtype, test2.subtype];
|
|
||||||
subtypes = selectSubtype(appState, "text");
|
|
||||||
expect(subtypes.customData).toBeDefined();
|
|
||||||
expect(subtypes.customData![test1.subtype]).toBeUndefined();
|
|
||||||
expect(subtypes.customData![test2.subtype]).toBe(true);
|
|
||||||
expect(subtypes.customData![test3.subtype]).toBeUndefined();
|
|
||||||
subtypes = selectSubtype(appState, "line");
|
|
||||||
expect(subtypes.customData).toBeDefined();
|
|
||||||
expect(subtypes.customData![test1.subtype]).toBe(true);
|
|
||||||
expect(subtypes.customData![test2.subtype]).toBeUndefined();
|
|
||||||
expect(subtypes.customData![test3.subtype]).toBeUndefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe("subtype actions", () => {
|
|
||||||
let elements: ExcalidrawElement[];
|
|
||||||
beforeEach(async () => {
|
|
||||||
elements = [
|
|
||||||
API.createElement({ type: "line", id: "A", subtype: test1.subtype }),
|
|
||||||
API.createElement({ type: "line", id: "B" }),
|
|
||||||
API.createElement({ type: "line", id: "C", subtype: test3.subtype }),
|
|
||||||
API.createElement({ type: "text", id: "D", subtype: test3.subtype }),
|
|
||||||
];
|
|
||||||
await render(<Excalidraw />, { localStorageData: { elements } });
|
|
||||||
});
|
|
||||||
it("should apply to elements with their subtype", async () => {
|
|
||||||
h.setState({ selectedElementIds: { A: true } });
|
|
||||||
const am = h.app.actionManager;
|
|
||||||
expect(am.isActionEnabled(makeTestActions()[0], { elements })).toBe(true);
|
|
||||||
expect(am.isActionEnabled(TEST_DISABLE1, { elements })).toBe(false);
|
|
||||||
});
|
|
||||||
it("should apply to elements without a subtype", async () => {
|
|
||||||
h.setState({ selectedElementIds: { B: true } });
|
|
||||||
const am = h.app.actionManager;
|
|
||||||
expect(am.isActionEnabled(makeTestActions()[0], { elements })).toBe(false);
|
|
||||||
expect(am.isActionEnabled(TEST_DISABLE1, { elements })).toBe(true);
|
|
||||||
});
|
|
||||||
it("should apply to elements with and without their subtype", async () => {
|
|
||||||
h.setState({ selectedElementIds: { A: true, B: true } });
|
|
||||||
const am = h.app.actionManager;
|
|
||||||
expect(am.isActionEnabled(makeTestActions()[0], { elements })).toBe(true);
|
|
||||||
expect(am.isActionEnabled(TEST_DISABLE1, { elements })).toBe(true);
|
|
||||||
});
|
|
||||||
it("should apply to elements with a different subtype", async () => {
|
|
||||||
h.setState({ selectedElementIds: { C: true, D: true } });
|
|
||||||
const am = h.app.actionManager;
|
|
||||||
expect(am.isActionEnabled(makeTestActions()[0], { elements })).toBe(false);
|
|
||||||
expect(am.isActionEnabled(TEST_DISABLE1, { elements })).toBe(true);
|
|
||||||
});
|
|
||||||
it("should apply to like types with varying subtypes", async () => {
|
|
||||||
h.setState({ selectedElementIds: { A: true, C: true } });
|
|
||||||
const am = h.app.actionManager;
|
|
||||||
expect(am.isActionEnabled(makeTestActions()[0], { elements })).toBe(true);
|
|
||||||
expect(am.isActionEnabled(TEST_DISABLE1, { elements })).toBe(true);
|
|
||||||
});
|
|
||||||
it("should apply to non-like types with varying subtypes", async () => {
|
|
||||||
h.setState({ selectedElementIds: { A: true, D: true } });
|
|
||||||
const am = h.app.actionManager;
|
|
||||||
expect(am.isActionEnabled(makeTestActions()[0], { elements })).toBe(true);
|
|
||||||
expect(am.isActionEnabled(TEST_DISABLE1, { elements })).toBe(false);
|
|
||||||
});
|
|
||||||
it("should apply to like/non-like types with varying subtypes", async () => {
|
|
||||||
h.setState({ selectedElementIds: { A: true, B: true, D: true } });
|
|
||||||
const am = h.app.actionManager;
|
|
||||||
expect(am.isActionEnabled(makeTestActions()[0], { elements })).toBe(true);
|
|
||||||
expect(am.isActionEnabled(TEST_DISABLE1, { elements })).toBe(true);
|
|
||||||
});
|
|
||||||
it("should apply to the correct parent type", async () => {
|
|
||||||
const am = h.app.actionManager;
|
|
||||||
h.setState({ selectedElementIds: { A: true, C: true } });
|
|
||||||
expect(am.isActionEnabled(TEST_DISABLE3, { elements })).toBe(true);
|
|
||||||
h.setState({ selectedElementIds: { A: true, D: true } });
|
|
||||||
expect(am.isActionEnabled(TEST_DISABLE3, { elements })).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe("subtype loading", () => {
|
|
||||||
let elements: ExcalidrawElement[];
|
|
||||||
beforeEach(async () => {
|
|
||||||
const testString = "A quick brown fox jumps over the lazy dog.";
|
|
||||||
elements = [
|
|
||||||
API.createElement({
|
|
||||||
type: "text",
|
|
||||||
id: "A",
|
|
||||||
subtype: test2.subtype,
|
|
||||||
text: testString,
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
await render(<Excalidraw />, { localStorageData: { elements } });
|
|
||||||
h.elements = elements;
|
|
||||||
});
|
|
||||||
it("should redraw text bounding boxes", async () => {
|
|
||||||
h.setState({ selectedElementIds: { A: true } });
|
|
||||||
const el = h.elements[0] as ExcalidrawTextElement;
|
|
||||||
expect(el.width).toEqual(100);
|
|
||||||
expect(el.height).toEqual(100);
|
|
||||||
ensureSubtypesLoadedForElements(elements);
|
|
||||||
expect(el.width).toEqual(TWIDTH * 2);
|
|
||||||
expect(el.height).toEqual(THEIGHT * 2);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -35,12 +35,6 @@ import type { ClipboardData } from "./clipboard";
|
|||||||
import type { isOverScrollBars } from "./scene/scrollbars";
|
import type { isOverScrollBars } from "./scene/scrollbars";
|
||||||
import type { MaybeTransformHandleType } from "./element/transformHandles";
|
import type { MaybeTransformHandleType } from "./element/transformHandles";
|
||||||
import type Library from "./data/library";
|
import type Library from "./data/library";
|
||||||
import type {
|
|
||||||
SubtypeMethods,
|
|
||||||
Subtype,
|
|
||||||
SubtypePrepFn,
|
|
||||||
SubtypeRecord,
|
|
||||||
} from "./element/subtypes";
|
|
||||||
import type { FileSystemHandle } from "./data/filesystem";
|
import type { FileSystemHandle } from "./data/filesystem";
|
||||||
import type { IMAGE_MIME_TYPES, MIME_TYPES } from "./constants";
|
import type { IMAGE_MIME_TYPES, MIME_TYPES } from "./constants";
|
||||||
import type { ContextMenuItems } from "./components/ContextMenu";
|
import type { ContextMenuItems } from "./components/ContextMenu";
|
||||||
@@ -182,6 +176,8 @@ export type StaticCanvasAppState = Readonly<
|
|||||||
gridStep: AppState["gridStep"];
|
gridStep: AppState["gridStep"];
|
||||||
frameRendering: AppState["frameRendering"];
|
frameRendering: AppState["frameRendering"];
|
||||||
currentHoveredFontFamily: AppState["currentHoveredFontFamily"];
|
currentHoveredFontFamily: AppState["currentHoveredFontFamily"];
|
||||||
|
// Cropping
|
||||||
|
croppingElementId: AppState["croppingElementId"];
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
@@ -204,6 +200,9 @@ export type InteractiveCanvasAppState = Readonly<
|
|||||||
snapLines: AppState["snapLines"];
|
snapLines: AppState["snapLines"];
|
||||||
zenModeEnabled: AppState["zenModeEnabled"];
|
zenModeEnabled: AppState["zenModeEnabled"];
|
||||||
editingTextElement: AppState["editingTextElement"];
|
editingTextElement: AppState["editingTextElement"];
|
||||||
|
// Cropping
|
||||||
|
isCropping: AppState["isCropping"];
|
||||||
|
croppingElementId: AppState["croppingElementId"];
|
||||||
// Search matches
|
// Search matches
|
||||||
searchMatches: AppState["searchMatches"];
|
searchMatches: AppState["searchMatches"];
|
||||||
}
|
}
|
||||||
@@ -225,6 +224,7 @@ export type ObservedElementsAppState = {
|
|||||||
editingLinearElementId: LinearElementEditor["elementId"] | null;
|
editingLinearElementId: LinearElementEditor["elementId"] | null;
|
||||||
// Right now it's coupled to `editingLinearElement`, ideally it should not be really needed as we already have selectedElementIds & editingLinearElementId
|
// Right now it's coupled to `editingLinearElement`, ideally it should not be really needed as we already have selectedElementIds & editingLinearElementId
|
||||||
selectedLinearElementId: LinearElementEditor["elementId"] | null;
|
selectedLinearElementId: LinearElementEditor["elementId"] | null;
|
||||||
|
croppingElementId: AppState["croppingElementId"];
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface AppState {
|
export interface AppState {
|
||||||
@@ -277,10 +277,6 @@ export interface AppState {
|
|||||||
*/
|
*/
|
||||||
editingTextElement: NonDeletedExcalidrawElement | null;
|
editingTextElement: NonDeletedExcalidrawElement | null;
|
||||||
editingLinearElement: LinearElementEditor | null;
|
editingLinearElement: LinearElementEditor | null;
|
||||||
activeSubtypes?: Subtype[];
|
|
||||||
customData?: {
|
|
||||||
[subtype: Subtype]: ExcalidrawElement["customData"];
|
|
||||||
};
|
|
||||||
activeTool: {
|
activeTool: {
|
||||||
/**
|
/**
|
||||||
* indicates a previous tool we should revert back to if we deselect the
|
* indicates a previous tool we should revert back to if we deselect the
|
||||||
@@ -396,6 +392,11 @@ export interface AppState {
|
|||||||
userToFollow: UserToFollow | null;
|
userToFollow: UserToFollow | null;
|
||||||
/** the socket ids of the users following the current user */
|
/** the socket ids of the users following the current user */
|
||||||
followedBy: Set<SocketId>;
|
followedBy: Set<SocketId>;
|
||||||
|
|
||||||
|
/** image cropping */
|
||||||
|
isCropping: boolean;
|
||||||
|
croppingElementId: ExcalidrawElement["id"] | null;
|
||||||
|
|
||||||
searchMatches: readonly SearchMatch[];
|
searchMatches: readonly SearchMatch[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -749,10 +750,6 @@ export interface ExcalidrawImperativeAPI {
|
|||||||
getName: InstanceType<typeof App>["getName"];
|
getName: InstanceType<typeof App>["getName"];
|
||||||
scrollToContent: InstanceType<typeof App>["scrollToContent"];
|
scrollToContent: InstanceType<typeof App>["scrollToContent"];
|
||||||
registerAction: (action: Action) => void;
|
registerAction: (action: Action) => void;
|
||||||
addSubtype: (
|
|
||||||
record: SubtypeRecord,
|
|
||||||
subtypePrepFn: SubtypePrepFn,
|
|
||||||
) => { actions: readonly Action[] | null; methods: Partial<SubtypeMethods> };
|
|
||||||
refresh: InstanceType<typeof App>["refresh"];
|
refresh: InstanceType<typeof App>["refresh"];
|
||||||
setToast: InstanceType<typeof App>["setToast"];
|
setToast: InstanceType<typeof App>["setToast"];
|
||||||
addFiles: (data: BinaryFileData[]) => void;
|
addFiles: (data: BinaryFileData[]) => void;
|
||||||
|
|||||||
@@ -28,3 +28,6 @@ export const average = (a: number, b: number) => (a + b) / 2;
|
|||||||
export const isFiniteNumber = (value: any): value is number => {
|
export const isFiniteNumber = (value: any): value is number => {
|
||||||
return typeof value === "number" && Number.isFinite(value);
|
return typeof value === "number" && Number.isFinite(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const isCloseTo = (a: number, b: number, precision = PRECISION) =>
|
||||||
|
Math.abs(a - b) < precision;
|
||||||
|
|||||||
@@ -139,3 +139,10 @@ export const vectorNormalize = (v: Vector): Vector => {
|
|||||||
|
|
||||||
return vector(v[0] / m, v[1] / m);
|
return vector(v[0] / m, v[1] / m);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project the first vector onto the second vector
|
||||||
|
*/
|
||||||
|
export const vectorProjection = (a: Vector, b: Vector) => {
|
||||||
|
return vectorScale(b, vectorDot(a, b) / vectorDot(b, b));
|
||||||
|
};
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ exports[`exportToSvg > with default arguments 1`] = `
|
|||||||
},
|
},
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"contextMenu": null,
|
"contextMenu": null,
|
||||||
|
"croppingElementId": null,
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentHoveredFontFamily": null,
|
"currentHoveredFontFamily": null,
|
||||||
"currentItemArrowType": "round",
|
"currentItemArrowType": "round",
|
||||||
@@ -53,6 +54,7 @@ exports[`exportToSvg > with default arguments 1`] = `
|
|||||||
"gridSize": 20,
|
"gridSize": 20,
|
||||||
"gridStep": 5,
|
"gridStep": 5,
|
||||||
"isBindingEnabled": true,
|
"isBindingEnabled": true,
|
||||||
|
"isCropping": false,
|
||||||
"isLoading": false,
|
"isLoading": false,
|
||||||
"isResizing": false,
|
"isResizing": false,
|
||||||
"isRotating": false,
|
"isRotating": false,
|
||||||
|
|||||||
@@ -1,130 +0,0 @@
|
|||||||
diff --git a/node_modules/mathjax-full/js/input/asciimath/mathjax2/input/AsciiMath.js b/node_modules/mathjax-full/js/input/asciimath/mathjax2/input/AsciiMath.js
|
|
||||||
index 41f6a1f..25096c6 100644
|
|
||||||
--- a/node_modules/mathjax-full/js/input/asciimath/mathjax2/input/AsciiMath.js
|
|
||||||
+++ b/node_modules/mathjax-full/js/input/asciimath/mathjax2/input/AsciiMath.js
|
|
||||||
@@ -1,4 +1,4 @@
|
|
||||||
-MathJax = Object.assign(global.MathJax || {}, require("../legacy/MathJax.js").MathJax);
|
|
||||||
+window.MathJax = Object.assign(window.MathJax || {}, require("../legacy/MathJax.js").MathJax);
|
|
||||||
|
|
||||||
//
|
|
||||||
// Load component-based configuration, if any
|
|
||||||
@@ -13,11 +13,13 @@ MathJax.Ajax.Preloading(
|
|
||||||
"[MathJax]/jax/element/mml/jax.js"
|
|
||||||
);
|
|
||||||
|
|
||||||
-require("../legacy/jax/element/mml/jax.js");
|
|
||||||
-require("../legacy/jax/input/AsciiMath/config.js");
|
|
||||||
-require("../legacy/jax/input/AsciiMath/jax.js");
|
|
||||||
+exports.LegacyAsciiMath = void 0;
|
|
||||||
+(async () => {
|
|
||||||
+ await import("../legacy/jax/element/mml/jax.js");
|
|
||||||
+ await import("../legacy/jax/input/AsciiMath/config.js");
|
|
||||||
+ await import("../legacy/jax/input/AsciiMath/jax.js");
|
|
||||||
|
|
||||||
-require("../legacy/jax/element/MmlNode.js");
|
|
||||||
+ await import("../legacy/jax/element/MmlNode.js");
|
|
||||||
|
|
||||||
var MmlFactory = require("../../../../core/MmlTree/MmlFactory.js").MmlFactory;
|
|
||||||
var factory = new MmlFactory();
|
|
||||||
@@ -37,3 +38,4 @@ exports.LegacyAsciiMath = {
|
|
||||||
return this.Compile(am,display);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
+})();
|
|
||||||
diff --git a/node_modules/mathjax-full/js/input/asciimath/mathjax2/legacy/MathJax.js b/node_modules/mathjax-full/js/input/asciimath/mathjax2/legacy/MathJax.js
|
|
||||||
index 903ede2..504ae4f 100644
|
|
||||||
--- a/node_modules/mathjax-full/js/input/asciimath/mathjax2/legacy/MathJax.js
|
|
||||||
+++ b/node_modules/mathjax-full/js/input/asciimath/mathjax2/legacy/MathJax.js
|
|
||||||
@@ -19,7 +19,7 @@ exports.MathJax = MathJax;
|
|
||||||
return obj;
|
|
||||||
};
|
|
||||||
var CONSTRUCTOR = function () {
|
|
||||||
- return function () {return arguments.callee.Init.call(this,arguments)};
|
|
||||||
+ return function fn() {return fn.Init.call(this,Object.assign(arguments,{call:fn}))};
|
|
||||||
};
|
|
||||||
|
|
||||||
BASE.Object = OBJECT({
|
|
||||||
@@ -40,7 +40,7 @@ exports.MathJax = MathJax;
|
|
||||||
Init: function (args) {
|
|
||||||
var obj = this;
|
|
||||||
if (args.length === 1 && args[0] === PROTO) {return obj}
|
|
||||||
- if (!(obj instanceof args.callee)) {obj = new args.callee(PROTO)}
|
|
||||||
+ if (!(obj instanceof args.call)) {obj = new args.call(PROTO)}
|
|
||||||
return obj.Init.apply(obj,args) || obj;
|
|
||||||
},
|
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ exports.MathJax = MathJax;
|
|
||||||
|
|
||||||
prototype: {
|
|
||||||
Init: function () {},
|
|
||||||
- SUPER: function (fn) {return fn.callee.SUPER},
|
|
||||||
+ SUPER: function (fn) {return fn.SUPER},
|
|
||||||
can: function (method) {return typeof(this[method]) === "function"},
|
|
||||||
has: function (property) {return typeof(this[property]) !== "undefined"},
|
|
||||||
isa: function (obj) {return (obj instanceof Object) && (this instanceof obj)}
|
|
||||||
@@ -177,7 +177,7 @@ exports.MathJax = MathJax;
|
|
||||||
// Create a callback from an associative array
|
|
||||||
//
|
|
||||||
var CALLBACK = function (data) {
|
|
||||||
- var cb = function () {return arguments.callee.execute.apply(arguments.callee,arguments)};
|
|
||||||
+ var cb = function fn() {return fn.execute.apply(fn,arguments)};
|
|
||||||
for (var id in CALLBACK.prototype) {
|
|
||||||
if (CALLBACK.prototype.hasOwnProperty(id)) {
|
|
||||||
if (typeof(data[id]) !== 'undefined') {cb[id] = data[id]}
|
|
||||||
diff --git a/node_modules/mathjax-full/js/input/asciimath/mathjax2/legacy/jax/element/mml/jax.js b/node_modules/mathjax-full/js/input/asciimath/mathjax2/legacy/jax/element/mml/jax.js
|
|
||||||
index 96fb918..473aca1 100644
|
|
||||||
--- a/node_modules/mathjax-full/js/input/asciimath/mathjax2/legacy/jax/element/mml/jax.js
|
|
||||||
+++ b/node_modules/mathjax-full/js/input/asciimath/mathjax2/legacy/jax/element/mml/jax.js
|
|
||||||
@@ -813,9 +813,9 @@ MathJax.ElementJax.mml.Augment({
|
|
||||||
if (!(this.isEmbellished()) || typeof(this.core) === "undefined") {return this}
|
|
||||||
return this.data[this.core].CoreMO();
|
|
||||||
},
|
|
||||||
- toString: function () {
|
|
||||||
+ toString: function fn() {
|
|
||||||
if (this.inferred) {return '[' + this.data.join(',') + ']'}
|
|
||||||
- return this.SUPER(arguments).toString.call(this);
|
|
||||||
+ return this.SUPER(fn).toString.call(this);
|
|
||||||
},
|
|
||||||
setTeXclass: function (prev) {
|
|
||||||
var i, m = this.data.length;
|
|
||||||
@@ -1196,12 +1196,12 @@ MathJax.ElementJax.mml.Augment({
|
|
||||||
}
|
|
||||||
},
|
|
||||||
linebreakContainer: true,
|
|
||||||
- Append: function () {
|
|
||||||
+ Append: function fn() {
|
|
||||||
for (var i = 0, m = arguments.length; i < m; i++) {
|
|
||||||
if (!((arguments[i] instanceof MML.mtr) ||
|
|
||||||
(arguments[i] instanceof MML.mlabeledtr))) {arguments[i] = MML.mtr(arguments[i])}
|
|
||||||
}
|
|
||||||
- this.SUPER(arguments).Append.apply(this,arguments);
|
|
||||||
+ this.SUPER(fn).Append.apply(this,arguments);
|
|
||||||
},
|
|
||||||
setTeXclass: MML.mbase.setSeparateTeXclasses
|
|
||||||
});
|
|
||||||
@@ -1221,11 +1221,11 @@ MathJax.ElementJax.mml.Augment({
|
|
||||||
mtable: {rowalign: true, columnalign: true, groupalign: true}
|
|
||||||
},
|
|
||||||
linebreakContainer: true,
|
|
||||||
- Append: function () {
|
|
||||||
+ Append: function fn() {
|
|
||||||
for (var i = 0, m = arguments.length; i < m; i++) {
|
|
||||||
if (!(arguments[i] instanceof MML.mtd)) {arguments[i] = MML.mtd(arguments[i])}
|
|
||||||
}
|
|
||||||
- this.SUPER(arguments).Append.apply(this,arguments);
|
|
||||||
+ this.SUPER(fn).Append.apply(this,arguments);
|
|
||||||
},
|
|
||||||
setTeXclass: MML.mbase.setSeparateTeXclasses
|
|
||||||
});
|
|
||||||
@@ -1420,9 +1420,9 @@ MathJax.ElementJax.mml.Augment({
|
|
||||||
|
|
||||||
MML.xml = MML.mbase.Subclass({
|
|
||||||
type: "xml",
|
|
||||||
- Init: function () {
|
|
||||||
+ Init: function fn() {
|
|
||||||
this.div = document.createElement("div");
|
|
||||||
- return this.SUPER(arguments).Init.apply(this,arguments);
|
|
||||||
+ return this.SUPER(fn).Init.apply(this,arguments);
|
|
||||||
},
|
|
||||||
Append: function () {
|
|
||||||
for (var i = 0, m = arguments.length; i < m; i++) {
|
|
||||||
+1
-1
@@ -104,7 +104,7 @@ console.error = (...args) => {
|
|||||||
// the react's act() warning usually doesn't contain any useful stack trace
|
// the react's act() warning usually doesn't contain any useful stack trace
|
||||||
// so we're catching the log and re-logging the message with the test name,
|
// so we're catching the log and re-logging the message with the test name,
|
||||||
// also stripping the actual component stack trace as it's not useful
|
// also stripping the actual component stack trace as it's not useful
|
||||||
if (args[0]?.includes("act(")) {
|
if (args[0]?.includes?.("act(")) {
|
||||||
_consoleError(
|
_consoleError(
|
||||||
yellow(
|
yellow(
|
||||||
`<<< WARNING: test "${
|
`<<< WARNING: test "${
|
||||||
|
|||||||
@@ -3786,11 +3786,6 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d"
|
resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d"
|
||||||
integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==
|
integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==
|
||||||
|
|
||||||
"@yarnpkg/lockfile@^1.1.0":
|
|
||||||
version "1.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31"
|
|
||||||
integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==
|
|
||||||
|
|
||||||
abab@^2.0.6:
|
abab@^2.0.6:
|
||||||
version "2.0.6"
|
version "2.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291"
|
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291"
|
||||||
@@ -4590,7 +4585,7 @@ chrome-trace-event@^1.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac"
|
resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac"
|
||||||
integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==
|
integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==
|
||||||
|
|
||||||
ci-info@^3.2.0, ci-info@^3.7.0:
|
ci-info@^3.2.0:
|
||||||
version "3.9.0"
|
version "3.9.0"
|
||||||
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4"
|
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4"
|
||||||
integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==
|
integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==
|
||||||
@@ -4699,11 +4694,6 @@ commander@7, commander@^7.0.0, commander@^7.2.0:
|
|||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7"
|
resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7"
|
||||||
integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==
|
integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==
|
||||||
|
|
||||||
commander@9.2.0:
|
|
||||||
version "9.2.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-9.2.0.tgz#6e21014b2ed90d8b7c9647230d8b7a94a4a419a9"
|
|
||||||
integrity sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w==
|
|
||||||
|
|
||||||
commander@^2.20.0:
|
commander@^2.20.0:
|
||||||
version "2.20.3"
|
version "2.20.3"
|
||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||||
@@ -6070,11 +6060,6 @@ eslint@^7.32.0:
|
|||||||
text-table "^0.2.0"
|
text-table "^0.2.0"
|
||||||
v8-compile-cache "^2.0.3"
|
v8-compile-cache "^2.0.3"
|
||||||
|
|
||||||
esm@^3.2.25:
|
|
||||||
version "3.2.25"
|
|
||||||
resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10"
|
|
||||||
integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==
|
|
||||||
|
|
||||||
espree@^7.3.0, espree@^7.3.1:
|
espree@^7.3.0, espree@^7.3.1:
|
||||||
version "7.3.1"
|
version "7.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6"
|
resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6"
|
||||||
@@ -6328,13 +6313,6 @@ find-up@^4.0.0:
|
|||||||
locate-path "^5.0.0"
|
locate-path "^5.0.0"
|
||||||
path-exists "^4.0.0"
|
path-exists "^4.0.0"
|
||||||
|
|
||||||
find-yarn-workspace-root@^2.0.0:
|
|
||||||
version "2.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz#f47fb8d239c900eb78179aa81b66673eac88f7bd"
|
|
||||||
integrity sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==
|
|
||||||
dependencies:
|
|
||||||
micromatch "^4.0.2"
|
|
||||||
|
|
||||||
firebase@8.3.3:
|
firebase@8.3.3:
|
||||||
version "8.3.3"
|
version "8.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/firebase/-/firebase-8.3.3.tgz#21d8fb8eec2c43b0d8f98ab6bda5535b7454fa54"
|
resolved "https://registry.yarnpkg.com/firebase/-/firebase-8.3.3.tgz#21d8fb8eec2c43b0d8f98ab6bda5535b7454fa54"
|
||||||
@@ -6450,7 +6428,7 @@ fs-extra@^11.1.0:
|
|||||||
jsonfile "^6.0.1"
|
jsonfile "^6.0.1"
|
||||||
universalify "^2.0.0"
|
universalify "^2.0.0"
|
||||||
|
|
||||||
fs-extra@^9.0.0, fs-extra@^9.0.1:
|
fs-extra@^9.0.1:
|
||||||
version "9.1.0"
|
version "9.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d"
|
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d"
|
||||||
integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==
|
integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==
|
||||||
@@ -6647,7 +6625,7 @@ gopd@^1.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
get-intrinsic "^1.1.3"
|
get-intrinsic "^1.1.3"
|
||||||
|
|
||||||
graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.9:
|
graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.9:
|
||||||
version "4.2.11"
|
version "4.2.11"
|
||||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
|
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
|
||||||
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
|
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
|
||||||
@@ -7040,11 +7018,6 @@ is-date-object@^1.0.1, is-date-object@^1.0.5:
|
|||||||
dependencies:
|
dependencies:
|
||||||
has-tostringtag "^1.0.0"
|
has-tostringtag "^1.0.0"
|
||||||
|
|
||||||
is-docker@^2.0.0:
|
|
||||||
version "2.2.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa"
|
|
||||||
integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==
|
|
||||||
|
|
||||||
is-extglob@^2.1.1:
|
is-extglob@^2.1.1:
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
|
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
|
||||||
@@ -7201,13 +7174,6 @@ is-weakset@^2.0.3:
|
|||||||
call-bind "^1.0.7"
|
call-bind "^1.0.7"
|
||||||
get-intrinsic "^1.2.4"
|
get-intrinsic "^1.2.4"
|
||||||
|
|
||||||
is-wsl@^2.1.1:
|
|
||||||
version "2.2.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
|
|
||||||
integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==
|
|
||||||
dependencies:
|
|
||||||
is-docker "^2.0.0"
|
|
||||||
|
|
||||||
isarray@^2.0.5:
|
isarray@^2.0.5:
|
||||||
version "2.0.5"
|
version "2.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
|
resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
|
||||||
@@ -7472,16 +7438,6 @@ json-stable-stringify-without-jsonify@^1.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
|
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
|
||||||
integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
|
integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
|
||||||
|
|
||||||
json-stable-stringify@^1.0.2:
|
|
||||||
version "1.1.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.1.1.tgz#52d4361b47d49168bcc4e564189a42e5a7439454"
|
|
||||||
integrity sha512-SU/971Kt5qVQfJpyDveVhQ/vya+5hvrjClFOcr8c0Fq5aODJjMwutrOfCU+eCnVD5gpx1Q3fEqkyom77zH1iIg==
|
|
||||||
dependencies:
|
|
||||||
call-bind "^1.0.5"
|
|
||||||
isarray "^2.0.5"
|
|
||||||
jsonify "^0.0.1"
|
|
||||||
object-keys "^1.1.1"
|
|
||||||
|
|
||||||
json5@^1.0.2:
|
json5@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593"
|
resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593"
|
||||||
@@ -7503,11 +7459,6 @@ jsonfile@^6.0.1:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
graceful-fs "^4.1.6"
|
graceful-fs "^4.1.6"
|
||||||
|
|
||||||
jsonify@^0.0.1:
|
|
||||||
version "0.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.1.tgz#2aa3111dae3d34a0f151c63f3a45d995d9420978"
|
|
||||||
integrity sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==
|
|
||||||
|
|
||||||
jsonpointer@^5.0.0:
|
jsonpointer@^5.0.0:
|
||||||
version "5.0.1"
|
version "5.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-5.0.1.tgz#2110e0af0900fd37467b5907ecd13a7884a1b559"
|
resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-5.0.1.tgz#2110e0af0900fd37467b5907ecd13a7884a1b559"
|
||||||
@@ -7547,13 +7498,6 @@ kind-of@^6.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
|
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
|
||||||
integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
|
integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
|
||||||
|
|
||||||
klaw-sync@^6.0.0:
|
|
||||||
version "6.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/klaw-sync/-/klaw-sync-6.0.0.tgz#1fd2cfd56ebb6250181114f0a581167099c2b28c"
|
|
||||||
integrity sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==
|
|
||||||
dependencies:
|
|
||||||
graceful-fs "^4.1.11"
|
|
||||||
|
|
||||||
kleur@^4.0.3:
|
kleur@^4.0.3:
|
||||||
version "4.1.5"
|
version "4.1.5"
|
||||||
resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780"
|
resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780"
|
||||||
@@ -7834,16 +7778,6 @@ make-dir@^4.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
semver "^7.5.3"
|
semver "^7.5.3"
|
||||||
|
|
||||||
mathjax-full@3.2.2:
|
|
||||||
version "3.2.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/mathjax-full/-/mathjax-full-3.2.2.tgz#43f02e55219db393030985d2b6537ceae82f1fa7"
|
|
||||||
integrity sha512-+LfG9Fik+OuI8SLwsiR02IVdjcnRCy5MufYLi0C3TdMT56L/pjB0alMVGgoWJF8pN9Rc7FESycZB9BMNWIid5w==
|
|
||||||
dependencies:
|
|
||||||
esm "^3.2.25"
|
|
||||||
mhchemparser "^4.1.0"
|
|
||||||
mj-context-menu "^0.6.1"
|
|
||||||
speech-rule-engine "^4.0.6"
|
|
||||||
|
|
||||||
mdast-util-from-markdown@^1.3.0:
|
mdast-util-from-markdown@^1.3.0:
|
||||||
version "1.3.1"
|
version "1.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz#9421a5a247f10d31d2faed2a30df5ec89ceafcf0"
|
resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz#9421a5a247f10d31d2faed2a30df5ec89ceafcf0"
|
||||||
@@ -7905,11 +7839,6 @@ mermaid@10.9.0:
|
|||||||
uuid "^9.0.0"
|
uuid "^9.0.0"
|
||||||
web-worker "^1.2.0"
|
web-worker "^1.2.0"
|
||||||
|
|
||||||
mhchemparser@^4.1.0:
|
|
||||||
version "4.2.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/mhchemparser/-/mhchemparser-4.2.1.tgz#d73982e66bc06170a85b1985600ee9dabe157cb0"
|
|
||||||
integrity sha512-kYmyrCirqJf3zZ9t/0wGgRZ4/ZJw//VwaRVGA75C4nhE60vtnIzhl9J9ndkX/h6hxSN7pjg/cE0VxbnNM+bnDQ==
|
|
||||||
|
|
||||||
micromark-core-commonmark@^1.0.1:
|
micromark-core-commonmark@^1.0.1:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz#1386628df59946b2d39fb2edfd10f3e8e0a75bb8"
|
resolved "https://registry.yarnpkg.com/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz#1386628df59946b2d39fb2edfd10f3e8e0a75bb8"
|
||||||
@@ -8112,14 +8041,6 @@ micromatch@^4.0.0, micromatch@^4.0.4:
|
|||||||
braces "^3.0.3"
|
braces "^3.0.3"
|
||||||
picomatch "^2.3.1"
|
picomatch "^2.3.1"
|
||||||
|
|
||||||
micromatch@^4.0.2:
|
|
||||||
version "4.0.5"
|
|
||||||
resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6"
|
|
||||||
integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==
|
|
||||||
dependencies:
|
|
||||||
braces "^3.0.2"
|
|
||||||
picomatch "^2.3.1"
|
|
||||||
|
|
||||||
mime-db@1.52.0:
|
mime-db@1.52.0:
|
||||||
version "1.52.0"
|
version "1.52.0"
|
||||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
|
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
|
||||||
@@ -8190,11 +8111,6 @@ minimist@^1.2.0, minimist@^1.2.6:
|
|||||||
resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707"
|
resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707"
|
||||||
integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==
|
integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==
|
||||||
|
|
||||||
mj-context-menu@^0.6.1:
|
|
||||||
version "0.6.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/mj-context-menu/-/mj-context-menu-0.6.1.tgz#a043c5282bf7e1cf3821de07b13525ca6f85aa69"
|
|
||||||
integrity sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA==
|
|
||||||
|
|
||||||
mkdirp-classic@^0.5.2:
|
mkdirp-classic@^0.5.2:
|
||||||
version "0.5.3"
|
version "0.5.3"
|
||||||
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
|
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
|
||||||
@@ -8488,14 +8404,6 @@ open-color@1.9.1:
|
|||||||
resolved "https://registry.yarnpkg.com/open-color/-/open-color-1.9.1.tgz#a6e6328f60eff7aa60e3e8fcfa50f53ff3eece35"
|
resolved "https://registry.yarnpkg.com/open-color/-/open-color-1.9.1.tgz#a6e6328f60eff7aa60e3e8fcfa50f53ff3eece35"
|
||||||
integrity sha512-vCseG/EQ6/RcvxhUcGJiHViOgrtz4x0XbZepXvKik66TMGkvbmjeJrKFyBEx6daG5rNyyd14zYXhz0hZVwQFOw==
|
integrity sha512-vCseG/EQ6/RcvxhUcGJiHViOgrtz4x0XbZepXvKik66TMGkvbmjeJrKFyBEx6daG5rNyyd14zYXhz0hZVwQFOw==
|
||||||
|
|
||||||
open@^7.4.2:
|
|
||||||
version "7.4.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321"
|
|
||||||
integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==
|
|
||||||
dependencies:
|
|
||||||
is-docker "^2.0.0"
|
|
||||||
is-wsl "^2.1.1"
|
|
||||||
|
|
||||||
opener@^1.5.1, opener@^1.5.2:
|
opener@^1.5.1, opener@^1.5.2:
|
||||||
version "1.5.2"
|
version "1.5.2"
|
||||||
resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598"
|
resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598"
|
||||||
@@ -8513,11 +8421,6 @@ optionator@^0.9.1:
|
|||||||
type-check "^0.4.0"
|
type-check "^0.4.0"
|
||||||
word-wrap "^1.2.5"
|
word-wrap "^1.2.5"
|
||||||
|
|
||||||
os-tmpdir@~1.0.2:
|
|
||||||
version "1.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
|
|
||||||
integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==
|
|
||||||
|
|
||||||
p-limit@^2.2.0:
|
p-limit@^2.2.0:
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
|
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
|
||||||
@@ -8594,27 +8497,6 @@ pascal-case@^3.1.2:
|
|||||||
no-case "^3.0.4"
|
no-case "^3.0.4"
|
||||||
tslib "^2.0.3"
|
tslib "^2.0.3"
|
||||||
|
|
||||||
patch-package@8.0.0:
|
|
||||||
version "8.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/patch-package/-/patch-package-8.0.0.tgz#d191e2f1b6e06a4624a0116bcb88edd6714ede61"
|
|
||||||
integrity sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA==
|
|
||||||
dependencies:
|
|
||||||
"@yarnpkg/lockfile" "^1.1.0"
|
|
||||||
chalk "^4.1.2"
|
|
||||||
ci-info "^3.7.0"
|
|
||||||
cross-spawn "^7.0.3"
|
|
||||||
find-yarn-workspace-root "^2.0.0"
|
|
||||||
fs-extra "^9.0.0"
|
|
||||||
json-stable-stringify "^1.0.2"
|
|
||||||
klaw-sync "^6.0.0"
|
|
||||||
minimist "^1.2.6"
|
|
||||||
open "^7.4.2"
|
|
||||||
rimraf "^2.6.3"
|
|
||||||
semver "^7.5.3"
|
|
||||||
slash "^2.0.0"
|
|
||||||
tmp "^0.0.33"
|
|
||||||
yaml "^2.2.2"
|
|
||||||
|
|
||||||
path-data-parser@0.1.0, path-data-parser@^0.1.0:
|
path-data-parser@0.1.0, path-data-parser@^0.1.0:
|
||||||
version "0.1.0"
|
version "0.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/path-data-parser/-/path-data-parser-0.1.0.tgz#8f5ba5cc70fc7becb3dcefaea08e2659aba60b8c"
|
resolved "https://registry.yarnpkg.com/path-data-parser/-/path-data-parser-0.1.0.tgz#8f5ba5cc70fc7becb3dcefaea08e2659aba60b8c"
|
||||||
@@ -8851,11 +8733,6 @@ postcss@^8.4.32, postcss@^8.4.38, postcss@^8.4.7:
|
|||||||
picocolors "^1.0.0"
|
picocolors "^1.0.0"
|
||||||
source-map-js "^1.2.0"
|
source-map-js "^1.2.0"
|
||||||
|
|
||||||
postinstall-postinstall@2.1.0:
|
|
||||||
version "2.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/postinstall-postinstall/-/postinstall-postinstall-2.1.0.tgz#4f7f77441ef539d1512c40bd04c71b06a4704ca3"
|
|
||||||
integrity sha512-7hQX6ZlZXIoRiWNrbMQaLzUUfH+sSx39u8EJ9HYuDc1kLo9IXKWjM5RSquZN1ad5GnH8CGFM78fsAAQi3OKEEQ==
|
|
||||||
|
|
||||||
prelude-ls@^1.2.1:
|
prelude-ls@^1.2.1:
|
||||||
version "1.2.1"
|
version "1.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
|
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
|
||||||
@@ -9294,13 +9171,6 @@ rimraf@3.0.2, rimraf@^3.0.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
glob "^7.1.3"
|
glob "^7.1.3"
|
||||||
|
|
||||||
rimraf@^2.6.3:
|
|
||||||
version "2.7.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
|
|
||||||
integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
|
|
||||||
dependencies:
|
|
||||||
glob "^7.1.3"
|
|
||||||
|
|
||||||
robust-predicates@^3.0.2:
|
robust-predicates@^3.0.2:
|
||||||
version "3.0.2"
|
version "3.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/robust-predicates/-/robust-predicates-3.0.2.tgz#d5b28528c4824d20fc48df1928d41d9efa1ad771"
|
resolved "https://registry.yarnpkg.com/robust-predicates/-/robust-predicates-3.0.2.tgz#d5b28528c4824d20fc48df1928d41d9efa1ad771"
|
||||||
@@ -9611,11 +9481,6 @@ size-limit@9.0.0:
|
|||||||
nanospinner "^1.1.0"
|
nanospinner "^1.1.0"
|
||||||
picocolors "^1.0.0"
|
picocolors "^1.0.0"
|
||||||
|
|
||||||
slash@^2.0.0:
|
|
||||||
version "2.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44"
|
|
||||||
integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==
|
|
||||||
|
|
||||||
slash@^3.0.0:
|
slash@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
|
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
|
||||||
@@ -9731,15 +9596,6 @@ sourcemap-codec@^1.4.8:
|
|||||||
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
|
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
|
||||||
integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
|
integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
|
||||||
|
|
||||||
speech-rule-engine@^4.0.6:
|
|
||||||
version "4.0.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/speech-rule-engine/-/speech-rule-engine-4.0.7.tgz#b655dacbad3dae04acc0f7665e26ef258397dd09"
|
|
||||||
integrity sha512-sJrL3/wHzNwJRLBdf6CjJWIlxC04iYKkyXvYSVsWVOiC2DSkHmxsqOhEeMsBA9XK+CHuNcsdkbFDnoUfAsmp9g==
|
|
||||||
dependencies:
|
|
||||||
commander "9.2.0"
|
|
||||||
wicked-good-xpath "1.3.0"
|
|
||||||
xmldom-sre "0.1.31"
|
|
||||||
|
|
||||||
sprintf-js@~1.0.2:
|
sprintf-js@~1.0.2:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
||||||
@@ -9777,7 +9633,7 @@ string-natural-compare@^3.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4"
|
resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4"
|
||||||
integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==
|
integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==
|
||||||
|
|
||||||
"string-width-cjs@npm:string-width@^4.2.0":
|
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.2.3:
|
||||||
version "4.2.3"
|
version "4.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||||
@@ -9795,15 +9651,6 @@ string-width@^4.1.0, string-width@^4.2.0:
|
|||||||
is-fullwidth-code-point "^3.0.0"
|
is-fullwidth-code-point "^3.0.0"
|
||||||
strip-ansi "^6.0.0"
|
strip-ansi "^6.0.0"
|
||||||
|
|
||||||
string-width@^4.2.3:
|
|
||||||
version "4.2.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
|
||||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
|
||||||
dependencies:
|
|
||||||
emoji-regex "^8.0.0"
|
|
||||||
is-fullwidth-code-point "^3.0.0"
|
|
||||||
strip-ansi "^6.0.1"
|
|
||||||
|
|
||||||
string-width@^5.0.0, string-width@^5.0.1, string-width@^5.1.2:
|
string-width@^5.0.0, string-width@^5.0.1, string-width@^5.1.2:
|
||||||
version "5.1.2"
|
version "5.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
|
||||||
@@ -9875,14 +9722,7 @@ stringify-object@^3.3.0:
|
|||||||
is-obj "^1.0.1"
|
is-obj "^1.0.1"
|
||||||
is-regexp "^1.0.0"
|
is-regexp "^1.0.0"
|
||||||
|
|
||||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@6.0.1, strip-ansi@^3.0.0, strip-ansi@^6.0.0, strip-ansi@^6.0.1, strip-ansi@^7.0.1:
|
||||||
version "6.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
|
||||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
|
||||||
dependencies:
|
|
||||||
ansi-regex "^5.0.1"
|
|
||||||
|
|
||||||
strip-ansi@6.0.1, strip-ansi@^3.0.0, strip-ansi@^6.0.0, strip-ansi@^6.0.1, strip-ansi@^7.0.1:
|
|
||||||
version "6.0.1"
|
version "6.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||||
@@ -10101,13 +9941,6 @@ tinyspy@^3.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-3.0.0.tgz#cb61644f2713cd84dee184863f4642e06ddf0585"
|
resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-3.0.0.tgz#cb61644f2713cd84dee184863f4642e06ddf0585"
|
||||||
integrity sha512-q5nmENpTHgiPVd1cJDDc9cVoYN5x4vCvwT3FMilvKPKneCBZAxn2YWQjDF0UMcE9k0Cay1gBiDfTMU0g+mPMQA==
|
integrity sha512-q5nmENpTHgiPVd1cJDDc9cVoYN5x4vCvwT3FMilvKPKneCBZAxn2YWQjDF0UMcE9k0Cay1gBiDfTMU0g+mPMQA==
|
||||||
|
|
||||||
tmp@^0.0.33:
|
|
||||||
version "0.0.33"
|
|
||||||
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
|
|
||||||
integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==
|
|
||||||
dependencies:
|
|
||||||
os-tmpdir "~1.0.2"
|
|
||||||
|
|
||||||
to-fast-properties@^1.0.3:
|
to-fast-properties@^1.0.3:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47"
|
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47"
|
||||||
@@ -10953,11 +10786,6 @@ why-is-node-running@^2.3.0:
|
|||||||
siginfo "^2.0.0"
|
siginfo "^2.0.0"
|
||||||
stackback "0.0.2"
|
stackback "0.0.2"
|
||||||
|
|
||||||
wicked-good-xpath@1.3.0:
|
|
||||||
version "1.3.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/wicked-good-xpath/-/wicked-good-xpath-1.3.0.tgz#81b0e95e8650e49c94b22298fff8686b5553cf6c"
|
|
||||||
integrity sha512-Gd9+TUn5nXdwj/hFsPVx5cuHHiF5Bwuc30jZ4+ronF1qHK5O7HD0sgmXWSEgwKquT3ClLoKPVbO6qGwVwLzvAw==
|
|
||||||
|
|
||||||
wildcard@^2.0.0:
|
wildcard@^2.0.0:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.1.tgz#5ab10d02487198954836b6349f74fff961e10f67"
|
resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.1.tgz#5ab10d02487198954836b6349f74fff961e10f67"
|
||||||
@@ -11126,7 +10954,7 @@ workbox-window@7.1.0, workbox-window@^7.0.0:
|
|||||||
"@types/trusted-types" "^2.0.2"
|
"@types/trusted-types" "^2.0.2"
|
||||||
workbox-core "7.1.0"
|
workbox-core "7.1.0"
|
||||||
|
|
||||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
|
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
|
||||||
version "7.0.0"
|
version "7.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||||
@@ -11144,15 +10972,6 @@ wrap-ansi@^6.2.0:
|
|||||||
string-width "^4.1.0"
|
string-width "^4.1.0"
|
||||||
strip-ansi "^6.0.0"
|
strip-ansi "^6.0.0"
|
||||||
|
|
||||||
wrap-ansi@^7.0.0:
|
|
||||||
version "7.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
|
||||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
|
||||||
dependencies:
|
|
||||||
ansi-styles "^4.0.0"
|
|
||||||
string-width "^4.1.0"
|
|
||||||
strip-ansi "^6.0.0"
|
|
||||||
|
|
||||||
wrap-ansi@^8.1.0:
|
wrap-ansi@^8.1.0:
|
||||||
version "8.1.0"
|
version "8.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
||||||
@@ -11197,11 +11016,6 @@ xmlchars@^2.2.0:
|
|||||||
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
|
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
|
||||||
integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
|
integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
|
||||||
|
|
||||||
xmldom-sre@0.1.31:
|
|
||||||
version "0.1.31"
|
|
||||||
resolved "https://registry.yarnpkg.com/xmldom-sre/-/xmldom-sre-0.1.31.tgz#10860d5bab2c603144597d04bf2c4980e98067f4"
|
|
||||||
integrity sha512-f9s+fUkX04BxQf+7mMWAp5zk61pciie+fFLC9hX9UVvCeJQfNHRHXpeo5MPcR0EUf57PYLdt+ZO4f3Ipk2oZUw==
|
|
||||||
|
|
||||||
xmlhttprequest-ssl@~2.0.0:
|
xmlhttprequest-ssl@~2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz#91360c86b914e67f44dce769180027c0da618c67"
|
resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz#91360c86b914e67f44dce769180027c0da618c67"
|
||||||
@@ -11232,11 +11046,6 @@ yaml@^1.10.0, yaml@^1.10.2:
|
|||||||
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
|
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
|
||||||
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
|
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
|
||||||
|
|
||||||
yaml@^2.2.2:
|
|
||||||
version "2.3.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.4.tgz#53fc1d514be80aabf386dc6001eb29bf3b7523b2"
|
|
||||||
integrity sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==
|
|
||||||
|
|
||||||
yargs-parser@^21.1.1:
|
yargs-parser@^21.1.1:
|
||||||
version "21.1.1"
|
version "21.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35"
|
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35"
|
||||||
|
|||||||
Reference in New Issue
Block a user