mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-12-05 03:55:06 +01:00
refactor: make element type conversion more generic (#9504)
* feat: add `reduceToCommonValue()` & improve opacity slider * feat: generalize and simplify type conversion cache * refactor: change cache from atoms to Map * feat: always attempt to reuse original fontSize when converting generic types
This commit is contained in:
@@ -20,6 +20,7 @@ import {
|
||||
getShortcutKey,
|
||||
tupleToCoors,
|
||||
getLineHeight,
|
||||
reduceToCommonValue,
|
||||
} from "@excalidraw/common";
|
||||
|
||||
import { getNonDeletedElements } from "@excalidraw/element";
|
||||
@@ -130,7 +131,6 @@ import { Fonts } from "../fonts";
|
||||
import { getLanguage, t } from "../i18n";
|
||||
import {
|
||||
canHaveArrowheads,
|
||||
getCommonAttributeOfSelectedElements,
|
||||
getSelectedElements,
|
||||
getTargetElements,
|
||||
isSomeElementSelected,
|
||||
@@ -167,12 +167,12 @@ export const changeProperty = (
|
||||
|
||||
export const getFormValue = function <T extends Primitive>(
|
||||
elements: readonly ExcalidrawElement[],
|
||||
appState: AppState,
|
||||
app: AppClassProperties,
|
||||
getAttribute: (element: ExcalidrawElement) => T,
|
||||
isRelevantElement: true | ((element: ExcalidrawElement) => boolean),
|
||||
defaultValue: T | ((isSomeElementSelected: boolean) => T),
|
||||
): T {
|
||||
const editingTextElement = appState.editingTextElement;
|
||||
const editingTextElement = app.state.editingTextElement;
|
||||
const nonDeletedElements = getNonDeletedElements(elements);
|
||||
|
||||
let ret: T | null = null;
|
||||
@@ -182,17 +182,17 @@ export const getFormValue = function <T extends Primitive>(
|
||||
}
|
||||
|
||||
if (!ret) {
|
||||
const hasSelection = isSomeElementSelected(nonDeletedElements, appState);
|
||||
const hasSelection = isSomeElementSelected(nonDeletedElements, app.state);
|
||||
|
||||
if (hasSelection) {
|
||||
const selectedElements = app.scene.getSelectedElements(app.state);
|
||||
const targetElements =
|
||||
isRelevantElement === true
|
||||
? selectedElements
|
||||
: selectedElements.filter((el) => isRelevantElement(el));
|
||||
|
||||
ret =
|
||||
getCommonAttributeOfSelectedElements(
|
||||
isRelevantElement === true
|
||||
? nonDeletedElements
|
||||
: nonDeletedElements.filter((el) => isRelevantElement(el)),
|
||||
appState,
|
||||
getAttribute,
|
||||
) ??
|
||||
reduceToCommonValue(targetElements, getAttribute) ??
|
||||
(typeof defaultValue === "function"
|
||||
? defaultValue(true)
|
||||
: defaultValue);
|
||||
@@ -320,7 +320,7 @@ export const actionChangeStrokeColor = register({
|
||||
: CaptureUpdateAction.EVENTUALLY,
|
||||
};
|
||||
},
|
||||
PanelComponent: ({ elements, appState, updateData, appProps }) => (
|
||||
PanelComponent: ({ elements, appState, updateData, app }) => (
|
||||
<>
|
||||
<h3 aria-hidden="true">{t("labels.stroke")}</h3>
|
||||
<ColorPicker
|
||||
@@ -330,7 +330,7 @@ export const actionChangeStrokeColor = register({
|
||||
label={t("labels.stroke")}
|
||||
color={getFormValue(
|
||||
elements,
|
||||
appState,
|
||||
app,
|
||||
(element) => element.strokeColor,
|
||||
true,
|
||||
appState.currentItemStrokeColor,
|
||||
@@ -366,7 +366,7 @@ export const actionChangeBackgroundColor = register({
|
||||
: CaptureUpdateAction.EVENTUALLY,
|
||||
};
|
||||
},
|
||||
PanelComponent: ({ elements, appState, updateData, appProps }) => (
|
||||
PanelComponent: ({ elements, appState, updateData, app }) => (
|
||||
<>
|
||||
<h3 aria-hidden="true">{t("labels.background")}</h3>
|
||||
<ColorPicker
|
||||
@@ -376,7 +376,7 @@ export const actionChangeBackgroundColor = register({
|
||||
label={t("labels.background")}
|
||||
color={getFormValue(
|
||||
elements,
|
||||
appState,
|
||||
app,
|
||||
(element) => element.backgroundColor,
|
||||
true,
|
||||
appState.currentItemBackgroundColor,
|
||||
@@ -410,7 +410,7 @@ export const actionChangeFillStyle = register({
|
||||
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
||||
};
|
||||
},
|
||||
PanelComponent: ({ elements, appState, updateData }) => {
|
||||
PanelComponent: ({ elements, appState, updateData, app }) => {
|
||||
const selectedElements = getSelectedElements(elements, appState);
|
||||
const allElementsZigZag =
|
||||
selectedElements.length > 0 &&
|
||||
@@ -446,7 +446,7 @@ export const actionChangeFillStyle = register({
|
||||
]}
|
||||
value={getFormValue(
|
||||
elements,
|
||||
appState,
|
||||
app,
|
||||
(element) => element.fillStyle,
|
||||
(element) => element.hasOwnProperty("fillStyle"),
|
||||
(hasSelection) =>
|
||||
@@ -483,7 +483,7 @@ export const actionChangeStrokeWidth = register({
|
||||
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
||||
};
|
||||
},
|
||||
PanelComponent: ({ elements, appState, updateData }) => (
|
||||
PanelComponent: ({ elements, appState, updateData, app }) => (
|
||||
<fieldset>
|
||||
<legend>{t("labels.strokeWidth")}</legend>
|
||||
<ButtonIconSelect
|
||||
@@ -510,7 +510,7 @@ export const actionChangeStrokeWidth = register({
|
||||
]}
|
||||
value={getFormValue(
|
||||
elements,
|
||||
appState,
|
||||
app,
|
||||
(element) => element.strokeWidth,
|
||||
(element) => element.hasOwnProperty("strokeWidth"),
|
||||
(hasSelection) =>
|
||||
@@ -538,7 +538,7 @@ export const actionChangeSloppiness = register({
|
||||
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
||||
};
|
||||
},
|
||||
PanelComponent: ({ elements, appState, updateData }) => (
|
||||
PanelComponent: ({ elements, appState, updateData, app }) => (
|
||||
<fieldset>
|
||||
<legend>{t("labels.sloppiness")}</legend>
|
||||
<ButtonIconSelect
|
||||
@@ -562,7 +562,7 @@ export const actionChangeSloppiness = register({
|
||||
]}
|
||||
value={getFormValue(
|
||||
elements,
|
||||
appState,
|
||||
app,
|
||||
(element) => element.roughness,
|
||||
(element) => element.hasOwnProperty("roughness"),
|
||||
(hasSelection) =>
|
||||
@@ -589,7 +589,7 @@ export const actionChangeStrokeStyle = register({
|
||||
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
||||
};
|
||||
},
|
||||
PanelComponent: ({ elements, appState, updateData }) => (
|
||||
PanelComponent: ({ elements, appState, updateData, app }) => (
|
||||
<fieldset>
|
||||
<legend>{t("labels.strokeStyle")}</legend>
|
||||
<ButtonIconSelect
|
||||
@@ -613,7 +613,7 @@ export const actionChangeStrokeStyle = register({
|
||||
]}
|
||||
value={getFormValue(
|
||||
elements,
|
||||
appState,
|
||||
app,
|
||||
(element) => element.strokeStyle,
|
||||
(element) => element.hasOwnProperty("strokeStyle"),
|
||||
(hasSelection) =>
|
||||
@@ -644,13 +644,8 @@ export const actionChangeOpacity = register({
|
||||
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
||||
};
|
||||
},
|
||||
PanelComponent: ({ elements, appState, updateData }) => (
|
||||
<Range
|
||||
updateData={updateData}
|
||||
elements={elements}
|
||||
appState={appState}
|
||||
testId="opacity"
|
||||
/>
|
||||
PanelComponent: ({ app, updateData }) => (
|
||||
<Range updateData={updateData} app={app} testId="opacity" />
|
||||
),
|
||||
});
|
||||
|
||||
@@ -694,7 +689,7 @@ export const actionChangeFontSize = register({
|
||||
]}
|
||||
value={getFormValue(
|
||||
elements,
|
||||
appState,
|
||||
app,
|
||||
(element) => {
|
||||
if (isTextElement(element)) {
|
||||
return element.fontSize;
|
||||
@@ -992,7 +987,7 @@ export const actionChangeFontFamily = register({
|
||||
) =>
|
||||
getFormValue(
|
||||
elementsArray,
|
||||
appState,
|
||||
app,
|
||||
(element) => {
|
||||
if (isTextElement(element)) {
|
||||
return element.fontFamily;
|
||||
@@ -1030,7 +1025,7 @@ export const actionChangeFontFamily = register({
|
||||
|
||||
// popup props are not in sync, hence we are in the middle of an update, so keeping the previous value we've had
|
||||
return prevSelectedFontFamilyRef.current;
|
||||
}, [batchedData.openPopup, appState, elements, app.scene]);
|
||||
}, [batchedData.openPopup, appState, elements, app]);
|
||||
|
||||
useEffect(() => {
|
||||
prevSelectedFontFamilyRef.current = selectedFontFamily;
|
||||
@@ -1216,7 +1211,7 @@ export const actionChangeTextAlign = register({
|
||||
]}
|
||||
value={getFormValue(
|
||||
elements,
|
||||
appState,
|
||||
app,
|
||||
(element) => {
|
||||
if (isTextElement(element)) {
|
||||
return element.textAlign;
|
||||
@@ -1304,7 +1299,7 @@ export const actionChangeVerticalAlign = register({
|
||||
]}
|
||||
value={getFormValue(
|
||||
elements,
|
||||
appState,
|
||||
app,
|
||||
(element) => {
|
||||
if (isTextElement(element) && element.containerId) {
|
||||
return element.verticalAlign;
|
||||
@@ -1362,7 +1357,7 @@ export const actionChangeRoundness = register({
|
||||
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
||||
};
|
||||
},
|
||||
PanelComponent: ({ elements, appState, updateData }) => {
|
||||
PanelComponent: ({ elements, appState, updateData, app }) => {
|
||||
const targetElements = getTargetElements(
|
||||
getNonDeletedElements(elements),
|
||||
appState,
|
||||
@@ -1391,7 +1386,7 @@ export const actionChangeRoundness = register({
|
||||
]}
|
||||
value={getFormValue(
|
||||
elements,
|
||||
appState,
|
||||
app,
|
||||
(element) =>
|
||||
hasLegacyRoundness ? null : element.roundness ? "round" : "sharp",
|
||||
(element) =>
|
||||
@@ -1521,7 +1516,7 @@ export const actionChangeArrowhead = register({
|
||||
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
||||
};
|
||||
},
|
||||
PanelComponent: ({ elements, appState, updateData }) => {
|
||||
PanelComponent: ({ elements, appState, updateData, app }) => {
|
||||
const isRTL = getLanguage().rtl;
|
||||
|
||||
return (
|
||||
@@ -1533,7 +1528,7 @@ export const actionChangeArrowhead = register({
|
||||
options={getArrowheadOptions(!isRTL)}
|
||||
value={getFormValue<Arrowhead | null>(
|
||||
elements,
|
||||
appState,
|
||||
app,
|
||||
(element) =>
|
||||
isLinearElement(element) && canHaveArrowheads(element.type)
|
||||
? element.startArrowhead
|
||||
@@ -1550,7 +1545,7 @@ export const actionChangeArrowhead = register({
|
||||
options={getArrowheadOptions(!!isRTL)}
|
||||
value={getFormValue<Arrowhead | null>(
|
||||
elements,
|
||||
appState,
|
||||
app,
|
||||
(element) =>
|
||||
isLinearElement(element) && canHaveArrowheads(element.type)
|
||||
? element.endArrowhead
|
||||
@@ -1759,7 +1754,7 @@ export const actionChangeArrowType = register({
|
||||
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
||||
};
|
||||
},
|
||||
PanelComponent: ({ elements, appState, updateData }) => {
|
||||
PanelComponent: ({ elements, appState, updateData, app }) => {
|
||||
return (
|
||||
<fieldset>
|
||||
<legend>{t("labels.arrowtypes")}</legend>
|
||||
@@ -1787,7 +1782,7 @@ export const actionChangeArrowType = register({
|
||||
]}
|
||||
value={getFormValue(
|
||||
elements,
|
||||
appState,
|
||||
app,
|
||||
(element) => {
|
||||
if (isArrowElement(element)) {
|
||||
return element.elbowed
|
||||
|
||||
Reference in New Issue
Block a user