Peng Xiao пре 3 година
родитељ
комит
e07f43e23e

+ 12 - 9
tldraw/apps/tldraw-logseq/src/hooks/usePaste.ts

@@ -10,7 +10,7 @@ import {
 import type { TLReactCallbacks } from '@tldraw/react'
 import * as React from 'react'
 import { NIL as NIL_UUID } from 'uuid'
-import { HTMLShape, LogseqPortalShape, Shape, YouTubeShape } from '~lib'
+import { HTMLShape, LogseqPortalShape, Shape, YouTubeShape, ImageShape, VideoShape } from '~lib'
 import type { LogseqContextValue } from '~lib/logseq-context'
 
 const isValidURL = (url: string) => {
@@ -28,11 +28,11 @@ export function usePaste(context: LogseqContextValue) {
   return React.useCallback<TLReactCallbacks<Shape>['onPaste']>(
     async (app, { point, shiftKey, files }) => {
       const assetId = uniqueId()
-      interface ImageAsset extends TLAsset {
+      interface VideoImageAsset extends TLAsset {
         size: number[]
       }
 
-      const assetsToCreate: ImageAsset[] = []
+      const assetsToCreate: VideoImageAsset[] = []
       const shapesToCreate: Shape['props'][] = []
       const bindingsToCreate: TLBinding[] = []
 
@@ -43,6 +43,7 @@ export function usePaste(context: LogseqContextValue) {
       // TODO: handle PDF?
       async function handleFiles(files: File[]) {
         const IMAGE_EXTENSIONS = ['.png', '.svg', '.jpg', '.jpeg', '.gif']
+        const VIDEO_EXTENSIONS = ['.mp4', '.webm', '.ogg']
 
         for (const file of files) {
           // Get extension, verify that it's an image
@@ -51,9 +52,10 @@ export function usePaste(context: LogseqContextValue) {
             continue
           }
           const extension = extensionMatch[0].toLowerCase()
-          if (!IMAGE_EXTENSIONS.includes(extension)) {
+          if (![...IMAGE_EXTENSIONS, ...VIDEO_EXTENSIONS].includes(extension)) {
             continue
           }
+          const isVideo = VIDEO_EXTENSIONS.includes(extension)
           try {
             // Turn the image into a base64 dataurl
             const dataurl = await createAsset(file)
@@ -63,16 +65,17 @@ export function usePaste(context: LogseqContextValue) {
             // Do we already have an asset for this image?
             const existingAsset = Object.values(app.assets).find(asset => asset.src === dataurl)
             if (existingAsset) {
-              assetsToCreate.push(existingAsset as ImageAsset)
+              assetsToCreate.push(existingAsset as VideoImageAsset)
               continue
             }
             // Create a new asset for this image
-            const asset: ImageAsset = {
+            const asset: VideoImageAsset = {
               id: assetId,
-              type: 'image',
+              type: isVideo ? 'video' : 'image',
               src: dataurl,
-              size: await getSizeFromSrc(handlers.makeAssetUrl(dataurl)),
+              size: await getSizeFromSrc(handlers.makeAssetUrl(dataurl), isVideo),
             }
+            console.log(asset)
             assetsToCreate.push(asset)
           } catch (error) {
             console.error(error)
@@ -277,7 +280,7 @@ export function usePaste(context: LogseqContextValue) {
       const allShapesToAdd: TLShapeModel[] = [
         // assets to images
         ...assetsToCreate.map((asset, i) => ({
-          type: 'image',
+          ...(asset.type === 'video' ? VideoShape : ImageShape).defaultProps,
           // TODO: Should be place near the last edited shape
           point: [point[0] - asset.size[0] / 2 + i * 16, point[1] - asset.size[1] / 2 + i * 16],
           size: asset.size,

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

@@ -79,7 +79,7 @@ export class HTMLShape extends TLBoxShape<HTMLShapeProps> {
           onPointerUp={stop}
           className="tl-html-container"
           style={{
-            pointerEvents: isEditing ? 'all' : 'none',
+            pointerEvents: !isMoving && (isEditing || isSelected) ? 'all' : 'none',
             overflow: isEditing ? 'auto' : 'hidden',
           }}
         >

+ 101 - 0
tldraw/apps/tldraw-logseq/src/lib/shapes/VideoShape.tsx

@@ -0,0 +1,101 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import * as React from 'react'
+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 { useCameraMovingRef } from '~hooks/useCameraMoving'
+import type { Shape } from '~lib'
+import { LogseqContext } from '~lib/logseq-context'
+
+export interface VideoShapeProps extends TLBoxShapeProps, CustomStyleProps {
+  type: 'video'
+  assetId: string
+  opacity: number
+}
+
+export class VideoShape extends TLBoxShape<VideoShapeProps> {
+  static id = 'video'
+
+  static defaultProps: VideoShapeProps = {
+    id: 'video1',
+    parentId: 'page',
+    type: 'video',
+    point: [0, 0],
+    size: [100, 100],
+    stroke: '#000000',
+    fill: '#ffffff',
+    strokeWidth: 2,
+    opacity: 1,
+    assetId: '',
+    clipping: 0,
+    isAspectRatioLocked: true,
+  }
+
+  canFlip = false
+  canEdit = true
+  canChangeAspectRatio = false
+
+  ReactComponent = observer(({ events, isErasing, asset, isEditing }: TLComponentProps) => {
+    const {
+      props: {
+        opacity,
+        size: [w, h],
+      },
+    } = this
+
+    const isMoving = useCameraMovingRef()
+    const app = useApp<Shape>()
+
+    const isSelected = app.selectedIds.has(this.id)
+
+    const tlEventsEnabled =
+      isMoving || (isSelected && !isEditing) || app.selectedTool.id !== 'select'
+    const stop = React.useCallback(
+      e => {
+        if (!tlEventsEnabled) {
+          // TODO: pinching inside Logseq Shape issue
+          e.stopPropagation()
+        }
+      },
+      [tlEventsEnabled]
+    )
+
+    const { handlers } = React.useContext(LogseqContext)
+
+    return (
+      <HTMLContainer
+        style={{
+          overflow: 'hidden',
+          pointerEvents: 'all',
+          opacity: isErasing ? 0.2 : opacity,
+        }}
+        {...events}
+      >
+        <div
+          onWheelCapture={stop}
+          onPointerDown={stop}
+          onPointerUp={stop}
+          className="tl-video-container"
+          style={{
+            pointerEvents: !isMoving && (isEditing || isSelected) ? 'all' : 'none',
+            overflow: isEditing ? 'auto' : 'hidden',
+          }}
+        >
+          {asset && (
+            <video controls src={handlers ? handlers.makeAssetUrl(asset.src) : asset.src} />
+          )}
+        </div>
+      </HTMLContainer>
+    )
+  })
+
+  ReactIndicator = observer(() => {
+    const {
+      props: {
+        size: [w, h],
+      },
+    } = this
+    return <rect width={w} height={h} fill="transparent" />
+  })
+}

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

@@ -5,6 +5,7 @@ import { EllipseShape } from './EllipseShape'
 import { HighlighterShape } from './HighlighterShape'
 import { HTMLShape } from './HTMLShape'
 import { ImageShape } from './ImageShape'
+import { VideoShape } from './VideoShape'
 import { LineShape } from './LineShape'
 import { LogseqPortalShape } from './LogseqPortalShape'
 import { PencilShape } from './PencilShape'
@@ -18,7 +19,7 @@ export type Shape =
   | EllipseShape
   | HighlighterShape
   | ImageShape
-  | LineShape
+  | VideoShape
   | LineShape
   | PencilShape
   | PolygonShape
@@ -33,6 +34,7 @@ export * from './EllipseShape'
 export * from './HighlighterShape'
 export * from './HTMLShape'
 export * from './ImageShape'
+export * from './VideoShape'
 export * from './LineShape'
 export * from './LogseqPortalShape'
 export * from './PencilShape'
@@ -40,12 +42,14 @@ export * from './PolygonShape'
 export * from './TextShape'
 export * from './YouTubeShape'
 
+
 export const shapes: TLReactShapeConstructor<Shape>[] = [
   BoxShape,
   DotShape,
   EllipseShape,
   HighlighterShape,
   ImageShape,
+  VideoShape,
   LineShape,
   PencilShape,
   PolygonShape,

+ 10 - 2
tldraw/apps/tldraw-logseq/src/styles.css

@@ -170,7 +170,7 @@
       transform: translateX(3px);
       will-change: transform;
     }
-  
+
     &[data-state='checked'] .switch-input-thumb {
       transform: translateX(17px);
     }
@@ -581,7 +581,7 @@
 }
 
 .tl-html-container {
-  @apply h-full w-full m-0 relative flex;
+  @apply h-full w-full m-0 relative;
   user-select: text;
 
   > iframe {
@@ -590,6 +590,14 @@
   }
 }
 
+.tl-video-container {
+  @apply h-full w-full m-0 relative;
+
+  > video {
+    @apply h-full w-full m-0 relative;
+  }
+}
+
 .tl-logseq-cp-container {
   @apply h-full w-full rounded-lg;
 

+ 1 - 5
tldraw/demo/src/App.jsx

@@ -136,10 +136,6 @@ const searchHandler = q => {
   })
 }
 
-const saveAssets = async files => {
-  return Promise.all(files.map(fileToBase64))
-}
-
 export default function App() {
   const [theme, setTheme] = React.useState('light')
 
@@ -158,7 +154,7 @@ export default function App() {
           addNewBlock: () => uniqueId(),
           queryBlockByUUID: uuid => ({ uuid, content: 'some random content' }),
           isWhiteboardPage: () => false,
-          saveAssets,
+          saveAsset: fileToBase64,
           makeAssetUrl: a => a,
         }}
         model={documentModel}

+ 1 - 1
tldraw/packages/core/src/types/types.ts

@@ -105,7 +105,7 @@ export interface TLOffset {
 
 export interface TLAsset {
   id: string
-  type: any
+  type: string
   src: string
 }
 

+ 24 - 4
tldraw/packages/core/src/utils/DataUtils.ts

@@ -76,11 +76,31 @@ export function fileToBase64(file: Blob): Promise<string | ArrayBuffer | null> {
   })
 }
 
-export function getSizeFromSrc(dataURL: string): Promise<number[]> {
+export function getSizeFromSrc(dataURL: string, isVideo: boolean): Promise<number[]> {
   return new Promise(resolve => {
-    const img = new Image()
-    img.onload = () => resolve([img.width, img.height])
-    img.src = dataURL
+    if (isVideo) {
+      const video = document.createElement('video')
+
+      // place a listener on it
+      video.addEventListener(
+        'loadedmetadata',
+        function () {
+          // retrieve dimensions
+          const height = this.videoHeight
+          const width = this.videoWidth
+
+          // send back result
+          resolve([width, height])
+        },
+        false
+      )
+      // start download meta-datas
+      video.src = dataURL
+    } else {
+      const img = new Image()
+      img.onload = () => resolve([img.width, img.height])
+      img.src = dataURL
+    }
   })
 }