excalidraw/src/actions/actionProperties.tsx

355 lines
9.8 KiB
TypeScript
Raw Normal View History

import React from "react";
import { ExcalidrawElement, ExcalidrawTextElement } from "../element/types";
import {
getCommonAttributeOfSelectedElements,
isSomeElementSelected,
} from "../scene";
import { ButtonSelect } from "../components/ButtonSelect";
import { isTextElement, redrawTextBoundingBox } from "../element";
import { ColorPicker } from "../components/ColorPicker";
import { AppState } from "../../src/types";
import { t } from "../i18n";
import { DEFAULT_FONT } from "../appState";
import { register } from "./register";
import { newElementWith, newTextElementWith } from "../element/mutateElement";
const changeProperty = (
elements: readonly ExcalidrawElement[],
appState: AppState,
2020-01-24 12:04:54 +02:00
callback: (element: ExcalidrawElement) => ExcalidrawElement,
) => {
return elements.map(element => {
if (appState.selectedElementIds[element.id]) {
return callback(element);
}
return element;
});
};
const getFormValue = function<T>(
elements: readonly ExcalidrawElement[],
appState: AppState,
getAttribute: (element: ExcalidrawElement) => T,
2020-01-24 12:04:54 +02:00
defaultValue?: T,
): T | null {
const editingElement = appState.editingElement;
return (
(editingElement && getAttribute(editingElement)) ??
(isSomeElementSelected(elements, appState)
? getCommonAttributeOfSelectedElements(elements, appState, getAttribute)
: defaultValue) ??
null
);
};
export const actionChangeStrokeColor = register({
name: "changeStrokeColor",
perform: (elements, appState, value) => {
return {
elements: changeProperty(elements, appState, el =>
newElementWith(el, {
strokeColor: value,
}),
),
2020-01-24 12:04:54 +02:00
appState: { ...appState, currentItemStrokeColor: value },
};
},
Fix issues related to history (#701) * Separate UI from Canvas * Explicitly define history recording * ActionManager: Set syncActionState during construction instead of in every call * Add commit to history flag to necessary actions * Disable undoing during multiElement * Write custom equality function for UI component to render it only when specific props and elements change * Remove stale comments about history skipping * Stop undo/redoing when in resizing element mode * wip * correctly reset resizingElement & add undo check * Separate selection element from the rest of the array and stop redrawing the UI when dragging the selection * Remove selectionElement from local storage * Remove unnecessary readonly type casting in actionFinalize * Fix undo / redo for multi points * Fix an issue that did not update history when elements were locked * Disable committing to history for noops - deleteSelected without deleting anything - Basic selection * Use generateEntry only inside history and pass elements and appstate to history * Update component after every history resume * Remove last item from the history only if in multi mode * Resume recording when element type is not selection * ensure we prevent hotkeys only on writable elements * Remove selection clearing from history * Remove one point arrows as they are invisibly small * Remove shape of elements from local storage * Fix removing invisible element from the array * add missing history resuming cases & simplify slice * fix lint * don't regenerate elements if no elements deselected * regenerate elements array on selection * reset state.selectionElement unconditionally * Use getter instead of passing appState and scene data through functions to actions * fix import Co-authored-by: David Luzar <luzar.david@gmail.com>
2020-02-05 22:47:10 +04:00
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,
2020-01-24 12:04:54 +02:00
appState.currentItemStrokeColor,
)}
onChange={updateData}
/>
</>
2020-01-24 12:04:54 +02:00
),
});
export const actionChangeBackgroundColor = register({
name: "changeBackgroundColor",
perform: (elements, appState, value) => {
return {
elements: changeProperty(elements, appState, el =>
newElementWith(el, {
backgroundColor: value,
}),
),
2020-01-24 12:04:54 +02:00
appState: { ...appState, currentItemBackgroundColor: value },
};
},
Fix issues related to history (#701) * Separate UI from Canvas * Explicitly define history recording * ActionManager: Set syncActionState during construction instead of in every call * Add commit to history flag to necessary actions * Disable undoing during multiElement * Write custom equality function for UI component to render it only when specific props and elements change * Remove stale comments about history skipping * Stop undo/redoing when in resizing element mode * wip * correctly reset resizingElement & add undo check * Separate selection element from the rest of the array and stop redrawing the UI when dragging the selection * Remove selectionElement from local storage * Remove unnecessary readonly type casting in actionFinalize * Fix undo / redo for multi points * Fix an issue that did not update history when elements were locked * Disable committing to history for noops - deleteSelected without deleting anything - Basic selection * Use generateEntry only inside history and pass elements and appstate to history * Update component after every history resume * Remove last item from the history only if in multi mode * Resume recording when element type is not selection * ensure we prevent hotkeys only on writable elements * Remove selection clearing from history * Remove one point arrows as they are invisibly small * Remove shape of elements from local storage * Fix removing invisible element from the array * add missing history resuming cases & simplify slice * fix lint * don't regenerate elements if no elements deselected * regenerate elements array on selection * reset state.selectionElement unconditionally * Use getter instead of passing appState and scene data through functions to actions * fix import Co-authored-by: David Luzar <luzar.david@gmail.com>
2020-02-05 22:47:10 +04:00
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,
2020-01-24 12:04:54 +02:00
appState.currentItemBackgroundColor,
)}
onChange={updateData}
/>
</>
2020-01-24 12:04:54 +02:00
),
});
export const actionChangeFillStyle = register({
name: "changeFillStyle",
perform: (elements, appState, value) => {
return {
elements: changeProperty(elements, appState, el =>
newElementWith(el, {
fillStyle: value,
}),
),
appState: { ...appState, currentItemFillStyle: value },
};
},
Fix issues related to history (#701) * Separate UI from Canvas * Explicitly define history recording * ActionManager: Set syncActionState during construction instead of in every call * Add commit to history flag to necessary actions * Disable undoing during multiElement * Write custom equality function for UI component to render it only when specific props and elements change * Remove stale comments about history skipping * Stop undo/redoing when in resizing element mode * wip * correctly reset resizingElement & add undo check * Separate selection element from the rest of the array and stop redrawing the UI when dragging the selection * Remove selectionElement from local storage * Remove unnecessary readonly type casting in actionFinalize * Fix undo / redo for multi points * Fix an issue that did not update history when elements were locked * Disable committing to history for noops - deleteSelected without deleting anything - Basic selection * Use generateEntry only inside history and pass elements and appstate to history * Update component after every history resume * Remove last item from the history only if in multi mode * Resume recording when element type is not selection * ensure we prevent hotkeys only on writable elements * Remove selection clearing from history * Remove one point arrows as they are invisibly small * Remove shape of elements from local storage * Fix removing invisible element from the array * add missing history resuming cases & simplify slice * fix lint * don't regenerate elements if no elements deselected * regenerate elements array on selection * reset state.selectionElement unconditionally * Use getter instead of passing appState and scene data through functions to actions * fix import Co-authored-by: David Luzar <luzar.david@gmail.com>
2020-02-05 22:47:10 +04:00
commitToHistory: () => true,
PanelComponent: ({ elements, appState, updateData }) => (
<fieldset>
<legend>{t("labels.fill")}</legend>
<ButtonSelect
options={[
{ value: "solid", text: t("labels.solid") },
{ value: "hachure", text: t("labels.hachure") },
2020-01-24 12:04:54 +02:00
{ value: "cross-hatch", text: t("labels.crossHatch") },
]}
group="fill"
value={getFormValue(
elements,
appState,
2020-01-24 12:04:54 +02:00
element => element.fillStyle,
appState.currentItemFillStyle,
)}
onChange={value => {
updateData(value);
}}
/>
</fieldset>
2020-01-24 12:04:54 +02:00
),
});
export const actionChangeStrokeWidth = register({
name: "changeStrokeWidth",
perform: (elements, appState, value) => {
return {
elements: changeProperty(elements, appState, el =>
newElementWith(el, {
strokeWidth: value,
}),
),
appState: { ...appState, currentItemStrokeWidth: value },
};
},
Fix issues related to history (#701) * Separate UI from Canvas * Explicitly define history recording * ActionManager: Set syncActionState during construction instead of in every call * Add commit to history flag to necessary actions * Disable undoing during multiElement * Write custom equality function for UI component to render it only when specific props and elements change * Remove stale comments about history skipping * Stop undo/redoing when in resizing element mode * wip * correctly reset resizingElement & add undo check * Separate selection element from the rest of the array and stop redrawing the UI when dragging the selection * Remove selectionElement from local storage * Remove unnecessary readonly type casting in actionFinalize * Fix undo / redo for multi points * Fix an issue that did not update history when elements were locked * Disable committing to history for noops - deleteSelected without deleting anything - Basic selection * Use generateEntry only inside history and pass elements and appstate to history * Update component after every history resume * Remove last item from the history only if in multi mode * Resume recording when element type is not selection * ensure we prevent hotkeys only on writable elements * Remove selection clearing from history * Remove one point arrows as they are invisibly small * Remove shape of elements from local storage * Fix removing invisible element from the array * add missing history resuming cases & simplify slice * fix lint * don't regenerate elements if no elements deselected * regenerate elements array on selection * reset state.selectionElement unconditionally * Use getter instead of passing appState and scene data through functions to actions * fix import Co-authored-by: David Luzar <luzar.david@gmail.com>
2020-02-05 22:47:10 +04:00
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") },
2020-01-24 12:04:54 +02:00
{ value: 4, text: t("labels.extraBold") },
]}
value={getFormValue(
elements,
appState,
2020-01-24 12:04:54 +02:00
element => element.strokeWidth,
appState.currentItemStrokeWidth,
)}
onChange={value => updateData(value)}
/>
</fieldset>
2020-01-24 12:04:54 +02:00
),
});
export const actionChangeSloppiness = register({
name: "changeSloppiness",
perform: (elements, appState, value) => {
return {
elements: changeProperty(elements, appState, el =>
newElementWith(el, {
roughness: value,
}),
),
appState: { ...appState, currentItemRoughness: value },
};
},
Fix issues related to history (#701) * Separate UI from Canvas * Explicitly define history recording * ActionManager: Set syncActionState during construction instead of in every call * Add commit to history flag to necessary actions * Disable undoing during multiElement * Write custom equality function for UI component to render it only when specific props and elements change * Remove stale comments about history skipping * Stop undo/redoing when in resizing element mode * wip * correctly reset resizingElement & add undo check * Separate selection element from the rest of the array and stop redrawing the UI when dragging the selection * Remove selectionElement from local storage * Remove unnecessary readonly type casting in actionFinalize * Fix undo / redo for multi points * Fix an issue that did not update history when elements were locked * Disable committing to history for noops - deleteSelected without deleting anything - Basic selection * Use generateEntry only inside history and pass elements and appstate to history * Update component after every history resume * Remove last item from the history only if in multi mode * Resume recording when element type is not selection * ensure we prevent hotkeys only on writable elements * Remove selection clearing from history * Remove one point arrows as they are invisibly small * Remove shape of elements from local storage * Fix removing invisible element from the array * add missing history resuming cases & simplify slice * fix lint * don't regenerate elements if no elements deselected * regenerate elements array on selection * reset state.selectionElement unconditionally * Use getter instead of passing appState and scene data through functions to actions * fix import Co-authored-by: David Luzar <luzar.david@gmail.com>
2020-02-05 22:47:10 +04:00
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") },
2020-01-26 20:16:33 +01:00
{ value: 2, text: t("labels.cartoonist") },
]}
value={getFormValue(
elements,
appState,
2020-01-24 12:04:54 +02:00
element => element.roughness,
appState.currentItemRoughness,
)}
onChange={value => updateData(value)}
/>
</fieldset>
2020-01-24 12:04:54 +02:00
),
});
export const actionChangeOpacity = register({
name: "changeOpacity",
perform: (elements, appState, value) => {
return {
elements: changeProperty(elements, appState, el =>
newElementWith(el, {
opacity: value,
}),
),
appState: { ...appState, currentItemOpacity: value },
};
},
Fix issues related to history (#701) * Separate UI from Canvas * Explicitly define history recording * ActionManager: Set syncActionState during construction instead of in every call * Add commit to history flag to necessary actions * Disable undoing during multiElement * Write custom equality function for UI component to render it only when specific props and elements change * Remove stale comments about history skipping * Stop undo/redoing when in resizing element mode * wip * correctly reset resizingElement & add undo check * Separate selection element from the rest of the array and stop redrawing the UI when dragging the selection * Remove selectionElement from local storage * Remove unnecessary readonly type casting in actionFinalize * Fix undo / redo for multi points * Fix an issue that did not update history when elements were locked * Disable committing to history for noops - deleteSelected without deleting anything - Basic selection * Use generateEntry only inside history and pass elements and appstate to history * Update component after every history resume * Remove last item from the history only if in multi mode * Resume recording when element type is not selection * ensure we prevent hotkeys only on writable elements * Remove selection clearing from history * Remove one point arrows as they are invisibly small * Remove shape of elements from local storage * Fix removing invisible element from the array * add missing history resuming cases & simplify slice * fix lint * don't regenerate elements if no elements deselected * regenerate elements array on selection * reset state.selectionElement unconditionally * Use getter instead of passing appState and scene data through functions to actions * fix import Co-authored-by: David Luzar <luzar.david@gmail.com>
2020-02-05 22:47:10 +04:00
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>
2020-01-24 12:04:54 +02:00
),
});
export const actionChangeFontSize = register({
name: "changeFontSize",
perform: (elements, appState, value) => {
return {
elements: changeProperty(elements, appState, el => {
if (isTextElement(el)) {
const element: ExcalidrawTextElement = newTextElementWith(el, {
2020-01-24 12:04:54 +02:00
font: `${value}px ${el.font.split("px ")[1]}`,
});
redrawTextBoundingBox(element);
return element;
}
return el;
2020-01-24 12:04:54 +02:00
}),
appState: {
...appState,
currentItemFont: `${value}px ${
appState.currentItemFont.split("px ")[1]
}`,
},
};
},
Fix issues related to history (#701) * Separate UI from Canvas * Explicitly define history recording * ActionManager: Set syncActionState during construction instead of in every call * Add commit to history flag to necessary actions * Disable undoing during multiElement * Write custom equality function for UI component to render it only when specific props and elements change * Remove stale comments about history skipping * Stop undo/redoing when in resizing element mode * wip * correctly reset resizingElement & add undo check * Separate selection element from the rest of the array and stop redrawing the UI when dragging the selection * Remove selectionElement from local storage * Remove unnecessary readonly type casting in actionFinalize * Fix undo / redo for multi points * Fix an issue that did not update history when elements were locked * Disable committing to history for noops - deleteSelected without deleting anything - Basic selection * Use generateEntry only inside history and pass elements and appstate to history * Update component after every history resume * Remove last item from the history only if in multi mode * Resume recording when element type is not selection * ensure we prevent hotkeys only on writable elements * Remove selection clearing from history * Remove one point arrows as they are invisibly small * Remove shape of elements from local storage * Fix removing invisible element from the array * add missing history resuming cases & simplify slice * fix lint * don't regenerate elements if no elements deselected * regenerate elements array on selection * reset state.selectionElement unconditionally * Use getter instead of passing appState and scene data through functions to actions * fix import Co-authored-by: David Luzar <luzar.david@gmail.com>
2020-02-05 22:47:10 +04:00
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") },
2020-01-24 12:04:54 +02:00
{ value: 36, text: t("labels.veryLarge") },
]}
value={getFormValue(
elements,
appState,
2020-01-24 12:04:54 +02:00
element => isTextElement(element) && +element.font.split("px ")[0],
+(appState.currentItemFont || DEFAULT_FONT).split("px ")[0],
)}
onChange={value => updateData(value)}
/>
</fieldset>
2020-01-24 12:04:54 +02:00
),
});
export const actionChangeFontFamily = register({
name: "changeFontFamily",
perform: (elements, appState, value) => {
return {
elements: changeProperty(elements, appState, el => {
if (isTextElement(el)) {
const element: ExcalidrawTextElement = newTextElementWith(el, {
2020-01-24 12:04:54 +02:00
font: `${el.font.split("px ")[0]}px ${value}`,
});
redrawTextBoundingBox(element);
return element;
}
return el;
2020-01-24 12:04:54 +02:00
}),
appState: {
...appState,
currentItemFont: `${
appState.currentItemFont.split("px ")[0]
}px ${value}`,
},
};
},
Fix issues related to history (#701) * Separate UI from Canvas * Explicitly define history recording * ActionManager: Set syncActionState during construction instead of in every call * Add commit to history flag to necessary actions * Disable undoing during multiElement * Write custom equality function for UI component to render it only when specific props and elements change * Remove stale comments about history skipping * Stop undo/redoing when in resizing element mode * wip * correctly reset resizingElement & add undo check * Separate selection element from the rest of the array and stop redrawing the UI when dragging the selection * Remove selectionElement from local storage * Remove unnecessary readonly type casting in actionFinalize * Fix undo / redo for multi points * Fix an issue that did not update history when elements were locked * Disable committing to history for noops - deleteSelected without deleting anything - Basic selection * Use generateEntry only inside history and pass elements and appstate to history * Update component after every history resume * Remove last item from the history only if in multi mode * Resume recording when element type is not selection * ensure we prevent hotkeys only on writable elements * Remove selection clearing from history * Remove one point arrows as they are invisibly small * Remove shape of elements from local storage * Fix removing invisible element from the array * add missing history resuming cases & simplify slice * fix lint * don't regenerate elements if no elements deselected * regenerate elements array on selection * reset state.selectionElement unconditionally * Use getter instead of passing appState and scene data through functions to actions * fix import Co-authored-by: David Luzar <luzar.david@gmail.com>
2020-02-05 22:47:10 +04:00
commitToHistory: () => true,
PanelComponent: ({ elements, appState, updateData }) => (
<fieldset>
<legend>{t("labels.fontFamily")}</legend>
<ButtonSelect
group="font-family"
options={[
{ value: "Virgil", text: t("labels.handDrawn") },
{ value: "Helvetica", text: t("labels.normal") },
2020-01-24 12:04:54 +02:00
{ value: "Cascadia", text: t("labels.code") },
]}
value={getFormValue(
elements,
appState,
2020-01-24 12:04:54 +02:00
element => isTextElement(element) && element.font.split("px ")[1],
(appState.currentItemFont || DEFAULT_FONT).split("px ")[1],
)}
onChange={value => updateData(value)}
/>
</fieldset>
2020-01-24 12:04:54 +02:00
),
});