@conduction/nextcloud-vue 0.1.0-beta.2 → 0.1.0-beta.4

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 (153) hide show
  1. package/README.md +226 -226
  2. package/css/index.css +5 -0
  3. package/dist/nextcloud-vue.cjs.js +60455 -8755
  4. package/dist/nextcloud-vue.cjs.js.map +1 -1
  5. package/dist/nextcloud-vue.css +2062 -528
  6. package/dist/nextcloud-vue.esm.js +60411 -8731
  7. package/dist/nextcloud-vue.esm.js.map +1 -1
  8. package/package.json +75 -61
  9. package/src/components/CnActionsBar/CnActionsBar.vue +235 -225
  10. package/src/components/CnActionsBar/index.js +1 -1
  11. package/src/components/CnAdvancedFormDialog/CnAdvancedFormDialog.vue +579 -0
  12. package/src/components/CnAdvancedFormDialog/CnDataTab.vue +217 -0
  13. package/src/components/CnAdvancedFormDialog/CnMetadataTab.vue +121 -0
  14. package/src/components/CnAdvancedFormDialog/CnPropertiesTab.vue +418 -0
  15. package/src/components/CnAdvancedFormDialog/CnPropertyValueCell.vue +247 -0
  16. package/src/components/CnAdvancedFormDialog/index.js +1 -0
  17. package/src/components/CnCardGrid/CnCardGrid.vue +152 -152
  18. package/src/components/CnCardGrid/index.js +1 -1
  19. package/src/components/CnCellRenderer/CnCellRenderer.vue +132 -132
  20. package/src/components/CnCellRenderer/index.js +1 -1
  21. package/src/components/CnChartWidget/CnChartWidget.vue +320 -0
  22. package/src/components/CnChartWidget/index.js +1 -0
  23. package/src/components/CnConfigurationCard/CnConfigurationCard.vue +77 -77
  24. package/src/components/CnConfigurationCard/index.js +1 -1
  25. package/src/components/CnDashboardGrid/CnDashboardGrid.vue +225 -0
  26. package/src/components/CnDashboardGrid/index.js +1 -0
  27. package/src/components/CnDashboardPage/CnDashboardPage.vue +390 -0
  28. package/src/components/CnDashboardPage/index.js +1 -0
  29. package/src/components/CnDataTable/CnDataTable.vue +349 -349
  30. package/src/components/CnDataTable/index.js +1 -1
  31. package/src/components/CnDetailCard/CnDetailCard.vue +214 -0
  32. package/src/components/CnDetailCard/index.js +1 -0
  33. package/src/components/CnDetailPage/CnDetailPage.vue +281 -0
  34. package/src/components/CnDetailPage/index.js +1 -0
  35. package/src/components/CnFacetSidebar/CnFacetSidebar.vue +231 -223
  36. package/src/components/CnFacetSidebar/index.js +1 -1
  37. package/src/components/CnFilterBar/CnFilterBar.vue +152 -152
  38. package/src/components/CnFilterBar/index.js +1 -1
  39. package/src/components/CnIcon/CnIcon.vue +89 -89
  40. package/src/components/CnIcon/index.js +1 -1
  41. package/src/components/CnIndexPage/CnIndexPage.vue +874 -816
  42. package/src/components/CnIndexPage/index.js +1 -1
  43. package/src/components/CnIndexSidebar/CnIndexSidebar.vue +503 -484
  44. package/src/components/CnIndexSidebar/index.js +1 -1
  45. package/src/components/CnItemCard/CnItemCard.vue +132 -0
  46. package/src/components/CnItemCard/index.js +1 -0
  47. package/src/components/CnKpiGrid/CnKpiGrid.vue +89 -89
  48. package/src/components/CnKpiGrid/index.js +1 -1
  49. package/src/components/CnMassActionBar/CnMassActionBar.vue +160 -160
  50. package/src/components/CnMassActionBar/index.js +1 -1
  51. package/src/components/CnMassCopyDialog/CnMassCopyDialog.vue +320 -320
  52. package/src/components/CnMassCopyDialog/index.js +1 -1
  53. package/src/components/CnMassDeleteDialog/CnMassDeleteDialog.vue +238 -238
  54. package/src/components/CnMassDeleteDialog/index.js +1 -1
  55. package/src/components/CnMassExportDialog/CnMassExportDialog.vue +190 -190
  56. package/src/components/CnMassExportDialog/index.js +1 -1
  57. package/src/components/CnMassImportDialog/CnMassImportDialog.vue +491 -491
  58. package/src/components/CnMassImportDialog/index.js +1 -1
  59. package/src/components/CnNoteCard/CnNoteCard.vue +149 -0
  60. package/src/components/CnNoteCard/index.js +1 -0
  61. package/src/components/CnNotesCard/CnNotesCard.vue +413 -0
  62. package/src/components/CnNotesCard/index.js +1 -0
  63. package/src/components/CnObjectCard/CnObjectCard.vue +292 -292
  64. package/src/components/CnObjectCard/index.js +1 -1
  65. package/src/components/CnObjectSidebar/CnObjectSidebar.vue +876 -0
  66. package/src/components/CnObjectSidebar/index.js +1 -0
  67. package/src/components/CnPageHeader/CnPageHeader.vue +57 -57
  68. package/src/components/CnPageHeader/index.js +1 -1
  69. package/src/components/CnPagination/CnPagination.vue +252 -252
  70. package/src/components/CnPagination/index.js +1 -1
  71. package/src/components/CnRowActions/CnRowActions.vue +73 -73
  72. package/src/components/CnRowActions/index.js +1 -1
  73. package/src/components/CnSchemaFormDialog/CnSchemaConfigurationTab.vue +226 -0
  74. package/src/components/CnSchemaFormDialog/CnSchemaFormDialog.vue +787 -0
  75. package/src/components/CnSchemaFormDialog/CnSchemaPropertiesTab.vue +305 -0
  76. package/src/components/CnSchemaFormDialog/CnSchemaPropertyActions.vue +1398 -0
  77. package/src/components/CnSchemaFormDialog/CnSchemaSecurityTab.vue +236 -0
  78. package/src/components/CnSchemaFormDialog/index.js +1 -0
  79. package/src/components/CnSettingsCard/CnSettingsCard.vue +92 -92
  80. package/src/components/CnSettingsCard/index.js +1 -1
  81. package/src/components/CnSettingsSection/CnSettingsSection.vue +266 -266
  82. package/src/components/CnSettingsSection/index.js +1 -1
  83. package/src/components/CnStatsBlock/CnStatsBlock.vue +420 -366
  84. package/src/components/CnStatsBlock/index.js +1 -1
  85. package/src/components/CnStatusBadge/CnStatusBadge.vue +77 -77
  86. package/src/components/CnStatusBadge/index.js +1 -1
  87. package/src/components/CnTabbedFormDialog/CnTabbedFormDialog.vue +540 -0
  88. package/src/components/CnTabbedFormDialog/index.js +1 -0
  89. package/src/components/CnTasksCard/CnTasksCard.vue +373 -0
  90. package/src/components/CnTasksCard/index.js +1 -0
  91. package/src/components/CnTileWidget/CnTileWidget.vue +159 -0
  92. package/src/components/CnTileWidget/index.js +1 -0
  93. package/src/components/CnTimelineStages/CnTimelineStages.vue +292 -0
  94. package/src/components/CnTimelineStages/index.js +1 -0
  95. package/src/components/CnUserActionMenu/CnUserActionMenu.vue +435 -0
  96. package/src/components/CnUserActionMenu/index.js +1 -0
  97. package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +312 -312
  98. package/src/components/CnVersionInfoCard/index.js +1 -1
  99. package/src/components/CnWidgetRenderer/CnWidgetRenderer.vue +180 -0
  100. package/src/components/CnWidgetRenderer/index.js +1 -0
  101. package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +211 -0
  102. package/src/components/CnWidgetWrapper/index.js +1 -0
  103. package/src/components/index.js +43 -29
  104. package/src/composables/index.js +4 -3
  105. package/src/composables/useDashboardView.js +240 -0
  106. package/src/composables/useDetailView.js +289 -132
  107. package/src/composables/useListView.js +363 -153
  108. package/src/composables/useSubResource.js +142 -142
  109. package/src/constants/metadata.js +30 -30
  110. package/src/css/CnSchemaFormDialog.css +546 -0
  111. package/src/css/__sample_nextcloud_tokens.css +110 -0
  112. package/src/css/actions-bar.css +48 -48
  113. package/src/css/badge.css +51 -51
  114. package/src/css/card.css +128 -128
  115. package/src/css/dashboard.css +70 -0
  116. package/src/css/detail-page.css +168 -0
  117. package/src/css/detail.css +68 -68
  118. package/src/css/index-page.css +44 -32
  119. package/src/css/index-sidebar.css +193 -187
  120. package/src/css/index.css +16 -12
  121. package/src/css/layout.css +90 -90
  122. package/src/css/page-header.css +33 -33
  123. package/src/css/pagination.css +72 -72
  124. package/src/css/table.css +142 -142
  125. package/src/css/timeline-stages.css +218 -0
  126. package/src/css/utilities.css +46 -46
  127. package/src/index.js +72 -53
  128. package/src/store/createSubResourcePlugin.js +135 -135
  129. package/src/store/index.js +3 -3
  130. package/src/store/plugins/auditTrails.js +17 -17
  131. package/src/store/plugins/files.js +250 -186
  132. package/src/store/plugins/index.js +7 -5
  133. package/src/store/plugins/lifecycle.js +180 -180
  134. package/src/store/plugins/relations.js +68 -68
  135. package/src/store/plugins/search.js +372 -0
  136. package/src/store/plugins/selection.js +104 -0
  137. package/src/store/useObjectStore.js +829 -686
  138. package/src/types/auditTrail.d.ts +32 -32
  139. package/src/types/file.d.ts +23 -23
  140. package/src/types/index.d.ts +35 -35
  141. package/src/types/notification.d.ts +36 -36
  142. package/src/types/object.d.ts +40 -40
  143. package/src/types/organisation.d.ts +41 -41
  144. package/src/types/register.d.ts +25 -25
  145. package/src/types/schema.d.ts +39 -39
  146. package/src/types/shared.d.ts +79 -79
  147. package/src/types/source.d.ts +14 -14
  148. package/src/types/task.d.ts +31 -31
  149. package/src/utils/errors.js +96 -96
  150. package/src/utils/headers.js +68 -50
  151. package/src/utils/id.js +13 -0
  152. package/src/utils/index.js +3 -3
  153. package/src/utils/schema.js +422 -419
@@ -0,0 +1,579 @@
1
+ <template>
2
+ <NcDialog
3
+ :name="resolvedTitle"
4
+ size="large"
5
+ :can-close="!loading"
6
+ @closing="$emit('close')">
7
+ <!-- Result phase -->
8
+ <div v-if="result !== null" class="cn-advanced-form-dialog__result">
9
+ <NcNoteCard v-if="result.success" type="success">
10
+ {{ resolvedSuccessText }}
11
+ </NcNoteCard>
12
+ <NcNoteCard v-if="result.error" type="error">
13
+ {{ result.error }}
14
+ </NcNoteCard>
15
+ </div>
16
+
17
+ <!-- Form phase -->
18
+ <div v-else class="cn-advanced-form-dialog__form">
19
+ <!-- Full form override slot -->
20
+ <slot
21
+ v-if="$scopedSlots.form"
22
+ name="form"
23
+ :form-data="formData"
24
+ :update-field="updateField"
25
+ :object-properties="objectPropertiesForSlot"
26
+ :json-data="jsonData"
27
+ :update-json="updateJsonFromExternal"
28
+ :is-valid-json="isValidJson(jsonData)" />
29
+
30
+ <!-- Default content -->
31
+ <template v-else>
32
+ <!-- Register/schema selection step (optional slot) -->
33
+ <slot
34
+ v-if="$scopedSlots['register-schema-selection']"
35
+ name="register-schema-selection"
36
+ :proceed="proceedFromRegisterSchemaStep" />
37
+
38
+ <!-- Main tabs -->
39
+ <div v-else class="cn-advanced-form-dialog__tabs tabContainer">
40
+ <BTabs v-model="activeTab" content-class="mt-3" justified>
41
+ <!-- Properties tab -->
42
+ <BTab v-if="showPropertiesTable" title="Properties">
43
+ <slot
44
+ name="tab-properties"
45
+ :form-data="formData"
46
+ :update-field="updateField"
47
+ :object-properties="objectPropertiesForSlot"
48
+ :selected-property="selectedProperty"
49
+ :handle-row-click="onRowClick"
50
+ :get-property-display-name="getPropertyDisplayName"
51
+ :get-property-validation-class="getPropertyValidationClass"
52
+ :is-property-editable="isPropertyEditable"
53
+ :validation-display="validationDisplay">
54
+ <CnPropertiesTab
55
+ ref="propertiesTab"
56
+ :schema="schema"
57
+ :item="item"
58
+ :form-data="formData"
59
+ :selected-property="selectedProperty"
60
+ :editable-types="editableTypes"
61
+ :validation-display="validationDisplay"
62
+ :exclude-fields="excludeFields"
63
+ :include-fields="includeFields"
64
+ @update:property-value="onPropertyValueUpdate"
65
+ @update:selected-property="selectedProperty = $event" />
66
+ </slot>
67
+ </BTab>
68
+
69
+ <!-- Metadata tab -->
70
+ <BTab v-if="resolvedShowMetadataTab" title="Metadata">
71
+ <slot name="tab-metadata" :item="item" :form-data="formData">
72
+ <CnMetadataTab :item="item" :form-data="formData" />
73
+ </slot>
74
+ </BTab>
75
+
76
+ <!-- Data (JSON) tab -->
77
+ <BTab v-if="showJsonTab" title="Data">
78
+ <slot
79
+ name="tab-data"
80
+ :json-data="jsonData"
81
+ :update-json="updateJsonFromExternal"
82
+ :is-valid="isValidJson(jsonData)"
83
+ :format-json="formatJSON">
84
+ <CnDataTab
85
+ :value="jsonData"
86
+ :dark="jsonEditorDark"
87
+ @update:value="jsonData = $event"
88
+ @format="onFormatResult" />
89
+ </slot>
90
+ </BTab>
91
+ </BTabs>
92
+ </div>
93
+ </template>
94
+ </div>
95
+
96
+ <template #actions>
97
+ <slot name="actions-left" />
98
+ <NcButton @click="$emit('close')">
99
+ {{ result !== null ? closeLabel : cancelLabel }}
100
+ </NcButton>
101
+ <NcButton
102
+ v-if="result === null"
103
+ type="primary"
104
+ :disabled="loading"
105
+ @click="executeConfirm">
106
+ <template #icon>
107
+ <NcLoadingIcon v-if="loading" :size="20" />
108
+ <Plus v-else-if="isCreateMode" :size="20" />
109
+ <ContentSaveOutline v-else :size="20" />
110
+ </template>
111
+ {{ resolvedConfirmLabel }}
112
+ </NcButton>
113
+ <slot name="actions-right" />
114
+ </template>
115
+ </NcDialog>
116
+ </template>
117
+
118
+ <script>
119
+ import {
120
+ NcDialog,
121
+ NcButton,
122
+ NcNoteCard,
123
+ NcLoadingIcon,
124
+ } from '@nextcloud/vue'
125
+ import Plus from 'vue-material-design-icons/Plus.vue'
126
+ import ContentSaveOutline from 'vue-material-design-icons/ContentSaveOutline.vue'
127
+ import { BTabs, BTab } from 'bootstrap-vue'
128
+ import { fieldsFromSchema } from '../../utils/schema.js'
129
+ import CnPropertiesTab from './CnPropertiesTab.vue'
130
+ import CnMetadataTab from './CnMetadataTab.vue'
131
+ import CnDataTab from './CnDataTab.vue'
132
+
133
+ /** Schema types for which we have built-in inline editing support in the properties table. */
134
+ const EDITABLE_SUPPORTED_TYPES = ['string', 'number', 'integer', 'boolean']
135
+
136
+ /**
137
+ * CnAdvancedFormDialog — Create/edit dialog with properties table (click-to-edit), JSON tab, and optional store integration.
138
+ *
139
+ * When `item` is null, operates in create mode. When `item` is provided, operates in edit mode.
140
+ * Provides a richer UX than CnFormDialog: properties table with inline editing, Data (JSON) tab with CodeMirror,
141
+ * optional Metadata tab. Editable property types are determined by coded-in support; optional editablePropertyTypes
142
+ * prop can restrict or extend. Dialog size is fixed to large.
143
+ *
144
+ * @event confirm Emitted when the user confirms. Payload: formData object.
145
+ * @event close Emitted when the dialog should be closed.
146
+ */
147
+ export default {
148
+ name: 'CnAdvancedFormDialog',
149
+
150
+ components: {
151
+ NcDialog,
152
+ NcButton,
153
+ NcNoteCard,
154
+ NcLoadingIcon,
155
+ Plus,
156
+ ContentSaveOutline,
157
+ BTabs,
158
+ BTab,
159
+ CnPropertiesTab,
160
+ CnMetadataTab,
161
+ CnDataTab,
162
+ },
163
+
164
+ props: {
165
+ schema: { type: Object, default: null },
166
+ item: { type: Object, default: null },
167
+ dialogTitle: { type: String, default: '' },
168
+ nameField: { type: String, default: 'title' },
169
+ successText: { type: String, default: '' },
170
+ cancelLabel: { type: String, default: 'Cancel' },
171
+ closeLabel: { type: String, default: 'Close' },
172
+ confirmLabel: { type: String, default: '' },
173
+ excludeFields: { type: Array, default: () => [] },
174
+ includeFields: { type: Array, default: null },
175
+ fieldOverrides: { type: Object, default: () => ({}) },
176
+ showPropertiesTable: { type: Boolean, default: true },
177
+ showJsonTab: { type: Boolean, default: true },
178
+ showMetadataTab: { type: Boolean, default: null },
179
+ editablePropertyTypes: { type: Array, default: null },
180
+ validationDisplay: { type: String, default: 'indicator', validator: (v) => ['indicator', 'none'].includes(v) },
181
+ jsonEditorDark: { type: Boolean, default: false },
182
+ },
183
+
184
+ data() {
185
+ return {
186
+ formData: {},
187
+ jsonData: '',
188
+ activeTab: 0,
189
+ selectedProperty: null,
190
+ errors: {},
191
+ loading: false,
192
+ result: null,
193
+ closeTimeout: null,
194
+ isInternalUpdate: false,
195
+ }
196
+ },
197
+
198
+ computed: {
199
+ isCreateMode() {
200
+ return !this.item
201
+ },
202
+
203
+ schemaTitle() {
204
+ return (this.schema && this.schema.title) || 'Item'
205
+ },
206
+
207
+ currentSchema() {
208
+ return this.schema
209
+ },
210
+
211
+ resolvedTitle() {
212
+ if (this.dialogTitle) return this.dialogTitle
213
+ return this.isCreateMode
214
+ ? `Create ${this.schemaTitle}`
215
+ : `Edit ${this.schemaTitle}`
216
+ },
217
+
218
+ resolvedConfirmLabel() {
219
+ if (this.confirmLabel) return this.confirmLabel
220
+ return this.isCreateMode ? 'Create' : 'Save'
221
+ },
222
+
223
+ resolvedSuccessText() {
224
+ if (this.successText) return this.successText
225
+ return `${this.schemaTitle} saved successfully.`
226
+ },
227
+
228
+ resolvedShowMetadataTab() {
229
+ if (this.showMetadataTab !== null) return this.showMetadataTab
230
+ return !!this.item
231
+ },
232
+
233
+ resolvedFields() {
234
+ return fieldsFromSchema(this.schema, {
235
+ exclude: this.excludeFields,
236
+ include: this.includeFields,
237
+ overrides: this.fieldOverrides,
238
+ includeReadOnly: true,
239
+ })
240
+ },
241
+
242
+ /** objectProperties exposed to the #form and #tab-properties slot consumers */
243
+ objectPropertiesForSlot() {
244
+ const schemaProps = this.schema?.properties || {}
245
+ const obj = this.item || {}
246
+ const exclude = this.excludeFields || []
247
+ const include = this.includeFields
248
+ const filterKey = (k) => {
249
+ if (k === '@self' || k === 'id') return false
250
+ if (exclude.includes(k)) return false
251
+ if (include && !include.includes(k)) return false
252
+ return true
253
+ }
254
+ const existing = Object.entries(obj).filter(([k]) => filterKey(k))
255
+ const missing = []
256
+ for (const [key, prop] of Object.entries(schemaProps)) {
257
+ if (!filterKey(key)) continue
258
+ if (!Object.prototype.hasOwnProperty.call(obj, key)) {
259
+ let def
260
+ switch (prop.type) {
261
+ case 'string': def = prop.const ?? ''; break
262
+ case 'number':
263
+ case 'integer': def = 0; break
264
+ case 'boolean': def = false; break
265
+ case 'array': def = []; break
266
+ case 'object': def = {}; break
267
+ default: def = ''
268
+ }
269
+ missing.push([key, def])
270
+ }
271
+ }
272
+ return [...existing, ...missing]
273
+ },
274
+
275
+ dataTabIndex() {
276
+ let index = 0
277
+ if (this.showPropertiesTable) index++
278
+ if (this.resolvedShowMetadataTab) index++
279
+ return index
280
+ },
281
+
282
+ isDataTabActive() {
283
+ return this.showJsonTab && this.activeTab === this.dataTabIndex
284
+ },
285
+
286
+ editableTypes() {
287
+ if (this.editablePropertyTypes && this.editablePropertyTypes.length > 0) {
288
+ return this.editablePropertyTypes
289
+ }
290
+ return EDITABLE_SUPPORTED_TYPES
291
+ },
292
+ },
293
+
294
+ watch: {
295
+ item: {
296
+ immediate: true,
297
+ handler(newItem) {
298
+ this.initFormData(newItem)
299
+ },
300
+ },
301
+ jsonData(newVal) {
302
+ if (!this.isInternalUpdate && this.isValidJson(newVal)) {
303
+ this.updateFormFromJson()
304
+ }
305
+ },
306
+ formData: {
307
+ handler() {
308
+ if (!this.isInternalUpdate) {
309
+ this.updateJsonFromForm()
310
+ }
311
+ },
312
+ deep: true,
313
+ },
314
+ },
315
+
316
+ beforeDestroy() {
317
+ if (this.closeTimeout) {
318
+ clearTimeout(this.closeTimeout)
319
+ this.closeTimeout = null
320
+ }
321
+ },
322
+
323
+ methods: {
324
+ proceedFromRegisterSchemaStep() {
325
+ // Placeholder for slot consumers
326
+ },
327
+
328
+ initFormData(item) {
329
+ if (item) {
330
+ this.formData = JSON.parse(JSON.stringify(item))
331
+ } else {
332
+ const data = {}
333
+ for (const field of this.resolvedFields) {
334
+ if (field.default !== null && field.default !== undefined) {
335
+ data[field.key] = field.default
336
+ } else if (field.widget === 'checkbox') {
337
+ data[field.key] = false
338
+ } else if (field.widget === 'tags' || field.widget === 'multiselect') {
339
+ data[field.key] = []
340
+ } else {
341
+ data[field.key] = null
342
+ }
343
+ }
344
+ this.formData = data
345
+ }
346
+ this.jsonData = JSON.stringify(this.formData, null, 2)
347
+ this.errors = {}
348
+ this.selectedProperty = null
349
+ },
350
+
351
+ updateField(key, value) {
352
+ this.$set(this.formData, key, value)
353
+ if (this.errors[key]) this.$delete(this.errors, key)
354
+ },
355
+
356
+ onPropertyValueUpdate({ key, value }) {
357
+ this.$set(this.formData, key, value)
358
+ if (this.errors[key]) this.$delete(this.errors, key)
359
+ },
360
+
361
+ onRowClick(key, event) {
362
+ // Forwarded for #tab-properties slot consumers — the sub-component handles it internally
363
+ },
364
+
365
+ /**
366
+ * Proxy for slot consumers: exposes isPropertyEditable from the tab sub-component.
367
+ * @param {string} key - Property key
368
+ * @param {*} value - Current property value
369
+ */
370
+ isPropertyEditable(key, value) {
371
+ const tab = this.$refs.propertiesTab
372
+ if (tab) return tab.isPropertyEditable(key, value)
373
+ return true
374
+ },
375
+
376
+ /**
377
+ * Proxy for slot consumers.
378
+ * @param {string} key - Property key
379
+ */
380
+ getPropertyDisplayName(key) {
381
+ const tab = this.$refs.propertiesTab
382
+ if (tab) return tab.getPropertyDisplayName(key)
383
+ return key
384
+ },
385
+
386
+ /**
387
+ * Proxy for slot consumers.
388
+ * @param {string} key - Property key
389
+ * @param {*} value - Current property value
390
+ */
391
+ getPropertyValidationClass(key, value) {
392
+ const tab = this.$refs.propertiesTab
393
+ if (tab) return tab.getPropertyValidationClass(key, value)
394
+ return ''
395
+ },
396
+
397
+ updateFormFromJson() {
398
+ if (this.isInternalUpdate) return
399
+ try {
400
+ this.isInternalUpdate = true
401
+ this.formData = JSON.parse(this.jsonData)
402
+ } catch {
403
+ // Keep previous formData
404
+ } finally {
405
+ this.$nextTick(() => { this.isInternalUpdate = false })
406
+ }
407
+ },
408
+
409
+ updateJsonFromForm() {
410
+ if (this.isInternalUpdate) return
411
+ try {
412
+ this.isInternalUpdate = true
413
+ this.jsonData = JSON.stringify(this.formData, null, 2)
414
+ } catch {
415
+ // Ignore
416
+ } finally {
417
+ this.$nextTick(() => { this.isInternalUpdate = false })
418
+ }
419
+ },
420
+
421
+ updateJsonFromExternal(newJson) {
422
+ this.jsonData = newJson
423
+ if (this.isValidJson(newJson)) this.updateFormFromJson()
424
+ },
425
+
426
+ isValidJson(str) {
427
+ if (!str || !str.trim()) return false
428
+ try {
429
+ JSON.parse(str)
430
+ return true
431
+ } catch {
432
+ return false
433
+ }
434
+ },
435
+
436
+ formatJSON() {
437
+ try {
438
+ if (this.jsonData) {
439
+ const parsed = JSON.parse(this.jsonData)
440
+ this.jsonData = JSON.stringify(parsed, null, 2)
441
+ if (!this.isInternalUpdate) {
442
+ this.isInternalUpdate = true
443
+ this.formData = parsed
444
+ this.$nextTick(() => { this.isInternalUpdate = false })
445
+ }
446
+ }
447
+ } catch {
448
+ // Keep invalid JSON as-is
449
+ }
450
+ },
451
+
452
+ onFormatResult(parsed) {
453
+ if (!this.isInternalUpdate) {
454
+ this.isInternalUpdate = true
455
+ this.formData = parsed
456
+ this.$nextTick(() => { this.isInternalUpdate = false })
457
+ }
458
+ },
459
+
460
+ validate() {
461
+ const newErrors = {}
462
+ for (const field of this.resolvedFields) {
463
+ const value = this.formData[field.key]
464
+ if (field.required && (value == null || value === '')) {
465
+ newErrors[field.key] = `${field.label} is required.`
466
+ }
467
+ }
468
+ this.errors = newErrors
469
+ return Object.keys(newErrors).length === 0
470
+ },
471
+
472
+ executeConfirm() {
473
+ if (!this.validate()) return
474
+ if (this.isDataTabActive && !this.isValidJson(this.jsonData)) return
475
+ this.$emit('confirm', JSON.parse(JSON.stringify(this.formData)))
476
+ },
477
+
478
+ setResult(resultData) {
479
+ this.loading = false
480
+ this.result = resultData
481
+ if (resultData?.success) {
482
+ this.closeTimeout = setTimeout(() => this.$emit('close'), 2000)
483
+ }
484
+ },
485
+ },
486
+ }
487
+ </script>
488
+
489
+ <style scoped>
490
+ .cn-advanced-form-dialog__form {
491
+ display: flex;
492
+ flex-direction: column;
493
+ gap: 8px;
494
+ }
495
+
496
+ .cn-advanced-form-dialog__tabs {
497
+ display: flex;
498
+ flex-direction: column;
499
+ gap: 12px;
500
+ }
501
+
502
+ /* Bootstrap-Vue tab styling to match ViewObject */
503
+ .tabContainer {
504
+ margin-top: 20px;
505
+ }
506
+
507
+ .tabContainer > * ul > li {
508
+ display: flex;
509
+ flex: 1;
510
+ }
511
+
512
+ .tabContainer > * ul > li:hover {
513
+ background-color: var(--color-background-hover);
514
+ }
515
+
516
+ .tabContainer > * ul > li > a {
517
+ flex: 1;
518
+ text-align: center;
519
+ }
520
+
521
+ .tabContainer > * ul > li > .active {
522
+ background: transparent !important;
523
+ color: var(--color-main-text) !important;
524
+ border-bottom: var(--default-grid-baseline) solid var(--color-primary-element) !important;
525
+ }
526
+
527
+ .tabContainer > * ul[role="tablist"] {
528
+ display: flex;
529
+ margin: 10px 8px 0 8px;
530
+ justify-content: space-between;
531
+ border-bottom: 1px solid var(--color-border);
532
+ }
533
+
534
+ .tabContainer > * ul[role="tablist"] > * a[role="tab"] {
535
+ padding-inline-start: 10px;
536
+ padding-inline-end: 10px;
537
+ padding-block-start: 10px;
538
+ padding-block-end: 10px;
539
+ }
540
+
541
+ .tabContainer > * div[role="tabpanel"] {
542
+ margin-block-start: var(--OR-margin-10);
543
+ }
544
+
545
+ :deep(.nav-tabs) {
546
+ border-bottom: 1px solid var(--color-border);
547
+ margin-bottom: 15px;
548
+ display: flex;
549
+ }
550
+
551
+ :deep(.nav-tabs .nav-item) {
552
+ display: flex;
553
+ flex: 1;
554
+ }
555
+
556
+ :deep(.nav-tabs .nav-link) {
557
+ flex: 1;
558
+ text-align: center;
559
+ border: none;
560
+ border-bottom: 2px solid transparent;
561
+ color: var(--color-text-maxcontrast);
562
+ padding: 8px 16px;
563
+ }
564
+
565
+ :deep(.nav-tabs .nav-link.active) {
566
+ color: var(--color-main-text);
567
+ border-bottom: 2px solid var(--color-primary);
568
+ background-color: transparent;
569
+ }
570
+
571
+ :deep(.nav-tabs .nav-link:hover) {
572
+ border-bottom: 2px solid var(--color-border);
573
+ }
574
+
575
+ :deep(.tab-content) {
576
+ padding: 16px;
577
+ background-color: var(--color-main-background);
578
+ }
579
+ </style>