Browse Source

Merge branch 'whiteboards' of https://github.com/logseq/logseq into whiteboards

sawhney17 3 years ago
parent
commit
5d869c2060

+ 0 - 3
tldraw/apps/tldraw-logseq/src/components/ContextBar/ContextBar.tsx

@@ -130,9 +130,6 @@ const _ContextBar: TLContextBarComponent<Shape> = ({ shapes, offsets }) => {
             ) : null}
           </>
         )}
-        <a className="shape-link" onClick={() => app.pubEvent('whiteboard-link', shapes)}>
-          Link
-        </a>
       </div>
     </HTMLContainer>
   )

+ 1 - 1
tldraw/apps/tldraw-logseq/src/lib/preview-manager.tsx

@@ -87,7 +87,7 @@ export class PreviewManager {
               const transformArr = [`translate(${tx}, ${ty})`, `rotate(${r}, ${rdx}, ${rdy})`]
               return (
                 <g transform={transformArr.join(' ')} key={s.id}>
-                  {s.getShapeSVGJsx()}
+                  {s.getShapeSVGJsx(true)}
                 </g>
               )
             })}

+ 33 - 12
tldraw/apps/tldraw-logseq/src/lib/shapes/LineShape.tsx

@@ -146,26 +146,47 @@ export class LineShape extends TLLineShape<LineShapeProps> {
     return withClampedStyles(props)
   }
 
-  getShapeSVGJsx() {
+  getShapeSVGJsx(preview = false) {
     const {
       stroke,
       fill,
       strokeWidth,
       decorations,
+      label,
       handles: { start, end },
     } = this.props
+    const midPoint = Vec.med(start.point, end.point)
     return (
-      <Arrow
-        style={{
-          stroke,
-          fill,
-          strokeWidth,
-        }}
-        start={start.point}
-        end={end.point}
-        decorationStart={decorations?.start}
-        decorationEnd={decorations?.end}
-      />
+      <>
+        <Arrow
+          style={{
+            stroke,
+            fill,
+            strokeWidth,
+          }}
+          start={start.point}
+          end={end.point}
+          decorationStart={decorations?.start}
+          decorationEnd={decorations?.end}
+        />
+        {preview && (
+          <>
+            <text
+              style={{
+                transformOrigin: 'top left',
+              }}
+              fontFamily="Inter"
+              fontSize={20}
+              transform={`translate(${midPoint[0]}, ${midPoint[1]})`}
+              textAnchor="middle"
+              stroke={stroke}
+              fill={stroke}
+            >
+              {label}
+            </text>
+          </>
+        )}
+      </>
     )
   }
 }

+ 137 - 157
tldraw/apps/tldraw-logseq/src/lib/shapes/LogseqPortalShape.tsx

@@ -1,8 +1,7 @@
-// TODO: provide "frontend.components.page/page" component?
-
 /* eslint-disable @typescript-eslint/no-explicit-any */
 import { TLBoxShape, TLBoxShapeProps } from '@tldraw/core'
 import { HTMLContainer, TLComponentProps, useApp } from '@tldraw/react'
+import { makeObservable, transaction } from 'mobx'
 import { observer } from 'mobx-react-lite'
 import * as React from 'react'
 import { TextInput } from '~components/inputs/TextInput'
@@ -16,17 +15,84 @@ export interface LogseqPortalShapeProps extends TLBoxShapeProps, CustomStyleProp
   pageId: string // page name or UUID
 }
 
+interface LogseqQuickSearchProps {
+  onChange: (id: string) => void
+}
+
+const LogseqQuickSearch = observer(({ onChange }: LogseqQuickSearchProps) => {
+  const [q, setQ] = React.useState('')
+  const rInput = React.useRef<HTMLInputElement>(null)
+  const { search } = React.useContext(LogseqContext)
+
+  const secretPrefix = 'œ::'
+
+  const commitChange = React.useCallback((id: string) => {
+    setQ(id)
+    onChange(id)
+    rInput.current?.blur()
+  }, [])
+
+  const handleChange = React.useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
+    const _q = e.currentTarget.value
+    if (_q.startsWith(secretPrefix)) {
+      const id = _q.substring(secretPrefix.length)
+      commitChange(id)
+    } else {
+      setQ(_q)
+    }
+  }, [])
+
+  const options = React.useMemo(() => {
+    if (search && q) {
+      return search(q)
+    }
+    return null
+  }, [search, q])
+
+  React.useEffect(() => {
+    setTimeout(() => {
+      rInput.current?.focus()
+    })
+  }, [])
+
+  return (
+    <>
+      <TextInput
+        ref={rInput}
+        label="Page name or block UUID"
+        type="text"
+        value={q}
+        onChange={handleChange}
+        onKeyDown={e => {
+          if (e.key === 'Enter') {
+            commitChange(q)
+          }
+        }}
+        list="logseq-portal-search-results"
+      />
+      <datalist id="logseq-portal-search-results">
+        {options?.map(option => (
+          <option key={option} value={secretPrefix + option}>
+            {option}
+          </option>
+        ))}
+      </datalist>
+    </>
+  )
+})
+
 export class LogseqPortalShape extends TLBoxShape<LogseqPortalShapeProps> {
   static id = 'logseq-portal'
+  static smart = true
 
   static defaultProps: LogseqPortalShapeProps = {
     id: 'logseq-portal',
     type: 'logseq-portal',
     parentId: 'page',
     point: [0, 0],
-    size: [600, 320],
-    stroke: '#000000',
-    fill: '#ffffff',
+    size: [180, 75],
+    stroke: 'transparent',
+    fill: 'var(--ls-secondary-background-color)',
     strokeWidth: 2,
     opacity: 1,
     pageId: '',
@@ -38,174 +104,88 @@ export class LogseqPortalShape extends TLBoxShape<LogseqPortalShapeProps> {
   canActivate = true
   canEdit = true
 
-  ReactContextBar = observer(() => {
-    const { pageId } = this.props
-    const [q, setQ] = React.useState(pageId)
-    const rInput = React.useRef<HTMLInputElement>(null)
-    const { search } = React.useContext(LogseqContext)
-    const app = useApp()
+  constructor(props = {} as Partial<LogseqPortalShapeProps>) {
+    super(props)
+    makeObservable(this)
+    this.draft = true
+  }
 
-    const secretPrefix = 'œ::'
+  ReactComponent = observer(({ events, isErasing, isActivated }: TLComponentProps) => {
+    const {
+      props: { opacity, pageId, strokeWidth, stroke },
+    } = this
 
-    const commitChange = React.useCallback((id: string) => {
-      setQ(id)
-      this.update({ pageId: id, size: LogseqPortalShape.defaultProps.size })
-      app.persist()
-      rInput.current?.blur()
-    }, [])
+    const app = useApp<Shape>()
+    const isMoving = useCameraMovingRef()
+    const { Page } = React.useContext(LogseqContext)
+    const isSelected = app.selectedIds.has(this.id)
+    const tlEventsEnabled = isMoving || isSelected || app.selectedTool.id !== 'select'
+    const stop = React.useCallback(
+      e => {
+        if (!tlEventsEnabled) {
+          e.stopPropagation()
+        }
+      },
+      [tlEventsEnabled]
+    )
 
-    const handleChange = React.useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
-      const _q = e.currentTarget.value
-      if (_q.startsWith(secretPrefix)) {
-        const id = _q.substring(secretPrefix.length)
-        commitChange(id)
-      } else {
-        setQ(_q)
-      }
+    const commitChange = React.useCallback((id: string) => {
+      transaction(() => {
+        this.update({
+          pageId: id,
+          size: [600, 320],
+        })
+        this.setDraft(false)
+        app.setActivatedShapes([])
+        app.persist()
+      })
     }, [])
 
-    const options = React.useMemo(() => {
-      if (search && q) {
-        return search(q)
-      }
-      return null
-    }, [search, q])
-
     return (
-      <>
-        <TextInput
-          ref={rInput}
-          label="Page name or block UUID"
-          type="text"
-          value={q}
-          onChange={handleChange}
-          onKeyDown={e => {
-            if (e.key === 'Enter') {
-              commitChange(q)
-            }
-          }}
-          list="logseq-portal-search-results"
-        />
-        <datalist id="logseq-portal-search-results">
-          {options?.map(option => (
-            <option key={option} value={secretPrefix + option}>
-              {option}
-            </option>
-          ))}
-        </datalist>
-      </>
-    )
-  })
-
-  ReactComponent = observer(
-    ({ events, isEditing, isErasing, isBinding, isActivated }: TLComponentProps) => {
-      const {
-        props: { opacity, pageId, strokeWidth },
-      } = this
-
-      const app = useApp<Shape>()
-      const isMoving = useCameraMovingRef()
-      const { Page } = React.useContext(LogseqContext)
-      const isSelected = app.selectedIds.has(this.id)
-      const enableTlEvents = () => {
-        return isMoving || isEditing || isSelected || app.selectedTool.id !== 'select'
-      }
-
-      const stop = React.useCallback(
-        e => {
-          if (!enableTlEvents()) {
-            e.stopPropagation()
-          }
-        },
-        [enableTlEvents]
-      )
-
-      if (!Page) {
-        return null
-      }
-
-      let linkButton = null
-      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>
-        )
-      }
-
-      return (
-        <HTMLContainer
+      <HTMLContainer
+        style={{
+          overflow: 'hidden',
+          pointerEvents: 'all',
+          opacity: isErasing ? 0.2 : opacity,
+          backgroundColor: 'var(--ls-primary-background-color)',
+        }}
+        {...events}
+      >
+        <div
+          onWheelCapture={stop}
+          onPointerDown={stop}
+          onPointerUp={stop}
           style={{
-            overflow: 'hidden',
-            pointerEvents: 'all',
-            opacity: isErasing ? 0.2 : opacity,
-            border: `${strokeWidth}px solid`,
-            borderColor: isActivated ? 'var(--tl-selectStroke)' : 'rgb(52, 52, 52)',
-            backgroundColor: '#ffffff',
-            boxShadow: isBinding ? '0px 0px 0 var(--tl-binding-distance) var(--tl-binding)' : '',
+            pointerEvents: isActivated ? 'all' : 'none',
           }}
-          {...events}
         >
-          {pageId && (
+          {this.draft ? (
+            <LogseqQuickSearch onChange={commitChange} />
+          ) : (
             <div
-              className="ls-whiteboard-card-header"
               style={{
-                height: '32px',
                 width: '100%',
-                background: isActivated ? 'var(--tl-selectStroke)' : '#bbb',
-                display: 'flex',
-                alignItems: 'center',
-                color: isActivated ? '#fff' : '#000',
-                justifyContent: 'center',
+                overflow: 'auto',
+                overscrollBehavior: 'none',
+                height: pageId ? 'calc(100% - 33px)' : '100%',
+                userSelect: 'none',
+                boxShadow: isActivated
+                  ? '0px 0px 0 var(--tl-binding-distance) var(--tl-binding)'
+                  : '',
+                opacity: isSelected ? 0.5 : 1,
               }}
             >
-              {pageId}
-              {linkButton}
+              {pageId && Page ? (
+                <div style={{ padding: '12px', height: '100%', cursor: 'default' }}>
+                  <Page pageId={pageId} />
+                </div>
+              ) : null}
             </div>
           )}
-          <div
-            style={{
-              width: '100%',
-              overflow: 'auto',
-              overscrollBehavior: 'none',
-              height: pageId ? 'calc(100% - 33px)' : '100%',
-              pointerEvents: isActivated ? 'all' : 'none',
-              userSelect: 'none',
-              opacity: isSelected ? 0.5 : 1,
-            }}
-          >
-            {pageId ? (
-              <div
-                onWheelCapture={stop}
-                onPointerDown={stop}
-                onPointerUp={stop}
-                style={{ padding: '12px', height: '100%', cursor: 'default' }}
-              >
-                <Page pageId={pageId} />
-              </div>
-            ) : (
-              <div
-                style={{
-                  width: '100%',
-                  height: '100%',
-                  display: 'flex',
-                  alignItems: 'center',
-                  overflow: 'hidden',
-                  justifyContent: 'center',
-                  padding: 16,
-                  fontSize: '24px'
-                }}
-              >
-                LOGSEQ PORTAL PLACEHOLDER
-              </div>
-            )}
-          </div>
-        </HTMLContainer>
-      )
-    }
-  )
+        </div>
+      </HTMLContainer>
+    )
+  })
 
   ReactIndicator = observer(() => {
     const {

+ 19 - 2
tldraw/apps/tldraw-logseq/src/lib/shapes/TextShape.tsx

@@ -272,8 +272,25 @@ export class TextShape extends TLTextShape<TextShapeProps> {
 
   getShapeSVGJsx() {
     const {
-      props: { text, stroke },
+      props: { text, stroke, fontSize, fontFamily },
     } = this
-    return <text stroke={stroke} fill={stroke}>{text}</text>
+    // Stretch to the bound size 
+    const bounds = this.getBounds()
+
+    return (
+      <text
+        style={{
+          transformOrigin: 'top left',
+        }}
+        transform={`translate(${bounds.width / 2}, ${bounds.height / 2})`}
+        textAnchor="middle"
+        fontFamily={fontFamily}
+        fontSize={fontSize}
+        stroke={stroke}
+        fill={stroke}
+      >
+        {text}
+      </text>
+    )
   }
 }

+ 2 - 2
tldraw/apps/tldraw-logseq/src/lib/tools/LogseqPortalTool.tsx

@@ -1,8 +1,8 @@
-import { TLBoxTool } from '@tldraw/core'
+import { TLBoxTool, TLDotTool } from '@tldraw/core'
 import type { TLReactEventMap } from '@tldraw/react'
 import { Shape, LogseqPortalShape } from '~lib/shapes'
 
-export class LogseqPortalTool extends TLBoxTool<LogseqPortalShape, Shape, TLReactEventMap> {
+export class LogseqPortalTool extends TLDotTool<LogseqPortalShape, Shape, TLReactEventMap> {
   static id = 'logseq-portal'
   static shortcut = ['i']
   Shape = LogseqPortalShape

+ 0 - 4
tldraw/apps/tldraw-logseq/src/styles.css

@@ -310,10 +310,6 @@
   -webkit-user-select: text;
 }
 
-.logseq-tldraw .tl-positioned-inner:focus-within {
-  box-shadow: 0 0 12px var(--ls-active-primary-color);
-}
-
 .logseq-tldraw .tl-stroke-hitarea {
   fill: none;
   stroke: transparent;

+ 1 - 0
tldraw/demo/src/main.jsx

@@ -1,6 +1,7 @@
 import React from 'react'
 import ReactDOM from 'react-dom'
 import 'tldraw-logseq/styles.css'
+import '../../../public/static/css/common.css'
 
 import App from './App'
 

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

@@ -664,6 +664,15 @@ export class TLApp<
 
   Shapes = new Map<string, TLShapeConstructor<S>>()
 
+  get SmartShape() {
+    for (const S of this.Shapes.values()) {
+      if (S.smart) {
+        return S
+      }
+    }
+    return null
+  }
+
   registerShapes = (Shapes: TLShapeConstructor<S>[]) => {
     Shapes.forEach(Shape => this.Shapes.set(Shape.id, Shape))
   }

+ 25 - 16
tldraw/packages/core/src/lib/TLPage/TLPage.ts

@@ -50,6 +50,7 @@ export class TLPage<S extends TLShape = TLShape, E extends TLEventMap = TLEventM
         shapes: this.shapes.map(shape => toJS(shape.props)),
         bindings: toJS(this.bindings),
         nonce: this.nonce,
+        activatedShape: toJS(this.app.activatedIds),
       }),
       (curr, prev) => {
         this.cleanup(curr, prev)
@@ -71,7 +72,8 @@ export class TLPage<S extends TLShape = TLShape, E extends TLEventMap = TLEventM
     return {
       id: this.id,
       name: this.name,
-      shapes: this.shapes.map(shape => shape.serialized),
+      // @ts-expect-error maybe later
+      shapes: this.shapes.map(shape => shape.serialized).filter(s => !!s),
       bindings: deepCopy(this.bindings),
       nonce: this.nonce,
     }
@@ -178,20 +180,22 @@ export class TLPage<S extends TLShape = TLShape, E extends TLEventMap = TLEventM
         direction === 'horizontal',
         direction === 'vertical'
       )
-      shape.onResize(shape.serialized, {
-        bounds: relativeBounds,
-        center: BoundsUtils.getBoundsCenter(relativeBounds),
-        rotation: shape.props.rotation ?? 0 * -1,
-        type: TLResizeCorner.TopLeft,
-        scale:
-          shape.canFlip && shape.props.scale
-            ? direction === 'horizontal'
-              ? [-shape.props.scale[0], 1]
-              : [1, -shape.props.scale[1]]
-            : [1, 1],
-        clip: false,
-        transformOrigin: [0.5, 0.5],
-      })
+      if (shape.serialized) {
+        shape.onResize(shape.serialized, {
+          bounds: relativeBounds,
+          center: BoundsUtils.getBoundsCenter(relativeBounds),
+          rotation: shape.props.rotation ?? 0 * -1,
+          type: TLResizeCorner.TopLeft,
+          scale:
+            shape.canFlip && shape.props.scale
+              ? direction === 'horizontal'
+                ? [-shape.props.scale[0], 1]
+                : [1, -shape.props.scale[1]]
+              : [1, 1],
+          clip: false,
+          transformOrigin: [0.5, 0.5],
+        })
+      }
     })
     return this
   }
@@ -271,12 +275,17 @@ export class TLPage<S extends TLShape = TLShape, E extends TLEventMap = TLEventM
       }
     })
 
-    if (!deepEqual(updated, curr)) {
+    // Cleanup inactive drafts
+    const shapesToDelete = this.shapes.filter(s => s.draft && !this.app.activatedShapes.includes(s))
+
+    if (!deepEqual(updated, curr) || shapesToDelete.length) {
       transaction(() => {
         this.update({
           bindings: updated.bindings,
         })
 
+        this.removeShapes(...shapesToDelete)
+
         updated.shapes.forEach(shape => {
           this.getShapeById(shape.id)?.update(shape)
         })

+ 12 - 3
tldraw/packages/core/src/lib/shapes/TLShape/TLShape.tsx

@@ -18,6 +18,7 @@ export type TLShapeModel<P extends TLShapeProps = TLShapeProps> = {
 export interface TLShapeConstructor<S extends TLShape = TLShape> {
   new (props: any): S
   id: string
+  smart: boolean
 }
 
 export type TLFlag = boolean
@@ -79,6 +80,8 @@ export abstract class TLShape<P extends TLShapeProps = TLShapeProps, M = any> {
     makeObservable(this)
   }
 
+  // there should be only one Shape that is smart (created by double click canvas)
+  static smart: boolean
   static type: string
 
   @observable props: P
@@ -104,6 +107,8 @@ export abstract class TLShape<P extends TLShapeProps = TLShapeProps, M = any> {
 
   bindingDistance = BINDING_DISTANCE
 
+  // For smart shape
+  @observable draft = false
   @observable private isDirty = false
   @observable private lastSerialized: TLShapeModel<P> | undefined
 
@@ -113,6 +118,10 @@ export abstract class TLShape<P extends TLShapeProps = TLShapeProps, M = any> {
     return this.props.id
   }
 
+  @action setDraft(draft: boolean) {
+    this.draft = draft
+  }
+
   @action setIsDirty(isDirty: boolean) {
     this.isDirty = isDirty
   }
@@ -278,8 +287,8 @@ export abstract class TLShape<P extends TLShapeProps = TLShapeProps, M = any> {
   }
 
   @computed
-  get serialized(): TLShapeModel<P> {
-    return this.getCachedSerialized()
+  get serialized(): TLShapeModel<P> | null {
+    return this.draft ? null : this.getCachedSerialized()
   }
 
   validateProps = (
@@ -345,7 +354,7 @@ export abstract class TLShape<P extends TLShapeProps = TLShapeProps, M = any> {
    * Get a svg group element that can be used to render the shape with only the props data. In the
    * base, draw any shape as a box. Can be overridden by subclasses.
    */
-  getShapeSVGJsx() {
+  getShapeSVGJsx(preview = false) {
     // Do not need to consider the original point here
     const bounds = this.getBounds()
     return (

+ 6 - 5
tldraw/packages/core/src/lib/tools/TLDotTool/TLDotTool.ts

@@ -1,9 +1,9 @@
-import { IdleState, CreatingState } from './states'
-import { TLTool, TLApp, TLShape, TLDotShape, TLDotShapeProps } from '~lib'
+import { TLApp, TLBoxShape, TLShape, TLTool } from '~lib'
 import { TLCursor, TLEventMap } from '~types'
+import { CreatingState, IdleState } from './states'
 
 export abstract class TLDotTool<
-  T extends TLDotShape = TLDotShape,
+  T extends TLBoxShape = TLBoxShape,
   S extends TLShape = TLShape,
   K extends TLEventMap = TLEventMap,
   R extends TLApp<S, K> = TLApp<S, K>
@@ -17,8 +17,9 @@ export abstract class TLDotTool<
   cursor = TLCursor.Cross
 
   abstract Shape: {
-    new (props: Partial<TLDotShapeProps>): T
+    new (props: Partial<T['props']>): T
     id: string
-    defaultProps: TLDotShapeProps
+    smart: boolean
+    defaultProps: T['props']
   }
 }

+ 16 - 8
tldraw/packages/core/src/lib/tools/TLDotTool/states/CreatingState.tsx

@@ -1,12 +1,13 @@
 import Vec from '@tldraw/vec'
-import { TLApp, TLShape, TLToolState, TLDotShape } from '~lib'
+import { TLApp, TLShape, TLToolState, TLBoxShape } from '~lib'
 import { uniqueId } from '~utils'
 import type { TLEventMap, TLStateEvents } from '~types'
 import type { TLDotTool } from '../TLDotTool'
+import { transaction } from 'mobx'
 
 export class CreatingState<
-  S extends TLShape,
-  T extends S & TLDotShape,
+  S extends TLBoxShape,
+  T extends S & TLShape,
   K extends TLEventMap,
   R extends TLApp<S, K>,
   P extends TLDotTool<T, S, K, R>
@@ -19,15 +20,14 @@ export class CreatingState<
 
   onEnter = () => {
     const { Shape } = this.tool
-    this.offset = [Shape.defaultProps.radius, Shape.defaultProps.radius]
+    this.offset = [Shape.defaultProps.size[0] / 2, Shape.defaultProps.size[1] / 2]
     const shape = new Shape({
       id: uniqueId(),
       parentId: this.app.currentPage.id,
       point: Vec.sub(this.app.inputs.originPoint, this.offset),
-    })
+      size: Shape.defaultProps.size,
+    } as any)
     this.creatingShape = shape
-    this.app.currentPage.addShapes(shape)
-    this.app.setSelectedShapes([shape])
   }
 
   onPointerMove: TLStateEvents<S, K>['onPointerMove'] = () => {
@@ -41,7 +41,15 @@ export class CreatingState<
   onPointerUp: TLStateEvents<S, K>['onPointerUp'] = () => {
     this.tool.transition('idle')
     if (this.creatingShape) {
-      this.app.setSelectedShapes([this.creatingShape])
+      const shape = this.creatingShape
+      transaction(() => {
+        this.app.currentPage.addShapes(shape)
+        if (this.tool.Shape.smart && shape.draft) {
+          this.app.setActivatedShapes([shape])
+        } else {
+          this.app.setSelectedShapes([shape])
+        }
+      })
     }
     if (!this.app.settings.isToolLocked) {
       this.app.transition('select')

+ 2 - 2
tldraw/packages/core/src/lib/tools/TLDotTool/states/IdleState.tsx

@@ -1,10 +1,10 @@
-import { TLDotShape, TLApp, TLShape, TLToolState } from '~lib'
+import { TLBoxShape, TLApp, TLShape, TLToolState } from '~lib'
 import type { TLEventMap, TLStateEvents } from '~types'
 import type { TLDotTool } from '../TLDotTool'
 
 export class IdleState<
   S extends TLShape,
-  T extends S & TLDotShape,
+  T extends S & TLBoxShape,
   K extends TLEventMap,
   R extends TLApp<S, K>,
   P extends TLDotTool<T, S, K, R>

+ 16 - 2
tldraw/packages/core/src/lib/tools/TLSelectTool/states/PointingCanvasState.ts

@@ -1,6 +1,8 @@
 import { Vec } from '@tldraw/vec'
+import { transaction } from 'mobx'
 import { TLApp, TLSelectTool, TLShape, TLToolState } from '~lib'
 import type { TLEventMap, TLEvents } from '~types'
+import { uniqueId } from '~utils'
 
 export class PointingCanvasState<
   S extends TLShape,
@@ -40,7 +42,19 @@ export class PointingCanvasState<
     this.tool.transition('pinching', { info, event })
   }
 
-  onDoubleClick: TLEvents<S>['pointer'] = info => {
-    console.log('TODO: bringing up Logseq autocomplete here', info)
+  onDoubleClick: TLEvents<S>['pointer'] = () => {
+    transaction(() => {
+      const Shape = this.app.SmartShape
+      if (Shape) {
+        const shape = new Shape({
+          id: uniqueId(),
+          type: Shape.id,
+          parentId: this.app.currentPage.id,
+          point: [...this.app.inputs.originPoint],
+        })
+        this.app.setActivatedShapes([shape.id])
+        this.app.currentPage.addShapes(shape)
+      }
+    })
   }
 }

+ 2 - 1
tldraw/packages/core/src/lib/tools/TLSelectTool/states/ResizingState.ts

@@ -64,8 +64,9 @@ export class ResizingState<
     this.isSingle = selectedShapesArray.length === 1
     this.selectionRotation = this.isSingle ? selectedShapesArray[0].props.rotation ?? 0 : 0
     this.initialCommonBounds = { ...selectionBounds }
+    // @ts-expect-error maybe later
     this.snapshots = Object.fromEntries(
-      selectedShapesArray.map(shape => {
+      selectedShapesArray.filter(s => !s.draft).map(shape => {
         const bounds = { ...shape.bounds }
         const [cx, cy] = BoundsUtils.getBoundsCenter(bounds)
         return [

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

@@ -25,7 +25,7 @@ export const AppCanvas = observer(function InnerApp<S extends TLReactShape>(
       selectedShapes={app.selectedShapesArray}
       activatedShapes={app.activatedShapes}
       erasingShapes={app.erasingShapesArray}
-      shapes={app.shapesInViewport}
+      shapes={app.shapes}
       assets={app.assets}
       showGrid={app.settings.showGrid}
       showSelection={app.showSelection}

+ 1 - 1
tldraw/packages/react/src/components/Canvas/Canvas.tsx

@@ -154,7 +154,7 @@ export const Canvas = observer(function Renderer<S extends TLReactShape>({
               isSelected={true}
             />
           ))}
-          {hoveredShape && (
+          {hoveredShape && !hoveredShape.draft && (
             <Indicator key={'hovered_indicator_' + hoveredShape.id} shape={hoveredShape} />
           )}
           {brush && components.Brush && <components.Brush bounds={brush} />}

+ 1 - 0
tldraw/packages/react/src/lib/TLReactShape.tsx

@@ -30,6 +30,7 @@ export interface TLComponentProps<M = unknown> extends TLCommonShapeProps<M> {
 export interface TLReactShapeConstructor<S extends TLReactShape = TLReactShape> {
   new (props: S['props'] & { type: any }): S
   id: string
+  smart: boolean
 }
 
 export abstract class TLReactShape<P extends TLShapeProps = TLShapeProps, M = any> extends TLShape<