Compare commits

..
20 changed files with 476 additions and 118 deletions
+1 -2
View File
@@ -104,7 +104,6 @@ import { openConfirmModal } from "../packages/excalidraw/components/OverwriteCon
import { OverwriteConfirmDialog } from "../packages/excalidraw/components/OverwriteConfirm/OverwriteConfirm"; import { OverwriteConfirmDialog } from "../packages/excalidraw/components/OverwriteConfirm/OverwriteConfirm";
import Trans from "../packages/excalidraw/components/Trans"; import Trans from "../packages/excalidraw/components/Trans";
import { ShareDialog, shareDialogStateAtom } from "./share/ShareDialog"; import { ShareDialog, shareDialogStateAtom } from "./share/ShareDialog";
import { getFileName } from "../packages/excalidraw/data/filename";
polyfill(); polyfill();
@@ -691,6 +690,7 @@ const ExcalidrawWrapper = () => {
</div> </div>
); );
} }
return ( return (
<div <div
style={{ height: "100%" }} style={{ height: "100%" }}
@@ -775,7 +775,6 @@ const ExcalidrawWrapper = () => {
excalidrawAPI.getSceneElements(), excalidrawAPI.getSceneElements(),
excalidrawAPI.getAppState(), excalidrawAPI.getAppState(),
excalidrawAPI.getFiles(), excalidrawAPI.getFiles(),
getFileName(),
); );
}} }}
> >
@@ -25,13 +25,11 @@ import { MIME_TYPES } from "../../packages/excalidraw/constants";
import { trackEvent } from "../../packages/excalidraw/analytics"; import { trackEvent } from "../../packages/excalidraw/analytics";
import { getFrame } from "../../packages/excalidraw/utils"; import { getFrame } from "../../packages/excalidraw/utils";
import { ExcalidrawLogo } from "../../packages/excalidraw/components/ExcalidrawLogo"; import { ExcalidrawLogo } from "../../packages/excalidraw/components/ExcalidrawLogo";
import { getFileName } from "../../packages/excalidraw/data/filename";
export const exportToExcalidrawPlus = async ( export const exportToExcalidrawPlus = async (
elements: readonly NonDeletedExcalidrawElement[], elements: readonly NonDeletedExcalidrawElement[],
appState: Partial<AppState>, appState: Partial<AppState>,
files: BinaryFiles, files: BinaryFiles,
name: string,
) => { ) => {
const firebase = await loadFirebaseStorage(); const firebase = await loadFirebaseStorage();
@@ -55,7 +53,7 @@ export const exportToExcalidrawPlus = async (
.ref(`/migrations/scenes/${id}`) .ref(`/migrations/scenes/${id}`)
.put(blob, { .put(blob, {
customMetadata: { customMetadata: {
data: JSON.stringify({ version: 2, name }), data: JSON.stringify({ version: 2, name: appState.name }),
created: Date.now().toString(), created: Date.now().toString(),
}, },
}); });
@@ -119,12 +117,7 @@ export const ExportToExcalidrawPlus: React.FC<{
onClick={async () => { onClick={async () => {
try { try {
trackEvent("export", "eplus", `ui (${getFrame()})`); trackEvent("export", "eplus", `ui (${getFrame()})`);
await exportToExcalidrawPlus( await exportToExcalidrawPlus(elements, appState, files);
elements,
appState,
files,
getFileName(),
);
onSuccess(); onSuccess();
} catch (error: any) { } catch (error: any) {
console.error(error); console.error(error);
@@ -13,7 +13,6 @@ import { exportCanvas, prepareElementsForExport } from "../data/index";
import { isTextElement } from "../element"; import { isTextElement } from "../element";
import { t } from "../i18n"; import { t } from "../i18n";
import { isFirefox } from "../constants"; import { isFirefox } from "../constants";
import { getFileName } from "../data/filename";
export const actionCopy = register({ export const actionCopy = register({
name: "copy", name: "copy",
@@ -139,7 +138,6 @@ export const actionCopyAsSvg = register({
{ {
...appState, ...appState,
exportingFrame, exportingFrame,
name: app.props.name || getFileName(),
}, },
); );
return { return {
@@ -186,7 +184,6 @@ export const actionCopyAsPng = register({
await exportCanvas("clipboard", exportedElements, appState, app.files, { await exportCanvas("clipboard", exportedElements, appState, app.files, {
...appState, ...appState,
exportingFrame, exportingFrame,
name: app.props.name || getFileName(),
}); });
return { return {
appState: { appState: {
+3 -15
View File
@@ -17,7 +17,6 @@ import { getNonDeletedElements } from "../element";
import { isImageFileHandle } from "../data/blob"; import { isImageFileHandle } from "../data/blob";
import { nativeFileSystemSupported } from "../data/filesystem"; import { nativeFileSystemSupported } from "../data/filesystem";
import { Theme } from "../element/types"; import { Theme } from "../element/types";
import { getFileName } from "../data/filename";
import "../components/ToolIcon.scss"; import "../components/ToolIcon.scss";
@@ -30,7 +29,7 @@ export const actionChangeProjectName = register({
PanelComponent: ({ appState, updateData, appProps, data }) => ( PanelComponent: ({ appState, updateData, appProps, data }) => (
<ProjectName <ProjectName
label={t("labels.fileTitle")} label={t("labels.fileTitle")}
value={appProps.name} value={appState.name || "Unnamed"}
onChange={(name: string) => updateData(name)} onChange={(name: string) => updateData(name)}
isNameEditable={ isNameEditable={
typeof appProps.name === "undefined" && !appState.viewModeEnabled typeof appProps.name === "undefined" && !appState.viewModeEnabled
@@ -145,18 +144,8 @@ export const actionSaveToActiveFile = register({
try { try {
const { fileHandle } = isImageFileHandle(appState.fileHandle) const { fileHandle } = isImageFileHandle(appState.fileHandle)
? await resaveAsImageWithScene( ? await resaveAsImageWithScene(elements, appState, app.files)
elements, : await saveAsJSON(elements, appState, app.files);
appState,
app.files,
app.props.name || getFileName(),
)
: await saveAsJSON(
elements,
appState,
app.files,
app.props.name || getFileName(),
);
return { return {
commitToHistory: false, commitToHistory: false,
@@ -201,7 +190,6 @@ export const actionSaveFileToDisk = register({
fileHandle: null, fileHandle: null,
}, },
app.files, app.files,
app.props.name || getFileName(),
); );
return { return {
commitToHistory: false, commitToHistory: false,
+4
View File
@@ -7,7 +7,9 @@ import {
EXPORT_SCALES, EXPORT_SCALES,
THEME, THEME,
} from "./constants"; } from "./constants";
import { t } from "./i18n";
import { AppState, NormalizedZoomValue } from "./types"; import { AppState, NormalizedZoomValue } from "./types";
import { getDateTime } from "./utils";
const defaultExportScale = EXPORT_SCALES.includes(devicePixelRatio) const defaultExportScale = EXPORT_SCALES.includes(devicePixelRatio)
? devicePixelRatio ? devicePixelRatio
@@ -63,6 +65,7 @@ export const getDefaultAppState = (): Omit<
isRotating: false, isRotating: false,
lastPointerDownWith: "mouse", lastPointerDownWith: "mouse",
multiElement: null, multiElement: null,
name: `${t("labels.untitled")}-${getDateTime()}`,
contextMenu: null, contextMenu: null,
openMenu: null, openMenu: null,
openPopup: null, openPopup: null,
@@ -172,6 +175,7 @@ const APP_STATE_STORAGE_CONF = (<
isRotating: { browser: false, export: false, server: false }, isRotating: { browser: false, export: false, server: false },
lastPointerDownWith: { browser: true, export: false, server: false }, lastPointerDownWith: { browser: true, export: false, server: false },
multiElement: { browser: false, export: false, server: false }, multiElement: { browser: false, export: false, server: false },
name: { browser: true, export: false, server: false },
offsetLeft: { browser: false, export: false, server: false }, offsetLeft: { browser: false, export: false, server: false },
offsetTop: { browser: false, export: false, server: false }, offsetTop: { browser: false, export: false, server: false },
contextMenu: { browser: false, export: false, server: false }, contextMenu: { browser: false, export: false, server: false },
+15 -3
View File
@@ -409,7 +409,6 @@ import { withBatchedUpdates, withBatchedUpdatesThrottled } from "../reactUtils";
import { getRenderOpacity } from "../renderer/renderElement"; import { getRenderOpacity } from "../renderer/renderElement";
import { textWysiwyg } from "../element/textWysiwyg"; import { textWysiwyg } from "../element/textWysiwyg";
import { isOverScrollBars } from "../scene/scrollbars"; import { isOverScrollBars } from "../scene/scrollbars";
import { getFileName } from "../data/filename";
const AppContext = React.createContext<AppClassProperties>(null!); const AppContext = React.createContext<AppClassProperties>(null!);
const AppPropsContext = React.createContext<AppProps>(null!); const AppPropsContext = React.createContext<AppProps>(null!);
@@ -620,6 +619,7 @@ class App extends React.Component<AppProps, AppState> {
gridModeEnabled = false, gridModeEnabled = false,
objectsSnapModeEnabled = false, objectsSnapModeEnabled = false,
theme = defaultAppState.theme, theme = defaultAppState.theme,
name = defaultAppState.name,
} = props; } = props;
this.state = { this.state = {
...defaultAppState, ...defaultAppState,
@@ -630,6 +630,7 @@ class App extends React.Component<AppProps, AppState> {
zenModeEnabled, zenModeEnabled,
objectsSnapModeEnabled, objectsSnapModeEnabled,
gridSize: gridModeEnabled ? GRID_SIZE : null, gridSize: gridModeEnabled ? GRID_SIZE : null,
name,
width: window.innerWidth, width: window.innerWidth,
height: window.innerHeight, height: window.innerHeight,
}; };
@@ -1724,7 +1725,7 @@ class App extends React.Component<AppProps, AppState> {
this.files, this.files,
{ {
exportBackground: this.state.exportBackground, exportBackground: this.state.exportBackground,
name: this.props.name || getFileName(), name: this.state.name,
viewBackgroundColor: this.state.viewBackgroundColor, viewBackgroundColor: this.state.viewBackgroundColor,
exportingFrame: opts.exportingFrame, exportingFrame: opts.exportingFrame,
}, },
@@ -2123,7 +2124,7 @@ class App extends React.Component<AppProps, AppState> {
let gridSize = actionResult?.appState?.gridSize || null; let gridSize = actionResult?.appState?.gridSize || null;
const theme = const theme =
actionResult?.appState?.theme || this.props.theme || THEME.LIGHT; actionResult?.appState?.theme || this.props.theme || THEME.LIGHT;
let name = actionResult?.appState?.name ?? this.state.name;
const errorMessage = const errorMessage =
actionResult?.appState?.errorMessage ?? this.state.errorMessage; actionResult?.appState?.errorMessage ?? this.state.errorMessage;
if (typeof this.props.viewModeEnabled !== "undefined") { if (typeof this.props.viewModeEnabled !== "undefined") {
@@ -2138,6 +2139,10 @@ class App extends React.Component<AppProps, AppState> {
gridSize = this.props.gridModeEnabled ? GRID_SIZE : null; gridSize = this.props.gridModeEnabled ? GRID_SIZE : null;
} }
if (typeof this.props.name !== "undefined") {
name = this.props.name;
}
editingElement = editingElement =
editingElement || actionResult.appState?.editingElement || null; editingElement || actionResult.appState?.editingElement || null;
@@ -2160,6 +2165,7 @@ class App extends React.Component<AppProps, AppState> {
zenModeEnabled, zenModeEnabled,
gridSize, gridSize,
theme, theme,
name,
errorMessage, errorMessage,
}); });
}, },
@@ -2693,6 +2699,12 @@ class App extends React.Component<AppProps, AppState> {
}); });
} }
if (this.props.name && prevProps.name !== this.props.name) {
this.setState({
name: this.props.name,
});
}
this.excalidrawContainerRef.current?.classList.toggle( this.excalidrawContainerRef.current?.classList.toggle(
"theme--dark", "theme--dark",
this.state.theme === "dark", this.state.theme === "dark",
@@ -36,7 +36,6 @@ import { useAppProps } from "./App";
import { FilledButton } from "./FilledButton"; import { FilledButton } from "./FilledButton";
import { cloneJSON } from "../utils"; import { cloneJSON } from "../utils";
import { prepareElementsForExport } from "../data"; import { prepareElementsForExport } from "../data";
import { getFileName } from "../data/filename";
const supportsContextFilters = const supportsContextFilters =
"filter" in document.createElement("canvas").getContext("2d")!; "filter" in document.createElement("canvas").getContext("2d")!;
@@ -74,9 +73,7 @@ const ImageExportModal = ({
); );
const appProps = useAppProps(); const appProps = useAppProps();
const [projectName, setProjectName] = useState( const [projectName, setProjectName] = useState(appStateSnapshot.name);
appProps.name || getFileName(),
);
const [exportSelectionOnly, setExportSelectionOnly] = useState(hasSelection); const [exportSelectionOnly, setExportSelectionOnly] = useState(hasSelection);
const [exportWithBackground, setExportWithBackground] = useState( const [exportWithBackground, setExportWithBackground] = useState(
appStateSnapshot.exportBackground, appStateSnapshot.exportBackground,
@@ -112,6 +109,7 @@ const ImageExportModal = ({
elements: exportedElements, elements: exportedElements,
appState: { appState: {
...appStateSnapshot, ...appStateSnapshot,
name: projectName,
exportBackground: exportWithBackground, exportBackground: exportWithBackground,
exportWithDarkMode: exportDarkMode, exportWithDarkMode: exportDarkMode,
exportScale, exportScale,
@@ -6,12 +6,9 @@ import { focusNearestParent } from "../utils";
import "./ProjectName.scss"; import "./ProjectName.scss";
import { useExcalidrawContainer } from "./App"; import { useExcalidrawContainer } from "./App";
import { KEYS } from "../keys"; import { KEYS } from "../keys";
import { EditorLocalStorage } from "../data/EditorLocalStorage";
import { EDITOR_LS_KEYS } from "../constants";
import { getFileName } from "../data/filename";
type Props = { type Props = {
value?: string; value: string;
onChange: (value: string) => void; onChange: (value: string) => void;
label: string; label: string;
isNameEditable: boolean; isNameEditable: boolean;
@@ -20,13 +17,9 @@ type Props = {
export const ProjectName = (props: Props) => { export const ProjectName = (props: Props) => {
const { id } = useExcalidrawContainer(); const { id } = useExcalidrawContainer();
const [fileName, setFileName] = useState<string>( const [fileName, setFileName] = useState<string>(props.value);
props.value || getFileName(),
);
const handleBlur = (event: any) => { const handleBlur = (event: any) => {
EditorLocalStorage.set(EDITOR_LS_KEYS.EXCALIDRAW_FILE_NAME, fileName);
if (!props.ignoreFocus) { if (!props.ignoreFocus) {
focusNearestParent(event.target); focusNearestParent(event.target);
} }
-1
View File
@@ -380,5 +380,4 @@ export const EDITOR_LS_KEYS = {
// legacy naming (non)scheme // legacy naming (non)scheme
MERMAID_TO_EXCALIDRAW: "mermaid-to-excalidraw", MERMAID_TO_EXCALIDRAW: "mermaid-to-excalidraw",
PUBLISH_LIBRARY: "publish-library-data", PUBLISH_LIBRARY: "publish-library-data",
EXCALIDRAW_FILE_NAME: "excalidraw-file-name",
} as const; } as const;
-11
View File
@@ -1,11 +0,0 @@
import { EDITOR_LS_KEYS } from "../constants";
import { t } from "../i18n";
import { getDateTime } from "../utils";
import { EditorLocalStorage } from "./EditorLocalStorage";
export const getFileName = () => {
return (
EditorLocalStorage.get<string>(EDITOR_LS_KEYS.EXCALIDRAW_FILE_NAME) ||
`${t("labels.untitled")}-${getDateTime()}`
);
};
+1 -2
View File
@@ -71,7 +71,6 @@ export const saveAsJSON = async (
elements: readonly ExcalidrawElement[], elements: readonly ExcalidrawElement[],
appState: AppState, appState: AppState,
files: BinaryFiles, files: BinaryFiles,
name: string,
) => { ) => {
const serialized = serializeAsJSON(elements, appState, files, "local"); const serialized = serializeAsJSON(elements, appState, files, "local");
const blob = new Blob([serialized], { const blob = new Blob([serialized], {
@@ -79,7 +78,7 @@ export const saveAsJSON = async (
}); });
const fileHandle = await fileSave(blob, { const fileHandle = await fileSave(blob, {
name, name: appState.name,
extension: "excalidraw", extension: "excalidraw",
description: "Excalidraw file", description: "Excalidraw file",
fileHandle: isImageFileHandle(appState.fileHandle) fileHandle: isImageFileHandle(appState.fileHandle)
+1 -2
View File
@@ -7,9 +7,8 @@ export const resaveAsImageWithScene = async (
elements: readonly ExcalidrawElement[], elements: readonly ExcalidrawElement[],
appState: AppState, appState: AppState,
files: BinaryFiles, files: BinaryFiles,
name: string,
) => { ) => {
const { exportBackground, viewBackgroundColor, fileHandle } = appState; const { exportBackground, viewBackgroundColor, name, fileHandle } = appState;
const fileHandleType = getFileHandleType(fileHandle); const fileHandleType = getFileHandleType(fileHandle);
+7 -2
View File
@@ -207,11 +207,16 @@ const restoreElement = (
: // no element height likely means programmatic use, so default : // no element height likely means programmatic use, so default
// to a fixed line height // to a fixed line height
getDefaultLineHeight(element.fontFamily)); getDefaultLineHeight(element.fontFamily));
const baseline = measureBaseline( const baseline = measureBaseline(
element.text, element.text,
getFontString(element), getFontString({
lineHeight, fontSize: element.fontSize,
fontFamily: element.fontFamily,
}),
String(lineHeight),
); );
element = restoreElementWithProperties(element, { element = restoreElementWithProperties(element, {
fontSize, fontSize,
fontFamily, fontFamily,
+26 -9
View File
@@ -54,6 +54,8 @@ import {
getApproxMinLineHeight, getApproxMinLineHeight,
measureText, measureText,
getBoundTextMaxHeight, getBoundTextMaxHeight,
measureBaselines,
BaselineInput,
} from "./textElement"; } from "./textElement";
import { LinearElementEditor } from "./linearElementEditor"; import { LinearElementEditor } from "./linearElementEditor";
@@ -769,6 +771,25 @@ export const resizeMultipleElements = (
}; };
}[] = []; }[] = [];
const precomputedBaselines = measureBaselines(
targetElements.reduce((inputs, { latest: element }) => {
if (!isTextElement(element)) {
return inputs;
}
inputs.push({
id: element.id,
text: element.text,
font: getFontString({
fontSize: element.fontSize,
fontFamily: element.fontFamily,
}),
lineHeight: String(element.lineHeight),
});
return inputs;
}, [] as BaselineInput[]),
);
for (const { orig, latest } of targetElements) { for (const { orig, latest } of targetElements) {
// bounded text elements are updated along with their container elements // bounded text elements are updated along with their container elements
if (isTextElement(orig) && isBoundToContainer(orig)) { if (isTextElement(orig) && isBoundToContainer(orig)) {
@@ -838,17 +859,13 @@ export const resizeMultipleElements = (
} }
if (isTextElement(orig)) { if (isTextElement(orig)) {
const metrics = measureFontSizeFromWidth( const nextFontSize = orig.fontSize * scale;
orig, if (nextFontSize < MIN_FONT_SIZE) {
elementsMap,
width,
height,
);
if (!metrics) {
return; return;
} }
update.fontSize = metrics.size;
update.baseline = metrics.baseline; update.fontSize = nextFontSize;
update.baseline = precomputedBaselines.get(orig.id);
} }
const boundTextElement = originalElements.get( const boundTextElement = originalElements.get(
+89 -46
View File
@@ -294,59 +294,102 @@ export const measureText = (
const fontSize = parseFloat(font); const fontSize = parseFloat(font);
const height = getTextHeight(text, fontSize, lineHeight); const height = getTextHeight(text, fontSize, lineHeight);
const width = getTextWidth(text, font); const width = getTextWidth(text, font);
const baseline = measureBaseline(text, font, lineHeight); const baseline = measureBaseline(text, font, String(lineHeight));
return { width, height, baseline }; return { width, height, baseline };
}; };
export type BaselineInput = {
id: string;
font: FontString;
lineHeight: string;
text: string;
};
/**
* Baseline calculation is based on expensive DOM operations, resulting in forced reflow.
* Therefore whenever we can, we should always batch the calculation of the baselines upfront for all the elements.
*/
export const measureBaselines = (inputs: BaselineInput[]) => {
const baselines = new Map<string, number>();
const containers = new Map<string, [HTMLDivElement, BaselineInput]>();
// Batch DOM writes (and reads below) to avoid layout trashing
for (const input of inputs) {
const container = document.createElement("div");
const span = document.createElement("span");
Object.assign(span.style, {
display: "inline-block",
// overflow: "hidden",
});
Object.assign(container.style, {
font: input.font,
lineHeight: input.lineHeight,
minHeight: "1em",
visibility: "hidden",
// whitespace: "pre",
// overflow: "hidden",
// wordBreak: "break-word",
// whiteSpace: "pre-wrap",
});
container.innerText = input.text;
container.appendChild(span);
document.body.appendChild(container);
containers.set(input.id, [container, input]);
}
for (const [id, [container, input]] of containers.entries()) {
const span = container.lastChild as HTMLSpanElement;
let baseline =
span.getBoundingClientRect().y - container.getBoundingClientRect().y;
if (isSafari) {
const height = container.offsetHeight;
const fontSize = parseFloat(input.font);
const canvasHeight = getTextHeight(
input.text,
fontSize,
Number(input.lineHeight) as any,
);
// In Safari the font size gets rounded off when rendering hence calculating the safari height and shifting the baseline if it differs
// from the actual canvas height
const domHeight = getTextHeight(
input.text,
Math.round(fontSize),
Number(input.lineHeight) as any,
);
if (canvasHeight > height) {
baseline += canvasHeight - domHeight;
}
if (height > canvasHeight) {
baseline -= domHeight - canvasHeight;
}
}
baselines.set(id, baseline);
}
for (const [container] of containers.values()) {
document.body.removeChild(container);
}
return baselines;
};
export const measureBaseline = ( export const measureBaseline = (
text: string, text: string,
font: FontString, font: FontString,
lineHeight: ExcalidrawTextElement["lineHeight"], lineHeight: string,
wrapInContainer?: boolean,
) => { ) => {
const container = document.createElement("div"); // In single measurement the element id is irrelevant
container.style.position = "absolute"; const fakeId = "fake-id";
container.style.whiteSpace = "pre"; const baselines = measureBaselines([{ id: fakeId, text, font, lineHeight }]);
container.style.font = font; return baselines.get(fakeId)!;
container.style.minHeight = "1em";
if (wrapInContainer) {
container.style.overflow = "hidden";
container.style.wordBreak = "break-word";
container.style.whiteSpace = "pre-wrap";
}
container.style.lineHeight = String(lineHeight);
container.innerText = text;
// Baseline is important for positioning text on canvas
document.body.appendChild(container);
const span = document.createElement("span");
span.style.display = "inline-block";
span.style.overflow = "hidden";
span.style.width = "1px";
span.style.height = "1px";
container.appendChild(span);
let baseline = span.offsetTop + span.offsetHeight;
const height = container.offsetHeight;
if (isSafari) {
const canvasHeight = getTextHeight(text, parseFloat(font), lineHeight);
const fontSize = parseFloat(font);
// In Safari the font size gets rounded off when rendering hence calculating the safari height and shifting the baseline if it differs
// from the actual canvas height
const domHeight = getTextHeight(text, Math.round(fontSize), lineHeight);
if (canvasHeight > height) {
baseline += canvasHeight - domHeight;
}
if (height > canvasHeight) {
baseline -= domHeight - canvasHeight;
}
}
document.body.removeChild(container);
return baseline;
}; };
/** /**
+1
View File
@@ -26,6 +26,7 @@ const clearAppStatePropertiesForHistory = (appState: AppState) => {
viewBackgroundColor: appState.viewBackgroundColor, viewBackgroundColor: appState.viewBackgroundColor,
editingLinearElement: appState.editingLinearElement, editingLinearElement: appState.editingLinearElement,
editingGroupId: appState.editingGroupId, editingGroupId: appState.editingGroupId,
name: appState.name,
}; };
}; };
@@ -328,6 +328,7 @@ exports[`contextMenu element > right-clicking on a group should select whole gro
"isRotating": false, "isRotating": false,
"lastPointerDownWith": "mouse", "lastPointerDownWith": "mouse",
"multiElement": null, "multiElement": null,
"name": "Untitled-201933152653",
"objectsSnapModeEnabled": false, "objectsSnapModeEnabled": false,
"offsetLeft": 20, "offsetLeft": 20,
"offsetTop": 10, "offsetTop": 10,
@@ -458,6 +459,7 @@ exports[`contextMenu element > right-clicking on a group should select whole gro
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": {}, "selectedElementIds": {},
"selectedGroupIds": {}, "selectedGroupIds": {},
"viewBackgroundColor": "#ffffff", "viewBackgroundColor": "#ffffff",
@@ -527,6 +529,7 @@ exports[`contextMenu element > selecting 'Add to library' in context menu adds e
"isRotating": false, "isRotating": false,
"lastPointerDownWith": "mouse", "lastPointerDownWith": "mouse",
"multiElement": null, "multiElement": null,
"name": "Untitled-201933152653",
"objectsSnapModeEnabled": false, "objectsSnapModeEnabled": false,
"offsetLeft": 20, "offsetLeft": 20,
"offsetTop": 10, "offsetTop": 10,
@@ -617,6 +620,7 @@ exports[`contextMenu element > selecting 'Add to library' in context menu adds e
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": {}, "selectedElementIds": {},
"selectedGroupIds": {}, "selectedGroupIds": {},
"viewBackgroundColor": "#ffffff", "viewBackgroundColor": "#ffffff",
@@ -627,6 +631,7 @@ exports[`contextMenu element > selecting 'Add to library' in context menu adds e
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": { "selectedElementIds": {
"id0": true, "id0": true,
}, },
@@ -728,6 +733,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
"isRotating": false, "isRotating": false,
"lastPointerDownWith": "mouse", "lastPointerDownWith": "mouse",
"multiElement": null, "multiElement": null,
"name": "Untitled-201933152653",
"objectsSnapModeEnabled": false, "objectsSnapModeEnabled": false,
"offsetLeft": 20, "offsetLeft": 20,
"offsetTop": 10, "offsetTop": 10,
@@ -848,6 +854,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": {}, "selectedElementIds": {},
"selectedGroupIds": {}, "selectedGroupIds": {},
"viewBackgroundColor": "#ffffff", "viewBackgroundColor": "#ffffff",
@@ -858,6 +865,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": { "selectedElementIds": {
"id0": true, "id0": true,
}, },
@@ -900,6 +908,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": { "selectedElementIds": {
"id1": true, "id1": true,
}, },
@@ -971,6 +980,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": { "selectedElementIds": {
"id0": true, "id0": true,
}, },
@@ -1101,6 +1111,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
"isRotating": false, "isRotating": false,
"lastPointerDownWith": "mouse", "lastPointerDownWith": "mouse",
"multiElement": null, "multiElement": null,
"name": "Untitled-201933152653",
"objectsSnapModeEnabled": false, "objectsSnapModeEnabled": false,
"offsetLeft": 20, "offsetLeft": 20,
"offsetTop": 10, "offsetTop": 10,
@@ -1221,6 +1232,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": {}, "selectedElementIds": {},
"selectedGroupIds": {}, "selectedGroupIds": {},
"viewBackgroundColor": "#ffffff", "viewBackgroundColor": "#ffffff",
@@ -1231,6 +1243,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": { "selectedElementIds": {
"id0": true, "id0": true,
}, },
@@ -1273,6 +1286,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": { "selectedElementIds": {
"id1": true, "id1": true,
}, },
@@ -1344,6 +1358,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": { "selectedElementIds": {
"id0": true, "id0": true,
}, },
@@ -1474,6 +1489,7 @@ exports[`contextMenu element > selecting 'Copy styles' in context menu copies st
"isRotating": false, "isRotating": false,
"lastPointerDownWith": "mouse", "lastPointerDownWith": "mouse",
"multiElement": null, "multiElement": null,
"name": "Untitled-201933152653",
"objectsSnapModeEnabled": false, "objectsSnapModeEnabled": false,
"offsetLeft": 20, "offsetLeft": 20,
"offsetTop": 10, "offsetTop": 10,
@@ -1564,6 +1580,7 @@ exports[`contextMenu element > selecting 'Copy styles' in context menu copies st
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": {}, "selectedElementIds": {},
"selectedGroupIds": {}, "selectedGroupIds": {},
"viewBackgroundColor": "#ffffff", "viewBackgroundColor": "#ffffff",
@@ -1574,6 +1591,7 @@ exports[`contextMenu element > selecting 'Copy styles' in context menu copies st
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": { "selectedElementIds": {
"id0": true, "id0": true,
}, },
@@ -1675,6 +1693,7 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen
"isRotating": false, "isRotating": false,
"lastPointerDownWith": "mouse", "lastPointerDownWith": "mouse",
"multiElement": null, "multiElement": null,
"name": "Untitled-201933152653",
"objectsSnapModeEnabled": false, "objectsSnapModeEnabled": false,
"offsetLeft": 20, "offsetLeft": 20,
"offsetTop": 10, "offsetTop": 10,
@@ -1761,6 +1780,7 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": {}, "selectedElementIds": {},
"selectedGroupIds": {}, "selectedGroupIds": {},
"viewBackgroundColor": "#ffffff", "viewBackgroundColor": "#ffffff",
@@ -1771,6 +1791,7 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": { "selectedElementIds": {
"id0": true, "id0": true,
}, },
@@ -1813,6 +1834,7 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": {}, "selectedElementIds": {},
"selectedGroupIds": {}, "selectedGroupIds": {},
"viewBackgroundColor": "#ffffff", "viewBackgroundColor": "#ffffff",
@@ -1912,6 +1934,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates
"isRotating": false, "isRotating": false,
"lastPointerDownWith": "mouse", "lastPointerDownWith": "mouse",
"multiElement": null, "multiElement": null,
"name": "Untitled-201933152653",
"objectsSnapModeEnabled": false, "objectsSnapModeEnabled": false,
"offsetLeft": 20, "offsetLeft": 20,
"offsetTop": 10, "offsetTop": 10,
@@ -2032,6 +2055,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": {}, "selectedElementIds": {},
"selectedGroupIds": {}, "selectedGroupIds": {},
"viewBackgroundColor": "#ffffff", "viewBackgroundColor": "#ffffff",
@@ -2042,6 +2066,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": { "selectedElementIds": {
"id0": true, "id0": true,
}, },
@@ -2084,6 +2109,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": { "selectedElementIds": {
"id0_copy": true, "id0_copy": true,
}, },
@@ -2214,6 +2240,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
"isRotating": false, "isRotating": false,
"lastPointerDownWith": "mouse", "lastPointerDownWith": "mouse",
"multiElement": null, "multiElement": null,
"name": "Untitled-201933152653",
"objectsSnapModeEnabled": false, "objectsSnapModeEnabled": false,
"offsetLeft": 20, "offsetLeft": 20,
"offsetTop": 10, "offsetTop": 10,
@@ -2343,6 +2370,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": {}, "selectedElementIds": {},
"selectedGroupIds": {}, "selectedGroupIds": {},
"viewBackgroundColor": "#ffffff", "viewBackgroundColor": "#ffffff",
@@ -2353,6 +2381,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": { "selectedElementIds": {
"id0": true, "id0": true,
}, },
@@ -2395,6 +2424,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": { "selectedElementIds": {
"id1": true, "id1": true,
}, },
@@ -2466,6 +2496,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": { "selectedElementIds": {
"id0": true, "id0": true,
"id1": true, "id1": true,
@@ -2603,6 +2634,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
"isRotating": false, "isRotating": false,
"lastPointerDownWith": "mouse", "lastPointerDownWith": "mouse",
"multiElement": null, "multiElement": null,
"name": "Untitled-201933152653",
"objectsSnapModeEnabled": false, "objectsSnapModeEnabled": false,
"offsetLeft": 20, "offsetLeft": 20,
"offsetTop": 10, "offsetTop": 10,
@@ -2725,6 +2757,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": {}, "selectedElementIds": {},
"selectedGroupIds": {}, "selectedGroupIds": {},
"viewBackgroundColor": "#ffffff", "viewBackgroundColor": "#ffffff",
@@ -2735,6 +2768,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": { "selectedElementIds": {
"id0": true, "id0": true,
}, },
@@ -2777,6 +2811,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": { "selectedElementIds": {
"id1": true, "id1": true,
}, },
@@ -2848,6 +2883,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": { "selectedElementIds": {
"id1": true, "id1": true,
}, },
@@ -2919,6 +2955,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": { "selectedElementIds": {
"id1": true, "id1": true,
}, },
@@ -2990,6 +3027,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": { "selectedElementIds": {
"id1": true, "id1": true,
}, },
@@ -3061,6 +3099,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": { "selectedElementIds": {
"id1": true, "id1": true,
}, },
@@ -3132,6 +3171,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": { "selectedElementIds": {
"id1": true, "id1": true,
}, },
@@ -3203,6 +3243,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": { "selectedElementIds": {
"id1": true, "id1": true,
}, },
@@ -3274,6 +3315,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": { "selectedElementIds": {
"id0": true, "id0": true,
}, },
@@ -3404,6 +3446,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
"isRotating": false, "isRotating": false,
"lastPointerDownWith": "mouse", "lastPointerDownWith": "mouse",
"multiElement": null, "multiElement": null,
"name": "Untitled-201933152653",
"objectsSnapModeEnabled": false, "objectsSnapModeEnabled": false,
"offsetLeft": 20, "offsetLeft": 20,
"offsetTop": 10, "offsetTop": 10,
@@ -3524,6 +3567,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": {}, "selectedElementIds": {},
"selectedGroupIds": {}, "selectedGroupIds": {},
"viewBackgroundColor": "#ffffff", "viewBackgroundColor": "#ffffff",
@@ -3534,6 +3578,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": { "selectedElementIds": {
"id0": true, "id0": true,
}, },
@@ -3576,6 +3621,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": { "selectedElementIds": {
"id1": true, "id1": true,
}, },
@@ -3647,6 +3693,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": { "selectedElementIds": {
"id1": true, "id1": true,
}, },
@@ -3777,6 +3824,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
"isRotating": false, "isRotating": false,
"lastPointerDownWith": "mouse", "lastPointerDownWith": "mouse",
"multiElement": null, "multiElement": null,
"name": "Untitled-201933152653",
"objectsSnapModeEnabled": false, "objectsSnapModeEnabled": false,
"offsetLeft": 20, "offsetLeft": 20,
"offsetTop": 10, "offsetTop": 10,
@@ -3897,6 +3945,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": {}, "selectedElementIds": {},
"selectedGroupIds": {}, "selectedGroupIds": {},
"viewBackgroundColor": "#ffffff", "viewBackgroundColor": "#ffffff",
@@ -3907,6 +3956,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": { "selectedElementIds": {
"id0": true, "id0": true,
}, },
@@ -3949,6 +3999,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": { "selectedElementIds": {
"id1": true, "id1": true,
}, },
@@ -4020,6 +4071,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": { "selectedElementIds": {
"id1": true, "id1": true,
}, },
@@ -4150,6 +4202,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
"isRotating": false, "isRotating": false,
"lastPointerDownWith": "mouse", "lastPointerDownWith": "mouse",
"multiElement": null, "multiElement": null,
"name": "Untitled-201933152653",
"objectsSnapModeEnabled": false, "objectsSnapModeEnabled": false,
"offsetLeft": 20, "offsetLeft": 20,
"offsetTop": 10, "offsetTop": 10,
@@ -4273,6 +4326,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": {}, "selectedElementIds": {},
"selectedGroupIds": {}, "selectedGroupIds": {},
"viewBackgroundColor": "#ffffff", "viewBackgroundColor": "#ffffff",
@@ -4283,6 +4337,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": { "selectedElementIds": {
"id0": true, "id0": true,
}, },
@@ -4325,6 +4380,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": { "selectedElementIds": {
"id1": true, "id1": true,
}, },
@@ -4396,6 +4452,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": { "selectedElementIds": {
"id0": true, "id0": true,
"id1": true, "id1": true,
@@ -4474,6 +4531,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": { "selectedElementIds": {
"id0": true, "id0": true,
"id1": true, "id1": true,
@@ -4878,6 +4936,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
"isRotating": false, "isRotating": false,
"lastPointerDownWith": "mouse", "lastPointerDownWith": "mouse",
"multiElement": null, "multiElement": null,
"name": "Untitled-201933152653",
"objectsSnapModeEnabled": false, "objectsSnapModeEnabled": false,
"offsetLeft": 20, "offsetLeft": 20,
"offsetTop": 10, "offsetTop": 10,
@@ -5001,6 +5060,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": {}, "selectedElementIds": {},
"selectedGroupIds": {}, "selectedGroupIds": {},
"viewBackgroundColor": "#ffffff", "viewBackgroundColor": "#ffffff",
@@ -5011,6 +5071,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": { "selectedElementIds": {
"id0": true, "id0": true,
}, },
@@ -5053,6 +5114,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": { "selectedElementIds": {
"id1": true, "id1": true,
}, },
@@ -5456,6 +5518,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
"isRotating": false, "isRotating": false,
"lastPointerDownWith": "mouse", "lastPointerDownWith": "mouse",
"multiElement": null, "multiElement": null,
"name": "Untitled-201933152653",
"objectsSnapModeEnabled": false, "objectsSnapModeEnabled": false,
"offsetLeft": 20, "offsetLeft": 20,
"offsetTop": 10, "offsetTop": 10,
@@ -5585,6 +5648,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": {}, "selectedElementIds": {},
"selectedGroupIds": {}, "selectedGroupIds": {},
"viewBackgroundColor": "#ffffff", "viewBackgroundColor": "#ffffff",
@@ -5595,6 +5659,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": { "selectedElementIds": {
"id0": true, "id0": true,
}, },
@@ -5637,6 +5702,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": { "selectedElementIds": {
"id1": true, "id1": true,
}, },
@@ -5708,6 +5774,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": { "selectedElementIds": {
"id0": true, "id0": true,
"id1": true, "id1": true,
@@ -5972,6 +6039,7 @@ exports[`contextMenu element > shows context menu for canvas > [end of test] app
"isRotating": false, "isRotating": false,
"lastPointerDownWith": "mouse", "lastPointerDownWith": "mouse",
"multiElement": null, "multiElement": null,
"name": "Untitled-201933152653",
"objectsSnapModeEnabled": false, "objectsSnapModeEnabled": false,
"offsetLeft": 20, "offsetLeft": 20,
"offsetTop": 10, "offsetTop": 10,
@@ -6029,6 +6097,7 @@ exports[`contextMenu element > shows context menu for canvas > [end of test] his
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": {}, "selectedElementIds": {},
"selectedGroupIds": {}, "selectedGroupIds": {},
"viewBackgroundColor": "#ffffff", "viewBackgroundColor": "#ffffff",
@@ -6371,6 +6440,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
"isRotating": false, "isRotating": false,
"lastPointerDownWith": "mouse", "lastPointerDownWith": "mouse",
"multiElement": null, "multiElement": null,
"name": "Untitled-201933152653",
"objectsSnapModeEnabled": false, "objectsSnapModeEnabled": false,
"offsetLeft": 20, "offsetLeft": 20,
"offsetTop": 10, "offsetTop": 10,
@@ -6746,6 +6816,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
"isRotating": false, "isRotating": false,
"lastPointerDownWith": "mouse", "lastPointerDownWith": "mouse",
"multiElement": null, "multiElement": null,
"name": "Untitled-201933152653",
"objectsSnapModeEnabled": false, "objectsSnapModeEnabled": false,
"offsetLeft": 20, "offsetLeft": 20,
"offsetTop": 10, "offsetTop": 10,
@@ -6901,6 +6972,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] hi
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": {}, "selectedElementIds": {},
"selectedGroupIds": {}, "selectedGroupIds": {},
"viewBackgroundColor": "#ffffff", "viewBackgroundColor": "#ffffff",
@@ -6911,6 +6983,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] hi
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": { "selectedElementIds": {
"id0": true, "id0": true,
}, },
@@ -6962,6 +7035,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] hi
"appState": { "appState": {
"editingGroupId": null, "editingGroupId": null,
"editingLinearElement": null, "editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": {}, "selectedElementIds": {},
"selectedGroupIds": {}, "selectedGroupIds": {},
"viewBackgroundColor": "#ffffff", "viewBackgroundColor": "#ffffff",
File diff suppressed because it is too large Load Diff
@@ -306,10 +306,12 @@ describe("restoreAppState", () => {
const stubImportedAppState = getDefaultAppState(); const stubImportedAppState = getDefaultAppState();
stubImportedAppState.activeTool.type = "selection"; stubImportedAppState.activeTool.type = "selection";
stubImportedAppState.cursorButton = "down"; stubImportedAppState.cursorButton = "down";
stubImportedAppState.name = "imported app state";
const stubLocalAppState = getDefaultAppState(); const stubLocalAppState = getDefaultAppState();
stubLocalAppState.activeTool.type = "rectangle"; stubLocalAppState.activeTool.type = "rectangle";
stubLocalAppState.cursorButton = "up"; stubLocalAppState.cursorButton = "up";
stubLocalAppState.name = "local app state";
const restoredAppState = restore.restoreAppState( const restoredAppState = restore.restoreAppState(
stubImportedAppState, stubImportedAppState,
@@ -319,6 +321,7 @@ describe("restoreAppState", () => {
stubImportedAppState.activeTool, stubImportedAppState.activeTool,
); );
expect(restoredAppState.cursorButton).toBe("up"); expect(restoredAppState.cursorButton).toBe("up");
expect(restoredAppState.name).toBe(stubImportedAppState.name);
}); });
it("should restore with current app state when imported data state is undefined", () => { it("should restore with current app state when imported data state is undefined", () => {
@@ -330,31 +333,37 @@ describe("restoreAppState", () => {
const stubLocalAppState = getDefaultAppState(); const stubLocalAppState = getDefaultAppState();
stubLocalAppState.cursorButton = "down"; stubLocalAppState.cursorButton = "down";
stubLocalAppState.name = "local app state";
const restoredAppState = restore.restoreAppState( const restoredAppState = restore.restoreAppState(
stubImportedAppState, stubImportedAppState,
stubLocalAppState, stubLocalAppState,
); );
expect(restoredAppState.cursorButton).toBe(stubLocalAppState.cursorButton); expect(restoredAppState.cursorButton).toBe(stubLocalAppState.cursorButton);
expect(restoredAppState.name).toBe(stubLocalAppState.name);
}); });
it("should return imported data when local app state is null", () => { it("should return imported data when local app state is null", () => {
const stubImportedAppState = getDefaultAppState(); const stubImportedAppState = getDefaultAppState();
stubImportedAppState.cursorButton = "down"; stubImportedAppState.cursorButton = "down";
stubImportedAppState.name = "imported app state";
const restoredAppState = restore.restoreAppState( const restoredAppState = restore.restoreAppState(
stubImportedAppState, stubImportedAppState,
null, null,
); );
expect(restoredAppState.cursorButton).toBe("up"); expect(restoredAppState.cursorButton).toBe("up");
expect(restoredAppState.name).toBe(stubImportedAppState.name);
}); });
it("should return local app state when imported data state is null", () => { it("should return local app state when imported data state is null", () => {
const stubLocalAppState = getDefaultAppState(); const stubLocalAppState = getDefaultAppState();
stubLocalAppState.cursorButton = "down"; stubLocalAppState.cursorButton = "down";
stubLocalAppState.name = "local app state";
const restoredAppState = restore.restoreAppState(null, stubLocalAppState); const restoredAppState = restore.restoreAppState(null, stubLocalAppState);
expect(restoredAppState.cursorButton).toBe(stubLocalAppState.cursorButton); expect(restoredAppState.cursorButton).toBe(stubLocalAppState.cursorButton);
expect(restoredAppState.name).toBe(stubLocalAppState.name);
}); });
it("should return default app state when imported data state and local app state are undefined", () => { it("should return default app state when imported data state and local app state are undefined", () => {
@@ -483,11 +492,13 @@ describe("restore", () => {
it("when imported data state is null it should return the local app state property", () => { it("when imported data state is null it should return the local app state property", () => {
const stubLocalAppState = getDefaultAppState(); const stubLocalAppState = getDefaultAppState();
stubLocalAppState.cursorButton = "down"; stubLocalAppState.cursorButton = "down";
stubLocalAppState.name = "local app state";
const restoredData = restore.restore(null, stubLocalAppState, null); const restoredData = restore.restore(null, stubLocalAppState, null);
expect(restoredData.appState.cursorButton).toBe( expect(restoredData.appState.cursorButton).toBe(
stubLocalAppState.cursorButton, stubLocalAppState.cursorButton,
); );
expect(restoredData.appState.name).toBe(stubLocalAppState.name);
}); });
it("when imported data state has elements", () => { it("when imported data state has elements", () => {
@@ -511,12 +522,14 @@ describe("restore", () => {
it("when local app state is null but imported app state is supplied", () => { it("when local app state is null but imported app state is supplied", () => {
const stubImportedAppState = getDefaultAppState(); const stubImportedAppState = getDefaultAppState();
stubImportedAppState.cursorButton = "down"; stubImportedAppState.cursorButton = "down";
stubImportedAppState.name = "imported app state";
const importedDataState = {} as ImportedDataState; const importedDataState = {} as ImportedDataState;
importedDataState.appState = stubImportedAppState; importedDataState.appState = stubImportedAppState;
const restoredData = restore.restore(importedDataState, null, null); const restoredData = restore.restore(importedDataState, null, null);
expect(restoredData.appState.cursorButton).toBe("up"); expect(restoredData.appState.cursorButton).toBe("up");
expect(restoredData.appState.name).toBe(stubImportedAppState.name);
}); });
it("bump versions of local duplicate elements when supplied", () => { it("bump versions of local duplicate elements when supplied", () => {
+1
View File
@@ -247,6 +247,7 @@ export interface AppState {
scrollY: number; scrollY: number;
cursorButton: "up" | "down"; cursorButton: "up" | "down";
scrolledOutside: boolean; scrolledOutside: boolean;
name: string;
isResizing: boolean; isResizing: boolean;
isRotating: boolean; isRotating: boolean;
zoom: Zoom; zoom: Zoom;