mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
feat: support creating text containers programatically
This commit is contained in:
parent
2a39d0b9a7
commit
15b5295baf
6 changed files with 105 additions and 13 deletions
|
@ -1,3 +1,4 @@
|
||||||
|
//@ts-nocheck
|
||||||
import {
|
import {
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
ExcalidrawSelectionElement,
|
ExcalidrawSelectionElement,
|
||||||
|
@ -40,6 +41,7 @@ import {
|
||||||
getDefaultLineHeight,
|
getDefaultLineHeight,
|
||||||
measureBaseline,
|
measureBaseline,
|
||||||
} from "../element/textElement";
|
} from "../element/textElement";
|
||||||
|
import { updateElementChildren } from "../element/newElement";
|
||||||
|
|
||||||
type RestoredAppState = Omit<
|
type RestoredAppState = Omit<
|
||||||
AppState,
|
AppState,
|
||||||
|
@ -194,6 +196,7 @@ const restoreElement = (
|
||||||
lineHeight,
|
lineHeight,
|
||||||
);
|
);
|
||||||
element = restoreElementWithProperties(element, {
|
element = restoreElementWithProperties(element, {
|
||||||
|
type: "text",
|
||||||
fontSize,
|
fontSize,
|
||||||
fontFamily,
|
fontFamily,
|
||||||
text,
|
text,
|
||||||
|
@ -390,7 +393,16 @@ export const restoreElements = (
|
||||||
migratedElement = { ...migratedElement, id: randomId() };
|
migratedElement = { ...migratedElement, id: randomId() };
|
||||||
}
|
}
|
||||||
existingIds.add(migratedElement.id);
|
existingIds.add(migratedElement.id);
|
||||||
elements.push(migratedElement);
|
if (element.children?.length) {
|
||||||
|
const newElements = updateElementChildren(element);
|
||||||
|
if (newElements) {
|
||||||
|
elements.push(...newElements);
|
||||||
|
} else {
|
||||||
|
elements.push(migratedElement);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
elements.push(migratedElement);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return elements;
|
return elements;
|
||||||
|
@ -540,6 +552,7 @@ export const restore = (
|
||||||
localElements: readonly ExcalidrawElement[] | null | undefined,
|
localElements: readonly ExcalidrawElement[] | null | undefined,
|
||||||
elementsConfig?: { refreshDimensions?: boolean; repairBindings?: boolean },
|
elementsConfig?: { refreshDimensions?: boolean; repairBindings?: boolean },
|
||||||
): RestoredDataState => {
|
): RestoredDataState => {
|
||||||
|
console.log(restoreElements(data?.elements, localElements, elementsConfig));
|
||||||
return {
|
return {
|
||||||
elements: restoreElements(data?.elements, localElements, elementsConfig),
|
elements: restoreElements(data?.elements, localElements, elementsConfig),
|
||||||
appState: restoreAppState(data?.appState, localAppState || null),
|
appState: restoreAppState(data?.appState, localAppState || null),
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { ExcalidrawElement } from "../element/types";
|
import { ExcalidrawElement, ExcalidrawTextContainer } from "../element/types";
|
||||||
import {
|
import {
|
||||||
AppState,
|
AppState,
|
||||||
BinaryFiles,
|
BinaryFiles,
|
||||||
|
@ -8,6 +8,8 @@ import {
|
||||||
import type { cleanAppStateForExport } from "../appState";
|
import type { cleanAppStateForExport } from "../appState";
|
||||||
import { VERSIONS } from "../constants";
|
import { VERSIONS } from "../constants";
|
||||||
|
|
||||||
|
import { ElementConstructorOpts } from "../element/newElement";
|
||||||
|
|
||||||
export interface ExportedDataState {
|
export interface ExportedDataState {
|
||||||
type: string;
|
type: string;
|
||||||
version: number;
|
version: number;
|
||||||
|
|
|
@ -35,6 +35,8 @@ import {
|
||||||
wrapText,
|
wrapText,
|
||||||
getBoundTextMaxWidth,
|
getBoundTextMaxWidth,
|
||||||
getDefaultLineHeight,
|
getDefaultLineHeight,
|
||||||
|
bindTextToContainer,
|
||||||
|
isValidTextContainer,
|
||||||
} from "./textElement";
|
} from "./textElement";
|
||||||
import {
|
import {
|
||||||
DEFAULT_ELEMENT_PROPS,
|
DEFAULT_ELEMENT_PROPS,
|
||||||
|
@ -44,10 +46,10 @@ import {
|
||||||
DEFAULT_VERTICAL_ALIGN,
|
DEFAULT_VERTICAL_ALIGN,
|
||||||
VERTICAL_ALIGN,
|
VERTICAL_ALIGN,
|
||||||
} from "../constants";
|
} from "../constants";
|
||||||
import { isArrowElement } from "./typeChecks";
|
import { isArrowElement, isTextElement } from "./typeChecks";
|
||||||
import { MarkOptional, Merge, Mutable } from "../utility-types";
|
import { MarkOptional, Merge, Mutable } from "../utility-types";
|
||||||
|
|
||||||
type ElementConstructorOpts = MarkOptional<
|
export type ElementConstructorOpts = MarkOptional<
|
||||||
Omit<ExcalidrawGenericElement, "id" | "type" | "isDeleted" | "updated">,
|
Omit<ExcalidrawGenericElement, "id" | "type" | "isDeleted" | "updated">,
|
||||||
| "width"
|
| "width"
|
||||||
| "height"
|
| "height"
|
||||||
|
@ -140,6 +142,7 @@ const getTextElementPositionOffsets = (
|
||||||
height: number;
|
height: number;
|
||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
|
console.log("metrics", metrics);
|
||||||
return {
|
return {
|
||||||
x:
|
x:
|
||||||
opts.textAlign === "center"
|
opts.textAlign === "center"
|
||||||
|
@ -643,3 +646,17 @@ export const duplicateElements = (
|
||||||
|
|
||||||
return clonedElements;
|
return clonedElements;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const updateElementChildren = (
|
||||||
|
element: {
|
||||||
|
type: ExcalidrawElement["type"];
|
||||||
|
} & MarkOptional<ElementConstructorOpts, "x" | "y">,
|
||||||
|
) => {
|
||||||
|
const textElement = element.children!.find((child) => child.text !== null);
|
||||||
|
if (isValidTextContainer(element) && textElement) {
|
||||||
|
//@ts-ignore
|
||||||
|
const elements = bindTextToContainer(element, textElement);
|
||||||
|
return elements;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { getFontString, arrayToMap, isTestEnv } from "../utils";
|
import { getFontString, arrayToMap, isTestEnv } from "../utils";
|
||||||
import {
|
import {
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
|
ExcalidrawGenericElement,
|
||||||
ExcalidrawTextContainer,
|
ExcalidrawTextContainer,
|
||||||
ExcalidrawTextElement,
|
ExcalidrawTextElement,
|
||||||
ExcalidrawTextElementWithContainer,
|
ExcalidrawTextElementWithContainer,
|
||||||
|
@ -20,7 +21,7 @@ import {
|
||||||
} from "../constants";
|
} from "../constants";
|
||||||
import { MaybeTransformHandleType } from "./transformHandles";
|
import { MaybeTransformHandleType } from "./transformHandles";
|
||||||
import Scene from "../scene/Scene";
|
import Scene from "../scene/Scene";
|
||||||
import { isTextElement } from ".";
|
import { isTextElement, newElement, newTextElement } from ".";
|
||||||
import { isBoundToContainer, isArrowElement } from "./typeChecks";
|
import { isBoundToContainer, isArrowElement } from "./typeChecks";
|
||||||
import { LinearElementEditor } from "./linearElementEditor";
|
import { LinearElementEditor } from "./linearElementEditor";
|
||||||
import { AppState } from "../types";
|
import { AppState } from "../types";
|
||||||
|
@ -32,7 +33,8 @@ import {
|
||||||
resetOriginalContainerCache,
|
resetOriginalContainerCache,
|
||||||
updateOriginalContainerCache,
|
updateOriginalContainerCache,
|
||||||
} from "./textWysiwyg";
|
} from "./textWysiwyg";
|
||||||
import { ExtractSetType } from "../utility-types";
|
import { ExtractSetType, MarkOptional } from "../utility-types";
|
||||||
|
import { ElementConstructorOpts } from "./newElement";
|
||||||
|
|
||||||
export const normalizeText = (text: string) => {
|
export const normalizeText = (text: string) => {
|
||||||
return (
|
return (
|
||||||
|
@ -83,21 +85,27 @@ export const redrawTextBoundingBox = (
|
||||||
boundTextUpdates.baseline = metrics.baseline;
|
boundTextUpdates.baseline = metrics.baseline;
|
||||||
|
|
||||||
if (container) {
|
if (container) {
|
||||||
const containerDims = getContainerDims(container);
|
|
||||||
const maxContainerHeight = getBoundTextMaxHeight(
|
const maxContainerHeight = getBoundTextMaxHeight(
|
||||||
container,
|
container,
|
||||||
textElement as ExcalidrawTextElementWithContainer,
|
textElement as ExcalidrawTextElementWithContainer,
|
||||||
);
|
);
|
||||||
|
const maxContainerWidth = getBoundTextMaxWidth(container);
|
||||||
|
|
||||||
let nextHeight = containerDims.height;
|
|
||||||
if (metrics.height > maxContainerHeight) {
|
if (metrics.height > maxContainerHeight) {
|
||||||
nextHeight = computeContainerDimensionForBoundText(
|
const nextHeight = computeContainerDimensionForBoundText(
|
||||||
metrics.height,
|
metrics.height,
|
||||||
container.type,
|
container.type,
|
||||||
);
|
);
|
||||||
mutateElement(container, { height: nextHeight });
|
mutateElement(container, { height: nextHeight });
|
||||||
updateOriginalContainerCache(container.id, nextHeight);
|
updateOriginalContainerCache(container.id, nextHeight);
|
||||||
}
|
}
|
||||||
|
if (metrics.width > maxContainerWidth) {
|
||||||
|
const nextWidth = computeContainerDimensionForBoundText(
|
||||||
|
metrics.width,
|
||||||
|
container.type,
|
||||||
|
);
|
||||||
|
mutateElement(container, { width: nextWidth });
|
||||||
|
}
|
||||||
const updatedTextElement = {
|
const updatedTextElement = {
|
||||||
...textElement,
|
...textElement,
|
||||||
...boundTextUpdates,
|
...boundTextUpdates,
|
||||||
|
@ -864,8 +872,9 @@ const VALID_CONTAINER_TYPES = new Set([
|
||||||
"arrow",
|
"arrow",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const isValidTextContainer = (element: ExcalidrawElement) =>
|
export const isValidTextContainer = (element: {
|
||||||
VALID_CONTAINER_TYPES.has(element.type);
|
type: ExcalidrawElement["type"];
|
||||||
|
}) => VALID_CONTAINER_TYPES.has(element.type);
|
||||||
|
|
||||||
export const computeContainerDimensionForBoundText = (
|
export const computeContainerDimensionForBoundText = (
|
||||||
dimension: number,
|
dimension: number,
|
||||||
|
@ -971,3 +980,34 @@ export const getDefaultLineHeight = (fontFamily: FontFamilyValues) => {
|
||||||
}
|
}
|
||||||
return DEFAULT_LINE_HEIGHT[DEFAULT_FONT_FAMILY];
|
return DEFAULT_LINE_HEIGHT[DEFAULT_FONT_FAMILY];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const bindTextToContainer = (
|
||||||
|
containerProps: {
|
||||||
|
type: ExcalidrawGenericElement["type"];
|
||||||
|
} & MarkOptional<ElementConstructorOpts, "x" | "y">,
|
||||||
|
textProps: { text: string } & MarkOptional<ElementConstructorOpts, "x" | "y">,
|
||||||
|
) => {
|
||||||
|
const container = newElement({
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
...containerProps,
|
||||||
|
});
|
||||||
|
const textElement: ExcalidrawTextElement = newTextElement({
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
...textProps,
|
||||||
|
containerId: container.id,
|
||||||
|
textAlign: TEXT_ALIGN.CENTER,
|
||||||
|
verticalAlign: VERTICAL_ALIGN.MIDDLE,
|
||||||
|
});
|
||||||
|
mutateElement(container, {
|
||||||
|
boundElements: (container.boundElements || []).concat({
|
||||||
|
type: "text",
|
||||||
|
id: textElement.id,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
redrawTextBoundingBox(textElement, container);
|
||||||
|
|
||||||
|
return [container, textElement];
|
||||||
|
};
|
||||||
|
|
|
@ -6,7 +6,8 @@ import {
|
||||||
THEME,
|
THEME,
|
||||||
VERTICAL_ALIGN,
|
VERTICAL_ALIGN,
|
||||||
} from "../constants";
|
} from "../constants";
|
||||||
import { MarkNonNullable, ValueOf } from "../utility-types";
|
import { MarkNonNullable, MarkOptional, ValueOf } from "../utility-types";
|
||||||
|
import { ElementConstructorOpts } from "./newElement";
|
||||||
|
|
||||||
export type ChartType = "bar" | "line";
|
export type ChartType = "bar" | "line";
|
||||||
export type FillStyle = "hachure" | "cross-hatch" | "solid" | "zigzag";
|
export type FillStyle = "hachure" | "cross-hatch" | "solid" | "zigzag";
|
||||||
|
@ -65,6 +66,9 @@ type _ExcalidrawElementBase = Readonly<{
|
||||||
link: string | null;
|
link: string | null;
|
||||||
locked: boolean;
|
locked: boolean;
|
||||||
customData?: Record<string, any>;
|
customData?: Record<string, any>;
|
||||||
|
children?: [
|
||||||
|
{ text: string } & MarkOptional<ElementConstructorOpts, "x" | "y">,
|
||||||
|
];
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type ExcalidrawSelectionElement = _ExcalidrawElementBase & {
|
export type ExcalidrawSelectionElement = _ExcalidrawElementBase & {
|
||||||
|
|
|
@ -344,7 +344,23 @@ const ExcalidrawWrapper = () => {
|
||||||
|
|
||||||
initializeScene({ collabAPI, excalidrawAPI }).then(async (data) => {
|
initializeScene({ collabAPI, excalidrawAPI }).then(async (data) => {
|
||||||
loadImages(data, /* isInitialLoad */ true);
|
loadImages(data, /* isInitialLoad */ true);
|
||||||
initialStatePromiseRef.current.promise.resolve(data.scene);
|
initialStatePromiseRef.current.promise.resolve({
|
||||||
|
type: "excalidraw",
|
||||||
|
version: 2,
|
||||||
|
source: "http://localhost:3000",
|
||||||
|
elements: [
|
||||||
|
//@ts-ignore
|
||||||
|
{
|
||||||
|
type: "rectangle",
|
||||||
|
children: [{ text: "HELLO DAMMMMY" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
appState: {
|
||||||
|
gridSize: null,
|
||||||
|
viewBackgroundColor: "#ffffff",
|
||||||
|
},
|
||||||
|
files: {},
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const onHashChange = async (event: HashChangeEvent) => {
|
const onHashChange = async (event: HashChangeEvent) => {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue