|
|
|
@@ -1,5 +1,5 @@
|
|
|
|
|
import clsx from "clsx";
|
|
|
|
|
import { useState } from "react";
|
|
|
|
|
import { useRef, useState } from "react";
|
|
|
|
|
import * as Popover from "@radix-ui/react-popover";
|
|
|
|
|
|
|
|
|
|
import {
|
|
|
|
@@ -86,6 +86,7 @@ import type {
|
|
|
|
|
AppState,
|
|
|
|
|
} from "../types";
|
|
|
|
|
import type { ActionManager } from "../actions/manager";
|
|
|
|
|
import { Island } from "./Island";
|
|
|
|
|
|
|
|
|
|
// Common CSS class combinations
|
|
|
|
|
const PROPERTIES_CLASSES = clsx([
|
|
|
|
@@ -305,27 +306,19 @@ export const SelectedShapeActions = ({
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const CompactShapeActions = ({
|
|
|
|
|
const CombinedShapeProperties = ({
|
|
|
|
|
appState,
|
|
|
|
|
elementsMap,
|
|
|
|
|
renderAction,
|
|
|
|
|
app,
|
|
|
|
|
setAppState,
|
|
|
|
|
targetElements,
|
|
|
|
|
container,
|
|
|
|
|
}: {
|
|
|
|
|
targetElements: ExcalidrawElement[];
|
|
|
|
|
appState: UIAppState;
|
|
|
|
|
elementsMap: NonDeletedElementsMap | NonDeletedSceneElementsMap;
|
|
|
|
|
renderAction: ActionManager["renderAction"];
|
|
|
|
|
app: AppClassProperties;
|
|
|
|
|
setAppState: React.Component<any, AppState>["setState"];
|
|
|
|
|
container: HTMLDivElement | null;
|
|
|
|
|
}) => {
|
|
|
|
|
const targetElements = getTargetElements(elementsMap, appState);
|
|
|
|
|
const { saveCaretPosition, restoreCaretPosition } = useTextEditorFocus();
|
|
|
|
|
const { container } = useExcalidrawContainer();
|
|
|
|
|
|
|
|
|
|
const isEditingTextOrNewElement = Boolean(
|
|
|
|
|
appState.editingTextElement || appState.newElement,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const showFillIcons =
|
|
|
|
|
(hasBackground(appState.activeTool.type) &&
|
|
|
|
|
!isTransparent(appState.currentItemBackgroundColor)) ||
|
|
|
|
@@ -334,56 +327,20 @@ export const CompactShapeActions = ({
|
|
|
|
|
hasBackground(element.type) && !isTransparent(element.backgroundColor),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const showLinkIcon = targetElements.length === 1;
|
|
|
|
|
|
|
|
|
|
const showLineEditorAction =
|
|
|
|
|
!appState.selectedLinearElement?.isEditing &&
|
|
|
|
|
targetElements.length === 1 &&
|
|
|
|
|
isLinearElement(targetElements[0]) &&
|
|
|
|
|
!isElbowArrow(targetElements[0]);
|
|
|
|
|
|
|
|
|
|
const showCropEditorAction =
|
|
|
|
|
!appState.croppingElementId &&
|
|
|
|
|
targetElements.length === 1 &&
|
|
|
|
|
isImageElement(targetElements[0]);
|
|
|
|
|
|
|
|
|
|
const showAlignActions = alignActionsPredicate(appState, app);
|
|
|
|
|
|
|
|
|
|
let isSingleElementBoundContainer = false;
|
|
|
|
|
if (
|
|
|
|
|
targetElements.length === 2 &&
|
|
|
|
|
(hasBoundTextElement(targetElements[0]) ||
|
|
|
|
|
hasBoundTextElement(targetElements[1]))
|
|
|
|
|
) {
|
|
|
|
|
isSingleElementBoundContainer = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const isRTL = document.documentElement.getAttribute("dir") === "rtl";
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="compact-shape-actions">
|
|
|
|
|
{/* Stroke Color */}
|
|
|
|
|
{canChangeStrokeColor(appState, targetElements) && (
|
|
|
|
|
<div className={clsx("compact-action-item")}>
|
|
|
|
|
{renderAction("changeStrokeColor")}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* Background Color */}
|
|
|
|
|
{canChangeBackgroundColor(appState, targetElements) && (
|
|
|
|
|
<div className="compact-action-item">
|
|
|
|
|
{renderAction("changeBackgroundColor")}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* Combined Properties (Fill, Stroke, Opacity) */}
|
|
|
|
|
{(showFillIcons ||
|
|
|
|
|
const showShowCombinedProperties =
|
|
|
|
|
showFillIcons ||
|
|
|
|
|
hasStrokeWidth(appState.activeTool.type) ||
|
|
|
|
|
targetElements.some((element) => hasStrokeWidth(element.type)) ||
|
|
|
|
|
hasStrokeStyle(appState.activeTool.type) ||
|
|
|
|
|
targetElements.some((element) => hasStrokeStyle(element.type)) ||
|
|
|
|
|
canChangeRoundness(appState.activeTool.type) ||
|
|
|
|
|
targetElements.some((element) => canChangeRoundness(element.type))) && (
|
|
|
|
|
targetElements.some((element) => canChangeRoundness(element.type));
|
|
|
|
|
|
|
|
|
|
if (!showShowCombinedProperties) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="compact-action-item">
|
|
|
|
|
<Popover.Root
|
|
|
|
|
open={appState.openPopup === "compactStrokeStyles"}
|
|
|
|
@@ -449,11 +406,33 @@ export const CompactShapeActions = ({
|
|
|
|
|
)}
|
|
|
|
|
</Popover.Root>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
{/* Combined Arrow Properties */}
|
|
|
|
|
{(toolIsArrow(appState.activeTool.type) ||
|
|
|
|
|
targetElements.some((element) => toolIsArrow(element.type))) && (
|
|
|
|
|
const CombinedArrowProperties = ({
|
|
|
|
|
appState,
|
|
|
|
|
renderAction,
|
|
|
|
|
setAppState,
|
|
|
|
|
targetElements,
|
|
|
|
|
container,
|
|
|
|
|
app,
|
|
|
|
|
}: {
|
|
|
|
|
targetElements: ExcalidrawElement[];
|
|
|
|
|
appState: UIAppState;
|
|
|
|
|
renderAction: ActionManager["renderAction"];
|
|
|
|
|
setAppState: React.Component<any, AppState>["setState"];
|
|
|
|
|
container: HTMLDivElement | null;
|
|
|
|
|
app: AppClassProperties;
|
|
|
|
|
}) => {
|
|
|
|
|
const showShowArrowProperties =
|
|
|
|
|
toolIsArrow(appState.activeTool.type) ||
|
|
|
|
|
targetElements.some((element) => toolIsArrow(element.type));
|
|
|
|
|
|
|
|
|
|
if (!showShowArrowProperties) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="compact-action-item">
|
|
|
|
|
<Popover.Root
|
|
|
|
|
open={appState.openPopup === "compactArrowProperties"}
|
|
|
|
@@ -524,22 +503,27 @@ export const CompactShapeActions = ({
|
|
|
|
|
)}
|
|
|
|
|
</Popover.Root>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
{/* Linear Editor */}
|
|
|
|
|
{showLineEditorAction && (
|
|
|
|
|
<div className="compact-action-item">
|
|
|
|
|
{renderAction("toggleLinearEditor")}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
const CombinedTextProperties = ({
|
|
|
|
|
appState,
|
|
|
|
|
renderAction,
|
|
|
|
|
setAppState,
|
|
|
|
|
targetElements,
|
|
|
|
|
container,
|
|
|
|
|
elementsMap,
|
|
|
|
|
}: {
|
|
|
|
|
appState: UIAppState;
|
|
|
|
|
renderAction: ActionManager["renderAction"];
|
|
|
|
|
setAppState: React.Component<any, AppState>["setState"];
|
|
|
|
|
targetElements: ExcalidrawElement[];
|
|
|
|
|
container: HTMLDivElement | null;
|
|
|
|
|
elementsMap: NonDeletedElementsMap | NonDeletedSceneElementsMap;
|
|
|
|
|
}) => {
|
|
|
|
|
const { saveCaretPosition, restoreCaretPosition } = useTextEditorFocus();
|
|
|
|
|
|
|
|
|
|
{/* Text Properties */}
|
|
|
|
|
{(appState.activeTool.type === "text" ||
|
|
|
|
|
targetElements.some(isTextElement)) && (
|
|
|
|
|
<>
|
|
|
|
|
<div className="compact-action-item">
|
|
|
|
|
{renderAction("changeFontFamily")}
|
|
|
|
|
</div>
|
|
|
|
|
return (
|
|
|
|
|
<div className="compact-action-item">
|
|
|
|
|
<Popover.Root
|
|
|
|
|
open={appState.openPopup === "compactTextProperties"}
|
|
|
|
@@ -607,25 +591,49 @@ export const CompactShapeActions = ({
|
|
|
|
|
)}
|
|
|
|
|
</Popover.Root>
|
|
|
|
|
</div>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
{/* Dedicated Copy Button */}
|
|
|
|
|
{!isEditingTextOrNewElement && targetElements.length > 0 && (
|
|
|
|
|
<div className="compact-action-item">
|
|
|
|
|
{renderAction("duplicateSelection")}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
const CombinedExtraActions = ({
|
|
|
|
|
appState,
|
|
|
|
|
renderAction,
|
|
|
|
|
targetElements,
|
|
|
|
|
setAppState,
|
|
|
|
|
container,
|
|
|
|
|
app,
|
|
|
|
|
}: {
|
|
|
|
|
appState: UIAppState;
|
|
|
|
|
targetElements: ExcalidrawElement[];
|
|
|
|
|
renderAction: ActionManager["renderAction"];
|
|
|
|
|
setAppState: React.Component<any, AppState>["setState"];
|
|
|
|
|
container: HTMLDivElement | null;
|
|
|
|
|
app: AppClassProperties;
|
|
|
|
|
}) => {
|
|
|
|
|
const isEditingTextOrNewElement = Boolean(
|
|
|
|
|
appState.editingTextElement || appState.newElement,
|
|
|
|
|
);
|
|
|
|
|
const showCropEditorAction =
|
|
|
|
|
!appState.croppingElementId &&
|
|
|
|
|
targetElements.length === 1 &&
|
|
|
|
|
isImageElement(targetElements[0]);
|
|
|
|
|
const showLinkIcon = targetElements.length === 1;
|
|
|
|
|
const showAlignActions = alignActionsPredicate(appState, app);
|
|
|
|
|
let isSingleElementBoundContainer = false;
|
|
|
|
|
if (
|
|
|
|
|
targetElements.length === 2 &&
|
|
|
|
|
(hasBoundTextElement(targetElements[0]) ||
|
|
|
|
|
hasBoundTextElement(targetElements[1]))
|
|
|
|
|
) {
|
|
|
|
|
isSingleElementBoundContainer = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{/* Dedicated Delete Button */}
|
|
|
|
|
{!isEditingTextOrNewElement && targetElements.length > 0 && (
|
|
|
|
|
<div className="compact-action-item">
|
|
|
|
|
{renderAction("deleteSelectedElements")}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
const isRTL = document.documentElement.getAttribute("dir") === "rtl";
|
|
|
|
|
|
|
|
|
|
{/* Combined Other Actions */}
|
|
|
|
|
{!isEditingTextOrNewElement && targetElements.length > 0 && (
|
|
|
|
|
if (isEditingTextOrNewElement || targetElements.length === 0) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="compact-action-item">
|
|
|
|
|
<Popover.Root
|
|
|
|
|
open={appState.openPopup === "compactOtherProperties"}
|
|
|
|
@@ -662,7 +670,6 @@ export const CompactShapeActions = ({
|
|
|
|
|
container={container}
|
|
|
|
|
style={{
|
|
|
|
|
maxWidth: "12rem",
|
|
|
|
|
// center the popover content
|
|
|
|
|
justifyContent: "center",
|
|
|
|
|
alignItems: "center",
|
|
|
|
|
}}
|
|
|
|
@@ -731,11 +738,281 @@ export const CompactShapeActions = ({
|
|
|
|
|
)}
|
|
|
|
|
</Popover.Root>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const LinearEditorAction = ({
|
|
|
|
|
appState,
|
|
|
|
|
renderAction,
|
|
|
|
|
targetElements,
|
|
|
|
|
}: {
|
|
|
|
|
appState: UIAppState;
|
|
|
|
|
targetElements: ExcalidrawElement[];
|
|
|
|
|
renderAction: ActionManager["renderAction"];
|
|
|
|
|
}) => {
|
|
|
|
|
const showLineEditorAction =
|
|
|
|
|
!appState.selectedLinearElement?.isEditing &&
|
|
|
|
|
targetElements.length === 1 &&
|
|
|
|
|
isLinearElement(targetElements[0]) &&
|
|
|
|
|
!isElbowArrow(targetElements[0]);
|
|
|
|
|
|
|
|
|
|
if (!showLineEditorAction) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="compact-action-item">
|
|
|
|
|
{renderAction("toggleLinearEditor")}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const CompactShapeActions = ({
|
|
|
|
|
appState,
|
|
|
|
|
elementsMap,
|
|
|
|
|
renderAction,
|
|
|
|
|
app,
|
|
|
|
|
setAppState,
|
|
|
|
|
}: {
|
|
|
|
|
appState: UIAppState;
|
|
|
|
|
elementsMap: NonDeletedElementsMap | NonDeletedSceneElementsMap;
|
|
|
|
|
renderAction: ActionManager["renderAction"];
|
|
|
|
|
app: AppClassProperties;
|
|
|
|
|
setAppState: React.Component<any, AppState>["setState"];
|
|
|
|
|
}) => {
|
|
|
|
|
const targetElements = getTargetElements(elementsMap, appState);
|
|
|
|
|
const { container } = useExcalidrawContainer();
|
|
|
|
|
|
|
|
|
|
const isEditingTextOrNewElement = Boolean(
|
|
|
|
|
appState.editingTextElement || appState.newElement,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const showLineEditorAction =
|
|
|
|
|
!appState.selectedLinearElement?.isEditing &&
|
|
|
|
|
targetElements.length === 1 &&
|
|
|
|
|
isLinearElement(targetElements[0]) &&
|
|
|
|
|
!isElbowArrow(targetElements[0]);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="compact-shape-actions">
|
|
|
|
|
{/* Stroke Color */}
|
|
|
|
|
{canChangeStrokeColor(appState, targetElements) && (
|
|
|
|
|
<div
|
|
|
|
|
className={clsx("compact-action-item")}
|
|
|
|
|
style={{
|
|
|
|
|
marginRight: 4,
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{renderAction("changeStrokeColor")}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* Background Color */}
|
|
|
|
|
{canChangeBackgroundColor(appState, targetElements) && (
|
|
|
|
|
<div className="compact-action-item">
|
|
|
|
|
{renderAction("changeBackgroundColor")}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<CombinedShapeProperties
|
|
|
|
|
appState={appState}
|
|
|
|
|
renderAction={renderAction}
|
|
|
|
|
setAppState={setAppState}
|
|
|
|
|
targetElements={targetElements}
|
|
|
|
|
container={container}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<CombinedArrowProperties
|
|
|
|
|
appState={appState}
|
|
|
|
|
renderAction={renderAction}
|
|
|
|
|
setAppState={setAppState}
|
|
|
|
|
targetElements={targetElements}
|
|
|
|
|
container={container}
|
|
|
|
|
app={app}
|
|
|
|
|
/>
|
|
|
|
|
{/* Linear Editor */}
|
|
|
|
|
{showLineEditorAction && (
|
|
|
|
|
<div className="compact-action-item">
|
|
|
|
|
{renderAction("toggleLinearEditor")}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* Text Properties */}
|
|
|
|
|
{(appState.activeTool.type === "text" ||
|
|
|
|
|
targetElements.some(isTextElement)) && (
|
|
|
|
|
<>
|
|
|
|
|
<div className="compact-action-item">
|
|
|
|
|
{renderAction("changeFontFamily")}
|
|
|
|
|
</div>
|
|
|
|
|
<CombinedTextProperties
|
|
|
|
|
appState={appState}
|
|
|
|
|
renderAction={renderAction}
|
|
|
|
|
setAppState={setAppState}
|
|
|
|
|
targetElements={targetElements}
|
|
|
|
|
container={container}
|
|
|
|
|
elementsMap={elementsMap}
|
|
|
|
|
/>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* Dedicated Copy Button */}
|
|
|
|
|
{!isEditingTextOrNewElement && targetElements.length > 0 && (
|
|
|
|
|
<div className="compact-action-item">
|
|
|
|
|
{renderAction("duplicateSelection")}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* Dedicated Delete Button */}
|
|
|
|
|
{!isEditingTextOrNewElement && targetElements.length > 0 && (
|
|
|
|
|
<div className="compact-action-item">
|
|
|
|
|
{renderAction("deleteSelectedElements")}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<CombinedExtraActions
|
|
|
|
|
appState={appState}
|
|
|
|
|
renderAction={renderAction}
|
|
|
|
|
targetElements={targetElements}
|
|
|
|
|
setAppState={setAppState}
|
|
|
|
|
container={container}
|
|
|
|
|
app={app}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const MobileShapeActions = ({
|
|
|
|
|
appState,
|
|
|
|
|
elementsMap,
|
|
|
|
|
renderAction,
|
|
|
|
|
app,
|
|
|
|
|
setAppState,
|
|
|
|
|
}: {
|
|
|
|
|
appState: UIAppState;
|
|
|
|
|
elementsMap: NonDeletedElementsMap | NonDeletedSceneElementsMap;
|
|
|
|
|
renderAction: ActionManager["renderAction"];
|
|
|
|
|
app: AppClassProperties;
|
|
|
|
|
setAppState: React.Component<any, AppState>["setState"];
|
|
|
|
|
}) => {
|
|
|
|
|
const targetElements = getTargetElements(elementsMap, appState);
|
|
|
|
|
const { container } = useExcalidrawContainer();
|
|
|
|
|
const mobileActionsRef = useRef<HTMLDivElement>(null);
|
|
|
|
|
|
|
|
|
|
const width = mobileActionsRef.current?.getBoundingClientRect()?.width ?? 0;
|
|
|
|
|
|
|
|
|
|
const WIDTH = 26;
|
|
|
|
|
const GAP = 8;
|
|
|
|
|
|
|
|
|
|
// max 6 actions + undo
|
|
|
|
|
const MIN_WIDTH = 7 * WIDTH + 6 * GAP;
|
|
|
|
|
|
|
|
|
|
const ADDITIONAL_WIDTH = WIDTH + GAP;
|
|
|
|
|
|
|
|
|
|
const showDelete = width >= MIN_WIDTH;
|
|
|
|
|
const showDuplicate = width >= MIN_WIDTH + 2 * ADDITIONAL_WIDTH;
|
|
|
|
|
const showRedo = width >= MIN_WIDTH + 3 * ADDITIONAL_WIDTH;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Island
|
|
|
|
|
className="compact-shape-actions mobile-shape-actions"
|
|
|
|
|
style={{
|
|
|
|
|
flexDirection: "row",
|
|
|
|
|
boxShadow: "none",
|
|
|
|
|
backgroundColor: "transparent",
|
|
|
|
|
padding: 0,
|
|
|
|
|
margin: "0 0.25rem",
|
|
|
|
|
zIndex: 2,
|
|
|
|
|
height: WIDTH * 1.75,
|
|
|
|
|
alignItems: "center",
|
|
|
|
|
gap: GAP,
|
|
|
|
|
}}
|
|
|
|
|
ref={mobileActionsRef}
|
|
|
|
|
>
|
|
|
|
|
<div
|
|
|
|
|
style={{
|
|
|
|
|
display: "flex",
|
|
|
|
|
flexDirection: "row",
|
|
|
|
|
gap: GAP,
|
|
|
|
|
flex: 1,
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{canChangeStrokeColor(appState, targetElements) && (
|
|
|
|
|
<div className={clsx("compact-action-item")}>
|
|
|
|
|
{renderAction("changeStrokeColor")}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
{canChangeBackgroundColor(appState, targetElements) && (
|
|
|
|
|
<div className="compact-action-item">
|
|
|
|
|
{renderAction("changeBackgroundColor")}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
<CombinedShapeProperties
|
|
|
|
|
appState={appState}
|
|
|
|
|
renderAction={renderAction}
|
|
|
|
|
setAppState={setAppState}
|
|
|
|
|
targetElements={targetElements}
|
|
|
|
|
container={container}
|
|
|
|
|
/>
|
|
|
|
|
{/* Combined Arrow Properties */}
|
|
|
|
|
<CombinedArrowProperties
|
|
|
|
|
appState={appState}
|
|
|
|
|
renderAction={renderAction}
|
|
|
|
|
setAppState={setAppState}
|
|
|
|
|
targetElements={targetElements}
|
|
|
|
|
container={container}
|
|
|
|
|
app={app}
|
|
|
|
|
/>
|
|
|
|
|
{/* Linear Editor */}
|
|
|
|
|
<LinearEditorAction
|
|
|
|
|
appState={appState}
|
|
|
|
|
renderAction={renderAction}
|
|
|
|
|
targetElements={targetElements}
|
|
|
|
|
/>
|
|
|
|
|
{/* Text Properties */}
|
|
|
|
|
{(appState.activeTool.type === "text" ||
|
|
|
|
|
targetElements.some(isTextElement)) && (
|
|
|
|
|
<>
|
|
|
|
|
<div className="compact-action-item">
|
|
|
|
|
{renderAction("changeFontFamily")}
|
|
|
|
|
</div>
|
|
|
|
|
<CombinedTextProperties
|
|
|
|
|
appState={appState}
|
|
|
|
|
renderAction={renderAction}
|
|
|
|
|
setAppState={setAppState}
|
|
|
|
|
targetElements={targetElements}
|
|
|
|
|
container={container}
|
|
|
|
|
elementsMap={elementsMap}
|
|
|
|
|
/>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{showDuplicate && renderAction("duplicateSelection")}
|
|
|
|
|
{showDelete && renderAction("deleteSelectedElements")}
|
|
|
|
|
|
|
|
|
|
{/* Combined Other Actions */}
|
|
|
|
|
<CombinedExtraActions
|
|
|
|
|
appState={appState}
|
|
|
|
|
renderAction={renderAction}
|
|
|
|
|
targetElements={targetElements}
|
|
|
|
|
setAppState={setAppState}
|
|
|
|
|
container={container}
|
|
|
|
|
app={app}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div
|
|
|
|
|
style={{
|
|
|
|
|
display: "flex",
|
|
|
|
|
flexDirection: "row",
|
|
|
|
|
gap: GAP,
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{renderAction("undo")}
|
|
|
|
|
{showRedo && renderAction("redo")}
|
|
|
|
|
</div>
|
|
|
|
|
</Island>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const ShapesSwitcher = ({
|
|
|
|
|
activeTool,
|
|
|
|
|
appState,
|
|
|
|
|