From 29e130193d66c22b0581ba3ce07cf7396623a4fb Mon Sep 17 00:00:00 2001 From: dib-a Date: Fri, 2 May 2025 18:56:12 +0200 Subject: [PATCH] Make stroke width configurable --- .../excalidraw/actions/actionProperties.tsx | 206 +++++++++--------- packages/excalidraw/components/Range.tsx | 47 ++-- 2 files changed, 133 insertions(+), 120 deletions(-) diff --git a/packages/excalidraw/actions/actionProperties.tsx b/packages/excalidraw/actions/actionProperties.tsx index df07960af..239ae8cf3 100644 --- a/packages/excalidraw/actions/actionProperties.tsx +++ b/packages/excalidraw/actions/actionProperties.tsx @@ -1,25 +1,25 @@ import { pointFrom } from "@excalidraw/math"; -import { useEffect, useMemo, useRef, useState } from "react"; + +import React, { useEffect, useMemo, useRef, useState } from "react"; import { + arrayToMap, + ARROW_TYPE, DEFAULT_ELEMENT_BACKGROUND_COLOR_PALETTE, DEFAULT_ELEMENT_BACKGROUND_PICKS, DEFAULT_ELEMENT_STROKE_COLOR_PALETTE, DEFAULT_ELEMENT_STROKE_PICKS, - ARROW_TYPE, DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, FONT_FAMILY, - ROUNDNESS, - STROKE_WIDTH, - VERTICAL_ALIGN, + getFontFamilyString, + getLineHeight, + getShortcutKey, KEYS, randomInteger, - arrayToMap, - getFontFamilyString, - getShortcutKey, + ROUNDNESS, tupleToCoors, - getLineHeight, + VERTICAL_ALIGN, } from "@excalidraw/common"; import { getNonDeletedElements } from "@excalidraw/element"; @@ -70,6 +70,49 @@ import type { import type Scene from "@excalidraw/element/Scene"; +import type { PanelComponentProps } from "@excalidraw/excalidraw/actions/types"; + +import { + ArrowheadArrowIcon, + ArrowheadBarIcon, + ArrowheadCircleIcon, + ArrowheadCircleOutlineIcon, + ArrowheadCrowfootIcon, + ArrowheadCrowfootOneIcon, + ArrowheadCrowfootOneOrManyIcon, + ArrowheadDiamondIcon, + ArrowheadDiamondOutlineIcon, + ArrowheadNoneIcon, + ArrowheadTriangleIcon, + ArrowheadTriangleOutlineIcon, + EdgeRoundIcon, + EdgeSharpIcon, + elbowArrowIcon, + FillCrossHatchIcon, + FillHachureIcon, + FillSolidIcon, + FillZigZagIcon, + FontSizeExtraLargeIcon, + fontSizeIcon, + FontSizeLargeIcon, + FontSizeMediumIcon, + FontSizeSmallIcon, + roundArrowIcon, + sharpArrowIcon, + SloppinessArchitectIcon, + SloppinessArtistIcon, + SloppinessCartoonistIcon, + StrokeStyleDashedIcon, + StrokeStyleDottedIcon, + StrokeWidthBaseIcon, + TextAlignBottomIcon, + TextAlignCenterIcon, + TextAlignLeftIcon, + TextAlignMiddleIcon, + TextAlignRightIcon, + TextAlignTopIcon, +} from "../components/icons"; + import { trackEvent } from "../analytics"; import { ButtonIconSelect } from "../components/ButtonIconSelect"; import { ColorPicker } from "../components/ColorPicker/ColorPicker"; @@ -79,48 +122,6 @@ import { IconPicker } from "../components/IconPicker"; // TextAlignTopIcon, TextAlignBottomIcon,TextAlignMiddleIcon, // ArrowHead icons import { Range } from "../components/Range"; -import { - ArrowheadArrowIcon, - ArrowheadBarIcon, - ArrowheadCircleIcon, - ArrowheadTriangleIcon, - ArrowheadNoneIcon, - StrokeStyleDashedIcon, - StrokeStyleDottedIcon, - TextAlignTopIcon, - TextAlignBottomIcon, - TextAlignMiddleIcon, - FillHachureIcon, - FillCrossHatchIcon, - FillSolidIcon, - SloppinessArchitectIcon, - SloppinessArtistIcon, - SloppinessCartoonistIcon, - StrokeWidthBaseIcon, - StrokeWidthBoldIcon, - StrokeWidthExtraBoldIcon, - FontSizeSmallIcon, - FontSizeMediumIcon, - FontSizeLargeIcon, - FontSizeExtraLargeIcon, - EdgeSharpIcon, - EdgeRoundIcon, - TextAlignLeftIcon, - TextAlignCenterIcon, - TextAlignRightIcon, - FillZigZagIcon, - ArrowheadTriangleOutlineIcon, - ArrowheadCircleOutlineIcon, - ArrowheadDiamondIcon, - ArrowheadDiamondOutlineIcon, - fontSizeIcon, - sharpArrowIcon, - roundArrowIcon, - elbowArrowIcon, - ArrowheadCrowfootIcon, - ArrowheadCrowfootOneIcon, - ArrowheadCrowfootOneOrManyIcon, -} from "../components/icons"; import { Fonts } from "../fonts"; import { getLanguage, t } from "../i18n"; @@ -131,11 +132,13 @@ import { getTargetElements, isSomeElementSelected, } from "../scene"; + import { CaptureUpdateAction } from "../store"; import { register } from "./register"; import type { CaptureUpdateActionType } from "../store"; + import type { AppClassProperties, AppState, Primitive } from "../types"; const FONT_SIZE_RELATIVE_INCREASE_STEP = 0.1; @@ -472,52 +475,42 @@ export const actionChangeStrokeWidth = register({ trackEvent: false, perform: (elements, appState, value) => { return { - elements: changeProperty(elements, appState, (el) => - newElementWith(el, { - strokeWidth: value, - }), + elements: changeProperty( + elements, + appState, + (el) => + newElementWith(el, { + strokeWidth: value, + }), + true, ), appState: { ...appState, currentItemStrokeWidth: value }, captureUpdate: CaptureUpdateAction.IMMEDIATELY, }; }, - PanelComponent: ({ elements, appState, updateData }) => ( -
- {t("labels.strokeWidth")} - element.strokeWidth, - (element) => element.hasOwnProperty("strokeWidth"), - (hasSelection) => - hasSelection ? null : appState.currentItemStrokeWidth, - )} - onChange={(value) => updateData(value)} + PanelComponent: ({ elements, appState, updateData }: PanelComponentProps) => { + const strokeWidthValue = getFormValue( + elements, + appState, + (element) => element.strokeWidth, + true, + appState.currentItemStrokeWidth, + ); + + return ( + -
- ), + ); + }, }); export const actionChangeSloppiness = register({ @@ -642,14 +635,29 @@ export const actionChangeOpacity = register({ captureUpdate: CaptureUpdateAction.IMMEDIATELY, }; }, - PanelComponent: ({ elements, appState, updateData }) => ( - - ), + PanelComponent: ({ elements, appState, updateData }: PanelComponentProps) => { + const opacityValue = getFormValue( + elements, + appState, + (element) => element.opacity, + true, + appState.currentItemOpacity, + ); + + return ( + + ); + }, }); export const actionChangeFontSize = register({ diff --git a/packages/excalidraw/components/Range.tsx b/packages/excalidraw/components/Range.tsx index 3ab9ede15..120d27479 100644 --- a/packages/excalidraw/components/Range.tsx +++ b/packages/excalidraw/components/Range.tsx @@ -1,8 +1,5 @@ import React, { useEffect } from "react"; -import { getFormValue } from "../actions/actionProperties"; -import { t } from "../i18n"; - import "./Range.scss"; export type RangeProps = { @@ -17,16 +14,21 @@ export const Range = ({ appState, elements, testId, -}: RangeProps) => { + min, + max, + step, + value, + label, +}: RangeProps & { + label: string; + min: number; + max: number; + step: number; + value: number; +}) => { const rangeRef = React.useRef(null); const valueRef = React.useRef(null); - const value = getFormValue( - elements, - appState, - (element) => element.opacity, - true, - appState.currentItemOpacity, - ); + useEffect(() => { if (rangeRef.current && valueRef.current) { const rangeElement = rangeRef.current; @@ -34,33 +36,36 @@ export const Range = ({ const inputWidth = rangeElement.offsetWidth; const thumbWidth = 15; // 15 is the width of the thumb const position = - (value / 100) * (inputWidth - thumbWidth) + thumbWidth / 2; + (value / (max - min)) * (inputWidth - thumbWidth) + thumbWidth / 2; valueElement.style.left = `${position}px`; - rangeElement.style.background = `linear-gradient(to right, var(--color-slider-track) 0%, var(--color-slider-track) ${value}%, var(--button-bg) ${value}%, var(--button-bg) 100%)`; + rangeElement.style.background = `linear-gradient(to right, var(--color-slider-track) 0%, var(--color-slider-track) ${ + ((value - min) * 100) / (max - min) + }%, var(--button-bg) ${ + ((value - min) * 100) / (max - min) + }%, var(--button-bg) 100%)`; } - }, [value]); + }, [value, max, min]); return ( );