lutaml-jsonschema 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +5 -0
  3. data/CODE_OF_CONDUCT.md +10 -0
  4. data/README.md +39 -0
  5. data/Rakefile +26 -0
  6. data/exe/lutaml-jsonschema +6 -0
  7. data/frontend/index.html +60 -0
  8. data/frontend/package-lock.json +2715 -0
  9. data/frontend/package.json +27 -0
  10. data/frontend/public/lutaml-logo-dark.svg +1 -0
  11. data/frontend/public/lutaml-logo-full-dark.svg +1 -0
  12. data/frontend/public/lutaml-logo-full-light.svg +1 -0
  13. data/frontend/public/lutaml-logo-light.svg +1 -0
  14. data/frontend/src/App.vue +80 -0
  15. data/frontend/src/__tests__/useBuilderField.test.ts +137 -0
  16. data/frontend/src/__tests__/useDefinitionResolver.test.ts +46 -0
  17. data/frontend/src/__tests__/useSchemaTypes.test.ts +219 -0
  18. data/frontend/src/app.ts +10 -0
  19. data/frontend/src/components/AppHeader.vue +152 -0
  20. data/frontend/src/components/AppSidebar.vue +427 -0
  21. data/frontend/src/components/DetailPanel.vue +403 -0
  22. data/frontend/src/components/SchemaBuilder.vue +543 -0
  23. data/frontend/src/components/SchemaStructure.vue +168 -0
  24. data/frontend/src/components/SearchModal.vue +275 -0
  25. data/frontend/src/composables/useBuilderField.ts +92 -0
  26. data/frontend/src/composables/useDefinitionResolver.ts +17 -0
  27. data/frontend/src/composables/useSchemaTypes.ts +152 -0
  28. data/frontend/src/composables/useSearch.ts +104 -0
  29. data/frontend/src/router.ts +14 -0
  30. data/frontend/src/stores/schemaStore.ts +118 -0
  31. data/frontend/src/stores/uiStore.ts +78 -0
  32. data/frontend/src/style.css +194 -0
  33. data/frontend/src/types.ts +70 -0
  34. data/frontend/src/views/HomeView.vue +396 -0
  35. data/frontend/tsconfig.json +20 -0
  36. data/frontend/vite.config.ts +28 -0
  37. data/lib/lutaml/jsonschema/base.rb +11 -0
  38. data/lib/lutaml/jsonschema/cli.rb +102 -0
  39. data/lib/lutaml/jsonschema/combiner.rb +54 -0
  40. data/lib/lutaml/jsonschema/configuration.rb +47 -0
  41. data/lib/lutaml/jsonschema/link.rb +25 -0
  42. data/lib/lutaml/jsonschema/property_entry.rb +15 -0
  43. data/lib/lutaml/jsonschema/reference_resolver.rb +74 -0
  44. data/lib/lutaml/jsonschema/schema.rb +205 -0
  45. data/lib/lutaml/jsonschema/schema_set.rb +217 -0
  46. data/lib/lutaml/jsonschema/spa/generator.rb +22 -0
  47. data/lib/lutaml/jsonschema/spa/metadata.rb +23 -0
  48. data/lib/lutaml/jsonschema/spa/output_strategy.rb +17 -0
  49. data/lib/lutaml/jsonschema/spa/spa_builder.rb +178 -0
  50. data/lib/lutaml/jsonschema/spa/spa_definition.rb +27 -0
  51. data/lib/lutaml/jsonschema/spa/spa_document.rb +23 -0
  52. data/lib/lutaml/jsonschema/spa/spa_property.rb +47 -0
  53. data/lib/lutaml/jsonschema/spa/spa_schema.rb +29 -0
  54. data/lib/lutaml/jsonschema/spa/spa_search_entry.rb +21 -0
  55. data/lib/lutaml/jsonschema/spa/vue_inlined_strategy.rb +53 -0
  56. data/lib/lutaml/jsonschema/version.rb +7 -0
  57. data/lib/lutaml/jsonschema.rb +29 -0
  58. data/sig/lutaml/jsonschema.rbs +6 -0
  59. metadata +163 -0
@@ -0,0 +1,427 @@
1
+ <template>
2
+ <aside class="sidebar" :class="{ collapsed: uiStore.sidebarCollapsed }">
3
+ <div class="sidebar-content">
4
+ <!-- Branding -->
5
+ <div class="sidebar-branding">
6
+ <img
7
+ :src="uiStore.isDark ? 'https://raw.githubusercontent.com/lutaml/branding/refs/heads/main/svg/lutaml-logo_logo-icon-dark.svg' : 'https://raw.githubusercontent.com/lutaml/branding/refs/heads/main/svg/lutaml-logo_logo-icon-light.svg'"
8
+ alt="LutaML"
9
+ class="branding-logo"
10
+ />
11
+ <div class="branding-text">
12
+ <span class="branding-title">{{ schemaStore.metadata?.title || 'JSON Schema Docs' }}</span>
13
+ <span class="branding-subtitle">LutaML JSON Schema</span>
14
+ </div>
15
+ </div>
16
+
17
+ <!-- Overview -->
18
+ <div class="sidebar-section overview-section">
19
+ <button
20
+ class="overview-btn"
21
+ :class="{ active: !schemaStore.selectedSchema }"
22
+ @click="goHome"
23
+ >
24
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
25
+ <path d="M2 7.5L8 2.5L14 7.5" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
26
+ <path d="M3 7.5V13.5C3 13.5 3 14 4 14H12C12 14 13 14 13 13.5V7.5" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
27
+ </svg>
28
+ <span>Overview</span>
29
+ </button>
30
+ </div>
31
+
32
+ <!-- Schema Tree -->
33
+ <div class="sidebar-section flex-1 overflow-auto">
34
+ <div class="section-header">
35
+ <span class="section-title">Schemas</span>
36
+ </div>
37
+ <div class="schema-tree">
38
+ <div
39
+ v-for="schema in schemaStore.schemas"
40
+ :key="schema.name"
41
+ class="schema-node"
42
+ >
43
+ <div
44
+ class="schema-node-header"
45
+ :class="{ active: schema.name === schemaStore.selectedSchemaName }"
46
+ @click="toggleAndSelect(schema.name)"
47
+ >
48
+ <svg width="12" height="12" viewBox="0 0 12 12" fill="none" :class="{ expanded: uiStore.isSchemaExpanded(schema.name) }">
49
+ <path d="M4 3l3 3-3 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
50
+ </svg>
51
+ <span class="schema-name">{{ schema.title || schema.name }}</span>
52
+ <span class="schema-badge-count">{{ schema.properties.length }}P {{ schema.definitions.length }}D</span>
53
+ </div>
54
+
55
+ <div v-if="uiStore.isSchemaExpanded(schema.name)" class="schema-children">
56
+ <!-- Definitions -->
57
+ <div
58
+ v-for="def in schema.definitions"
59
+ :key="def.name"
60
+ class="tree-item definition-item"
61
+ :class="{ active: schema.name === schemaStore.selectedSchemaName && def.name === schemaStore.selectedDefinitionName }"
62
+ @click.stop="selectDefinition(schema.name, def.name)"
63
+ >
64
+ <span class="badge badge-definition-sm">D</span>
65
+ <span class="tree-item-name">{{ def.title || def.name }}</span>
66
+ </div>
67
+ <!-- Properties -->
68
+ <div
69
+ v-for="prop in schema.properties"
70
+ :key="prop.name"
71
+ class="tree-item property-item"
72
+ @click.stop="selectProperty(schema.name, prop.name)"
73
+ >
74
+ <span class="badge badge-property-sm">P</span>
75
+ <span class="tree-item-name">{{ prop.name }}</span>
76
+ <span v-if="prop.required" class="required-dot" title="Required"></span>
77
+ </div>
78
+ </div>
79
+ </div>
80
+ </div>
81
+ </div>
82
+
83
+ <!-- Quick Stats -->
84
+ <div class="sidebar-section stats-section">
85
+ <div class="stats-grid">
86
+ <div class="stat-item">
87
+ <span class="stat-value">{{ schemaStore.schemaCounts.schemas }}</span>
88
+ <span class="stat-label">Schemas</span>
89
+ </div>
90
+ <div class="stat-item">
91
+ <span class="stat-value">{{ schemaStore.schemaCounts.properties }}</span>
92
+ <span class="stat-label">Properties</span>
93
+ </div>
94
+ <div class="stat-item">
95
+ <span class="stat-value">{{ schemaStore.schemaCounts.definitions }}</span>
96
+ <span class="stat-label">Definitions</span>
97
+ </div>
98
+ </div>
99
+ </div>
100
+
101
+ <!-- Footer -->
102
+ <div class="sidebar-footer">
103
+ <span class="footer-text">
104
+ Generated with
105
+ <a href="https://github.com/lutaml/lutaml-jsonschema" target="_blank" rel="noopener">LutaML JSON Schema</a>
106
+ </span>
107
+ </div>
108
+ </div>
109
+ </aside>
110
+ </template>
111
+
112
+ <script setup lang="ts">
113
+ import { useSchemaStore } from '../stores/schemaStore'
114
+ import { useUiStore } from '../stores/uiStore'
115
+
116
+ const schemaStore = useSchemaStore()
117
+ const uiStore = useUiStore()
118
+
119
+ function goHome() {
120
+ schemaStore.selectSchema(null)
121
+ uiStore.closeDetailPanel()
122
+ }
123
+
124
+ function toggleAndSelect(name: string) {
125
+ schemaStore.selectSchema(name)
126
+ uiStore.toggleSchemaExpanded(name)
127
+ }
128
+
129
+ function selectDefinition(schemaName: string, defName: string) {
130
+ schemaStore.selectSchema(schemaName)
131
+ schemaStore.selectDefinition(defName)
132
+ uiStore.openDetailPanel()
133
+ }
134
+
135
+ function selectProperty(schemaName: string, propName: string) {
136
+ schemaStore.selectSchema(schemaName)
137
+ uiStore.openDetailPanel()
138
+ }
139
+ </script>
140
+
141
+ <style scoped>
142
+ .sidebar {
143
+ width: 280px;
144
+ flex-shrink: 0;
145
+ background: var(--bg-secondary);
146
+ border-right: 1px solid var(--border-light);
147
+ display: flex;
148
+ flex-direction: column;
149
+ transition: width var(--transition-slow);
150
+ overflow: hidden;
151
+ }
152
+
153
+ .sidebar.collapsed {
154
+ width: 0;
155
+ }
156
+
157
+ .sidebar-content {
158
+ flex: 1;
159
+ display: flex;
160
+ flex-direction: column;
161
+ overflow: hidden;
162
+ min-width: 280px;
163
+ }
164
+
165
+ .sidebar-branding {
166
+ display: flex;
167
+ align-items: center;
168
+ gap: var(--space-2);
169
+ padding: var(--space-4);
170
+ border-bottom: 1px solid var(--border-light);
171
+ }
172
+
173
+ .branding-logo {
174
+ width: 28px;
175
+ height: 28px;
176
+ flex-shrink: 0;
177
+ }
178
+
179
+ .branding-text {
180
+ display: flex;
181
+ flex-direction: column;
182
+ line-height: 1.2;
183
+ min-width: 0;
184
+ }
185
+
186
+ .branding-title {
187
+ font-size: var(--text-sm);
188
+ font-weight: 600;
189
+ color: var(--text-primary);
190
+ white-space: nowrap;
191
+ overflow: hidden;
192
+ text-overflow: ellipsis;
193
+ }
194
+
195
+ .branding-subtitle {
196
+ font-size: var(--text-xs);
197
+ color: var(--text-muted);
198
+ font-weight: 400;
199
+ }
200
+
201
+ .overview-section {
202
+ padding: var(--space-2) var(--space-3);
203
+ border-bottom: 1px solid var(--border-light);
204
+ }
205
+
206
+ .overview-btn {
207
+ display: flex;
208
+ align-items: center;
209
+ gap: var(--space-2);
210
+ width: 100%;
211
+ padding: var(--space-2) var(--space-3);
212
+ font-size: var(--text-sm);
213
+ font-weight: 500;
214
+ color: var(--text-secondary);
215
+ border-radius: var(--radius-md);
216
+ cursor: pointer;
217
+ transition: all var(--transition-fast);
218
+ }
219
+
220
+ .overview-btn:hover {
221
+ background: var(--bg-hover);
222
+ color: var(--text-primary);
223
+ }
224
+
225
+ .overview-btn.active {
226
+ background: var(--color-primary-alpha);
227
+ color: var(--color-primary);
228
+ }
229
+
230
+ .sidebar-section {
231
+ padding: var(--space-4);
232
+ border-bottom: 1px solid var(--border-light);
233
+ }
234
+
235
+ .sidebar-section.flex-1 {
236
+ flex: 1;
237
+ overflow: auto;
238
+ }
239
+
240
+ .section-header {
241
+ display: flex;
242
+ align-items: center;
243
+ justify-content: space-between;
244
+ margin-bottom: var(--space-3);
245
+ }
246
+
247
+ .section-title {
248
+ font-size: var(--text-xs);
249
+ font-weight: 600;
250
+ text-transform: uppercase;
251
+ letter-spacing: 0.05em;
252
+ color: var(--text-muted);
253
+ }
254
+
255
+ .schema-tree {
256
+ display: flex;
257
+ flex-direction: column;
258
+ gap: var(--space-1);
259
+ }
260
+
261
+ .schema-node-header {
262
+ display: flex;
263
+ align-items: center;
264
+ gap: var(--space-2);
265
+ padding: var(--space-1) var(--space-2);
266
+ cursor: pointer;
267
+ transition: background var(--transition-fast);
268
+ font-size: var(--text-sm);
269
+ border-radius: var(--radius-sm);
270
+ }
271
+
272
+ .schema-node-header:hover {
273
+ background: var(--bg-hover);
274
+ }
275
+
276
+ .schema-node-header.active {
277
+ background: var(--color-primary-alpha);
278
+ }
279
+
280
+ .schema-node-header.active .schema-name {
281
+ color: var(--color-primary);
282
+ }
283
+
284
+ .schema-node-header.active svg {
285
+ color: var(--color-primary);
286
+ }
287
+
288
+ .schema-node-header svg {
289
+ color: var(--text-muted);
290
+ transition: transform var(--transition-fast);
291
+ flex-shrink: 0;
292
+ }
293
+
294
+ .schema-node-header svg.expanded {
295
+ transform: rotate(90deg);
296
+ }
297
+
298
+ .schema-name {
299
+ flex: 1;
300
+ font-weight: 500;
301
+ color: var(--text-primary);
302
+ white-space: nowrap;
303
+ overflow: hidden;
304
+ text-overflow: ellipsis;
305
+ }
306
+
307
+ .schema-badge-count {
308
+ font-size: var(--text-xs);
309
+ color: var(--text-muted);
310
+ background: var(--bg-primary);
311
+ padding: 1px 5px;
312
+ border-radius: var(--radius-sm);
313
+ }
314
+
315
+ .schema-children {
316
+ margin-left: var(--space-4);
317
+ display: flex;
318
+ flex-direction: column;
319
+ gap: 2px;
320
+ }
321
+
322
+ .tree-item {
323
+ display: flex;
324
+ align-items: center;
325
+ gap: var(--space-2);
326
+ padding: 3px var(--space-2);
327
+ font-size: var(--text-xs);
328
+ cursor: pointer;
329
+ border-radius: var(--radius-sm);
330
+ transition: background var(--transition-fast);
331
+ }
332
+
333
+ .tree-item:hover {
334
+ background: var(--bg-hover);
335
+ }
336
+
337
+ .tree-item.active {
338
+ background: var(--color-primary-alpha);
339
+ }
340
+
341
+ .tree-item.active .tree-item-name {
342
+ color: var(--color-primary);
343
+ }
344
+
345
+ .tree-item-name {
346
+ flex: 1;
347
+ color: var(--text-secondary);
348
+ white-space: nowrap;
349
+ overflow: hidden;
350
+ text-overflow: ellipsis;
351
+ }
352
+
353
+ .badge-definition-sm {
354
+ background: var(--badge-definition-bg);
355
+ color: var(--badge-definition);
356
+ font-size: 9px;
357
+ padding: 1px 4px;
358
+ border-radius: 2px;
359
+ font-weight: 600;
360
+ flex-shrink: 0;
361
+ }
362
+
363
+ .badge-property-sm {
364
+ background: var(--badge-property-bg);
365
+ color: var(--badge-property);
366
+ font-size: 9px;
367
+ padding: 1px 4px;
368
+ border-radius: 2px;
369
+ font-weight: 600;
370
+ flex-shrink: 0;
371
+ }
372
+
373
+ .required-dot {
374
+ width: 6px;
375
+ height: 6px;
376
+ border-radius: 50%;
377
+ background: var(--badge-required);
378
+ flex-shrink: 0;
379
+ }
380
+
381
+ .stats-section {
382
+ background: var(--bg-primary);
383
+ }
384
+
385
+ .stats-grid {
386
+ display: grid;
387
+ grid-template-columns: repeat(3, 1fr);
388
+ gap: var(--space-2);
389
+ text-align: center;
390
+ }
391
+
392
+ .stat-item {
393
+ display: flex;
394
+ flex-direction: column;
395
+ gap: 2px;
396
+ }
397
+
398
+ .stat-value {
399
+ font-size: var(--text-lg);
400
+ font-weight: 600;
401
+ color: var(--text-primary);
402
+ }
403
+
404
+ .stat-label {
405
+ font-size: var(--text-xs);
406
+ color: var(--text-muted);
407
+ }
408
+
409
+ .sidebar-footer {
410
+ padding: var(--space-3) var(--space-4);
411
+ border-top: 1px solid var(--border-light);
412
+ }
413
+
414
+ .footer-text {
415
+ font-size: var(--text-xs);
416
+ color: var(--text-muted);
417
+ }
418
+
419
+ .footer-text a {
420
+ color: var(--color-primary);
421
+ text-decoration: none;
422
+ }
423
+
424
+ .footer-text a:hover {
425
+ text-decoration: underline;
426
+ }
427
+ </style>