Browse Source

feat: more toolbar actions

Peng Xiao 3 years ago
parent
commit
81637484d2
31 changed files with 487 additions and 154 deletions
  1. 14 13
      src/main/frontend/extensions/tldraw.cljs
  2. 2 0
      tldraw/apps/tldraw-logseq/package.json
  3. 217 32
      tldraw/apps/tldraw-logseq/src/components/ContextBar/contextBarActionFactory.tsx
  4. 2 6
      tldraw/apps/tldraw-logseq/src/components/inputs/ColorInput.tsx
  5. 0 1
      tldraw/apps/tldraw-logseq/src/components/inputs/SwitchInput.tsx
  6. 35 2
      tldraw/apps/tldraw-logseq/src/components/inputs/ToggleGroupInput.tsx
  7. 17 0
      tldraw/apps/tldraw-logseq/src/components/inputs/ToggleInput.tsx
  8. 0 1
      tldraw/apps/tldraw-logseq/src/hooks/usePaste.ts
  9. 9 5
      tldraw/apps/tldraw-logseq/src/lib/shapes/BoxShape.tsx
  10. 2 0
      tldraw/apps/tldraw-logseq/src/lib/shapes/DotShape.tsx
  11. 45 3
      tldraw/apps/tldraw-logseq/src/lib/shapes/EllipseShape.tsx
  12. 6 10
      tldraw/apps/tldraw-logseq/src/lib/shapes/HTMLShape.tsx
  13. 3 1
      tldraw/apps/tldraw-logseq/src/lib/shapes/HighlighterShape.tsx
  14. 1 5
      tldraw/apps/tldraw-logseq/src/lib/shapes/ImageShape.tsx
  15. 4 0
      tldraw/apps/tldraw-logseq/src/lib/shapes/LineShape.tsx
  16. 8 9
      tldraw/apps/tldraw-logseq/src/lib/shapes/LogseqPortalShape.tsx
  17. 2 0
      tldraw/apps/tldraw-logseq/src/lib/shapes/PenShape.tsx
  18. 5 2
      tldraw/apps/tldraw-logseq/src/lib/shapes/PencilShape.tsx
  19. 38 6
      tldraw/apps/tldraw-logseq/src/lib/shapes/PolygonShape.tsx
  20. 2 0
      tldraw/apps/tldraw-logseq/src/lib/shapes/TextShape.tsx
  21. 3 7
      tldraw/apps/tldraw-logseq/src/lib/shapes/VideoShape.tsx
  22. 5 14
      tldraw/apps/tldraw-logseq/src/lib/shapes/YouTubeShape.tsx
  23. 2 1
      tldraw/apps/tldraw-logseq/src/lib/shapes/arrow/Arrow.tsx
  24. 2 12
      tldraw/apps/tldraw-logseq/src/lib/shapes/style-props.tsx
  25. 33 3
      tldraw/apps/tldraw-logseq/src/styles.css
  26. 5 0
      tldraw/packages/core/src/lib/TLApi/TLApi.ts
  27. 1 1
      tldraw/packages/core/src/lib/TLApp/TLApp.ts
  28. 5 3
      tldraw/packages/core/src/lib/shapes/TLShape/TLShape.tsx
  29. 10 1
      tldraw/packages/core/src/utils/index.ts
  30. 0 14
      tldraw/packages/react/src/hooks/useStylesheet.ts
  31. 9 2
      tldraw/yarn.lock

+ 14 - 13
src/main/frontend/extensions/tldraw.cljs

@@ -60,18 +60,19 @@
                        :Breadcrumb breadcrumb
                        :PageNameLink page-name-link})
 
-(def tldraw-handlers {:search search-handler
-                      :queryBlockByUUID #(clj->js (model/query-block-by-uuid (parse-uuid %)))
-                      :isWhiteboardPage model/whiteboard-page?
-                      :saveAsset save-asset-handler
-                      :makeAssetUrl editor-handler/make-asset-url
-                      :addNewBlock (fn [content]
-                                     (str (whiteboard-handler/add-new-block! name content)))
-                      :sidebarAddBlock (fn [uuid type]
-                                         (state/sidebar-add-block! (state/get-current-repo)
-                                                                   (:db/id (model/get-page uuid))
-                                                                   (keyword type)))
-                      :redirectToPage route-handler/redirect-to-page!})
+(defn get-tldraw-handlers [name]
+  {:search search-handler
+   :queryBlockByUUID #(clj->js (model/query-block-by-uuid (parse-uuid %)))
+   :isWhiteboardPage model/whiteboard-page?
+   :saveAsset save-asset-handler
+   :makeAssetUrl editor-handler/make-asset-url
+   :addNewBlock (fn [content]
+                  (str (whiteboard-handler/add-new-block! name content)))
+   :sidebarAddBlock (fn [uuid type]
+                      (state/sidebar-add-block! (state/get-current-repo)
+                                                (:db/id (model/get-page uuid))
+                                                (keyword type)))
+   :redirectToPage route-handler/redirect-to-page!})
 
 (rum/defc tldraw-app
   [name block-id]
@@ -97,7 +98,7 @@
         :on-wheel util/stop-propagation}
 
        (tldraw {:renderers tldraw-renderers
-                :handlers tldraw-handlers
+                :handlers (get-tldraw-handlers name)
                 :onMount (fn [app] (set-tln ^js app))
                 :onPersist (fn [app]
                              (let [document (gobj/get app "serialized")]

+ 2 - 0
tldraw/apps/tldraw-logseq/package.json

@@ -30,10 +30,12 @@
     "tsup": "^6.1.2",
     "typescript": "^4.7.3",
     "zx": "^6.2.4",
+    "polished": "^4.0.0",
     "@radix-ui/react-dropdown-menu": "^1.0.0",
     "@radix-ui/react-select": "^1.0.0",
     "@radix-ui/react-switch": "^1.0.0",
     "@radix-ui/react-toggle-group": "^1.0.0",
+    "@radix-ui/react-toggle": "^1.0.0",
     "@radix-ui/react-separator": "^1.0.0"
   },
   "peerDependencies": {

+ 217 - 32
tldraw/apps/tldraw-logseq/src/components/ContextBar/contextBarActionFactory.tsx

@@ -1,36 +1,99 @@
-import { isNonNullable } from '@tldraw/core'
+import { isNonNullable, debounce, Decoration, TLLineShapeProps } from '@tldraw/core'
 import { useApp } from '@tldraw/react'
 import { observer } from 'mobx-react-lite'
 import React from 'react'
 import { TablerIcon } from '~components/icons'
+import { ColorInput } from '~components/inputs/ColorInput'
 import { SelectInput, SelectOption } from '~components/inputs/SelectInput'
 import { TextInput } from '~components/inputs/TextInput'
-import { ToggleGroupInput, ToggleGroupInputOption } from '~components/inputs/ToggleGroupInput'
-import { LogseqPortalShape, Shape, YouTubeShape } from '~lib'
+import {
+  ToggleGroupInput,
+  ToggleGroupInputOption,
+  ToggleGroupMultipleInput,
+} from '~components/inputs/ToggleGroupInput'
+import { ToggleInput } from '~components/inputs/ToggleInput'
+import { tint } from 'polished'
+import type {
+  BoxShape,
+  EllipseShape,
+  LineShape,
+  LogseqPortalShape,
+  PencilShape,
+  PolygonShape,
+  Shape,
+  TextShape,
+  YouTubeShape,
+} from '~lib'
 import { LogseqContext } from '~lib/logseq-context'
 
 export const contextBarActionTypes = [
   // Order matters
+  'Edit',
+  'Swatch',
   'NoFill',
-  'ColorAccent',
-  'StrokeColor',
-  'NoStroke',
+  'StrokeType',
   'ScaleLevel',
   'YoutubeLink',
   'LogseqPortalViewMode',
+  'ArrowMode',
   'OpenPage',
 ] as const
 
 type ContextBarActionType = typeof contextBarActionTypes[number]
-const singleShapeActions: ContextBarActionType[] = ['YoutubeLink', 'OpenPage']
+const singleShapeActions: ContextBarActionType[] = ['Edit', 'YoutubeLink', 'OpenPage']
 
 const contextBarActionMapping = new Map<ContextBarActionType, React.FC>()
 
+type ShapeType = Shape['props']['type']
+
+const shapeMapping: Partial<Record<ShapeType, ContextBarActionType[]>> = {
+  'logseq-portal': ['Edit', 'LogseqPortalViewMode', 'ScaleLevel', 'OpenPage'],
+  youtube: ['YoutubeLink'],
+  box: ['Swatch', 'NoFill', 'StrokeType'],
+  ellipse: ['Swatch', 'NoFill', 'StrokeType'],
+  polygon: ['Swatch', 'NoFill', 'StrokeType'],
+  line: ['Edit', 'Swatch', 'ArrowMode'],
+  pencil: ['Swatch'],
+  highlighter: ['Swatch'],
+  text: ['Edit', 'Swatch'],
+}
+
+const noStrokeShapes = Object.entries(shapeMapping)
+  .filter(([key, types]) => {
+    return !types.includes('NoFill') && types.includes('Swatch')
+  })
+  .map(([key]) => key) as ShapeType[]
+
+function filterShapeByAction<S extends Shape>(shapes: Shape[], type: ContextBarActionType): S[] {
+  return shapes.filter(shape => shapeMapping[shape.props.type]?.includes(type)) as S[]
+}
+
+const EditAction = observer(() => {
+  const app = useApp<Shape>()
+  const shape = filterShapeByAction(app.selectedShapesArray, 'Edit')[0]
+
+  return (
+    <button
+      className="tl-contextbar-button"
+      type="button"
+      onClick={() => {
+        app.api.editShape(shape)
+        if (shape.props.type === 'logseq-portal') {
+          window.logseq?.api?.edit_block?.(shape.props.pageId)
+        }
+      }}
+    >
+      <TablerIcon name="text" />
+    </button>
+  )
+})
+
 const LogseqPortalViewModeAction = observer(() => {
   const app = useApp<Shape>()
-  const shapes = app.selectedShapesArray.filter(
-    s => s.props.type === LogseqPortalShape.defaultProps.type
-  ) as LogseqPortalShape[]
+  const shapes = filterShapeByAction<LogseqPortalShape>(
+    app.selectedShapesArray,
+    'LogseqPortalViewMode'
+  )
 
   const collapsed = shapes.every(s => s.collapsed)
   const ViewModeOptions: ToggleGroupInputOption[] = [
@@ -51,6 +114,7 @@ const LogseqPortalViewModeAction = observer(() => {
         shapes.forEach(shape => {
           shape.setCollapsed(v === '1' ? true : false)
         })
+        app.persist()
       }}
     />
   )
@@ -58,10 +122,7 @@ const LogseqPortalViewModeAction = observer(() => {
 
 const ScaleLevelAction = observer(() => {
   const app = useApp<Shape>()
-  const shapes = app.selectedShapesArray.filter(
-    s => s.props.type === LogseqPortalShape.defaultProps.type
-  ) as LogseqPortalShape[]
-
+  const shapes = filterShapeByAction<LogseqPortalShape>(app.selectedShapesArray, 'ScaleLevel')
   const scaleLevel = new Set(shapes.map(s => s.scaleLevel)).size > 1 ? '' : shapes[0].scaleLevel
   const sizeOptions: SelectOption[] = [
     {
@@ -94,11 +155,10 @@ const ScaleLevelAction = observer(() => {
       options={sizeOptions}
       value={scaleLevel}
       onValueChange={v => {
-        if (v) {
-          shapes.forEach(shape => {
-            shape.setScaleLevel(v as LogseqPortalShape['props']['scaleLevel'])
-          })
-        }
+        shapes.forEach(shape => {
+          shape.setScaleLevel(v as LogseqPortalShape['props']['scaleLevel'])
+        })
+        app.persist()
       }}
     />
   )
@@ -107,9 +167,7 @@ const ScaleLevelAction = observer(() => {
 const OpenPageAction = observer(() => {
   const { handlers } = React.useContext(LogseqContext)
   const app = useApp<Shape>()
-  const shapes = app.selectedShapesArray.filter(
-    s => s.props.type === LogseqPortalShape.defaultProps.type
-  ) as LogseqPortalShape[]
+  const shapes = filterShapeByAction<LogseqPortalShape>(app.selectedShapesArray, 'OpenPage')
   const shape = shapes[0]
   const { pageId, blockType } = shape.props
 
@@ -135,11 +193,10 @@ const OpenPageAction = observer(() => {
 
 const YoutubeLinkAction = observer(() => {
   const app = useApp<Shape>()
-  const shape = app.selectedShapesArray.find(
-    s => s.props.type === YouTubeShape.defaultProps.type
-  ) as YouTubeShape
+  const shape = filterShapeByAction<YouTubeShape>(app.selectedShapesArray, 'YoutubeLink')[0]
   const handleChange = React.useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
     shape.onYoutubeLinkChange(e.target.value)
+    app.persist()
   }, [])
 
   return (
@@ -156,17 +213,145 @@ const YoutubeLinkAction = observer(() => {
   )
 })
 
+const NoFillAction = observer(() => {
+  const app = useApp<Shape>()
+  const shapes = filterShapeByAction<BoxShape | PolygonShape | EllipseShape>(
+    app.selectedShapesArray,
+    'NoFill'
+  )
+  const handleChange = React.useCallback((v: boolean) => {
+    shapes.forEach(s => s.update({ noFill: v }))
+    app.persist()
+  }, [])
+
+  const noFill = shapes.every(s => s.props.noFill)
+
+  return (
+    <ToggleInput className="tl-contextbar-button" pressed={noFill} onPressedChange={handleChange}>
+      {noFill ? <TablerIcon name="eye-off" /> : <TablerIcon name="eye" />}
+    </ToggleInput>
+  )
+})
+
+const SwatchAction = observer(() => {
+  const app = useApp<Shape>()
+  // Placeholder
+  const shapes = filterShapeByAction<
+    BoxShape | PolygonShape | EllipseShape | LineShape | PencilShape | TextShape
+  >(app.selectedShapesArray, 'Swatch')
+  const handleChange = React.useMemo(() => {
+    let latestValue = ''
+    const handler: React.ChangeEventHandler<HTMLInputElement> = e => {
+      const strokeColor = tint(0.4, latestValue)
+      shapes.forEach(s => {
+        const strokeOnly = noStrokeShapes.includes(s.props.type)
+        s.update(
+          strokeOnly
+            ? { stroke: latestValue, fill: latestValue }
+            : { fill: latestValue, stroke: strokeColor }
+        )
+      })
+      app.persist(true)
+    }
+    return debounce(handler, 100, e => {
+      latestValue = e.target.value
+    })
+  }, [])
+
+  return <ColorInput value={shapes[0].props.fill} onChange={handleChange} />
+})
+
+const StrokeTypeAction = observer(() => {
+  const app = useApp<Shape>()
+  const shapes = filterShapeByAction<
+    BoxShape | PolygonShape | EllipseShape | LineShape | PencilShape
+  >(app.selectedShapesArray, 'StrokeType')
+
+  const StrokeTypeOptions: ToggleGroupInputOption[] = [
+    {
+      value: 'line',
+      icon: 'circle',
+    },
+    {
+      value: 'dashed',
+      icon: 'circle-dashed',
+    },
+  ]
+
+  const value = shapes.every(s => s.props.strokeType === 'dashed')
+    ? 'dashed'
+    : shapes.every(s => s.props.strokeType === 'line')
+    ? 'line'
+    : 'mixed'
+
+  return (
+    <ToggleGroupInput
+      options={StrokeTypeOptions}
+      value={value}
+      onValueChange={v => {
+        shapes.forEach(shape => {
+          shape.update({
+            strokeType: v,
+          })
+        })
+        app.persist()
+      }}
+    />
+  )
+})
+
+const ArrowModeAction = observer(() => {
+  const app = useApp<Shape>()
+  const shapes = filterShapeByAction<LineShape>(app.selectedShapesArray, 'ArrowMode')
+
+  const StrokeTypeOptions: ToggleGroupInputOption[] = [
+    {
+      value: 'start',
+      icon: 'arrow-narrow-left',
+    },
+    {
+      value: 'end',
+      icon: 'arrow-narrow-right',
+    },
+  ]
+
+  const startValue = shapes.every(s => s.props.decorations?.start === Decoration.Arrow)
+  const endValue = shapes.every(s => s.props.decorations?.end === Decoration.Arrow)
+
+  const value = [startValue ? 'start' : null, endValue ? 'end' : null].filter(isNonNullable)
+
+  const valueToDecorations = (value: string[]) => {
+    return {
+      start: value.includes('start') ? Decoration.Arrow : null,
+      end: value.includes('end') ? Decoration.Arrow : null,
+    }
+  }
+
+  return (
+    <ToggleGroupMultipleInput
+      options={StrokeTypeOptions}
+      value={value}
+      onValueChange={v => {
+        shapes.forEach(shape => {
+          shape.update({
+            decorations: valueToDecorations(v),
+          })
+        })
+        app.persist()
+      }}
+    />
+  )
+})
+
+contextBarActionMapping.set('Edit', EditAction)
 contextBarActionMapping.set('LogseqPortalViewMode', LogseqPortalViewModeAction)
 contextBarActionMapping.set('ScaleLevel', ScaleLevelAction)
 contextBarActionMapping.set('OpenPage', OpenPageAction)
 contextBarActionMapping.set('YoutubeLink', YoutubeLinkAction)
-
-type ShapeType = Shape['props']['type']
-
-const shapeMapping: Partial<Record<ShapeType, ContextBarActionType[]>> = {
-  'logseq-portal': ['LogseqPortalViewMode', 'ScaleLevel', 'OpenPage'],
-  youtube: ['YoutubeLink'],
-}
+contextBarActionMapping.set('NoFill', NoFillAction)
+contextBarActionMapping.set('Swatch', SwatchAction)
+contextBarActionMapping.set('StrokeType', StrokeTypeAction)
+contextBarActionMapping.set('ArrowMode', ArrowModeAction)
 
 const getContextBarActionTypes = (type: ShapeType) => {
   return (shapeMapping[type] ?? []).filter(isNonNullable)

+ 2 - 6
tldraw/apps/tldraw-logseq/src/components/inputs/ColorInput.tsx

@@ -1,10 +1,8 @@
 import * as React from 'react'
 
-interface ColorInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
-  label: string
-}
+interface ColorInputProps extends React.InputHTMLAttributes<HTMLInputElement> {}
 
-export function ColorInput({ label, value, onChange, ...rest }: ColorInputProps) {
+export function ColorInput({ value, onChange, ...rest }: ColorInputProps) {
   const ref = React.useRef<HTMLDivElement>(null)
   const [computedValue, setComputedValue] = React.useState(value)
 
@@ -21,11 +19,9 @@ export function ColorInput({ label, value, onChange, ...rest }: ColorInputProps)
 
   return (
     <div className="input" ref={ref}>
-      <label htmlFor={`color-${label}`}>{label}</label>
       <div className="color-input-wrapper">
         <input
           className="color-input"
-          name={`color-${label}`}
           type="color"
           value={computedValue}
           onChange={e => {

+ 0 - 1
tldraw/apps/tldraw-logseq/src/components/inputs/SwitchInput.tsx

@@ -7,7 +7,6 @@ interface SwitchInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
 export function SwitchInput({ label, onCheckedChange, checked, ...rest }: SwitchInputProps) {
   return (
     <div {...rest} className="input">
-      <label htmlFor={`switch-${label}`}>{label}</label>
       <Switch.Root
         className="switch-input-root"
         checked={checked}

+ 35 - 2
tldraw/apps/tldraw-logseq/src/components/inputs/ToggleGroupInput.tsx

@@ -6,13 +6,19 @@ export interface ToggleGroupInputOption {
   icon: string
 }
 
-interface SelectInputProps extends React.HTMLAttributes<HTMLElement> {
+interface ToggleGroupInputProps extends React.HTMLAttributes<HTMLElement> {
   options: ToggleGroupInputOption[]
   value: string
   onValueChange: (value: string) => void
 }
 
-export function ToggleGroupInput({ options, value, onValueChange }: SelectInputProps) {
+interface ToggleGroupMultipleInputProps extends React.HTMLAttributes<HTMLElement> {
+  options: ToggleGroupInputOption[]
+  value: string[]
+  onValueChange: (value: string[]) => void
+}
+
+export function ToggleGroupInput({ options, value, onValueChange }: ToggleGroupInputProps) {
   return (
     <ToggleGroup.Root
       className="tl-toggle-group-input"
@@ -35,3 +41,30 @@ export function ToggleGroupInput({ options, value, onValueChange }: SelectInputP
     </ToggleGroup.Root>
   )
 }
+
+export function ToggleGroupMultipleInput({
+  options,
+  value,
+  onValueChange,
+}: ToggleGroupMultipleInputProps) {
+  return (
+    <ToggleGroup.Root
+      className="tl-toggle-group-input"
+      type="multiple"
+      value={value}
+      onValueChange={onValueChange}
+    >
+      {options.map(option => {
+        return (
+          <ToggleGroup.Item
+            className="tl-toggle-group-input-button"
+            key={option.value}
+            value={option.value}
+          >
+            <TablerIcon name={option.icon} />
+          </ToggleGroup.Item>
+        )
+      })}
+    </ToggleGroup.Root>
+  )
+}

+ 17 - 0
tldraw/apps/tldraw-logseq/src/components/inputs/ToggleInput.tsx

@@ -0,0 +1,17 @@
+import * as Toggle from '@radix-ui/react-toggle'
+
+interface ToggleInputProps extends React.HTMLAttributes<HTMLElement> {
+  pressed: boolean
+  onPressedChange: (value: boolean) => void
+}
+
+export function ToggleInput({ pressed, onPressedChange, className, ...rest }: ToggleInputProps) {
+  return (
+    <Toggle.Root
+      {...rest}
+      className={'tl-toggle-input' + (className ? ' ' + className : '')}
+      pressed={pressed}
+      onPressedChange={onPressedChange}
+    ></Toggle.Root>
+  )
+}

+ 0 - 1
tldraw/apps/tldraw-logseq/src/hooks/usePaste.ts

@@ -203,7 +203,6 @@ export function usePaste(context: LogseqContextValue) {
             const youtubeRegex = /^(?:https?:\/\/)?(?:www\.)?(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))((\w|-){11})(?:\S+)?$/
             return youtubeRegex.test(url)
           }
-          console.log(rawText)
           if (isYoutubeUrl(rawText)) {
             shapesToCreate.push({
               ...YouTubeShape.defaultProps,

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

@@ -1,5 +1,4 @@
 /* eslint-disable @typescript-eslint/no-explicit-any */
-import * as React from 'react'
 import { SVGContainer, TLComponentProps } from '@tldraw/react'
 import { TLBoxShape, TLBoxShapeProps } from '@tldraw/core'
 import { observer } from 'mobx-react-lite'
@@ -20,9 +19,11 @@ export class BoxShape extends TLBoxShape<BoxShapeProps> {
     type: 'box',
     point: [0, 0],
     size: [100, 100],
-    borderRadius: 0,
+    borderRadius: 2,
     stroke: '#000000',
     fill: '#ffffff',
+    noFill: false,
+    strokeType: 'line',
     strokeWidth: 2,
     opacity: 1,
   }
@@ -33,7 +34,9 @@ export class BoxShape extends TLBoxShape<BoxShapeProps> {
         size: [w, h],
         stroke,
         fill,
+        noFill,
         strokeWidth,
+        strokeType,
         borderRadius,
         opacity,
       },
@@ -43,7 +46,7 @@ export class BoxShape extends TLBoxShape<BoxShapeProps> {
       <SVGContainer {...events} opacity={isErasing ? 0.2 : opacity}>
         {isBinding && <BindingIndicator strokeWidth={strokeWidth} size={[w, h]} />}
         <rect
-          className={isSelected || fill !== 'transparent' ? 'tl-hitarea-fill' : 'tl-hitarea-stroke'}
+          className={isSelected || !noFill ? 'tl-hitarea-fill' : 'tl-hitarea-stroke'}
           x={strokeWidth / 2}
           y={strokeWidth / 2}
           rx={borderRadius}
@@ -60,8 +63,9 @@ export class BoxShape extends TLBoxShape<BoxShapeProps> {
           width={Math.max(0.01, w - strokeWidth)}
           height={Math.max(0.01, h - strokeWidth)}
           strokeWidth={strokeWidth}
-          stroke={stroke}
-          fill={fill}
+          stroke={noFill ? fill : stroke}
+          strokeDasharray={strokeType === 'dashed' ? '8 2' : undefined}
+          fill={noFill ? 'none' : fill}
         />
       </SVGContainer>
     )

+ 2 - 0
tldraw/apps/tldraw-logseq/src/lib/shapes/DotShape.tsx

@@ -19,6 +19,8 @@ export class DotShape extends TLDotShape<DotShapeProps> {
     radius: 4,
     stroke: '#000000',
     fill: '#ffffff',
+    noFill: false,
+    strokeType: 'line',
     strokeWidth: 2,
     opacity: 1,
   }

+ 45 - 3
tldraw/apps/tldraw-logseq/src/lib/shapes/EllipseShape.tsx

@@ -21,6 +21,8 @@ export class EllipseShape extends TLEllipseShape<EllipseShapeProps> {
     size: [100, 100],
     stroke: '#000000',
     fill: '#ffffff',
+    noFill: false,
+    strokeType: 'line',
     strokeWidth: 2,
     opacity: 1,
   }
@@ -30,13 +32,15 @@ export class EllipseShape extends TLEllipseShape<EllipseShapeProps> {
       size: [w, h],
       stroke,
       fill,
+      noFill,
       strokeWidth,
+      strokeType,
       opacity,
     } = this.props
     return (
       <SVGContainer {...events} opacity={isErasing ? 0.2 : opacity}>
         <ellipse
-          className={isSelected || fill !== 'transparent' ? 'tl-hitarea-fill' : 'tl-hitarea-stroke'}
+          className={isSelected || !noFill ? 'tl-hitarea-fill' : 'tl-hitarea-stroke'}
           cx={w / 2}
           cy={h / 2}
           rx={Math.max(0.01, (w - strokeWidth) / 2)}
@@ -48,8 +52,9 @@ export class EllipseShape extends TLEllipseShape<EllipseShapeProps> {
           rx={Math.max(0.01, (w - strokeWidth) / 2)}
           ry={Math.max(0.01, (h - strokeWidth) / 2)}
           strokeWidth={strokeWidth}
-          stroke={stroke}
-          fill={fill}
+          stroke={noFill ? fill : stroke}
+          strokeDasharray={strokeType === 'dashed' ? '8 2' : undefined}
+          fill={noFill ? 'none' : fill}
         />
       </SVGContainer>
     )
@@ -71,4 +76,41 @@ export class EllipseShape extends TLEllipseShape<EllipseShapeProps> {
     }
     return withClampedStyles(props)
   }
+
+  /**
+   * 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(opts: any) {
+    const {
+      size: [w, h],
+      stroke,
+      fill,
+      noFill,
+      strokeWidth,
+      strokeType,
+      opacity,
+    } = this.props
+    return (
+      <g opacity={opacity}>
+        <ellipse
+          className={!noFill ? 'tl-hitarea-fill' : 'tl-hitarea-stroke'}
+          cx={w / 2}
+          cy={h / 2}
+          rx={Math.max(0.01, (w - strokeWidth) / 2)}
+          ry={Math.max(0.01, (h - strokeWidth) / 2)}
+        />
+        <ellipse
+          cx={w / 2}
+          cy={h / 2}
+          rx={Math.max(0.01, (w - strokeWidth) / 2)}
+          ry={Math.max(0.01, (h - strokeWidth) / 2)}
+          strokeWidth={strokeWidth}
+          stroke={noFill ? fill : stroke}
+          strokeDasharray={strokeType === 'dashed' ? '8 2' : undefined}
+          fill={noFill ? 'none' : fill}
+        />
+      </g>
+    )
+  }
 }

+ 6 - 10
tldraw/apps/tldraw-logseq/src/lib/shapes/HTMLShape.tsx

@@ -1,13 +1,13 @@
 /* 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 { observer } from 'mobx-react-lite'
-import { CustomStyleProps, withClampedStyles } from './style-props'
+import * as React from 'react'
 import { useCameraMovingRef } from '~hooks/useCameraMoving'
 import type { Shape } from '~lib'
+import { withClampedStyles } from './style-props'
 
-export interface HTMLShapeProps extends TLBoxShapeProps, CustomStyleProps {
+export interface HTMLShapeProps extends TLBoxShapeProps {
   type: 'html'
   html: string
 }
@@ -21,10 +21,6 @@ export class HTMLShape extends TLBoxShape<HTMLShapeProps> {
     parentId: 'page',
     point: [0, 0],
     size: [600, 0],
-    stroke: '#000000',
-    fill: '#ffffff',
-    strokeWidth: 2,
-    opacity: 1,
     html: '',
   }
 
@@ -35,7 +31,7 @@ export class HTMLShape extends TLBoxShape<HTMLShapeProps> {
 
   ReactComponent = observer(({ events, isErasing, isEditing }: TLComponentProps) => {
     const {
-      props: { opacity, html },
+      props: { html },
     } = this
     const isMoving = useCameraMovingRef()
     const app = useApp<Shape>()
@@ -58,7 +54,7 @@ export class HTMLShape extends TLBoxShape<HTMLShapeProps> {
     React.useEffect(() => {
       if (this.props.size[1] === 0 && anchorRef.current) {
         this.update({
-          size: [this.props.size[0], anchorRef.current.offsetHeight],
+          size: [this.props.size[0], anchorRef.current.offsetHeight || 400],
         })
         app.persist(true)
       }
@@ -69,7 +65,7 @@ export class HTMLShape extends TLBoxShape<HTMLShapeProps> {
         style={{
           overflow: 'hidden',
           pointerEvents: 'all',
-          opacity: isErasing ? 0.2 : opacity,
+          opacity: isErasing ? 0.2 : 1,
         }}
         {...events}
       >

+ 3 - 1
tldraw/apps/tldraw-logseq/src/lib/shapes/HighlighterShape.tsx

@@ -26,7 +26,9 @@ export class HighlighterShape extends TLDrawShape<HighlighterShapeProps> {
     points: [],
     isComplete: false,
     stroke: '#ffcc00',
-    fill: '#ffffff',
+    fill: '#ffcc00',
+    noFill: true,
+    strokeType: 'line',
     strokeWidth: 2,
     opacity: 1,
   }

+ 1 - 5
tldraw/apps/tldraw-logseq/src/lib/shapes/ImageShape.tsx

@@ -3,10 +3,9 @@ import * as React from 'react'
 import { HTMLContainer, TLComponentProps } from '@tldraw/react'
 import { TLAsset, TLImageShape, TLImageShapeProps } from '@tldraw/core'
 import { observer } from 'mobx-react-lite'
-import type { CustomStyleProps } from './style-props'
 import { LogseqContext } from '~lib/logseq-context'
 
-export interface ImageShapeProps extends TLImageShapeProps, CustomStyleProps {
+export interface ImageShapeProps extends TLImageShapeProps {
   type: 'image'
   assetId: string
   opacity: number
@@ -21,9 +20,6 @@ export class ImageShape extends TLImageShape<ImageShapeProps> {
     type: 'image',
     point: [0, 0],
     size: [100, 100],
-    stroke: '#000000',
-    fill: '#ffffff',
-    strokeWidth: 2,
     opacity: 1,
     assetId: '',
     clipping: 0,

+ 4 - 0
tldraw/apps/tldraw-logseq/src/lib/shapes/LineShape.tsx

@@ -32,6 +32,8 @@ export class LineShape extends TLLineShape<LineShapeProps> {
     },
     stroke: 'var(--ls-primary-text-color, #000)',
     fill: '#ffffff',
+    noFill: false,
+    strokeType: 'line',
     strokeWidth: 1,
     opacity: 1,
     decorations: {
@@ -151,6 +153,7 @@ export class LineShape extends TLLineShape<LineShapeProps> {
       stroke,
       fill,
       strokeWidth,
+      strokeType,
       decorations,
       label,
       handles: { start, end },
@@ -163,6 +166,7 @@ export class LineShape extends TLLineShape<LineShapeProps> {
             stroke,
             fill,
             strokeWidth,
+            strokeType
           }}
           start={start.point}
           end={end.point}

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

@@ -1,5 +1,5 @@
 /* eslint-disable @typescript-eslint/no-explicit-any */
-import { TLBoxShape, TLBoxShapeProps, TLResizeInfo, validUUID } from '@tldraw/core'
+import { delay, TLBoxShape, TLBoxShapeProps, TLResizeInfo, validUUID } from '@tldraw/core'
 import { HTMLContainer, TLComponentProps, useApp } from '@tldraw/react'
 import Vec from '@tldraw/vec'
 import { action, computed, makeObservable } from 'mobx'
@@ -131,7 +131,9 @@ export class LogseqPortalShape extends TLBoxShape<LogseqPortalShapeProps> {
     collapsedHeight: 0,
     stroke: 'var(--ls-primary-text-color)',
     fill: 'var(--ls-secondary-background-color)',
+    noFill: false,
     strokeWidth: 2,
+    strokeType: 'line',
     opacity: 1,
     pageId: '',
     collapsed: false,
@@ -190,7 +192,6 @@ export class LogseqPortalShape extends TLBoxShape<LogseqPortalShapeProps> {
         size: [this.props.size[0], collapsed ? HEADER_HEIGHT : this.props.collapsedHeight],
         collapsedHeight: collapsed ? originalHeight : this.props.collapsedHeight,
       })
-      this.persist?.()
     }
   }
 
@@ -198,7 +199,7 @@ export class LogseqPortalShape extends TLBoxShape<LogseqPortalShapeProps> {
     return this.props.scaleLevel ?? 'md'
   }
 
-  @action setScaleLevel = (v?: SizeLevel) => {
+  @action setScaleLevel = async (v?: SizeLevel) => {
     const newSize = Vec.mul(
       this.props.size,
       levelToScale[(v as SizeLevel) ?? 'md'] / levelToScale[this.props.scaleLevel ?? 'md']
@@ -206,11 +207,9 @@ export class LogseqPortalShape extends TLBoxShape<LogseqPortalShapeProps> {
     this.update({
       scaleLevel: v,
     })
-    setTimeout(() => {
-      this.update({
-        size: newSize,
-      })
-      this.persist?.()
+    await delay()
+    this.update({
+      size: newSize,
     })
   }
 
@@ -312,7 +311,7 @@ export class LogseqPortalShape extends TLBoxShape<LogseqPortalShapeProps> {
         finishCreating(uuid)
         // wait until the editor is mounted
         setTimeout(() => {
-          app.setEditingShape(this)
+          app.api.editShape(this)
           window.logseq?.api?.edit_block?.(uuid)
         })
       }

+ 2 - 0
tldraw/apps/tldraw-logseq/src/lib/shapes/PenShape.tsx

@@ -28,6 +28,8 @@ export class PenShape extends TLDrawShape<PenShapeProps> {
     isComplete: false,
     stroke: '#000000',
     fill: '#ffffff',
+    noFill: false,
+    strokeType: 'line',
     strokeWidth: 2,
     opacity: 1,
   }

+ 5 - 2
tldraw/apps/tldraw-logseq/src/lib/shapes/PencilShape.tsx

@@ -26,7 +26,9 @@ export class PencilShape extends TLDrawShape<PencilShapeProps> {
     points: [],
     isComplete: false,
     stroke: 'var(--tl-foreground, #000)',
-    fill: '#ffffff',
+    fill: 'var(--tl-foreground, #000)',
+    noFill: true,
+    strokeType: 'line',
     strokeWidth: 2,
     opacity: 1,
   }
@@ -69,7 +71,7 @@ export class PencilShape extends TLDrawShape<PencilShapeProps> {
   getShapeSVGJsx() {
     const {
       pointsPath,
-      props: { stroke, strokeWidth },
+      props: { stroke, noFill, strokeWidth, strokeType },
     } = this
     return (
       <path
@@ -78,6 +80,7 @@ export class PencilShape extends TLDrawShape<PencilShapeProps> {
         stroke={stroke}
         fill={stroke}
         pointerEvents="all"
+        strokeDasharray={strokeType === 'dashed' ? '12 4' : undefined}
       />
     )
   }

+ 38 - 6
tldraw/apps/tldraw-logseq/src/lib/shapes/PolygonShape.tsx

@@ -23,6 +23,8 @@ export class PolygonShape extends TLPolygonShape<PolygonShapeProps> {
     isFlippedY: false,
     stroke: '#000000',
     fill: '#ffffff',
+    noFill: false,
+    strokeType: 'line',
     strokeWidth: 2,
     opacity: 1,
   }
@@ -30,24 +32,25 @@ export class PolygonShape extends TLPolygonShape<PolygonShapeProps> {
   ReactComponent = observer(({ events, isErasing, isSelected }: TLComponentProps) => {
     const {
       offset: [x, y],
-      props: { stroke, fill, strokeWidth, opacity },
+      props: { stroke, fill, noFill, strokeWidth, opacity, strokeType },
     } = this
     const path = this.getVertices(strokeWidth / 2).join()
     return (
       <SVGContainer {...events} opacity={isErasing ? 0.2 : opacity}>
         <g transform={`translate(${x}, ${y})`}>
           <polygon
-            className={
-              isSelected || fill !== 'transparent' ? 'tl-hitarea-fill' : 'tl-hitarea-stroke'
-            }
+            className={isSelected || !noFill ? 'tl-hitarea-fill' : 'tl-hitarea-stroke'}
             points={path}
           />
           <polygon
             points={path}
-            stroke={stroke}
-            fill={fill}
+            stroke={noFill ? fill : stroke}
+            fill={noFill ? 'none' : fill}
             strokeWidth={strokeWidth}
+            rx={2}
+            ry={2}
             strokeLinejoin="round"
+            strokeDasharray={strokeType === 'dashed' ? '8 2' : undefined}
           />
         </g>
       </SVGContainer>
@@ -71,4 +74,33 @@ export class PolygonShape extends TLPolygonShape<PolygonShapeProps> {
     if (props.sides !== undefined) props.sides = Math.max(props.sides, 3)
     return withClampedStyles(props)
   }
+
+  /**
+   * 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(opts: any) {
+    // Do not need to consider the original point here
+    const {
+      offset: [x, y],
+      props: { stroke, fill, noFill, strokeWidth, opacity, strokeType },
+    } = this
+    const path = this.getVertices(strokeWidth / 2).join()
+
+    return (
+      <g transform={`translate(${x}, ${y})`} opacity={opacity}>
+        <polygon className={!noFill ? 'tl-hitarea-fill' : 'tl-hitarea-stroke'} points={path} />
+        <polygon
+          points={path}
+          stroke={noFill ? fill : stroke}
+          fill={noFill ? 'none' : fill}
+          strokeWidth={strokeWidth}
+          rx={2}
+          ry={2}
+          strokeLinejoin="round"
+          strokeDasharray={strokeType === 'dashed' ? '8 2' : undefined}
+        />
+      </g>
+    )
+  }
 }

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

@@ -34,6 +34,8 @@ export class TextShape extends TLTextShape<TextShapeProps> {
     borderRadius: 0,
     stroke: 'var(--tl-foreground, #000)',
     fill: '#ffffff',
+    noFill: false,
+    strokeType: 'line',
     strokeWidth: 2,
     opacity: 1,
   }

+ 3 - 7
tldraw/apps/tldraw-logseq/src/lib/shapes/VideoShape.tsx

@@ -1,14 +1,13 @@
 /* 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 { TLAsset, TLBoxShape, TLBoxShapeProps, TLImageShape, TLImageShapeProps } from '@tldraw/core'
 import { observer } from 'mobx-react-lite'
-import type { CustomStyleProps } from './style-props'
+import * as React from 'react'
 import { useCameraMovingRef } from '~hooks/useCameraMoving'
 import type { Shape } from '~lib'
 import { LogseqContext } from '~lib/logseq-context'
 
-export interface VideoShapeProps extends TLBoxShapeProps, CustomStyleProps {
+export interface VideoShapeProps extends TLBoxShapeProps {
   type: 'video'
   assetId: string
   opacity: number
@@ -23,9 +22,6 @@ export class VideoShape extends TLBoxShape<VideoShapeProps> {
     type: 'video',
     point: [0, 0],
     size: [100, 100],
-    stroke: '#000000',
-    fill: '#ffffff',
-    strokeWidth: 2,
     opacity: 1,
     assetId: '',
     clipping: 0,

+ 5 - 14
tldraw/apps/tldraw-logseq/src/lib/shapes/YouTubeShape.tsx

@@ -1,13 +1,11 @@
 /* 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 { observer } from 'mobx-react-lite'
-import { CustomStyleProps, withClampedStyles } from './style-props'
-import { TextInput } from '~components/inputs/TextInput'
+import { HTMLContainer, TLComponentProps } from '@tldraw/react'
 import { action, computed } from 'mobx'
+import { observer } from 'mobx-react-lite'
+import { withClampedStyles } from './style-props'
 
-export interface YouTubeShapeProps extends TLBoxShapeProps, CustomStyleProps {
+export interface YouTubeShapeProps extends TLBoxShapeProps {
   type: 'youtube'
   url: string
 }
@@ -21,10 +19,6 @@ export class YouTubeShape extends TLBoxShape<YouTubeShapeProps> {
     parentId: 'page',
     point: [0, 0],
     size: [600, 320],
-    stroke: '#000000',
-    fill: '#ffffff',
-    strokeWidth: 2,
-    opacity: 1,
     url: '',
   }
 
@@ -50,15 +44,12 @@ export class YouTubeShape extends TLBoxShape<YouTubeShapeProps> {
   }
 
   ReactComponent = observer(({ events, isErasing, isEditing }: TLComponentProps) => {
-    const {
-      props: { opacity },
-    } = this
     return (
       <HTMLContainer
         style={{
           overflow: 'hidden',
           pointerEvents: 'all',
-          opacity: isErasing ? 0.2 : opacity,
+          opacity: isErasing ? 0.2 : 1,
         }}
         {...events}
       >

+ 2 - 1
tldraw/apps/tldraw-logseq/src/lib/shapes/arrow/Arrow.tsx

@@ -7,6 +7,7 @@ import { getStraightArrowHeadPoints } from './arrowHelpers'
 interface ShapeStyles {
   stroke: string
   strokeWidth: number
+  strokeType: 'line' | 'dashed'
   fill: string
 }
 
@@ -44,11 +45,11 @@ export const Arrow = React.memo(function StraightArrow({
       <path className="tl-stroke-hitarea" d={path} />
       <path
         d={path}
-        fill={style.stroke}
         strokeWidth={sw}
         stroke={style.stroke}
         strokeLinecap="round"
         strokeLinejoin="round"
+        strokeDasharray={style.strokeType === 'dashed' ? '8 4' : undefined}
         pointerEvents="stroke"
       />
       {startArrowHead && (

+ 2 - 12
tldraw/apps/tldraw-logseq/src/lib/shapes/style-props.tsx

@@ -1,22 +1,12 @@
 export interface CustomStyleProps {
   stroke: string
   fill: string
+  noFill: boolean
   strokeWidth: number
+  strokeType: 'dashed' | 'line'
   opacity: number
 }
 
-export function withDefaultStyles<P>(props: P & Partial<CustomStyleProps>): P & CustomStyleProps {
-  return Object.assign(
-    {
-      stroke: '#000000',
-      fill: '#ffffff',
-      strokeWidth: 2,
-      opacity: 1,
-    },
-    props
-  )
-}
-
 export function withClampedStyles<P>(props: P & Partial<CustomStyleProps>) {
   if (props.strokeWidth !== undefined) props.strokeWidth = Math.max(props.strokeWidth, 1)
   if (props.opacity !== undefined) props.opacity = Math.min(1, Math.max(props.opacity, 0))

+ 33 - 3
tldraw/apps/tldraw-logseq/src/styles.css

@@ -135,15 +135,15 @@
 
   .color-input-wrapper {
     overflow: hidden;
-    height: 18px;
-    width: 46px;
+    height: 20px;
+    width: 20px;
     border-radius: 2px;
     margin: 2px;
     box-shadow: 0 0 0 2px var(--ls-tertiary-background-color);
   }
 
   .color-input {
-    transform: translate(-4px, -4px) scale(1.5);
+    transform: translate(-50%, -50%) scale(4);
   }
 
   .switch-input-root {
@@ -734,6 +734,22 @@ html[data-theme='dark'] {
   }
 }
 
+.tl-toggle-input {
+  @apply inline-flex items-center justify-center;
+  height: 32px;
+  width: 32px;
+  color: var(--ls-secondary-text-color);
+  opacity: 0.3;
+  &:hover {
+    background-color: var(--ls-tertiary-background-color);
+  }
+  &[data-state='on'] {
+    background-color: var(--ls-tertiary-background-color);
+    color: var(--ls-primary-text-color);
+    opacity: 1;
+  }
+}
+
 .tl-contextbar-button {
   @apply rounded inline-flex items-center justify-center;
   height: 32px;
@@ -755,3 +771,17 @@ html[data-theme='dark'] {
   box-shadow: 0 0 0 1px var(--ls-secondary-border-color);
   padding: 4px 14px;
 }
+
+.tl-hitarea-stroke {
+  fill: none;
+  stroke: transparent;
+  pointer-events: stroke;
+  stroke-width: min(100px, calc(24px * var(--tl-scale)));
+}
+
+.tl-hitarea-fill {
+  fill: transparent;
+  stroke: transparent;
+  pointer-events: all;
+  stroke-width: min(100px, calc(24px * var(--tl-scale)));
+}

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

@@ -20,6 +20,11 @@ export class TLApi<S extends TLShape = TLShape, K extends TLEventMap = TLEventMa
     return this
   }
 
+  editShape = (shape: string | S | undefined): this => {
+    this.app.transition('select').selectedTool.transition('editingShape', { shape })
+    return this
+  }
+
   /**
    * Set the hovered shape.
    *

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

@@ -425,7 +425,7 @@ export class TLApp<
         // pasting into other whiteboard may require this if any shape uses asset
         assets: this.getCleanUpAssets().filter(asset => {
           return this.selectedShapesArray.some(shape => shape.props.assetId === asset.id)
-        })
+        }),
       })
       navigator.clipboard.write([
         new ClipboardItem({

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

@@ -354,12 +354,14 @@ export abstract class TLShape<P extends TLShapeProps = TLShapeProps, M = any> {
   getShapeSVGJsx(opts: any) {
     // Do not need to consider the original point here
     const bounds = this.getBounds()
-    const { stroke, strokeWidth, opacity, fill, borderRadius } = this.props as any
+    const { stroke, strokeWidth, strokeType, opacity, fill, noFill, borderRadius } = this
+      .props as any
     return (
       <rect
-        fill={fill}
-        stroke={stroke}
+        fill={noFill ? 'none' : fill}
+        stroke={noFill ? fill : stroke}
         strokeWidth={strokeWidth ?? 2}
+        strokeDasharray={strokeType === 'dashed' ? '8 2' : undefined}
         fillOpacity={opacity ?? 0.2}
         width={bounds.width}
         height={bounds.height}

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

@@ -44,10 +44,15 @@ export function throttle<T extends (...args: any) => any>(
   }
 }
 
-export function debounce<T extends (...args: any[]) => void>(fn: T, ms = 0) {
+export function debounce<T extends (...args: any[]) => void>(
+  fn: T,
+  ms = 0,
+  immediateFn: T | undefined = undefined
+) {
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
   let timeoutId: number | any
   return function (...args: Parameters<T>) {
+    immediateFn?.(...args)
     clearTimeout(timeoutId)
     timeoutId = setTimeout(() => fn.apply(args), ms)
   }
@@ -75,3 +80,7 @@ export function modKey(e: any): boolean {
 export function isNonNullable<TValue>(value: TValue): value is NonNullable<TValue> {
   return Boolean(value)
 }
+
+export function delay(ms: number = 0) {
+  return new Promise(resolve => setTimeout(resolve, ms))
+}

+ 0 - 14
tldraw/packages/react/src/hooks/useStylesheet.ts

@@ -418,20 +418,6 @@ const tlcss = css`
     color: var(--tl-background);
   }
 
-  .tl-hitarea-stroke {
-    fill: none;
-    stroke: transparent;
-    pointer-events: stroke;
-    stroke-width: min(100px, calc(24px * var(--tl-scale)));
-  }
-
-  .tl-hitarea-fill {
-    fill: transparent;
-    stroke: transparent;
-    pointer-events: all;
-    stroke-width: min(100px, calc(24px * var(--tl-scale)));
-  }
-
   .tl-grid {
     position: absolute;
     width: 100%;

+ 9 - 2
tldraw/yarn.lock

@@ -358,7 +358,7 @@
     "@babel/plugin-syntax-jsx" "^7.18.6"
     "@babel/types" "^7.18.10"
 
-"@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.9.2":
+"@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.17.8", "@babel/runtime@^7.9.2":
   version "7.18.9"
   resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.9.tgz#b4fcfce55db3d2e5e080d2490f608a3b9f407f4a"
   integrity sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==
@@ -2053,7 +2053,7 @@
     "@radix-ui/react-toggle" "1.0.0"
     "@radix-ui/react-use-controllable-state" "1.0.0"
 
-"@radix-ui/[email protected]":
+"@radix-ui/[email protected]", "@radix-ui/react-toggle@^1.0.0":
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/@radix-ui/react-toggle/-/react-toggle-1.0.0.tgz#9c8f655497b26410766b9c01aa2954159c0cb60b"
   integrity sha512-RvY06eyDlZMC4rZdWK8jNovEDKf2jBvYFOB4rkQ/ypMOjFQuoh2QodlxlGakrZDrLnfxzyNnn/pg88CWVtAAdw==
@@ -7492,6 +7492,13 @@ pkg-dir@^4.2.0:
   dependencies:
     find-up "^4.0.0"
 
+polished@^4.0.0:
+  version "4.2.2"
+  resolved "https://registry.yarnpkg.com/polished/-/polished-4.2.2.tgz#2529bb7c3198945373c52e34618c8fe7b1aa84d1"
+  integrity sha512-Sz2Lkdxz6F2Pgnpi9U5Ng/WdWAUZxmHrNPoVlm3aAemxoy2Qy7LGjQg4uf8qKelDAUW94F4np3iH2YPf2qefcQ==
+  dependencies:
+    "@babel/runtime" "^7.17.8"
+
 postcss-import@^14.1.0:
   version "14.1.0"
   resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-14.1.0.tgz#a7333ffe32f0b8795303ee9e40215dac922781f0"