|
@@ -1,4 +1,4 @@
|
|
-import { AppState } from "../../src/types";
|
|
|
|
|
|
+import { AppState, Primitive } from "../../src/types";
|
|
import {
|
|
import {
|
|
DEFAULT_ELEMENT_BACKGROUND_COLOR_PALETTE,
|
|
DEFAULT_ELEMENT_BACKGROUND_COLOR_PALETTE,
|
|
DEFAULT_ELEMENT_BACKGROUND_PICKS,
|
|
DEFAULT_ELEMENT_BACKGROUND_PICKS,
|
|
@@ -51,6 +51,7 @@ import {
|
|
DEFAULT_FONT_SIZE,
|
|
DEFAULT_FONT_SIZE,
|
|
FONT_FAMILY,
|
|
FONT_FAMILY,
|
|
ROUNDNESS,
|
|
ROUNDNESS,
|
|
|
|
+ STROKE_WIDTH,
|
|
VERTICAL_ALIGN,
|
|
VERTICAL_ALIGN,
|
|
} from "../constants";
|
|
} from "../constants";
|
|
import {
|
|
import {
|
|
@@ -82,7 +83,6 @@ import { getLanguage, t } from "../i18n";
|
|
import { KEYS } from "../keys";
|
|
import { KEYS } from "../keys";
|
|
import { randomInteger } from "../random";
|
|
import { randomInteger } from "../random";
|
|
import {
|
|
import {
|
|
- canChangeRoundness,
|
|
|
|
canHaveArrowheads,
|
|
canHaveArrowheads,
|
|
getCommonAttributeOfSelectedElements,
|
|
getCommonAttributeOfSelectedElements,
|
|
getSelectedElements,
|
|
getSelectedElements,
|
|
@@ -118,25 +118,44 @@ export const changeProperty = (
|
|
});
|
|
});
|
|
};
|
|
};
|
|
|
|
|
|
-export const getFormValue = function <T>(
|
|
|
|
|
|
+export const getFormValue = function <T extends Primitive>(
|
|
elements: readonly ExcalidrawElement[],
|
|
elements: readonly ExcalidrawElement[],
|
|
appState: AppState,
|
|
appState: AppState,
|
|
getAttribute: (element: ExcalidrawElement) => T,
|
|
getAttribute: (element: ExcalidrawElement) => T,
|
|
- defaultValue: T,
|
|
|
|
|
|
+ isRelevantElement: true | ((element: ExcalidrawElement) => boolean),
|
|
|
|
+ defaultValue: T | ((isSomeElementSelected: boolean) => T),
|
|
): T {
|
|
): T {
|
|
const editingElement = appState.editingElement;
|
|
const editingElement = appState.editingElement;
|
|
const nonDeletedElements = getNonDeletedElements(elements);
|
|
const nonDeletedElements = getNonDeletedElements(elements);
|
|
- return (
|
|
|
|
- (editingElement && getAttribute(editingElement)) ??
|
|
|
|
- (isSomeElementSelected(nonDeletedElements, appState)
|
|
|
|
- ? getCommonAttributeOfSelectedElements(
|
|
|
|
- nonDeletedElements,
|
|
|
|
|
|
+
|
|
|
|
+ let ret: T | null = null;
|
|
|
|
+
|
|
|
|
+ if (editingElement) {
|
|
|
|
+ ret = getAttribute(editingElement);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!ret) {
|
|
|
|
+ const hasSelection = isSomeElementSelected(nonDeletedElements, appState);
|
|
|
|
+
|
|
|
|
+ if (hasSelection) {
|
|
|
|
+ ret =
|
|
|
|
+ getCommonAttributeOfSelectedElements(
|
|
|
|
+ isRelevantElement === true
|
|
|
|
+ ? nonDeletedElements
|
|
|
|
+ : nonDeletedElements.filter((el) => isRelevantElement(el)),
|
|
appState,
|
|
appState,
|
|
getAttribute,
|
|
getAttribute,
|
|
- )
|
|
|
|
- : defaultValue) ??
|
|
|
|
- defaultValue
|
|
|
|
- );
|
|
|
|
|
|
+ ) ??
|
|
|
|
+ (typeof defaultValue === "function"
|
|
|
|
+ ? defaultValue(true)
|
|
|
|
+ : defaultValue);
|
|
|
|
+ } else {
|
|
|
|
+ ret =
|
|
|
|
+ typeof defaultValue === "function" ? defaultValue(false) : defaultValue;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return ret;
|
|
};
|
|
};
|
|
|
|
|
|
const offsetElementAfterFontResize = (
|
|
const offsetElementAfterFontResize = (
|
|
@@ -247,6 +266,7 @@ export const actionChangeStrokeColor = register({
|
|
elements,
|
|
elements,
|
|
appState,
|
|
appState,
|
|
(element) => element.strokeColor,
|
|
(element) => element.strokeColor,
|
|
|
|
+ true,
|
|
appState.currentItemStrokeColor,
|
|
appState.currentItemStrokeColor,
|
|
)}
|
|
)}
|
|
onChange={(color) => updateData({ currentItemStrokeColor: color })}
|
|
onChange={(color) => updateData({ currentItemStrokeColor: color })}
|
|
@@ -289,6 +309,7 @@ export const actionChangeBackgroundColor = register({
|
|
elements,
|
|
elements,
|
|
appState,
|
|
appState,
|
|
(element) => element.backgroundColor,
|
|
(element) => element.backgroundColor,
|
|
|
|
+ true,
|
|
appState.currentItemBackgroundColor,
|
|
appState.currentItemBackgroundColor,
|
|
)}
|
|
)}
|
|
onChange={(color) => updateData({ currentItemBackgroundColor: color })}
|
|
onChange={(color) => updateData({ currentItemBackgroundColor: color })}
|
|
@@ -338,23 +359,28 @@ export const actionChangeFillStyle = register({
|
|
} (${getShortcutKey("Alt-Click")})`,
|
|
} (${getShortcutKey("Alt-Click")})`,
|
|
icon: allElementsZigZag ? FillZigZagIcon : FillHachureIcon,
|
|
icon: allElementsZigZag ? FillZigZagIcon : FillHachureIcon,
|
|
active: allElementsZigZag ? true : undefined,
|
|
active: allElementsZigZag ? true : undefined,
|
|
|
|
+ testId: `fill-hachure`,
|
|
},
|
|
},
|
|
{
|
|
{
|
|
value: "cross-hatch",
|
|
value: "cross-hatch",
|
|
text: t("labels.crossHatch"),
|
|
text: t("labels.crossHatch"),
|
|
icon: FillCrossHatchIcon,
|
|
icon: FillCrossHatchIcon,
|
|
|
|
+ testId: `fill-cross-hatch`,
|
|
},
|
|
},
|
|
{
|
|
{
|
|
value: "solid",
|
|
value: "solid",
|
|
text: t("labels.solid"),
|
|
text: t("labels.solid"),
|
|
icon: FillSolidIcon,
|
|
icon: FillSolidIcon,
|
|
|
|
+ testId: `fill-solid`,
|
|
},
|
|
},
|
|
]}
|
|
]}
|
|
value={getFormValue(
|
|
value={getFormValue(
|
|
elements,
|
|
elements,
|
|
appState,
|
|
appState,
|
|
(element) => element.fillStyle,
|
|
(element) => element.fillStyle,
|
|
- appState.currentItemFillStyle,
|
|
|
|
|
|
+ (element) => element.hasOwnProperty("fillStyle"),
|
|
|
|
+ (hasSelection) =>
|
|
|
|
+ hasSelection ? null : appState.currentItemFillStyle,
|
|
)}
|
|
)}
|
|
onClick={(value, event) => {
|
|
onClick={(value, event) => {
|
|
const nextValue =
|
|
const nextValue =
|
|
@@ -393,26 +419,31 @@ export const actionChangeStrokeWidth = register({
|
|
group="stroke-width"
|
|
group="stroke-width"
|
|
options={[
|
|
options={[
|
|
{
|
|
{
|
|
- value: 1,
|
|
|
|
|
|
+ value: STROKE_WIDTH.thin,
|
|
text: t("labels.thin"),
|
|
text: t("labels.thin"),
|
|
icon: StrokeWidthBaseIcon,
|
|
icon: StrokeWidthBaseIcon,
|
|
|
|
+ testId: "strokeWidth-thin",
|
|
},
|
|
},
|
|
{
|
|
{
|
|
- value: 2,
|
|
|
|
|
|
+ value: STROKE_WIDTH.bold,
|
|
text: t("labels.bold"),
|
|
text: t("labels.bold"),
|
|
icon: StrokeWidthBoldIcon,
|
|
icon: StrokeWidthBoldIcon,
|
|
|
|
+ testId: "strokeWidth-bold",
|
|
},
|
|
},
|
|
{
|
|
{
|
|
- value: 4,
|
|
|
|
|
|
+ value: STROKE_WIDTH.extraBold,
|
|
text: t("labels.extraBold"),
|
|
text: t("labels.extraBold"),
|
|
icon: StrokeWidthExtraBoldIcon,
|
|
icon: StrokeWidthExtraBoldIcon,
|
|
|
|
+ testId: "strokeWidth-extraBold",
|
|
},
|
|
},
|
|
]}
|
|
]}
|
|
value={getFormValue(
|
|
value={getFormValue(
|
|
elements,
|
|
elements,
|
|
appState,
|
|
appState,
|
|
(element) => element.strokeWidth,
|
|
(element) => element.strokeWidth,
|
|
- appState.currentItemStrokeWidth,
|
|
|
|
|
|
+ (element) => element.hasOwnProperty("strokeWidth"),
|
|
|
|
+ (hasSelection) =>
|
|
|
|
+ hasSelection ? null : appState.currentItemStrokeWidth,
|
|
)}
|
|
)}
|
|
onChange={(value) => updateData(value)}
|
|
onChange={(value) => updateData(value)}
|
|
/>
|
|
/>
|
|
@@ -461,7 +492,9 @@ export const actionChangeSloppiness = register({
|
|
elements,
|
|
elements,
|
|
appState,
|
|
appState,
|
|
(element) => element.roughness,
|
|
(element) => element.roughness,
|
|
- appState.currentItemRoughness,
|
|
|
|
|
|
+ (element) => element.hasOwnProperty("roughness"),
|
|
|
|
+ (hasSelection) =>
|
|
|
|
+ hasSelection ? null : appState.currentItemRoughness,
|
|
)}
|
|
)}
|
|
onChange={(value) => updateData(value)}
|
|
onChange={(value) => updateData(value)}
|
|
/>
|
|
/>
|
|
@@ -509,7 +542,9 @@ export const actionChangeStrokeStyle = register({
|
|
elements,
|
|
elements,
|
|
appState,
|
|
appState,
|
|
(element) => element.strokeStyle,
|
|
(element) => element.strokeStyle,
|
|
- appState.currentItemStrokeStyle,
|
|
|
|
|
|
+ (element) => element.hasOwnProperty("strokeStyle"),
|
|
|
|
+ (hasSelection) =>
|
|
|
|
+ hasSelection ? null : appState.currentItemStrokeStyle,
|
|
)}
|
|
)}
|
|
onChange={(value) => updateData(value)}
|
|
onChange={(value) => updateData(value)}
|
|
/>
|
|
/>
|
|
@@ -549,6 +584,7 @@ export const actionChangeOpacity = register({
|
|
elements,
|
|
elements,
|
|
appState,
|
|
appState,
|
|
(element) => element.opacity,
|
|
(element) => element.opacity,
|
|
|
|
+ true,
|
|
appState.currentItemOpacity,
|
|
appState.currentItemOpacity,
|
|
) ?? undefined
|
|
) ?? undefined
|
|
}
|
|
}
|
|
@@ -607,7 +643,12 @@ export const actionChangeFontSize = register({
|
|
}
|
|
}
|
|
return null;
|
|
return null;
|
|
},
|
|
},
|
|
- appState.currentItemFontSize || DEFAULT_FONT_SIZE,
|
|
|
|
|
|
+ (element) =>
|
|
|
|
+ isTextElement(element) || getBoundTextElement(element) !== null,
|
|
|
|
+ (hasSelection) =>
|
|
|
|
+ hasSelection
|
|
|
|
+ ? null
|
|
|
|
+ : appState.currentItemFontSize || DEFAULT_FONT_SIZE,
|
|
)}
|
|
)}
|
|
onChange={(value) => updateData(value)}
|
|
onChange={(value) => updateData(value)}
|
|
/>
|
|
/>
|
|
@@ -692,21 +733,25 @@ export const actionChangeFontFamily = register({
|
|
value: FontFamilyValues;
|
|
value: FontFamilyValues;
|
|
text: string;
|
|
text: string;
|
|
icon: JSX.Element;
|
|
icon: JSX.Element;
|
|
|
|
+ testId: string;
|
|
}[] = [
|
|
}[] = [
|
|
{
|
|
{
|
|
value: FONT_FAMILY.Virgil,
|
|
value: FONT_FAMILY.Virgil,
|
|
text: t("labels.handDrawn"),
|
|
text: t("labels.handDrawn"),
|
|
icon: FreedrawIcon,
|
|
icon: FreedrawIcon,
|
|
|
|
+ testId: "font-family-virgil",
|
|
},
|
|
},
|
|
{
|
|
{
|
|
value: FONT_FAMILY.Helvetica,
|
|
value: FONT_FAMILY.Helvetica,
|
|
text: t("labels.normal"),
|
|
text: t("labels.normal"),
|
|
icon: FontFamilyNormalIcon,
|
|
icon: FontFamilyNormalIcon,
|
|
|
|
+ testId: "font-family-normal",
|
|
},
|
|
},
|
|
{
|
|
{
|
|
value: FONT_FAMILY.Cascadia,
|
|
value: FONT_FAMILY.Cascadia,
|
|
text: t("labels.code"),
|
|
text: t("labels.code"),
|
|
icon: FontFamilyCodeIcon,
|
|
icon: FontFamilyCodeIcon,
|
|
|
|
+ testId: "font-family-code",
|
|
},
|
|
},
|
|
];
|
|
];
|
|
|
|
|
|
@@ -729,7 +774,12 @@ export const actionChangeFontFamily = register({
|
|
}
|
|
}
|
|
return null;
|
|
return null;
|
|
},
|
|
},
|
|
- appState.currentItemFontFamily || DEFAULT_FONT_FAMILY,
|
|
|
|
|
|
+ (element) =>
|
|
|
|
+ isTextElement(element) || getBoundTextElement(element) !== null,
|
|
|
|
+ (hasSelection) =>
|
|
|
|
+ hasSelection
|
|
|
|
+ ? null
|
|
|
|
+ : appState.currentItemFontFamily || DEFAULT_FONT_FAMILY,
|
|
)}
|
|
)}
|
|
onChange={(value) => updateData(value)}
|
|
onChange={(value) => updateData(value)}
|
|
/>
|
|
/>
|
|
@@ -806,7 +856,10 @@ export const actionChangeTextAlign = register({
|
|
}
|
|
}
|
|
return null;
|
|
return null;
|
|
},
|
|
},
|
|
- appState.currentItemTextAlign,
|
|
|
|
|
|
+ (element) =>
|
|
|
|
+ isTextElement(element) || getBoundTextElement(element) !== null,
|
|
|
|
+ (hasSelection) =>
|
|
|
|
+ hasSelection ? null : appState.currentItemTextAlign,
|
|
)}
|
|
)}
|
|
onChange={(value) => updateData(value)}
|
|
onChange={(value) => updateData(value)}
|
|
/>
|
|
/>
|
|
@@ -882,7 +935,9 @@ export const actionChangeVerticalAlign = register({
|
|
}
|
|
}
|
|
return null;
|
|
return null;
|
|
},
|
|
},
|
|
- VERTICAL_ALIGN.MIDDLE,
|
|
|
|
|
|
+ (element) =>
|
|
|
|
+ isTextElement(element) || getBoundTextElement(element) !== null,
|
|
|
|
+ (hasSelection) => (hasSelection ? null : VERTICAL_ALIGN.MIDDLE),
|
|
)}
|
|
)}
|
|
onChange={(value) => updateData(value)}
|
|
onChange={(value) => updateData(value)}
|
|
/>
|
|
/>
|
|
@@ -947,9 +1002,9 @@ export const actionChangeRoundness = register({
|
|
appState,
|
|
appState,
|
|
(element) =>
|
|
(element) =>
|
|
hasLegacyRoundness ? null : element.roundness ? "round" : "sharp",
|
|
hasLegacyRoundness ? null : element.roundness ? "round" : "sharp",
|
|
- (canChangeRoundness(appState.activeTool.type) &&
|
|
|
|
- appState.currentItemRoundness) ||
|
|
|
|
- null,
|
|
|
|
|
|
+ (element) => element.hasOwnProperty("roundness"),
|
|
|
|
+ (hasSelection) =>
|
|
|
|
+ hasSelection ? null : appState.currentItemRoundness,
|
|
)}
|
|
)}
|
|
onChange={(value) => updateData(value)}
|
|
onChange={(value) => updateData(value)}
|
|
/>
|
|
/>
|
|
@@ -1043,6 +1098,7 @@ export const actionChangeArrowhead = register({
|
|
isLinearElement(element) && canHaveArrowheads(element.type)
|
|
isLinearElement(element) && canHaveArrowheads(element.type)
|
|
? element.startArrowhead
|
|
? element.startArrowhead
|
|
: appState.currentItemStartArrowhead,
|
|
: appState.currentItemStartArrowhead,
|
|
|
|
+ true,
|
|
appState.currentItemStartArrowhead,
|
|
appState.currentItemStartArrowhead,
|
|
)}
|
|
)}
|
|
onChange={(value) => updateData({ position: "start", type: value })}
|
|
onChange={(value) => updateData({ position: "start", type: value })}
|
|
@@ -1089,6 +1145,7 @@ export const actionChangeArrowhead = register({
|
|
isLinearElement(element) && canHaveArrowheads(element.type)
|
|
isLinearElement(element) && canHaveArrowheads(element.type)
|
|
? element.endArrowhead
|
|
? element.endArrowhead
|
|
: appState.currentItemEndArrowhead,
|
|
: appState.currentItemEndArrowhead,
|
|
|
|
+ true,
|
|
appState.currentItemEndArrowhead,
|
|
appState.currentItemEndArrowhead,
|
|
)}
|
|
)}
|
|
onChange={(value) => updateData({ position: "end", type: value })}
|
|
onChange={(value) => updateData({ position: "end", type: value })}
|