|
@@ -41,9 +41,16 @@ import {
|
|
isTextElement,
|
|
isTextElement,
|
|
redrawTextBoundingBox,
|
|
redrawTextBoundingBox,
|
|
} from "../element";
|
|
} 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 {
|
|
import {
|
|
Arrowhead,
|
|
Arrowhead,
|
|
ExcalidrawElement,
|
|
ExcalidrawElement,
|
|
@@ -53,6 +60,7 @@ import {
|
|
TextAlign,
|
|
TextAlign,
|
|
} from "../element/types";
|
|
} from "../element/types";
|
|
import { getLanguage, t } from "../i18n";
|
|
import { getLanguage, t } from "../i18n";
|
|
|
|
+import { KEYS } from "../keys";
|
|
import { randomInteger } from "../random";
|
|
import { randomInteger } from "../random";
|
|
import {
|
|
import {
|
|
canChangeSharpness,
|
|
canChangeSharpness,
|
|
@@ -63,10 +71,11 @@ import {
|
|
isSomeElementSelected,
|
|
isSomeElementSelected,
|
|
} from "../scene";
|
|
} from "../scene";
|
|
import { hasStrokeColor } from "../scene/comparisons";
|
|
import { hasStrokeColor } from "../scene/comparisons";
|
|
-import Scene from "../scene/Scene";
|
|
|
|
import { arrayToMap } from "../utils";
|
|
import { arrayToMap } from "../utils";
|
|
import { register } from "./register";
|
|
import { register } from "./register";
|
|
|
|
|
|
|
|
+const FONT_SIZE_RELATIVE_INCREASE_STEP = 0.1;
|
|
|
|
+
|
|
const changeProperty = (
|
|
const changeProperty = (
|
|
elements: readonly ExcalidrawElement[],
|
|
elements: readonly ExcalidrawElement[],
|
|
appState: AppState,
|
|
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({
|
|
export const actionChangeStrokeColor = register({
|
|
name: "changeStrokeColor",
|
|
name: "changeStrokeColor",
|
|
perform: (elements, appState, value) => {
|
|
perform: (elements, appState, value) => {
|
|
@@ -438,33 +520,7 @@ export const actionChangeOpacity = register({
|
|
export const actionChangeFontSize = register({
|
|
export const actionChangeFontSize = register({
|
|
name: "changeFontSize",
|
|
name: "changeFontSize",
|
|
perform: (elements, appState, value) => {
|
|
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 }) => (
|
|
PanelComponent: ({ elements, appState, updateData }) => (
|
|
<fieldset>
|
|
<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({
|
|
export const actionChangeFontFamily = register({
|
|
name: "changeFontFamily",
|
|
name: "changeFontFamily",
|
|
perform: (elements, appState, value) => {
|
|
perform: (elements, appState, value) => {
|
|
@@ -521,20 +615,23 @@ export const actionChangeFontFamily = register({
|
|
elements: changeProperty(
|
|
elements: changeProperty(
|
|
elements,
|
|
elements,
|
|
appState,
|
|
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,
|
|
true,
|
|
),
|
|
),
|
|
@@ -603,20 +700,23 @@ export const actionChangeTextAlign = register({
|
|
elements: changeProperty(
|
|
elements: changeProperty(
|
|
elements,
|
|
elements,
|
|
appState,
|
|
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,
|
|
true,
|
|
),
|
|
),
|