mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
Merge 29e130193d
into 6e655cdb24
This commit is contained in:
commit
0021bfc1b1
2 changed files with 133 additions and 120 deletions
|
@ -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({
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue