Przeglądaj źródła

(wip): introduce clone handles

Konstantinos Kaloutas 2 lat temu
rodzic
commit
98b615b22d

+ 5 - 1
tldraw/packages/core/src/lib/TLApi/TLApi.ts

@@ -1,5 +1,5 @@
 import Vec from '@tldraw/vec'
-import type { TLAsset, TLBinding, TLEventMap } from '../../types'
+import type { TLAsset, TLBinding, TLEventMap, TLCloneDirection } from '../../types'
 import { BoundsUtils, isNonNullable, uniqueId } from '../../utils'
 import type { TLShape, TLShapeModel } from '../shapes'
 import type { TLApp } from '../TLApp'
@@ -230,6 +230,10 @@ export class TLApi<S extends TLShape = TLShape, K extends TLEventMap = TLEventMa
     return this.app.createNewLineBinding(source, target)
   }
 
+  clone = (direction: TLCloneDirection) => {
+    // TODO: Clone to direction, clear label, create binding, select shape
+  }
+  
   /** Clone shapes with given context */
   cloneShapes = ({
     shapes,

+ 14 - 0
tldraw/packages/core/src/lib/TLApp/TLApp.ts

@@ -837,6 +837,20 @@ export class TLApp<
     )
   }
 
+  @computed get showCloneHandles() {
+    const { selectedShapesArray } = this
+    return (
+      this.isInAny(
+        'select.idle',
+        'select.hoveringSelectionHandle',
+        'select.pointingShape',
+        'select.pointingSelectedShape',
+      ) &&
+      selectedShapesArray.length === 1 &&
+      !this.readOnly
+    )
+  }
+
   /* ------------------ Shape Classes ----------------- */
 
   Shapes = new Map<string, TLShapeConstructor<S>>()

+ 7 - 0
tldraw/packages/core/src/types/types.ts

@@ -41,6 +41,13 @@ export enum TLResizeEdge {
   Left = 'left_edge',
 }
 
+export enum TLCloneDirection {
+  Top = 'top',
+  Right = 'right',
+  Bottom = 'bottom',
+  Left = 'left',
+}
+
 export enum TLResizeCorner {
   TopLeft = 'top_left_corner',
   TopRight = 'top_right_corner',

+ 1 - 0
tldraw/packages/react/src/components/AppCanvas.tsx

@@ -32,6 +32,7 @@ export const AppCanvas = observer(function InnerApp<S extends TLReactShape>(
       showSelectionRotation={app.showSelectionRotation}
       showResizeHandles={app.showResizeHandles}
       showRotateHandles={app.showRotateHandles}
+      showCloneHandles={app.showCloneHandles}
       showSelectionDetail={app.showSelectionDetail}
       showContextBar={app.showContextBar}
       cursor={app.cursors.cursor}

+ 3 - 0
tldraw/packages/react/src/components/Canvas/Canvas.tsx

@@ -64,6 +64,7 @@ export interface TLCanvasProps<S extends TLReactShape> {
   showHandles: boolean
   showResizeHandles: boolean
   showRotateHandles: boolean
+  showCloneHandles: boolean
   showContextBar: boolean
   showSelectionDetail: boolean
   showSelectionRotation: boolean
@@ -92,6 +93,7 @@ export const Canvas = observer(function Renderer<S extends TLReactShape>({
   showSelectionRotation = false,
   showResizeHandles = true,
   showRotateHandles = true,
+  showCloneHandles = true,
   showSelectionDetail = true,
   showContextBar = true,
   showGrid = true,
@@ -200,6 +202,7 @@ export const Canvas = observer(function Renderer<S extends TLReactShape>({
                     bounds={selectionBounds}
                     showResizeHandles={showResizeHandles}
                     showRotateHandles={showRotateHandles}
+                    showCloneHandles={showCloneHandles}
                   />
                 </Container>
               )}

+ 32 - 2
tldraw/packages/react/src/components/ui/SelectionForeground/SelectionForeground.tsx

@@ -1,16 +1,17 @@
-import { TLResizeCorner, TLResizeEdge, TLRotateCorner } from '@tldraw/core'
+import { TLCloneDirection, TLResizeCorner, TLResizeEdge, TLRotateCorner } from '@tldraw/core'
 import { observer } from 'mobx-react-lite'
 import { useApp } from '../../../hooks'
 import type { TLReactShape } from '../../../lib'
 import type { TLSelectionComponentProps } from '../../../types'
 import { SVGContainer } from '../../SVGContainer'
-import { CornerHandle, EdgeHandle } from './handles'
+import { CornerHandle, EdgeHandle, CloneHandle } from './handles'
 import { RotateCornerHandle } from './handles/RotateCornerHandle'
 
 export const SelectionForeground = observer(function SelectionForeground<S extends TLReactShape>({
   bounds,
   showResizeHandles,
   showRotateHandles,
+  showCloneHandles,
   shapes,
 }: TLSelectionComponentProps<S>) {
   const app = useApp()
@@ -19,6 +20,7 @@ export const SelectionForeground = observer(function SelectionForeground<S exten
 
   const size = 8 / zoom
   const targetSize = 6 / zoom
+  const clonePadding = 30 / zoom
 
   const canResize = shapes.length === 1 ? shapes[0].canResize : [true, true]
 
@@ -107,6 +109,34 @@ export const SelectionForeground = observer(function SelectionForeground<S exten
             corner={TLRotateCorner.BottomLeft}
             isHidden={!showRotateHandles}
           />
+          <CloneHandle
+            cx={- clonePadding}
+            cy={height / 2}
+            size={size}
+            direction={TLCloneDirection.Left}
+            isHidden={!showCloneHandles}
+          />
+          <CloneHandle
+            cx={width + clonePadding}
+            cy={height / 2}
+            size={size}
+            direction={TLCloneDirection.Right}
+            isHidden={!showCloneHandles}
+          />
+          <CloneHandle
+            cx={width / 2}
+            cy={height + clonePadding}
+            size={size}
+            direction={TLCloneDirection.Bottom}
+            isHidden={!showCloneHandles}
+          />
+          <CloneHandle
+            cx={width / 2}
+            cy={- clonePadding}
+            size={size}
+            direction={TLCloneDirection.Top}
+            isHidden={!showCloneHandles}
+          />
           {canResize?.every(r => r) && (
             <>
               <CornerHandle

+ 34 - 0
tldraw/packages/react/src/components/ui/SelectionForeground/handles/CloneHandle.tsx

@@ -0,0 +1,34 @@
+import { observer } from 'mobx-react-lite'
+import type { TLCloneDirection } from '@tldraw/core'
+import { useApp } from '../../../../hooks'
+
+interface CloneHandleProps {
+    cx: number
+    cy: number
+    size: number
+    direction: TLCloneDirection
+    isHidden?: boolean
+}
+
+export const CloneHandle = observer(function CloneHandle({
+    cx,
+    cy,
+    size,
+    direction,
+    isHidden,
+}: CloneHandleProps): JSX.Element {
+    const app = useApp()
+
+    return (
+        <circle
+            pointerEvents="all"
+            onPointerDown={() => app.api.clone(direction)}
+            opacity={isHidden ? 0 : 1}
+            className="tl-clone-handle"
+            aria-label={`${direction} handle`}
+            cx={cx}
+            cy={cy}
+            r={size}
+        />
+    )
+})

+ 1 - 0
tldraw/packages/react/src/components/ui/SelectionForeground/handles/index.ts

@@ -1,3 +1,4 @@
 export * from './CornerHandle'
+export * from './CloneHandle'
 export * from './EdgeHandle'
 export * from './RotateHandle'

+ 7 - 1
tldraw/packages/react/src/hooks/useStylesheet.ts

@@ -81,7 +81,7 @@ const tlcss = css`
   .tl-container {
     --tl-zoom: 1;
     --tl-scale: calc(1 / var(--tl-zoom));
-    --tl-padding: 64px;
+    --tl-padding: calc(64px / var(--tl-zoom));;
     --tl-shadow-color: 0deg 0% 0%;
     --tl-binding-distance: ${BINDING_DISTANCE}px;
     --tl-shadow-elevation-low: 0px 0.4px 0.5px hsl(var(--tl-shadow-color) / 0.04),
@@ -226,6 +226,12 @@ const tlcss = css`
     stroke-width: calc(1.5px * var(--tl-scale));
   }
 
+  .tl-clone-handle {
+    stroke: var(--tl-selectStroke);
+    fill: var(--tl-background);
+    stroke-width: calc(1.5px * var(--tl-scale));
+  }
+
   .tl-user {
     left: -4px;
     top: -4px;

+ 1 - 0
tldraw/packages/react/src/types/component-props.ts

@@ -10,6 +10,7 @@ export type TLSelectionComponentProps<S extends TLReactShape = TLReactShape> = {
   bounds: TLBounds
   showResizeHandles?: boolean
   showRotateHandles?: boolean
+  showCloneHandles?: boolean
 }
 
 export type TLBoundsComponent<S extends TLReactShape = TLReactShape> = (