From bb58902d5b4015ee282bc2664b347c292fe4a503 Mon Sep 17 00:00:00 2001 From: Steve Ruiz Date: Wed, 18 May 2022 22:04:04 +0100 Subject: [PATCH] ok --- apps/www/hooks/useMultiplayerAssets.ts | 34 ++++++++++++++++-- apps/www/hooks/useUploadAssets.ts | 36 +++++++++++++++++++ .../MultiplayerMenu/MultiplayerMenu.tsx | 36 ++++++++++++++++++- packages/tldraw/src/state/TldrawApp.ts | 6 ++++ packages/tldraw/src/types.ts | 2 ++ 5 files changed, 111 insertions(+), 3 deletions(-) create mode 100644 apps/www/hooks/useUploadAssets.ts diff --git a/apps/www/hooks/useMultiplayerAssets.ts b/apps/www/hooks/useMultiplayerAssets.ts index 68bea16d7..14bf9b48e 100644 --- a/apps/www/hooks/useMultiplayerAssets.ts +++ b/apps/www/hooks/useMultiplayerAssets.ts @@ -3,7 +3,37 @@ import { useCallback } from 'react' export function useMultiplayerAssets() { const onAssetCreate = useCallback( - // Send the asset to our upload enpoint, which in turn will send it to AWS and + // Send the asset to our upload endpoint, which in turn will send it to AWS and + // respond with the URL of the uploaded file. + async (app: TldrawApp, file: File, id: string): Promise => { + const filename = encodeURIComponent(file.name) + + const fileType = encodeURIComponent(file.type) + + const res = await fetch(`/api/upload?file=${filename}&fileType=${fileType}`) + + const { url, fields } = await res.json() + + const formData = new FormData() + + Object.entries({ ...fields, file }).forEach(([key, value]) => { + formData.append(key, value as any) + }) + + const upload = await fetch(url, { + method: 'POST', + body: formData, + }) + + if (!upload.ok) return false + + return url + '/' + filename + }, + [] + ) + + const onAssetUpload = useCallback( + // Send the asset to our upload endpoint, which in turn will send it to AWS and // respond with the URL of the uploaded file. async (app: TldrawApp, file: File, id: string): Promise => { const filename = encodeURIComponent(file.name) @@ -37,5 +67,5 @@ export function useMultiplayerAssets() { return true }, []) - return { onAssetCreate, onAssetDelete } + return { onAssetCreate, onAssetUpload, onAssetDelete } } diff --git a/apps/www/hooks/useUploadAssets.ts b/apps/www/hooks/useUploadAssets.ts new file mode 100644 index 000000000..fc9f39543 --- /dev/null +++ b/apps/www/hooks/useUploadAssets.ts @@ -0,0 +1,36 @@ +import { TDAsset, TldrawApp } from '@tldraw/tldraw' +import { useCallback } from 'react' + +export function useUploadAssets() { + const onAssetUpload = useCallback( + // Send the asset to our upload endpoint, which in turn will send it to AWS and + // respond with the URL of the uploaded file. + + async (app: TldrawApp, file: File, id: string): Promise => { + const filename = encodeURIComponent(file.name) + + const fileType = encodeURIComponent(file.type) + + const res = await fetch(`/api/upload?file=${filename}&fileType=${fileType}`) + + const { url, fields } = await res.json() + + const formData = new FormData() + + Object.entries({ ...fields, file }).forEach(([key, value]) => { + formData.append(key, value as any) + }) + + const upload = await fetch(url, { + method: 'POST', + body: formData, + }) + + if (!upload.ok) return false + + return url + '/' + filename + }, + [] + ) + return { onAssetUpload } +} diff --git a/packages/tldraw/src/components/TopPanel/MultiplayerMenu/MultiplayerMenu.tsx b/packages/tldraw/src/components/TopPanel/MultiplayerMenu/MultiplayerMenu.tsx index 304f9ed73..0c61579fc 100644 --- a/packages/tldraw/src/components/TopPanel/MultiplayerMenu/MultiplayerMenu.tsx +++ b/packages/tldraw/src/components/TopPanel/MultiplayerMenu/MultiplayerMenu.tsx @@ -5,7 +5,7 @@ import { useTldrawApp } from '~hooks' import { DMItem, DMContent, DMDivider, DMTriggerIcon } from '~components/Primitives/DropdownMenu' import { SmallIcon } from '~components/Primitives/SmallIcon' import { MultiplayerIcon } from '~components/Primitives/icons' -import type { TDSnapshot } from '~types' +import { TDAssetType, TDSnapshot } from '~types' import { TLDR } from '~state/TLDR' import { Utils } from '@tldraw/core' @@ -43,6 +43,26 @@ export const MultiplayerMenu = React.memo(function MultiplayerMenu() { }, []) const handleCopyToMultiplayerRoom = React.useCallback(async () => { + const nextDocument = { ...app.document } + + if (app.callbacks.onAssetUpload) { + for (const id in nextDocument.assets) { + const asset = nextDocument.assets[id] + if (asset.src.includes('base64')) { + const file = dataURLtoFile( + asset.src, + asset.fileName ?? asset.type === TDAssetType.Video ? 'image.png' : 'image.mp4' + ) + const newSrc = await app.callbacks.onAssetUpload(app, file, id) + if (newSrc) { + asset.src = newSrc + } else { + asset.src = '' + } + } + } + } + const body = JSON.stringify({ roomId: Utils.uniqueId(), pageId: app.currentPageId, @@ -86,3 +106,17 @@ export const MultiplayerMenu = React.memo(function MultiplayerMenu() { ) }) + +function dataURLtoFile(dataurl: string, filename: string) { + const arr = dataurl.split(',') + const mime = arr[0]?.match(/:(.*?);/)?.[1] + const bstr = window.atob(arr[1]) + let n = bstr.length + const u8arr = new Uint8Array(n) + + while (n--) { + u8arr[n] = bstr.charCodeAt(n) + } + + return new File([u8arr], filename, { type: mime }) +} diff --git a/packages/tldraw/src/state/TldrawApp.ts b/packages/tldraw/src/state/TldrawApp.ts index 7effee32f..91e6cf2a0 100644 --- a/packages/tldraw/src/state/TldrawApp.ts +++ b/packages/tldraw/src/state/TldrawApp.ts @@ -162,6 +162,10 @@ export interface TDCallbacks { * (optional) A callback to run when an asset will be created. Should return the value for the image/video's `src` property. */ onAssetCreate?: (app: TldrawApp, file: File, id: string) => Promise + /** + * (optional) A callback to run when an asset will be uploaded. Should return the value for the image/video's `src` property. + */ + onAssetUpload?: (app: TldrawApp, file: File, id: string) => Promise /** * (optional) A callback to run when the user exports their page or selection. */ @@ -3344,6 +3348,7 @@ export class TldrawApp extends StateManager { const shapeType = isImage ? TDShapeType.Image : TDShapeType.Video const assetType = isImage ? TDAssetType.Image : TDAssetType.Video + const fileName = file.name let src: string | ArrayBuffer | null @@ -3393,6 +3398,7 @@ export class TldrawApp extends StateManager { const asset = { id: assetId, type: assetType, + name: fileName, src, size, } diff --git a/packages/tldraw/src/types.ts b/packages/tldraw/src/types.ts index 9d1bba789..223e712f0 100644 --- a/packages/tldraw/src/types.ts +++ b/packages/tldraw/src/types.ts @@ -473,12 +473,14 @@ export enum TDAssetType { export interface TDImageAsset extends TLAsset { type: TDAssetType.Image + fileName: string src: string size: number[] } export interface TDVideoAsset extends TLAsset { type: TDAssetType.Video + fileName: string src: string size: number[] }