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 { 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 }) => (
<fieldset>
<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,
appState,
(element) => 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 (
<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({
@ -642,14 +635,29 @@ export const actionChangeOpacity = register({
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
};
},
PanelComponent: ({ elements, appState, updateData }) => (
<Range
updateData={updateData}
elements={elements}
appState={appState}
testId="opacity"
/>
),
PanelComponent: ({ elements, appState, updateData }: PanelComponentProps) => {
const opacityValue = getFormValue(
elements,
appState,
(element) => element.opacity,
true,
appState.currentItemOpacity,
);
return (
<Range
updateData={updateData}
elements={elements}
appState={appState}
testId="opacity"
min={0}
max={100}
step={1}
value={opacityValue}
label={t("labels.opacity")}
/>
);
},
});
export const actionChangeFontSize = register({

View file

@ -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<HTMLInputElement>(null);
const valueRef = React.useRef<HTMLDivElement>(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 (
<label className="control-label">
{t("labels.opacity")}
{label}
<div className="range-wrapper">
<input
ref={rangeRef}
type="range"
min="0"
max="100"
step="10"
min={min}
max={max}
step={step}
onChange={(event) => {
updateData(+event.target.value);
updateData(parseFloat(event.target.value));
}}
value={value}
className="range-input"
data-testid={testId}
/>
<div className="value-bubble" ref={valueRef}>
{value !== 0 ? value : null}
{value !== 0 ? value.toFixed(1) : null} {}
</div>
<div className="zero-label">0</div>
</div>
</label>
);