Pārlūkot izejas kodu

binding indicators

Peng Xiao 3 gadi atpakaļ
vecāks
revīzija
260f032afa

+ 18 - 0
tldraw/apps/tldraw-logseq/src/lib/shapes/BindingIndicator.tsx

@@ -0,0 +1,18 @@
+import * as React from 'react'
+
+interface BindingIndicatorProps {
+  strokeWidth: number
+  size: number[]
+}
+export function BindingIndicator({ strokeWidth, size }: BindingIndicatorProps) {
+  return (
+    <rect
+      className="tl-binding-indicator"
+      x={strokeWidth}
+      y={strokeWidth}
+      width={Math.max(0, size[0] - strokeWidth / 2)}
+      height={Math.max(0, size[1] - strokeWidth / 2)}
+      strokeWidth={16 * 2}
+    />
+  )
+}

+ 4 - 1
tldraw/apps/tldraw-logseq/src/lib/shapes/BoxShape.tsx

@@ -4,6 +4,7 @@ import { SVGContainer, TLComponentProps } from '@tldraw/react'
 import { TLBoxShape, TLBoxShapeProps } from '@tldraw/core'
 import { observer } from 'mobx-react-lite'
 import { CustomStyleProps, withClampedStyles } from './style-props'
+import { BindingIndicator } from './BindingIndicator'
 
 export interface BoxShapeProps extends TLBoxShapeProps, CustomStyleProps {
   borderRadius: number
@@ -37,8 +38,10 @@ export class BoxShape extends TLBoxShape<BoxShapeProps> {
         opacity,
       },
     } = this
+
     return (
       <SVGContainer {...events} opacity={isErasing ? 0.2 : opacity}>
+        {isBinding && <BindingIndicator strokeWidth={strokeWidth} size={[w, h]} />}
         <rect
           className={isSelected ? 'tl-hitarea-fill' : 'tl-hitarea-stroke'}
           x={strokeWidth / 2}
@@ -58,7 +61,7 @@ export class BoxShape extends TLBoxShape<BoxShapeProps> {
           height={Math.max(0.01, h - strokeWidth)}
           strokeWidth={strokeWidth}
           stroke={stroke}
-          fill={!isBinding ? fill : 'red'}
+          fill={fill}
         />
       </SVGContainer>
     )

+ 3 - 11
tldraw/apps/tldraw-logseq/src/lib/shapes/LineShape.tsx

@@ -34,7 +34,7 @@ export class LineShape extends TLLineShape<LineShapeProps> {
 
   hideSelection = true
 
-  ReactComponent = observer(({ events, isErasing, isSelected }: TLComponentProps) => {
+  ReactComponent = observer(({ events, isErasing }: TLComponentProps) => {
     const {
       stroke,
       fill,
@@ -62,20 +62,12 @@ export class LineShape extends TLLineShape<LineShapeProps> {
     )
   })
 
-  ReactIndicator = observer(({ events, isErasing, isSelected }: TLComponentProps) => {
+  ReactIndicator = observer(() => {
     const {
-      stroke,
-      fill,
-      strokeWidth,
       decorations,
       handles: { start, end },
-      opacity,
     } = this.props
-    return (
-      <>
-        <path d={getArrowPath(start.point, end.point, decorations?.start, decorations?.end)} />
-      </>
-    )
+    return <path d={getArrowPath(start.point, end.point, decorations?.start, decorations?.end)} />
   })
 
   validateProps = (props: Partial<LineShapeProps>) => {

+ 10 - 4
tldraw/apps/tldraw-logseq/src/lib/shapes/LogseqPortalShape.tsx

@@ -3,13 +3,14 @@
 /* eslint-disable @typescript-eslint/no-explicit-any */
 import * as React from 'react'
 import { TLBoxShape, TLBoxShapeProps } from '@tldraw/core'
-import { HTMLContainer, TLComponentProps, useApp } from '@tldraw/react'
+import { HTMLContainer, SVGContainer, TLComponentProps, useApp } from '@tldraw/react'
 import { observer } from 'mobx-react-lite'
 import { CustomStyleProps, withClampedStyles } from './style-props'
 import { TextInput } from '~components/inputs/TextInput'
 import { LogseqContext } from '~lib/logseq-context'
 import type { Shape } from '~lib'
 import { useCameraMovingRef } from '~hooks/useCameraMoving'
+import { BindingIndicator } from './BindingIndicator'
 
 export interface LogseqPortalShapeProps extends TLBoxShapeProps, CustomStyleProps {
   type: 'logseq-portal'
@@ -95,9 +96,9 @@ export class LogseqPortalShape extends TLBoxShape<LogseqPortalShapeProps> {
     )
   })
 
-  ReactComponent = observer(({ events, isEditing, isErasing }: TLComponentProps) => {
+  ReactComponent = observer(({ events, isEditing, isErasing, isBinding }: TLComponentProps) => {
     const {
-      props: { opacity, pageId },
+      props: { opacity, pageId, size, strokeWidth },
     } = this
 
     const app = useApp<Shape>()
@@ -125,7 +126,11 @@ export class LogseqPortalShape extends TLBoxShape<LogseqPortalShapeProps> {
     const logseqLink = this.props.logseqLink
     if (logseqLink) {
       const f = () => app.pubEvent('whiteboard-go-to-link', logseqLink)
-      linkButton = <a className='ml-2' onMouseDown={f}>🔗 {logseqLink}</a>
+      linkButton = (
+        <a className="ml-2" onMouseDown={f}>
+          🔗 {logseqLink}
+        </a>
+      )
     }
 
     return (
@@ -136,6 +141,7 @@ export class LogseqPortalShape extends TLBoxShape<LogseqPortalShapeProps> {
           opacity: isErasing ? 0.2 : opacity,
           border: '1px solid rgb(52, 52, 52)',
           backgroundColor: '#ffffff',
+          boxShadow: isBinding ? '0px 0px 0 16px var(--tl-binding)' : '',
         }}
         {...events}
       >

+ 47 - 40
tldraw/packages/core/src/lib/TLBaseLineBindingState.ts

@@ -28,7 +28,7 @@ export class TLBaseLineBindingState<
       currentGrid,
     } = this.app
     // @ts-expect-error just ignore
-    const shape = this.app.getShapeById<TLLineShape>(this.initialShape.id)
+    const shape = this.app.getShapeById<TLLineShape>(this.initialShape.id)!
 
     const { handles } = this.initialShape
     const handleId = this.handleId
@@ -83,44 +83,46 @@ export class TLBaseLineBindingState<
       let nextStartBinding: TLBinding | undefined
 
       const startTarget = this.app.getShapeById(this.startBindingShapeId)
-      const center = startTarget.getCenter()
-
-      const startHandle = next.shape.handles.start
-      const endHandle = next.shape.handles.end
-
-      const rayPoint = Vec.add(startHandle.point, next.shape.point)
-
-      if (Vec.isEqual(rayPoint, center)) rayPoint[1]++ // Fix bug where ray and center are identical
-
-      const rayOrigin = center
-
-      const isInsideShape = startTarget.hitTestPoint(currentPoint)
-
-      const rayDirection = Vec.uni(Vec.sub(rayPoint, rayOrigin))
-
-      const hasStartBinding = this.app.currentPage.bindings[this.newStartBindingId] !== undefined
-
-      // 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(
-          shape.props,
-          startTarget,
-          'start',
-          this.newStartBindingId,
-          center,
-          rayOrigin,
-          rayDirection,
-          isInsideShape
-        )
-      }
-
-      if (nextStartBinding && !hasStartBinding) {
-        next.bindings[this.newStartBindingId] = nextStartBinding
-        next.shape.handles.start.bindingId = nextStartBinding.id
-      } else if (!nextStartBinding && hasStartBinding) {
-        console.log('removing start binding')
-        delete next.bindings[this.newStartBindingId]
-        next.shape.handles.start.bindingId = undefined
+      if (startTarget) {
+        const center = startTarget.getCenter()
+
+        const startHandle = next.shape.handles.start
+        const endHandle = next.shape.handles.end
+
+        const rayPoint = Vec.add(startHandle.point, next.shape.point)
+
+        if (Vec.isEqual(rayPoint, center)) rayPoint[1]++ // Fix bug where ray and center are identical
+
+        const rayOrigin = center
+
+        const isInsideShape = startTarget.hitTestPoint(currentPoint)
+
+        const rayDirection = Vec.uni(Vec.sub(rayPoint, rayOrigin))
+
+        const hasStartBinding = this.app.currentPage.bindings[this.newStartBindingId] !== undefined
+
+        // 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(
+            shape.props,
+            startTarget,
+            'start',
+            this.newStartBindingId,
+            center,
+            rayOrigin,
+            rayDirection,
+            isInsideShape
+          )
+        }
+
+        if (nextStartBinding && !hasStartBinding) {
+          next.bindings[this.newStartBindingId] = nextStartBinding
+          next.shape.handles.start.bindingId = nextStartBinding.id
+        } else if (!nextStartBinding && hasStartBinding) {
+          console.log('removing start binding')
+          delete next.bindings[this.newStartBindingId]
+          next.shape.handles.start.bindingId = undefined
+        }
       }
     }
 
@@ -136,7 +138,7 @@ export class TLBaseLineBindingState<
       const endPoint = Vec.add(next.shape.point, next.shape.handles.end.point)
 
       const targets = this.bindableShapeIds
-        .map(id => this.app.getShapeById(id))
+        .map(id => this.app.getShapeById(id)!)
         .sort((a, b) => b.nonce - a.nonce)
         .filter(shape => {
           return ![startPoint, endPoint].every(point => shape.hitTestPoint(point))
@@ -191,6 +193,11 @@ export class TLBaseLineBindingState<
     if (updated) {
       this.currentShape.update(updated)
       this.app.currentPage.updateBindings(next.bindings)
+      this.app.setBindingShapes(
+        Object.values(updated.handles ?? {})
+          .map(h => next.bindings[h.bindingId!]?.toId)
+          .filter(Boolean)
+      )
     }
   }
 

+ 1 - 1
tldraw/packages/core/src/lib/tools/TLLineTool/states/CreatingState.tsx

@@ -38,7 +38,7 @@ export class CreatingState<
     this.app.setSelectedShapes([shape])
 
     this.startBindingShapeId = this.bindableShapeIds
-      .map(id => this.app.getShapeById(id))
+      .map(id => this.app.getShapeById(id)!)
       .filter(s => PointUtils.pointInBounds(originPoint, s.bounds))[0]?.id
 
     if (this.startBindingShapeId) {

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

@@ -68,6 +68,7 @@ export interface TLTheme {
   brushFill?: string
   brushStroke?: string
   selectFill?: string
+  binding?: string
   selectStroke?: string
   background?: string
   foreground?: string

+ 2 - 2
tldraw/packages/react/src/components/SVGContainer/SVGContainer.tsx

@@ -7,11 +7,11 @@ interface SvgContainerProps extends React.SVGProps<SVGGElement> {
 }
 
 export const SVGContainer = React.forwardRef<SVGSVGElement, SvgContainerProps>(
-  function SVGContainer({ id, className = '', children, ...rest }, ref) {
+  function SVGContainer({ id, className = '', style, children, ...rest }, ref) {
     return (
       <Observer>
         {() => (
-          <svg ref={ref} className={`tl-positioned-svg ${className}`}>
+          <svg ref={ref} style={style} className={`tl-positioned-svg ${className}`}>
             <g id={id} className="tl-centered-g" {...rest}>
               {children}
             </g>

+ 3 - 3
tldraw/packages/react/src/hooks/useStylesheet.ts

@@ -71,6 +71,7 @@ const defaultTheme: TLTheme = {
   brushStroke: 'rgba(0,0,0,.25)',
   selectStroke: 'rgb(66, 133, 244)',
   selectFill: 'rgba(65, 132, 244, 0.05)',
+  binding: 'rgba(65, 55, 55, 0.05)',
   background: 'rgb(248, 249, 250)',
   foreground: 'rgb(51, 51, 51)',
   grid: 'rgba(144, 144, 144, .9)',
@@ -388,9 +389,8 @@ const tlcss = css`
   }
 
   .tl-binding-indicator {
-    stroke-width: calc(3px * var(--tl-scale));
-    fill: var(--tl-selectFill);
-    stroke: var(--tl-selectStroke);
+    fill: transparent;
+    stroke: var(--tl-binding);
   }
 
   .tl-centered {