Kaynağa Gözat

support making transform handles optional

ad1992 3 yıl önce
ebeveyn
işleme
3d0a1106ff

+ 18 - 4
src/components/App.tsx

@@ -223,6 +223,7 @@ import {
   withBatchedUpdatesThrottled,
   updateObject,
   setEraserCursor,
+  getCustomElementConfig,
 } from "../utils";
 import ContextMenu, { ContextMenuOption } from "./ContextMenu";
 import LayerUI from "./LayerUI";
@@ -422,10 +423,13 @@ class App extends React.Component<AppProps, AppState> {
     coords: { x: number; y: number },
     name: string = "",
   ) => {
-    const config = this.props.customElementsConfig!.find(
-      (config) => config.name === name,
-    )!;
-
+    const config = getCustomElementConfig(
+      this.props.customElementsConfig,
+      name,
+    );
+    if (!config) {
+      return;
+    }
     const [gridX, gridY] = getGridPoint(
       coords.x,
       coords.y,
@@ -3393,6 +3397,15 @@ class App extends React.Component<AppProps, AppState> {
       const elements = this.scene.getElements();
       const selectedElements = getSelectedElements(elements, this.state);
       if (selectedElements.length === 1 && !this.state.editingLinearElement) {
+        if (selectedElements[0].type === "custom") {
+          const config = getCustomElementConfig(
+            this.props.customElementsConfig,
+            selectedElements[0].name,
+          );
+          if (!config?.transformHandles) {
+            return false;
+          }
+        }
         const elementWithTransformHandleType =
           getElementWithTransformHandleType(
             elements,
@@ -4209,6 +4222,7 @@ class App extends React.Component<AppProps, AppState> {
           const elementsWithinSelection = getElementsWithinSelection(
             elements,
             draggingElement,
+            this.props.customElementsConfig,
           );
           this.setState((prevState) =>
             selectGroupsForSelectedElements(

+ 9 - 1
src/constants.ts

@@ -1,5 +1,5 @@
 import cssVariables from "./css/variables.module.scss";
-import { AppProps } from "./types";
+import { AppProps, CustomElementConfig } from "./types";
 import { FontFamilyValues } from "./element/types";
 
 export const APP_NAME = "Excalidraw";
@@ -152,6 +152,14 @@ export const DEFAULT_UI_OPTIONS: AppProps["UIOptions"] = {
   },
 };
 
+export const DEFAULT_CUSTOM_ELEMENT_CONFIG: Required<CustomElementConfig> = {
+  type: "custom",
+  name: "custom",
+  transformHandles: true,
+  svg: "",
+  width: 40,
+  height: 40,
+};
 export const MQ_MAX_WIDTH_PORTRAIT = 730;
 export const MQ_MAX_WIDTH_LANDSCAPE = 1000;
 export const MQ_MAX_HEIGHT_LANDSCAPE = 500;

+ 1 - 0
src/packages/excalidraw/example/App.js

@@ -196,6 +196,7 @@ export default function App() {
         }, ${encodeURIComponent(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
         <path d="M256 32C114.6 32 .0272 125.1 .0272 240c0 47.63 19.91 91.25 52.91 126.2c-14.88 39.5-45.87 72.88-46.37 73.25c-6.625 7-8.375 17.25-4.625 26C5.818 474.2 14.38 480 24 480c61.5 0 109.1-25.75 139.1-46.25C191.1 442.8 223.3 448 256 448c141.4 0 255.1-93.13 255.1-208S397.4 32 256 32zM256.1 400c-26.75 0-53.12-4.125-78.38-12.12l-22.75-7.125l-19.5 13.75c-14.25 10.12-33.88 21.38-57.5 29c7.375-12.12 14.37-25.75 19.88-40.25l10.62-28l-20.62-21.87C69.82 314.1 48.07 282.2 48.07 240c0-88.25 93.25-160 208-160s208 71.75 208 160S370.8 400 256.1 400z" />
       </svg>`)}`,
+        transformHandles: false,
       },
     ];
   };

+ 9 - 2
src/packages/excalidraw/index.tsx

@@ -9,7 +9,10 @@ import "../../css/styles.scss";
 
 import { AppProps, ExcalidrawAPIRefValue, ExcalidrawProps } from "../../types";
 import { defaultLang } from "../../i18n";
-import { DEFAULT_UI_OPTIONS } from "../../constants";
+import {
+  DEFAULT_UI_OPTIONS,
+  DEFAULT_CUSTOM_ELEMENT_CONFIG,
+} from "../../constants";
 
 const Excalidraw = (props: ExcalidrawProps) => {
   const {
@@ -37,7 +40,6 @@ const Excalidraw = (props: ExcalidrawProps) => {
     generateIdForFile,
     onLinkOpen,
     renderCustomElementWidget,
-    customElementsConfig,
   } = props;
 
   const canvasActions = props.UIOptions?.canvasActions;
@@ -48,6 +50,11 @@ const Excalidraw = (props: ExcalidrawProps) => {
       ...canvasActions,
     },
   };
+  const customElementsConfig: AppProps["customElementsConfig"] =
+    props.customElementsConfig?.map((customElementConfig) => ({
+      ...DEFAULT_CUSTOM_ELEMENT_CONFIG,
+      ...customElementConfig,
+    }));
 
   if (canvasActions?.export) {
     UIOptions.canvasActions.export.saveFileToDisk =

+ 14 - 4
src/renderer/renderElement.ts

@@ -25,7 +25,13 @@ import { RoughSVG } from "roughjs/bin/svg";
 import { RoughGenerator } from "roughjs/bin/generator";
 
 import { RenderConfig } from "../scene/types";
-import { distance, getFontString, getFontFamilyString, isRTL } from "../utils";
+import {
+  distance,
+  getFontString,
+  getFontFamilyString,
+  isRTL,
+  getCustomElementConfig,
+} from "../utils";
 import { isPathALoop } from "../math";
 import rough from "roughjs/bin/rough";
 import { AppState, BinaryFiles, Zoom } from "../types";
@@ -256,9 +262,13 @@ const drawElementOnCanvas = (
     }
 
     case "custom": {
-      const config = renderConfig.customElementsConfig?.find(
-        (config) => config.name === element.name,
-      )!;
+      const config = getCustomElementConfig(
+        renderConfig.customElementsConfig,
+        element.name,
+      );
+      if (!config) {
+        break;
+      }
       if (!customElementImgCache[config.name]) {
         const img = document.createElement("img");
         img.id = config.name;

+ 59 - 31
src/renderer/renderScene.ts

@@ -2,7 +2,7 @@ import { RoughCanvas } from "roughjs/bin/canvas";
 import { RoughSVG } from "roughjs/bin/svg";
 import oc from "open-color";
 
-import { AppState, BinaryFiles, Zoom } from "../types";
+import { AppState, BinaryFiles, CustomElementConfig, Zoom } from "../types";
 import {
   ExcalidrawElement,
   NonDeletedExcalidrawElement,
@@ -47,7 +47,11 @@ import {
   TransformHandles,
   TransformHandleType,
 } from "../element/transformHandles";
-import { viewportCoordsToSceneCoords, supportsEmoji } from "../utils";
+import {
+  viewportCoordsToSceneCoords,
+  supportsEmoji,
+  getCustomElementConfig,
+} from "../utils";
 import { UserIdleState } from "../types";
 import { THEME_FILTER } from "../constants";
 import {
@@ -304,24 +308,35 @@ export const renderScene = (
     !appState.editingLinearElement
   ) {
     const selections = elements.reduce((acc, element) => {
+      const isCustom = element.type === "custom";
+      let config: CustomElementConfig;
       const selectionColors = [];
-      // local user
-      if (
-        appState.selectedElementIds[element.id] &&
-        !isSelectedViaGroup(appState, element)
-      ) {
-        selectionColors.push(oc.black);
+
+      if (element.type === "custom") {
+        config = getCustomElementConfig(
+          renderConfig.customElementsConfig,
+          element.name,
+        )!;
       }
-      // remote users
-      if (renderConfig.remoteSelectedElementIds[element.id]) {
-        selectionColors.push(
-          ...renderConfig.remoteSelectedElementIds[element.id].map(
-            (socketId) => {
-              const { background } = getClientColors(socketId, appState);
-              return background;
-            },
-          ),
-        );
+      if (!isCustom || (isCustom && config!.transformHandles)) {
+        // local user
+        if (
+          appState.selectedElementIds[element.id] &&
+          !isSelectedViaGroup(appState, element)
+        ) {
+          selectionColors.push(oc.black);
+        }
+        // remote users
+        if (renderConfig.remoteSelectedElementIds[element.id]) {
+          selectionColors.push(
+            ...renderConfig.remoteSelectedElementIds[element.id].map(
+              (socketId) => {
+                const { background } = getClientColors(socketId, appState);
+                return background;
+              },
+            ),
+          );
+        }
       }
       if (selectionColors.length) {
         const [elementX1, elementY1, elementX2, elementY2] =
@@ -351,7 +366,6 @@ export const renderScene = (
         selectionColors: [oc.black],
       });
     };
-
     for (const groupId of getSelectedGroupIds(appState)) {
       // TODO: support multiplayer selected group IDs
       addSelectionForGroupId(groupId);
@@ -371,19 +385,32 @@ export const renderScene = (
     context.save();
     context.translate(renderConfig.scrollX, renderConfig.scrollY);
     if (locallySelectedElements.length === 1) {
-      context.fillStyle = oc.white;
-      const transformHandles = getTransformHandles(
-        locallySelectedElements[0],
-        renderConfig.zoom,
-        "mouse", // when we render we don't know which pointer type so use mouse
-      );
-      if (!appState.viewModeEnabled) {
-        renderTransformHandles(
-          context,
-          renderConfig,
-          transformHandles,
-          locallySelectedElements[0].angle,
+      let showTransformHandles = true;
+      if (locallySelectedElements[0].type === "custom") {
+        const config = getCustomElementConfig(
+          renderConfig.customElementsConfig,
+          locallySelectedElements[0].name,
         );
+        if (!config || !config.transformHandles) {
+          showTransformHandles = false;
+        }
+      }
+      if (showTransformHandles) {
+        context.fillStyle = oc.white;
+        const transformHandles = getTransformHandles(
+          locallySelectedElements[0],
+          renderConfig.zoom,
+          "mouse", // when we render we don't know which pointer type so use mouse
+        );
+
+        if (!appState.viewModeEnabled) {
+          renderTransformHandles(
+            context,
+            renderConfig,
+            transformHandles,
+            locallySelectedElements[0].angle,
+          );
+        }
       }
     } else if (locallySelectedElements.length > 1 && !appState.isRotating) {
       const dashedLinePadding = 4 / renderConfig.zoom.value;
@@ -570,6 +597,7 @@ const renderTransformHandles = (
   renderConfig: RenderConfig,
   transformHandles: TransformHandles,
   angle: number,
+  name?: string,
 ): void => {
   Object.keys(transformHandles).forEach((key) => {
     const transformHandle = transformHandles[key as TransformHandleType];

+ 9 - 2
src/scene/selection.ts

@@ -3,20 +3,27 @@ import {
   NonDeletedExcalidrawElement,
 } from "../element/types";
 import { getElementAbsoluteCoords, getElementBounds } from "../element";
-import { AppState } from "../types";
+import { AppState, ExcalidrawProps } from "../types";
 import { isBoundToContainer } from "../element/typeChecks";
+import { getCustomElementConfig } from "../utils";
 
 export const getElementsWithinSelection = (
   elements: readonly NonDeletedExcalidrawElement[],
   selection: NonDeletedExcalidrawElement,
+  customElementConfig: ExcalidrawProps["customElementsConfig"],
 ) => {
   const [selectionX1, selectionY1, selectionX2, selectionY2] =
     getElementAbsoluteCoords(selection);
   return elements.filter((element) => {
     const [elementX1, elementY1, elementX2, elementY2] =
       getElementBounds(element);
-
+    const isCustom = element.type === "custom";
+    const allowSelection = isCustom
+      ? getCustomElementConfig(customElementConfig, element.name)
+          ?.transformHandles
+      : true;
     return (
+      allowSelection &&
       element.type !== "selection" &&
       !isBoundToContainer(element) &&
       selectionX1 <= elementX1 &&

+ 3 - 3
src/types.ts

@@ -206,11 +206,10 @@ export type ExcalidrawAPIRefValue =
       ready?: false;
     };
 
-type CustomElementConfig = {
+export type CustomElementConfig = {
   type: "custom";
   name: string;
-  resize?: boolean;
-  rotate?: boolean;
+  transformHandles?: boolean;
   svg: string;
   width?: number;
   height?: number;
@@ -317,6 +316,7 @@ export type AppProps = ExcalidrawProps & {
   detectScroll: boolean;
   handleKeyboardGlobally: boolean;
   isCollaborating: boolean;
+  customElementsConfig: Required<CustomElementConfig>[] | undefined;
 };
 
 /** A subset of App class properties that we need to use elsewhere

+ 11 - 1
src/utils.ts

@@ -11,7 +11,7 @@ import {
   WINDOWS_EMOJI_FALLBACK_FONT,
 } from "./constants";
 import { FontFamilyValues, FontString } from "./element/types";
-import { AppState, DataURL, Zoom } from "./types";
+import { AppState, DataURL, ExcalidrawProps, Zoom } from "./types";
 import { unstable_batchedUpdates } from "react-dom";
 import { isDarwin } from "./keys";
 
@@ -612,3 +612,13 @@ export const updateObject = <T extends Record<string, any>>(
     ...updates,
   };
 };
+
+export const getCustomElementConfig = (
+  customElementConfig: ExcalidrawProps["customElementsConfig"],
+  name: string,
+) => {
+  if (!customElementConfig) {
+    return null;
+  }
+  return customElementConfig.find((config) => config.name === name);
+};