2023-03-03 17:40:42 +05:30
|
|
|
import {
|
|
|
|
BOUND_TEXT_PADDING,
|
|
|
|
ROUNDNESS,
|
|
|
|
TEXT_ALIGN,
|
|
|
|
VERTICAL_ALIGN,
|
|
|
|
} from "../constants";
|
|
|
|
import { getNonDeletedElements, isTextElement, newElement } from "../element";
|
2022-03-21 17:54:54 +05:30
|
|
|
import { mutateElement } from "../element/mutateElement";
|
|
|
|
import {
|
2023-03-03 17:40:42 +05:30
|
|
|
computeContainerDimensionForBoundText,
|
2022-03-21 17:54:54 +05:30
|
|
|
getBoundTextElement,
|
|
|
|
measureText,
|
|
|
|
redrawTextBoundingBox,
|
|
|
|
} from "../element/textElement";
|
2022-12-23 11:57:48 +05:30
|
|
|
import {
|
|
|
|
getOriginalContainerHeightFromCache,
|
|
|
|
resetOriginalContainerCache,
|
|
|
|
} from "../element/textWysiwyg";
|
2022-03-21 17:54:54 +05:30
|
|
|
import {
|
|
|
|
hasBoundTextElement,
|
|
|
|
isTextBindableContainer,
|
2023-03-03 17:40:42 +05:30
|
|
|
isUsingAdaptiveRadius,
|
2022-03-21 17:54:54 +05:30
|
|
|
} from "../element/typeChecks";
|
|
|
|
import {
|
2023-03-03 17:40:42 +05:30
|
|
|
ExcalidrawElement,
|
|
|
|
ExcalidrawLinearElement,
|
2022-03-21 17:54:54 +05:30
|
|
|
ExcalidrawTextContainer,
|
|
|
|
ExcalidrawTextElement,
|
|
|
|
} from "../element/types";
|
|
|
|
import { getSelectedElements } from "../scene";
|
|
|
|
import { getFontString } from "../utils";
|
|
|
|
import { register } from "./register";
|
|
|
|
|
|
|
|
export const actionUnbindText = register({
|
|
|
|
name: "unbindText",
|
|
|
|
contextItemLabel: "labels.unbindText",
|
2022-03-28 14:46:40 +02:00
|
|
|
trackEvent: { category: "element" },
|
2023-01-06 14:32:55 +01:00
|
|
|
predicate: (elements, appState) => {
|
2022-03-21 17:54:54 +05:30
|
|
|
const selectedElements = getSelectedElements(elements, appState);
|
|
|
|
return selectedElements.some((element) => hasBoundTextElement(element));
|
|
|
|
},
|
|
|
|
perform: (elements, appState) => {
|
|
|
|
const selectedElements = getSelectedElements(
|
|
|
|
getNonDeletedElements(elements),
|
|
|
|
appState,
|
|
|
|
);
|
|
|
|
selectedElements.forEach((element) => {
|
|
|
|
const boundTextElement = getBoundTextElement(element);
|
|
|
|
if (boundTextElement) {
|
2023-02-23 16:33:10 +05:30
|
|
|
const { width, height } = measureText(
|
2022-03-21 17:54:54 +05:30
|
|
|
boundTextElement.originalText,
|
|
|
|
getFontString(boundTextElement),
|
|
|
|
);
|
2022-12-23 11:57:48 +05:30
|
|
|
const originalContainerHeight = getOriginalContainerHeightFromCache(
|
|
|
|
element.id,
|
|
|
|
);
|
|
|
|
resetOriginalContainerCache(element.id);
|
|
|
|
|
2022-03-21 17:54:54 +05:30
|
|
|
mutateElement(boundTextElement as ExcalidrawTextElement, {
|
|
|
|
containerId: null,
|
|
|
|
width,
|
|
|
|
height,
|
|
|
|
text: boundTextElement.originalText,
|
|
|
|
});
|
|
|
|
mutateElement(element, {
|
|
|
|
boundElements: element.boundElements?.filter(
|
|
|
|
(ele) => ele.id !== boundTextElement.id,
|
|
|
|
),
|
2022-12-23 11:57:48 +05:30
|
|
|
height: originalContainerHeight
|
|
|
|
? originalContainerHeight
|
|
|
|
: element.height,
|
2022-03-21 17:54:54 +05:30
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return {
|
|
|
|
elements,
|
|
|
|
appState,
|
|
|
|
commitToHistory: true,
|
|
|
|
};
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
export const actionBindText = register({
|
|
|
|
name: "bindText",
|
|
|
|
contextItemLabel: "labels.bindText",
|
2022-03-28 14:46:40 +02:00
|
|
|
trackEvent: { category: "element" },
|
2023-01-06 14:32:55 +01:00
|
|
|
predicate: (elements, appState) => {
|
2022-03-21 17:54:54 +05:30
|
|
|
const selectedElements = getSelectedElements(elements, appState);
|
|
|
|
|
|
|
|
if (selectedElements.length === 2) {
|
|
|
|
const textElement =
|
|
|
|
isTextElement(selectedElements[0]) ||
|
|
|
|
isTextElement(selectedElements[1]);
|
|
|
|
|
|
|
|
let bindingContainer;
|
|
|
|
if (isTextBindableContainer(selectedElements[0])) {
|
|
|
|
bindingContainer = selectedElements[0];
|
|
|
|
} else if (isTextBindableContainer(selectedElements[1])) {
|
|
|
|
bindingContainer = selectedElements[1];
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
textElement &&
|
|
|
|
bindingContainer &&
|
|
|
|
getBoundTextElement(bindingContainer) === null
|
|
|
|
) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
perform: (elements, appState) => {
|
|
|
|
const selectedElements = getSelectedElements(
|
|
|
|
getNonDeletedElements(elements),
|
|
|
|
appState,
|
|
|
|
);
|
|
|
|
|
|
|
|
let textElement: ExcalidrawTextElement;
|
|
|
|
let container: ExcalidrawTextContainer;
|
|
|
|
|
|
|
|
if (
|
|
|
|
isTextElement(selectedElements[0]) &&
|
|
|
|
isTextBindableContainer(selectedElements[1])
|
|
|
|
) {
|
|
|
|
textElement = selectedElements[0];
|
|
|
|
container = selectedElements[1];
|
|
|
|
} else {
|
|
|
|
textElement = selectedElements[1] as ExcalidrawTextElement;
|
|
|
|
container = selectedElements[0] as ExcalidrawTextContainer;
|
|
|
|
}
|
|
|
|
mutateElement(textElement, {
|
|
|
|
containerId: container.id,
|
|
|
|
verticalAlign: VERTICAL_ALIGN.MIDDLE,
|
|
|
|
});
|
|
|
|
mutateElement(container, {
|
|
|
|
boundElements: (container.boundElements || []).concat({
|
|
|
|
type: "text",
|
|
|
|
id: textElement.id,
|
|
|
|
}),
|
|
|
|
});
|
|
|
|
redrawTextBoundingBox(textElement, container);
|
2023-03-03 17:40:42 +05:30
|
|
|
|
|
|
|
return {
|
|
|
|
elements: pushTextAboveContainer(elements, container, textElement),
|
|
|
|
appState: { ...appState, selectedElementIds: { [container.id]: 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,
|
2022-03-21 17:54:54 +05:30
|
|
|
);
|
2023-03-03 17:40:42 +05:30
|
|
|
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,
|
|
|
|
};
|
|
|
|
}
|
2022-03-21 17:54:54 +05:30
|
|
|
return {
|
|
|
|
elements: updatedElements,
|
2023-03-03 17:40:42 +05:30
|
|
|
appState,
|
2022-03-21 17:54:54 +05:30
|
|
|
commitToHistory: true,
|
|
|
|
};
|
|
|
|
},
|
|
|
|
});
|