mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
fix
This commit is contained in:
parent
7087db42c0
commit
59e8bf498d
9 changed files with 109 additions and 65 deletions
|
@ -129,6 +129,7 @@ import {
|
||||||
import { LinearElementEditor } from "../element/linearElementEditor";
|
import { LinearElementEditor } from "../element/linearElementEditor";
|
||||||
import { mutateElement, newElementWith } from "../element/mutateElement";
|
import { mutateElement, newElementWith } from "../element/mutateElement";
|
||||||
import {
|
import {
|
||||||
|
convertToExcalidrawElements,
|
||||||
deepCopyElement,
|
deepCopyElement,
|
||||||
duplicateElements,
|
duplicateElements,
|
||||||
newFreeDrawElement,
|
newFreeDrawElement,
|
||||||
|
@ -2001,7 +2002,9 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sceneData.elements) {
|
if (sceneData.elements) {
|
||||||
this.scene.replaceAllElements(sceneData.elements);
|
this.scene.replaceAllElements(
|
||||||
|
convertToExcalidrawElements(sceneData.elements),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sceneData.collaborators) {
|
if (sceneData.collaborators) {
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { FileSystemHandle, nativeFileSystemSupported } from "./filesystem";
|
||||||
import { isValidExcalidrawData, isValidLibrary } from "./json";
|
import { isValidExcalidrawData, isValidLibrary } from "./json";
|
||||||
import { restore, restoreLibraryItems } from "./restore";
|
import { restore, restoreLibraryItems } from "./restore";
|
||||||
import { ImportedLibraryData } from "./types";
|
import { ImportedLibraryData } from "./types";
|
||||||
|
import { convertToExcalidrawElements } from "../element/newElement";
|
||||||
|
|
||||||
const parseFileContents = async (blob: Blob | File) => {
|
const parseFileContents = async (blob: Blob | File) => {
|
||||||
let contents: string;
|
let contents: string;
|
||||||
|
@ -138,14 +139,16 @@ export const loadSceneOrLibraryFromBlob = async (
|
||||||
type: MIME_TYPES.excalidraw,
|
type: MIME_TYPES.excalidraw,
|
||||||
data: restore(
|
data: restore(
|
||||||
{
|
{
|
||||||
elements: clearElementsForExport(data.elements || []),
|
elements: clearElementsForExport(
|
||||||
|
convertToExcalidrawElements(data.elements || []),
|
||||||
|
),
|
||||||
appState: {
|
appState: {
|
||||||
theme: localAppState?.theme,
|
theme: localAppState?.theme,
|
||||||
fileHandle: fileHandle || blob.handle || null,
|
fileHandle: fileHandle || blob.handle || null,
|
||||||
...cleanAppStateForExport(data.appState || {}),
|
...cleanAppStateForExport(data.appState || {}),
|
||||||
...(localAppState
|
...(localAppState
|
||||||
? calculateScrollCenter(
|
? calculateScrollCenter(
|
||||||
data.elements || [],
|
convertToExcalidrawElements(data.elements || []),
|
||||||
localAppState,
|
localAppState,
|
||||||
null,
|
null,
|
||||||
)
|
)
|
||||||
|
|
|
@ -40,7 +40,7 @@ import {
|
||||||
getDefaultLineHeight,
|
getDefaultLineHeight,
|
||||||
measureBaseline,
|
measureBaseline,
|
||||||
} from "../element/textElement";
|
} from "../element/textElement";
|
||||||
import { updateElementChildren } from "../element/newElement";
|
import { convertToExcalidrawElements } from "../element/newElement";
|
||||||
|
|
||||||
type RestoredAppState = Omit<
|
type RestoredAppState = Omit<
|
||||||
AppState,
|
AppState,
|
||||||
|
@ -373,9 +373,10 @@ export const restoreElements = (
|
||||||
): ExcalidrawElement[] => {
|
): ExcalidrawElement[] => {
|
||||||
// used to detect duplicate top-level element ids
|
// used to detect duplicate top-level element ids
|
||||||
const existingIds = new Set<string>();
|
const existingIds = new Set<string>();
|
||||||
|
const excalidrawElements = convertToExcalidrawElements(elements);
|
||||||
const localElementsMap = localElements ? arrayToMap(localElements) : null;
|
const localElementsMap = localElements ? arrayToMap(localElements) : null;
|
||||||
const restoredElements = (elements || []).reduce((elements, element) => {
|
const restoredElements = (excalidrawElements || []).reduce(
|
||||||
|
(elements, element) => {
|
||||||
// filtering out selection, which is legacy, no longer kept in elements,
|
// filtering out selection, which is legacy, no longer kept in elements,
|
||||||
// and causing issues if retained
|
// and causing issues if retained
|
||||||
if (element.type !== "selection" && !isInvisiblySmallElement(element)) {
|
if (element.type !== "selection" && !isInvisiblySmallElement(element)) {
|
||||||
|
@ -386,28 +387,23 @@ export const restoreElements = (
|
||||||
if (migratedElement) {
|
if (migratedElement) {
|
||||||
const localElement = localElementsMap?.get(element.id);
|
const localElement = localElementsMap?.get(element.id);
|
||||||
if (localElement && localElement.version > migratedElement.version) {
|
if (localElement && localElement.version > migratedElement.version) {
|
||||||
migratedElement = bumpVersion(migratedElement, localElement.version);
|
migratedElement = bumpVersion(
|
||||||
|
migratedElement,
|
||||||
|
localElement.version,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (existingIds.has(migratedElement.id)) {
|
if (existingIds.has(migratedElement.id)) {
|
||||||
migratedElement = { ...migratedElement, id: randomId() };
|
migratedElement = { ...migratedElement, id: randomId() };
|
||||||
}
|
}
|
||||||
existingIds.add(migratedElement.id);
|
existingIds.add(migratedElement.id);
|
||||||
//@ts-ignore
|
|
||||||
if (element.children?.length) {
|
|
||||||
//@ts-ignore
|
|
||||||
const newElements = updateElementChildren(element);
|
|
||||||
if (newElements) {
|
|
||||||
elements.push(...newElements);
|
|
||||||
} else {
|
|
||||||
elements.push(migratedElement);
|
elements.push(migratedElement);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
elements.push(migratedElement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return elements;
|
return elements;
|
||||||
}, [] as ExcalidrawElement[]);
|
},
|
||||||
|
[] as ExcalidrawElement[],
|
||||||
|
);
|
||||||
|
|
||||||
if (!opts?.repairBindings) {
|
if (!opts?.repairBindings) {
|
||||||
return restoredElements;
|
return restoredElements;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { ExcalidrawElement } from "../element/types";
|
import { ExcalidrawElement, ExcalidrawGenericElement } from "../element/types";
|
||||||
import {
|
import {
|
||||||
AppState,
|
AppState,
|
||||||
BinaryFiles,
|
BinaryFiles,
|
||||||
|
@ -7,6 +7,8 @@ import {
|
||||||
} from "../types";
|
} from "../types";
|
||||||
import type { cleanAppStateForExport } from "../appState";
|
import type { cleanAppStateForExport } from "../appState";
|
||||||
import { VERSIONS } from "../constants";
|
import { VERSIONS } from "../constants";
|
||||||
|
import { MarkOptional } from "../utility-types";
|
||||||
|
import { ElementConstructorOpts } from "../element/newElement";
|
||||||
|
|
||||||
export interface ExportedDataState {
|
export interface ExportedDataState {
|
||||||
type: string;
|
type: string;
|
||||||
|
@ -35,7 +37,28 @@ export interface ImportedDataState {
|
||||||
type?: string;
|
type?: string;
|
||||||
version?: number;
|
version?: number;
|
||||||
source?: string;
|
source?: string;
|
||||||
elements?: readonly ExcalidrawElement[] | null;
|
elements?:
|
||||||
|
| readonly (
|
||||||
|
| (ExcalidrawElement & {
|
||||||
|
children?: [
|
||||||
|
{ text: string } & MarkOptional<
|
||||||
|
ElementConstructorOpts,
|
||||||
|
"x" | "y"
|
||||||
|
>,
|
||||||
|
];
|
||||||
|
})
|
||||||
|
| {
|
||||||
|
type: Exclude<ExcalidrawGenericElement["type"], "selection">;
|
||||||
|
children?: [
|
||||||
|
{ text: string } & MarkOptional<
|
||||||
|
ElementConstructorOpts,
|
||||||
|
"x" | "y"
|
||||||
|
>,
|
||||||
|
] &
|
||||||
|
MarkOptional<ElementConstructorOpts, "x" | "y">;
|
||||||
|
}
|
||||||
|
)[]
|
||||||
|
| null;
|
||||||
appState?: Readonly<
|
appState?: Readonly<
|
||||||
Partial<
|
Partial<
|
||||||
AppState & {
|
AppState & {
|
||||||
|
|
|
@ -12,7 +12,6 @@ import {
|
||||||
ExcalidrawFreeDrawElement,
|
ExcalidrawFreeDrawElement,
|
||||||
FontFamilyValues,
|
FontFamilyValues,
|
||||||
ExcalidrawTextContainer,
|
ExcalidrawTextContainer,
|
||||||
NonDeletedExcalidrawElement,
|
|
||||||
} from "../element/types";
|
} from "../element/types";
|
||||||
import {
|
import {
|
||||||
arrayToMap,
|
arrayToMap,
|
||||||
|
@ -47,9 +46,9 @@ import {
|
||||||
DEFAULT_VERTICAL_ALIGN,
|
DEFAULT_VERTICAL_ALIGN,
|
||||||
VERTICAL_ALIGN,
|
VERTICAL_ALIGN,
|
||||||
} from "../constants";
|
} from "../constants";
|
||||||
import { isArrowElement, isTextElement } from "./typeChecks";
|
import { isArrowElement } from "./typeChecks";
|
||||||
import { MarkOptional, Merge, Mutable } from "../utility-types";
|
import { MarkOptional, Merge, Mutable } from "../utility-types";
|
||||||
import { Children } from "react";
|
import { ImportedDataState } from "../data/types";
|
||||||
|
|
||||||
export type ElementConstructorOpts = MarkOptional<
|
export type ElementConstructorOpts = MarkOptional<
|
||||||
Omit<ExcalidrawGenericElement, "id" | "type" | "isDeleted" | "updated">,
|
Omit<ExcalidrawGenericElement, "id" | "type" | "isDeleted" | "updated">,
|
||||||
|
@ -649,21 +648,33 @@ export const duplicateElements = (
|
||||||
return clonedElements;
|
return clonedElements;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateElementChildren = (element: {
|
export const convertToExcalidrawElements = (
|
||||||
type: ExcalidrawGenericElement["type"];
|
elements: ImportedDataState["elements"],
|
||||||
children?: [
|
) => {
|
||||||
{ text: string } & MarkOptional<ElementConstructorOpts, "x" | "y">,
|
const res: ExcalidrawElement[] = [];
|
||||||
] &
|
if (!elements) {
|
||||||
MarkOptional<ElementConstructorOpts, "x" | "y">;
|
return [];
|
||||||
}) => {
|
|
||||||
const textElement = element.children?.find(
|
|
||||||
(
|
|
||||||
child: { text: string } & MarkOptional<ElementConstructorOpts, "x" | "y">,
|
|
||||||
) => child.text !== null,
|
|
||||||
);
|
|
||||||
if (isValidTextContainer(element) && textElement) {
|
|
||||||
const elements = bindTextToContainer(element, textElement);
|
|
||||||
return elements;
|
|
||||||
}
|
}
|
||||||
return null;
|
elements.forEach((element) => {
|
||||||
|
if (!element) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const textElement = element.children?.find((child) => child.text !== null);
|
||||||
|
if (
|
||||||
|
isValidTextContainer(element) &&
|
||||||
|
textElement &&
|
||||||
|
(element.type === "rectangle" ||
|
||||||
|
element.type === "ellipse" ||
|
||||||
|
element.type === "diamond")
|
||||||
|
) {
|
||||||
|
const elements = bindTextToContainer(element, textElement);
|
||||||
|
res.push(...elements);
|
||||||
|
} else {
|
||||||
|
delete element.children;
|
||||||
|
//@ts-ignore
|
||||||
|
res.push(element);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return res;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
import { getFontString, arrayToMap, isTestEnv } from "../utils";
|
import { getFontString, arrayToMap, isTestEnv } from "../utils";
|
||||||
import {
|
import {
|
||||||
|
ExcalidrawDiamondElement,
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
|
ExcalidrawEllipseElement,
|
||||||
ExcalidrawGenericElement,
|
ExcalidrawGenericElement,
|
||||||
|
ExcalidrawRectangleElement,
|
||||||
ExcalidrawTextContainer,
|
ExcalidrawTextContainer,
|
||||||
ExcalidrawTextElement,
|
ExcalidrawTextElement,
|
||||||
ExcalidrawTextElementWithContainer,
|
ExcalidrawTextElementWithContainer,
|
||||||
|
@ -982,8 +985,9 @@ export const getDefaultLineHeight = (fontFamily: FontFamilyValues) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const bindTextToContainer = (
|
export const bindTextToContainer = (
|
||||||
containerProps: {
|
containerProps:
|
||||||
type: ExcalidrawGenericElement["type"];
|
| {
|
||||||
|
type: Exclude<ExcalidrawGenericElement["type"], "selection">;
|
||||||
} & MarkOptional<ElementConstructorOpts, "x" | "y">,
|
} & MarkOptional<ElementConstructorOpts, "x" | "y">,
|
||||||
textProps: { text: string } & MarkOptional<ElementConstructorOpts, "x" | "y">,
|
textProps: { text: string } & MarkOptional<ElementConstructorOpts, "x" | "y">,
|
||||||
) => {
|
) => {
|
||||||
|
@ -1000,6 +1004,7 @@ export const bindTextToContainer = (
|
||||||
textAlign: TEXT_ALIGN.CENTER,
|
textAlign: TEXT_ALIGN.CENTER,
|
||||||
verticalAlign: VERTICAL_ALIGN.MIDDLE,
|
verticalAlign: VERTICAL_ALIGN.MIDDLE,
|
||||||
});
|
});
|
||||||
|
|
||||||
mutateElement(container, {
|
mutateElement(container, {
|
||||||
boundElements: (container.boundElements || []).concat({
|
boundElements: (container.boundElements || []).concat({
|
||||||
type: "text",
|
type: "text",
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {
|
||||||
import { serializeAsJSON } from "../../data/json";
|
import { serializeAsJSON } from "../../data/json";
|
||||||
import { restore } from "../../data/restore";
|
import { restore } from "../../data/restore";
|
||||||
import { ImportedDataState } from "../../data/types";
|
import { ImportedDataState } from "../../data/types";
|
||||||
|
import { convertToExcalidrawElements } from "../../element/newElement";
|
||||||
import { isInvisiblySmallElement } from "../../element/sizeHelpers";
|
import { isInvisiblySmallElement } from "../../element/sizeHelpers";
|
||||||
import { isInitializedImageElement } from "../../element/typeChecks";
|
import { isInitializedImageElement } from "../../element/typeChecks";
|
||||||
import { ExcalidrawElement, FileId } from "../../element/types";
|
import { ExcalidrawElement, FileId } from "../../element/types";
|
||||||
|
@ -262,7 +263,7 @@ export const loadScene = async (
|
||||||
data = restore(
|
data = restore(
|
||||||
await importFromBackend(id, privateKey),
|
await importFromBackend(id, privateKey),
|
||||||
localDataState?.appState,
|
localDataState?.appState,
|
||||||
localDataState?.elements,
|
convertToExcalidrawElements(localDataState?.elements),
|
||||||
{ repairBindings: true, refreshDimensions: false },
|
{ repairBindings: true, refreshDimensions: false },
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -86,6 +86,7 @@ import { appJotaiStore } from "./app-jotai";
|
||||||
|
|
||||||
import "./index.scss";
|
import "./index.scss";
|
||||||
import { ResolutionType } from "../utility-types";
|
import { ResolutionType } from "../utility-types";
|
||||||
|
import { convertToExcalidrawElements } from "../element/newElement";
|
||||||
|
|
||||||
polyfill();
|
polyfill();
|
||||||
|
|
||||||
|
@ -206,7 +207,7 @@ const initializeScene = async (opts: {
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
},
|
},
|
||||||
elements: reconcileElements(
|
elements: reconcileElements(
|
||||||
scene?.elements || [],
|
convertToExcalidrawElements(scene?.elements || []),
|
||||||
excalidrawAPI.getSceneElementsIncludingDeleted(),
|
excalidrawAPI.getSceneElementsIncludingDeleted(),
|
||||||
excalidrawAPI.getAppState(),
|
excalidrawAPI.getAppState(),
|
||||||
),
|
),
|
||||||
|
@ -286,7 +287,7 @@ const ExcalidrawWrapper = () => {
|
||||||
if (data.scene.elements) {
|
if (data.scene.elements) {
|
||||||
collabAPI
|
collabAPI
|
||||||
.fetchImageFilesFromFirebase({
|
.fetchImageFilesFromFirebase({
|
||||||
elements: data.scene.elements,
|
elements: convertToExcalidrawElements(data.scene.elements),
|
||||||
forceFetchFiles: true,
|
forceFetchFiles: true,
|
||||||
})
|
})
|
||||||
.then(({ loadedFiles, erroredFiles }) => {
|
.then(({ loadedFiles, erroredFiles }) => {
|
||||||
|
@ -299,8 +300,9 @@ const ExcalidrawWrapper = () => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
const sceneElements = convertToExcalidrawElements(data.scene.elements);
|
||||||
const fileIds =
|
const fileIds =
|
||||||
data.scene.elements?.reduce((acc, element) => {
|
sceneElements?.reduce((acc, element) => {
|
||||||
if (isInitializedImageElement(element)) {
|
if (isInitializedImageElement(element)) {
|
||||||
return acc.concat(element.fileId);
|
return acc.concat(element.fileId);
|
||||||
}
|
}
|
||||||
|
@ -351,7 +353,6 @@ const ExcalidrawWrapper = () => {
|
||||||
elements: [
|
elements: [
|
||||||
{
|
{
|
||||||
type: "rectangle",
|
type: "rectangle",
|
||||||
//@ts-ignore
|
|
||||||
children: [{ text: "HELLO DAMMMMY" }],
|
children: [{ text: "HELLO DAMMMMY" }],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { decodePngMetadata, decodeSvgMetadata } from "../../data/image";
|
import { decodePngMetadata, decodeSvgMetadata } from "../../data/image";
|
||||||
import { ImportedDataState } from "../../data/types";
|
import { ImportedDataState } from "../../data/types";
|
||||||
|
import { convertToExcalidrawElements } from "../../element/newElement";
|
||||||
import * as utils from "../../packages/utils";
|
import * as utils from "../../packages/utils";
|
||||||
import { API } from "../helpers/api";
|
import { API } from "../helpers/api";
|
||||||
|
|
||||||
|
@ -31,7 +32,7 @@ describe("embedding scene data", () => {
|
||||||
const importedData: ImportedDataState = JSON.parse(parsedString);
|
const importedData: ImportedDataState = JSON.parse(parsedString);
|
||||||
|
|
||||||
expect(sourceElements.map((x) => x.id)).toEqual(
|
expect(sourceElements.map((x) => x.id)).toEqual(
|
||||||
importedData.elements?.map((el) => el.id),
|
convertToExcalidrawElements(importedData.elements)?.map((el) => el.id),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -60,7 +61,7 @@ describe("embedding scene data", () => {
|
||||||
const importedData: ImportedDataState = JSON.parse(parsedString);
|
const importedData: ImportedDataState = JSON.parse(parsedString);
|
||||||
|
|
||||||
expect(sourceElements.map((x) => x.id)).toEqual(
|
expect(sourceElements.map((x) => x.id)).toEqual(
|
||||||
importedData.elements?.map((el) => el.id),
|
convertToExcalidrawElements(importedData.elements)?.map((el) => el.id),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue