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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/README.md +39 -0
- data/Rakefile +26 -0
- data/exe/lutaml-jsonschema +6 -0
- data/frontend/index.html +60 -0
- data/frontend/package-lock.json +2715 -0
- data/frontend/package.json +27 -0
- data/frontend/public/lutaml-logo-dark.svg +1 -0
- data/frontend/public/lutaml-logo-full-dark.svg +1 -0
- data/frontend/public/lutaml-logo-full-light.svg +1 -0
- data/frontend/public/lutaml-logo-light.svg +1 -0
- data/frontend/src/App.vue +80 -0
- data/frontend/src/__tests__/useBuilderField.test.ts +137 -0
- data/frontend/src/__tests__/useDefinitionResolver.test.ts +46 -0
- data/frontend/src/__tests__/useSchemaTypes.test.ts +219 -0
- data/frontend/src/app.ts +10 -0
- data/frontend/src/components/AppHeader.vue +152 -0
- data/frontend/src/components/AppSidebar.vue +427 -0
- data/frontend/src/components/DetailPanel.vue +403 -0
- data/frontend/src/components/SchemaBuilder.vue +543 -0
- data/frontend/src/components/SchemaStructure.vue +168 -0
- data/frontend/src/components/SearchModal.vue +275 -0
- data/frontend/src/composables/useBuilderField.ts +92 -0
- data/frontend/src/composables/useDefinitionResolver.ts +17 -0
- data/frontend/src/composables/useSchemaTypes.ts +152 -0
- data/frontend/src/composables/useSearch.ts +104 -0
- data/frontend/src/router.ts +14 -0
- data/frontend/src/stores/schemaStore.ts +118 -0
- data/frontend/src/stores/uiStore.ts +78 -0
- data/frontend/src/style.css +194 -0
- data/frontend/src/types.ts +70 -0
- data/frontend/src/views/HomeView.vue +396 -0
- data/frontend/tsconfig.json +20 -0
- data/frontend/vite.config.ts +28 -0
- data/lib/lutaml/jsonschema/base.rb +11 -0
- data/lib/lutaml/jsonschema/cli.rb +102 -0
- data/lib/lutaml/jsonschema/combiner.rb +54 -0
- data/lib/lutaml/jsonschema/configuration.rb +47 -0
- data/lib/lutaml/jsonschema/link.rb +25 -0
- data/lib/lutaml/jsonschema/property_entry.rb +15 -0
- data/lib/lutaml/jsonschema/reference_resolver.rb +74 -0
- data/lib/lutaml/jsonschema/schema.rb +205 -0
- data/lib/lutaml/jsonschema/schema_set.rb +217 -0
- data/lib/lutaml/jsonschema/spa/generator.rb +22 -0
- data/lib/lutaml/jsonschema/spa/metadata.rb +23 -0
- data/lib/lutaml/jsonschema/spa/output_strategy.rb +17 -0
- data/lib/lutaml/jsonschema/spa/spa_builder.rb +178 -0
- data/lib/lutaml/jsonschema/spa/spa_definition.rb +27 -0
- data/lib/lutaml/jsonschema/spa/spa_document.rb +23 -0
- data/lib/lutaml/jsonschema/spa/spa_property.rb +47 -0
- data/lib/lutaml/jsonschema/spa/spa_schema.rb +29 -0
- data/lib/lutaml/jsonschema/spa/spa_search_entry.rb +21 -0
- data/lib/lutaml/jsonschema/spa/vue_inlined_strategy.rb +53 -0
- data/lib/lutaml/jsonschema/version.rb +7 -0
- data/lib/lutaml/jsonschema.rb +29 -0
- data/sig/lutaml/jsonschema.rbs +6 -0
- metadata +163 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { defineStore } from 'pinia'
|
|
2
|
+
import { ref, computed } from 'vue'
|
|
3
|
+
import type { SpaDocument, SpaSchema, SpaDefinition, SpaProperty } from '../types'
|
|
4
|
+
|
|
5
|
+
export type SelectedItem =
|
|
6
|
+
| { kind: 'schema'; schema: SpaSchema }
|
|
7
|
+
| { kind: 'definition'; schema: SpaSchema; definition: SpaDefinition }
|
|
8
|
+
| { kind: 'property'; schema: SpaSchema; property: SpaProperty }
|
|
9
|
+
|
|
10
|
+
export const useSchemaStore = defineStore('schema', () => {
|
|
11
|
+
const data = ref<SpaDocument | null>(null)
|
|
12
|
+
const selectedSchemaName = ref<string | null>(null)
|
|
13
|
+
const selectedItemKey = ref<string | null>(null)
|
|
14
|
+
|
|
15
|
+
const metadata = computed(() => data.value?.metadata)
|
|
16
|
+
const schemas = computed(() => data.value?.schemas ?? [])
|
|
17
|
+
const searchIndex = computed(() => data.value?.searchIndex ?? [])
|
|
18
|
+
|
|
19
|
+
const selectedSchema = computed<SpaSchema | undefined>(() =>
|
|
20
|
+
schemas.value.find(s => s.name === selectedSchemaName.value)
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
const selectedItem = computed<SelectedItem | null>(() => {
|
|
24
|
+
const schema = selectedSchema.value
|
|
25
|
+
if (!schema) return null
|
|
26
|
+
if (!selectedItemKey.value) return { kind: 'schema', schema }
|
|
27
|
+
|
|
28
|
+
if (selectedItemKey.value.startsWith('def:')) {
|
|
29
|
+
const name = selectedItemKey.value.slice(4)
|
|
30
|
+
const definition = schema.definitions.find(d => d.name === name)
|
|
31
|
+
if (definition) return { kind: 'definition', schema, definition }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (selectedItemKey.value.startsWith('prop:')) {
|
|
35
|
+
const name = selectedItemKey.value.slice(5)
|
|
36
|
+
const property = schema.properties.find(p => p.name === name)
|
|
37
|
+
if (property) return { kind: 'property', schema, property }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return { kind: 'schema', schema }
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
const schemaCounts = computed(() => ({
|
|
44
|
+
schemas: schemas.value.length,
|
|
45
|
+
properties: schemas.value.reduce((acc, s) => acc + s.properties.length, 0),
|
|
46
|
+
definitions: schemas.value.reduce((acc, s) => acc + s.definitions.length, 0),
|
|
47
|
+
}))
|
|
48
|
+
|
|
49
|
+
function loadFromWindow() {
|
|
50
|
+
if (typeof window !== 'undefined' && window.SCHEMA_DATA) {
|
|
51
|
+
const raw = window.SCHEMA_DATA
|
|
52
|
+
// Normalize: ensure arrays are always arrays (backend may omit empty ones)
|
|
53
|
+
for (const schema of raw.schemas) {
|
|
54
|
+
schema.properties = schema.properties ?? []
|
|
55
|
+
schema.definitions = schema.definitions ?? []
|
|
56
|
+
schema.required = schema.required ?? []
|
|
57
|
+
// Normalize: JSON key is "$ref" (Ruby maps it that way) but TypeScript uses "ref"
|
|
58
|
+
for (const prop of schema.properties) {
|
|
59
|
+
if (prop.$ref !== undefined) {
|
|
60
|
+
prop.ref = prop.$ref
|
|
61
|
+
delete prop.$ref
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
for (const def of schema.definitions) {
|
|
65
|
+
def.properties = def.properties ?? []
|
|
66
|
+
def.required = def.required ?? []
|
|
67
|
+
for (const prop of def.properties) {
|
|
68
|
+
if (prop.$ref !== undefined) {
|
|
69
|
+
prop.ref = prop.$ref
|
|
70
|
+
delete prop.$ref
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
raw.searchIndex = raw.searchIndex ?? []
|
|
76
|
+
data.value = raw
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function selectSchema(name: string | null) {
|
|
81
|
+
selectedSchemaName.value = name
|
|
82
|
+
selectedItemKey.value = null
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function selectDefinition(name: string) {
|
|
86
|
+
selectedItemKey.value = `def:${name}`
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function selectProperty(name: string) {
|
|
90
|
+
selectedItemKey.value = `prop:${name}`
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function clearSelection() {
|
|
94
|
+
selectedItemKey.value = null
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function schemaByName(name: string): SpaSchema | undefined {
|
|
98
|
+
return schemas.value.find(s => s.name === name)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
data,
|
|
103
|
+
selectedSchemaName,
|
|
104
|
+
selectedItemKey,
|
|
105
|
+
metadata,
|
|
106
|
+
schemas,
|
|
107
|
+
searchIndex,
|
|
108
|
+
selectedSchema,
|
|
109
|
+
selectedItem,
|
|
110
|
+
schemaCounts,
|
|
111
|
+
loadFromWindow,
|
|
112
|
+
selectSchema,
|
|
113
|
+
selectDefinition,
|
|
114
|
+
selectProperty,
|
|
115
|
+
clearSelection,
|
|
116
|
+
schemaByName,
|
|
117
|
+
}
|
|
118
|
+
})
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { defineStore } from 'pinia'
|
|
2
|
+
import { ref, computed } from 'vue'
|
|
3
|
+
|
|
4
|
+
export type Theme = 'light' | 'dark' | 'system'
|
|
5
|
+
|
|
6
|
+
export const useUiStore = defineStore('ui', () => {
|
|
7
|
+
const theme = ref<Theme>('system')
|
|
8
|
+
const resolvedTheme = ref<'light' | 'dark'>('light')
|
|
9
|
+
const sidebarCollapsed = ref(false)
|
|
10
|
+
const detailPanelOpen = ref(false)
|
|
11
|
+
const activePanelTab = ref<'overview' | 'definition'>('overview')
|
|
12
|
+
const searchOpen = ref(false)
|
|
13
|
+
const expandedSchemaNames = ref<Set<string>>(new Set())
|
|
14
|
+
|
|
15
|
+
const isDark = computed(() => resolvedTheme.value === 'dark')
|
|
16
|
+
|
|
17
|
+
function initTheme() {
|
|
18
|
+
const stored = localStorage.getItem('lutaml-jsonschema-theme') as Theme | null
|
|
19
|
+
if (stored) theme.value = stored
|
|
20
|
+
updateResolvedTheme()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function updateResolvedTheme() {
|
|
24
|
+
if (theme.value === 'system') {
|
|
25
|
+
resolvedTheme.value = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
|
|
26
|
+
} else {
|
|
27
|
+
resolvedTheme.value = theme.value
|
|
28
|
+
}
|
|
29
|
+
applyTheme()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function applyTheme() {
|
|
33
|
+
if (resolvedTheme.value === 'dark') {
|
|
34
|
+
document.documentElement.setAttribute('data-theme', 'dark')
|
|
35
|
+
} else {
|
|
36
|
+
document.documentElement.removeAttribute('data-theme')
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function toggleTheme() {
|
|
41
|
+
const next = resolvedTheme.value === 'light' ? 'dark' : 'light'
|
|
42
|
+
theme.value = next
|
|
43
|
+
localStorage.setItem('lutaml-jsonschema-theme', next)
|
|
44
|
+
updateResolvedTheme()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function toggleSidebar() { sidebarCollapsed.value = !sidebarCollapsed.value }
|
|
48
|
+
function openDetailPanel() { detailPanelOpen.value = true }
|
|
49
|
+
function closeDetailPanel() { detailPanelOpen.value = false }
|
|
50
|
+
function setPanelTab(tab: 'overview' | 'definition') { activePanelTab.value = tab }
|
|
51
|
+
function openSearch() { searchOpen.value = true }
|
|
52
|
+
function closeSearch() { searchOpen.value = false }
|
|
53
|
+
|
|
54
|
+
function toggleSchemaExpanded(name: string) {
|
|
55
|
+
if (expandedSchemaNames.value.has(name)) {
|
|
56
|
+
expandedSchemaNames.value.delete(name)
|
|
57
|
+
} else {
|
|
58
|
+
expandedSchemaNames.value.add(name)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function isSchemaExpanded(name: string): boolean {
|
|
63
|
+
return expandedSchemaNames.value.has(name)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (typeof window !== 'undefined') {
|
|
67
|
+
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
|
|
68
|
+
if (theme.value === 'system') updateResolvedTheme()
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
theme, resolvedTheme, sidebarCollapsed, detailPanelOpen, activePanelTab,
|
|
74
|
+
searchOpen, expandedSchemaNames, isDark,
|
|
75
|
+
initTheme, toggleTheme, toggleSidebar, openDetailPanel, closeDetailPanel,
|
|
76
|
+
setPanelTab, openSearch, closeSearch, toggleSchemaExpanded, isSchemaExpanded,
|
|
77
|
+
}
|
|
78
|
+
})
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/* Lutaml-JSONSchema SPA - LutaML Branded Design */
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
--color-primary: #5b9cd4;
|
|
5
|
+
--color-primary-light: #7db5e2;
|
|
6
|
+
--color-primary-dark: #26489e;
|
|
7
|
+
--color-primary-alpha: rgba(91, 156, 212, 0.12);
|
|
8
|
+
|
|
9
|
+
--color-accent: #dbab3e;
|
|
10
|
+
--color-accent-light: #f0ca6e;
|
|
11
|
+
--color-accent-alpha: rgba(219, 171, 62, 0.12);
|
|
12
|
+
|
|
13
|
+
--color-teal: #60c3a7;
|
|
14
|
+
--color-teal-alpha: rgba(96, 195, 167, 0.12);
|
|
15
|
+
--color-orange: #ea5624;
|
|
16
|
+
--color-orange-alpha: rgba(234, 86, 36, 0.12);
|
|
17
|
+
--color-red: #b31f24;
|
|
18
|
+
--color-red-alpha: rgba(179, 31, 36, 0.12);
|
|
19
|
+
--color-green: #7cc242;
|
|
20
|
+
--color-green-alpha: rgba(124, 194, 66, 0.12);
|
|
21
|
+
|
|
22
|
+
--bg-primary: #FAFAF9;
|
|
23
|
+
--bg-secondary: #F5F5F4;
|
|
24
|
+
--bg-elevated: #FFFFFF;
|
|
25
|
+
--bg-overlay: rgba(28, 25, 23, 0.5);
|
|
26
|
+
--bg-hover: rgba(28, 25, 23, 0.04);
|
|
27
|
+
--bg-active: rgba(28, 25, 23, 0.06);
|
|
28
|
+
|
|
29
|
+
--text-primary: #1C1917;
|
|
30
|
+
--text-secondary: #57534E;
|
|
31
|
+
--text-muted: #A8A29E;
|
|
32
|
+
|
|
33
|
+
--border-light: #E7E5E4;
|
|
34
|
+
--border-medium: #D6D3D1;
|
|
35
|
+
--border-focus: #5b9cd4;
|
|
36
|
+
|
|
37
|
+
--badge-schema: #26489e;
|
|
38
|
+
--badge-schema-bg: rgba(38, 72, 158, 0.12);
|
|
39
|
+
--badge-property: #60c3a7;
|
|
40
|
+
--badge-property-bg: rgba(96, 195, 167, 0.15);
|
|
41
|
+
--badge-definition: #dbab3e;
|
|
42
|
+
--badge-definition-bg: rgba(219, 171, 62, 0.15);
|
|
43
|
+
--badge-required: #5b9cd4;
|
|
44
|
+
--badge-required-bg: rgba(91, 156, 212, 0.12);
|
|
45
|
+
--badge-deprecated: #b31f24;
|
|
46
|
+
--badge-deprecated-bg: rgba(179, 31, 36, 0.12);
|
|
47
|
+
|
|
48
|
+
--shadow-sm: 0 1px 2px rgba(28, 25, 23, 0.05);
|
|
49
|
+
--shadow-md: 0 4px 6px -1px rgba(28, 25, 23, 0.07), 0 2px 4px -1px rgba(28, 25, 23, 0.04);
|
|
50
|
+
--shadow-lg: 0 10px 15px -3px rgba(28, 25, 23, 0.08), 0 4px 6px -2px rgba(28, 25, 23, 0.04);
|
|
51
|
+
|
|
52
|
+
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
53
|
+
--font-mono: 'JetBrains Mono', 'SF Mono', Monaco, 'Consolas', monospace;
|
|
54
|
+
|
|
55
|
+
--text-xs: 0.75rem;
|
|
56
|
+
--text-sm: 0.875rem;
|
|
57
|
+
--text-base: 1rem;
|
|
58
|
+
--text-lg: 1.125rem;
|
|
59
|
+
--text-xl: 1.25rem;
|
|
60
|
+
--text-2xl: 1.5rem;
|
|
61
|
+
|
|
62
|
+
--leading-tight: 1.25;
|
|
63
|
+
--leading-normal: 1.5;
|
|
64
|
+
--leading-relaxed: 1.75;
|
|
65
|
+
|
|
66
|
+
--space-1: 0.25rem;
|
|
67
|
+
--space-2: 0.5rem;
|
|
68
|
+
--space-3: 0.75rem;
|
|
69
|
+
--space-4: 1rem;
|
|
70
|
+
--space-5: 1.25rem;
|
|
71
|
+
--space-6: 1.5rem;
|
|
72
|
+
--space-8: 2rem;
|
|
73
|
+
--space-10: 2.5rem;
|
|
74
|
+
--space-12: 3rem;
|
|
75
|
+
|
|
76
|
+
--radius-sm: 0.25rem;
|
|
77
|
+
--radius-md: 0.375rem;
|
|
78
|
+
--radius-lg: 0.5rem;
|
|
79
|
+
--radius-xl: 0.75rem;
|
|
80
|
+
|
|
81
|
+
--transition-fast: 100ms ease;
|
|
82
|
+
--transition-normal: 150ms ease;
|
|
83
|
+
--transition-slow: 200ms ease-out;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
:root[data-theme="dark"] {
|
|
87
|
+
--bg-primary: #1a1a24;
|
|
88
|
+
--bg-secondary: #232736;
|
|
89
|
+
--bg-elevated: #2d3344;
|
|
90
|
+
--bg-overlay: rgba(0, 0, 0, 0.7);
|
|
91
|
+
--bg-hover: rgba(255, 255, 255, 0.06);
|
|
92
|
+
--bg-active: rgba(255, 255, 255, 0.10);
|
|
93
|
+
|
|
94
|
+
--text-primary: #e8edf4;
|
|
95
|
+
--text-secondary: #a8b4c8;
|
|
96
|
+
--text-muted: #6b7a8f;
|
|
97
|
+
|
|
98
|
+
--border-light: #3a4256;
|
|
99
|
+
--border-medium: #4a5572;
|
|
100
|
+
--border-focus: #5b9cd4;
|
|
101
|
+
|
|
102
|
+
--badge-schema: #5b9cd4;
|
|
103
|
+
--badge-schema-bg: rgba(91, 156, 212, 0.2);
|
|
104
|
+
--badge-property: #60c3a7;
|
|
105
|
+
--badge-property-bg: rgba(96, 195, 167, 0.2);
|
|
106
|
+
--badge-definition: #dbab3e;
|
|
107
|
+
--badge-definition-bg: rgba(219, 171, 62, 0.2);
|
|
108
|
+
--badge-required: #5b9cd4;
|
|
109
|
+
--badge-required-bg: rgba(91, 156, 212, 0.2);
|
|
110
|
+
--badge-deprecated: #b31f24;
|
|
111
|
+
--badge-deprecated-bg: rgba(179, 31, 36, 0.2);
|
|
112
|
+
|
|
113
|
+
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
|
|
114
|
+
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.5), 0 2px 4px -1px rgba(0, 0, 0, 0.3);
|
|
115
|
+
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.6), 0 4px 6px -2px rgba(0, 0, 0, 0.4);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
119
|
+
|
|
120
|
+
html {
|
|
121
|
+
font-size: 16px;
|
|
122
|
+
-webkit-font-smoothing: antialiased;
|
|
123
|
+
-moz-osx-font-smoothing: grayscale;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
body {
|
|
127
|
+
font-family: var(--font-sans);
|
|
128
|
+
font-size: var(--text-base);
|
|
129
|
+
line-height: var(--leading-normal);
|
|
130
|
+
color: var(--text-primary);
|
|
131
|
+
background-color: var(--bg-primary);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
h1, h2, h3, h4, h5, h6 {
|
|
135
|
+
font-weight: 600;
|
|
136
|
+
line-height: var(--leading-tight);
|
|
137
|
+
color: var(--text-primary);
|
|
138
|
+
}
|
|
139
|
+
h1 { font-size: var(--text-2xl); }
|
|
140
|
+
h2 { font-size: var(--text-xl); }
|
|
141
|
+
h3 { font-size: var(--text-lg); }
|
|
142
|
+
|
|
143
|
+
code, pre { font-family: var(--font-mono); font-size: 0.9em; }
|
|
144
|
+
code { background: var(--bg-secondary); padding: 0.125em 0.375em; border-radius: var(--radius-sm); }
|
|
145
|
+
pre { background: var(--bg-secondary); padding: var(--space-4); border-radius: var(--radius-md); overflow-x: auto; }
|
|
146
|
+
pre code { background: none; padding: 0; display: block; }
|
|
147
|
+
|
|
148
|
+
a { color: var(--color-primary); text-decoration: none; transition: color var(--transition-fast); }
|
|
149
|
+
a:hover { color: var(--color-primary-dark); text-decoration: underline; }
|
|
150
|
+
|
|
151
|
+
button { font-family: inherit; font-size: inherit; cursor: pointer; border: none; background: none; }
|
|
152
|
+
|
|
153
|
+
.btn {
|
|
154
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
155
|
+
gap: var(--space-2); padding: var(--space-2) var(--space-4);
|
|
156
|
+
font-size: var(--text-sm); font-weight: 500; border-radius: var(--radius-md);
|
|
157
|
+
transition: all var(--transition-fast);
|
|
158
|
+
}
|
|
159
|
+
.btn-primary { background: var(--color-primary); color: white; }
|
|
160
|
+
.btn-primary:hover { background: var(--color-primary-dark); }
|
|
161
|
+
.btn-secondary { background: var(--bg-secondary); color: var(--text-primary); border: 1px solid var(--border-medium); }
|
|
162
|
+
.btn-secondary:hover { background: var(--bg-hover); }
|
|
163
|
+
.btn-ghost { color: var(--text-secondary); }
|
|
164
|
+
.btn-ghost:hover { background: var(--bg-hover); color: var(--text-primary); }
|
|
165
|
+
|
|
166
|
+
.badge {
|
|
167
|
+
display: inline-flex; align-items: center; padding: 0.125rem 0.5rem;
|
|
168
|
+
font-size: var(--text-xs); font-weight: 500; border-radius: var(--radius-sm);
|
|
169
|
+
text-transform: uppercase; letter-spacing: 0.025em;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.card {
|
|
173
|
+
background: var(--bg-elevated); border: 1px solid var(--border-light);
|
|
174
|
+
border-radius: var(--radius-lg); box-shadow: var(--shadow-sm);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
|
|
178
|
+
.table th, .table td { padding: var(--space-2) var(--space-3); text-align: left; border-bottom: 1px solid var(--border-light); }
|
|
179
|
+
.table th { font-weight: 500; color: var(--text-secondary); background: var(--bg-secondary); }
|
|
180
|
+
.table tr:hover td { background: var(--bg-hover); }
|
|
181
|
+
|
|
182
|
+
::-webkit-scrollbar { width: 8px; height: 8px; }
|
|
183
|
+
::-webkit-scrollbar-track { background: transparent; }
|
|
184
|
+
::-webkit-scrollbar-thumb { background: var(--border-medium); border-radius: 4px; }
|
|
185
|
+
::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }
|
|
186
|
+
|
|
187
|
+
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
|
188
|
+
@keyframes slideIn { from { opacity: 0; transform: translateX(10px); } to { opacity: 1; transform: translateX(0); } }
|
|
189
|
+
|
|
190
|
+
.text-muted { color: var(--text-muted); }
|
|
191
|
+
.text-secondary { color: var(--text-secondary); }
|
|
192
|
+
.font-mono { font-family: var(--font-mono); }
|
|
193
|
+
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
194
|
+
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export interface SpaMetadata {
|
|
2
|
+
title?: string
|
|
3
|
+
version?: string
|
|
4
|
+
description?: string
|
|
5
|
+
baseUrl?: string
|
|
6
|
+
theme?: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface SpaProperty {
|
|
10
|
+
name: string
|
|
11
|
+
title?: string
|
|
12
|
+
description?: string
|
|
13
|
+
type?: string
|
|
14
|
+
format?: string
|
|
15
|
+
required?: boolean
|
|
16
|
+
default?: string
|
|
17
|
+
pattern?: string
|
|
18
|
+
enum?: string[]
|
|
19
|
+
/** Resolved $ref — normalized from backend's "$ref" key during loadFromWindow() */
|
|
20
|
+
ref?: string
|
|
21
|
+
/** Raw JSON Schema $ref — present in window.SCHEMA_DATA before normalization */
|
|
22
|
+
$ref?: string
|
|
23
|
+
minLength?: number
|
|
24
|
+
maxLength?: number
|
|
25
|
+
minimum?: number
|
|
26
|
+
maximum?: number
|
|
27
|
+
itemsType?: string
|
|
28
|
+
deprecated?: boolean
|
|
29
|
+
examples?: string[]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface SpaDefinition {
|
|
33
|
+
name: string
|
|
34
|
+
title?: string
|
|
35
|
+
description?: string
|
|
36
|
+
type?: string
|
|
37
|
+
properties: SpaProperty[]
|
|
38
|
+
required: string[]
|
|
39
|
+
examples?: string[]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface SpaSchema {
|
|
43
|
+
name: string
|
|
44
|
+
title?: string
|
|
45
|
+
description?: string
|
|
46
|
+
type?: string
|
|
47
|
+
properties: SpaProperty[]
|
|
48
|
+
definitions: SpaDefinition[]
|
|
49
|
+
required: string[]
|
|
50
|
+
examples?: string[]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface SpaSearchEntry {
|
|
54
|
+
name: string
|
|
55
|
+
title?: string
|
|
56
|
+
type: string
|
|
57
|
+
schemaName: string
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface SpaDocument {
|
|
61
|
+
metadata: SpaMetadata
|
|
62
|
+
schemas: SpaSchema[]
|
|
63
|
+
searchIndex: SpaSearchEntry[]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
declare global {
|
|
67
|
+
interface Window {
|
|
68
|
+
SCHEMA_DATA: SpaDocument
|
|
69
|
+
}
|
|
70
|
+
}
|