feat: create bound container from text (#6301)
* feat: create container from text * fix lint and spec * fix * round off dims * ceil * review fixes * fix * Add specs * fix * fix z-index and type * consider group * consider linear bindings * lint
This commit is contained in:
parent
1ce933d2f5
commit
0f06fa3851
@ -1,7 +1,13 @@
|
|||||||
import { VERTICAL_ALIGN } from "../constants";
|
import {
|
||||||
import { getNonDeletedElements, isTextElement } from "../element";
|
BOUND_TEXT_PADDING,
|
||||||
|
ROUNDNESS,
|
||||||
|
TEXT_ALIGN,
|
||||||
|
VERTICAL_ALIGN,
|
||||||
|
} from "../constants";
|
||||||
|
import { getNonDeletedElements, isTextElement, newElement } from "../element";
|
||||||
import { mutateElement } from "../element/mutateElement";
|
import { mutateElement } from "../element/mutateElement";
|
||||||
import {
|
import {
|
||||||
|
computeContainerDimensionForBoundText,
|
||||||
getBoundTextElement,
|
getBoundTextElement,
|
||||||
measureText,
|
measureText,
|
||||||
redrawTextBoundingBox,
|
redrawTextBoundingBox,
|
||||||
@ -13,8 +19,11 @@ import {
|
|||||||
import {
|
import {
|
||||||
hasBoundTextElement,
|
hasBoundTextElement,
|
||||||
isTextBindableContainer,
|
isTextBindableContainer,
|
||||||
|
isUsingAdaptiveRadius,
|
||||||
} from "../element/typeChecks";
|
} from "../element/typeChecks";
|
||||||
import {
|
import {
|
||||||
|
ExcalidrawElement,
|
||||||
|
ExcalidrawLinearElement,
|
||||||
ExcalidrawTextContainer,
|
ExcalidrawTextContainer,
|
||||||
ExcalidrawTextElement,
|
ExcalidrawTextElement,
|
||||||
} from "../element/types";
|
} from "../element/types";
|
||||||
@ -129,19 +138,152 @@ export const actionBindText = register({
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
redrawTextBoundingBox(textElement, container);
|
redrawTextBoundingBox(textElement, container);
|
||||||
const updatedElements = elements.slice();
|
|
||||||
const textElementIndex = updatedElements.findIndex(
|
|
||||||
(ele) => ele.id === textElement.id,
|
|
||||||
);
|
|
||||||
updatedElements.splice(textElementIndex, 1);
|
|
||||||
const containerIndex = updatedElements.findIndex(
|
|
||||||
(ele) => ele.id === container.id,
|
|
||||||
);
|
|
||||||
updatedElements.splice(containerIndex + 1, 0, textElement);
|
|
||||||
return {
|
return {
|
||||||
elements: updatedElements,
|
elements: pushTextAboveContainer(elements, container, textElement),
|
||||||
appState: { ...appState, selectedElementIds: { [container.id]: true } },
|
appState: { ...appState, selectedElementIds: { [container.id]: true } },
|
||||||
commitToHistory: true,
|
commitToHistory: true,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const pushTextAboveContainer = (
|
||||||
|
elements: readonly ExcalidrawElement[],
|
||||||
|
container: ExcalidrawElement,
|
||||||
|
textElement: ExcalidrawTextElement,
|
||||||
|
) => {
|
||||||
|
const updatedElements = elements.slice();
|
||||||
|
const textElementIndex = updatedElements.findIndex(
|
||||||
|
(ele) => ele.id === textElement.id,
|
||||||
|
);
|
||||||
|
updatedElements.splice(textElementIndex, 1);
|
||||||
|
|
||||||
|
const containerIndex = updatedElements.findIndex(
|
||||||
|
(ele) => ele.id === container.id,
|
||||||
|
);
|
||||||
|
updatedElements.splice(containerIndex + 1, 0, textElement);
|
||||||
|
return updatedElements;
|
||||||
|
};
|
||||||
|
|
||||||
|
const pushContainerBelowText = (
|
||||||
|
elements: readonly ExcalidrawElement[],
|
||||||
|
container: ExcalidrawElement,
|
||||||
|
textElement: ExcalidrawTextElement,
|
||||||
|
) => {
|
||||||
|
const updatedElements = elements.slice();
|
||||||
|
const containerIndex = updatedElements.findIndex(
|
||||||
|
(ele) => ele.id === container.id,
|
||||||
|
);
|
||||||
|
updatedElements.splice(containerIndex, 1);
|
||||||
|
|
||||||
|
const textElementIndex = updatedElements.findIndex(
|
||||||
|
(ele) => ele.id === textElement.id,
|
||||||
|
);
|
||||||
|
updatedElements.splice(textElementIndex, 0, container);
|
||||||
|
return updatedElements;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const actionCreateContainerFromText = register({
|
||||||
|
name: "createContainerFromText",
|
||||||
|
contextItemLabel: "labels.createContainerFromText",
|
||||||
|
trackEvent: { category: "element" },
|
||||||
|
predicate: (elements, appState) => {
|
||||||
|
const selectedElements = getSelectedElements(elements, appState);
|
||||||
|
return selectedElements.length === 1 && isTextElement(selectedElements[0]);
|
||||||
|
},
|
||||||
|
perform: (elements, appState) => {
|
||||||
|
const selectedElements = getSelectedElements(
|
||||||
|
getNonDeletedElements(elements),
|
||||||
|
appState,
|
||||||
|
);
|
||||||
|
const updatedElements = elements.slice();
|
||||||
|
if (selectedElements.length === 1 && isTextElement(selectedElements[0])) {
|
||||||
|
const textElement = selectedElements[0];
|
||||||
|
const container = newElement({
|
||||||
|
type: "rectangle",
|
||||||
|
backgroundColor: appState.currentItemBackgroundColor,
|
||||||
|
boundElements: [
|
||||||
|
...(textElement.boundElements || []),
|
||||||
|
{ id: textElement.id, type: "text" },
|
||||||
|
],
|
||||||
|
angle: textElement.angle,
|
||||||
|
fillStyle: appState.currentItemFillStyle,
|
||||||
|
strokeColor: appState.currentItemStrokeColor,
|
||||||
|
roughness: appState.currentItemRoughness,
|
||||||
|
strokeWidth: appState.currentItemStrokeWidth,
|
||||||
|
strokeStyle: appState.currentItemStrokeStyle,
|
||||||
|
roundness:
|
||||||
|
appState.currentItemRoundness === "round"
|
||||||
|
? {
|
||||||
|
type: isUsingAdaptiveRadius("rectangle")
|
||||||
|
? ROUNDNESS.ADAPTIVE_RADIUS
|
||||||
|
: ROUNDNESS.PROPORTIONAL_RADIUS,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
opacity: 100,
|
||||||
|
locked: false,
|
||||||
|
x: textElement.x - BOUND_TEXT_PADDING,
|
||||||
|
y: textElement.y - BOUND_TEXT_PADDING,
|
||||||
|
width: computeContainerDimensionForBoundText(
|
||||||
|
textElement.width,
|
||||||
|
"rectangle",
|
||||||
|
),
|
||||||
|
height: computeContainerDimensionForBoundText(
|
||||||
|
textElement.height,
|
||||||
|
"rectangle",
|
||||||
|
),
|
||||||
|
groupIds: textElement.groupIds,
|
||||||
|
});
|
||||||
|
|
||||||
|
// update bindings
|
||||||
|
if (textElement.boundElements?.length) {
|
||||||
|
const linearElementIds = textElement.boundElements
|
||||||
|
.filter((ele) => ele.type === "arrow")
|
||||||
|
.map((el) => el.id);
|
||||||
|
const linearElements = updatedElements.filter((ele) =>
|
||||||
|
linearElementIds.includes(ele.id),
|
||||||
|
) as ExcalidrawLinearElement[];
|
||||||
|
linearElements.forEach((ele) => {
|
||||||
|
let startBinding = null;
|
||||||
|
let endBinding = null;
|
||||||
|
if (ele.startBinding) {
|
||||||
|
startBinding = { ...ele.startBinding, elementId: container.id };
|
||||||
|
}
|
||||||
|
if (ele.endBinding) {
|
||||||
|
endBinding = { ...ele.endBinding, elementId: container.id };
|
||||||
|
}
|
||||||
|
mutateElement(ele, { startBinding, endBinding });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
mutateElement(textElement, {
|
||||||
|
containerId: container.id,
|
||||||
|
verticalAlign: VERTICAL_ALIGN.MIDDLE,
|
||||||
|
textAlign: TEXT_ALIGN.CENTER,
|
||||||
|
boundElements: null,
|
||||||
|
});
|
||||||
|
redrawTextBoundingBox(textElement, container);
|
||||||
|
|
||||||
|
return {
|
||||||
|
elements: pushContainerBelowText(
|
||||||
|
[...elements, container],
|
||||||
|
container,
|
||||||
|
textElement,
|
||||||
|
),
|
||||||
|
appState: {
|
||||||
|
...appState,
|
||||||
|
selectedElementIds: {
|
||||||
|
[container.id]: true,
|
||||||
|
[textElement.id]: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
commitToHistory: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
elements: updatedElements,
|
||||||
|
appState,
|
||||||
|
commitToHistory: true,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
@ -113,7 +113,8 @@ export type ActionName =
|
|||||||
| "toggleLock"
|
| "toggleLock"
|
||||||
| "toggleLinearEditor"
|
| "toggleLinearEditor"
|
||||||
| "toggleEraserTool"
|
| "toggleEraserTool"
|
||||||
| "toggleHandTool";
|
| "toggleHandTool"
|
||||||
|
| "createContainerFromText";
|
||||||
|
|
||||||
export type PanelComponentProps = {
|
export type PanelComponentProps = {
|
||||||
elements: readonly ExcalidrawElement[];
|
elements: readonly ExcalidrawElement[];
|
||||||
|
@ -284,6 +284,7 @@ import { actionPaste } from "../actions/actionClipboard";
|
|||||||
import { actionToggleHandTool } from "../actions/actionCanvas";
|
import { actionToggleHandTool } from "../actions/actionCanvas";
|
||||||
import { jotaiStore } from "../jotai";
|
import { jotaiStore } from "../jotai";
|
||||||
import { activeConfirmDialogAtom } from "./ActiveConfirmDialog";
|
import { activeConfirmDialogAtom } from "./ActiveConfirmDialog";
|
||||||
|
import { actionCreateContainerFromText } from "../actions/actionBoundText";
|
||||||
|
|
||||||
const deviceContextInitialValue = {
|
const deviceContextInitialValue = {
|
||||||
isSmScreen: false,
|
isSmScreen: false,
|
||||||
@ -6237,6 +6238,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
actionGroup,
|
actionGroup,
|
||||||
actionUnbindText,
|
actionUnbindText,
|
||||||
actionBindText,
|
actionBindText,
|
||||||
|
actionCreateContainerFromText,
|
||||||
actionUngroup,
|
actionUngroup,
|
||||||
CONTEXT_MENU_SEPARATOR,
|
CONTEXT_MENU_SEPARATOR,
|
||||||
actionAddToLibrary,
|
actionAddToLibrary,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { BOUND_TEXT_PADDING } from "../constants";
|
import { BOUND_TEXT_PADDING } from "../constants";
|
||||||
import { API } from "../tests/helpers/api";
|
import { API } from "../tests/helpers/api";
|
||||||
import {
|
import {
|
||||||
computeContainerHeightForBoundText,
|
computeContainerDimensionForBoundText,
|
||||||
getContainerCoords,
|
getContainerCoords,
|
||||||
getMaxContainerWidth,
|
getMaxContainerWidth,
|
||||||
getMaxContainerHeight,
|
getMaxContainerHeight,
|
||||||
@ -35,10 +35,11 @@ describe("Test wrapText", () => {
|
|||||||
|
|
||||||
describe("When text doesn't contain new lines", () => {
|
describe("When text doesn't contain new lines", () => {
|
||||||
const text = "Hello whats up";
|
const text = "Hello whats up";
|
||||||
|
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
desc: "break all words when width of each word is less than container width",
|
desc: "break all words when width of each word is less than container width",
|
||||||
width: 90,
|
width: 80,
|
||||||
res: `Hello
|
res: `Hello
|
||||||
whats
|
whats
|
||||||
up`,
|
up`,
|
||||||
@ -62,7 +63,7 @@ p`,
|
|||||||
{
|
{
|
||||||
desc: "break words as per the width",
|
desc: "break words as per the width",
|
||||||
|
|
||||||
width: 150,
|
width: 140,
|
||||||
res: `Hello whats
|
res: `Hello whats
|
||||||
up`,
|
up`,
|
||||||
},
|
},
|
||||||
@ -93,7 +94,7 @@ whats up`;
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
desc: "break all words when width of each word is less than container width",
|
desc: "break all words when width of each word is less than container width",
|
||||||
width: 90,
|
width: 80,
|
||||||
res: `Hello
|
res: `Hello
|
||||||
whats
|
whats
|
||||||
up`,
|
up`,
|
||||||
@ -214,7 +215,7 @@ describe("Test measureText", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Test computeContainerHeightForBoundText", () => {
|
describe("Test computeContainerDimensionForBoundText", () => {
|
||||||
const params = {
|
const params = {
|
||||||
width: 178,
|
width: 178,
|
||||||
height: 194,
|
height: 194,
|
||||||
@ -225,7 +226,9 @@ describe("Test measureText", () => {
|
|||||||
type: "rectangle",
|
type: "rectangle",
|
||||||
...params,
|
...params,
|
||||||
});
|
});
|
||||||
expect(computeContainerHeightForBoundText(element, 150)).toEqual(160);
|
expect(computeContainerDimensionForBoundText(150, element.type)).toEqual(
|
||||||
|
160,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should compute container height correctly for ellipse", () => {
|
it("should compute container height correctly for ellipse", () => {
|
||||||
@ -233,7 +236,9 @@ describe("Test measureText", () => {
|
|||||||
type: "ellipse",
|
type: "ellipse",
|
||||||
...params,
|
...params,
|
||||||
});
|
});
|
||||||
expect(computeContainerHeightForBoundText(element, 150)).toEqual(226);
|
expect(computeContainerDimensionForBoundText(150, element.type)).toEqual(
|
||||||
|
226,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should compute container height correctly for diamond", () => {
|
it("should compute container height correctly for diamond", () => {
|
||||||
@ -241,7 +246,9 @@ describe("Test measureText", () => {
|
|||||||
type: "diamond",
|
type: "diamond",
|
||||||
...params,
|
...params,
|
||||||
});
|
});
|
||||||
expect(computeContainerHeightForBoundText(element, 150)).toEqual(320);
|
expect(computeContainerDimensionForBoundText(150, element.type)).toEqual(
|
||||||
|
320,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -12,11 +12,7 @@ import { BOUND_TEXT_PADDING, TEXT_ALIGN, VERTICAL_ALIGN } 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 } from ".";
|
||||||
import {
|
import { isBoundToContainer, isArrowElement } from "./typeChecks";
|
||||||
isBoundToContainer,
|
|
||||||
isImageElement,
|
|
||||||
isArrowElement,
|
|
||||||
} from "./typeChecks";
|
|
||||||
import { LinearElementEditor } from "./linearElementEditor";
|
import { LinearElementEditor } from "./linearElementEditor";
|
||||||
import { AppState } from "../types";
|
import { AppState } from "../types";
|
||||||
import { isTextBindableContainer } from "./typeChecks";
|
import { isTextBindableContainer } from "./typeChecks";
|
||||||
@ -84,9 +80,9 @@ export const redrawTextBoundingBox = (
|
|||||||
|
|
||||||
let nextHeight = containerDims.height;
|
let nextHeight = containerDims.height;
|
||||||
if (metrics.height > maxContainerHeight) {
|
if (metrics.height > maxContainerHeight) {
|
||||||
nextHeight = computeContainerHeightForBoundText(
|
nextHeight = computeContainerDimensionForBoundText(
|
||||||
container,
|
|
||||||
metrics.height,
|
metrics.height,
|
||||||
|
container.type,
|
||||||
);
|
);
|
||||||
mutateElement(container, { height: nextHeight });
|
mutateElement(container, { height: nextHeight });
|
||||||
maxContainerHeight = getMaxContainerHeight(container);
|
maxContainerHeight = getMaxContainerHeight(container);
|
||||||
@ -188,9 +184,9 @@ export const handleBindTextResize = (
|
|||||||
}
|
}
|
||||||
// increase height in case text element height exceeds
|
// increase height in case text element height exceeds
|
||||||
if (nextHeight > maxHeight) {
|
if (nextHeight > maxHeight) {
|
||||||
containerHeight = computeContainerHeightForBoundText(
|
containerHeight = computeContainerDimensionForBoundText(
|
||||||
container,
|
|
||||||
nextHeight,
|
nextHeight,
|
||||||
|
container.type,
|
||||||
);
|
);
|
||||||
|
|
||||||
const diff = containerHeight - containerDims.height;
|
const diff = containerHeight - containerDims.height;
|
||||||
@ -324,7 +320,6 @@ export const wrapText = (text: string, font: FontString, maxWidth: number) => {
|
|||||||
const lines: Array<string> = [];
|
const lines: Array<string> = [];
|
||||||
const originalLines = text.split("\n");
|
const originalLines = text.split("\n");
|
||||||
const spaceWidth = getLineWidth(" ", font);
|
const spaceWidth = getLineWidth(" ", font);
|
||||||
|
|
||||||
const push = (str: string) => {
|
const push = (str: string) => {
|
||||||
if (str.trim()) {
|
if (str.trim()) {
|
||||||
lines.push(str);
|
lines.push(str);
|
||||||
@ -398,7 +393,7 @@ export const wrapText = (text: string, font: FontString, maxWidth: number) => {
|
|||||||
const word = words[index];
|
const word = words[index];
|
||||||
currentLineWidthTillNow = getLineWidth(currentLine + word, font);
|
currentLineWidthTillNow = getLineWidth(currentLine + word, font);
|
||||||
|
|
||||||
if (currentLineWidthTillNow >= maxWidth) {
|
if (currentLineWidthTillNow > maxWidth) {
|
||||||
push(currentLine);
|
push(currentLine);
|
||||||
currentLineWidthTillNow = 0;
|
currentLineWidthTillNow = 0;
|
||||||
currentLine = "";
|
currentLine = "";
|
||||||
@ -714,32 +709,34 @@ export const getTextBindableContainerAtPosition = (
|
|||||||
return isTextBindableContainer(hitElement, false) ? hitElement : null;
|
return isTextBindableContainer(hitElement, false) ? hitElement : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isValidTextContainer = (element: ExcalidrawElement) => {
|
const VALID_CONTAINER_TYPES = new Set([
|
||||||
return (
|
"rectangle",
|
||||||
element.type === "rectangle" ||
|
"ellipse",
|
||||||
element.type === "ellipse" ||
|
"diamond",
|
||||||
element.type === "diamond" ||
|
"image",
|
||||||
isImageElement(element) ||
|
"arrow",
|
||||||
isArrowElement(element)
|
]);
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const computeContainerHeightForBoundText = (
|
export const isValidTextContainer = (element: ExcalidrawElement) =>
|
||||||
container: NonDeletedExcalidrawElement,
|
VALID_CONTAINER_TYPES.has(element.type);
|
||||||
boundTextElementHeight: number,
|
|
||||||
|
export const computeContainerDimensionForBoundText = (
|
||||||
|
dimension: number,
|
||||||
|
containerType: ExtractSetType<typeof VALID_CONTAINER_TYPES>,
|
||||||
) => {
|
) => {
|
||||||
if (container.type === "ellipse") {
|
dimension = Math.ceil(dimension);
|
||||||
return Math.round(
|
const padding = BOUND_TEXT_PADDING * 2;
|
||||||
((boundTextElementHeight + BOUND_TEXT_PADDING * 2) / Math.sqrt(2)) * 2,
|
|
||||||
);
|
if (containerType === "ellipse") {
|
||||||
|
return Math.round(((dimension + padding) / Math.sqrt(2)) * 2);
|
||||||
}
|
}
|
||||||
if (isArrowElement(container)) {
|
if (containerType === "arrow") {
|
||||||
return boundTextElementHeight + BOUND_TEXT_PADDING * 8 * 2;
|
return dimension + padding * 8;
|
||||||
}
|
}
|
||||||
if (container.type === "diamond") {
|
if (containerType === "diamond") {
|
||||||
return 2 * (boundTextElementHeight + BOUND_TEXT_PADDING * 2);
|
return 2 * (dimension + padding);
|
||||||
}
|
}
|
||||||
return boundTextElementHeight + BOUND_TEXT_PADDING * 2;
|
return dimension + padding;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getMaxContainerWidth = (container: ExcalidrawElement) => {
|
export const getMaxContainerWidth = (container: ExcalidrawElement) => {
|
||||||
|
@ -19,6 +19,7 @@ import { API } from "../tests/helpers/api";
|
|||||||
import { mutateElement } from "./mutateElement";
|
import { mutateElement } from "./mutateElement";
|
||||||
import { resize } from "../tests/utils";
|
import { resize } from "../tests/utils";
|
||||||
import { getOriginalContainerHeightFromCache } from "./textWysiwyg";
|
import { getOriginalContainerHeightFromCache } from "./textWysiwyg";
|
||||||
|
|
||||||
// Unmount ReactDOM from root
|
// Unmount ReactDOM from root
|
||||||
ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
|
ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
|
||||||
|
|
||||||
@ -1307,5 +1308,78 @@ describe("textWysiwyg", () => {
|
|||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should wrap text in a container when wrap text in container triggered from context menu", async () => {
|
||||||
|
UI.clickTool("text");
|
||||||
|
mouse.clickAt(20, 30);
|
||||||
|
const editor = document.querySelector(
|
||||||
|
".excalidraw-textEditorContainer > textarea",
|
||||||
|
) as HTMLTextAreaElement;
|
||||||
|
|
||||||
|
fireEvent.change(editor, {
|
||||||
|
target: {
|
||||||
|
value: "Excalidraw is an opensource virtual collaborative whiteboard",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
editor.dispatchEvent(new Event("input"));
|
||||||
|
await new Promise((cb) => setTimeout(cb, 0));
|
||||||
|
editor.blur();
|
||||||
|
expect(h.elements[1].width).toBe(600);
|
||||||
|
expect(h.elements[1].height).toBe(24);
|
||||||
|
expect((h.elements[1] as ExcalidrawTextElement).text).toBe(
|
||||||
|
"Excalidraw is an opensource virtual collaborative whiteboard",
|
||||||
|
);
|
||||||
|
|
||||||
|
API.setSelectedElements([h.elements[1]]);
|
||||||
|
|
||||||
|
fireEvent.contextMenu(GlobalTestState.canvas, {
|
||||||
|
button: 2,
|
||||||
|
clientX: 20,
|
||||||
|
clientY: 30,
|
||||||
|
});
|
||||||
|
|
||||||
|
const contextMenu = document.querySelector(".context-menu");
|
||||||
|
fireEvent.click(
|
||||||
|
queryByText(contextMenu as HTMLElement, "Wrap text in a container")!,
|
||||||
|
);
|
||||||
|
expect(h.elements.length).toBe(3);
|
||||||
|
|
||||||
|
expect(h.elements[1]).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
angle: 0,
|
||||||
|
backgroundColor: "transparent",
|
||||||
|
boundElements: [
|
||||||
|
{
|
||||||
|
id: h.elements[2].id,
|
||||||
|
type: "text",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
fillStyle: "hachure",
|
||||||
|
groupIds: [],
|
||||||
|
height: 34,
|
||||||
|
isDeleted: false,
|
||||||
|
link: null,
|
||||||
|
locked: false,
|
||||||
|
opacity: 100,
|
||||||
|
roughness: 1,
|
||||||
|
roundness: {
|
||||||
|
type: 3,
|
||||||
|
},
|
||||||
|
strokeColor: "#000000",
|
||||||
|
strokeStyle: "solid",
|
||||||
|
strokeWidth: 1,
|
||||||
|
type: "rectangle",
|
||||||
|
updated: 1,
|
||||||
|
version: 1,
|
||||||
|
width: 610,
|
||||||
|
x: 15,
|
||||||
|
y: 25,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect((h.elements[2] as ExcalidrawTextElement).text).toBe(
|
||||||
|
"Excalidraw is an opensource virtual collaborative whiteboard",
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
2
src/global.d.ts
vendored
2
src/global.d.ts
vendored
@ -165,3 +165,5 @@ declare module "image-blob-reduce" {
|
|||||||
const reduce: ImageBlobReduce.ImageBlobReduceStatic;
|
const reduce: ImageBlobReduce.ImageBlobReduceStatic;
|
||||||
export = reduce;
|
export = reduce;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ExtractSetType<T extends Set<any>> = T extends Set<infer U> ? U : never;
|
||||||
|
@ -110,6 +110,7 @@
|
|||||||
"increaseFontSize": "Increase font size",
|
"increaseFontSize": "Increase font size",
|
||||||
"unbindText": "Unbind text",
|
"unbindText": "Unbind text",
|
||||||
"bindText": "Bind text to the container",
|
"bindText": "Bind text to the container",
|
||||||
|
"createContainerFromText": "Wrap text in a container",
|
||||||
"link": {
|
"link": {
|
||||||
"edit": "Edit link",
|
"edit": "Edit link",
|
||||||
"create": "Create link",
|
"create": "Create link",
|
||||||
|
@ -119,6 +119,15 @@ Object {
|
|||||||
"category": "element",
|
"category": "element",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Object {
|
||||||
|
"contextItemLabel": "labels.createContainerFromText",
|
||||||
|
"name": "createContainerFromText",
|
||||||
|
"perform": [Function],
|
||||||
|
"predicate": [Function],
|
||||||
|
"trackEvent": Object {
|
||||||
|
"category": "element",
|
||||||
|
},
|
||||||
|
},
|
||||||
Object {
|
Object {
|
||||||
"PanelComponent": [Function],
|
"PanelComponent": [Function],
|
||||||
"contextItemLabel": "labels.ungroup",
|
"contextItemLabel": "labels.ungroup",
|
||||||
@ -4507,6 +4516,15 @@ Object {
|
|||||||
"category": "element",
|
"category": "element",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Object {
|
||||||
|
"contextItemLabel": "labels.createContainerFromText",
|
||||||
|
"name": "createContainerFromText",
|
||||||
|
"perform": [Function],
|
||||||
|
"predicate": [Function],
|
||||||
|
"trackEvent": Object {
|
||||||
|
"category": "element",
|
||||||
|
},
|
||||||
|
},
|
||||||
Object {
|
Object {
|
||||||
"PanelComponent": [Function],
|
"PanelComponent": [Function],
|
||||||
"contextItemLabel": "labels.ungroup",
|
"contextItemLabel": "labels.ungroup",
|
||||||
@ -5048,6 +5066,15 @@ Object {
|
|||||||
"category": "element",
|
"category": "element",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Object {
|
||||||
|
"contextItemLabel": "labels.createContainerFromText",
|
||||||
|
"name": "createContainerFromText",
|
||||||
|
"perform": [Function],
|
||||||
|
"predicate": [Function],
|
||||||
|
"trackEvent": Object {
|
||||||
|
"category": "element",
|
||||||
|
},
|
||||||
|
},
|
||||||
Object {
|
Object {
|
||||||
"PanelComponent": [Function],
|
"PanelComponent": [Function],
|
||||||
"contextItemLabel": "labels.ungroup",
|
"contextItemLabel": "labels.ungroup",
|
||||||
@ -5888,6 +5915,15 @@ Object {
|
|||||||
"category": "element",
|
"category": "element",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Object {
|
||||||
|
"contextItemLabel": "labels.createContainerFromText",
|
||||||
|
"name": "createContainerFromText",
|
||||||
|
"perform": [Function],
|
||||||
|
"predicate": [Function],
|
||||||
|
"trackEvent": Object {
|
||||||
|
"category": "element",
|
||||||
|
},
|
||||||
|
},
|
||||||
Object {
|
Object {
|
||||||
"PanelComponent": [Function],
|
"PanelComponent": [Function],
|
||||||
"contextItemLabel": "labels.ungroup",
|
"contextItemLabel": "labels.ungroup",
|
||||||
@ -6225,6 +6261,15 @@ Object {
|
|||||||
"category": "element",
|
"category": "element",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Object {
|
||||||
|
"contextItemLabel": "labels.createContainerFromText",
|
||||||
|
"name": "createContainerFromText",
|
||||||
|
"perform": [Function],
|
||||||
|
"predicate": [Function],
|
||||||
|
"trackEvent": Object {
|
||||||
|
"category": "element",
|
||||||
|
},
|
||||||
|
},
|
||||||
Object {
|
Object {
|
||||||
"PanelComponent": [Function],
|
"PanelComponent": [Function],
|
||||||
"contextItemLabel": "labels.ungroup",
|
"contextItemLabel": "labels.ungroup",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user