@@ -22,6 +22,7 @@ import { TYPE_OPTIONS, X_AWS_IDP_DOCUMENT_TYPE } from '../../constants/schemaCon
2222import SchemaCanvas from './SchemaCanvas' ;
2323import SchemaInspector from './SchemaInspector' ;
2424import SchemaPreviewTabs from './SchemaPreviewTabs' ;
25+ import { formatTypeBadge , DocumentTypeBadge } from './utils/badgeHelpers.jsx' ;
2526
2627const SchemaBuilder = ( { initialSchema, onChange, onValidate } ) => {
2728 const {
@@ -191,35 +192,60 @@ const SchemaBuilder = ({ initialSchema, onChange, onValidate }) => {
191192 const docTypeCount = classes . filter ( ( c ) => c [ X_AWS_IDP_DOCUMENT_TYPE ] ) . length ;
192193 const sharedCount = classes . filter ( ( c ) => ! c [ X_AWS_IDP_DOCUMENT_TYPE ] ) . length ;
193194
194- // Debug: Log all classes to help identify duplicates
195- useEffect ( ( ) => {
196- console . log ( '=== SchemaBuilder Classes Debug ===' ) ;
197- console . log ( 'Total classes:' , classes . length ) ;
198- classes . forEach ( ( cls , index ) => {
199- console . log ( `Class ${ index } :` , {
200- id : cls . id ,
201- name : cls . name ,
202- isDocType : cls [ X_AWS_IDP_DOCUMENT_TYPE ] ,
203- flag : X_AWS_IDP_DOCUMENT_TYPE ,
204- flagValue : cls [ X_AWS_IDP_DOCUMENT_TYPE ]
205- } ) ;
206- } ) ;
207-
208- // Check for duplicate names
209- const nameCount = { } ;
210- classes . forEach ( cls => {
211- nameCount [ cls . name ] = ( nameCount [ cls . name ] || 0 ) + 1 ;
212- } ) ;
213- Object . entries ( nameCount ) . forEach ( ( [ name , count ] ) => {
214- if ( count > 1 ) {
215- console . warn ( `⚠️ DUPLICATE CLASS NAME: "${ name } " appears ${ count } times` ) ;
216- }
217- } ) ;
218- } , [ classes ] ) ;
219-
220195 return (
221- < SpaceBetween size = "l" >
222- { aggregatedValidationErrors . length > 0 && (
196+ < >
197+ { /* Floating breadcrumb bar showing current selection - fixed to viewport */ }
198+ { ( selectedClassId || selectedAttributeId ) && (
199+ < div
200+ style = { {
201+ position : 'sticky' ,
202+ top : 0 ,
203+ zIndex : 1000 ,
204+ backgroundColor : '#ffffff' ,
205+ borderBottom : '2px solid #0972d3' ,
206+ padding : '12px 20px' ,
207+ boxShadow : '0 2px 4px rgba(0,0,0,0.1)' ,
208+ marginBottom : '16px' ,
209+ } }
210+ >
211+ < SpaceBetween direction = "horizontal" size = "xs" alignItems = "center" >
212+ { selectedClassId && (
213+ < >
214+ < Box fontSize = "body-m" fontWeight = "bold" >
215+ { getSelectedClass ( ) ?. name || 'Unknown Class' }
216+ </ Box >
217+ { getSelectedClass ( ) ?. [ X_AWS_IDP_DOCUMENT_TYPE ] && < DocumentTypeBadge /> }
218+ </ >
219+ ) }
220+ { selectedAttributeId && (
221+ < >
222+ < Box fontSize = "body-s" color = "text-body-secondary" > ›</ Box >
223+ < Box fontSize = "body-m" color = "text-label" >
224+ { selectedAttributeId }
225+ </ Box >
226+ { getSelectedAttribute ( ) && formatTypeBadge ( getSelectedAttribute ( ) ) }
227+ </ >
228+ ) }
229+ < Box flex = "1" />
230+ < Button
231+ variant = "inline-link"
232+ iconName = "close"
233+ onClick = { ( ) => {
234+ setSelectedAttributeId ( null ) ;
235+ if ( ! selectedAttributeId ) {
236+ setSelectedClassId ( null ) ;
237+ }
238+ } }
239+ ariaLabel = "Clear selection"
240+ >
241+ { selectedAttributeId ? 'Deselect attribute' : 'Deselect class' }
242+ </ Button >
243+ </ SpaceBetween >
244+ </ div >
245+ ) }
246+
247+ < SpaceBetween size = "l" >
248+ { aggregatedValidationErrors . length > 0 && (
223249 < Alert
224250 type = "error"
225251 dismissible
@@ -236,51 +262,52 @@ const SchemaBuilder = ({ initialSchema, onChange, onValidate }) => {
236262 </ Alert >
237263 ) }
238264
239- < Container
240- header = {
241- < Header
242- variant = "h2"
243- actions = {
244- < Box >
245- < SpaceBetween direction = "horizontal" size = "xs" >
246- < Button onClick = { handleAddClass } iconName = "add-plus" >
247- Add Class
248- </ Button >
249- < Button onClick = { handleAddAttribute } disabled = { ! selectedClassId } iconName = "add-plus" >
250- Add Attribute
251- </ Button >
252- < Button
253- onClick = { ( ) => setShowPreview ( ! showPreview ) }
254- iconName = { showPreview ? 'view-vertical' : 'view-horizontal' }
255- >
256- { showPreview ? 'Hide' : 'Show' } Preview
257- </ Button >
258- < Button
259- onClick = { ( ) => {
260- const schema = exportSchema ( ) ;
261- const blob = new Blob ( [ JSON . stringify ( schema , null , 2 ) ] , { type : 'application/json' } ) ;
262- const url = URL . createObjectURL ( blob ) ;
263- const a = document . createElement ( 'a' ) ;
264- a . href = url ;
265- a . download = `schema-${ Date . now ( ) } .json` ;
266- a . click ( ) ;
267- URL . revokeObjectURL ( url ) ;
268- } }
269- iconName = "download"
270- disabled = { classes . length === 0 }
271- >
272- Export
273- </ Button >
274- </ SpaceBetween >
275- </ Box >
276- }
277- description = "Build JSON Schema Draft 2020-12 compliant extraction schemas with advanced features"
278- >
279- Schema Builder
280- </ Header >
281- }
282- >
283- < ColumnLayout columns = { showPreview ? 2 : 3 } variant = "text-grid" minColumnWidth = { 300 } >
265+ < div style = { { maxHeight : 'calc(100vh - 200px)' , overflow : 'auto' } } >
266+ < Container
267+ header = {
268+ < Header
269+ variant = "h2"
270+ actions = {
271+ < Box >
272+ < SpaceBetween direction = "horizontal" size = "xs" >
273+ < Button onClick = { handleAddClass } iconName = "add-plus" >
274+ Add Class
275+ </ Button >
276+ < Button onClick = { handleAddAttribute } disabled = { ! selectedClassId } iconName = "add-plus" >
277+ Add Attribute
278+ </ Button >
279+ < Button
280+ onClick = { ( ) => setShowPreview ( ! showPreview ) }
281+ iconName = { showPreview ? 'view-vertical' : 'view-horizontal' }
282+ >
283+ { showPreview ? 'Hide' : 'Show' } Preview
284+ </ Button >
285+ < Button
286+ onClick = { ( ) => {
287+ const schema = exportSchema ( ) ;
288+ const blob = new Blob ( [ JSON . stringify ( schema , null , 2 ) ] , { type : 'application/json' } ) ;
289+ const url = URL . createObjectURL ( blob ) ;
290+ const a = document . createElement ( 'a' ) ;
291+ a . href = url ;
292+ a . download = `schema-${ Date . now ( ) } .json` ;
293+ a . click ( ) ;
294+ URL . revokeObjectURL ( url ) ;
295+ } }
296+ iconName = "download"
297+ disabled = { classes . length === 0 }
298+ >
299+ Export
300+ </ Button >
301+ </ SpaceBetween >
302+ </ Box >
303+ }
304+ description = "Build JSON Schema Draft 2020-12 compliant extraction schemas with advanced features"
305+ >
306+ Schema Builder
307+ </ Header >
308+ }
309+ >
310+ < ColumnLayout columns = { showPreview ? 2 : 3 } variant = "text-grid" minColumnWidth = { 300 } >
284311 < Box >
285312 < SpaceBetween size = "m" >
286313 < Header variant = "h3" >
@@ -297,7 +324,7 @@ const SchemaBuilder = ({ initialSchema, onChange, onValidate }) => {
297324
298325 < Box >
299326 < Header variant = "h4" > Document Types</ Header >
300- < SpaceBetween size = "xs " >
327+ < SpaceBetween size = "s " >
301328 { classes . filter ( ( c ) => c [ X_AWS_IDP_DOCUMENT_TYPE ] ) . length === 0 && (
302329 < Box fontSize = "body-s" color = "text-body-secondary" padding = "s" >
303330 No document types yet. Add a class and mark it as a document type.
@@ -314,21 +341,24 @@ const SchemaBuilder = ({ initialSchema, onChange, onValidate }) => {
314341 role = "button"
315342 tabIndex = { 0 }
316343 onClick = { ( ) => setSelectedClassId ( cls . id ) }
317- onKeyPress = { ( e ) => {
344+ onKeyDown = { ( e ) => {
318345 if ( e . key === 'Enter' || e . key === ' ' ) {
319346 setSelectedClassId ( cls . id ) ;
320347 }
321348 } }
322349 style = { {
323350 cursor : 'pointer' ,
324- borderLeft : selectedClassId === cls . id ? '4px solid #0972d3' : '4px solid transparent' ,
325- paddingLeft : '8px' ,
351+ padding : '12px' ,
352+ borderRadius : '8px' ,
353+ border : selectedClassId === cls . id ? '2px solid #0972d3' : '2px solid transparent' ,
354+ backgroundColor : selectedClassId === cls . id ? '#e8f4fd' : 'transparent' ,
355+ transition : 'all 0.2s ease' ,
326356 } }
327357 >
328358 < SpaceBetween size = "xs" >
329359 < Box >
330- < SpaceBetween direction = "horizontal" size = "xs " >
331- < Badge color = "blue" > Document Type </ Badge >
360+ < SpaceBetween direction = "horizontal" size = "s" alignItems = "center ">
361+ < DocumentTypeBadge / >
332362 < Box fontWeight = "bold" > { cls . name } </ Box >
333363 < Box float = "right" >
334364 < SpaceBetween direction = "horizontal" size = "xs" >
@@ -372,7 +402,7 @@ const SchemaBuilder = ({ initialSchema, onChange, onValidate }) => {
372402
373403 < Box >
374404 < Header variant = "h4" > Shared Classes</ Header >
375- < SpaceBetween size = "xs " >
405+ < SpaceBetween size = "s " >
376406 { classes . filter ( ( c ) => ! c [ X_AWS_IDP_DOCUMENT_TYPE ] ) . length === 0 && (
377407 < Box fontSize = "body-s" color = "text-body-secondary" padding = "s" >
378408 No shared classes. Shared classes can be referenced by document types via $ref.
@@ -389,20 +419,23 @@ const SchemaBuilder = ({ initialSchema, onChange, onValidate }) => {
389419 role = "button"
390420 tabIndex = { 0 }
391421 onClick = { ( ) => setSelectedClassId ( cls . id ) }
392- onKeyPress = { ( e ) => {
422+ onKeyDown = { ( e ) => {
393423 if ( e . key === 'Enter' || e . key === ' ' ) {
394424 setSelectedClassId ( cls . id ) ;
395425 }
396426 } }
397427 style = { {
398428 cursor : 'pointer' ,
399- borderLeft : selectedClassId === cls . id ? '4px solid #0972d3' : '4px solid transparent' ,
400- paddingLeft : '8px' ,
429+ padding : '12px' ,
430+ borderRadius : '8px' ,
431+ border : selectedClassId === cls . id ? '2px solid #0972d3' : '2px solid transparent' ,
432+ backgroundColor : selectedClassId === cls . id ? '#e8f4fd' : 'transparent' ,
433+ transition : 'all 0.2s ease' ,
401434 } }
402435 >
403436 < SpaceBetween size = "xs" >
404437 < Box >
405- < SpaceBetween direction = "horizontal" size = "xs " >
438+ < SpaceBetween direction = "horizontal" size = "s" alignItems = "center ">
406439 < Box fontWeight = "bold" > { cls . name } </ Box >
407440 < Box float = "right" >
408441 < SpaceBetween direction = "horizontal" size = "xs" >
@@ -455,6 +488,15 @@ const SchemaBuilder = ({ initialSchema, onChange, onValidate }) => {
455488 onUpdateAttribute = { ( name , updates ) => updateAttribute ( selectedClassId , name , updates ) }
456489 onRemoveAttribute = { ( name ) => removeAttribute ( selectedClassId , name ) }
457490 onReorder = { ( oldIndex , newIndex ) => reorderAttributes ( selectedClassId , oldIndex , newIndex ) }
491+ onNavigateToClass = { ( classId ) => {
492+ setSelectedClassId ( classId ) ;
493+ setSelectedAttributeId ( null ) ;
494+ } }
495+ onNavigateToAttribute = { ( classId , attributeName ) => {
496+ setSelectedClassId ( classId ) ;
497+ setSelectedAttributeId ( attributeName ) ;
498+ } }
499+ availableClasses = { classes }
458500 />
459501
460502 < SchemaInspector
@@ -491,6 +533,14 @@ const SchemaBuilder = ({ initialSchema, onChange, onValidate }) => {
491533 } ,
492534 } ) ;
493535 } }
536+ onNavigateToClass = { ( classId ) => {
537+ setSelectedClassId ( classId ) ;
538+ setSelectedAttributeId ( null ) ;
539+ } }
540+ onNavigateToAttribute = { ( classId , attributeName ) => {
541+ setSelectedClassId ( classId ) ;
542+ setSelectedAttributeId ( attributeName ) ;
543+ } }
494544 />
495545 </ >
496546 ) }
@@ -500,6 +550,7 @@ const SchemaBuilder = ({ initialSchema, onChange, onValidate }) => {
500550 ) }
501551 </ ColumnLayout >
502552 </ Container >
553+ </ div >
503554
504555 < Modal
505556 visible = { showAddClassModal }
@@ -749,7 +800,8 @@ const SchemaBuilder = ({ initialSchema, onChange, onValidate }) => {
749800 ) }
750801 </ SpaceBetween >
751802 </ Modal >
752- </ SpaceBetween >
803+ </ SpaceBetween >
804+ </ >
753805 ) ;
754806} ;
755807
0 commit comments