@quazardous/qdadm 1.19.3
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.
- package/LICENSE +21 -0
- package/README.md +111 -0
- package/package.json +101 -0
- package/src/assets/logo.svg +6 -0
- package/src/auth/SessionAuthAdapter.ts +459 -0
- package/src/auth/index.ts +15 -0
- package/src/chain/ActiveStack.ts +154 -0
- package/src/chain/StackHydrator.ts +292 -0
- package/src/chain/index.ts +25 -0
- package/src/chain/useActiveStack.ts +120 -0
- package/src/chain/useStackHydrator.ts +155 -0
- package/src/components/BoolCell.vue +34 -0
- package/src/components/InfoBanner.vue +73 -0
- package/src/components/QdadmRoot.vue +52 -0
- package/src/components/SeverityTag.vue +86 -0
- package/src/components/dialogs/BulkStatusDialog.vue +60 -0
- package/src/components/dialogs/MultiStepDialog.vue +331 -0
- package/src/components/dialogs/SimpleDialog.vue +122 -0
- package/src/components/dialogs/UnsavedChangesDialog.vue +80 -0
- package/src/components/display/BannerZone.vue +65 -0
- package/src/components/display/CardsGrid.vue +72 -0
- package/src/components/display/CopyableId.vue +84 -0
- package/src/components/display/EmptyState.vue +62 -0
- package/src/components/display/IntensityBar.vue +190 -0
- package/src/components/display/RichCardsGrid.vue +101 -0
- package/src/components/edit/FormActions.vue +78 -0
- package/src/components/edit/FormField.vue +82 -0
- package/src/components/edit/FormInput.vue +158 -0
- package/src/components/edit/FormPage.vue +260 -0
- package/src/components/edit/FormTab.vue +217 -0
- package/src/components/edit/FormTabs.vue +120 -0
- package/src/components/edit/LookupField.vue +406 -0
- package/src/components/edit/LookupPickerDialog.vue +230 -0
- package/src/components/editors/JsonEditorFoldable.vue +470 -0
- package/src/components/editors/JsonStructuredField.vue +264 -0
- package/src/components/editors/JsonViewer.vue +51 -0
- package/src/components/editors/KeyValueEditor.vue +314 -0
- package/src/components/editors/LanguageEditor.vue +259 -0
- package/src/components/editors/PermissionEditor.vue +558 -0
- package/src/components/editors/ScopeEditor.vue +372 -0
- package/src/components/editors/VanillaJsonEditor.vue +183 -0
- package/src/components/index.ts +83 -0
- package/src/components/item/FieldGroups.vue +392 -0
- package/src/components/layout/AppLayout.vue +1032 -0
- package/src/components/layout/BaseLayout.vue +190 -0
- package/src/components/layout/Breadcrumb.vue +55 -0
- package/src/components/layout/DashboardLayout.vue +104 -0
- package/src/components/layout/FormLayout.vue +256 -0
- package/src/components/layout/ListLayout.vue +278 -0
- package/src/components/layout/PageHeader.vue +156 -0
- package/src/components/layout/PageLayout.vue +126 -0
- package/src/components/layout/PageNav.vue +192 -0
- package/src/components/layout/SidebarBox.vue +87 -0
- package/src/components/layout/Zone.vue +182 -0
- package/src/components/layout/defaults/DefaultBreadcrumb.vue +149 -0
- package/src/components/layout/defaults/DefaultFooter.vue +89 -0
- package/src/components/layout/defaults/DefaultFormActions.vue +67 -0
- package/src/components/layout/defaults/DefaultHeader.vue +78 -0
- package/src/components/layout/defaults/DefaultMenu.vue +188 -0
- package/src/components/layout/defaults/DefaultPagination.vue +75 -0
- package/src/components/layout/defaults/DefaultTable.vue +137 -0
- package/src/components/layout/defaults/DefaultUserInfo.vue +111 -0
- package/src/components/layout/defaults/index.ts +16 -0
- package/src/components/lists/ActionButtons.vue +49 -0
- package/src/components/lists/ActionColumn.vue +40 -0
- package/src/components/lists/FilterBar.vue +54 -0
- package/src/components/lists/ListPage.vue +467 -0
- package/src/components/pages/LoginPage.vue +334 -0
- package/src/components/pages/NotFoundPage.vue +98 -0
- package/src/components/show/ShowDisplay.vue +343 -0
- package/src/components/show/ShowField.vue +67 -0
- package/src/components/show/ShowPage.vue +431 -0
- package/src/composables/index.ts +155 -0
- package/src/composables/useApp.ts +67 -0
- package/src/composables/useAuth.ts +98 -0
- package/src/composables/useBareForm.ts +319 -0
- package/src/composables/useBreadcrumb.ts +252 -0
- package/src/composables/useChildPage.ts +113 -0
- package/src/composables/useCurrentEntity.ts +60 -0
- package/src/composables/useDeferred.ts +94 -0
- package/src/composables/useDirtyState.ts +138 -0
- package/src/composables/useEntityItemFormPage.ts +942 -0
- package/src/composables/useEntityItemFormPage.types.ts +451 -0
- package/src/composables/useEntityItemPage.ts +438 -0
- package/src/composables/useEntityItemShowPage.ts +722 -0
- package/src/composables/useFieldManager.ts +692 -0
- package/src/composables/useForm.ts +544 -0
- package/src/composables/useGuardStore.ts +53 -0
- package/src/composables/useHooks.ts +56 -0
- package/src/composables/useInfoBanner.ts +217 -0
- package/src/composables/useJsonSyntax.ts +122 -0
- package/src/composables/useLayoutResolver.ts +329 -0
- package/src/composables/useListPage.ts +1535 -0
- package/src/composables/useListPage.types.ts +437 -0
- package/src/composables/useListPage.utils.ts +101 -0
- package/src/composables/useNavContext.ts +439 -0
- package/src/composables/useNavigation.ts +216 -0
- package/src/composables/useOptionsLookup.ts +323 -0
- package/src/composables/usePageTitle.ts +84 -0
- package/src/composables/useSSEBridge.ts +142 -0
- package/src/composables/useSemanticBreadcrumb.ts +222 -0
- package/src/composables/useSidebarState.ts +24 -0
- package/src/composables/useSignals.ts +52 -0
- package/src/composables/useUnsavedChangesGuard.ts +155 -0
- package/src/composables/useUserImpersonator.ts +317 -0
- package/src/composables/useZoneRegistry.ts +252 -0
- package/src/core/bundles.ts +558 -0
- package/src/core/decorator.ts +420 -0
- package/src/core/extension.ts +470 -0
- package/src/core/index.ts +59 -0
- package/src/deferred/DeferredRegistry.ts +353 -0
- package/src/deferred/index.ts +15 -0
- package/src/editors/index.ts +12 -0
- package/src/entity/EntityManager.cache.ts +530 -0
- package/src/entity/EntityManager.crud.ts +615 -0
- package/src/entity/EntityManager.interface.ts +78 -0
- package/src/entity/EntityManager.query.ts +221 -0
- package/src/entity/EntityManager.relations.ts +151 -0
- package/src/entity/EntityManager.ts +944 -0
- package/src/entity/EntityManager.types.ts +288 -0
- package/src/entity/auth/CompositeAuthAdapter.ts +194 -0
- package/src/entity/auth/EntityAuthAdapter.ts +119 -0
- package/src/entity/auth/PermissiveAdapter.ts +43 -0
- package/src/entity/auth/RoleHierarchy.ts +128 -0
- package/src/entity/auth/SecurityChecker.ts +202 -0
- package/src/entity/auth/factory.ts +203 -0
- package/src/entity/auth/index.ts +47 -0
- package/src/entity/factory.test.js +189 -0
- package/src/entity/factory.ts +146 -0
- package/src/entity/index.ts +49 -0
- package/src/entity/storage/ApiStorage.ts +228 -0
- package/src/entity/storage/IStorage.ts +100 -0
- package/src/entity/storage/LocalStorage.ts +200 -0
- package/src/entity/storage/MemoryStorage.ts +184 -0
- package/src/entity/storage/MockApiStorage.ts +318 -0
- package/src/entity/storage/SdkStorage.ts +377 -0
- package/src/entity/storage/factory.test.js +159 -0
- package/src/entity/storage/factory.ts +209 -0
- package/src/entity/storage/index.ts +75 -0
- package/src/gen/FieldMapper.ts +122 -0
- package/src/gen/StorageProfileFactory.ts +93 -0
- package/src/gen/connectors/BaseConnector.ts +150 -0
- package/src/gen/connectors/ManualConnector.test.js +499 -0
- package/src/gen/connectors/ManualConnector.ts +414 -0
- package/src/gen/connectors/OpenAPIConnector.test.js +836 -0
- package/src/gen/connectors/OpenAPIConnector.ts +693 -0
- package/src/gen/connectors/__fixtures__/sample-openapi.json +311 -0
- package/src/gen/connectors/index.ts +27 -0
- package/src/gen/createManagers.ts +245 -0
- package/src/gen/decorators.ts +134 -0
- package/src/gen/generateManagers.test.js +540 -0
- package/src/gen/generateManagers.ts +398 -0
- package/src/gen/index.ts +66 -0
- package/src/gen/schema.ts +248 -0
- package/src/gen/vite-plugin.ts +114 -0
- package/src/generated/managers/testManager.js +45 -0
- package/src/generated/managers/testManager.ts +46 -0
- package/src/hooks/HookRegistry.ts +16 -0
- package/src/hooks/index.ts +15 -0
- package/src/i18n/I18n.ts +344 -0
- package/src/i18n/IncrementalDomainProvider.ts +153 -0
- package/src/i18n/InlineTranslationProvider.ts +4 -0
- package/src/i18n/LazyTranslationProvider.ts +102 -0
- package/src/i18n/MessagesRegistry.ts +4 -0
- package/src/i18n/Resolver.ts +4 -0
- package/src/i18n/__tests__/I18n.test.ts +169 -0
- package/src/i18n/__tests__/IncrementalDomainProvider.test.ts +146 -0
- package/src/i18n/__tests__/LazyTranslationProvider.test.ts +100 -0
- package/src/i18n/__tests__/Resolver.test.ts +271 -0
- package/src/i18n/defaults/DefaultCoreProvider.ts +28 -0
- package/src/i18n/defaults/core.en.yml +55 -0
- package/src/i18n/defaults/core.fr.yml +55 -0
- package/src/i18n/index.ts +55 -0
- package/src/i18n/loaders/raw-modules.d.ts +15 -0
- package/src/i18n/loaders/yaml.ts +35 -0
- package/src/i18n/strategies.ts +4 -0
- package/src/i18n/types.ts +34 -0
- package/src/i18n/useI18n.ts +34 -0
- package/src/index.ts +453 -0
- package/src/kernel/EventRouter.ts +23 -0
- package/src/kernel/Kernel.api.ts +79 -0
- package/src/kernel/Kernel.auth.ts +120 -0
- package/src/kernel/Kernel.i18n.ts +29 -0
- package/src/kernel/Kernel.modules.ts +170 -0
- package/src/kernel/Kernel.registries.ts +237 -0
- package/src/kernel/Kernel.routing.ts +334 -0
- package/src/kernel/Kernel.ts +460 -0
- package/src/kernel/Kernel.types.ts +263 -0
- package/src/kernel/Kernel.vue.ts +304 -0
- package/src/kernel/KernelContext.entities.ts +80 -0
- package/src/kernel/KernelContext.events.ts +57 -0
- package/src/kernel/KernelContext.i18n.ts +37 -0
- package/src/kernel/KernelContext.permissions.ts +38 -0
- package/src/kernel/KernelContext.routing.ts +280 -0
- package/src/kernel/KernelContext.ts +252 -0
- package/src/kernel/KernelContext.types.ts +173 -0
- package/src/kernel/KernelContext.zones.ts +54 -0
- package/src/kernel/Module.ts +169 -0
- package/src/kernel/ModuleLoader.ts +645 -0
- package/src/kernel/SSEBridge.ts +11 -0
- package/src/kernel/SignalBus.ts +48 -0
- package/src/kernel/index.ts +56 -0
- package/src/module/index.ts +33 -0
- package/src/module/moduleRegistry.ts +492 -0
- package/src/modules/debug/AuthCollector.ts +501 -0
- package/src/modules/debug/Collector.ts +20 -0
- package/src/modules/debug/DebugBridge.ts +14 -0
- package/src/modules/debug/DebugModule.ts +220 -0
- package/src/modules/debug/EntitiesCollector.ts +839 -0
- package/src/modules/debug/ErrorCollector.ts +4 -0
- package/src/modules/debug/I18nCollector.ts +9 -0
- package/src/modules/debug/LocalStorageAdapter.ts +7 -0
- package/src/modules/debug/RouterCollector.ts +449 -0
- package/src/modules/debug/SignalCollector.ts +4 -0
- package/src/modules/debug/ToastCollector.ts +4 -0
- package/src/modules/debug/ZonesCollector.ts +447 -0
- package/src/modules/debug/components/DebugBar.vue +31 -0
- package/src/modules/debug/components/adminPanelsConfig.ts +42 -0
- package/src/modules/debug/components/index.ts +9 -0
- package/src/modules/debug/components/panels/AuthPanel.vue +267 -0
- package/src/modules/debug/components/panels/EntitiesPanel.vue +572 -0
- package/src/modules/debug/components/panels/I18nPanel.vue +738 -0
- package/src/modules/debug/components/panels/RouterPanel.vue +440 -0
- package/src/modules/debug/components/panels/ZonesPanel.vue +133 -0
- package/src/modules/debug/components/panels/index.ts +16 -0
- package/src/modules/debug/index.ts +58 -0
- package/src/modules/debug/styles.scss +2671 -0
- package/src/notifications/NotificationBadge.vue +35 -0
- package/src/notifications/NotificationListener.vue +71 -0
- package/src/notifications/NotificationModule.ts +76 -0
- package/src/notifications/NotificationPanel.vue +478 -0
- package/src/notifications/NotificationStore.ts +288 -0
- package/src/notifications/index.ts +42 -0
- package/src/notifications/styles.scss +32 -0
- package/src/orchestrator/Orchestrator.ts +427 -0
- package/src/orchestrator/index.ts +8 -0
- package/src/orchestrator/useOrchestrator.ts +80 -0
- package/src/plugin.ts +251 -0
- package/src/query/FilterQuery.ts +306 -0
- package/src/query/QueryExecutor.ts +341 -0
- package/src/query/index.ts +16 -0
- package/src/security/EntityRolesProvider.ts +385 -0
- package/src/security/PermissionMatcher.ts +152 -0
- package/src/security/PermissionRegistry.ts +273 -0
- package/src/security/PersistableRolesProvider.ts +584 -0
- package/src/security/RolesManager.ts +136 -0
- package/src/security/RolesProvider.ts +167 -0
- package/src/security/RolesProviderStorage.ts +211 -0
- package/src/security/SecurityModule.ts +94 -0
- package/src/security/StaticRolesProvider.ts +117 -0
- package/src/security/UsersManager.ts +163 -0
- package/src/security/index.ts +77 -0
- package/src/security/pages/RoleForm.vue +276 -0
- package/src/security/pages/RoleList.vue +123 -0
- package/src/styles/_alerts.scss +95 -0
- package/src/styles/_buttons.scss +391 -0
- package/src/styles/_cards.scss +241 -0
- package/src/styles/_code.scss +78 -0
- package/src/styles/_dialogs.scss +55 -0
- package/src/styles/_filter-bar.scss +70 -0
- package/src/styles/_forms.scss +434 -0
- package/src/styles/_helpers.scss +153 -0
- package/src/styles/_lists.scss +225 -0
- package/src/styles/_main.scss +356 -0
- package/src/styles/_markdown.scss +88 -0
- package/src/styles/_responsive.scss +255 -0
- package/src/styles/_show-pages.scss +186 -0
- package/src/styles/_sidebar.scss +380 -0
- package/src/styles/_states.scss +115 -0
- package/src/styles/_stats.scss +47 -0
- package/src/styles/_variables.scss +224 -0
- package/src/styles/index.scss +38 -0
- package/src/styles/theme/_components.scss +331 -0
- package/src/styles/theme/_index.scss +10 -0
- package/src/styles/theme/_tokens.scss +147 -0
- package/src/styles/theme/_utilities.scss +175 -0
- package/src/toast/ToastBridgeModule.ts +68 -0
- package/src/toast/ToastListener.vue +55 -0
- package/src/toast/index.ts +26 -0
- package/src/toast/useSignalToast.ts +154 -0
- package/src/types/index.ts +285 -0
- package/src/types/pluralize.d.ts +19 -0
- package/src/types/quarkernel.d.ts +50 -0
- package/src/utils/formatters.ts +174 -0
- package/src/utils/index.ts +35 -0
- package/src/utils/transformers.ts +131 -0
- package/src/vite/qdadmDebugPlugin.ts +401 -0
- package/src/vite-env.d.ts +50 -0
- package/src/zones/ZoneRegistry.ts +934 -0
- package/src/zones/index.ts +29 -0
- package/src/zones/zones.ts +191 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* JsonStructuredField - Reusable field with structured/JSON toggle
|
|
4
|
+
*
|
|
5
|
+
* A form field that shows either a structured view (slot) or raw JSON editor.
|
|
6
|
+
* Uses a mini SelectButton to toggle between views.
|
|
7
|
+
*
|
|
8
|
+
* Usage patterns:
|
|
9
|
+
*
|
|
10
|
+
* 1. Simple - same v-model for both views:
|
|
11
|
+
* <JsonStructuredField v-model="myData">
|
|
12
|
+
* <MyStructuredEditor v-model="myData" />
|
|
13
|
+
* </JsonStructuredField>
|
|
14
|
+
*
|
|
15
|
+
* 2. With separate JSON binding (for complex computed mappings):
|
|
16
|
+
* <JsonStructuredField v-model="myData" :jsonValue="jsonComputed" @update:jsonValue="onJsonUpdate">
|
|
17
|
+
* <MyStructuredEditor v-model="myData" />
|
|
18
|
+
* </JsonStructuredField>
|
|
19
|
+
*
|
|
20
|
+
* 3. Controlled mode (parent manages viewMode):
|
|
21
|
+
* <JsonStructuredField v-model="myData" v-model:mode="viewMode">
|
|
22
|
+
* <MyStructuredEditor v-model="myData" />
|
|
23
|
+
* </JsonStructuredField>
|
|
24
|
+
*
|
|
25
|
+
* Props:
|
|
26
|
+
* - modelValue: The JSON object to edit (used for JSON view if jsonValue not provided)
|
|
27
|
+
* - jsonValue: Optional separate binding for JSON editor (for complex computed mappings)
|
|
28
|
+
* - mode: Optional v-model for view mode ('structured' or 'json')
|
|
29
|
+
* - label: Optional label for the field
|
|
30
|
+
* - jsonHeight: Height of JSON editor (default: 400px)
|
|
31
|
+
* - jsonMode: Mode for JSON editor - 'tree' or 'text' (default: 'text')
|
|
32
|
+
* - defaultMode: Initial mode if not using v-model:mode (default: 'structured')
|
|
33
|
+
* - showToggle: Whether to show the toggle (default: true, useful to hide in nested usage)
|
|
34
|
+
* - guardInvalidJson: Block switching to structured view while JSON is invalid (default: true)
|
|
35
|
+
* - invalidJsonMessage: Error message shown when JSON is invalid (default: 'Invalid JSON — fix errors before switching views')
|
|
36
|
+
*
|
|
37
|
+
* Events:
|
|
38
|
+
* - update:modelValue, update:jsonValue, update:mode: standard v-model
|
|
39
|
+
* - json-error: emitted with true when JSON becomes invalid, false when valid again
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
import { ref, computed, watch } from 'vue'
|
|
43
|
+
import SelectButton from 'primevue/selectbutton'
|
|
44
|
+
import Message from 'primevue/message'
|
|
45
|
+
import VanillaJsonEditor from './VanillaJsonEditor.vue'
|
|
46
|
+
import type { Mode } from 'vanilla-jsoneditor'
|
|
47
|
+
|
|
48
|
+
type JsonValue = Record<string, unknown> | unknown[] | string | null
|
|
49
|
+
type ViewMode = 'structured' | 'json'
|
|
50
|
+
|
|
51
|
+
interface ViewModeOption {
|
|
52
|
+
label: string
|
|
53
|
+
value: ViewMode
|
|
54
|
+
icon: string
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface Props {
|
|
58
|
+
modelValue?: JsonValue
|
|
59
|
+
jsonValue?: JsonValue | null
|
|
60
|
+
mode?: ViewMode | null
|
|
61
|
+
label?: string | null
|
|
62
|
+
jsonHeight?: string
|
|
63
|
+
jsonMode?: Mode
|
|
64
|
+
defaultMode?: ViewMode
|
|
65
|
+
showToggle?: boolean
|
|
66
|
+
guardInvalidJson?: boolean
|
|
67
|
+
invalidJsonMessage?: string
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
71
|
+
modelValue: () => ({}),
|
|
72
|
+
jsonValue: null,
|
|
73
|
+
mode: null,
|
|
74
|
+
label: null,
|
|
75
|
+
jsonHeight: '400px',
|
|
76
|
+
jsonMode: 'text' as Mode,
|
|
77
|
+
defaultMode: 'structured',
|
|
78
|
+
showToggle: true,
|
|
79
|
+
guardInvalidJson: true,
|
|
80
|
+
invalidJsonMessage: 'Invalid JSON — fix errors before switching views'
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
const emit = defineEmits<{
|
|
84
|
+
'update:modelValue': [value: JsonValue]
|
|
85
|
+
'update:jsonValue': [value: JsonValue]
|
|
86
|
+
'update:mode': [value: ViewMode]
|
|
87
|
+
'json-error': [hasError: boolean]
|
|
88
|
+
}>()
|
|
89
|
+
|
|
90
|
+
// Internal view mode (used when not controlled externally)
|
|
91
|
+
const internalMode = ref<ViewMode>(props.defaultMode)
|
|
92
|
+
|
|
93
|
+
// Invalid JSON state, tracked from VanillaJsonEditor error/update events
|
|
94
|
+
const hasJsonError = ref<boolean>(false)
|
|
95
|
+
|
|
96
|
+
// Computed view mode - use external if provided, else internal
|
|
97
|
+
const viewMode = computed<ViewMode>({
|
|
98
|
+
get: () => props.mode ?? internalMode.value,
|
|
99
|
+
set: (val: ViewMode) => {
|
|
100
|
+
// Guard: block json → structured while JSON is invalid
|
|
101
|
+
if (
|
|
102
|
+
props.guardInvalidJson &&
|
|
103
|
+
hasJsonError.value &&
|
|
104
|
+
(props.mode ?? internalMode.value) === 'json' &&
|
|
105
|
+
val === 'structured'
|
|
106
|
+
) {
|
|
107
|
+
return
|
|
108
|
+
}
|
|
109
|
+
if (props.mode !== null) {
|
|
110
|
+
emit('update:mode', val)
|
|
111
|
+
} else {
|
|
112
|
+
internalMode.value = val
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
const viewModeOptions: ViewModeOption[] = [
|
|
118
|
+
{ label: 'Structured', value: 'structured', icon: 'pi pi-list' },
|
|
119
|
+
{ label: 'JSON', value: 'json', icon: 'pi pi-code' }
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
// Computed JSON value - use separate jsonValue if provided, else modelValue
|
|
123
|
+
const effectiveJsonValue = computed<JsonValue>(() => {
|
|
124
|
+
return props.jsonValue !== null ? props.jsonValue : props.modelValue
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
// Handle JSON editor updates (fires only when JSON is valid)
|
|
128
|
+
function onJsonUpdate(newValue: JsonValue): void {
|
|
129
|
+
if (hasJsonError.value) {
|
|
130
|
+
hasJsonError.value = false
|
|
131
|
+
emit('json-error', false)
|
|
132
|
+
}
|
|
133
|
+
if (props.jsonValue !== null) {
|
|
134
|
+
// Using separate jsonValue binding
|
|
135
|
+
emit('update:jsonValue', newValue)
|
|
136
|
+
} else {
|
|
137
|
+
// Using modelValue for both
|
|
138
|
+
emit('update:modelValue', newValue)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Track invalid JSON state from the editor
|
|
143
|
+
function onJsonError(): void {
|
|
144
|
+
if (!hasJsonError.value) {
|
|
145
|
+
hasJsonError.value = true
|
|
146
|
+
emit('json-error', true)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Sync internal mode with prop if it changes
|
|
151
|
+
watch(() => props.mode, (newMode: ViewMode | null | undefined) => {
|
|
152
|
+
if (newMode !== null && newMode !== undefined) {
|
|
153
|
+
internalMode.value = newMode
|
|
154
|
+
}
|
|
155
|
+
})
|
|
156
|
+
</script>
|
|
157
|
+
|
|
158
|
+
<template>
|
|
159
|
+
<div class="json-structured-field">
|
|
160
|
+
<!-- Header with label and mode toggle -->
|
|
161
|
+
<div v-if="showToggle || label" class="field-header">
|
|
162
|
+
<label v-if="label" class="field-label">{{ label }}</label>
|
|
163
|
+
<SelectButton
|
|
164
|
+
v-if="showToggle"
|
|
165
|
+
v-model="viewMode"
|
|
166
|
+
:options="viewModeOptions"
|
|
167
|
+
optionLabel="label"
|
|
168
|
+
optionValue="value"
|
|
169
|
+
:allowEmpty="false"
|
|
170
|
+
class="mode-toggle"
|
|
171
|
+
>
|
|
172
|
+
<template #option="{ option }">
|
|
173
|
+
<i :class="option.icon"></i>
|
|
174
|
+
<span class="toggle-label">{{ option.label }}</span>
|
|
175
|
+
</template>
|
|
176
|
+
</SelectButton>
|
|
177
|
+
</div>
|
|
178
|
+
|
|
179
|
+
<!-- JSON View -->
|
|
180
|
+
<div v-if="viewMode === 'json'" class="json-view">
|
|
181
|
+
<Message
|
|
182
|
+
v-if="guardInvalidJson && hasJsonError"
|
|
183
|
+
severity="error"
|
|
184
|
+
:closable="false"
|
|
185
|
+
class="json-error"
|
|
186
|
+
>
|
|
187
|
+
{{ invalidJsonMessage }}
|
|
188
|
+
</Message>
|
|
189
|
+
<VanillaJsonEditor
|
|
190
|
+
:modelValue="effectiveJsonValue"
|
|
191
|
+
:mode="jsonMode"
|
|
192
|
+
:height="jsonHeight"
|
|
193
|
+
@update:modelValue="onJsonUpdate"
|
|
194
|
+
@error="onJsonError"
|
|
195
|
+
/>
|
|
196
|
+
</div>
|
|
197
|
+
|
|
198
|
+
<!-- Structured View (slot) -->
|
|
199
|
+
<div v-else class="structured-view">
|
|
200
|
+
<slot></slot>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
</template>
|
|
204
|
+
|
|
205
|
+
<style scoped>
|
|
206
|
+
/*
|
|
207
|
+
* Only keep styles here that REQUIRE scoping (:deep, dynamic binding, component-specific overrides).
|
|
208
|
+
* Generic/reusable styles belong in src/styles/ partials (see _forms.scss, _cards.scss, etc.).
|
|
209
|
+
*/
|
|
210
|
+
.json-structured-field {
|
|
211
|
+
width: 100%;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.field-header {
|
|
215
|
+
display: flex;
|
|
216
|
+
justify-content: space-between;
|
|
217
|
+
align-items: center;
|
|
218
|
+
margin-bottom: 0.75rem;
|
|
219
|
+
flex-wrap: wrap;
|
|
220
|
+
gap: 0.5rem;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.field-label {
|
|
224
|
+
font-weight: 600;
|
|
225
|
+
font-size: 0.95rem;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.mode-toggle {
|
|
229
|
+
flex-shrink: 0;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/* Smaller toggle buttons */
|
|
233
|
+
.mode-toggle :deep(.p-button) {
|
|
234
|
+
padding: 0.35rem 0.6rem;
|
|
235
|
+
font-size: 0.8rem;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.mode-toggle :deep(.p-button i) {
|
|
239
|
+
font-size: 0.75rem;
|
|
240
|
+
margin-right: 0.25rem;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.toggle-label {
|
|
244
|
+
display: none;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
@media (min-width: 640px) {
|
|
248
|
+
.toggle-label {
|
|
249
|
+
display: inline;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.json-view {
|
|
254
|
+
margin-top: 0.25rem;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.json-error {
|
|
258
|
+
margin-bottom: 0.5rem;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.structured-view {
|
|
262
|
+
margin-top: 0.25rem;
|
|
263
|
+
}
|
|
264
|
+
</style>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* JsonViewer - Reusable JSON viewer component
|
|
4
|
+
*
|
|
5
|
+
* Simple formatted JSON display with syntax highlighting.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* <JsonViewer :model-value="jsonData" height="300px" />
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { computed } from 'vue'
|
|
12
|
+
import { highlightJson } from '../../composables/useJsonSyntax'
|
|
13
|
+
|
|
14
|
+
type JsonValue = Record<string, unknown> | unknown[] | string | number | boolean | null
|
|
15
|
+
|
|
16
|
+
interface Props {
|
|
17
|
+
modelValue?: JsonValue
|
|
18
|
+
height?: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
22
|
+
modelValue: () => ({}),
|
|
23
|
+
height: '300px'
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
// Parse value if string
|
|
27
|
+
const parsedValue = computed<JsonValue>(() => {
|
|
28
|
+
if (typeof props.modelValue === 'string') {
|
|
29
|
+
try {
|
|
30
|
+
return JSON.parse(props.modelValue) as JsonValue
|
|
31
|
+
} catch {
|
|
32
|
+
return props.modelValue
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return props.modelValue
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
// Format JSON with syntax highlighting
|
|
39
|
+
const formattedJson = computed<string>(() => {
|
|
40
|
+
if (parsedValue.value === null || parsedValue.value === undefined) {
|
|
41
|
+
return '<span class="json-null">null</span>'
|
|
42
|
+
}
|
|
43
|
+
return highlightJson(JSON.stringify(parsedValue.value, null, 2))
|
|
44
|
+
})
|
|
45
|
+
</script>
|
|
46
|
+
|
|
47
|
+
<template>
|
|
48
|
+
<div class="json-viewer" :style="{ maxHeight: height }">
|
|
49
|
+
<pre class="json-content" v-html="formattedJson"></pre>
|
|
50
|
+
</div>
|
|
51
|
+
</template>
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* KeyValueEditor - Reusable component for editing key-value pairs
|
|
4
|
+
*
|
|
5
|
+
* Props:
|
|
6
|
+
* - modelValue: Array of {key, value} objects
|
|
7
|
+
* - label: Field label
|
|
8
|
+
* - help: Help text
|
|
9
|
+
* - keyPlaceholder: Placeholder for key input
|
|
10
|
+
* - valuePlaceholder: Placeholder for value input (text mode only)
|
|
11
|
+
* - keySuggestions: Array of strings for autocomplete on key input
|
|
12
|
+
* - valueType: 'number' (default) or 'text'
|
|
13
|
+
* - min: Minimum value (default: 0, number mode only)
|
|
14
|
+
* - max: Maximum value (default: 1, number mode only)
|
|
15
|
+
* - step: Slider step (default: 0.1, number mode only)
|
|
16
|
+
* - showSign: Show +/- sign for values (default: false, number mode only)
|
|
17
|
+
* - colorize: Color negative/positive values (default: false, number mode only)
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { ref, computed } from 'vue'
|
|
21
|
+
import Button from 'primevue/button'
|
|
22
|
+
import InputText from 'primevue/inputtext'
|
|
23
|
+
import AutoComplete from 'primevue/autocomplete'
|
|
24
|
+
import Slider from 'primevue/slider'
|
|
25
|
+
|
|
26
|
+
interface KeyValueItem {
|
|
27
|
+
key: string
|
|
28
|
+
value: string | number
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface Props {
|
|
32
|
+
modelValue?: KeyValueItem[]
|
|
33
|
+
label?: string
|
|
34
|
+
help?: string
|
|
35
|
+
keyPlaceholder?: string
|
|
36
|
+
valuePlaceholder?: string
|
|
37
|
+
keySuggestions?: string[]
|
|
38
|
+
valueType?: 'number' | 'text'
|
|
39
|
+
min?: number
|
|
40
|
+
max?: number
|
|
41
|
+
step?: number
|
|
42
|
+
showSign?: boolean
|
|
43
|
+
colorize?: boolean
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
47
|
+
modelValue: () => [],
|
|
48
|
+
label: 'Key-Value Pairs',
|
|
49
|
+
help: '',
|
|
50
|
+
keyPlaceholder: 'Key',
|
|
51
|
+
valuePlaceholder: 'Value',
|
|
52
|
+
keySuggestions: () => [],
|
|
53
|
+
valueType: 'number',
|
|
54
|
+
min: 0,
|
|
55
|
+
max: 1,
|
|
56
|
+
step: 0.1,
|
|
57
|
+
showSign: false,
|
|
58
|
+
colorize: false
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
const emit = defineEmits<{
|
|
62
|
+
'update:modelValue': [value: KeyValueItem[]]
|
|
63
|
+
}>()
|
|
64
|
+
|
|
65
|
+
const items = computed<KeyValueItem[]>({
|
|
66
|
+
get: () => props.modelValue || [],
|
|
67
|
+
set: (value: KeyValueItem[]) => emit('update:modelValue', value)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
const isTextMode = computed<boolean>(() => props.valueType === 'text')
|
|
71
|
+
const hasKeySuggestions = computed<boolean>(() => props.keySuggestions.length > 0)
|
|
72
|
+
const filteredKeySuggestions = ref<string[]>([])
|
|
73
|
+
|
|
74
|
+
const newItem = ref<KeyValueItem>({
|
|
75
|
+
key: '',
|
|
76
|
+
value: props.valueType === 'text' ? '' : (props.min + props.max) / 2
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
// Filter key suggestions for autocomplete (exclude already used keys)
|
|
80
|
+
function searchKeys(event: { query: string }): void {
|
|
81
|
+
const query = event.query.toLowerCase()
|
|
82
|
+
const usedKeys = items.value.map(item => item.key)
|
|
83
|
+
filteredKeySuggestions.value = props.keySuggestions
|
|
84
|
+
.filter(k => !usedKeys.includes(k))
|
|
85
|
+
.filter(k => k.toLowerCase().includes(query))
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function formatValue(value: string | number): string {
|
|
89
|
+
if (isTextMode.value) {
|
|
90
|
+
return String(value)
|
|
91
|
+
}
|
|
92
|
+
const formatted = Number(value).toFixed(2)
|
|
93
|
+
if (props.showSign && Number(value) > 0) {
|
|
94
|
+
return '+' + formatted
|
|
95
|
+
}
|
|
96
|
+
return formatted
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function addItem(): void {
|
|
100
|
+
if (!newItem.value.key) return
|
|
101
|
+
if (isTextMode.value && !newItem.value.value) return
|
|
102
|
+
const updated = [...items.value, { ...newItem.value }]
|
|
103
|
+
emit('update:modelValue', updated)
|
|
104
|
+
newItem.value = {
|
|
105
|
+
key: '',
|
|
106
|
+
value: isTextMode.value ? '' : (props.min + props.max) / 2
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function removeItem(index: number): void {
|
|
111
|
+
const updated = items.value.filter((_, i) => i !== index)
|
|
112
|
+
emit('update:modelValue', updated)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function updateItemValue(index: number, value: string | number): void {
|
|
116
|
+
const updated = [...items.value]
|
|
117
|
+
const item = updated[index]
|
|
118
|
+
if (item) {
|
|
119
|
+
updated[index] = { key: item.key, value }
|
|
120
|
+
emit('update:modelValue', updated)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
</script>
|
|
124
|
+
|
|
125
|
+
<template>
|
|
126
|
+
<div class="form-field">
|
|
127
|
+
<label>{{ label }}</label>
|
|
128
|
+
<div class="kv-list">
|
|
129
|
+
<div v-if="items.length === 0" class="kv-empty">
|
|
130
|
+
No items added yet
|
|
131
|
+
</div>
|
|
132
|
+
<div v-for="(item, index) in items" :key="index" class="kv-item">
|
|
133
|
+
<span class="kv-key">{{ item.key }}</span>
|
|
134
|
+
<div class="kv-value-container">
|
|
135
|
+
<!-- Text mode: show text input -->
|
|
136
|
+
<template v-if="isTextMode">
|
|
137
|
+
<InputText
|
|
138
|
+
:modelValue="String(item.value)"
|
|
139
|
+
@update:modelValue="(v: string | undefined) => v !== undefined && updateItemValue(index, v)"
|
|
140
|
+
class="kv-text-input"
|
|
141
|
+
/>
|
|
142
|
+
</template>
|
|
143
|
+
<!-- Number mode: show slider -->
|
|
144
|
+
<template v-else>
|
|
145
|
+
<span
|
|
146
|
+
class="kv-value"
|
|
147
|
+
:class="{
|
|
148
|
+
negative: colorize && Number(item.value) < 0,
|
|
149
|
+
positive: colorize && Number(item.value) > 0
|
|
150
|
+
}"
|
|
151
|
+
>
|
|
152
|
+
{{ formatValue(item.value) }}
|
|
153
|
+
</span>
|
|
154
|
+
<Slider
|
|
155
|
+
:modelValue="Number(item.value)"
|
|
156
|
+
@update:modelValue="(v: number | number[]) => updateItemValue(index, Array.isArray(v) ? v[0] ?? 0 : v)"
|
|
157
|
+
:min="min"
|
|
158
|
+
:max="max"
|
|
159
|
+
:step="step"
|
|
160
|
+
style="width: 100px"
|
|
161
|
+
/>
|
|
162
|
+
</template>
|
|
163
|
+
</div>
|
|
164
|
+
<Button
|
|
165
|
+
icon="pi pi-times"
|
|
166
|
+
severity="danger"
|
|
167
|
+
text
|
|
168
|
+
rounded
|
|
169
|
+
size="small"
|
|
170
|
+
@click="removeItem(index)"
|
|
171
|
+
/>
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
<div class="kv-add">
|
|
175
|
+
<Button
|
|
176
|
+
icon="pi pi-plus"
|
|
177
|
+
@click="addItem"
|
|
178
|
+
:disabled="!newItem.key || (isTextMode && !newItem.value)"
|
|
179
|
+
v-tooltip.top="'Add item'"
|
|
180
|
+
/>
|
|
181
|
+
<!-- With autocomplete suggestions -->
|
|
182
|
+
<AutoComplete
|
|
183
|
+
v-if="hasKeySuggestions"
|
|
184
|
+
v-model="newItem.key"
|
|
185
|
+
:suggestions="filteredKeySuggestions"
|
|
186
|
+
@complete="searchKeys"
|
|
187
|
+
:placeholder="keyPlaceholder"
|
|
188
|
+
class="kv-add-input"
|
|
189
|
+
@keyup.enter="addItem"
|
|
190
|
+
/>
|
|
191
|
+
<!-- Without suggestions: plain input -->
|
|
192
|
+
<InputText
|
|
193
|
+
v-else
|
|
194
|
+
v-model="newItem.key"
|
|
195
|
+
:placeholder="keyPlaceholder"
|
|
196
|
+
class="kv-add-input"
|
|
197
|
+
@keyup.enter="addItem"
|
|
198
|
+
/>
|
|
199
|
+
<!-- Text mode: show text input for value -->
|
|
200
|
+
<template v-if="isTextMode">
|
|
201
|
+
<InputText
|
|
202
|
+
:modelValue="String(newItem.value)"
|
|
203
|
+
@update:modelValue="(v: string | undefined) => newItem.value = v ?? ''"
|
|
204
|
+
:placeholder="valuePlaceholder"
|
|
205
|
+
class="kv-add-value"
|
|
206
|
+
@keyup.enter="addItem"
|
|
207
|
+
/>
|
|
208
|
+
</template>
|
|
209
|
+
<!-- Number mode: show slider -->
|
|
210
|
+
<template v-else>
|
|
211
|
+
<div class="kv-slider">
|
|
212
|
+
<span>{{ formatValue(newItem.value) }}</span>
|
|
213
|
+
<Slider
|
|
214
|
+
:modelValue="Number(newItem.value)"
|
|
215
|
+
@update:modelValue="(v: number | number[]) => newItem.value = Array.isArray(v) ? v[0] ?? 0 : v"
|
|
216
|
+
:min="min"
|
|
217
|
+
:max="max"
|
|
218
|
+
:step="step"
|
|
219
|
+
style="width: 100px"
|
|
220
|
+
/>
|
|
221
|
+
</div>
|
|
222
|
+
</template>
|
|
223
|
+
</div>
|
|
224
|
+
<small v-if="help" class="form-field-help">{{ help }}</small>
|
|
225
|
+
</div>
|
|
226
|
+
</template>
|
|
227
|
+
|
|
228
|
+
<style scoped>
|
|
229
|
+
/*
|
|
230
|
+
* Only keep styles here that REQUIRE scoping (:deep, dynamic binding, component-specific overrides).
|
|
231
|
+
* Generic/reusable styles belong in src/styles/ partials (see _forms.scss, _cards.scss, etc.).
|
|
232
|
+
*/
|
|
233
|
+
.kv-list {
|
|
234
|
+
display: flex;
|
|
235
|
+
flex-direction: column;
|
|
236
|
+
gap: 0.5rem;
|
|
237
|
+
margin-bottom: 0.5rem;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.kv-item {
|
|
241
|
+
display: flex;
|
|
242
|
+
align-items: center;
|
|
243
|
+
gap: 0.5rem;
|
|
244
|
+
padding: 0.5rem;
|
|
245
|
+
background: var(--p-surface-50);
|
|
246
|
+
border-radius: 0.25rem;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.kv-key {
|
|
250
|
+
font-weight: 500;
|
|
251
|
+
min-width: 120px;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.kv-value-container {
|
|
255
|
+
display: flex;
|
|
256
|
+
align-items: center;
|
|
257
|
+
gap: 0.5rem;
|
|
258
|
+
flex: 1;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.kv-value {
|
|
262
|
+
min-width: 50px;
|
|
263
|
+
text-align: right;
|
|
264
|
+
font-family: monospace;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.kv-value.negative {
|
|
268
|
+
color: var(--p-red-500);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.kv-value.positive {
|
|
272
|
+
color: var(--p-green-500);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.kv-add {
|
|
276
|
+
display: flex;
|
|
277
|
+
align-items: center;
|
|
278
|
+
gap: 0.5rem;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.kv-add-input {
|
|
282
|
+
flex: 1;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
.kv-add-value {
|
|
286
|
+
flex: 1;
|
|
287
|
+
min-width: 150px;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.kv-text-input {
|
|
291
|
+
flex: 1;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.kv-slider {
|
|
295
|
+
display: flex;
|
|
296
|
+
align-items: center;
|
|
297
|
+
gap: 0.5rem;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.kv-slider span {
|
|
301
|
+
min-width: 50px;
|
|
302
|
+
text-align: right;
|
|
303
|
+
font-family: monospace;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.kv-empty {
|
|
307
|
+
padding: 0.75rem;
|
|
308
|
+
color: var(--p-text-secondary);
|
|
309
|
+
font-style: italic;
|
|
310
|
+
background: var(--p-surface-50);
|
|
311
|
+
border-radius: 0.25rem;
|
|
312
|
+
text-align: center;
|
|
313
|
+
}
|
|
314
|
+
</style>
|