Ver código fonte

wip pasting files

Peng Xiao 3 anos atrás
pai
commit
3ebfd5e56f

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

@@ -11,6 +11,7 @@ import * as React from 'react'
 import { AppUI } from '~components/AppUI'
 import { ContextBar } from '~components/ContextBar/ContextBar'
 import { useFileDrop } from '~hooks/useFileDrop'
+import { usePaste } from '~hooks/usePaste'
 import { LogseqContext } from '~lib/logseq-context'
 import { Shape, shapes } from '~lib/shapes'
 import {
@@ -55,6 +56,7 @@ interface LogseqTldrawProps {
 
 export const App = function App(props: LogseqTldrawProps): JSX.Element {
   const onFileDrop = useFileDrop()
+  const onPaste = usePaste()
 
   const Page = React.useMemo(() => React.memo(props.PageComponent), [])
 
@@ -65,6 +67,7 @@ export const App = function App(props: LogseqTldrawProps): JSX.Element {
         Shapes={shapes}
         Tools={tools}
         onFileDrop={onFileDrop}
+        onPaste={onPaste}
         {...props}
       >
         <div className="logseq-tldraw logseq-tldraw-wrapper">

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

@@ -0,0 +1,54 @@
+import { fileToBase64, getSizeFromSrc, TLAsset, uniqueId } from '@tldraw/core'
+import type { TLReactCallbacks } from '@tldraw/react'
+import * as React from 'react'
+import type { Shape } from '~lib'
+
+export function usePaste() {
+  return React.useCallback<TLReactCallbacks<Shape>['onFileDrop']>(async (app, { point }) => {
+    const assetId = uniqueId()
+    interface ImageAsset extends TLAsset {
+      size: number[]
+    }
+
+    // TODO: supporting other pasting formats
+    const assetsToCreate: ImageAsset[] = []
+    for (const item of await navigator.clipboard.read()) {
+      try {
+        const firstImageType = item.types.find(type => type.startsWith('image'))
+        if (firstImageType) {
+          const blob = await item.getType(firstImageType)
+          const dataurl = await fileToBase64(blob)
+          if (typeof dataurl !== 'string') continue
+          const existingAsset = Object.values(app.assets).find(asset => asset.src === dataurl)
+          if (existingAsset) {
+            assetsToCreate.push(existingAsset as ImageAsset)
+            continue
+          }
+          // Create a new asset for this image
+          const asset: ImageAsset = {
+            id: assetId,
+            type: 'image',
+            src: dataurl,
+            size: await getSizeFromSrc(dataurl),
+          }
+          assetsToCreate.push(asset)
+        }
+      } catch (error) {
+        console.error(error)
+      }
+    }
+
+    app.createAssets(assetsToCreate)
+    app.createShapes(
+      assetsToCreate.map((asset, i) => ({
+        id: uniqueId(),
+        type: 'image',
+        parentId: app.currentPageId,
+        point: [point[0] - asset.size[0] / 2 + i * 16, point[1] - asset.size[1] / 2 + i * 16],
+        size: asset.size,
+        assetId: asset.id,
+        opacity: 1,
+      }))
+    )
+  }, [])
+}

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

@@ -346,6 +346,14 @@ export class TLApp<
     return this
   }
 
+  paste = (e?: ClipboardEvent) => {
+    this.notify('paste', {
+      point: this.inputs.currentPoint
+    })
+    // This callback may be over-written manually, see useSetup.ts in React.
+    return void null
+  }
+
   dropFiles = (files: FileList, point?: number[]) => {
     this.notify('drop-files', {
       files: Array.from(files),

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

@@ -156,6 +156,10 @@ export type TLSubscriptionEvent =
       event: 'drop-files'
       info: { files: File[]; point: number[] }
     }
+  | {
+      event: 'paste'
+      info: { point: number[] }
+    }
   | {
       event: 'create-assets'
       info: { assets: TLAsset[] }

+ 6 - 1
tldraw/packages/react/src/hooks/useKeyboardEvents.ts

@@ -1,9 +1,10 @@
 import * as React from 'react'
-import { useRendererContext } from '~hooks'
+import { useApp, useRendererContext } from '~hooks'
 import { TLTargetType } from '@tldraw/core'
 import type { TLReactCustomEvents } from '~types'
 
 export function useKeyboardEvents() {
+  const app = useApp()
   const { callbacks } = useRendererContext()
 
   React.useEffect(() => {
@@ -15,6 +16,10 @@ export function useKeyboardEvents() {
     }
     window.addEventListener('keydown', onKeyDown)
     window.addEventListener('keyup', onKeyUp)
+    document.addEventListener('paste', (e) => {
+      e.preventDefault()
+      app.paste(e)
+    })
     return () => {
       window.removeEventListener('keydown', onKeyDown)
       window.removeEventListener('keyup', onKeyUp)

+ 2 - 0
tldraw/packages/react/src/hooks/useSetup.ts

@@ -19,6 +19,7 @@ export function useSetup<
     onDeleteAssets,
     onDeleteShapes,
     onFileDrop,
+    onPaste
   } = props
 
   React.useLayoutEffect(() => {
@@ -44,6 +45,7 @@ export function useSetup<
     if (onDeleteShapes) unsubs.push(app.subscribe('delete-shapes', onDeleteShapes))
     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))
     // Kind of unusual, is this the right pattern?
 
     return () => unsubs.forEach(unsub => unsub())

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

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