|
|
|
@ -41,9 +41,16 @@ import {
|
|
|
|
|
isTextElement,
|
|
|
|
|
redrawTextBoundingBox,
|
|
|
|
|
} from "../element";
|
|
|
|
|
import { newElementWith } from "../element/mutateElement";
|
|
|
|
|
import { getBoundTextElement } from "../element/textElement";
|
|
|
|
|
import { isLinearElement, isLinearElementType } from "../element/typeChecks";
|
|
|
|
|
import { mutateElement, newElementWith } from "../element/mutateElement";
|
|
|
|
|
import {
|
|
|
|
|
getBoundTextElement,
|
|
|
|
|
getContainerElement,
|
|
|
|
|
} from "../element/textElement";
|
|
|
|
|
import {
|
|
|
|
|
isBoundToContainer,
|
|
|
|
|
isLinearElement,
|
|
|
|
|
isLinearElementType,
|
|
|
|
|
} from "../element/typeChecks";
|
|
|
|
|
import {
|
|
|
|
|
Arrowhead,
|
|
|
|
|
ExcalidrawElement,
|
|
|
|
@ -53,6 +60,7 @@ import {
|
|
|
|
|
TextAlign,
|
|
|
|
|
} from "../element/types";
|
|
|
|
|
import { getLanguage, t } from "../i18n";
|
|
|
|
|
import { KEYS } from "../keys";
|
|
|
|
|
import { randomInteger } from "../random";
|
|
|
|
|
import {
|
|
|
|
|
canChangeSharpness,
|
|
|
|
@ -63,10 +71,11 @@ import {
|
|
|
|
|
isSomeElementSelected,
|
|
|
|
|
} from "../scene";
|
|
|
|
|
import { hasStrokeColor } from "../scene/comparisons";
|
|
|
|
|
import Scene from "../scene/Scene";
|
|
|
|
|
import { arrayToMap } from "../utils";
|
|
|
|
|
import { register } from "./register";
|
|
|
|
|
|
|
|
|
|
const FONT_SIZE_RELATIVE_INCREASE_STEP = 0.1;
|
|
|
|
|
|
|
|
|
|
const changeProperty = (
|
|
|
|
|
elements: readonly ExcalidrawElement[],
|
|
|
|
|
appState: AppState,
|
|
|
|
@ -108,6 +117,79 @@ const getFormValue = function <T>(
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const offsetElementAfterFontResize = (
|
|
|
|
|
prevElement: ExcalidrawTextElement,
|
|
|
|
|
nextElement: ExcalidrawTextElement,
|
|
|
|
|
) => {
|
|
|
|
|
if (isBoundToContainer(nextElement)) {
|
|
|
|
|
return nextElement;
|
|
|
|
|
}
|
|
|
|
|
return mutateElement(
|
|
|
|
|
nextElement,
|
|
|
|
|
{
|
|
|
|
|
x:
|
|
|
|
|
prevElement.textAlign === "left"
|
|
|
|
|
? prevElement.x
|
|
|
|
|
: prevElement.x +
|
|
|
|
|
(prevElement.width - nextElement.width) /
|
|
|
|
|
(prevElement.textAlign === "center" ? 2 : 1),
|
|
|
|
|
// centering vertically is non-standard, but for Excalidraw I think
|
|
|
|
|
// it makes sense
|
|
|
|
|
y: prevElement.y + (prevElement.height - nextElement.height) / 2,
|
|
|
|
|
},
|
|
|
|
|
false,
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const changeFontSize = (
|
|
|
|
|
elements: readonly ExcalidrawElement[],
|
|
|
|
|
appState: AppState,
|
|
|
|
|
getNewFontSize: (element: ExcalidrawTextElement) => number,
|
|
|
|
|
) => {
|
|
|
|
|
const newFontSizes = new Set<number>();
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
elements: changeProperty(
|
|
|
|
|
elements,
|
|
|
|
|
appState,
|
|
|
|
|
(oldElement) => {
|
|
|
|
|
if (isTextElement(oldElement)) {
|
|
|
|
|
const newFontSize = getNewFontSize(oldElement);
|
|
|
|
|
newFontSizes.add(newFontSize);
|
|
|
|
|
|
|
|
|
|
let newElement: ExcalidrawTextElement = newElementWith(oldElement, {
|
|
|
|
|
fontSize: newFontSize,
|
|
|
|
|
});
|
|
|
|
|
redrawTextBoundingBox(
|
|
|
|
|
newElement,
|
|
|
|
|
getContainerElement(oldElement),
|
|
|
|
|
appState,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
newElement = offsetElementAfterFontResize(oldElement, newElement);
|
|
|
|
|
|
|
|
|
|
return newElement;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return oldElement;
|
|
|
|
|
},
|
|
|
|
|
true,
|
|
|
|
|
),
|
|
|
|
|
appState: {
|
|
|
|
|
...appState,
|
|
|
|
|
// update state only if we've set all select text elements to
|
|
|
|
|
// the same font size
|
|
|
|
|
currentItemFontSize:
|
|
|
|
|
newFontSizes.size === 1
|
|
|
|
|
? [...newFontSizes][0]
|
|
|
|
|
: appState.currentItemFontSize,
|
|
|
|
|
},
|
|
|
|
|
commitToHistory: true,
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
export const actionChangeStrokeColor = register({
|
|
|
|
|
name: "changeStrokeColor",
|
|
|
|
|
perform: (elements, appState, value) => {
|
|
|
|
@ -438,33 +520,7 @@ export const actionChangeOpacity = register({
|
|
|
|
|
export const actionChangeFontSize = register({
|
|
|
|
|
name: "changeFontSize",
|
|
|
|
|
perform: (elements, appState, value) => {
|
|
|
|
|
return {
|
|
|
|
|
elements: changeProperty(
|
|
|
|
|
elements,
|
|
|
|
|
appState,
|
|
|
|
|
(el) => {
|
|
|
|
|
if (isTextElement(el)) {
|
|
|
|
|
const element: ExcalidrawTextElement = newElementWith(el, {
|
|
|
|
|
fontSize: value,
|
|
|
|
|
});
|
|
|
|
|
let container = null;
|
|
|
|
|
if (el.containerId) {
|
|
|
|
|
container = Scene.getScene(el)!.getElement(el.containerId);
|
|
|
|
|
}
|
|
|
|
|
redrawTextBoundingBox(element, container, appState);
|
|
|
|
|
return element;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return el;
|
|
|
|
|
},
|
|
|
|
|
true,
|
|
|
|
|
),
|
|
|
|
|
appState: {
|
|
|
|
|
...appState,
|
|
|
|
|
currentItemFontSize: value,
|
|
|
|
|
},
|
|
|
|
|
commitToHistory: true,
|
|
|
|
|
};
|
|
|
|
|
return changeFontSize(elements, appState, () => value);
|
|
|
|
|
},
|
|
|
|
|
PanelComponent: ({ elements, appState, updateData }) => (
|
|
|
|
|
<fieldset>
|
|
|
|
@ -514,6 +570,44 @@ export const actionChangeFontSize = register({
|
|
|
|
|
),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const actionDecreaseFontSize = register({
|
|
|
|
|
name: "decreaseFontSize",
|
|
|
|
|
perform: (elements, appState, value) => {
|
|
|
|
|
return changeFontSize(elements, appState, (element) =>
|
|
|
|
|
Math.round(
|
|
|
|
|
// get previous value before relative increase (doesn't work fully
|
|
|
|
|
// due to rounding and float precision issues)
|
|
|
|
|
(1 / (1 + FONT_SIZE_RELATIVE_INCREASE_STEP)) * element.fontSize,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
keyTest: (event) => {
|
|
|
|
|
return (
|
|
|
|
|
event[KEYS.CTRL_OR_CMD] &&
|
|
|
|
|
event.shiftKey &&
|
|
|
|
|
// KEYS.COMMA needed for MacOS
|
|
|
|
|
(event.key === KEYS.CHEVRON_LEFT || event.key === KEYS.COMMA)
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const actionIncreaseFontSize = register({
|
|
|
|
|
name: "increaseFontSize",
|
|
|
|
|
perform: (elements, appState, value) => {
|
|
|
|
|
return changeFontSize(elements, appState, (element) =>
|
|
|
|
|
Math.round(element.fontSize * (1 + FONT_SIZE_RELATIVE_INCREASE_STEP)),
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
keyTest: (event) => {
|
|
|
|
|
return (
|
|
|
|
|
event[KEYS.CTRL_OR_CMD] &&
|
|
|
|
|
event.shiftKey &&
|
|
|
|
|
// KEYS.PERIOD needed for MacOS
|
|
|
|
|
(event.key === KEYS.CHEVRON_RIGHT || event.key === KEYS.PERIOD)
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const actionChangeFontFamily = register({
|
|
|
|
|
name: "changeFontFamily",
|
|
|
|
|
perform: (elements, appState, value) => {
|
|
|
|
@ -521,20 +615,23 @@ export const actionChangeFontFamily = register({
|
|
|
|
|
elements: changeProperty(
|
|
|
|
|
elements,
|
|
|
|
|
appState,
|
|
|
|
|
(el) => {
|
|
|
|
|
if (isTextElement(el)) {
|
|
|
|
|
const element: ExcalidrawTextElement = newElementWith(el, {
|
|
|
|
|
fontFamily: value,
|
|
|
|
|
});
|
|
|
|
|
let container = null;
|
|
|
|
|
if (el.containerId) {
|
|
|
|
|
container = Scene.getScene(el)!.getElement(el.containerId);
|
|
|
|
|
}
|
|
|
|
|
redrawTextBoundingBox(element, container, appState);
|
|
|
|
|
return element;
|
|
|
|
|
(oldElement) => {
|
|
|
|
|
if (isTextElement(oldElement)) {
|
|
|
|
|
const newElement: ExcalidrawTextElement = newElementWith(
|
|
|
|
|
oldElement,
|
|
|
|
|
{
|
|
|
|
|
fontFamily: value,
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
redrawTextBoundingBox(
|
|
|
|
|
newElement,
|
|
|
|
|
getContainerElement(oldElement),
|
|
|
|
|
appState,
|
|
|
|
|
);
|
|
|
|
|
return newElement;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return el;
|
|
|
|
|
return oldElement;
|
|
|
|
|
},
|
|
|
|
|
true,
|
|
|
|
|
),
|
|
|
|
@ -603,20 +700,23 @@ export const actionChangeTextAlign = register({
|
|
|
|
|
elements: changeProperty(
|
|
|
|
|
elements,
|
|
|
|
|
appState,
|
|
|
|
|
(el) => {
|
|
|
|
|
if (isTextElement(el)) {
|
|
|
|
|
const element: ExcalidrawTextElement = newElementWith(el, {
|
|
|
|
|
textAlign: value,
|
|
|
|
|
});
|
|
|
|
|
let container = null;
|
|
|
|
|
if (el.containerId) {
|
|
|
|
|
container = Scene.getScene(el)!.getElement(el.containerId);
|
|
|
|
|
}
|
|
|
|
|
redrawTextBoundingBox(element, container, appState);
|
|
|
|
|
return element;
|
|
|
|
|
(oldElement) => {
|
|
|
|
|
if (isTextElement(oldElement)) {
|
|
|
|
|
const newElement: ExcalidrawTextElement = newElementWith(
|
|
|
|
|
oldElement,
|
|
|
|
|
{
|
|
|
|
|
textAlign: value,
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
redrawTextBoundingBox(
|
|
|
|
|
newElement,
|
|
|
|
|
getContainerElement(oldElement),
|
|
|
|
|
appState,
|
|
|
|
|
);
|
|
|
|
|
return newElement;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return el;
|
|
|
|
|
return oldElement;
|
|
|
|
|
},
|
|
|
|
|
true,
|
|
|
|
|
),
|
|
|
|
|