|  | @@ -1,4 +1,11 @@
 | 
	
		
			
				|  |  | -import { fileToBase64, getSizeFromSrc, TLAsset, uniqueId } from '@tldraw/core'
 | 
	
		
			
				|  |  | +import {
 | 
	
		
			
				|  |  | +  BoundsUtils,
 | 
	
		
			
				|  |  | +  fileToBase64,
 | 
	
		
			
				|  |  | +  getSizeFromSrc,
 | 
	
		
			
				|  |  | +  TLAsset,
 | 
	
		
			
				|  |  | +  TLShapeModel,
 | 
	
		
			
				|  |  | +  uniqueId,
 | 
	
		
			
				|  |  | +} from '@tldraw/core'
 | 
	
		
			
				|  |  |  import type { TLReactCallbacks } from '@tldraw/react'
 | 
	
		
			
				|  |  |  import * as React from 'react'
 | 
	
		
			
				|  |  |  import type { Shape } from '~lib'
 | 
	
	
		
			
				|  | @@ -10,37 +17,82 @@ export function usePaste() {
 | 
	
		
			
				|  |  |        size: number[]
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    // TODO: supporting other pasting formats
 | 
	
		
			
				|  |  |      const assetsToCreate: ImageAsset[] = []
 | 
	
		
			
				|  |  | +    const shapesToCreate: TLShapeModel[] = []
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    async function handleImage(item: ClipboardItem) {
 | 
	
		
			
				|  |  | +      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') return false
 | 
	
		
			
				|  |  | +        const existingAsset = Object.values(app.assets).find(asset => asset.src === dataurl)
 | 
	
		
			
				|  |  | +        if (existingAsset) {
 | 
	
		
			
				|  |  | +          assetsToCreate.push(existingAsset as ImageAsset)
 | 
	
		
			
				|  |  | +          return false
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        // Create a new asset for this image
 | 
	
		
			
				|  |  | +        const asset: ImageAsset = {
 | 
	
		
			
				|  |  | +          id: assetId,
 | 
	
		
			
				|  |  | +          type: 'image',
 | 
	
		
			
				|  |  | +          src: dataurl,
 | 
	
		
			
				|  |  | +          size: await getSizeFromSrc(dataurl),
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        assetsToCreate.push(asset)
 | 
	
		
			
				|  |  | +        return true
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +      return false
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    async function handleLogseqShapes(item: ClipboardItem) {
 | 
	
		
			
				|  |  | +      const plainTextType = item.types.find(type => type.startsWith('text/plain'))
 | 
	
		
			
				|  |  | +      if (plainTextType) {
 | 
	
		
			
				|  |  | +        const blob = await item.getType(plainTextType)
 | 
	
		
			
				|  |  | +        const rawText = await blob.text()
 | 
	
		
			
				|  |  | +        const data = JSON.parse(rawText)
 | 
	
		
			
				|  |  | +        if (data.type === 'logseq/whiteboard-shapes') {
 | 
	
		
			
				|  |  | +          const shapes = data.shapes as TLShapeModel[]
 | 
	
		
			
				|  |  | +          const commonBounds = BoundsUtils.getCommonBounds(
 | 
	
		
			
				|  |  | +            shapes.map(shape => ({
 | 
	
		
			
				|  |  | +              minX: shape.point?.[0] ?? point[0],
 | 
	
		
			
				|  |  | +              minY: shape.point?.[1] ?? point[1],
 | 
	
		
			
				|  |  | +              width: shape.size?.[0] ?? 4,
 | 
	
		
			
				|  |  | +              height: shape.size?.[1] ?? 4,
 | 
	
		
			
				|  |  | +              maxX: (shape.point?.[0] ?? point[0]) + (shape.size?.[0] ?? 4),
 | 
	
		
			
				|  |  | +              maxY: (shape.point?.[1] ?? point[1]) + (shape.size?.[1] ?? 4),
 | 
	
		
			
				|  |  | +            }))
 | 
	
		
			
				|  |  | +          )
 | 
	
		
			
				|  |  | +          const clonedShape = data.shapes.map((shape: TLShapeModel) => {
 | 
	
		
			
				|  |  | +            return {
 | 
	
		
			
				|  |  | +              ...shape,
 | 
	
		
			
				|  |  | +              handles: {}, // TODO: may add this later?
 | 
	
		
			
				|  |  | +              id: uniqueId(),
 | 
	
		
			
				|  |  | +              parentId: app.currentPageId,
 | 
	
		
			
				|  |  | +              point: [
 | 
	
		
			
				|  |  | +                point[0] + shape.point![0] - commonBounds.minX,
 | 
	
		
			
				|  |  | +                point[1] + shape.point![1] - commonBounds.minY,
 | 
	
		
			
				|  |  | +              ],
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +          })
 | 
	
		
			
				|  |  | +          shapesToCreate.push(...clonedShape)
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    // TODO: supporting other pasting formats
 | 
	
		
			
				|  |  |      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)
 | 
	
		
			
				|  |  | +        let handled = await handleImage(item)
 | 
	
		
			
				|  |  | +        if (!handled) {
 | 
	
		
			
				|  |  | +          await handleLogseqShapes(item)
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |        } catch (error) {
 | 
	
		
			
				|  |  |          console.error(error)
 | 
	
		
			
				|  |  |        }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    app.createAssets(assetsToCreate)
 | 
	
		
			
				|  |  | -    app.createShapes(
 | 
	
		
			
				|  |  | -      assetsToCreate.map((asset, i) => ({
 | 
	
		
			
				|  |  | +    const allShapesToAdd = [
 | 
	
		
			
				|  |  | +      ...assetsToCreate.map((asset, i) => ({
 | 
	
		
			
				|  |  |          id: uniqueId(),
 | 
	
		
			
				|  |  |          type: 'image',
 | 
	
		
			
				|  |  |          parentId: app.currentPageId,
 | 
	
	
		
			
				|  | @@ -48,7 +100,13 @@ export function usePaste() {
 | 
	
		
			
				|  |  |          size: asset.size,
 | 
	
		
			
				|  |  |          assetId: asset.id,
 | 
	
		
			
				|  |  |          opacity: 1,
 | 
	
		
			
				|  |  | -      }))
 | 
	
		
			
				|  |  | -    )
 | 
	
		
			
				|  |  | +      })),
 | 
	
		
			
				|  |  | +      ...shapesToCreate,
 | 
	
		
			
				|  |  | +    ]
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    app.createAssets(assetsToCreate)
 | 
	
		
			
				|  |  | +    app.createShapes(allShapesToAdd)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    app.setSelectedShapes(allShapesToAdd.map(s => s.id))
 | 
	
		
			
				|  |  |    }, [])
 | 
	
		
			
				|  |  |  }
 |