| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058 |
- import { AppState } from "../../src/types";
- import { ButtonIconSelect } from "../components/ButtonIconSelect";
- import { ColorPicker } from "../components/ColorPicker";
- import { IconPicker } from "../components/IconPicker";
- // TODO barnabasmolnar/editor-redesign
- // TextAlignTopIcon, TextAlignBottomIcon,TextAlignMiddleIcon,
- // ArrowHead icons
- import {
- ArrowheadArrowIcon,
- ArrowheadBarIcon,
- ArrowheadDotIcon,
- ArrowheadTriangleIcon,
- ArrowheadNoneIcon,
- StrokeStyleDashedIcon,
- StrokeStyleDottedIcon,
- TextAlignTopIcon,
- TextAlignBottomIcon,
- TextAlignMiddleIcon,
- FillHachureIcon,
- FillCrossHatchIcon,
- FillSolidIcon,
- SloppinessArchitectIcon,
- SloppinessArtistIcon,
- SloppinessCartoonistIcon,
- StrokeWidthBaseIcon,
- StrokeWidthBoldIcon,
- StrokeWidthExtraBoldIcon,
- FontSizeSmallIcon,
- FontSizeMediumIcon,
- FontSizeLargeIcon,
- FontSizeExtraLargeIcon,
- EdgeSharpIcon,
- EdgeRoundIcon,
- FreedrawIcon,
- FontFamilyNormalIcon,
- FontFamilyCodeIcon,
- TextAlignLeftIcon,
- TextAlignCenterIcon,
- TextAlignRightIcon,
- } from "../components/icons";
- import {
- DEFAULT_FONT_FAMILY,
- DEFAULT_FONT_SIZE,
- FONT_FAMILY,
- ROUNDNESS,
- VERTICAL_ALIGN,
- } from "../constants";
- import {
- getNonDeletedElements,
- isTextElement,
- redrawTextBoundingBox,
- } from "../element";
- import { mutateElement, newElementWith } from "../element/mutateElement";
- import {
- getBoundTextElement,
- getContainerElement,
- } from "../element/textElement";
- import {
- isBoundToContainer,
- isLinearElement,
- isUsingAdaptiveRadius,
- } from "../element/typeChecks";
- import {
- Arrowhead,
- ExcalidrawElement,
- ExcalidrawLinearElement,
- ExcalidrawTextElement,
- FontFamilyValues,
- TextAlign,
- VerticalAlign,
- } from "../element/types";
- import { getLanguage, t } from "../i18n";
- import { KEYS } from "../keys";
- import { randomInteger } from "../random";
- import {
- canChangeRoundness,
- canHaveArrowheads,
- getCommonAttributeOfSelectedElements,
- getSelectedElements,
- getTargetElements,
- isSomeElementSelected,
- } from "../scene";
- import { hasStrokeColor } from "../scene/comparisons";
- import { arrayToMap } from "../utils";
- import { register } from "./register";
- const FONT_SIZE_RELATIVE_INCREASE_STEP = 0.1;
- const changeProperty = (
- elements: readonly ExcalidrawElement[],
- appState: AppState,
- callback: (element: ExcalidrawElement) => ExcalidrawElement,
- includeBoundText = false,
- ) => {
- const selectedElementIds = arrayToMap(
- getSelectedElements(elements, appState, includeBoundText),
- );
- return elements.map((element) => {
- if (
- selectedElementIds.get(element.id) ||
- element.id === appState.editingElement?.id
- ) {
- return callback(element);
- }
- return element;
- });
- };
- const getFormValue = function <T>(
- elements: readonly ExcalidrawElement[],
- appState: AppState,
- getAttribute: (element: ExcalidrawElement) => T,
- defaultValue?: T,
- ): T | null {
- const editingElement = appState.editingElement;
- const nonDeletedElements = getNonDeletedElements(elements);
- return (
- (editingElement && getAttribute(editingElement)) ??
- (isSomeElementSelected(nonDeletedElements, appState)
- ? getCommonAttributeOfSelectedElements(
- nonDeletedElements,
- appState,
- getAttribute,
- )
- : defaultValue) ??
- null
- );
- };
- const offsetElementAfterFontResize = (
- prevElement: ExcalidrawTextElement,
- nextElement: ExcalidrawTextElement,
- ) => {
- if (isBoundToContainer(nextElement)) {
- return nextElement;
- }
- return mutateElement(
- nextElement,
- {
- x:
- prevElement.textAlign === "left"
- ? prevElement.x
- : prevElement.x +
- (prevElement.width - nextElement.width) /
- (prevElement.textAlign === "center" ? 2 : 1),
- // centering vertically is non-standard, but for Excalidraw I think
- // it makes sense
- y: prevElement.y + (prevElement.height - nextElement.height) / 2,
- },
- false,
- );
- };
- const changeFontSize = (
- elements: readonly ExcalidrawElement[],
- appState: AppState,
- getNewFontSize: (element: ExcalidrawTextElement) => number,
- fallbackValue?: ExcalidrawTextElement["fontSize"],
- ) => {
- const newFontSizes = new Set<number>();
- return {
- elements: changeProperty(
- elements,
- appState,
- (oldElement) => {
- if (isTextElement(oldElement)) {
- const newFontSize = getNewFontSize(oldElement);
- newFontSizes.add(newFontSize);
- let newElement: ExcalidrawTextElement = newElementWith(oldElement, {
- fontSize: newFontSize,
- });
- redrawTextBoundingBox(newElement, getContainerElement(oldElement));
- newElement = offsetElementAfterFontResize(oldElement, newElement);
- return newElement;
- }
- return oldElement;
- },
- true,
- ),
- appState: {
- ...appState,
- // update state only if we've set all select text elements to
- // the same font size
- currentItemFontSize:
- newFontSizes.size === 1
- ? [...newFontSizes][0]
- : fallbackValue ?? appState.currentItemFontSize,
- },
- commitToHistory: true,
- };
- };
- // -----------------------------------------------------------------------------
- export const actionChangeStrokeColor = register({
- name: "changeStrokeColor",
- trackEvent: false,
- perform: (elements, appState, value) => {
- return {
- ...(value.currentItemStrokeColor && {
- elements: changeProperty(
- elements,
- appState,
- (el) => {
- return hasStrokeColor(el.type)
- ? newElementWith(el, {
- strokeColor: value.currentItemStrokeColor,
- })
- : el;
- },
- true,
- ),
- }),
- appState: {
- ...appState,
- ...value,
- },
- commitToHistory: !!value.currentItemStrokeColor,
- };
- },
- PanelComponent: ({ elements, appState, updateData }) => (
- <>
- <h3 aria-hidden="true">{t("labels.stroke")}</h3>
- <ColorPicker
- type="elementStroke"
- label={t("labels.stroke")}
- color={getFormValue(
- elements,
- appState,
- (element) => element.strokeColor,
- appState.currentItemStrokeColor,
- )}
- onChange={(color) => updateData({ currentItemStrokeColor: color })}
- isActive={appState.openPopup === "strokeColorPicker"}
- setActive={(active) =>
- updateData({ openPopup: active ? "strokeColorPicker" : null })
- }
- elements={elements}
- appState={appState}
- />
- </>
- ),
- });
- export const actionChangeBackgroundColor = register({
- name: "changeBackgroundColor",
- trackEvent: false,
- perform: (elements, appState, value) => {
- return {
- ...(value.currentItemBackgroundColor && {
- elements: changeProperty(elements, appState, (el) =>
- newElementWith(el, {
- backgroundColor: value.currentItemBackgroundColor,
- }),
- ),
- }),
- appState: {
- ...appState,
- ...value,
- },
- commitToHistory: !!value.currentItemBackgroundColor,
- };
- },
- PanelComponent: ({ elements, appState, updateData }) => (
- <>
- <h3 aria-hidden="true">{t("labels.background")}</h3>
- <ColorPicker
- type="elementBackground"
- label={t("labels.background")}
- color={getFormValue(
- elements,
- appState,
- (element) => element.backgroundColor,
- appState.currentItemBackgroundColor,
- )}
- onChange={(color) => updateData({ currentItemBackgroundColor: color })}
- isActive={appState.openPopup === "backgroundColorPicker"}
- setActive={(active) =>
- updateData({ openPopup: active ? "backgroundColorPicker" : null })
- }
- elements={elements}
- appState={appState}
- />
- </>
- ),
- });
- export const actionChangeFillStyle = register({
- name: "changeFillStyle",
- trackEvent: false,
- perform: (elements, appState, value) => {
- return {
- elements: changeProperty(elements, appState, (el) =>
- newElementWith(el, {
- fillStyle: value,
- }),
- ),
- appState: { ...appState, currentItemFillStyle: value },
- commitToHistory: true,
- };
- },
- PanelComponent: ({ elements, appState, updateData }) => (
- <fieldset>
- <legend>{t("labels.fill")}</legend>
- <ButtonIconSelect
- options={[
- {
- value: "hachure",
- text: t("labels.hachure"),
- icon: FillHachureIcon,
- },
- {
- value: "cross-hatch",
- text: t("labels.crossHatch"),
- icon: FillCrossHatchIcon,
- },
- {
- value: "solid",
- text: t("labels.solid"),
- icon: FillSolidIcon,
- },
- ]}
- group="fill"
- value={getFormValue(
- elements,
- appState,
- (element) => element.fillStyle,
- appState.currentItemFillStyle,
- )}
- onChange={(value) => {
- updateData(value);
- }}
- />
- </fieldset>
- ),
- });
- export const actionChangeStrokeWidth = register({
- name: "changeStrokeWidth",
- trackEvent: false,
- perform: (elements, appState, value) => {
- return {
- elements: changeProperty(elements, appState, (el) =>
- newElementWith(el, {
- strokeWidth: value,
- }),
- ),
- appState: { ...appState, currentItemStrokeWidth: value },
- commitToHistory: true,
- };
- },
- PanelComponent: ({ elements, appState, updateData }) => (
- <fieldset>
- <legend>{t("labels.strokeWidth")}</legend>
- <ButtonIconSelect
- group="stroke-width"
- options={[
- {
- value: 1,
- text: t("labels.thin"),
- icon: StrokeWidthBaseIcon,
- },
- {
- value: 2,
- text: t("labels.bold"),
- icon: StrokeWidthBoldIcon,
- },
- {
- value: 4,
- text: t("labels.extraBold"),
- icon: StrokeWidthExtraBoldIcon,
- },
- ]}
- value={getFormValue(
- elements,
- appState,
- (element) => element.strokeWidth,
- appState.currentItemStrokeWidth,
- )}
- onChange={(value) => updateData(value)}
- />
- </fieldset>
- ),
- });
- export const actionChangeSloppiness = register({
- name: "changeSloppiness",
- trackEvent: false,
- perform: (elements, appState, value) => {
- return {
- elements: changeProperty(elements, appState, (el) =>
- newElementWith(el, {
- seed: randomInteger(),
- roughness: value,
- }),
- ),
- appState: { ...appState, currentItemRoughness: value },
- commitToHistory: true,
- };
- },
- PanelComponent: ({ elements, appState, updateData }) => (
- <fieldset>
- <legend>{t("labels.sloppiness")}</legend>
- <ButtonIconSelect
- group="sloppiness"
- options={[
- {
- value: 0,
- text: t("labels.architect"),
- icon: SloppinessArchitectIcon,
- },
- {
- value: 1,
- text: t("labels.artist"),
- icon: SloppinessArtistIcon,
- },
- {
- value: 2,
- text: t("labels.cartoonist"),
- icon: SloppinessCartoonistIcon,
- },
- ]}
- value={getFormValue(
- elements,
- appState,
- (element) => element.roughness,
- appState.currentItemRoughness,
- )}
- onChange={(value) => updateData(value)}
- />
- </fieldset>
- ),
- });
- export const actionChangeStrokeStyle = register({
- name: "changeStrokeStyle",
- trackEvent: false,
- perform: (elements, appState, value) => {
- return {
- elements: changeProperty(elements, appState, (el) =>
- newElementWith(el, {
- strokeStyle: value,
- }),
- ),
- appState: { ...appState, currentItemStrokeStyle: value },
- commitToHistory: true,
- };
- },
- PanelComponent: ({ elements, appState, updateData }) => (
- <fieldset>
- <legend>{t("labels.strokeStyle")}</legend>
- <ButtonIconSelect
- group="strokeStyle"
- options={[
- {
- value: "solid",
- text: t("labels.strokeStyle_solid"),
- icon: StrokeWidthBaseIcon,
- },
- {
- value: "dashed",
- text: t("labels.strokeStyle_dashed"),
- icon: StrokeStyleDashedIcon,
- },
- {
- value: "dotted",
- text: t("labels.strokeStyle_dotted"),
- icon: StrokeStyleDottedIcon,
- },
- ]}
- value={getFormValue(
- elements,
- appState,
- (element) => element.strokeStyle,
- appState.currentItemStrokeStyle,
- )}
- onChange={(value) => updateData(value)}
- />
- </fieldset>
- ),
- });
- export const actionChangeOpacity = register({
- name: "changeOpacity",
- trackEvent: false,
- perform: (elements, appState, value) => {
- return {
- elements: changeProperty(
- elements,
- appState,
- (el) =>
- newElementWith(el, {
- opacity: value,
- }),
- true,
- ),
- appState: { ...appState, currentItemOpacity: value },
- commitToHistory: true,
- };
- },
- PanelComponent: ({ elements, appState, updateData }) => (
- <label className="control-label">
- {t("labels.opacity")}
- <input
- type="range"
- min="0"
- max="100"
- step="10"
- onChange={(event) => updateData(+event.target.value)}
- value={
- getFormValue(
- elements,
- appState,
- (element) => element.opacity,
- appState.currentItemOpacity,
- ) ?? undefined
- }
- />
- </label>
- ),
- });
- export const actionChangeFontSize = register({
- name: "changeFontSize",
- trackEvent: false,
- perform: (elements, appState, value) => {
- return changeFontSize(elements, appState, () => value, value);
- },
- PanelComponent: ({ elements, appState, updateData }) => (
- <fieldset>
- <legend>{t("labels.fontSize")}</legend>
- <ButtonIconSelect
- group="font-size"
- options={[
- {
- value: 16,
- text: t("labels.small"),
- icon: FontSizeSmallIcon,
- testId: "fontSize-small",
- },
- {
- value: 20,
- text: t("labels.medium"),
- icon: FontSizeMediumIcon,
- testId: "fontSize-medium",
- },
- {
- value: 28,
- text: t("labels.large"),
- icon: FontSizeLargeIcon,
- testId: "fontSize-large",
- },
- {
- value: 36,
- text: t("labels.veryLarge"),
- icon: FontSizeExtraLargeIcon,
- testId: "fontSize-veryLarge",
- },
- ]}
- value={getFormValue(
- elements,
- appState,
- (element) => {
- if (isTextElement(element)) {
- return element.fontSize;
- }
- const boundTextElement = getBoundTextElement(element);
- if (boundTextElement) {
- return boundTextElement.fontSize;
- }
- return null;
- },
- appState.currentItemFontSize || DEFAULT_FONT_SIZE,
- )}
- onChange={(value) => updateData(value)}
- />
- </fieldset>
- ),
- });
- export const actionDecreaseFontSize = register({
- name: "decreaseFontSize",
- trackEvent: false,
- perform: (elements, appState, value) => {
- return changeFontSize(elements, appState, (element) =>
- Math.round(
- // get previous value before relative increase (doesn't work fully
- // due to rounding and float precision issues)
- (1 / (1 + FONT_SIZE_RELATIVE_INCREASE_STEP)) * element.fontSize,
- ),
- );
- },
- keyTest: (event) => {
- return (
- event[KEYS.CTRL_OR_CMD] &&
- event.shiftKey &&
- // KEYS.COMMA needed for MacOS
- (event.key === KEYS.CHEVRON_LEFT || event.key === KEYS.COMMA)
- );
- },
- });
- export const actionIncreaseFontSize = register({
- name: "increaseFontSize",
- trackEvent: false,
- perform: (elements, appState, value) => {
- return changeFontSize(elements, appState, (element) =>
- Math.round(element.fontSize * (1 + FONT_SIZE_RELATIVE_INCREASE_STEP)),
- );
- },
- keyTest: (event) => {
- return (
- event[KEYS.CTRL_OR_CMD] &&
- event.shiftKey &&
- // KEYS.PERIOD needed for MacOS
- (event.key === KEYS.CHEVRON_RIGHT || event.key === KEYS.PERIOD)
- );
- },
- });
- export const actionChangeFontFamily = register({
- name: "changeFontFamily",
- trackEvent: false,
- perform: (elements, appState, value) => {
- return {
- elements: changeProperty(
- elements,
- appState,
- (oldElement) => {
- if (isTextElement(oldElement)) {
- const newElement: ExcalidrawTextElement = newElementWith(
- oldElement,
- {
- fontFamily: value,
- },
- );
- redrawTextBoundingBox(newElement, getContainerElement(oldElement));
- return newElement;
- }
- return oldElement;
- },
- true,
- ),
- appState: {
- ...appState,
- currentItemFontFamily: value,
- },
- commitToHistory: true,
- };
- },
- PanelComponent: ({ elements, appState, updateData }) => {
- const options: {
- value: FontFamilyValues;
- text: string;
- icon: JSX.Element;
- }[] = [
- {
- value: FONT_FAMILY.Virgil,
- text: t("labels.handDrawn"),
- icon: FreedrawIcon,
- },
- {
- value: FONT_FAMILY.Helvetica,
- text: t("labels.normal"),
- icon: FontFamilyNormalIcon,
- },
- {
- value: FONT_FAMILY.Cascadia,
- text: t("labels.code"),
- icon: FontFamilyCodeIcon,
- },
- ];
- return (
- <fieldset>
- <legend>{t("labels.fontFamily")}</legend>
- <ButtonIconSelect<FontFamilyValues | false>
- group="font-family"
- options={options}
- value={getFormValue(
- elements,
- appState,
- (element) => {
- if (isTextElement(element)) {
- return element.fontFamily;
- }
- const boundTextElement = getBoundTextElement(element);
- if (boundTextElement) {
- return boundTextElement.fontFamily;
- }
- return null;
- },
- appState.currentItemFontFamily || DEFAULT_FONT_FAMILY,
- )}
- onChange={(value) => updateData(value)}
- />
- </fieldset>
- );
- },
- });
- export const actionChangeTextAlign = register({
- name: "changeTextAlign",
- trackEvent: false,
- perform: (elements, appState, value) => {
- return {
- elements: changeProperty(
- elements,
- appState,
- (oldElement) => {
- if (isTextElement(oldElement)) {
- const newElement: ExcalidrawTextElement = newElementWith(
- oldElement,
- { textAlign: value },
- );
- redrawTextBoundingBox(newElement, getContainerElement(oldElement));
- return newElement;
- }
- return oldElement;
- },
- true,
- ),
- appState: {
- ...appState,
- currentItemTextAlign: value,
- },
- commitToHistory: true,
- };
- },
- PanelComponent: ({ elements, appState, updateData }) => {
- return (
- <fieldset>
- <legend>{t("labels.textAlign")}</legend>
- <ButtonIconSelect<TextAlign | false>
- group="text-align"
- options={[
- {
- value: "left",
- text: t("labels.left"),
- icon: TextAlignLeftIcon,
- },
- {
- value: "center",
- text: t("labels.center"),
- icon: TextAlignCenterIcon,
- },
- {
- value: "right",
- text: t("labels.right"),
- icon: TextAlignRightIcon,
- },
- ]}
- value={getFormValue(
- elements,
- appState,
- (element) => {
- if (isTextElement(element)) {
- return element.textAlign;
- }
- const boundTextElement = getBoundTextElement(element);
- if (boundTextElement) {
- return boundTextElement.textAlign;
- }
- return null;
- },
- appState.currentItemTextAlign,
- )}
- onChange={(value) => updateData(value)}
- />
- </fieldset>
- );
- },
- });
- export const actionChangeVerticalAlign = register({
- name: "changeVerticalAlign",
- trackEvent: { category: "element" },
- perform: (elements, appState, value) => {
- return {
- elements: changeProperty(
- elements,
- appState,
- (oldElement) => {
- if (isTextElement(oldElement)) {
- const newElement: ExcalidrawTextElement = newElementWith(
- oldElement,
- { verticalAlign: value },
- );
- redrawTextBoundingBox(newElement, getContainerElement(oldElement));
- return newElement;
- }
- return oldElement;
- },
- true,
- ),
- appState: {
- ...appState,
- },
- commitToHistory: true,
- };
- },
- PanelComponent: ({ elements, appState, updateData }) => {
- return (
- <fieldset>
- <ButtonIconSelect<VerticalAlign | false>
- group="text-align"
- options={[
- {
- value: VERTICAL_ALIGN.TOP,
- text: t("labels.alignTop"),
- icon: <TextAlignTopIcon theme={appState.theme} />,
- testId: "align-top",
- },
- {
- value: VERTICAL_ALIGN.MIDDLE,
- text: t("labels.centerVertically"),
- icon: <TextAlignMiddleIcon theme={appState.theme} />,
- testId: "align-middle",
- },
- {
- value: VERTICAL_ALIGN.BOTTOM,
- text: t("labels.alignBottom"),
- icon: <TextAlignBottomIcon theme={appState.theme} />,
- testId: "align-bottom",
- },
- ]}
- value={getFormValue(elements, appState, (element) => {
- if (isTextElement(element) && element.containerId) {
- return element.verticalAlign;
- }
- const boundTextElement = getBoundTextElement(element);
- if (boundTextElement) {
- return boundTextElement.verticalAlign;
- }
- return null;
- })}
- onChange={(value) => updateData(value)}
- />
- </fieldset>
- );
- },
- });
- export const actionChangeRoundness = register({
- name: "changeRoundness",
- trackEvent: false,
- perform: (elements, appState, value) => {
- return {
- elements: changeProperty(elements, appState, (el) =>
- newElementWith(el, {
- roundness:
- value === "round"
- ? {
- type: isUsingAdaptiveRadius(el.type)
- ? ROUNDNESS.ADAPTIVE_RADIUS
- : ROUNDNESS.PROPORTIONAL_RADIUS,
- }
- : null,
- }),
- ),
- appState: {
- ...appState,
- currentItemRoundness: value,
- },
- commitToHistory: true,
- };
- },
- PanelComponent: ({ elements, appState, updateData }) => {
- const targetElements = getTargetElements(
- getNonDeletedElements(elements),
- appState,
- );
- const hasLegacyRoundness = targetElements.some(
- (el) => el.roundness?.type === ROUNDNESS.LEGACY,
- );
- return (
- <fieldset>
- <legend>{t("labels.edges")}</legend>
- <ButtonIconSelect
- group="edges"
- options={[
- {
- value: "sharp",
- text: t("labels.sharp"),
- icon: EdgeSharpIcon,
- },
- {
- value: "round",
- text: t("labels.round"),
- icon: EdgeRoundIcon,
- },
- ]}
- value={getFormValue(
- elements,
- appState,
- (element) =>
- hasLegacyRoundness ? null : element.roundness ? "round" : "sharp",
- (canChangeRoundness(appState.activeTool.type) &&
- appState.currentItemRoundness) ||
- null,
- )}
- onChange={(value) => updateData(value)}
- />
- </fieldset>
- );
- },
- });
- export const actionChangeArrowhead = register({
- name: "changeArrowhead",
- trackEvent: false,
- perform: (
- elements,
- appState,
- value: { position: "start" | "end"; type: Arrowhead },
- ) => {
- return {
- elements: changeProperty(elements, appState, (el) => {
- if (isLinearElement(el)) {
- const { position, type } = value;
- if (position === "start") {
- const element: ExcalidrawLinearElement = newElementWith(el, {
- startArrowhead: type,
- });
- return element;
- } else if (position === "end") {
- const element: ExcalidrawLinearElement = newElementWith(el, {
- endArrowhead: type,
- });
- return element;
- }
- }
- return el;
- }),
- appState: {
- ...appState,
- [value.position === "start"
- ? "currentItemStartArrowhead"
- : "currentItemEndArrowhead"]: value.type,
- },
- commitToHistory: true,
- };
- },
- PanelComponent: ({ elements, appState, updateData }) => {
- const isRTL = getLanguage().rtl;
- return (
- <fieldset>
- <legend>{t("labels.arrowheads")}</legend>
- <div className="iconSelectList buttonList">
- <IconPicker
- label="arrowhead_start"
- options={[
- {
- value: null,
- text: t("labels.arrowhead_none"),
- icon: ArrowheadNoneIcon,
- keyBinding: "q",
- },
- {
- value: "arrow",
- text: t("labels.arrowhead_arrow"),
- icon: <ArrowheadArrowIcon flip={!isRTL} />,
- keyBinding: "w",
- },
- {
- value: "bar",
- text: t("labels.arrowhead_bar"),
- icon: <ArrowheadBarIcon flip={!isRTL} />,
- keyBinding: "e",
- },
- {
- value: "dot",
- text: t("labels.arrowhead_dot"),
- icon: <ArrowheadDotIcon flip={!isRTL} />,
- keyBinding: "r",
- },
- {
- value: "triangle",
- text: t("labels.arrowhead_triangle"),
- icon: <ArrowheadTriangleIcon flip={!isRTL} />,
- keyBinding: "t",
- },
- ]}
- value={getFormValue<Arrowhead | null>(
- elements,
- appState,
- (element) =>
- isLinearElement(element) && canHaveArrowheads(element.type)
- ? element.startArrowhead
- : appState.currentItemStartArrowhead,
- appState.currentItemStartArrowhead,
- )}
- onChange={(value) => updateData({ position: "start", type: value })}
- />
- <IconPicker
- label="arrowhead_end"
- group="arrowheads"
- options={[
- {
- value: null,
- text: t("labels.arrowhead_none"),
- keyBinding: "q",
- icon: ArrowheadNoneIcon,
- },
- {
- value: "arrow",
- text: t("labels.arrowhead_arrow"),
- keyBinding: "w",
- icon: <ArrowheadArrowIcon flip={isRTL} />,
- },
- {
- value: "bar",
- text: t("labels.arrowhead_bar"),
- keyBinding: "e",
- icon: <ArrowheadBarIcon flip={isRTL} />,
- },
- {
- value: "dot",
- text: t("labels.arrowhead_dot"),
- keyBinding: "r",
- icon: <ArrowheadDotIcon flip={isRTL} />,
- },
- {
- value: "triangle",
- text: t("labels.arrowhead_triangle"),
- icon: <ArrowheadTriangleIcon flip={isRTL} />,
- keyBinding: "t",
- },
- ]}
- value={getFormValue<Arrowhead | null>(
- elements,
- appState,
- (element) =>
- isLinearElement(element) && canHaveArrowheads(element.type)
- ? element.endArrowhead
- : appState.currentItemEndArrowhead,
- appState.currentItemEndArrowhead,
- )}
- onChange={(value) => updateData({ position: "end", type: value })}
- />
- </div>
- </fieldset>
- );
- },
- });
|