Skip to content

Commit cb12418

Browse files
committed
schema canvas updates
1 parent a1d511f commit cb12418

File tree

9 files changed

+683
-280
lines changed

9 files changed

+683
-280
lines changed

src/ui/src/components/json-schema-builder/SchemaBuilder.jsx

Lines changed: 137 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { TYPE_OPTIONS, X_AWS_IDP_DOCUMENT_TYPE } from '../../constants/schemaCon
2222
import SchemaCanvas from './SchemaCanvas';
2323
import SchemaInspector from './SchemaInspector';
2424
import SchemaPreviewTabs from './SchemaPreviewTabs';
25+
import { formatTypeBadge, DocumentTypeBadge } from './utils/badgeHelpers.jsx';
2526

2627
const 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

Comments
 (0)