Add support for persisting images/video in multiplayer (#475)
* Kee track of changed assets * Add support for multiplayer image assets * fix tests * Add images * Improve asset deletion * Remove assets from document * Test with v small image only * Add test for deleteShapes * Cleanup document assets * Add multiplayer assets to www * remove default storage root, which was preventing migration * Update onAssetDelete flow, rename some files * bump upload size to 5mb * Fix freeze bug where image fails to load * fix logic for removing assets * Update useMultiplayerState.ts Co-authored-by: Steve Ruiz <steveruizok@gmail.com>
This commit is contained in:
parent
f2481642f9
commit
e62755ef10
19 changed files with 512 additions and 102 deletions
|
@ -1,7 +1,7 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import * as React from 'react'
|
||||
import type { TldrawApp, TDUser, TDShape, TDBinding, TDDocument } from '@tldraw/tldraw'
|
||||
import type { TldrawApp, TDUser, TDShape, TDBinding, TDDocument, TDAsset } from '@tldraw/tldraw'
|
||||
import { useRedo, useUndo, useRoom, useUpdateMyPresence } from '@liveblocks/react'
|
||||
import { LiveMap, LiveObject } from '@liveblocks/client'
|
||||
|
||||
|
@ -19,6 +19,7 @@ export function useMultiplayerState(roomId: string) {
|
|||
|
||||
const rLiveShapes = React.useRef<LiveMap<string, TDShape>>()
|
||||
const rLiveBindings = React.useRef<LiveMap<string, TDBinding>>()
|
||||
const rLiveAssets = React.useRef<LiveMap<string, TDAsset>>()
|
||||
|
||||
// Callbacks --------------
|
||||
|
||||
|
@ -38,13 +39,15 @@ export function useMultiplayerState(roomId: string) {
|
|||
(
|
||||
app: TldrawApp,
|
||||
shapes: Record<string, TDShape | undefined>,
|
||||
bindings: Record<string, TDBinding | undefined>
|
||||
bindings: Record<string, TDBinding | undefined>,
|
||||
assets: Record<string, TDAsset | undefined>
|
||||
) => {
|
||||
room.batch(() => {
|
||||
const lShapes = rLiveShapes.current
|
||||
const lBindings = rLiveBindings.current
|
||||
const lAssets = rLiveAssets.current
|
||||
|
||||
if (!(lShapes && lBindings)) return
|
||||
if (!(lShapes && lBindings && lAssets)) return
|
||||
|
||||
Object.entries(shapes).forEach(([id, shape]) => {
|
||||
if (!shape) {
|
||||
|
@ -61,6 +64,14 @@ export function useMultiplayerState(roomId: string) {
|
|||
lBindings.set(binding.id, binding)
|
||||
}
|
||||
})
|
||||
|
||||
Object.entries(assets).forEach(([id, asset]) => {
|
||||
if (!asset) {
|
||||
lAssets.delete(id)
|
||||
} else {
|
||||
lAssets.set(asset.id, asset)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
[room]
|
||||
|
@ -125,24 +136,31 @@ export function useMultiplayerState(roomId: string) {
|
|||
async function setupDocument() {
|
||||
const storage = await room.getStorage<any>()
|
||||
|
||||
// Initialize (get or create) shapes and bindings maps
|
||||
// Migrate previous versions
|
||||
const version = storage.root.get('version')
|
||||
|
||||
// Initialize (get or create) maps for shapes/bindings/assets
|
||||
|
||||
let lShapes: LiveMap<string, TDShape> = storage.root.get('shapes')
|
||||
if (!lShapes) {
|
||||
if (!lShapes || !('_serialize' in lShapes)) {
|
||||
storage.root.set('shapes', new LiveMap<string, TDShape>())
|
||||
lShapes = storage.root.get('shapes')
|
||||
}
|
||||
rLiveShapes.current = lShapes
|
||||
|
||||
let lBindings: LiveMap<string, TDBinding> = storage.root.get('bindings')
|
||||
if (!lBindings) {
|
||||
if (!lBindings || !('_serialize' in lBindings)) {
|
||||
storage.root.set('bindings', new LiveMap<string, TDBinding>())
|
||||
lBindings = storage.root.get('bindings')
|
||||
}
|
||||
rLiveBindings.current = lBindings
|
||||
|
||||
// Migrate previous versions
|
||||
const version = storage.root.get('version')
|
||||
let lAssets: LiveMap<string, TDAsset> = storage.root.get('assets')
|
||||
if (!lAssets || !('_serialize' in lAssets)) {
|
||||
storage.root.set('assets', new LiveMap<string, TDAsset>())
|
||||
lAssets = storage.root.get('assets')
|
||||
}
|
||||
rLiveAssets.current = lAssets
|
||||
|
||||
if (!version) {
|
||||
// The doc object will only be present if the document was created
|
||||
|
@ -163,22 +181,25 @@ export function useMultiplayerState(roomId: string) {
|
|||
pages: {
|
||||
page: { shapes, bindings },
|
||||
},
|
||||
assets,
|
||||
},
|
||||
} = doc.toObject()
|
||||
|
||||
Object.values(shapes).forEach((shape) => lShapes.set(shape.id, shape))
|
||||
Object.values(bindings).forEach((binding) => lBindings.set(binding.id, binding))
|
||||
Object.values(assets).forEach((asset) => lAssets.set(asset.id, asset))
|
||||
}
|
||||
}
|
||||
|
||||
// Save the version number for future migrations
|
||||
storage.root.set('version', 2)
|
||||
storage.root.set('version', 2.1)
|
||||
|
||||
// Subscribe to changes
|
||||
const handleChanges = () => {
|
||||
app?.replacePageContent(
|
||||
Object.fromEntries(lShapes.entries()),
|
||||
Object.fromEntries(lBindings.entries())
|
||||
Object.fromEntries(lBindings.entries()),
|
||||
Object.fromEntries(lAssets.entries())
|
||||
)
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue