fix: improve backwards compatibility for older JSON imports
- Add validation for highlight shapes (same as draw) - Improve segment validation to check for NaN/Infinity in point coordinates - Add more custom shape types to valid shapes list - Fix arrow shape validation (use start/end props instead of points array) - Fix line shape validation (uses object format for points, not array) - Better error messages for invalid shapes Prevents "No nearest point found" errors when importing older files with malformed path geometry data. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
c2469a375d
commit
6db2d9c576
|
|
@ -32,7 +32,7 @@ export function CustomMainMenu() {
|
|||
const validateAndNormalizeShapeType = (shape: any): string => {
|
||||
if (!shape || !shape.type) return 'text'
|
||||
|
||||
const validCustomShapes = ['ObsNote', 'VideoChat', 'Transcription', 'Prompt', 'ChatBox', 'Embed', 'Markdown', 'MycrozineTemplate', 'Slide', 'Holon', 'ObsidianBrowser', 'HolonBrowser', 'FathomMeetingsBrowser', 'ImageGen', 'VideoGen', 'Multmux']
|
||||
const validCustomShapes = ['ObsNote', 'VideoChat', 'Transcription', 'Prompt', 'ChatBox', 'Embed', 'Markdown', 'MycrozineTemplate', 'Slide', 'Holon', 'ObsidianBrowser', 'HolonBrowser', 'FathomMeetingsBrowser', 'ImageGen', 'VideoGen', 'Multmux', 'FathomNote', 'GoogleItem', 'Map', 'PrivateWorkspace', 'SharedPiano', 'Drawfast', 'MycelialIntelligence']
|
||||
const validDefaultShapes = ['arrow', 'bookmark', 'draw', 'embed', 'frame', 'geo', 'group', 'highlight', 'image', 'line', 'note', 'text', 'video']
|
||||
const allValidShapes = [...validCustomShapes, ...validDefaultShapes]
|
||||
|
||||
|
|
@ -73,75 +73,141 @@ export function CustomMainMenu() {
|
|||
shape.rotation = validateNumericValue(shape.rotation, 0, 'rotation')
|
||||
shape.opacity = validateNumericValue(shape.opacity, 1, 'opacity')
|
||||
|
||||
// Validate shape-specific geometry based on type
|
||||
if (shape.type === 'line' && shape.props?.points) {
|
||||
// Validate line points
|
||||
if (Array.isArray(shape.props.points)) {
|
||||
shape.props.points = shape.props.points.filter((point: any) => {
|
||||
if (!point || typeof point !== 'object') return false
|
||||
const x = validateNumericValue(point.x, 0, 'point.x')
|
||||
const y = validateNumericValue(point.y, 0, 'point.y')
|
||||
return true
|
||||
}).map((point: any) => ({
|
||||
x: validateNumericValue(point.x, 0, 'point.x'),
|
||||
y: validateNumericValue(point.y, 0, 'point.y'),
|
||||
z: point.z !== undefined ? validateNumericValue(point.z, 0.5, 'point.z') : 0.5
|
||||
}))
|
||||
// Helper to validate and fix segments (used by draw and highlight)
|
||||
const validateSegments = (segments: any[], shapeType: string): any[] | null => {
|
||||
if (!Array.isArray(segments)) return null
|
||||
|
||||
// Line must have at least 2 points
|
||||
if (shape.props.points.length < 2) {
|
||||
console.warn(`⚠️ Line shape has insufficient points (${shape.props.points.length}), skipping shape:`, shape.id)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shape.type === 'draw' && shape.props?.segments) {
|
||||
// Validate draw segments
|
||||
if (Array.isArray(shape.props.segments)) {
|
||||
shape.props.segments = shape.props.segments.filter((segment: any) => {
|
||||
const validSegments = segments.filter((segment: any) => {
|
||||
if (!segment || typeof segment !== 'object') return false
|
||||
if (segment.points && Array.isArray(segment.points)) {
|
||||
if (!segment.points || !Array.isArray(segment.points)) return false
|
||||
|
||||
// Filter and fix points in the segment
|
||||
segment.points = segment.points.filter((point: any) => {
|
||||
if (!point || typeof point !== 'object') return false
|
||||
const x = validateNumericValue(point.x, 0, 'segment.point.x')
|
||||
const y = validateNumericValue(point.y, 0, 'segment.point.y')
|
||||
// Check for NaN/Infinity
|
||||
if (typeof point.x !== 'number' || isNaN(point.x) || !isFinite(point.x)) return false
|
||||
if (typeof point.y !== 'number' || isNaN(point.y) || !isFinite(point.y)) return false
|
||||
return true
|
||||
}).map((point: any) => ({
|
||||
x: validateNumericValue(point.x, 0, 'segment.point.x'),
|
||||
y: validateNumericValue(point.y, 0, 'segment.point.y')
|
||||
y: validateNumericValue(point.y, 0, 'segment.point.y'),
|
||||
z: point.z !== undefined ? validateNumericValue(point.z, 0.5, 'segment.point.z') : 0.5
|
||||
}))
|
||||
return segment.points.length > 0
|
||||
}
|
||||
return false
|
||||
|
||||
// Segment must have at least 1 point for path building
|
||||
return segment.points.length >= 1
|
||||
})
|
||||
|
||||
// Draw must have at least 1 segment with points
|
||||
if (shape.props.segments.length === 0 ||
|
||||
!shape.props.segments.some((s: any) => s.points && s.points.length > 0)) {
|
||||
console.warn(`⚠️ Draw shape has no valid segments, skipping shape:`, shape.id)
|
||||
// Must have at least 1 valid segment with at least 1 point
|
||||
if (validSegments.length === 0) {
|
||||
console.warn(`⚠️ ${shapeType} shape has no valid segments`)
|
||||
return null
|
||||
}
|
||||
|
||||
// Check if we have enough points total for a valid path
|
||||
const totalPoints = validSegments.reduce((sum: number, seg: any) => sum + (seg.points?.length || 0), 0)
|
||||
if (totalPoints < 1) {
|
||||
console.warn(`⚠️ ${shapeType} shape has no valid points`)
|
||||
return null
|
||||
}
|
||||
|
||||
return validSegments
|
||||
}
|
||||
|
||||
// Validate shape-specific geometry based on type
|
||||
if (shape.type === 'line') {
|
||||
// Line shapes use props.points (array of {id, index, x, y})
|
||||
if (!shape.props?.points || typeof shape.props.points !== 'object') {
|
||||
console.warn(`⚠️ Line shape missing points, skipping shape:`, shape.id)
|
||||
return false
|
||||
}
|
||||
|
||||
// Convert points object to validated format
|
||||
const pointKeys = Object.keys(shape.props.points)
|
||||
if (pointKeys.length < 2) {
|
||||
console.warn(`⚠️ Line shape has insufficient points (${pointKeys.length}), skipping shape:`, shape.id)
|
||||
return false
|
||||
}
|
||||
|
||||
// Validate each point
|
||||
for (const key of pointKeys) {
|
||||
const point = shape.props.points[key]
|
||||
if (!point || typeof point !== 'object') {
|
||||
console.warn(`⚠️ Line shape has invalid point at ${key}, skipping shape:`, shape.id)
|
||||
return false
|
||||
}
|
||||
if (typeof point.x !== 'number' || isNaN(point.x) || !isFinite(point.x)) {
|
||||
console.warn(`⚠️ Line shape has invalid x coordinate at ${key}, skipping shape:`, shape.id)
|
||||
return false
|
||||
}
|
||||
if (typeof point.y !== 'number' || isNaN(point.y) || !isFinite(point.y)) {
|
||||
console.warn(`⚠️ Line shape has invalid y coordinate at ${key}, skipping shape:`, shape.id)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shape.type === 'arrow' && shape.props?.points) {
|
||||
// Validate arrow points
|
||||
if (Array.isArray(shape.props.points)) {
|
||||
shape.props.points = shape.props.points.filter((point: any) => {
|
||||
if (!point || typeof point !== 'object') return false
|
||||
return true
|
||||
}).map((point: any) => ({
|
||||
x: validateNumericValue(point.x, 0, 'arrow.point.x'),
|
||||
y: validateNumericValue(point.y, 0, 'arrow.point.y'),
|
||||
z: point.z !== undefined ? validateNumericValue(point.z, 0.5, 'arrow.point.z') : 0.5
|
||||
}))
|
||||
// Validate draw shapes (freehand drawing)
|
||||
if (shape.type === 'draw') {
|
||||
if (!shape.props) shape.props = {}
|
||||
|
||||
// Arrow must have at least 2 points
|
||||
if (shape.props.points.length < 2) {
|
||||
console.warn(`⚠️ Arrow shape has insufficient points (${shape.props.points.length}), skipping shape:`, shape.id)
|
||||
// Initialize segments if missing
|
||||
if (!shape.props.segments) {
|
||||
console.warn(`⚠️ Draw shape missing segments, skipping shape:`, shape.id)
|
||||
return false
|
||||
}
|
||||
|
||||
const validatedSegments = validateSegments(shape.props.segments, 'Draw')
|
||||
if (!validatedSegments) {
|
||||
console.warn(`⚠️ Draw shape has invalid segments, skipping shape:`, shape.id)
|
||||
return false
|
||||
}
|
||||
shape.props.segments = validatedSegments
|
||||
}
|
||||
|
||||
// Validate highlight shapes (same structure as draw)
|
||||
if (shape.type === 'highlight') {
|
||||
if (!shape.props) shape.props = {}
|
||||
|
||||
if (!shape.props.segments) {
|
||||
console.warn(`⚠️ Highlight shape missing segments, skipping shape:`, shape.id)
|
||||
return false
|
||||
}
|
||||
|
||||
const validatedSegments = validateSegments(shape.props.segments, 'Highlight')
|
||||
if (!validatedSegments) {
|
||||
console.warn(`⚠️ Highlight shape has invalid segments, skipping shape:`, shape.id)
|
||||
return false
|
||||
}
|
||||
shape.props.segments = validatedSegments
|
||||
}
|
||||
|
||||
// Validate arrow shapes
|
||||
if (shape.type === 'arrow') {
|
||||
if (!shape.props) shape.props = {}
|
||||
|
||||
// Arrow shapes need start and end bindings or points
|
||||
// Validate start/end if they exist
|
||||
if (shape.props.start) {
|
||||
if (typeof shape.props.start.x === 'number') {
|
||||
shape.props.start.x = validateNumericValue(shape.props.start.x, 0, 'arrow.start.x')
|
||||
}
|
||||
if (typeof shape.props.start.y === 'number') {
|
||||
shape.props.start.y = validateNumericValue(shape.props.start.y, 0, 'arrow.start.y')
|
||||
}
|
||||
}
|
||||
if (shape.props.end) {
|
||||
if (typeof shape.props.end.x === 'number') {
|
||||
shape.props.end.x = validateNumericValue(shape.props.end.x, 100, 'arrow.end.x')
|
||||
}
|
||||
if (typeof shape.props.end.y === 'number') {
|
||||
shape.props.end.y = validateNumericValue(shape.props.end.y, 0, 'arrow.end.y')
|
||||
}
|
||||
}
|
||||
|
||||
// Validate bend
|
||||
if ('bend' in shape.props) {
|
||||
shape.props.bend = validateNumericValue(shape.props.bend, 0, 'arrow.bend')
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue