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

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 (71) hide show
  1. package/dist/nextcloud-vue.cjs +67614 -0
  2. package/dist/nextcloud-vue.cjs.js +9559 -8983
  3. package/dist/nextcloud-vue.cjs.js.map +1 -1
  4. package/dist/nextcloud-vue.cjs.map +1 -0
  5. package/dist/nextcloud-vue.css +1231 -1231
  6. package/dist/nextcloud-vue.esm.js +9559 -8983
  7. package/dist/nextcloud-vue.esm.js.map +1 -1
  8. package/package.json +14 -5
  9. package/src/components/CnActionsBar/CnActionsBar.vue +235 -235
  10. package/src/components/CnAdvancedFormDialog/CnAdvancedFormDialog.vue +579 -579
  11. package/src/components/CnAdvancedFormDialog/CnDataTab.vue +217 -217
  12. package/src/components/CnAdvancedFormDialog/CnMetadataTab.vue +121 -121
  13. package/src/components/CnAdvancedFormDialog/CnPropertiesTab.vue +418 -418
  14. package/src/components/CnAdvancedFormDialog/CnPropertyValueCell.vue +247 -247
  15. package/src/components/CnCardGrid/CnCardGrid.vue +152 -152
  16. package/src/components/CnCellRenderer/CnCellRenderer.vue +132 -132
  17. package/src/components/CnChartWidget/CnChartWidget.vue +320 -320
  18. package/src/components/CnConfigurationCard/CnConfigurationCard.vue +77 -77
  19. package/src/components/CnCopyDialog/CnCopyDialog.vue +250 -250
  20. package/src/components/CnDashboardGrid/CnDashboardGrid.vue +225 -225
  21. package/src/components/CnDashboardPage/CnDashboardPage.vue +390 -390
  22. package/src/components/CnDataTable/CnDataTable.vue +349 -349
  23. package/src/components/CnDeleteDialog/CnDeleteDialog.vue +170 -170
  24. package/src/components/CnDetailCard/CnDetailCard.vue +214 -214
  25. package/src/components/CnDetailPage/CnDetailPage.vue +285 -281
  26. package/src/components/CnFacetSidebar/CnFacetSidebar.vue +231 -231
  27. package/src/components/CnFilterBar/CnFilterBar.vue +152 -152
  28. package/src/components/CnFormDialog/CnFormDialog.vue +302 -11
  29. package/src/components/CnIcon/CnIcon.vue +89 -89
  30. package/src/components/CnIndexPage/CnIndexPage.vue +884 -874
  31. package/src/components/CnIndexSidebar/CnIndexSidebar.vue +503 -503
  32. package/src/components/CnItemCard/CnItemCard.vue +132 -132
  33. package/src/components/CnKpiGrid/CnKpiGrid.vue +89 -89
  34. package/src/components/CnMassActionBar/CnMassActionBar.vue +160 -160
  35. package/src/components/CnMassCopyDialog/CnMassCopyDialog.vue +320 -320
  36. package/src/components/CnMassDeleteDialog/CnMassDeleteDialog.vue +238 -238
  37. package/src/components/CnMassExportDialog/CnMassExportDialog.vue +190 -190
  38. package/src/components/CnMassImportDialog/CnMassImportDialog.vue +491 -491
  39. package/src/components/CnNoteCard/CnNoteCard.vue +149 -149
  40. package/src/components/CnNotesCard/CnNotesCard.vue +413 -413
  41. package/src/components/CnObjectCard/CnObjectCard.vue +292 -292
  42. package/src/components/CnObjectCard/eslint-setup.md +235 -0
  43. package/src/components/CnObjectCard/package.json-or.json +132 -0
  44. package/src/components/CnObjectSidebar/CnObjectSidebar.vue +876 -876
  45. package/src/components/CnPageHeader/CnPageHeader.vue +57 -57
  46. package/src/components/CnPagination/CnPagination.vue +252 -252
  47. package/src/components/CnRegisterMapping/CnRegisterMapping.vue +792 -792
  48. package/src/components/CnRowActions/CnRowActions.vue +95 -73
  49. package/src/components/CnSchemaFormDialog/CnSchemaConfigurationTab.vue +226 -226
  50. package/src/components/CnSchemaFormDialog/CnSchemaFormDialog.vue +787 -787
  51. package/src/components/CnSchemaFormDialog/CnSchemaPropertiesTab.vue +305 -305
  52. package/src/components/CnSchemaFormDialog/CnSchemaPropertyActions.vue +1398 -1398
  53. package/src/components/CnSchemaFormDialog/CnSchemaSecurityTab.vue +236 -236
  54. package/src/components/CnSettingsCard/CnSettingsCard.vue +92 -92
  55. package/src/components/CnSettingsSection/CnSettingsSection.vue +266 -266
  56. package/src/components/CnStatsBlock/CnStatsBlock.vue +420 -420
  57. package/src/components/CnStatusBadge/CnStatusBadge.vue +77 -77
  58. package/src/components/CnTabbedFormDialog/CnTabbedFormDialog.vue +540 -540
  59. package/src/components/CnTasksCard/CnTasksCard.vue +373 -373
  60. package/src/components/CnTileWidget/CnTileWidget.vue +159 -159
  61. package/src/components/CnTimelineStages/CnTimelineStages.vue +292 -292
  62. package/src/components/CnUserActionMenu/CnUserActionMenu.vue +435 -435
  63. package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +312 -312
  64. package/src/components/CnWidgetRenderer/CnWidgetRenderer.vue +180 -180
  65. package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +211 -211
  66. package/src/index.js +1 -1
  67. package/src/types/notification.d.ts +13 -13
  68. package/src/types/organisation.d.ts +15 -15
  69. package/src/types/schema.d.ts +13 -13
  70. package/src/types/task.d.ts +6 -6
  71. package/src/utils/headers.js +5 -3
@@ -1,418 +1,418 @@
1
- <template>
2
- <div class="cn-advanced-form-dialog__table-container">
3
- <table class="cn-advanced-form-dialog__table">
4
- <thead>
5
- <tr class="cn-advanced-form-dialog__table-row">
6
- <th class="cn-advanced-form-dialog__table-col-constrained">
7
- Property
8
- </th>
9
- <th class="cn-advanced-form-dialog__table-col-expanded">
10
- Value
11
- </th>
12
- </tr>
13
- </thead>
14
- <tbody>
15
- <tr
16
- v-for="([key, value]) in objectProperties"
17
- :key="key"
18
- class="cn-advanced-form-dialog__table-row"
19
- :class="{
20
- 'cn-advanced-form-dialog__table-row--selected': selectedProperty === key,
21
- 'cn-advanced-form-dialog__table-row--edited': formData[key] !== undefined,
22
- 'cn-advanced-form-dialog__table-row--non-editable': !isPropertyEditable(key, resolvedValue(key, value)),
23
- [getPropertyValidationClass(key, value)]: validationDisplay === 'indicator',
24
- }"
25
- @click="handleRowClick(key, $event)">
26
- <td class="cn-advanced-form-dialog__table-col-constrained cn-advanced-form-dialog__prop-cell">
27
- <div class="cn-advanced-form-dialog__prop-cell-content">
28
- <AlertCircle
29
- v-if="validationDisplay === 'indicator' && getPropertyValidationState(key, resolvedValue(key, value)) === 'invalid'"
30
- class="cn-advanced-form-dialog__validation-icon cn-advanced-form-dialog__validation-icon--error"
31
- :size="16"
32
- :title="getPropertyErrorMessage(key, resolvedValue(key, value))" />
33
- <Alert
34
- v-else-if="validationDisplay === 'indicator' && getPropertyValidationState(key, resolvedValue(key, value)) === 'warning'"
35
- class="cn-advanced-form-dialog__validation-icon cn-advanced-form-dialog__validation-icon--warning"
36
- :size="16"
37
- :title="getPropertyWarningMessage(key, resolvedValue(key, value))" />
38
- <Plus
39
- v-else-if="validationDisplay === 'indicator' && getPropertyValidationState(key, resolvedValue(key, value)) === 'new'"
40
- class="cn-advanced-form-dialog__validation-icon cn-advanced-form-dialog__validation-icon--new"
41
- :size="16"
42
- :title="getPropertyNewMessage(key)" />
43
- <LockOutline
44
- v-else-if="!isPropertyEditable(key, resolvedValue(key, value))"
45
- class="cn-advanced-form-dialog__validation-icon cn-advanced-form-dialog__validation-icon--lock"
46
- :size="16"
47
- :title="getEditabilityWarning(key, resolvedValue(key, value)) || ''" />
48
- <span :title="getPropertyTooltip(key)">{{ getPropertyDisplayName(key) }}</span>
49
- </div>
50
- </td>
51
- <td class="cn-advanced-form-dialog__table-col-expanded cn-advanced-form-dialog__value-cell">
52
- <CnPropertyValueCell
53
- :ref="'cell-' + key"
54
- :property-key="key"
55
- :schema="schema"
56
- :value="resolvedValue(key, value)"
57
- :is-editable="isPropertyEditable(key, resolvedValue(key, value))"
58
- :is-editing="selectedProperty === key"
59
- :display-name="getPropertyDisplayName(key)"
60
- :editability-warning="getPropertyEditabilityWarning(key, resolvedValue(key, value))"
61
- @update:value="onPropertyValueUpdate(key, $event)" />
62
- </td>
63
- </tr>
64
- </tbody>
65
- </table>
66
- </div>
67
- </template>
68
-
69
- <script>
70
- import AlertCircle from 'vue-material-design-icons/AlertCircle.vue'
71
- import Alert from 'vue-material-design-icons/Alert.vue'
72
- import LockOutline from 'vue-material-design-icons/LockOutline.vue'
73
- import Plus from 'vue-material-design-icons/Plus.vue'
74
- import CnPropertyValueCell from './CnPropertyValueCell.vue'
75
-
76
- export default {
77
- name: 'CnPropertiesTab',
78
-
79
- components: {
80
- AlertCircle,
81
- Alert,
82
- LockOutline,
83
- Plus,
84
- CnPropertyValueCell,
85
- },
86
-
87
- props: {
88
- schema: { type: Object, default: null },
89
- item: { type: Object, default: null },
90
- formData: { type: Object, default: () => ({}) },
91
- selectedProperty: { type: String, default: null },
92
- editableTypes: { type: Array, default: () => ['string', 'number', 'integer', 'boolean'] },
93
- validationDisplay: { type: String, default: 'indicator' },
94
- excludeFields: { type: Array, default: () => [] },
95
- includeFields: { type: Array, default: null },
96
- },
97
-
98
- computed: {
99
- objectProperties() {
100
- const schemaProps = this.schema?.properties || {}
101
- const obj = this.item || {}
102
- const exclude = this.excludeFields || []
103
- const include = this.includeFields
104
- const filterKey = (k) => {
105
- if (k === '@self' || k === 'id') return false
106
- if (exclude.includes(k)) return false
107
- if (include && !include.includes(k)) return false
108
- return true
109
- }
110
- const existing = Object.entries(obj).filter(([k]) => filterKey(k))
111
- const missing = []
112
- for (const [key, prop] of Object.entries(schemaProps)) {
113
- if (!filterKey(key)) continue
114
- if (!Object.prototype.hasOwnProperty.call(obj, key)) {
115
- let def
116
- switch (prop.type) {
117
- case 'string': def = prop.const ?? ''; break
118
- case 'number':
119
- case 'integer': def = 0; break
120
- case 'boolean': def = false; break
121
- case 'array': def = []; break
122
- case 'object': def = {}; break
123
- default: def = ''
124
- }
125
- missing.push([key, def])
126
- }
127
- }
128
- return [...existing, ...missing]
129
- },
130
- },
131
-
132
- methods: {
133
- /** The effective value for a key: formData override or the object's own value */
134
- resolvedValue(key, objectValue) {
135
- return this.formData[key] !== undefined ? this.formData[key] : objectValue
136
- },
137
-
138
- onPropertyValueUpdate(key, value) {
139
- this.$emit('update:property-value', { key, value })
140
- },
141
-
142
- isPropertyEditable(key, value) {
143
- const prop = this.schema?.properties?.[key]
144
- if (!prop) return true
145
- if (prop.const !== undefined) return false
146
- if (prop.immutable && value != null && value !== '') return false
147
- const type = prop.type || 'string'
148
- return this.editableTypes.includes(type)
149
- },
150
-
151
- getPropertyDisplayName(key) {
152
- return (this.schema?.properties?.[key]?.title) || key
153
- },
154
-
155
- getPropertyTooltip(key) {
156
- const prop = this.schema?.properties?.[key]
157
- if (prop?.description) {
158
- if (prop.title && prop.title !== key) {
159
- return `${prop.title}: ${prop.description}`
160
- }
161
- return prop.description
162
- }
163
- return `Property: ${key}`
164
- },
165
-
166
- getPropertyValidationClass(key, value) {
167
- const state = this.getPropertyValidationState(key, value)
168
- switch (state) {
169
- case 'invalid': return 'cn-advanced-form-dialog__table-row--invalid'
170
- case 'warning': return 'cn-advanced-form-dialog__table-row--warning'
171
- case 'new': return 'cn-advanced-form-dialog__table-row--new'
172
- case 'valid': return 'cn-advanced-form-dialog__table-row--valid'
173
- default: return ''
174
- }
175
- },
176
-
177
- getPropertyValidationState(key, value) {
178
- const prop = this.schema?.properties?.[key]
179
- const existsInObject = this.item ? Object.prototype.hasOwnProperty.call(this.item, key) : false
180
- if (!prop) return 'warning'
181
- if (!existsInObject) return 'new'
182
- if (this.isValidPropertyValue(key, value, prop)) return 'valid'
183
- return 'invalid'
184
- },
185
-
186
- isValidPropertyValue(key, value, schemaProperty) {
187
- if (value === null || value === undefined || value === '') {
188
- const required = (this.schema?.required || []).includes(key) || schemaProperty?.required
189
- return !required
190
- }
191
- const type = schemaProperty?.type || 'string'
192
- switch (type) {
193
- case 'string':
194
- if (typeof value !== 'string') return false
195
- if (schemaProperty?.format === 'date-time' && !this.isValidDate(value)) return false
196
- if (schemaProperty?.const && value !== schemaProperty.const) return false
197
- return true
198
- case 'number':
199
- return typeof value === 'number' && !Number.isNaN(value)
200
- case 'boolean':
201
- return typeof value === 'boolean'
202
- case 'array':
203
- return Array.isArray(value)
204
- case 'object':
205
- return typeof value === 'object' && value !== null && !Array.isArray(value)
206
- default:
207
- return true
208
- }
209
- },
210
-
211
- getPropertyErrorMessage(key, value) {
212
- const prop = this.schema?.properties?.[key]
213
- if (!prop) {
214
- return `Property '${key}' is not defined in the current schema. This property exists in the object but is not part of the schema definition.`
215
- }
216
- const isRequired = (this.schema?.required || []).includes(key) || prop.required
217
- if ((value === null || value === undefined || value === '') && isRequired) {
218
- return `Required property '${key}' is missing or empty.`
219
- }
220
- const expectedType = prop.type
221
- const actualType = Array.isArray(value) ? 'array' : typeof value
222
- if (expectedType && expectedType !== actualType) {
223
- return `Property '${key}' should be ${expectedType} but is ${actualType}.`
224
- }
225
- if (prop.format === 'date-time' && !this.isValidDate(value)) {
226
- return `Property '${key}' should be a valid date-time value.`
227
- }
228
- if (prop.const && value !== prop.const) {
229
- return `Property '${key}' should be '${prop.const}' but is '${value}'.`
230
- }
231
- return `Property '${key}' has an invalid value.`
232
- },
233
-
234
- getPropertyWarningMessage(key, value) {
235
- const displayValue = String(value).length > 100 ? String(value).slice(0, 100) + '…' : String(value)
236
- return `Property '${key}' exists in the object but is not defined in the current schema. This might happen when property names are changed in the schema. Current value: '${displayValue}'.`
237
- },
238
-
239
- getPropertyNewMessage(key) {
240
- return `Property '${key}' is defined in the schema but doesn't have a value yet. Click to add a value.`
241
- },
242
-
243
- getPropertyEditabilityWarning(key, value) {
244
- if (!this.isPropertyEditable(key, value)) {
245
- return 'This property cannot be edited in the Properties tab. Use the Data tab to modify it.'
246
- }
247
- return null
248
- },
249
-
250
- getEditabilityWarning(key, value) {
251
- const prop = this.schema?.properties?.[key]
252
- if (prop?.const !== undefined) {
253
- return `This property is constant and must always be '${prop.const}'. Const properties cannot be modified to maintain data integrity.`
254
- }
255
- if (prop?.immutable && (value !== null && value !== undefined && value !== '')) {
256
- const displayValue = String(value).length > 100 ? String(value).slice(0, 100) + '…' : String(value)
257
- return `This property is immutable and cannot be changed once it has a value. Current value: '${displayValue}'. Immutable properties preserve data consistency.`
258
- }
259
- if (!this.isPropertyEditable(key, value)) {
260
- return this.getPropertyEditabilityWarning(key, value)
261
- }
262
- return null
263
- },
264
-
265
- handleRowClick(key, event) {
266
- if (event.target.tagName === 'INPUT' || event.target.tagName === 'BUTTON' || event.target.closest('.cn-advanced-form-dialog__value-input-container')) return
267
- const value = this.resolvedValue(key, this.objectProperties.find(([k]) => k === key)?.[1])
268
- if (!this.isPropertyEditable(key, value)) return
269
- const prop = this.schema?.properties?.[key]
270
- if (prop && !this.editableTypes.includes(prop.type || 'string')) return
271
- this.$emit('update:selected-property', key)
272
- this.$nextTick(() => {
273
- const ref = this.$refs['cell-' + key]
274
- const cell = ref && (Array.isArray(ref) ? ref[0] : ref)
275
- if (cell && cell.focus) cell.focus()
276
- })
277
- },
278
-
279
- isValidDate(v) {
280
- if (!v) return false
281
- const d = new Date(v)
282
- return d instanceof Date && !Number.isNaN(d.getTime())
283
- },
284
- },
285
- }
286
- </script>
287
-
288
- <style scoped>
289
- .cn-advanced-form-dialog__table-container {
290
- background: var(--color-main-background);
291
- border-radius: var(--border-radius);
292
- overflow: hidden;
293
- box-shadow: 0 2px 4px var(--color-box-shadow);
294
- border: 1px solid var(--color-border);
295
- margin-bottom: calc(5 * var(--default-grid-baseline));
296
- }
297
-
298
- .cn-advanced-form-dialog__table {
299
- width: 100%;
300
- border-collapse: collapse;
301
- background-color: var(--color-main-background);
302
- }
303
-
304
- .cn-advanced-form-dialog__table th,
305
- .cn-advanced-form-dialog__table td {
306
- padding: calc(3 * var(--default-grid-baseline));
307
- text-align: left;
308
- border-bottom: 1px solid var(--color-border);
309
- vertical-align: middle;
310
- }
311
-
312
- .cn-advanced-form-dialog__table th {
313
- background: var(--color-background-dark);
314
- font-weight: 500;
315
- color: var(--color-text-maxcontrast);
316
- }
317
-
318
- .cn-advanced-form-dialog__table-row {
319
- cursor: pointer;
320
- transition: background-color 0.2s ease;
321
- background-color: var(--color-main-background);
322
- }
323
-
324
- .cn-advanced-form-dialog__table-row:hover {
325
- background-color: var(--color-background-hover);
326
- }
327
-
328
- /* Active/selected row: light blue; ensure it wins over validation state classes */
329
- .cn-advanced-form-dialog__table-row.cn-advanced-form-dialog__table-row--selected,
330
- .cn-advanced-form-dialog__table-row--selected:hover {
331
- background-color: var(--color-primary-light);
332
- box-shadow: inset 3px 0 0 0 var(--color-primary);
333
- }
334
-
335
- .cn-advanced-form-dialog__table-row--edited {
336
- background-color: var(--color-success-light);
337
- box-shadow: inset 3px 0 0 0 var(--color-success);
338
- }
339
-
340
- .cn-advanced-form-dialog__table-row--non-editable {
341
- background-color: var(--color-background-dark);
342
- cursor: not-allowed;
343
- opacity: 0.7;
344
- }
345
-
346
- .cn-advanced-form-dialog__table-row--non-editable * {
347
- cursor: not-allowed !important;
348
- }
349
-
350
- .cn-advanced-form-dialog__table-row--valid {
351
- box-shadow: inset 3px 0 0 0 var(--color-success);
352
- }
353
-
354
- .cn-advanced-form-dialog__table-row--invalid {
355
- background-color: var(--color-error-light);
356
- box-shadow: inset 3px 0 0 0 var(--color-error);
357
- }
358
-
359
- .cn-advanced-form-dialog__table-row--warning {
360
- background-color: var(--color-warning-light);
361
- box-shadow: inset 3px 0 0 0 var(--color-warning);
362
- }
363
-
364
- .cn-advanced-form-dialog__table-row--new {
365
- box-shadow: inset 3px 0 0 0 var(--color-primary-element);
366
- }
367
-
368
- .cn-advanced-form-dialog__table-col-constrained {
369
- width: 150px;
370
- max-width: 150px;
371
- overflow: hidden;
372
- text-overflow: ellipsis;
373
- white-space: nowrap;
374
- }
375
-
376
- .cn-advanced-form-dialog__table-col-expanded {
377
- width: auto;
378
- min-width: 200px;
379
- }
380
-
381
- .cn-advanced-form-dialog__prop-cell {
382
- width: 30%;
383
- font-weight: 600;
384
- box-shadow: inset 3px 0 0 0 var(--color-primary);
385
- }
386
-
387
- .cn-advanced-form-dialog__value-cell {
388
- width: 70%;
389
- word-break: break-word;
390
- border-radius: 4px;
391
- }
392
-
393
- .cn-advanced-form-dialog__prop-cell-content {
394
- display: flex;
395
- align-items: center;
396
- gap: 8px;
397
- }
398
-
399
- .cn-advanced-form-dialog__validation-icon {
400
- flex-shrink: 0;
401
- }
402
-
403
- .cn-advanced-form-dialog__validation-icon--error {
404
- color: var(--color-error);
405
- }
406
-
407
- .cn-advanced-form-dialog__validation-icon--warning {
408
- color: var(--color-warning);
409
- }
410
-
411
- .cn-advanced-form-dialog__validation-icon--lock {
412
- color: var(--color-text-lighter);
413
- }
414
-
415
- .cn-advanced-form-dialog__validation-icon--new {
416
- color: var(--color-primary-element);
417
- }
418
- </style>
1
+ <template>
2
+ <div class="cn-advanced-form-dialog__table-container">
3
+ <table class="cn-advanced-form-dialog__table">
4
+ <thead>
5
+ <tr class="cn-advanced-form-dialog__table-row">
6
+ <th class="cn-advanced-form-dialog__table-col-constrained">
7
+ Property
8
+ </th>
9
+ <th class="cn-advanced-form-dialog__table-col-expanded">
10
+ Value
11
+ </th>
12
+ </tr>
13
+ </thead>
14
+ <tbody>
15
+ <tr
16
+ v-for="([key, value]) in objectProperties"
17
+ :key="key"
18
+ class="cn-advanced-form-dialog__table-row"
19
+ :class="{
20
+ 'cn-advanced-form-dialog__table-row--selected': selectedProperty === key,
21
+ 'cn-advanced-form-dialog__table-row--edited': formData[key] !== undefined,
22
+ 'cn-advanced-form-dialog__table-row--non-editable': !isPropertyEditable(key, resolvedValue(key, value)),
23
+ [getPropertyValidationClass(key, value)]: validationDisplay === 'indicator',
24
+ }"
25
+ @click="handleRowClick(key, $event)">
26
+ <td class="cn-advanced-form-dialog__table-col-constrained cn-advanced-form-dialog__prop-cell">
27
+ <div class="cn-advanced-form-dialog__prop-cell-content">
28
+ <AlertCircle
29
+ v-if="validationDisplay === 'indicator' && getPropertyValidationState(key, resolvedValue(key, value)) === 'invalid'"
30
+ class="cn-advanced-form-dialog__validation-icon cn-advanced-form-dialog__validation-icon--error"
31
+ :size="16"
32
+ :title="getPropertyErrorMessage(key, resolvedValue(key, value))" />
33
+ <Alert
34
+ v-else-if="validationDisplay === 'indicator' && getPropertyValidationState(key, resolvedValue(key, value)) === 'warning'"
35
+ class="cn-advanced-form-dialog__validation-icon cn-advanced-form-dialog__validation-icon--warning"
36
+ :size="16"
37
+ :title="getPropertyWarningMessage(key, resolvedValue(key, value))" />
38
+ <Plus
39
+ v-else-if="validationDisplay === 'indicator' && getPropertyValidationState(key, resolvedValue(key, value)) === 'new'"
40
+ class="cn-advanced-form-dialog__validation-icon cn-advanced-form-dialog__validation-icon--new"
41
+ :size="16"
42
+ :title="getPropertyNewMessage(key)" />
43
+ <LockOutline
44
+ v-else-if="!isPropertyEditable(key, resolvedValue(key, value))"
45
+ class="cn-advanced-form-dialog__validation-icon cn-advanced-form-dialog__validation-icon--lock"
46
+ :size="16"
47
+ :title="getEditabilityWarning(key, resolvedValue(key, value)) || ''" />
48
+ <span :title="getPropertyTooltip(key)">{{ getPropertyDisplayName(key) }}</span>
49
+ </div>
50
+ </td>
51
+ <td class="cn-advanced-form-dialog__table-col-expanded cn-advanced-form-dialog__value-cell">
52
+ <CnPropertyValueCell
53
+ :ref="'cell-' + key"
54
+ :property-key="key"
55
+ :schema="schema"
56
+ :value="resolvedValue(key, value)"
57
+ :is-editable="isPropertyEditable(key, resolvedValue(key, value))"
58
+ :is-editing="selectedProperty === key"
59
+ :display-name="getPropertyDisplayName(key)"
60
+ :editability-warning="getPropertyEditabilityWarning(key, resolvedValue(key, value))"
61
+ @update:value="onPropertyValueUpdate(key, $event)" />
62
+ </td>
63
+ </tr>
64
+ </tbody>
65
+ </table>
66
+ </div>
67
+ </template>
68
+
69
+ <script>
70
+ import AlertCircle from 'vue-material-design-icons/AlertCircle.vue'
71
+ import Alert from 'vue-material-design-icons/Alert.vue'
72
+ import LockOutline from 'vue-material-design-icons/LockOutline.vue'
73
+ import Plus from 'vue-material-design-icons/Plus.vue'
74
+ import CnPropertyValueCell from './CnPropertyValueCell.vue'
75
+
76
+ export default {
77
+ name: 'CnPropertiesTab',
78
+
79
+ components: {
80
+ AlertCircle,
81
+ Alert,
82
+ LockOutline,
83
+ Plus,
84
+ CnPropertyValueCell,
85
+ },
86
+
87
+ props: {
88
+ schema: { type: Object, default: null },
89
+ item: { type: Object, default: null },
90
+ formData: { type: Object, default: () => ({}) },
91
+ selectedProperty: { type: String, default: null },
92
+ editableTypes: { type: Array, default: () => ['string', 'number', 'integer', 'boolean'] },
93
+ validationDisplay: { type: String, default: 'indicator' },
94
+ excludeFields: { type: Array, default: () => [] },
95
+ includeFields: { type: Array, default: null },
96
+ },
97
+
98
+ computed: {
99
+ objectProperties() {
100
+ const schemaProps = this.schema?.properties || {}
101
+ const obj = this.item || {}
102
+ const exclude = this.excludeFields || []
103
+ const include = this.includeFields
104
+ const filterKey = (k) => {
105
+ if (k === '@self' || k === 'id') return false
106
+ if (exclude.includes(k)) return false
107
+ if (include && !include.includes(k)) return false
108
+ return true
109
+ }
110
+ const existing = Object.entries(obj).filter(([k]) => filterKey(k))
111
+ const missing = []
112
+ for (const [key, prop] of Object.entries(schemaProps)) {
113
+ if (!filterKey(key)) continue
114
+ if (!Object.prototype.hasOwnProperty.call(obj, key)) {
115
+ let def
116
+ switch (prop.type) {
117
+ case 'string': def = prop.const ?? ''; break
118
+ case 'number':
119
+ case 'integer': def = 0; break
120
+ case 'boolean': def = false; break
121
+ case 'array': def = []; break
122
+ case 'object': def = {}; break
123
+ default: def = ''
124
+ }
125
+ missing.push([key, def])
126
+ }
127
+ }
128
+ return [...existing, ...missing]
129
+ },
130
+ },
131
+
132
+ methods: {
133
+ /** The effective value for a key: formData override or the object's own value */
134
+ resolvedValue(key, objectValue) {
135
+ return this.formData[key] !== undefined ? this.formData[key] : objectValue
136
+ },
137
+
138
+ onPropertyValueUpdate(key, value) {
139
+ this.$emit('update:property-value', { key, value })
140
+ },
141
+
142
+ isPropertyEditable(key, value) {
143
+ const prop = this.schema?.properties?.[key]
144
+ if (!prop) return true
145
+ if (prop.const !== undefined) return false
146
+ if (prop.immutable && value != null && value !== '') return false
147
+ const type = prop.type || 'string'
148
+ return this.editableTypes.includes(type)
149
+ },
150
+
151
+ getPropertyDisplayName(key) {
152
+ return (this.schema?.properties?.[key]?.title) || key
153
+ },
154
+
155
+ getPropertyTooltip(key) {
156
+ const prop = this.schema?.properties?.[key]
157
+ if (prop?.description) {
158
+ if (prop.title && prop.title !== key) {
159
+ return `${prop.title}: ${prop.description}`
160
+ }
161
+ return prop.description
162
+ }
163
+ return `Property: ${key}`
164
+ },
165
+
166
+ getPropertyValidationClass(key, value) {
167
+ const state = this.getPropertyValidationState(key, value)
168
+ switch (state) {
169
+ case 'invalid': return 'cn-advanced-form-dialog__table-row--invalid'
170
+ case 'warning': return 'cn-advanced-form-dialog__table-row--warning'
171
+ case 'new': return 'cn-advanced-form-dialog__table-row--new'
172
+ case 'valid': return 'cn-advanced-form-dialog__table-row--valid'
173
+ default: return ''
174
+ }
175
+ },
176
+
177
+ getPropertyValidationState(key, value) {
178
+ const prop = this.schema?.properties?.[key]
179
+ const existsInObject = this.item ? Object.prototype.hasOwnProperty.call(this.item, key) : false
180
+ if (!prop) return 'warning'
181
+ if (!existsInObject) return 'new'
182
+ if (this.isValidPropertyValue(key, value, prop)) return 'valid'
183
+ return 'invalid'
184
+ },
185
+
186
+ isValidPropertyValue(key, value, schemaProperty) {
187
+ if (value === null || value === undefined || value === '') {
188
+ const required = (this.schema?.required || []).includes(key) || schemaProperty?.required
189
+ return !required
190
+ }
191
+ const type = schemaProperty?.type || 'string'
192
+ switch (type) {
193
+ case 'string':
194
+ if (typeof value !== 'string') return false
195
+ if (schemaProperty?.format === 'date-time' && !this.isValidDate(value)) return false
196
+ if (schemaProperty?.const && value !== schemaProperty.const) return false
197
+ return true
198
+ case 'number':
199
+ return typeof value === 'number' && !Number.isNaN(value)
200
+ case 'boolean':
201
+ return typeof value === 'boolean'
202
+ case 'array':
203
+ return Array.isArray(value)
204
+ case 'object':
205
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
206
+ default:
207
+ return true
208
+ }
209
+ },
210
+
211
+ getPropertyErrorMessage(key, value) {
212
+ const prop = this.schema?.properties?.[key]
213
+ if (!prop) {
214
+ return `Property '${key}' is not defined in the current schema. This property exists in the object but is not part of the schema definition.`
215
+ }
216
+ const isRequired = (this.schema?.required || []).includes(key) || prop.required
217
+ if ((value === null || value === undefined || value === '') && isRequired) {
218
+ return `Required property '${key}' is missing or empty.`
219
+ }
220
+ const expectedType = prop.type
221
+ const actualType = Array.isArray(value) ? 'array' : typeof value
222
+ if (expectedType && expectedType !== actualType) {
223
+ return `Property '${key}' should be ${expectedType} but is ${actualType}.`
224
+ }
225
+ if (prop.format === 'date-time' && !this.isValidDate(value)) {
226
+ return `Property '${key}' should be a valid date-time value.`
227
+ }
228
+ if (prop.const && value !== prop.const) {
229
+ return `Property '${key}' should be '${prop.const}' but is '${value}'.`
230
+ }
231
+ return `Property '${key}' has an invalid value.`
232
+ },
233
+
234
+ getPropertyWarningMessage(key, value) {
235
+ const displayValue = String(value).length > 100 ? String(value).slice(0, 100) + '…' : String(value)
236
+ return `Property '${key}' exists in the object but is not defined in the current schema. This might happen when property names are changed in the schema. Current value: '${displayValue}'.`
237
+ },
238
+
239
+ getPropertyNewMessage(key) {
240
+ return `Property '${key}' is defined in the schema but doesn't have a value yet. Click to add a value.`
241
+ },
242
+
243
+ getPropertyEditabilityWarning(key, value) {
244
+ if (!this.isPropertyEditable(key, value)) {
245
+ return 'This property cannot be edited in the Properties tab. Use the Data tab to modify it.'
246
+ }
247
+ return null
248
+ },
249
+
250
+ getEditabilityWarning(key, value) {
251
+ const prop = this.schema?.properties?.[key]
252
+ if (prop?.const !== undefined) {
253
+ return `This property is constant and must always be '${prop.const}'. Const properties cannot be modified to maintain data integrity.`
254
+ }
255
+ if (prop?.immutable && (value !== null && value !== undefined && value !== '')) {
256
+ const displayValue = String(value).length > 100 ? String(value).slice(0, 100) + '…' : String(value)
257
+ return `This property is immutable and cannot be changed once it has a value. Current value: '${displayValue}'. Immutable properties preserve data consistency.`
258
+ }
259
+ if (!this.isPropertyEditable(key, value)) {
260
+ return this.getPropertyEditabilityWarning(key, value)
261
+ }
262
+ return null
263
+ },
264
+
265
+ handleRowClick(key, event) {
266
+ if (event.target.tagName === 'INPUT' || event.target.tagName === 'BUTTON' || event.target.closest('.cn-advanced-form-dialog__value-input-container')) return
267
+ const value = this.resolvedValue(key, this.objectProperties.find(([k]) => k === key)?.[1])
268
+ if (!this.isPropertyEditable(key, value)) return
269
+ const prop = this.schema?.properties?.[key]
270
+ if (prop && !this.editableTypes.includes(prop.type || 'string')) return
271
+ this.$emit('update:selected-property', key)
272
+ this.$nextTick(() => {
273
+ const ref = this.$refs['cell-' + key]
274
+ const cell = ref && (Array.isArray(ref) ? ref[0] : ref)
275
+ if (cell && cell.focus) cell.focus()
276
+ })
277
+ },
278
+
279
+ isValidDate(v) {
280
+ if (!v) return false
281
+ const d = new Date(v)
282
+ return d instanceof Date && !Number.isNaN(d.getTime())
283
+ },
284
+ },
285
+ }
286
+ </script>
287
+
288
+ <style scoped>
289
+ .cn-advanced-form-dialog__table-container {
290
+ background: var(--color-main-background);
291
+ border-radius: var(--border-radius);
292
+ overflow: hidden;
293
+ box-shadow: 0 2px 4px var(--color-box-shadow);
294
+ border: 1px solid var(--color-border);
295
+ margin-bottom: calc(5 * var(--default-grid-baseline));
296
+ }
297
+
298
+ .cn-advanced-form-dialog__table {
299
+ width: 100%;
300
+ border-collapse: collapse;
301
+ background-color: var(--color-main-background);
302
+ }
303
+
304
+ .cn-advanced-form-dialog__table th,
305
+ .cn-advanced-form-dialog__table td {
306
+ padding: calc(3 * var(--default-grid-baseline));
307
+ text-align: left;
308
+ border-bottom: 1px solid var(--color-border);
309
+ vertical-align: middle;
310
+ }
311
+
312
+ .cn-advanced-form-dialog__table th {
313
+ background: var(--color-background-dark);
314
+ font-weight: 500;
315
+ color: var(--color-text-maxcontrast);
316
+ }
317
+
318
+ .cn-advanced-form-dialog__table-row {
319
+ cursor: pointer;
320
+ transition: background-color 0.2s ease;
321
+ background-color: var(--color-main-background);
322
+ }
323
+
324
+ .cn-advanced-form-dialog__table-row:hover {
325
+ background-color: var(--color-background-hover);
326
+ }
327
+
328
+ /* Active/selected row: light blue; ensure it wins over validation state classes */
329
+ .cn-advanced-form-dialog__table-row.cn-advanced-form-dialog__table-row--selected,
330
+ .cn-advanced-form-dialog__table-row--selected:hover {
331
+ background-color: var(--color-primary-light);
332
+ box-shadow: inset 3px 0 0 0 var(--color-primary);
333
+ }
334
+
335
+ .cn-advanced-form-dialog__table-row--edited {
336
+ background-color: var(--color-success-light);
337
+ box-shadow: inset 3px 0 0 0 var(--color-success);
338
+ }
339
+
340
+ .cn-advanced-form-dialog__table-row--non-editable {
341
+ background-color: var(--color-background-dark);
342
+ cursor: not-allowed;
343
+ opacity: 0.7;
344
+ }
345
+
346
+ .cn-advanced-form-dialog__table-row--non-editable * {
347
+ cursor: not-allowed !important;
348
+ }
349
+
350
+ .cn-advanced-form-dialog__table-row--valid {
351
+ box-shadow: inset 3px 0 0 0 var(--color-success);
352
+ }
353
+
354
+ .cn-advanced-form-dialog__table-row--invalid {
355
+ background-color: var(--color-error-light);
356
+ box-shadow: inset 3px 0 0 0 var(--color-error);
357
+ }
358
+
359
+ .cn-advanced-form-dialog__table-row--warning {
360
+ background-color: var(--color-warning-light);
361
+ box-shadow: inset 3px 0 0 0 var(--color-warning);
362
+ }
363
+
364
+ .cn-advanced-form-dialog__table-row--new {
365
+ box-shadow: inset 3px 0 0 0 var(--color-primary-element);
366
+ }
367
+
368
+ .cn-advanced-form-dialog__table-col-constrained {
369
+ width: 150px;
370
+ max-width: 150px;
371
+ overflow: hidden;
372
+ text-overflow: ellipsis;
373
+ white-space: nowrap;
374
+ }
375
+
376
+ .cn-advanced-form-dialog__table-col-expanded {
377
+ width: auto;
378
+ min-width: 200px;
379
+ }
380
+
381
+ .cn-advanced-form-dialog__prop-cell {
382
+ width: 30%;
383
+ font-weight: 600;
384
+ box-shadow: inset 3px 0 0 0 var(--color-primary);
385
+ }
386
+
387
+ .cn-advanced-form-dialog__value-cell {
388
+ width: 70%;
389
+ word-break: break-word;
390
+ border-radius: 4px;
391
+ }
392
+
393
+ .cn-advanced-form-dialog__prop-cell-content {
394
+ display: flex;
395
+ align-items: center;
396
+ gap: 8px;
397
+ }
398
+
399
+ .cn-advanced-form-dialog__validation-icon {
400
+ flex-shrink: 0;
401
+ }
402
+
403
+ .cn-advanced-form-dialog__validation-icon--error {
404
+ color: var(--color-error);
405
+ }
406
+
407
+ .cn-advanced-form-dialog__validation-icon--warning {
408
+ color: var(--color-warning);
409
+ }
410
+
411
+ .cn-advanced-form-dialog__validation-icon--lock {
412
+ color: var(--color-text-lighter);
413
+ }
414
+
415
+ .cn-advanced-form-dialog__validation-icon--new {
416
+ color: var(--color-primary-element);
417
+ }
418
+ </style>