preserve coordinates

This commit is contained in:
Jeff Emmett 2025-11-10 23:51:53 -08:00
parent 8bcbf082c5
commit 8e3db10245
3 changed files with 104 additions and 19 deletions

View File

@ -135,8 +135,10 @@ export function applyAutomergePatchesToTLStore(
}
// CRITICAL: Store original x and y before patch application to preserve them
// We need to preserve coordinates from existing records to prevent them from being reset
const originalX = (record.typeName === 'shape' && typeof record.x === 'number' && !isNaN(record.x)) ? record.x : undefined
const originalY = (record.typeName === 'shape' && typeof record.y === 'number' && !isNaN(record.y)) ? record.y : undefined
const hadOriginalCoordinates = originalX !== undefined && originalY !== undefined
switch (patch.action) {
case "insert": {
@ -172,19 +174,42 @@ export function applyAutomergePatchesToTLStore(
}
// CRITICAL: After patch application, ensure x and y coordinates are preserved for shapes
// This prevents coordinates from being reset to 0,0 when patches don't include them
if (updatedObjects[id] && updatedObjects[id].typeName === 'shape') {
const patchedRecord = updatedObjects[id]
// Preserve original x and y if they were valid, otherwise use defaults
if (originalX !== undefined && (typeof patchedRecord.x !== 'number' || patchedRecord.x === null || isNaN(patchedRecord.x))) {
updatedObjects[id] = { ...patchedRecord, x: originalX }
} else if (typeof patchedRecord.x !== 'number' || patchedRecord.x === null || isNaN(patchedRecord.x)) {
updatedObjects[id] = { ...patchedRecord, x: defaultRecord.x || 0 }
}
const patchedX = (patchedRecord as any).x
const patchedY = (patchedRecord as any).y
const patchedHasValidX = typeof patchedX === 'number' && !isNaN(patchedX) && patchedX !== null && patchedX !== undefined
const patchedHasValidY = typeof patchedY === 'number' && !isNaN(patchedY) && patchedY !== null && patchedY !== undefined
if (originalY !== undefined && (typeof patchedRecord.y !== 'number' || patchedRecord.y === null || isNaN(patchedRecord.y))) {
updatedObjects[id] = { ...patchedRecord, y: originalY }
} else if (typeof patchedRecord.y !== 'number' || patchedRecord.y === null || isNaN(patchedRecord.y)) {
updatedObjects[id] = { ...patchedRecord, y: defaultRecord.y || 0 }
// CRITICAL: If we had original coordinates, preserve them unless patch explicitly set different valid coordinates
// This prevents coordinates from collapsing to 0,0 after bulk upload
if (hadOriginalCoordinates) {
// Only use patched coordinates if they're explicitly set and different from original
// Otherwise, preserve the original coordinates
if (patchedHasValidX && patchedX !== originalX) {
// Patch explicitly set a different X coordinate - use it
updatedObjects[id] = { ...patchedRecord, x: patchedX }
} else {
// Preserve original X coordinate
updatedObjects[id] = { ...patchedRecord, x: originalX }
}
if (patchedHasValidY && patchedY !== originalY) {
// Patch explicitly set a different Y coordinate - use it
updatedObjects[id] = { ...updatedObjects[id], y: patchedY } as TLRecord
} else {
// Preserve original Y coordinate
updatedObjects[id] = { ...updatedObjects[id], y: originalY } as TLRecord
}
} else {
// No original coordinates - use patched values or defaults
if (!patchedHasValidX) {
updatedObjects[id] = { ...patchedRecord, x: defaultRecord.x || 0 }
}
if (!patchedHasValidY) {
updatedObjects[id] = { ...updatedObjects[id], y: defaultRecord.y || 0 } as TLRecord
}
}
}

View File

@ -55,8 +55,15 @@ function sanitizeRecord(record: TLRecord): TLRecord {
// Ensure required top-level fields exist
if (sanitized.typeName === 'shape') {
if (typeof sanitized.x !== 'number') sanitized.x = 0
if (typeof sanitized.y !== 'number') sanitized.y = 0
// CRITICAL: Only set defaults if coordinates are truly missing or invalid
// DO NOT overwrite valid coordinates (including 0, which is a valid position)
// Only set to 0 if the value is undefined, null, or NaN
if (sanitized.x === undefined || sanitized.x === null || (typeof sanitized.x === 'number' && isNaN(sanitized.x))) {
sanitized.x = 0
}
if (sanitized.y === undefined || sanitized.y === null || (typeof sanitized.y === 'number' && isNaN(sanitized.y))) {
sanitized.y = 0
}
if (typeof sanitized.rotation !== 'number') sanitized.rotation = 0
if (typeof sanitized.isLocked !== 'boolean') sanitized.isLocked = false
if (typeof sanitized.opacity !== 'number') sanitized.opacity = 1
@ -311,8 +318,29 @@ export function applyTLStoreChangesToAutomerge(
// Handle added records
if (changes.added) {
Object.values(changes.added).forEach((record) => {
// CRITICAL: For shapes, preserve x and y coordinates before sanitization
// This ensures coordinates aren't lost when saving to Automerge
let originalX: number | undefined = undefined
let originalY: number | undefined = undefined
if (record.typeName === 'shape') {
originalX = (record as any).x
originalY = (record as any).y
}
// Sanitize record before saving to ensure all required fields are present
const sanitizedRecord = sanitizeRecord(record)
// CRITICAL: Restore original coordinates if they were valid
// This prevents coordinates from being reset to 0,0 when saving to Automerge
if (record.typeName === 'shape' && originalX !== undefined && originalY !== undefined) {
if (typeof originalX === 'number' && !isNaN(originalX) && originalX !== null) {
(sanitizedRecord as any).x = originalX
}
if (typeof originalY === 'number' && !isNaN(originalY) && originalY !== null) {
(sanitizedRecord as any).y = originalY
}
}
// CRITICAL: Create a deep copy to ensure all properties (including richText and text) are preserved
// This prevents Automerge from treating the object as read-only
const recordToSave = JSON.parse(JSON.stringify(sanitizedRecord))
@ -326,6 +354,14 @@ export function applyTLStoreChangesToAutomerge(
// This is simpler than deep comparison and leverages Automerge's conflict resolution
if (changes.updated) {
Object.values(changes.updated).forEach(([_, record]) => {
// CRITICAL: For shapes, preserve x and y coordinates before sanitization
// This ensures coordinates aren't lost when updating records in Automerge
let originalX: number | undefined = undefined
let originalY: number | undefined = undefined
if (record.typeName === 'shape') {
originalX = (record as any).x
originalY = (record as any).y
}
// DEBUG: Log richText, meta.text, and Obsidian note properties before sanitization
if (record.typeName === 'shape') {
if (record.type === 'geo' && (record.props as any)?.richText) {
@ -371,6 +407,17 @@ export function applyTLStoreChangesToAutomerge(
const sanitizedRecord = sanitizeRecord(record)
// CRITICAL: Restore original coordinates if they were valid
// This prevents coordinates from being reset to 0,0 when updating records in Automerge
if (record.typeName === 'shape' && originalX !== undefined && originalY !== undefined) {
if (typeof originalX === 'number' && !isNaN(originalX) && originalX !== null) {
(sanitizedRecord as any).x = originalX
}
if (typeof originalY === 'number' && !isNaN(originalY) && originalY !== null) {
(sanitizedRecord as any).y = originalY
}
}
// DEBUG: Log richText, meta.text, and Obsidian note properties after sanitization
if (sanitizedRecord.typeName === 'shape') {
if (sanitizedRecord.type === 'geo' && (sanitizedRecord.props as any)?.richText) {

View File

@ -436,23 +436,36 @@ export function useAutomergeStoreV2({
// Create a clean copy of the record
const cleanRecord = JSON.parse(JSON.stringify(record))
// CRITICAL: For shapes, preserve x and y coordinates BEFORE sanitization
// This ensures coordinates aren't lost during the sanitization process
// CRITICAL: For shapes, preserve x and y coordinates
// We MUST preserve coordinates - they should never be reset to 0,0 unless truly missing
if (cleanRecord.typeName === 'shape') {
// Store original coordinates BEFORE any processing
const originalX = cleanRecord.x
const originalY = cleanRecord.y
const hadValidX = typeof originalX === 'number' && !isNaN(originalX) && originalX !== null && originalX !== undefined
const hadValidY = typeof originalY === 'number' && !isNaN(originalY) && originalY !== null && originalY !== undefined
// Use the same sanitizeRecord function that patches use
// This ensures consistency between dev and production
const sanitized = sanitizeRecord(cleanRecord)
// CRITICAL: Restore original coordinates if they were valid
// sanitizeRecord only sets defaults if coordinates are missing/invalid
// But we want to preserve the original values if they exist
if (typeof originalX === 'number' && !isNaN(originalX) && originalX !== null && originalX !== undefined) {
// CRITICAL: ALWAYS restore original coordinates if they were valid
// Even if sanitizeRecord preserved them, we ensure they're correct
// This prevents any possibility of coordinates being reset
if (hadValidX) {
(sanitized as any).x = originalX
}
if (typeof originalY === 'number' && !isNaN(originalY) && originalY !== null && originalY !== undefined) {
if (hadValidY) {
(sanitized as any).y = originalY
}
// Log if coordinates were changed (for debugging)
if (hadValidX && (sanitized as any).x !== originalX) {
console.warn(`⚠️ Coordinate X was changed during sanitization for shape ${cleanRecord.id}: ${originalX} -> ${(sanitized as any).x}. Restored to ${originalX}.`)
(sanitized as any).x = originalX
}
if (hadValidY && (sanitized as any).y !== originalY) {
console.warn(`⚠️ Coordinate Y was changed during sanitization for shape ${cleanRecord.id}: ${originalY} -> ${(sanitized as any).y}. Restored to ${originalY}.`)
(sanitized as any).y = originalY
}