[improvement] copy assets when copying to multiplayer room (#694)

This commit is contained in:
Steve Ruiz 2022-05-18 21:46:24 +01:00 committed by GitHub
parent 13f5787c31
commit cacb4b7827
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 122 additions and 15 deletions

View file

@ -1,5 +1,6 @@
import { Tldraw, TldrawApp, TldrawProps, useFileSystem } from '@tldraw/tldraw' import { Tldraw, TldrawApp, TldrawProps, useFileSystem } from '@tldraw/tldraw'
import { useAccountHandlers } from 'hooks/useAccountHandlers' import { useAccountHandlers } from 'hooks/useAccountHandlers'
import { useUploadAssets } from 'hooks/useUploadAssets'
import React, { FC } from 'react' import React, { FC } from 'react'
import * as gtag from 'utils/gtag' import * as gtag from 'utils/gtag'
@ -35,6 +36,8 @@ const Editor: FC<EditorProps & Partial<TldrawProps>> = ({
const { onSignIn, onSignOut } = useAccountHandlers() const { onSignIn, onSignOut } = useAccountHandlers()
const { onAssetUpload } = useUploadAssets()
return ( return (
<div className="tldraw"> <div className="tldraw">
<Tldraw <Tldraw
@ -45,6 +48,7 @@ const Editor: FC<EditorProps & Partial<TldrawProps>> = ({
showSponsorLink={!isSponsor} showSponsorLink={!isSponsor}
onSignIn={isSponsor ? undefined : onSignIn} onSignIn={isSponsor ? undefined : onSignIn}
onSignOut={isUser ? onSignOut : undefined} onSignOut={isUser ? onSignOut : undefined}
onAssetUpload={onAssetUpload}
{...fileSystemEvents} {...fileSystemEvents}
{...rest} {...rest}
/> />

View file

@ -42,7 +42,7 @@ function Editor({ roomId, isUser, isSponsor }: Props) {
const fileSystemEvents = useFileSystem() const fileSystemEvents = useFileSystem()
const { onSignIn, onSignOut } = useAccountHandlers() const { onSignIn, onSignOut } = useAccountHandlers()
const { error, ...events } = useMultiplayerState(roomId) const { error, ...events } = useMultiplayerState(roomId)
const { onAssetCreate, onAssetDelete } = useMultiplayerAssets() const { onAssetCreate, onAssetUpload, onAssetDelete } = useMultiplayerAssets()
if (error) return <LoadingScreen>Error: {error.message}</LoadingScreen> if (error) return <LoadingScreen>Error: {error.message}</LoadingScreen>
@ -57,6 +57,7 @@ function Editor({ roomId, isUser, isSponsor }: Props) {
onSignOut={isUser ? onSignOut : undefined} onSignOut={isUser ? onSignOut : undefined}
onAssetCreate={onAssetCreate} onAssetCreate={onAssetCreate}
onAssetDelete={onAssetDelete} onAssetDelete={onAssetDelete}
onAssetUpload={onAssetUpload}
{...fileSystemEvents} {...fileSystemEvents}
{...events} {...events}
/> />

View file

@ -1,4 +1,4 @@
import { TldrawApp } from '@tldraw/tldraw' import { TDAsset, TldrawApp } from '@tldraw/tldraw'
import { useCallback } from 'react' import { useCallback } from 'react'
export function useMultiplayerAssets() { export function useMultiplayerAssets() {
@ -32,10 +32,40 @@ export function useMultiplayerAssets() {
[] []
) )
const onAssetUpload = useCallback(
// Send the asset to our upload enpoint, which in turn will send it to AWS and
// respond with the URL of the uploaded file.
async (app: TldrawApp, id: string, asset: TDAsset): Promise<string | false> => {
const filename = encodeURIComponent(asset.id)
const fileType = encodeURIComponent(asset.type)
const res = await fetch(`/api/upload?file=${filename}&fileType=${fileType}`)
const { url, fields } = await res.json()
const formData = new FormData()
Object.entries({ ...fields, asset }).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 onAssetDelete = useCallback(async (app: TldrawApp, id: string): Promise<boolean> => { const onAssetDelete = useCallback(async (app: TldrawApp, id: string): Promise<boolean> => {
// noop // noop
return true return true
}, []) }, [])
return { onAssetCreate, onAssetDelete } return { onAssetCreate, onAssetUpload, onAssetDelete }
} }

View file

@ -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 enpoint, which in turn will send it to AWS and
// respond with the URL of the uploaded file.
async (app: TldrawApp, id: string, asset: TDAsset): Promise<string | false> => {
const filename = encodeURIComponent(asset.id)
const fileType = encodeURIComponent(asset.type)
const res = await fetch(`/api/upload?file=${filename}&fileType=${fileType}`)
const { url, fields } = await res.json()
const formData = new FormData()
Object.entries({ ...fields, asset }).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 }
}

View file

@ -51,7 +51,7 @@ export default async function CreateMultiplayerRoom(req: NextApiRequest, res: Ne
// }, // },
// }).then((d) => d.json()) // }).then((d) => d.json())
//POST // POST
const result = await fetch(`https://liveblocks.net/api/v1/room/${roomId}/storage`, { const result = await fetch(`https://liveblocks.net/api/v1/room/${roomId}/storage`, {
method: 'POST', method: 'POST',
mode: 'cors', mode: 'cors',
@ -64,10 +64,12 @@ export default async function CreateMultiplayerRoom(req: NextApiRequest, res: Ne
}) })
if (result.status === 200) { if (result.status === 200) {
res.send({ status: 'success', roomId }) res.send({ status: 'success', message: result.statusText, roomId })
} else {
res.send({ status: 'error', message: result.statusText })
} }
} catch (e) { } catch (e) {
res.send({ status: 'error' }) res.send({ status: 'error', message: e.message })
// noop // noop
} }
} }

View file

@ -128,6 +128,7 @@ export function Tldraw({
onChangePage, onChangePage,
onAssetCreate, onAssetCreate,
onAssetDelete, onAssetDelete,
onAssetUpload,
onExport, onExport,
}: TldrawProps) { }: TldrawProps) {
const [sId, setSId] = React.useState(id) const [sId, setSId] = React.useState(id)
@ -153,6 +154,7 @@ export function Tldraw({
onChangePage, onChangePage,
onAssetDelete, onAssetDelete,
onAssetCreate, onAssetCreate,
onAssetUpload,
}) })
return app return app
}) })
@ -179,6 +181,7 @@ export function Tldraw({
onChangePage, onChangePage,
onAssetDelete, onAssetDelete,
onAssetCreate, onAssetCreate,
onAssetUpload,
onExport, onExport,
}) })
@ -243,6 +246,7 @@ export function Tldraw({
onChangePage, onChangePage,
onAssetDelete, onAssetDelete,
onAssetCreate, onAssetCreate,
onAssetUpload,
onExport, onExport,
} }
}, [ }, [
@ -264,6 +268,7 @@ export function Tldraw({
onChangePage, onChangePage,
onAssetDelete, onAssetDelete,
onAssetCreate, onAssetCreate,
onAssetUpload,
onExport, onExport,
]) ])

View file

@ -43,10 +43,25 @@ export const MultiplayerMenu = React.memo(function MultiplayerMenu() {
}, []) }, [])
const handleCopyToMultiplayerRoom = React.useCallback(async () => { const handleCopyToMultiplayerRoom = React.useCallback(async () => {
const nextDocument = { ...app.document }
// TODO: Upload images to server
if (app.callbacks.onAssetUpload) {
for (const id in nextDocument.assets) {
const asset = nextDocument.assets[id]
const newSrc = await app.callbacks.onAssetUpload(app, id, asset)
if (newSrc) {
asset.src = newSrc
} else {
asset.src = ''
}
}
}
const body = JSON.stringify({ const body = JSON.stringify({
roomId: Utils.uniqueId(), roomId: Utils.uniqueId(),
pageId: app.currentPageId, pageId: app.currentPageId,
document: app.document, document: nextDocument,
}) })
const myHeaders = new Headers({ const myHeaders = new Headers({
@ -54,16 +69,26 @@ export const MultiplayerMenu = React.memo(function MultiplayerMenu() {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}) })
const res = await fetch(`/api/create`, { app.setIsLoading(true)
headers: myHeaders,
method: 'POST',
mode: 'no-cors',
body,
}).then((res) => res.json())
if (res?.roomId) { try {
window.location.href = `/r/${res.roomId}` const res = await fetch(`/api/create`, {
headers: myHeaders,
method: 'POST',
mode: 'no-cors',
body,
}).then((res) => res.json())
if (res?.roomId) {
window.location.href = `/r/${res.roomId}`
} else {
TLDR.warn(res.message)
}
} catch (e: any) {
TLDR.warn(e.message)
} }
app.setIsLoading(false)
}, []) }, [])
return ( return (

View file

@ -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. * (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<string | false> onAssetCreate?: (app: TldrawApp, file: File, id: string) => Promise<string | false>
/**
* (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, id: string, asset: TDAsset) => Promise<string | false>
/** /**
* (optional) A callback to run when the user exports their page or selection. * (optional) A callback to run when the user exports their page or selection.
*/ */