update R2 storage to JSON format
This commit is contained in:
parent
6cb70b4da3
commit
5fe28ba7f8
|
|
@ -23,8 +23,140 @@ export function CustomMainMenu() {
|
|||
if (typeof event.target?.result !== 'string') {
|
||||
return
|
||||
}
|
||||
const jsonData = JSON.parse(event.target.result) as TLContent
|
||||
editor.putContentOntoCurrentPage(jsonData, { select: true })
|
||||
try {
|
||||
const jsonData = JSON.parse(event.target.result)
|
||||
console.log('Parsed JSON data:', jsonData)
|
||||
|
||||
// Handle different JSON formats
|
||||
let contentToImport: TLContent
|
||||
|
||||
// Check if it's a worker export format (has documents array)
|
||||
if (jsonData.documents && Array.isArray(jsonData.documents)) {
|
||||
console.log('Detected worker export format with', jsonData.documents.length, 'documents')
|
||||
|
||||
// Convert worker export format to TLContent format
|
||||
const shapes = jsonData.documents
|
||||
.filter((doc: any) => doc.state?.typeName === 'shape')
|
||||
.map((doc: any) => doc.state)
|
||||
|
||||
const bindings = jsonData.documents
|
||||
.filter((doc: any) => doc.state?.typeName === 'binding')
|
||||
.map((doc: any) => doc.state)
|
||||
|
||||
const assets = jsonData.documents
|
||||
.filter((doc: any) => doc.state?.typeName === 'asset')
|
||||
.map((doc: any) => doc.state)
|
||||
|
||||
console.log('Extracted:', { shapes: shapes.length, bindings: bindings.length, assets: assets.length })
|
||||
|
||||
contentToImport = {
|
||||
rootShapeIds: shapes.map((shape: any) => shape.id).filter(Boolean),
|
||||
schema: jsonData.schema || { schemaVersion: 1, storeVersion: 4, recordVersions: {} },
|
||||
shapes: shapes,
|
||||
bindings: bindings,
|
||||
assets: assets,
|
||||
}
|
||||
} else if (jsonData.shapes && Array.isArray(jsonData.shapes)) {
|
||||
console.log('Detected standard TLContent format with', jsonData.shapes.length, 'shapes')
|
||||
// Already in TLContent format, but ensure all required properties exist
|
||||
contentToImport = {
|
||||
rootShapeIds: jsonData.rootShapeIds || jsonData.shapes.map((shape: any) => shape.id).filter(Boolean),
|
||||
schema: jsonData.schema || { schemaVersion: 1, storeVersion: 4, recordVersions: {} },
|
||||
shapes: jsonData.shapes,
|
||||
bindings: jsonData.bindings || [],
|
||||
assets: jsonData.assets || [],
|
||||
}
|
||||
} else {
|
||||
console.log('Detected unknown format, attempting fallback')
|
||||
// Try to extract shapes from any other format
|
||||
contentToImport = {
|
||||
rootShapeIds: jsonData.rootShapeIds || [],
|
||||
schema: jsonData.schema || { schemaVersion: 1, storeVersion: 4, recordVersions: {} },
|
||||
shapes: jsonData.shapes || [],
|
||||
bindings: jsonData.bindings || [],
|
||||
assets: jsonData.assets || [],
|
||||
}
|
||||
}
|
||||
|
||||
// Validate all required properties
|
||||
console.log('Final contentToImport:', contentToImport)
|
||||
|
||||
if (!contentToImport.shapes || !Array.isArray(contentToImport.shapes)) {
|
||||
console.error('Invalid JSON format: missing or invalid shapes array')
|
||||
alert('Invalid JSON format. Please ensure the file contains valid TLDraw content.')
|
||||
return
|
||||
}
|
||||
|
||||
if (!contentToImport.rootShapeIds || !Array.isArray(contentToImport.rootShapeIds)) {
|
||||
console.error('Invalid JSON format: missing or invalid rootShapeIds array')
|
||||
alert('Invalid JSON format. Please ensure the file contains valid TLDraw content.')
|
||||
return
|
||||
}
|
||||
|
||||
if (!contentToImport.schema) {
|
||||
console.error('Invalid JSON format: missing schema')
|
||||
alert('Invalid JSON format. Please ensure the file contains valid TLDraw content.')
|
||||
return
|
||||
}
|
||||
|
||||
if (!contentToImport.bindings || !Array.isArray(contentToImport.bindings)) {
|
||||
contentToImport.bindings = []
|
||||
}
|
||||
|
||||
if (!contentToImport.assets || !Array.isArray(contentToImport.assets)) {
|
||||
contentToImport.assets = []
|
||||
}
|
||||
|
||||
console.log('About to call putContentOntoCurrentPage with:', contentToImport)
|
||||
|
||||
try {
|
||||
editor.putContentOntoCurrentPage(contentToImport, { select: true })
|
||||
} catch (putContentError) {
|
||||
console.error('putContentOntoCurrentPage failed, trying alternative approach:', putContentError)
|
||||
|
||||
// Fallback: Create shapes individually
|
||||
if (contentToImport.shapes && contentToImport.shapes.length > 0) {
|
||||
console.log('Attempting to create shapes individually...')
|
||||
|
||||
// Clear current page first
|
||||
const currentShapes = editor.getCurrentPageShapes()
|
||||
if (currentShapes.length > 0) {
|
||||
editor.deleteShapes(currentShapes.map(shape => shape.id))
|
||||
}
|
||||
|
||||
// Create shapes one by one
|
||||
contentToImport.shapes.forEach((shape: any) => {
|
||||
try {
|
||||
if (shape && shape.id && shape.type) {
|
||||
editor.createShape(shape)
|
||||
}
|
||||
} catch (shapeError) {
|
||||
console.error('Failed to create shape:', shape, shapeError)
|
||||
}
|
||||
})
|
||||
|
||||
// Create bindings if any
|
||||
if (contentToImport.bindings && contentToImport.bindings.length > 0) {
|
||||
contentToImport.bindings.forEach((binding: any) => {
|
||||
try {
|
||||
if (binding && binding.id) {
|
||||
editor.createBinding(binding)
|
||||
}
|
||||
} catch (bindingError) {
|
||||
console.error('Failed to create binding:', binding, bindingError)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
console.log('Individual shape creation completed')
|
||||
} else {
|
||||
alert('No valid shapes found in the JSON file.')
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error parsing JSON:', error)
|
||||
alert('Error parsing JSON file. Please ensure the file is valid JSON.')
|
||||
}
|
||||
};
|
||||
if (file) {
|
||||
reader.readAsText(file);
|
||||
|
|
|
|||
|
|
@ -270,7 +270,11 @@ export class TldrawDurableObject {
|
|||
|
||||
// convert the room to JSON and upload it to R2
|
||||
const snapshot = JSON.stringify(room.getCurrentSnapshot())
|
||||
await this.r2.put(`rooms/${this.roomId}`, snapshot)
|
||||
await this.r2.put(`rooms/${this.roomId}`, snapshot, {
|
||||
httpMetadata: {
|
||||
contentType: 'application/json'
|
||||
}
|
||||
})
|
||||
console.log(`Board persisted to R2: ${this.roomId}`)
|
||||
}, 30_000)
|
||||
|
||||
|
|
|
|||
|
|
@ -595,8 +595,12 @@ async function backupAllBoards(env: Environment) {
|
|||
// Create backup key with date only
|
||||
const backupKey = `${date}/${room.key}`
|
||||
|
||||
// Store in backup bucket as JSON
|
||||
await env.BOARD_BACKUPS_BUCKET.put(backupKey, jsonData)
|
||||
// Store in backup bucket as JSON with proper content-type
|
||||
await env.BOARD_BACKUPS_BUCKET.put(backupKey, jsonData, {
|
||||
httpMetadata: {
|
||||
contentType: 'application/json'
|
||||
}
|
||||
})
|
||||
|
||||
// Backed up successfully
|
||||
} catch (error) {
|
||||
|
|
@ -624,6 +628,48 @@ async function backupAllBoards(env: Environment) {
|
|||
}
|
||||
|
||||
router
|
||||
.get("/export/:roomId", async (request, env) => {
|
||||
try {
|
||||
const roomId = request.params.roomId
|
||||
if (!roomId) {
|
||||
return new Response(JSON.stringify({ error: 'Room ID is required' }), {
|
||||
status: 400,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
})
|
||||
}
|
||||
|
||||
// Get the room data from R2
|
||||
const roomData = await env.TLDRAW_BUCKET.get(`rooms/${roomId}`)
|
||||
if (!roomData) {
|
||||
return new Response(JSON.stringify({ error: 'Room not found' }), {
|
||||
status: 404,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
})
|
||||
}
|
||||
|
||||
// Get the JSON data
|
||||
const jsonData = await roomData.text()
|
||||
|
||||
// Return as downloadable JSON file
|
||||
return new Response(jsonData, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Disposition': `attachment; filename="${roomId}-board.json"`,
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Headers': '*',
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Export failed:', error)
|
||||
return new Response(JSON.stringify({
|
||||
error: 'Export failed',
|
||||
message: (error as Error).message
|
||||
}), {
|
||||
status: 500,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
})
|
||||
}
|
||||
})
|
||||
.get("/backup", async (_, env) => {
|
||||
const result = await backupAllBoards(env)
|
||||
return new Response(JSON.stringify(result), {
|
||||
|
|
|
|||
Loading…
Reference in New Issue