| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450 |
- import React from "react";
- import {
- ExcalidrawElement,
- ExcalidrawTextElement,
- TextAlign,
- FontFamily,
- } from "../element/types";
- import {
- getCommonAttributeOfSelectedElements,
- isSomeElementSelected,
- } from "../scene";
- import { ButtonSelect } from "../components/ButtonSelect";
- import {
- isTextElement,
- redrawTextBoundingBox,
- getNonDeletedElements,
- } from "../element";
- import { ColorPicker } from "../components/ColorPicker";
- import { AppState } from "../../src/types";
- import { t } from "../i18n";
- import { register } from "./register";
- import { newElementWith } from "../element/mutateElement";
- import { DEFAULT_FONT_SIZE, DEFAULT_FONT_FAMILY } from "../appState";
- const changeProperty = (
- elements: readonly ExcalidrawElement[],
- appState: AppState,
- callback: (element: ExcalidrawElement) => ExcalidrawElement,
- ) => {
- return elements.map((element) => {
- if (
- appState.selectedElementIds[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
- );
- };
- export const actionChangeStrokeColor = register({
- name: "changeStrokeColor",
- perform: (elements, appState, value) => {
- return {
- elements: changeProperty(elements, appState, (el) =>
- newElementWith(el, {
- strokeColor: value,
- }),
- ),
- appState: { ...appState, currentItemStrokeColor: value },
- commitToHistory: true,
- };
- },
- 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={updateData}
- />
- </>
- ),
- });
- export const actionChangeBackgroundColor = register({
- name: "changeBackgroundColor",
- perform: (elements, appState, value) => {
- return {
- elements: changeProperty(elements, appState, (el) =>
- newElementWith(el, {
- backgroundColor: value,
- }),
- ),
- appState: { ...appState, currentItemBackgroundColor: value },
- commitToHistory: true,
- };
- },
- 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={updateData}
- />
- </>
- ),
- });
- export const actionChangeFillStyle = register({
- name: "changeFillStyle",
- 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>
- <ButtonSelect
- options={[
- { value: "hachure", text: t("labels.hachure") },
- { value: "cross-hatch", text: t("labels.crossHatch") },
- { value: "solid", text: t("labels.solid") },
- ]}
- group="fill"
- value={getFormValue(
- elements,
- appState,
- (element) => element.fillStyle,
- appState.currentItemFillStyle,
- )}
- onChange={(value) => {
- updateData(value);
- }}
- />
- </fieldset>
- ),
- });
- export const actionChangeStrokeWidth = register({
- name: "changeStrokeWidth",
- 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>
- <ButtonSelect
- group="stroke-width"
- options={[
- { value: 1, text: t("labels.thin") },
- { value: 2, text: t("labels.bold") },
- { value: 4, text: t("labels.extraBold") },
- ]}
- value={getFormValue(
- elements,
- appState,
- (element) => element.strokeWidth,
- appState.currentItemStrokeWidth,
- )}
- onChange={(value) => updateData(value)}
- />
- </fieldset>
- ),
- });
- export const actionChangeSloppiness = register({
- name: "changeSloppiness",
- perform: (elements, appState, value) => {
- return {
- elements: changeProperty(elements, appState, (el) =>
- newElementWith(el, {
- roughness: value,
- }),
- ),
- appState: { ...appState, currentItemRoughness: value },
- commitToHistory: true,
- };
- },
- PanelComponent: ({ elements, appState, updateData }) => (
- <fieldset>
- <legend>{t("labels.sloppiness")}</legend>
- <ButtonSelect
- group="sloppiness"
- options={[
- { value: 0, text: t("labels.architect") },
- { value: 1, text: t("labels.artist") },
- { value: 2, text: t("labels.cartoonist") },
- ]}
- value={getFormValue(
- elements,
- appState,
- (element) => element.roughness,
- appState.currentItemRoughness,
- )}
- onChange={(value) => updateData(value)}
- />
- </fieldset>
- ),
- });
- export const actionChangeStrokeStyle = register({
- name: "changeStrokeStyle",
- 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>
- <ButtonSelect
- group="strokeStyle"
- options={[
- { value: "solid", text: t("labels.strokeStyle_solid") },
- { value: "dashed", text: t("labels.strokeStyle_dashed") },
- { value: "dotted", text: t("labels.strokeStyle_dotted") },
- ]}
- value={getFormValue(
- elements,
- appState,
- (element) => element.strokeStyle,
- appState.currentItemStrokeStyle,
- )}
- onChange={(value) => updateData(value)}
- />
- </fieldset>
- ),
- });
- export const actionChangeOpacity = register({
- name: "changeOpacity",
- perform: (elements, appState, value) => {
- return {
- elements: changeProperty(elements, appState, (el) =>
- newElementWith(el, {
- opacity: value,
- }),
- ),
- 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)}
- onWheel={(event) => {
- event.stopPropagation();
- const target = event.target as HTMLInputElement;
- const STEP = 10;
- const MAX = 100;
- const MIN = 0;
- const value = +target.value;
- if (event.deltaY < 0 && value < MAX) {
- updateData(value + STEP);
- } else if (event.deltaY > 0 && value > MIN) {
- updateData(value - STEP);
- }
- }}
- value={
- getFormValue(
- elements,
- appState,
- (element) => element.opacity,
- appState.currentItemOpacity,
- ) ?? undefined
- }
- />
- </label>
- ),
- });
- export const actionChangeFontSize = register({
- name: "changeFontSize",
- perform: (elements, appState, value) => {
- return {
- elements: changeProperty(elements, appState, (el) => {
- if (isTextElement(el)) {
- const element: ExcalidrawTextElement = newElementWith(el, {
- fontSize: value,
- });
- redrawTextBoundingBox(element);
- return element;
- }
- return el;
- }),
- appState: {
- ...appState,
- currentItemFontSize: value,
- },
- commitToHistory: true,
- };
- },
- PanelComponent: ({ elements, appState, updateData }) => (
- <fieldset>
- <legend>{t("labels.fontSize")}</legend>
- <ButtonSelect
- group="font-size"
- options={[
- { value: 16, text: t("labels.small") },
- { value: 20, text: t("labels.medium") },
- { value: 28, text: t("labels.large") },
- { value: 36, text: t("labels.veryLarge") },
- ]}
- value={getFormValue(
- elements,
- appState,
- (element) => isTextElement(element) && element.fontSize,
- appState.currentItemFontSize || DEFAULT_FONT_SIZE,
- )}
- onChange={(value) => updateData(value)}
- />
- </fieldset>
- ),
- });
- export const actionChangeFontFamily = register({
- name: "changeFontFamily",
- perform: (elements, appState, value) => {
- return {
- elements: changeProperty(elements, appState, (el) => {
- if (isTextElement(el)) {
- const element: ExcalidrawTextElement = newElementWith(el, {
- fontFamily: value,
- });
- redrawTextBoundingBox(element);
- return element;
- }
- return el;
- }),
- appState: {
- ...appState,
- currentItemFontFamily: value,
- },
- commitToHistory: true,
- };
- },
- PanelComponent: ({ elements, appState, updateData }) => {
- const options: { value: FontFamily; text: string }[] = [
- { value: 1, text: t("labels.handDrawn") },
- { value: 2, text: t("labels.normal") },
- { value: 3, text: t("labels.code") },
- ];
- return (
- <fieldset>
- <legend>{t("labels.fontFamily")}</legend>
- <ButtonSelect<FontFamily | false>
- group="font-family"
- options={options}
- value={getFormValue(
- elements,
- appState,
- (element) => isTextElement(element) && element.fontFamily,
- appState.currentItemFontFamily || DEFAULT_FONT_FAMILY,
- )}
- onChange={(value) => updateData(value)}
- />
- </fieldset>
- );
- },
- });
- export const actionChangeTextAlign = register({
- name: "changeTextAlign",
- perform: (elements, appState, value) => {
- return {
- elements: changeProperty(elements, appState, (el) => {
- if (isTextElement(el)) {
- const element: ExcalidrawTextElement = newElementWith(el, {
- textAlign: value,
- });
- redrawTextBoundingBox(element);
- return element;
- }
- return el;
- }),
- appState: {
- ...appState,
- currentItemTextAlign: value,
- },
- commitToHistory: true,
- };
- },
- PanelComponent: ({ elements, appState, updateData }) => (
- <fieldset>
- <legend>{t("labels.textAlign")}</legend>
- <ButtonSelect<TextAlign | false>
- group="text-align"
- options={[
- { value: "left", text: t("labels.left") },
- { value: "center", text: t("labels.center") },
- { value: "right", text: t("labels.right") },
- ]}
- value={getFormValue(
- elements,
- appState,
- (element) => isTextElement(element) && element.textAlign,
- appState.currentItemTextAlign,
- )}
- onChange={(value) => updateData(value)}
- />
- </fieldset>
- ),
- });
|