Browse Source

refactor: how quick add works

Peng Xiao 3 years ago
parent
commit
769dce01f9
22 changed files with 166 additions and 84 deletions
  1. 3 0
      tldraw/apps/tldraw-logseq/src/app.tsx
  2. 8 13
      tldraw/apps/tldraw-logseq/src/components/inputs/ColorInput.tsx
  3. 11 0
      tldraw/apps/tldraw-logseq/src/hooks/useQuickAdd.ts
  4. 9 5
      tldraw/apps/tldraw-logseq/src/lib/shapes/LogseqPortalShape.tsx
  5. 0 11
      tldraw/apps/tldraw-logseq/src/lib/tools/LogseqPortalTool.tsx
  6. 17 0
      tldraw/apps/tldraw-logseq/src/lib/tools/LogseqPortalTool/LogseqPortalTool.tsx
  7. 1 0
      tldraw/apps/tldraw-logseq/src/lib/tools/LogseqPortalTool/index.ts
  8. 65 0
      tldraw/apps/tldraw-logseq/src/lib/tools/LogseqPortalTool/states/CreatingState.tsx
  9. 19 0
      tldraw/apps/tldraw-logseq/src/lib/tools/LogseqPortalTool/states/IdleState.tsx
  10. 2 0
      tldraw/apps/tldraw-logseq/src/lib/tools/LogseqPortalTool/states/index.ts
  11. 1 1
      tldraw/apps/tldraw-logseq/src/lib/tools/index.ts
  12. 0 9
      tldraw/packages/core/src/lib/TLApp/TLApp.ts
  13. 3 3
      tldraw/packages/core/src/lib/shapes/TLDotShape/TLDotShape.tsx
  14. 11 9
      tldraw/packages/core/src/lib/shapes/TLShape/TLShape.tsx
  15. 2 3
      tldraw/packages/core/src/lib/tools/TLDotTool/TLDotTool.ts
  16. 3 10
      tldraw/packages/core/src/lib/tools/TLDotTool/states/CreatingState.tsx
  17. 2 2
      tldraw/packages/core/src/lib/tools/TLDotTool/states/IdleState.tsx
  18. 1 16
      tldraw/packages/core/src/lib/tools/TLSelectTool/states/PointingCanvasState.ts
  19. 4 0
      tldraw/packages/core/src/types/types.ts
  20. 3 1
      tldraw/packages/react/src/hooks/useSetup.ts
  21. 0 1
      tldraw/packages/react/src/lib/TLReactShape.tsx
  22. 1 0
      tldraw/packages/react/src/types/TLReactSubscriptions.tsx

+ 3 - 0
tldraw/apps/tldraw-logseq/src/app.tsx

@@ -13,6 +13,7 @@ import { AppUI } from '~components/AppUI'
 import { ContextBar } from '~components/ContextBar/ContextBar'
 import { useFileDrop } from '~hooks/useFileDrop'
 import { usePaste } from '~hooks/usePaste'
+import { useQuickAdd } from '~hooks/useQuickAdd'
 import { LogseqContext } from '~lib/logseq-context'
 import { Shape, shapes } from '~lib/shapes'
 import {
@@ -62,6 +63,7 @@ export const App = function App({
 }: LogseqTldrawProps): JSX.Element {
   const onFileDrop = useFileDrop()
   const onPaste = usePaste()
+  const onQuickAdd = useQuickAdd()
 
   const Page = React.useMemo(() => React.memo(PageComponent), [])
   return (
@@ -71,6 +73,7 @@ export const App = function App({
         Tools={tools}
         onFileDrop={onFileDrop}
         onPaste={onPaste}
+        onCanvasDBClick={onQuickAdd}
         {...props}
       >
         <div className="logseq-tldraw logseq-tldraw-wrapper">

+ 8 - 13
tldraw/apps/tldraw-logseq/src/components/inputs/ColorInput.tsx

@@ -7,21 +7,16 @@ interface ColorInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
 export function ColorInput({ label, value, onChange, ...rest }: ColorInputProps) {
   const ref = React.useRef<HTMLDivElement>(null)
   const [computedValue, setComputedValue] = React.useState(value)
-  let varName: string | undefined
+
   // TODO: listen to theme change?
-  if (value?.toString().startsWith('var') && ref.current) {
-    varName = /var\((.*)\)/.exec(value.toString())?.[1]
-    if (varName) {
-      const newValue = getComputedStyle(ref.current).getPropertyValue(varName).trim();
-      if (newValue !== computedValue) {
-        setComputedValue(newValue)
+  React.useEffect(() => {
+    if (value?.toString().startsWith('var') && ref.current) {
+      const varName = /var\((.*)\)/.exec(value.toString())?.[1]
+      if (varName) {
+        setComputedValue(getComputedStyle(ref.current).getPropertyValue(varName).trim())
       }
     }
-  }
-
-  if (varName) {
-    return null
-  }
+  }, [value])
 
   return (
     <div className="input" ref={ref}>
@@ -32,7 +27,7 @@ export function ColorInput({ label, value, onChange, ...rest }: ColorInputProps)
           name={`color-${label}`}
           type="color"
           value={computedValue}
-          onChange={e => {
+          onChange={(e) => {
             setComputedValue(e.target.value)
             onChange?.(e)
           }}

+ 11 - 0
tldraw/apps/tldraw-logseq/src/hooks/useQuickAdd.ts

@@ -0,0 +1,11 @@
+import type { TLReactCallbacks } from '@tldraw/react'
+import React from 'react'
+import type { Shape } from '~lib'
+
+export function useQuickAdd() {
+  return React.useCallback<TLReactCallbacks<Shape>['onCanvasDBClick']>(async app => {
+    app.selectTool('logseq-portal', {
+      quick: true,
+    })
+  }, [])
+}

+ 9 - 5
tldraw/apps/tldraw-logseq/src/lib/shapes/LogseqPortalShape.tsx

@@ -1,7 +1,7 @@
 /* eslint-disable @typescript-eslint/no-explicit-any */
 import { MagnifyingGlassIcon } from '@radix-ui/react-icons'
 import { TLBoxShape, TLBoxShapeProps } from '@tldraw/core'
-import { HTMLContainer, TLComponentProps, useApp } from '@tldraw/react'
+import { HTMLContainer, TLComponentProps, TLContextBarProps, useApp } from '@tldraw/react'
 import { makeObservable } from 'mobx'
 import { observer } from 'mobx-react-lite'
 import * as React from 'react'
@@ -93,7 +93,6 @@ const LogseqPortalShapeHeader = observer(
 
 export class LogseqPortalShape extends TLBoxShape<LogseqPortalShapeProps> {
   static id = 'logseq-portal'
-  static smart = true
 
   static defaultProps: LogseqPortalShapeProps = {
     id: 'logseq-portal',
@@ -133,6 +132,7 @@ export class LogseqPortalShape extends TLBoxShape<LogseqPortalShapeProps> {
   }
 
   ReactContextBar = observer(() => {
+    const app = useApp<Shape>()
     return (
       <>
         <ColorInput
@@ -142,6 +142,7 @@ export class LogseqPortalShape extends TLBoxShape<LogseqPortalShapeProps> {
             this.update({
               fill: e.target.value,
             })
+            app.persist(true)
           }}
         />
         <ColorInput
@@ -151,6 +152,7 @@ export class LogseqPortalShape extends TLBoxShape<LogseqPortalShapeProps> {
             this.update({
               stroke: e.target.value,
             })
+            app.persist(true)
           }}
         />
         <SwitchInput
@@ -164,6 +166,7 @@ export class LogseqPortalShape extends TLBoxShape<LogseqPortalShapeProps> {
               size: [this.props.size[0], collapsing ? HEADER_HEIGHT : this.props.collapsedHeight],
               collapsedHeight: collapsing ? originalHeight : this.props.collapsedHeight,
             })
+            app.persist()
           }}
         />
       </>
@@ -180,7 +183,7 @@ export class LogseqPortalShape extends TLBoxShape<LogseqPortalShapeProps> {
     const { Page } = React.useContext(LogseqContext)
     const isSelected = app.selectedIds.has(this.id)
     const tlEventsEnabled =
-      isMoving || (isSelected && !isEditing) || app.selectedTool.id !== 'select'
+      (isMoving || (isSelected && !isEditing) || app.selectedTool.id !== 'select') && !this.draft
     const stop = React.useCallback(
       e => {
         if (!tlEventsEnabled) {
@@ -210,14 +213,15 @@ export class LogseqPortalShape extends TLBoxShape<LogseqPortalShapeProps> {
     }, [isEditing, this.props.collapsed])
 
     const onPageNameChanged = React.useCallback((id: string) => {
+      app.history.resume()
       app.wrapUpdate(() => {
+        this.setDraft(false)
         this.update({
           pageId: id,
           size: [600, 320],
           blockType: 'P',
         })
-        this.setDraft(false)
-        app.clearEditingShape()
+        app.selectTool('select')
       })
     }, [])
 

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

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

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

@@ -0,0 +1,17 @@
+import { TLApp, TLTool } from '@tldraw/core'
+import type { TLReactEventMap } from '@tldraw/react'
+import { LogseqPortalShape, Shape } from '~lib/shapes'
+import { CreatingState, IdleState } from './states'
+
+export class LogseqPortalTool extends TLTool<
+  Shape,
+  TLReactEventMap,
+  TLApp<Shape, TLReactEventMap>
+> {
+  static id = 'logseq-portal'
+  static shortcut = ['i']
+  static states = [IdleState, CreatingState]
+  static initial = 'idle'
+
+  Shape = LogseqPortalShape
+}

+ 1 - 0
tldraw/apps/tldraw-logseq/src/lib/tools/LogseqPortalTool/index.ts

@@ -0,0 +1 @@
+export * from './LogseqPortalTool'

+ 65 - 0
tldraw/apps/tldraw-logseq/src/lib/tools/LogseqPortalTool/states/CreatingState.tsx

@@ -0,0 +1,65 @@
+import { TLApp, TLTargetType, TLToolState, uniqueId } from '@tldraw/core'
+import type { TLReactEventMap, TLReactEvents } from '@tldraw/react'
+import Vec from '@tldraw/vec'
+import { transaction } from 'mobx'
+import { LogseqPortalShape, Shape } from '~lib/shapes'
+import type { LogseqPortalTool } from '../LogseqPortalTool'
+
+export class CreatingState extends TLToolState<
+  Shape,
+  TLReactEventMap,
+  TLApp<Shape, TLReactEventMap>,
+  LogseqPortalTool
+> {
+  static id = 'creating'
+
+  creatingShape?: LogseqPortalShape
+
+  offset: number[] = [0, 0]
+
+  onEnter = () => {
+    this.app.history.pause()
+    transaction(() => {
+      const shape = new LogseqPortalShape({
+        id: uniqueId(),
+        parentId: this.app.currentPage.id,
+        point: Vec.sub(this.app.inputs.originPoint, this.offset),
+        size: LogseqPortalShape.defaultProps.size,
+      } as any)
+      shape.setDraft(true)
+      this.creatingShape = shape
+      this.app.currentPage.addShapes(shape)
+      this.app.setEditingShape(shape)
+    })
+  }
+
+  onPointerDown: TLReactEvents<Shape>['pointer'] = info => {
+    switch (info.type) {
+      case TLTargetType.Shape: {
+        if (info.shape === this.creatingShape) return
+        this.app.selectTool('select')
+        break
+      }
+      case TLTargetType.Selection: {
+        break
+      }
+      case TLTargetType.Handle: {
+        break
+      }
+      case TLTargetType.Canvas: {
+        if (!info.order) {
+          this.app.selectTool('select')
+        }
+        break
+      }
+    }
+  }
+
+  onExit = () => {
+    if (this.creatingShape?.draft) {
+      this.app.deleteShapes([this.creatingShape.id])
+    }
+    this.app.clearEditingShape()
+    this.app.history.resume()
+  }
+}

+ 19 - 0
tldraw/apps/tldraw-logseq/src/lib/tools/LogseqPortalTool/states/IdleState.tsx

@@ -0,0 +1,19 @@
+import { TLApp, TLStateEvents, TLToolState } from '@tldraw/core'
+import type { TLReactEventMap } from '@tldraw/react'
+import type { Shape } from '~lib/shapes'
+import type { LogseqPortalTool } from '../LogseqPortalTool'
+
+export class IdleState extends TLToolState<
+  Shape,
+  TLReactEventMap,
+  TLApp<Shape, TLReactEventMap>,
+  LogseqPortalTool
+> {
+  static id = 'idle'
+
+  onEnter = ({ quick }: { quick: boolean } = { quick: false }) => {
+    if (quick) {
+      this.tool.transition('creating', { quick })
+    }
+  }
+}

+ 2 - 0
tldraw/apps/tldraw-logseq/src/lib/tools/LogseqPortalTool/states/index.ts

@@ -0,0 +1,2 @@
+export * from './CreatingState'
+export * from './IdleState'

+ 1 - 1
tldraw/apps/tldraw-logseq/src/lib/tools/index.ts

@@ -8,4 +8,4 @@ export * from './PencilTool'
 export * from './PolygonTool'
 export * from './TextTool'
 export * from './YouTubeTool'
-export * from './LogseqPortalTool'
+export * from './LogseqPortalTool'

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

@@ -673,15 +673,6 @@ 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))
   }

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

@@ -4,7 +4,7 @@ import { TLShape, TLResizeInfo, TLShapeProps } from '../TLShape'
 import { BoundsUtils } from '~utils'
 
 export interface TLDotShapeProps extends TLShapeProps {
-  radius: number
+  radius?: number
 }
 
 export class TLDotShape<P extends TLDotShapeProps = TLDotShapeProps, M = any> extends TLShape<
@@ -35,7 +35,7 @@ export class TLDotShape<P extends TLDotShapeProps = TLDotShapeProps, M = any> ex
     const {
       props: {
         point: [x, y],
-        radius,
+        radius = 0,
       },
     } = this
     return {
@@ -56,7 +56,7 @@ export class TLDotShape<P extends TLDotShapeProps = TLDotShapeProps, M = any> ex
 
   onResize = (initialProps: any, info: TLResizeInfo): this => {
     const {
-      props: { radius },
+      props: { radius = 0 },
     } = this
     return this.update({
       point: [

+ 11 - 9
tldraw/packages/core/src/lib/shapes/TLShape/TLShape.tsx

@@ -6,7 +6,7 @@ import {
   intersectRayBounds,
 } from '@tldraw/intersect'
 import Vec from '@tldraw/vec'
-import { action, computed, makeObservable, observable, toJS } from 'mobx'
+import { action, computed, makeObservable, observable, toJS, transaction } from 'mobx'
 import { BINDING_DISTANCE } from '~constants'
 import type { TLAsset, TLBounds, TLHandle, TLResizeCorner, TLResizeEdge } from '~types'
 import { BoundsUtils, deepCopy, PointUtils } from '~utils'
@@ -18,7 +18,6 @@ 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
@@ -81,8 +80,6 @@ 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
@@ -103,12 +100,11 @@ export abstract class TLShape<P extends TLShapeProps = TLShapeProps, M = any> {
   canFlip: TLFlag = true
   canEdit: TLFlag = false
   canBind: TLFlag = false
-  
+
   @observable nonce = 0
 
   bindingDistance = BINDING_DISTANCE
 
-  // For smart shape
   @observable private _draft = false
   @observable private isDirty = false
   @observable private lastSerialized: TLShapeModel<P> | undefined
@@ -128,6 +124,10 @@ export abstract class TLShape<P extends TLShapeProps = TLShapeProps, M = any> {
     this._draft = draft
   }
 
+  @action setNonce(nonce: number) {
+    this.nonce = nonce
+  }
+
   @action setIsDirty(isDirty: boolean) {
     this.isDirty = isDirty
   }
@@ -282,9 +282,11 @@ export abstract class TLShape<P extends TLShapeProps = TLShapeProps, M = any> {
 
   protected getCachedSerialized = (): TLShapeModel<P> => {
     if (this.isDirty || !this.lastSerialized) {
-      this.nonce = Date.now()
-      this.setIsDirty(false)
-      this.setLastSerialized(this.getSerialized())
+      transaction(() => {
+        this.setNonce(Date.now())
+        this.setIsDirty(false)
+        this.setLastSerialized(this.getSerialized())
+      })
     }
     if (this.lastSerialized) {
       return this.lastSerialized

+ 2 - 3
tldraw/packages/core/src/lib/tools/TLDotTool/TLDotTool.ts

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

+ 3 - 10
tldraw/packages/core/src/lib/tools/TLDotTool/states/CreatingState.tsx

@@ -1,12 +1,12 @@
 import Vec from '@tldraw/vec'
-import { TLApp, TLShape, TLToolState, TLBoxShape } from '~lib'
+import { TLApp, TLShape, TLToolState, TLDotShape } 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 TLBoxShape,
+  S extends TLDotShape,
   T extends S & TLShape,
   K extends TLEventMap,
   R extends TLApp<S, K>,
@@ -26,9 +26,6 @@ export class CreatingState<
       point: Vec.sub(this.app.inputs.originPoint, this.offset),
       size: Shape.defaultProps.size,
     } as any)
-    if (Shape.smart) {
-      shape.setDraft(true)
-    }
     this.creatingShape = shape
   }
 
@@ -46,11 +43,7 @@ export class CreatingState<
       const shape = this.creatingShape
       transaction(() => {
         this.app.currentPage.addShapes(shape)
-        if (this.tool.Shape.smart && shape.draft) {
-          this.app.setEditingShape(shape)
-        } else {
-          this.app.setSelectedShapes([shape])
-        }
+        this.app.setSelectedShapes([shape])
       })
     }
     if (!this.app.settings.isToolLocked) {

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

@@ -1,10 +1,10 @@
-import { TLBoxShape, TLApp, TLShape, TLToolState } from '~lib'
+import { TLDotShape, 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 & TLBoxShape,
+  T extends S & TLDotShape,
   K extends TLEventMap,
   R extends TLApp<S, K>,
   P extends TLDotTool<T, S, K, R>

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

@@ -2,7 +2,6 @@ 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,
@@ -42,20 +41,6 @@ export class PointingCanvasState<
   }
 
   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],
-        })
-        shape.setDraft(true)
-        this.app.history.pause()
-        this.app.setEditingShape(shape)
-        this.app.currentPage.addShapes(shape)
-      }
-    })
+    this.app.notify('canvas-dbclick', { point: this.app.inputs.originPoint })
   }
 }

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

@@ -168,6 +168,10 @@ export type TLSubscriptionEvent =
       event: 'delete-assets'
       info: { assets: TLAsset[] }
     }
+    | {
+      event: 'canvas-dbclick'
+      info: { point: number[] }
+    }
 
 export type TLSubscriptionEventName = TLSubscriptionEvent['event']
 

+ 3 - 1
tldraw/packages/react/src/hooks/useSetup.ts

@@ -19,7 +19,8 @@ export function useSetup<
     onDeleteAssets,
     onDeleteShapes,
     onFileDrop,
-    onPaste
+    onPaste,
+    onCanvasDBClick
   } = props
 
   React.useLayoutEffect(() => {
@@ -46,6 +47,7 @@ export function useSetup<
     if (onDeleteAssets) unsubs.push(app.subscribe('delete-assets', onDeleteAssets))
     if (onFileDrop) unsubs.push(app.subscribe('drop-files', onFileDrop))
     if (onPaste) unsubs.push(app.subscribe('paste', onPaste))
+    if (onCanvasDBClick) unsubs.push(app.subscribe('canvas-dbclick', onCanvasDBClick))
     // Kind of unusual, is this the right pattern?
 
     return () => unsubs.forEach(unsub => unsub())

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

@@ -29,7 +29,6 @@ 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<

+ 1 - 0
tldraw/packages/react/src/types/TLReactSubscriptions.tsx

@@ -39,5 +39,6 @@ export interface TLReactCallbacks<
   onDeleteShapes: TLReactCallback<S, R, 'delete-shapes'>
   onDeleteAssets: TLReactCallback<S, R, 'delete-assets'>
   onFileDrop: TLReactCallback<S, R, 'drop-files'>
+  onCanvasDBClick: TLReactCallback<S, R, 'canvas-dbclick'>
   onPaste: TLReactCallback<S, R, 'paste'>
 }