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 { 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(
|
||||||
newElementWith(el, {
|
elements,
|
||||||
strokeWidth: value,
|
appState,
|
||||||
}),
|
(el) =>
|
||||||
|
newElementWith(el, {
|
||||||
|
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>
|
elements,
|
||||||
<ButtonIconSelect
|
appState,
|
||||||
group="stroke-width"
|
(element) => element.strokeWidth,
|
||||||
options={[
|
true,
|
||||||
{
|
appState.currentItemStrokeWidth,
|
||||||
value: STROKE_WIDTH.thin,
|
);
|
||||||
text: t("labels.thin"),
|
|
||||||
icon: StrokeWidthBaseIcon,
|
return (
|
||||||
testId: "strokeWidth-thin",
|
<Range
|
||||||
},
|
updateData={updateData}
|
||||||
{
|
elements={elements}
|
||||||
value: STROKE_WIDTH.bold,
|
appState={appState}
|
||||||
text: t("labels.bold"),
|
testId="strokeWidth"
|
||||||
icon: StrokeWidthBoldIcon,
|
min={0.1}
|
||||||
testId: "strokeWidth-bold",
|
max={10}
|
||||||
},
|
step={0.1}
|
||||||
{
|
value={strokeWidthValue}
|
||||||
value: STROKE_WIDTH.extraBold,
|
label={t("labels.strokeWidth")}
|
||||||
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)}
|
|
||||||
/>
|
/>
|
||||||
</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) => {
|
||||||
<Range
|
const opacityValue = getFormValue(
|
||||||
updateData={updateData}
|
elements,
|
||||||
elements={elements}
|
appState,
|
||||||
appState={appState}
|
(element) => element.opacity,
|
||||||
testId="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({
|
export const actionChangeFontSize = register({
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue