refactor: rename elementType to activeTool and make it an object (#4968)

* refactor: rename elementType to activeTool

* update docs

* fix snap

* update activeToll to be an object and review fixes

* fix tests

* fix
This commit is contained in:
Aakansha Doshi 2022-03-25 20:46:01 +05:30 committed by GitHub
parent 2209e2c1e8
commit 127af9db23
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 364 additions and 207 deletions

View file

@ -30,12 +30,12 @@ export const SelectedShapeActions = ({
appState,
elements,
renderAction,
elementType,
activeTool,
}: {
appState: AppState;
elements: readonly ExcalidrawElement[];
renderAction: ActionManager["renderAction"];
elementType: AppState["elementType"];
activeTool: AppState["activeTool"]["type"];
}) => {
const targetElements = getTargetElements(
getNonDeletedElements(elements),
@ -55,13 +55,13 @@ export const SelectedShapeActions = ({
const isRTL = document.documentElement.getAttribute("dir") === "rtl";
const showFillIcons =
hasBackground(elementType) ||
hasBackground(activeTool) ||
targetElements.some(
(element) =>
hasBackground(element.type) && !isTransparent(element.backgroundColor),
);
const showChangeBackgroundIcons =
hasBackground(elementType) ||
hasBackground(activeTool) ||
targetElements.some((element) => hasBackground(element.type));
const showLinkIcon =
@ -78,23 +78,23 @@ export const SelectedShapeActions = ({
return (
<div className="panelColumn">
{((hasStrokeColor(elementType) &&
elementType !== "image" &&
{((hasStrokeColor(activeTool) &&
activeTool !== "image" &&
commonSelectedType !== "image") ||
targetElements.some((element) => hasStrokeColor(element.type))) &&
renderAction("changeStrokeColor")}
{showChangeBackgroundIcons && renderAction("changeBackgroundColor")}
{showFillIcons && renderAction("changeFillStyle")}
{(hasStrokeWidth(elementType) ||
{(hasStrokeWidth(activeTool) ||
targetElements.some((element) => hasStrokeWidth(element.type))) &&
renderAction("changeStrokeWidth")}
{(elementType === "freedraw" ||
{(activeTool === "freedraw" ||
targetElements.some((element) => element.type === "freedraw")) &&
renderAction("changeStrokeShape")}
{(hasStrokeStyle(elementType) ||
{(hasStrokeStyle(activeTool) ||
targetElements.some((element) => hasStrokeStyle(element.type))) && (
<>
{renderAction("changeStrokeStyle")}
@ -102,12 +102,12 @@ export const SelectedShapeActions = ({
</>
)}
{(canChangeSharpness(elementType) ||
{(canChangeSharpness(activeTool) ||
targetElements.some((element) => canChangeSharpness(element.type))) && (
<>{renderAction("changeSharpness")}</>
)}
{(hasText(elementType) ||
{(hasText(activeTool) ||
targetElements.some((element) => hasText(element.type))) && (
<>
{renderAction("changeFontSize")}
@ -122,7 +122,7 @@ export const SelectedShapeActions = ({
(element) =>
hasBoundTextElement(element) || isBoundToContainer(element),
) && renderAction("changeVerticalAlign")}
{(canHaveArrowheads(elementType) ||
{(canHaveArrowheads(activeTool) ||
targetElements.some((element) => canHaveArrowheads(element.type))) && (
<>{renderAction("changeArrowhead")}</>
)}
@ -190,23 +190,23 @@ export const SelectedShapeActions = ({
export const ShapesSwitcher = ({
canvas,
elementType,
activeTool,
setAppState,
onImageAction,
appState,
}: {
canvas: HTMLCanvasElement | null;
elementType: AppState["elementType"];
activeTool: AppState["activeTool"];
setAppState: React.Component<any, AppState>["setState"];
onImageAction: (data: { pointerType: PointerType | null }) => void;
appState: AppState;
}) => {
const onChange = withBatchedUpdates(
({
elementType,
activeToolType,
pointerType,
}: {
elementType: typeof SHAPES[number]["value"];
activeToolType: typeof SHAPES[number]["value"];
pointerType: PointerType | null;
}) => {
if (!appState.penDetected && pointerType === "pen") {
@ -216,12 +216,12 @@ export const ShapesSwitcher = ({
});
}
setAppState({
elementType,
activeTool: { type: activeToolType },
multiElement: null,
selectedElementIds: {},
});
setCursorForShape(canvas, { ...appState, elementType });
if (elementType === "image") {
setCursorForShape(canvas, { ...appState, activeTool });
if (activeTool.type === "image") {
onImageAction({ pointerType });
}
},
@ -241,7 +241,7 @@ export const ShapesSwitcher = ({
key={value}
type="radio"
icon={icon}
checked={elementType === value}
checked={activeTool.type === value}
name="editor-current-shape"
title={`${capitalizeString(label)}${shortcut}`}
keyBindingLabel={`${index + 1}`}
@ -249,10 +249,10 @@ export const ShapesSwitcher = ({
aria-keyshortcuts={shortcut}
data-testid={value}
onPointerDown={({ pointerType }) => {
onChange({ elementType: value, pointerType });
onChange({ activeToolType: value, pointerType });
}}
onChange={({ pointerType }) => {
onChange({ elementType: value, pointerType });
onChange({ activeToolType: value, pointerType });
}}
/>
);

View file

@ -811,10 +811,10 @@ class App extends React.Component<AppProps, AppState> {
scene.appState = {
...scene.appState,
elementType:
scene.appState.elementType === "image"
? "selection"
: scene.appState.elementType,
activeTool:
scene.appState.activeTool.type === "image"
? { type: "selection" }
: scene.appState.activeTool,
isLoading: false,
};
if (initialData?.scrollToContent) {
@ -1068,15 +1068,15 @@ class App extends React.Component<AppProps, AppState> {
Object.keys(this.state.selectedElementIds).length &&
isEraserActive(this.state)
) {
this.setState({ elementType: "selection" });
this.setState({ activeTool: { type: "selection" } });
}
if (prevState.theme !== this.state.theme) {
setEraserCursor(this.canvas, this.state.theme);
}
// Hide hyperlink popup if shown when element type is not selection
if (
prevState.elementType === "selection" &&
this.state.elementType !== "selection" &&
prevState.activeTool.type === "selection" &&
this.state.activeTool.type !== "selection" &&
this.state.showHyperlinkPopup
) {
this.setState({ showHyperlinkPopup: false });
@ -1130,7 +1130,7 @@ class App extends React.Component<AppProps, AppState> {
}
const { multiElement } = prevState;
if (
prevState.elementType !== this.state.elementType &&
prevState.activeTool !== this.state.activeTool &&
multiElement != null &&
isBindingEnabled(this.state) &&
isBindingElement(multiElement)
@ -1433,7 +1433,7 @@ class App extends React.Component<AppProps, AppState> {
} else if (data.text) {
this.addTextFromPaste(data.text);
}
this.selectShapeTool("selection");
this.setActiveTool({ type: "selection" });
event?.preventDefault();
},
);
@ -1521,7 +1521,7 @@ class App extends React.Component<AppProps, AppState> {
}
},
);
this.selectShapeTool("selection");
this.setActiveTool({ type: "selection" });
};
private addTextFromPaste(text: any) {
@ -1574,9 +1574,9 @@ class App extends React.Component<AppProps, AppState> {
this.setState((prevState) => {
return {
elementLocked: !prevState.elementLocked,
elementType: prevState.elementLocked
? "selection"
: prevState.elementType,
activeTool: prevState.elementLocked
? { type: "selection" }
: prevState.activeTool,
};
});
};
@ -1851,7 +1851,7 @@ class App extends React.Component<AppProps, AppState> {
) {
const shape = findShapeByKey(event.key);
if (shape) {
this.selectShapeTool(shape);
this.setActiveTool({ type: shape });
} else if (event.key === KEYS.Q) {
this.toggleLock();
}
@ -1868,7 +1868,7 @@ class App extends React.Component<AppProps, AppState> {
this.state,
);
if (
this.state.elementType === "selection" &&
this.state.activeTool.type === "selection" &&
!selectedElements.length
) {
return;
@ -1876,7 +1876,7 @@ class App extends React.Component<AppProps, AppState> {
if (
event.key === KEYS.G &&
(hasBackground(this.state.elementType) ||
(hasBackground(this.state.activeTool.type) ||
selectedElements.some((element) => hasBackground(element.type)))
) {
this.setState({ openPopup: "backgroundColorPicker" });
@ -1892,7 +1892,7 @@ class App extends React.Component<AppProps, AppState> {
if (event.key === KEYS.SPACE) {
if (this.state.viewModeEnabled) {
setCursor(this.canvas, CURSOR_TYPE.GRAB);
} else if (this.state.elementType === "selection") {
} else if (this.state.activeTool.type === "selection") {
resetCursor(this.canvas);
} else {
setCursorForShape(this.canvas, this.state);
@ -1919,28 +1919,28 @@ class App extends React.Component<AppProps, AppState> {
}
});
private selectShapeTool(elementType: AppState["elementType"]) {
private setActiveTool(tool: AppState["activeTool"]) {
if (!isHoldingSpace) {
setCursorForShape(this.canvas, this.state);
}
if (isToolIcon(document.activeElement)) {
this.focusContainer();
}
if (!isLinearElementType(elementType)) {
if (!isLinearElementType(tool.type)) {
this.setState({ suggestedBindings: [] });
}
if (elementType === "image") {
if (tool.type === "image") {
this.onImageAction();
}
if (elementType !== "selection") {
if (tool.type !== "selection") {
this.setState({
elementType,
activeTool: tool,
selectedElementIds: {},
selectedGroupIds: {},
editingGroupId: null,
});
} else {
this.setState({ elementType });
this.setState({ activeTool: tool });
}
}
@ -2314,7 +2314,7 @@ class App extends React.Component<AppProps, AppState> {
return;
}
// we should only be able to double click when mode is selection
if (this.state.elementType !== "selection") {
if (this.state.activeTool.type !== "selection") {
return;
}
@ -2504,7 +2504,7 @@ class App extends React.Component<AppProps, AppState> {
const distance = getDistance(Array.from(gesture.pointers.values()));
const scaleFactor =
this.state.elementType === "freedraw" && this.state.penMode
this.state.activeTool.type === "freedraw" && this.state.penMode
? 1
: distance / gesture.initialDistance;
@ -2579,7 +2579,7 @@ class App extends React.Component<AppProps, AppState> {
}
}
if (isBindingElementType(this.state.elementType)) {
if (isBindingElementType(this.state.activeTool.type)) {
// Hovering with a selected tool or creating new linear element via click
// and point
const { draggingElement } = this.state;
@ -2655,9 +2655,9 @@ class App extends React.Component<AppProps, AppState> {
const hasDeselectedButton = Boolean(event.buttons);
if (
hasDeselectedButton ||
(this.state.elementType !== "selection" &&
this.state.elementType !== "text" &&
this.state.elementType !== "eraser")
(this.state.activeTool.type !== "selection" &&
this.state.activeTool.type !== "text" &&
this.state.activeTool.type !== "eraser")
) {
return;
}
@ -2734,7 +2734,7 @@ class App extends React.Component<AppProps, AppState> {
!this.state.showHyperlinkPopup
) {
this.setState({ showHyperlinkPopup: "info" });
} else if (this.state.elementType === "text") {
} else if (this.state.activeTool.type === "text") {
setCursor(
this.canvas,
isTextElement(hitElement) ? CURSOR_TYPE.TEXT : CURSOR_TYPE.CROSSHAIR,
@ -2946,27 +2946,27 @@ class App extends React.Component<AppProps, AppState> {
const allowOnPointerDown =
!this.state.penMode ||
event.pointerType !== "touch" ||
this.state.elementType === "selection" ||
this.state.elementType === "text" ||
this.state.elementType === "image";
this.state.activeTool.type === "selection" ||
this.state.activeTool.type === "text" ||
this.state.activeTool.type === "image";
if (!allowOnPointerDown) {
return;
}
if (this.state.elementType === "text") {
if (this.state.activeTool.type === "text") {
this.handleTextOnPointerDown(event, pointerDownState);
return;
} else if (
this.state.elementType === "arrow" ||
this.state.elementType === "line"
this.state.activeTool.type === "arrow" ||
this.state.activeTool.type === "line"
) {
this.handleLinearElementOnPointerDown(
event,
this.state.elementType,
this.state.activeTool.type,
pointerDownState,
);
} else if (this.state.elementType === "image") {
} else if (this.state.activeTool.type === "image") {
// reset image preview on pointerdown
setCursor(this.canvas, CURSOR_TYPE.CROSSHAIR);
@ -2986,15 +2986,15 @@ class App extends React.Component<AppProps, AppState> {
x,
y,
});
} else if (this.state.elementType === "freedraw") {
} else if (this.state.activeTool.type === "freedraw") {
this.handleFreeDrawElementOnPointerDown(
event,
this.state.elementType,
this.state.activeTool.type,
pointerDownState,
);
} else if (this.state.elementType !== "eraser") {
} else if (this.state.activeTool.type !== "eraser") {
this.createGenericElementOnPointerDown(
this.state.elementType,
this.state.activeTool.type,
pointerDownState,
);
}
@ -3312,7 +3312,7 @@ class App extends React.Component<AppProps, AppState> {
}
private clearSelectionIfNotUsingSelection = (): void => {
if (this.state.elementType !== "selection") {
if (this.state.activeTool.type !== "selection") {
this.setState({
selectedElementIds: {},
selectedGroupIds: {},
@ -3328,7 +3328,7 @@ class App extends React.Component<AppProps, AppState> {
event: React.PointerEvent<HTMLCanvasElement>,
pointerDownState: PointerDownState,
): boolean => {
if (this.state.elementType === "selection") {
if (this.state.activeTool.type === "selection") {
const elements = this.scene.getElements();
const selectedElements = getSelectedElements(elements, this.state);
if (selectedElements.length === 1 && !this.state.editingLinearElement) {
@ -3573,7 +3573,7 @@ class App extends React.Component<AppProps, AppState> {
resetCursor(this.canvas);
if (!this.state.elementLocked) {
this.setState({
elementType: "selection",
activeTool: { type: "selection" },
});
}
};
@ -3882,8 +3882,8 @@ class App extends React.Component<AppProps, AppState> {
// triggering pointermove)
if (
!pointerDownState.drag.hasOccurred &&
(this.state.elementType === "arrow" ||
this.state.elementType === "line")
(this.state.activeTool.type === "arrow" ||
this.state.activeTool.type === "line")
) {
if (
distance2d(
@ -4078,7 +4078,7 @@ class App extends React.Component<AppProps, AppState> {
if (shouldRotateWithDiscreteAngle(event) && points.length === 2) {
({ width: dx, height: dy } = getPerfectElementSize(
this.state.elementType,
this.state.activeTool.type,
dx,
dy,
));
@ -4106,7 +4106,7 @@ class App extends React.Component<AppProps, AppState> {
this.maybeDragNewGenericElement(pointerDownState, event);
}
if (this.state.elementType === "selection") {
if (this.state.activeTool.type === "selection") {
pointerDownState.boxSelection.hasOccurred = true;
const elements = this.scene.getElements();
@ -4217,7 +4217,7 @@ class App extends React.Component<AppProps, AppState> {
draggingElement,
resizingElement,
multiElement,
elementType,
activeTool,
elementLocked,
isResizing,
isRotating,
@ -4386,7 +4386,7 @@ class App extends React.Component<AppProps, AppState> {
resetCursor(this.canvas);
this.setState((prevState) => ({
draggingElement: null,
elementType: "selection",
activeTool: { type: "selection" },
selectedElementIds: {
...prevState.selectedElementIds,
[this.state.draggingElement!.id]: true,
@ -4406,7 +4406,7 @@ class App extends React.Component<AppProps, AppState> {
}
if (
elementType !== "selection" &&
activeTool.type !== "selection" &&
draggingElement &&
isInvisiblySmallElement(draggingElement)
) {
@ -4575,7 +4575,7 @@ class App extends React.Component<AppProps, AppState> {
return;
}
if (!elementLocked && elementType !== "freedraw" && draggingElement) {
if (!elementLocked && activeTool.type !== "freedraw" && draggingElement) {
this.setState((prevState) => ({
selectedElementIds: {
...prevState.selectedElementIds,
@ -4585,7 +4585,7 @@ class App extends React.Component<AppProps, AppState> {
}
if (
elementType !== "selection" ||
activeTool.type !== "selection" ||
isSomeElementSelected(this.scene.getElements(), this.state)
) {
this.history.resumeRecording();
@ -4599,12 +4599,12 @@ class App extends React.Component<AppProps, AppState> {
);
}
if (!elementLocked && elementType !== "freedraw") {
if (!elementLocked && activeTool.type !== "freedraw") {
resetCursor(this.canvas);
this.setState({
draggingElement: null,
suggestedBindings: [],
elementType: "selection",
activeTool: { type: "selection" },
});
} else {
this.setState({
@ -4910,7 +4910,7 @@ class App extends React.Component<AppProps, AppState> {
{
pendingImageElement: null,
editingElement: null,
elementType: "selection",
activeTool: { type: "selection" },
},
() => {
this.actionManager.executeAction(actionFinalize);
@ -5280,7 +5280,7 @@ class App extends React.Component<AppProps, AppState> {
(event.nativeEvent.pointerType === "pen" &&
// always allow if user uses a pen secondary button
event.button !== POINTER_BUTTON.SECONDARY)) &&
this.state.elementType !== "selection"
this.state.activeTool.type !== "selection"
) {
return;
}
@ -5314,10 +5314,13 @@ class App extends React.Component<AppProps, AppState> {
if (!draggingElement) {
return;
}
if (draggingElement.type === "selection") {
if (
draggingElement.type === "selection" &&
this.state.activeTool.type !== "eraser"
) {
dragNewElement(
draggingElement,
this.state.elementType,
this.state.activeTool.type,
pointerDownState.origin.x,
pointerDownState.origin.y,
pointerCoords.x,
@ -5344,7 +5347,7 @@ class App extends React.Component<AppProps, AppState> {
dragNewElement(
draggingElement,
this.state.elementType,
this.state.activeTool.type,
pointerDownState.originInGrid.x,
pointerDownState.originInGrid.y,
gridX,

View file

@ -20,28 +20,28 @@ interface HintViewerProps {
}
const getHints = ({ appState, elements, isMobile }: HintViewerProps) => {
const { elementType, isResizing, isRotating, lastPointerDownWith } = appState;
const { activeTool, isResizing, isRotating, lastPointerDownWith } = appState;
const multiMode = appState.multiElement !== null;
if (isEraserActive(appState)) {
return t("hints.eraserRevert");
}
if (elementType === "arrow" || elementType === "line") {
if (activeTool.type === "arrow" || activeTool.type === "line") {
if (!multiMode) {
return t("hints.linearElement");
}
return t("hints.linearElementMulti");
}
if (elementType === "freedraw") {
if (activeTool.type === "freedraw") {
return t("hints.freeDraw");
}
if (elementType === "text") {
if (activeTool.type === "text") {
return t("hints.text");
}
if (appState.elementType === "image" && appState.pendingImageElement) {
if (appState.activeTool.type === "image" && appState.pendingImageElement) {
return t("hints.placeImage");
}
@ -73,7 +73,7 @@ const getHints = ({ appState, elements, isMobile }: HintViewerProps) => {
return t("hints.text_editing");
}
if (elementType === "selection") {
if (activeTool.type === "selection") {
if (
appState.draggingElement?.type === "selection" &&
!appState.editingElement &&

View file

@ -248,7 +248,7 @@ const LayerUI = ({
appState={appState}
elements={elements}
renderAction={actionManager.renderAction}
elementType={appState.elementType}
activeTool={appState.activeTool.type}
/>
</Island>
</Section>
@ -345,7 +345,7 @@ const LayerUI = ({
<ShapesSwitcher
appState={appState}
canvas={canvas}
elementType={appState.elementType}
activeTool={appState.activeTool}
setAppState={setAppState}
onImageAction={({ pointerType }) => {
onImageAction({

View file

@ -74,7 +74,7 @@ export const MobileMenu = ({
<ShapesSwitcher
appState={appState}
canvas={canvas}
elementType={appState.elementType}
activeTool={appState.activeTool}
setAppState={setAppState}
onImageAction={({ pointerType }) => {
onImageAction({
@ -226,7 +226,7 @@ export const MobileMenu = ({
appState={appState}
elements={elements}
renderAction={actionManager.renderAction}
elementType={appState.elementType}
activeTool={appState.activeTool.type}
/>
</Section>
) : null}