Browse Source

feat: create binding api

Peng Xiao 3 years ago
parent
commit
bb5b8b98b3

+ 0 - 9
src/main/frontend/extensions/tldraw.cljs

@@ -34,14 +34,6 @@
   [props]
   (block/page-cp {:preview? true} {:block/name (gobj/get props "pageName")}))
 
-(defn create-block-shape-by-id
-  [e]
-  (when-let [block (block/get-dragging-block)]
-    (let [uuid (:block/uuid block)
-          client-x (gobj/get e "clientX")
-          client-y (gobj/get e "clientY")]
-      (whiteboard-handler/add-new-block-portal-shape! uuid client-x client-y))))
-
 (defn search-handler
   [q filters]
   (let [{:keys [pages? blocks? files?]} (js->clj filters {:keywordize-keys true})
@@ -101,7 +93,6 @@
         :on-blur (fn [e] 
                    (when (#{"INPUT" "TEXTAREA"} (.-tagName (gobj/get e "target")))
                      (state/clear-edit!)))
-        :on-drop create-block-shape-by-id
         ;; wheel -> overscroll may cause browser navigation
         :on-wheel util/stop-propagation}
 

+ 20 - 51
tldraw/apps/tldraw-logseq/src/hooks/usePaste.ts

@@ -8,6 +8,7 @@ import {
   TLShapeModel,
   uniqueId,
   validUUID,
+  createNewLineBinding,
 } from '@tldraw/core'
 import type { TLReactCallbacks } from '@tldraw/react'
 import Vec from '@tldraw/vec'
@@ -146,61 +147,12 @@ export function usePaste() {
         }
       }
 
-      // async function handleItems(items: any) {
-      //   for (const item of items) {
-      //     if (await handleDroppedItem(item)) {
-      //       const lineId = uniqueId()
-
-      //       const startBinding: TLBinding = {
-      //         id: uniqueId(),
-      //         distance: 200,
-      //         handleId: 'start',
-      //         fromId: lineId,
-      //         toId: app.selectedShapesArray[app.selectedShapesArray.length - 1].id,
-      //         point: [point[0], point[1]],
-      //       }
-      //       bindingsToCreate.push(startBinding)
-
-      //       const endBinding: TLBinding = {
-      //         id: uniqueId(),
-      //         distance: 200,
-      //         handleId: 'end',
-      //         fromId: lineId,
-      //         toId: shapesToCreate[shapesToCreate.length - 1].id,
-      //         point: [point[0], point[1]],
-      //       }
-      //       bindingsToCreate.push(endBinding)
-
-      //       shapesToCreate.push({
-      //         ...LineShape.defaultProps,
-      //         id: lineId,
-      //         handles: {
-      //           start: {
-      //             id: 'start',
-      //             canBind: true,
-      //             point: app.selectedShapesArray[0].getCenter(),
-      //             bindingId: startBinding.id,
-      //           },
-      //           end: {
-      //             id: 'end',
-      //             canBind: true,
-      //             point: [point[0], point[1]],
-      //             bindingId: endBinding.id,
-      //           },
-      //         },
-      //       })
-
-      //       return true
-      //     }
-      //   }
-      //   return false
-      // }
-
       async function tryCreateShapesFromDataTransfer(dataTransfer: DataTransfer) {
         return tryCreateShapeHelper(
           tryCreateShapeFromFiles,
           tryCreateShapeFromTextHTML,
-          tryCreateShapeFromTextPlain
+          tryCreateShapeFromTextPlain,
+          tryCreateShapeFromBlockUUID
         )(dataTransfer)
       }
 
@@ -261,6 +213,16 @@ export function usePaste() {
         return null
       }
 
+      async function tryCreateShapeFromBlockUUID(dataTransfer: DataTransfer) {
+        // This is a Logseq custom data type defined in frontend.components.block
+        const rawText = dataTransfer.getData('block-uuid')
+        if (rawText) {
+          const text = rawText.trim()
+          return tryCreateShapeHelper(tryCreateLogseqPortalShapesFromString)(`((${text}))`)
+        }
+        return null
+      }
+
       async function tryCreateShapeFromTextPlain(item: DataTransfer | ClipboardItem) {
         const rawText = await getDataFromType(item, 'text/plain')
         if (rawText) {
@@ -464,6 +426,13 @@ export function usePaste() {
         if (allShapesToAdd.length > 0) {
           app.createShapes(allShapesToAdd)
         }
+
+        if (app.selectedShapesArray.length === 1 && allShapesToAdd.length === 1) {
+          const source = app.selectedShapesArray[0]
+          const target = app.getShapeById(allShapesToAdd[0].id!)!
+          app.createNewLineBinding(source, target)
+        }
+
         app.currentPage.updateBindings(Object.fromEntries(bindingsToCreate.map(b => [b.id, b])))
         app.setSelectedShapes(allShapesToAdd.map(s => s.id))
       })

+ 4 - 0
tldraw/packages/core/src/lib/TLApi/TLApi.ts

@@ -191,4 +191,8 @@ export class TLApi<S extends TLShape = TLShape, K extends TLEventMap = TLEventMa
     this.app.redo()
     return this
   }
+
+  createNewLineBinding = (source: TLShape, target: TLShape) => {
+    return this.app.createNewLineBinding(source, target)
+  }
 }

+ 15 - 1
tldraw/packages/core/src/lib/TLApp/TLApp.ts

@@ -17,7 +17,7 @@ import type {
   TLEvents,
   TLHandle,
 } from '../../types'
-import { KeyUtils, BoundsUtils, isNonNullable } from '../../utils'
+import { KeyUtils, BoundsUtils, isNonNullable, createNewLineBinding } from '../../utils'
 import type { TLShape, TLShapeConstructor, TLShapeModel } from '../shapes'
 import { TLApi } from '../TLApi'
 import { TLCursors } from '../TLCursors'
@@ -604,6 +604,20 @@ export class TLApp<
     return this.setBindingShapes()
   }
 
+  @action createNewLineBinding = (source: TLShape, target: TLShape) => {
+    if (source.canBind && target.canBind) {
+      const result = createNewLineBinding(source, target)
+      if (result) {
+        const [newLine, newBindings] = result
+        this.createShapes([newLine])
+        this.currentPage.updateBindings(Object.fromEntries(newBindings.map(b => [b.id, b])))
+        this.persist()
+        return true
+      }
+    }
+    return false
+  }
+
   /* ---------------------- Brush --------------------- */
 
   @observable brush?: TLBounds

+ 3 - 33
tldraw/packages/core/src/lib/TLBaseLineBindingState.ts

@@ -2,6 +2,7 @@ import Vec from '@tldraw/vec'
 import { transaction } from 'mobx'
 import type { TLBinding, TLEventMap, TLHandle, TLStateEvents } from '../types'
 import { deepMerge, GeomUtils } from '../utils'
+import { findBindingPoint } from '../utils/BindingUtils'
 import type { TLLineShape, TLLineShapeProps, TLShape } from './shapes'
 import type { TLApp } from './TLApp'
 import type { TLTool } from './TLTool'
@@ -109,7 +110,7 @@ export class TLBaseLineBindingState<
 
         // Don't bind the start handle if both handles are inside of the target shape.
         if (!modKey && !startTarget.hitTestPoint(Vec.add(next.shape.point, endHandle.point))) {
-          nextStartBinding = this.findBindingPoint(
+          nextStartBinding = findBindingPoint(
             shape.props,
             startTarget,
             'start',
@@ -151,7 +152,7 @@ export class TLBaseLineBindingState<
         })
 
       for (const target of targets) {
-        draggedBinding = this.findBindingPoint(
+        draggedBinding = findBindingPoint(
           shape.props,
           target,
           this.handleId,
@@ -240,35 +241,4 @@ export class TLBaseLineBindingState<
       }
     }
   }
-
-  private findBindingPoint = (
-    shape: TLLineShapeProps,
-    target: TLShape,
-    handleId: 'start' | 'end',
-    bindingId: string,
-    point: number[],
-    origin: number[],
-    direction: number[],
-    bindAnywhere: boolean
-  ) => {
-    const bindingPoint = target.getBindingPoint(
-      point, // fix dead center bug
-      origin,
-      direction,
-      bindAnywhere
-    )
-
-    // Not all shapes will produce a binding point
-    if (!bindingPoint) return
-
-    return {
-      id: bindingId,
-      type: 'line',
-      fromId: shape.id,
-      toId: target.id,
-      handleId: handleId,
-      point: Vec.toFixed(bindingPoint.point),
-      distance: bindingPoint.distance,
-    }
-  }
 }

+ 85 - 0
tldraw/packages/core/src/utils/BindingUtils.ts

@@ -0,0 +1,85 @@
+import Vec from '@tldraw/vec'
+import { uniqueId } from '.'
+import { TLLineShape, TLLineShapeProps, TLShape } from '../lib'
+import type { TLBinding } from '../types'
+
+export function findBindingPoint(
+  shape: TLLineShapeProps,
+  target: TLShape,
+  handleId: 'start' | 'end',
+  bindingId: string,
+  point: number[],
+  origin: number[],
+  direction: number[],
+  bindAnywhere: boolean
+) {
+  const bindingPoint = target.getBindingPoint(
+    point, // fix dead center bug
+    origin,
+    direction,
+    bindAnywhere
+  )
+
+  // Not all shapes will produce a binding point
+  if (!bindingPoint) return
+
+  return {
+    id: bindingId,
+    type: 'line',
+    fromId: shape.id,
+    toId: target.id,
+    handleId: handleId,
+    point: Vec.toFixed(bindingPoint.point),
+    distance: bindingPoint.distance,
+  }
+}
+
+/** Given source & target, calculate a new Line shape from the center of source and to the center of target */
+export function createNewLineBinding(
+  source: TLShape,
+  target: TLShape
+): [TLLineShapeProps, TLBinding[]] | null {
+  // cs -> center of source, etc
+  const cs = source.getCenter()
+  const ct = target.getCenter()
+  const lineId = uniqueId()
+  const lineShape = {
+    ...TLLineShape.defaultProps,
+    id: lineId,
+    type: TLLineShape.id,
+    parentId: source.props.parentId,
+    point: cs,
+  }
+
+  const startBinding = findBindingPoint(
+    lineShape,
+    source,
+    'start',
+    uniqueId(),
+    cs,
+    cs,
+    Vec.uni(Vec.sub(ct, cs)),
+    false
+  )
+
+  const endBinding = findBindingPoint(
+    lineShape,
+    target,
+    'end',
+    uniqueId(),
+    ct,
+    ct,
+    Vec.uni(Vec.sub(cs, ct)),
+    false
+  )
+
+  if (startBinding && endBinding) {
+    lineShape.handles.start.point = [0, 0]
+    lineShape.handles.end.point = Vec.sub(ct, cs)
+    lineShape.handles.start.bindingId = startBinding.id
+    lineShape.handles.end.bindingId = endBinding.id
+
+    return [lineShape, [startBinding, endBinding]]
+  }
+  return null
+}

+ 1 - 0
tldraw/packages/core/src/utils/index.ts

@@ -5,6 +5,7 @@ export * from './KeyUtils'
 export * from './GeomUtils'
 export * from './PolygonUtils'
 export * from './SvgPathUtils'
+export * from './BindingUtils'
 export * from './DataUtils'
 export * from './TextUtils'
 export * from './getTextSize'