fix: preserve coordinates and convert geo shape text during JSON import
- Fix coordinate collapse bug where shapes were resetting to (0,0) - Convert geo shape props.text to props.richText (tldraw schema change) - Preserve text in meta.text for backward compatibility - Add .nvmrc to enforce Node 20 - Update package.json to require Node >=20.0.0 - Add debug logging for sync and import operations 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
ed5628029d
commit
e69fcad457
|
|
@ -80,6 +80,6 @@
|
|||
"wrangler": "^4.33.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -210,8 +210,15 @@ export function useAutomergeStoreV2({
|
|||
// The Automerge Repo doesn't auto-broadcast because our WebSocket setup doesn't use peer discovery
|
||||
const triggerSync = () => {
|
||||
try {
|
||||
console.log('🔄 triggerSync() called')
|
||||
const repo = (handle as any).repo
|
||||
console.log('🔍 repo:', !!repo, 'handle:', !!handle, 'documentId:', handle?.documentId)
|
||||
|
||||
if (repo) {
|
||||
console.log('🔍 repo.networkSubsystem:', !!repo.networkSubsystem)
|
||||
console.log('🔍 repo.networkSubsystem.syncDoc:', typeof repo.networkSubsystem?.syncDoc)
|
||||
console.log('🔍 repo.networkSubsystem.adapters:', !!repo.networkSubsystem?.adapters)
|
||||
|
||||
// Try multiple approaches to trigger sync
|
||||
|
||||
// Approach 1: Use networkSubsystem.syncDoc if available
|
||||
|
|
@ -223,10 +230,13 @@ export function useAutomergeStoreV2({
|
|||
else if (repo.networkSubsystem && repo.networkSubsystem.adapters) {
|
||||
console.log('🔄 Broadcasting sync to all network adapters')
|
||||
const adapters = Array.from(repo.networkSubsystem.adapters.values())
|
||||
console.log('🔍 Found adapters:', adapters.length)
|
||||
adapters.forEach((adapter: any) => {
|
||||
console.log('🔍 Adapter has send:', typeof adapter?.send)
|
||||
if (adapter && typeof adapter.send === 'function') {
|
||||
// Send a sync message via the adapter
|
||||
// The adapter should handle converting this to the right format
|
||||
console.log('📤 Sending sync via adapter')
|
||||
adapter.send({
|
||||
type: 'sync',
|
||||
documentId: handle.documentId,
|
||||
|
|
@ -243,6 +253,8 @@ export function useAutomergeStoreV2({
|
|||
else {
|
||||
console.warn('⚠️ No known method to trigger sync broadcast found')
|
||||
}
|
||||
} else {
|
||||
console.warn('⚠️ No repo found on handle')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Error triggering manual sync:', error)
|
||||
|
|
|
|||
|
|
@ -63,10 +63,24 @@ export function CustomMainMenu() {
|
|||
// Helper function to validate shape geometry data
|
||||
const validateShapeGeometry = (shape: any): boolean => {
|
||||
if (!shape || !shape.id) return false
|
||||
|
||||
// Validate basic numeric properties
|
||||
shape.x = validateNumericValue(shape.x, 0, 'x')
|
||||
shape.y = validateNumericValue(shape.y, 0, 'y')
|
||||
|
||||
// CRITICAL: Only validate that x/y are valid numbers if they exist
|
||||
// DO NOT set default values here - let fixIncompleteShape handle that
|
||||
// This preserves original coordinates and prevents coordinate collapse
|
||||
if (shape.x !== undefined && shape.x !== null) {
|
||||
if (typeof shape.x !== 'number' || isNaN(shape.x) || !isFinite(shape.x)) {
|
||||
console.warn(`⚠️ Invalid x coordinate for shape ${shape.id}:`, shape.x)
|
||||
shape.x = undefined // Mark as invalid so fixIncompleteShape can handle it
|
||||
}
|
||||
}
|
||||
if (shape.y !== undefined && shape.y !== null) {
|
||||
if (typeof shape.y !== 'number' || isNaN(shape.y) || !isFinite(shape.y)) {
|
||||
console.warn(`⚠️ Invalid y coordinate for shape ${shape.id}:`, shape.y)
|
||||
shape.y = undefined // Mark as invalid so fixIncompleteShape can handle it
|
||||
}
|
||||
}
|
||||
|
||||
// Validate rotation and opacity with defaults (these are safe to default)
|
||||
shape.rotation = validateNumericValue(shape.rotation, 0, 'rotation')
|
||||
shape.opacity = validateNumericValue(shape.opacity, 1, 'opacity')
|
||||
|
||||
|
|
@ -164,12 +178,21 @@ export function CustomMainMenu() {
|
|||
// Function to fix incomplete shape data for proper rendering
|
||||
const fixIncompleteShape = (shape: any, pageId: string): any => {
|
||||
const fixedShape = { ...shape }
|
||||
|
||||
|
||||
// DEBUG: Log coordinates before validation
|
||||
const originalX = fixedShape.x
|
||||
const originalY = fixedShape.y
|
||||
|
||||
// CRITICAL: Validate geometry first (fixes NaN/Infinity values)
|
||||
if (!validateShapeGeometry(fixedShape)) {
|
||||
console.warn(`⚠️ Shape failed geometry validation, skipping:`, fixedShape.id)
|
||||
return null // Return null to indicate shape should be skipped
|
||||
}
|
||||
|
||||
// DEBUG: Log if coordinates changed during validation
|
||||
if (originalX !== fixedShape.x || originalY !== fixedShape.y) {
|
||||
console.log(`🔍 Coordinates changed during validation for ${fixedShape.id}: (${originalX},${originalY}) → (${fixedShape.x},${fixedShape.y})`)
|
||||
}
|
||||
|
||||
// CRITICAL: Validate and normalize shape type
|
||||
const normalizedType = validateAndNormalizeShapeType(fixedShape)
|
||||
|
|
@ -239,24 +262,51 @@ export function CustomMainMenu() {
|
|||
const wValue = fixedShape.w !== undefined ? fixedShape.w : 100
|
||||
const hValue = fixedShape.h !== undefined ? fixedShape.h : 100
|
||||
const geoValue = fixedShape.geo !== undefined ? fixedShape.geo : 'rectangle'
|
||||
|
||||
|
||||
// Remove w/h/geo from top level (TLDraw validation requires they be in props only)
|
||||
delete fixedShape.w
|
||||
delete fixedShape.h
|
||||
delete fixedShape.geo
|
||||
|
||||
|
||||
// Ensure props exists and has the correct values
|
||||
if (!fixedShape.props) fixedShape.props = {}
|
||||
if (fixedShape.props.w === undefined) fixedShape.props.w = wValue
|
||||
if (fixedShape.props.h === undefined) fixedShape.props.h = hValue
|
||||
if (fixedShape.props.geo === undefined) fixedShape.props.geo = geoValue
|
||||
|
||||
|
||||
// Set default props if missing
|
||||
if (!fixedShape.props.color) fixedShape.props.color = 'black'
|
||||
if (!fixedShape.props.fill) fixedShape.props.fill = 'none'
|
||||
if (!fixedShape.props.dash) fixedShape.props.dash = 'draw'
|
||||
if (!fixedShape.props.size) fixedShape.props.size = 'm'
|
||||
if (!fixedShape.props.font) fixedShape.props.font = 'draw'
|
||||
|
||||
// CRITICAL: Convert props.text to props.richText for geo shapes (tldraw schema change)
|
||||
// tldraw no longer accepts props.text on geo shapes - must use richText
|
||||
// Also preserve in meta.text for backward compatibility (used by search and runLLMprompt)
|
||||
if ('text' in fixedShape.props && typeof fixedShape.props.text === 'string') {
|
||||
const textContent = fixedShape.props.text
|
||||
|
||||
// Convert text string to richText format for tldraw
|
||||
fixedShape.props.richText = {
|
||||
type: 'doc',
|
||||
content: textContent ? [{
|
||||
type: 'paragraph',
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: textContent
|
||||
}]
|
||||
}] : []
|
||||
}
|
||||
|
||||
// CRITICAL: Preserve original text in meta.text for backward compatibility
|
||||
// This is used by search (src/utils/searchUtils.ts) and other legacy code
|
||||
if (!fixedShape.meta) fixedShape.meta = {}
|
||||
fixedShape.meta.text = textContent
|
||||
|
||||
// Remove invalid props.text
|
||||
delete fixedShape.props.text
|
||||
}
|
||||
} else if (fixedShape.type === 'VideoChat') {
|
||||
// VideoChat shapes also need w/h in props, not top level
|
||||
const wValue = fixedShape.w !== undefined ? fixedShape.w : 200
|
||||
|
|
@ -492,17 +542,44 @@ export function CustomMainMenu() {
|
|||
const wValue = 'w' in shape ? shape.w : undefined
|
||||
const hValue = 'h' in shape ? shape.h : undefined
|
||||
const geoValue = 'geo' in shape ? shape.geo : undefined
|
||||
|
||||
|
||||
// Remove from top level
|
||||
delete shape.w
|
||||
delete shape.h
|
||||
delete shape.geo
|
||||
|
||||
|
||||
// Ensure props exists and move values there
|
||||
if (!shape.props) shape.props = {}
|
||||
if (wValue !== undefined && !shape.props.w) shape.props.w = wValue
|
||||
if (hValue !== undefined && !shape.props.h) shape.props.h = hValue
|
||||
if (geoValue !== undefined && !shape.props.geo) shape.props.geo = geoValue
|
||||
|
||||
// CRITICAL: Convert props.text to props.richText for geo shapes (tldraw schema change)
|
||||
// tldraw no longer accepts props.text on geo shapes - must use richText
|
||||
// Also preserve in meta.text for backward compatibility (used by search and runLLMprompt)
|
||||
if ('text' in shape.props && typeof shape.props.text === 'string') {
|
||||
const textContent = shape.props.text
|
||||
|
||||
// Convert text string to richText format for tldraw
|
||||
shape.props.richText = {
|
||||
type: 'doc',
|
||||
content: textContent ? [{
|
||||
type: 'paragraph',
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: textContent
|
||||
}]
|
||||
}] : []
|
||||
}
|
||||
|
||||
// CRITICAL: Preserve original text in meta.text for backward compatibility
|
||||
// This is used by search (src/utils/searchUtils.ts) and other legacy code
|
||||
if (!shape.meta) shape.meta = {}
|
||||
shape.meta.text = textContent
|
||||
|
||||
// Remove invalid props.text
|
||||
delete shape.props.text
|
||||
}
|
||||
}
|
||||
|
||||
// CRITICAL: Remove invalid 'text' property from text shapes (TLDraw schema doesn't allow props.text)
|
||||
|
|
@ -516,9 +593,22 @@ export function CustomMainMenu() {
|
|||
}
|
||||
|
||||
console.log('About to call putContentOntoCurrentPage with:', contentToImport)
|
||||
|
||||
|
||||
// DEBUG: Log first 5 shapes' coordinates before import
|
||||
console.log('🔍 Coordinates before putContentOntoCurrentPage:')
|
||||
contentToImport.shapes.slice(0, 5).forEach((shape: any) => {
|
||||
console.log(` Shape ${shape.id} (${shape.type}): x=${shape.x}, y=${shape.y}`)
|
||||
})
|
||||
|
||||
try {
|
||||
editor.putContentOntoCurrentPage(contentToImport, { select: true })
|
||||
|
||||
// DEBUG: Log first 5 shapes' coordinates after import
|
||||
console.log('🔍 Coordinates after putContentOntoCurrentPage:')
|
||||
const importedShapes = editor.getCurrentPageShapes()
|
||||
importedShapes.slice(0, 5).forEach((shape: any) => {
|
||||
console.log(` Shape ${shape.id} (${shape.type}): x=${shape.x}, y=${shape.y}`)
|
||||
})
|
||||
} catch (putContentError) {
|
||||
console.error('putContentOntoCurrentPage failed, trying alternative approach:', putContentError)
|
||||
|
||||
|
|
@ -582,17 +672,44 @@ export function CustomMainMenu() {
|
|||
const wValue = 'w' in shape ? shape.w : undefined
|
||||
const hValue = 'h' in shape ? shape.h : undefined
|
||||
const geoValue = 'geo' in shape ? shape.geo : undefined
|
||||
|
||||
|
||||
// Remove from top level
|
||||
delete shape.w
|
||||
delete shape.h
|
||||
delete shape.geo
|
||||
|
||||
|
||||
// Ensure props exists and move values there
|
||||
if (!shape.props) shape.props = {}
|
||||
if (wValue !== undefined && !shape.props.w) shape.props.w = wValue
|
||||
if (hValue !== undefined && !shape.props.h) shape.props.h = hValue
|
||||
if (geoValue !== undefined && !shape.props.geo) shape.props.geo = geoValue
|
||||
|
||||
// CRITICAL: Convert props.text to props.richText for geo shapes (tldraw schema change)
|
||||
// tldraw no longer accepts props.text on geo shapes - must use richText
|
||||
// Also preserve in meta.text for backward compatibility (used by search and runLLMprompt)
|
||||
if ('text' in shape.props && typeof shape.props.text === 'string') {
|
||||
const textContent = shape.props.text
|
||||
|
||||
// Convert text string to richText format for tldraw
|
||||
shape.props.richText = {
|
||||
type: 'doc',
|
||||
content: textContent ? [{
|
||||
type: 'paragraph',
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: textContent
|
||||
}]
|
||||
}] : []
|
||||
}
|
||||
|
||||
// CRITICAL: Preserve original text in meta.text for backward compatibility
|
||||
// This is used by search (src/utils/searchUtils.ts) and other legacy code
|
||||
if (!shape.meta) shape.meta = {}
|
||||
shape.meta.text = textContent
|
||||
|
||||
// Remove invalid props.text
|
||||
delete shape.props.text
|
||||
}
|
||||
}
|
||||
|
||||
// CRITICAL: Remove invalid 'text' property from text shapes (TLDraw schema doesn't allow props.text)
|
||||
|
|
|
|||
Loading…
Reference in New Issue