support ids, clean up code and move the api related stuff to transform.ts

This commit is contained in:
Aakansha Doshi 2023-05-24 17:12:26 +05:30
parent da8e97ad14
commit 985318e960
11 changed files with 337 additions and 250 deletions

View file

@ -130,7 +130,6 @@ import {
import { LinearElementEditor } from "../element/linearElementEditor";
import { mutateElement, newElementWith } from "../element/mutateElement";
import {
convertToExcalidrawElements,
deepCopyElement,
duplicateElements,
newFreeDrawElement,
@ -305,6 +304,7 @@ import { jotaiStore } from "../jotai";
import { activeConfirmDialogAtom } from "./ActiveConfirmDialog";
import { actionWrapTextInContainer } from "../actions/actionBoundText";
import BraveMeasureTextError from "./BraveMeasureTextError";
import { convertToExcalidrawElements } from "../data/transform";
const AppContext = React.createContext<AppClassProperties>(null!);
const AppPropsContext = React.createContext<AppProps>(null!);
@ -2044,6 +2044,8 @@ class App extends React.Component<AppProps, AppState> {
}
if (sceneData.elements) {
console.log("HEYYYYYYYY", sceneData.elements);
this.scene.replaceAllElements(
convertToExcalidrawElements(sceneData.elements),
);

View file

@ -13,7 +13,7 @@ import { FileSystemHandle, nativeFileSystemSupported } from "./filesystem";
import { isValidExcalidrawData, isValidLibrary } from "./json";
import { restore, restoreLibraryItems } from "./restore";
import { ImportedLibraryData } from "./types";
import { convertToExcalidrawElements } from "../element/newElement";
import { convertToExcalidrawElements } from "../data/transform";
const parseFileContents = async (blob: Blob | File) => {
let contents: string;

View file

@ -41,7 +41,7 @@ import {
getDefaultLineHeight,
measureBaseline,
} from "../element/textElement";
import { convertToExcalidrawElements } from "../element/newElement";
import { convertToExcalidrawElements } from "../data/transform";
type RestoredAppState = Omit<
AppState,

310
src/data/transform.ts Normal file
View file

@ -0,0 +1,310 @@
import { TEXT_ALIGN, VERTICAL_ALIGN } from "../constants";
import {
newElement,
newLinearElement,
redrawTextBoundingBox,
} from "../element";
import { bindLinearElement } from "../element/binding";
import { mutateElement } from "../element/mutateElement";
import { ElementConstructorOpts, newTextElement } from "../element/newElement";
import { VALID_CONTAINER_TYPES } from "../element/textElement";
import {
ExcalidrawBindableElement,
ExcalidrawElement,
ExcalidrawGenericElement,
ExcalidrawLinearElement,
ExcalidrawTextElement,
FontFamilyValues,
TextAlign,
VerticalAlign,
} from "../element/types";
import { MarkOptional } from "../utility-types";
import { ImportedDataState } from "./types";
export const ELEMENTS_SUPPORTING_PROGRAMMATIC_API = [
"rectangle",
"ellipse",
"diamond",
"text",
"arrow",
"line",
];
const bindTextToContainer = (
containerProps:
| {
type:
| Exclude<ExcalidrawGenericElement["type"], "selection">
| ExcalidrawLinearElement["type"];
} & MarkOptional<ElementConstructorOpts, "x" | "y">,
textProps: { text: string } & MarkOptional<ElementConstructorOpts, "x" | "y">,
) => {
let container;
if (containerProps.type === "arrow") {
container = newLinearElement({
width: containerProps.width || 300,
height: containerProps.height || 24,
//@ts-ignore
type: containerProps.type,
//@ts-ignore,
endArrowhead: containerProps.type === "arrow" ? "arrow" : null,
//@ts-ignore
points: [
[0, 0],
[300, 0],
],
...containerProps,
});
} else {
//@ts-ignore
container = newElement({
...containerProps,
});
}
const textElement: ExcalidrawTextElement = newTextElement({
x: 0,
y: 0,
textAlign: TEXT_ALIGN.CENTER,
verticalAlign: VERTICAL_ALIGN.MIDDLE,
...textProps,
containerId: container.id,
});
mutateElement(container, {
boundElements: (container.boundElements || []).concat({
type: "text",
id: textElement.id,
}),
});
redrawTextBoundingBox(textElement, container);
return [container, textElement];
};
const bindLinearElementToElement = (
linearElement: {
type: ExcalidrawLinearElement["type"];
x: number;
y: number;
label?: {
text: string;
fontSize?: number;
fontFamily?: FontFamilyValues;
textAlign?: TextAlign;
verticalAlign?: VerticalAlign;
} & MarkOptional<ElementConstructorOpts, "x" | "y">;
start?: {
type: Exclude<
ExcalidrawBindableElement["type"],
"image" | "selection" | "text"
>;
id?: ExcalidrawGenericElement["id"];
} & MarkOptional<ElementConstructorOpts, "x" | "y">;
end?: {
type: ExcalidrawGenericElement["type"];
id?: ExcalidrawGenericElement["id"];
} & MarkOptional<ElementConstructorOpts, "x" | "y">;
} & Partial<ExcalidrawLinearElement>,
elements: ImportedDataState["elements"],
): {
linearElement: ExcalidrawLinearElement;
startBoundElement?: ExcalidrawElement;
endBoundElement?: ExcalidrawElement;
} => {
const {
start,
end,
type,
endArrowhead = linearElement.type === "arrow" ? "arrow" : null,
...rest
} = linearElement;
const excliadrawLinearElement = newLinearElement({
type,
width: 200,
height: 24,
points: [
[0, 0],
[200, 0],
],
endArrowhead,
...rest,
});
if (!elements || !elements.length) {
return { linearElement: excliadrawLinearElement };
}
let startBoundElement;
let endBoundElement;
mutateElement(excliadrawLinearElement, {
startBinding: linearElement?.startBinding || null,
endBinding: linearElement.endBinding || null,
});
if (start) {
const width = start?.width ?? 100;
const height = start?.height ?? 100;
const existingElement = start.id
? elements.find((ele) => ele?.id === start.id)
: undefined;
startBoundElement = newElement({
x: start.x || excliadrawLinearElement.x - width,
y: start.y || excliadrawLinearElement.y - height / 2,
width,
height,
...existingElement,
...start,
});
bindLinearElement(
excliadrawLinearElement,
startBoundElement as ExcalidrawBindableElement,
"start",
);
}
if (end) {
const height = end?.height ?? 100;
const existingElement = end.id
? elements.find((ele) => ele?.id === end.id)
: undefined;
endBoundElement = newElement({
x: end.x || excliadrawLinearElement.x + excliadrawLinearElement.width,
y: end.y || excliadrawLinearElement.y - height / 2,
width: end?.width ?? 100,
height,
...existingElement,
...end,
}) as ExcalidrawBindableElement;
bindLinearElement(
excliadrawLinearElement,
endBoundElement as ExcalidrawBindableElement,
"end",
);
}
return {
linearElement: excliadrawLinearElement,
//@ts-ignore
startBoundElement,
//@ts-ignore
endBoundElement,
};
};
const excalidrawElements = (() => {
const res: ExcalidrawElement[] = [];
const elementMap = new Map<string, number>();
const push = (ele?: ExcalidrawElement) => {
if (!ele) {
return;
}
const index = elementMap.get(ele.id);
if (index !== undefined && index >= 0) {
res[index] = ele;
} else {
res.push(ele);
const index = res.length - 1;
elementMap.set(ele.id, index);
}
};
const clear = () => {
res.length = 0;
};
const get = () => {
return res;
};
return {
push,
clear,
get,
};
})();
export const convertToExcalidrawElements = (
elements: ImportedDataState["elements"],
): ExcalidrawElement[] => {
excalidrawElements.clear();
if (!elements) {
return [];
}
elements.forEach((element) => {
if (!element) {
return;
}
if (!ELEMENTS_SUPPORTING_PROGRAMMATIC_API.includes(element.type)) {
excalidrawElements.push(element as ExcalidrawElement);
return;
}
//@ts-ignore
if (VALID_CONTAINER_TYPES.has(element.type) && element?.label?.text) {
//@ts-ignore
let [container, text] = bindTextToContainer(element, element.label);
excalidrawElements.push(container);
excalidrawElements.push(text);
if (container.type === "arrow") {
const { linearElement, startBoundElement, endBoundElement } =
bindLinearElementToElement(
{
...container,
//@ts-ignore
start: element?.start,
//@ts-ignore
end: element?.end,
},
elements,
);
container = linearElement;
excalidrawElements.push(linearElement);
excalidrawElements.push(startBoundElement);
excalidrawElements.push(endBoundElement);
}
} else {
let excalidrawElement;
if (element.type === "text") {
excalidrawElement = {
...element,
} as ExcalidrawTextElement;
excalidrawElements.push(excalidrawElement);
} else if (element.type === "arrow" || element.type === "line") {
const { linearElement, startBoundElement, endBoundElement } =
//@ts-ignore
bindLinearElementToElement(element, elements);
excalidrawElements.push(linearElement);
excalidrawElements.push(startBoundElement);
excalidrawElements.push(endBoundElement);
//@ts-ignore
if (startBoundElement && !element.start.id) {
//@ts-ignore
excalidrawElements.push(startBoundElement);
}
//@ts-ignore
if (endBoundElement && !element.end.id) {
//@ts-ignore
excalidrawElements.push(endBoundElement);
}
} else {
excalidrawElement = {
...element,
width:
element?.width ||
(ELEMENTS_SUPPORTING_PROGRAMMATIC_API.includes(element.type)
? 100
: 0),
height:
element?.height ||
(ELEMENTS_SUPPORTING_PROGRAMMATIC_API.includes(element.type)
? 100
: 0),
} as ExcalidrawGenericElement;
excalidrawElements.push(excalidrawElement);
}
}
});
return excalidrawElements.get();
};

View file

@ -1,7 +1,8 @@
import {
Arrowhead,
ExcalidrawBindableElement,
ExcalidrawElement,
ExcalidrawGenericElement,
ExcalidrawLinearElement,
FontFamilyValues,
TextAlign,
VerticalAlign,
@ -15,10 +16,8 @@ import {
import type { cleanAppStateForExport } from "../appState";
import { VERSIONS } from "../constants";
import { MarkOptional } from "../utility-types";
import {
ElementConstructorOpts,
ELEMENTS_SUPPORTING_PROGRAMMATIC_API,
} from "../element/newElement";
import { ElementConstructorOpts } from "../element/newElement";
import { ELEMENTS_SUPPORTING_PROGRAMMATIC_API } from "./transform";
export interface ExportedDataState {
type: string;
@ -74,9 +73,12 @@ export interface ImportedDataState {
| ({
type: "text";
text: string;
id?: ExcalidrawBindableElement["id"];
} & ElementConstructorOpts)
| ({
type: "arrow";
type: ExcalidrawLinearElement["type"];
x: number;
y: number;
label?: {
text: string;
fontSize?: number;
@ -85,13 +87,17 @@ export interface ImportedDataState {
verticalAlign?: VerticalAlign;
} & MarkOptional<ElementConstructorOpts, "x" | "y">;
start?: {
type: ExcalidrawBindableElement["type"];
type: Exclude<
ExcalidrawBindableElement["type"],
"image" | "selection" | "text"
>;
id?: ExcalidrawGenericElement["id"];
} & MarkOptional<ElementConstructorOpts, "x" | "y">;
end?: {
type: ExcalidrawBindableElement["type"];
type: ExcalidrawGenericElement["type"];
id?: ExcalidrawGenericElement["id"];
} & MarkOptional<ElementConstructorOpts, "x" | "y">;
endArrowhead?: Arrowhead | null;
} & ElementConstructorOpts)
} & Partial<ExcalidrawLinearElement>)
)[]
| null;
appState?: Readonly<

View file

@ -12,7 +12,6 @@ import {
ExcalidrawFreeDrawElement,
FontFamilyValues,
ExcalidrawTextContainer,
ExcalidrawBindableElement,
} from "../element/types";
import {
arrayToMap,
@ -36,8 +35,6 @@ import {
wrapText,
getBoundTextMaxWidth,
getDefaultLineHeight,
bindTextToContainer,
VALID_CONTAINER_TYPES,
} from "./textElement";
import {
DEFAULT_ELEMENT_PROPS,
@ -49,17 +46,7 @@ import {
} from "../constants";
import { isArrowElement } from "./typeChecks";
import { MarkOptional, Merge, Mutable } from "../utility-types";
import { ImportedDataState } from "../data/types";
import { bindLinearElement } from "./binding";
export const ELEMENTS_SUPPORTING_PROGRAMMATIC_API = [
"rectangle",
"ellipse",
"diamond",
"text",
"arrow",
"line",
];
export type ElementConstructorOpts = MarkOptional<
Omit<ExcalidrawGenericElement, "id" | "type" | "isDeleted" | "updated">,
| "width"
@ -656,169 +643,3 @@ export const duplicateElements = (
return clonedElements;
};
export const convertToExcalidrawElements = (
elements: ImportedDataState["elements"],
) => {
const res: ExcalidrawElement[] = [];
if (!elements) {
return [];
}
elements.forEach((element) => {
if (!element) {
return;
}
if (!ELEMENTS_SUPPORTING_PROGRAMMATIC_API.includes(element.type)) {
res.push(element as ExcalidrawElement);
return;
}
let startBoundElement;
let endBoundElement;
//@ts-ignore
if (VALID_CONTAINER_TYPES.has(element.type) && element?.label?.text) {
//@ts-ignore
const elements = bindTextToContainer(element, element.label);
const [container, text] = elements;
if (container.type === "arrow") {
//@ts-ignore
const { start, end } = element;
mutateElement(container, {
//@ts-ignore
startBinding: element?.startBinding || null,
//@ts-ignore
endBinding: element.endBinding || null,
});
if (start) {
const width = start?.width ?? 100;
const height = start?.height ?? 100;
startBoundElement = newElement({
x: start.x || container.x - width,
y: start.y || container.y - height / 2,
width,
height,
...start,
}) as ExcalidrawBindableElement;
bindLinearElement(
container as ExcalidrawLinearElement,
startBoundElement,
"start",
);
}
if (end) {
const height = end?.height ?? 100;
endBoundElement = newElement({
x: end.x || container.x + container.width,
y: end.y || container.y - height / 2,
width: end?.width ?? 100,
height,
...end,
}) as ExcalidrawBindableElement;
bindLinearElement(
container as ExcalidrawLinearElement,
endBoundElement,
"end",
);
}
}
res.push(container);
res.push(text);
if (startBoundElement) {
res.push(startBoundElement);
}
if (endBoundElement) {
res.push(endBoundElement);
}
} else {
let excalidrawElement;
if (element.type === "text") {
excalidrawElement = {
...element,
} as ExcalidrawTextElement;
res.push(excalidrawElement);
} else if (element.type === "arrow" || element.type === "line") {
const {
//@ts-ignore
start,
//@ts-ignore
end,
type,
//@ts-ignore
endArrowhead = element.type === "arrow" ? "arrow" : null,
...rest
} = element;
excalidrawElement = newLinearElement({
type,
width: 200,
height: 24,
points: [
[0, 0],
[200, 0],
],
endArrowhead,
...rest,
});
mutateElement(excalidrawElement, {
//@ts-ignore
startBinding: element?.startBinding || null,
//@ts-ignore
endBinding: element.endBinding || null,
});
let startBoundElement;
let endBoundElement;
if (start) {
const width = start?.width ?? 100;
const height = start?.height ?? 100;
startBoundElement = newElement({
x: start.x || excalidrawElement.x - width,
y: start.y || excalidrawElement.y - height / 2,
width,
height,
...start,
}) as ExcalidrawBindableElement;
bindLinearElement(excalidrawElement, startBoundElement, "start");
}
if (end) {
const height = end?.height ?? 100;
endBoundElement = newElement({
x: end.x || excalidrawElement.x + excalidrawElement.width,
y: end.y || excalidrawElement.y - height / 2,
width: end?.width ?? 100,
height,
...end,
}) as ExcalidrawBindableElement;
bindLinearElement(excalidrawElement, endBoundElement, "end");
}
res.push(excalidrawElement);
if (startBoundElement) {
res.push(startBoundElement);
}
if (endBoundElement) {
res.push(endBoundElement);
}
} else {
excalidrawElement = {
...element,
width:
element?.width ||
(ELEMENTS_SUPPORTING_PROGRAMMATIC_API.includes(element.type)
? 100
: 0),
height:
element?.height ||
(ELEMENTS_SUPPORTING_PROGRAMMATIC_API.includes(element.type)
? 100
: 0),
} as ExcalidrawGenericElement;
res.push(excalidrawElement);
}
}
});
return res;
};

View file

@ -980,55 +980,3 @@ export const getDefaultLineHeight = (fontFamily: FontFamilyValues) => {
}
return DEFAULT_LINE_HEIGHT[DEFAULT_FONT_FAMILY];
};
export const bindTextToContainer = (
containerProps:
| {
type:
| Exclude<ExcalidrawGenericElement["type"], "selection">
| ExcalidrawLinearElement["type"];
} & MarkOptional<ElementConstructorOpts, "x" | "y">,
textProps: { text: string } & MarkOptional<ElementConstructorOpts, "x" | "y">,
) => {
let container;
if (containerProps.type === "arrow") {
container = newLinearElement({
width: containerProps.width || 300,
height: containerProps.height || 24,
//@ts-ignore
type: containerProps.type,
//@ts-ignore,
endArrowhead: containerProps.type === "arrow" ? "arrow" : null,
//@ts-ignore
points: [
[0, 0],
[300, 0],
],
...containerProps,
});
} else {
//@ts-ignore
container = newElement({
...containerProps,
});
}
const textElement: ExcalidrawTextElement = newTextElement({
x: 0,
y: 0,
textAlign: TEXT_ALIGN.CENTER,
verticalAlign: VERTICAL_ALIGN.MIDDLE,
...textProps,
containerId: container.id,
});
mutateElement(container, {
boundElements: (container.boundElements || []).concat({
type: "text",
id: textElement.id,
}),
});
redrawTextBoundingBox(textElement, container);
return [container, textElement];
};

View file

@ -7,7 +7,7 @@ import {
import { serializeAsJSON } from "../../data/json";
import { restore } from "../../data/restore";
import { ImportedDataState } from "../../data/types";
import { convertToExcalidrawElements } from "../../element/newElement";
import { convertToExcalidrawElements } from "../../data/transform";
import { isInvisiblySmallElement } from "../../element/sizeHelpers";
import { isInitializedImageElement } from "../../element/typeChecks";
import { ExcalidrawElement, FileId } from "../../element/types";

View file

@ -87,7 +87,7 @@ import { appJotaiStore } from "./app-jotai";
import "./index.scss";
import { ResolutionType } from "../utility-types";
import { convertToExcalidrawElements } from "../element/newElement";
import { convertToExcalidrawElements } from "../data/transform";
polyfill();

View file

@ -207,9 +207,9 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
{
type: "arrow",
x: -160,
y: 300,
start: { type: "rectangle", width: 300, height: 300 },
x: 300,
y: 150,
start: { type: "rectangle", id: "rect-1" },
end: { type: "ellipse" },
},
],

View file

@ -1,6 +1,6 @@
import { decodePngMetadata, decodeSvgMetadata } from "../../data/image";
import { ImportedDataState } from "../../data/types";
import { convertToExcalidrawElements } from "../../element/newElement";
import { convertToExcalidrawElements } from "../../data/transform";
import * as utils from "../../packages/utils";
import { API } from "../helpers/api";