This commit is contained in:
dib-a 2025-05-02 18:57:13 +02:00 committed by GitHub
commit 0021bfc1b1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 133 additions and 120 deletions

View file

@ -1,25 +1,25 @@
import { pointFrom } from "@excalidraw/math"; import { pointFrom } from "@excalidraw/math";
import { useEffect, useMemo, useRef, useState } from "react";
import React, { useEffect, useMemo, useRef, useState } from "react";
import { import {
arrayToMap,
ARROW_TYPE,
DEFAULT_ELEMENT_BACKGROUND_COLOR_PALETTE, DEFAULT_ELEMENT_BACKGROUND_COLOR_PALETTE,
DEFAULT_ELEMENT_BACKGROUND_PICKS, DEFAULT_ELEMENT_BACKGROUND_PICKS,
DEFAULT_ELEMENT_STROKE_COLOR_PALETTE, DEFAULT_ELEMENT_STROKE_COLOR_PALETTE,
DEFAULT_ELEMENT_STROKE_PICKS, DEFAULT_ELEMENT_STROKE_PICKS,
ARROW_TYPE,
DEFAULT_FONT_FAMILY, DEFAULT_FONT_FAMILY,
DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE,
FONT_FAMILY, FONT_FAMILY,
ROUNDNESS, getFontFamilyString,
STROKE_WIDTH, getLineHeight,
VERTICAL_ALIGN, getShortcutKey,
KEYS, KEYS,
randomInteger, randomInteger,
arrayToMap, ROUNDNESS,
getFontFamilyString,
getShortcutKey,
tupleToCoors, tupleToCoors,
getLineHeight, VERTICAL_ALIGN,
} from "@excalidraw/common"; } from "@excalidraw/common";
import { getNonDeletedElements } from "@excalidraw/element"; import { getNonDeletedElements } from "@excalidraw/element";
@ -70,6 +70,49 @@ import type {
import type Scene from "@excalidraw/element/Scene"; 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 { trackEvent } from "../analytics";
import { ButtonIconSelect } from "../components/ButtonIconSelect"; import { ButtonIconSelect } from "../components/ButtonIconSelect";
import { ColorPicker } from "../components/ColorPicker/ColorPicker"; import { ColorPicker } from "../components/ColorPicker/ColorPicker";
@ -79,48 +122,6 @@ import { IconPicker } from "../components/IconPicker";
// TextAlignTopIcon, TextAlignBottomIcon,TextAlignMiddleIcon, // TextAlignTopIcon, TextAlignBottomIcon,TextAlignMiddleIcon,
// ArrowHead icons // ArrowHead icons
import { Range } from "../components/Range"; 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 { Fonts } from "../fonts";
import { getLanguage, t } from "../i18n"; import { getLanguage, t } from "../i18n";
@ -131,11 +132,13 @@ import {
getTargetElements, getTargetElements,
isSomeElementSelected, isSomeElementSelected,
} from "../scene"; } from "../scene";
import { CaptureUpdateAction } from "../store"; import { CaptureUpdateAction } from "../store";
import { register } from "./register"; import { register } from "./register";
import type { CaptureUpdateActionType } from "../store"; import type { CaptureUpdateActionType } from "../store";
import type { AppClassProperties, AppState, Primitive } from "../types"; import type { AppClassProperties, AppState, Primitive } from "../types";
const FONT_SIZE_RELATIVE_INCREASE_STEP = 0.1; const FONT_SIZE_RELATIVE_INCREASE_STEP = 0.1;
@ -472,52 +475,42 @@ export const actionChangeStrokeWidth = register({
trackEvent: false, trackEvent: false,
perform: (elements, appState, value) => { perform: (elements, appState, value) => {
return { return {
elements: changeProperty(elements, appState, (el) => elements: changeProperty(
elements,
appState,
(el) =>
newElementWith(el, { newElementWith(el, {
strokeWidth: value, strokeWidth: value,
}), }),
true,
), ),
appState: { ...appState, currentItemStrokeWidth: value }, appState: { ...appState, currentItemStrokeWidth: value },
captureUpdate: CaptureUpdateAction.IMMEDIATELY, captureUpdate: CaptureUpdateAction.IMMEDIATELY,
}; };
}, },
PanelComponent: ({ elements, appState, updateData }) => ( PanelComponent: ({ elements, appState, updateData }: PanelComponentProps) => {
<fieldset> const strokeWidthValue = getFormValue(
<legend>{t("labels.strokeWidth")}</legend>
<ButtonIconSelect
group="stroke-width"
options={[
{
value: STROKE_WIDTH.thin,
text: t("labels.thin"),
icon: StrokeWidthBaseIcon,
testId: "strokeWidth-thin",
},
{
value: STROKE_WIDTH.bold,
text: t("labels.bold"),
icon: StrokeWidthBoldIcon,
testId: "strokeWidth-bold",
},
{
value: STROKE_WIDTH.extraBold,
text: t("labels.extraBold"),
icon: StrokeWidthExtraBoldIcon,
testId: "strokeWidth-extraBold",
},
]}
value={getFormValue(
elements, elements,
appState, appState,
(element) => element.strokeWidth, (element) => element.strokeWidth,
(element) => element.hasOwnProperty("strokeWidth"), true,
(hasSelection) => appState.currentItemStrokeWidth,
hasSelection ? null : appState.currentItemStrokeWidth, );
)}
onChange={(value) => updateData(value)} return (
<Range
updateData={updateData}
elements={elements}
appState={appState}
testId="strokeWidth"
min={0.1}
max={10}
step={0.1}
value={strokeWidthValue}
label={t("labels.strokeWidth")}
/> />
</fieldset> );
), },
}); });
export const actionChangeSloppiness = register({ export const actionChangeSloppiness = register({
@ -642,14 +635,29 @@ export const actionChangeOpacity = register({
captureUpdate: CaptureUpdateAction.IMMEDIATELY, captureUpdate: CaptureUpdateAction.IMMEDIATELY,
}; };
}, },
PanelComponent: ({ elements, appState, updateData }) => ( PanelComponent: ({ elements, appState, updateData }: PanelComponentProps) => {
const opacityValue = getFormValue(
elements,
appState,
(element) => element.opacity,
true,
appState.currentItemOpacity,
);
return (
<Range <Range
updateData={updateData} updateData={updateData}
elements={elements} elements={elements}
appState={appState} appState={appState}
testId="opacity" testId="opacity"
min={0}
max={100}
step={1}
value={opacityValue}
label={t("labels.opacity")}
/> />
), );
},
}); });
export const actionChangeFontSize = register({ export const actionChangeFontSize = register({

View file

@ -1,8 +1,5 @@
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { getFormValue } from "../actions/actionProperties";
import { t } from "../i18n";
import "./Range.scss"; import "./Range.scss";
export type RangeProps = { export type RangeProps = {
@ -17,16 +14,21 @@ export const Range = ({
appState, appState,
elements, elements,
testId, testId,
}: RangeProps) => { min,
max,
step,
value,
label,
}: RangeProps & {
label: string;
min: number;
max: number;
step: number;
value: number;
}) => {
const rangeRef = React.useRef<HTMLInputElement>(null); const rangeRef = React.useRef<HTMLInputElement>(null);
const valueRef = React.useRef<HTMLDivElement>(null); const valueRef = React.useRef<HTMLDivElement>(null);
const value = getFormValue(
elements,
appState,
(element) => element.opacity,
true,
appState.currentItemOpacity,
);
useEffect(() => { useEffect(() => {
if (rangeRef.current && valueRef.current) { if (rangeRef.current && valueRef.current) {
const rangeElement = rangeRef.current; const rangeElement = rangeRef.current;
@ -34,33 +36,36 @@ export const Range = ({
const inputWidth = rangeElement.offsetWidth; const inputWidth = rangeElement.offsetWidth;
const thumbWidth = 15; // 15 is the width of the thumb const thumbWidth = 15; // 15 is the width of the thumb
const position = const position =
(value / 100) * (inputWidth - thumbWidth) + thumbWidth / 2; (value / (max - min)) * (inputWidth - thumbWidth) + thumbWidth / 2;
valueElement.style.left = `${position}px`; 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 ( return (
<label className="control-label"> <label className="control-label">
{t("labels.opacity")} {label}
<div className="range-wrapper"> <div className="range-wrapper">
<input <input
ref={rangeRef} ref={rangeRef}
type="range" type="range"
min="0" min={min}
max="100" max={max}
step="10" step={step}
onChange={(event) => { onChange={(event) => {
updateData(+event.target.value); updateData(parseFloat(event.target.value));
}} }}
value={value} value={value}
className="range-input" className="range-input"
data-testid={testId} data-testid={testId}
/> />
<div className="value-bubble" ref={valueRef}> <div className="value-bubble" ref={valueRef}>
{value !== 0 ? value : null} {value !== 0 ? value.toFixed(1) : null} {}
</div> </div>
<div className="zero-label">0</div>
</div> </div>
</label> </label>
); );