This commit is contained in:
Aakansha Doshi 2023-05-03 13:41:41 +05:30
parent 7087db42c0
commit 59e8bf498d
9 changed files with 109 additions and 65 deletions

View file

@ -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) {

View file

@ -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,
) )

View file

@ -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;

View file

@ -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 & {

View file

@ -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;
}; };

View file

@ -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",

View file

@ -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 {

View file

@ -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" }],
}, },
], ],

View file

@ -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),
); );
}); });
}); });