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

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 (82) hide show
  1. package/dist/nextcloud-vue.cjs.js +13606 -1918
  2. package/dist/nextcloud-vue.cjs.js.map +1 -1
  3. package/dist/nextcloud-vue.css +1238 -270
  4. package/dist/nextcloud-vue.esm.js +13548 -1880
  5. package/dist/nextcloud-vue.esm.js.map +1 -1
  6. package/package.json +9 -4
  7. package/src/components/CnActionsBar/CnActionsBar.vue +6 -1
  8. package/src/components/CnAdvancedFormDialog/CnAdvancedFormDialog.vue +1 -11
  9. package/src/components/CnAdvancedFormDialog/CnPropertiesTab.vue +5 -1
  10. package/src/components/CnAdvancedFormDialog/CnPropertyValueCell.vue +1 -1
  11. package/src/components/CnCard/CnCard.vue +415 -0
  12. package/src/components/CnCard/index.js +1 -0
  13. package/src/components/CnCardGrid/CnCardGrid.vue +20 -20
  14. package/src/components/CnChartWidget/CnChartWidget.vue +3 -1
  15. package/src/components/CnCopyDialog/CnCopyDialog.vue +7 -1
  16. package/src/components/CnDashboardGrid/CnDashboardGrid.vue +4 -0
  17. package/src/components/CnDashboardPage/CnDashboardPage.vue +2 -0
  18. package/src/components/CnDataTable/CnDataTable.vue +6 -2
  19. package/src/components/CnDeleteDialog/CnDeleteDialog.vue +7 -1
  20. package/src/components/CnDetailCard/CnDetailCard.vue +12 -1
  21. package/src/components/CnDetailGrid/CnDetailGrid.vue +254 -0
  22. package/src/components/CnDetailGrid/index.js +1 -0
  23. package/src/components/CnDetailPage/CnDetailPage.vue +157 -11
  24. package/src/components/CnFacetSidebar/CnFacetSidebar.vue +3 -1
  25. package/src/components/CnFormDialog/CnFormDialog.vue +934 -920
  26. package/src/components/CnIcon/CnIcon.vue +1 -1
  27. package/src/components/CnIndexPage/CnIndexPage.vue +51 -9
  28. package/src/components/CnIndexSidebar/CnIndexSidebar.vue +37 -9
  29. package/src/components/CnInfoWidget/CnInfoWidget.vue +219 -0
  30. package/src/components/CnInfoWidget/index.js +1 -0
  31. package/src/components/CnJsonViewer/CnJsonViewer.vue +283 -0
  32. package/src/components/CnJsonViewer/index.js +1 -0
  33. package/src/components/CnKpiGrid/CnKpiGrid.vue +5 -1
  34. package/src/components/CnMassCopyDialog/CnMassCopyDialog.vue +7 -1
  35. package/src/components/CnMassDeleteDialog/CnMassDeleteDialog.vue +7 -1
  36. package/src/components/CnMassExportDialog/CnMassExportDialog.vue +1 -1
  37. package/src/components/CnMassImportDialog/CnMassImportDialog.vue +1 -1
  38. package/src/components/CnObjectCard/CnObjectCard.vue +1 -1
  39. package/src/components/CnObjectSidebar/CnAuditTrailTab.vue +368 -0
  40. package/src/components/CnObjectSidebar/CnFilesTab.vue +286 -0
  41. package/src/components/CnObjectSidebar/CnNotesTab.vue +249 -0
  42. package/src/components/CnObjectSidebar/CnObjectSidebar.vue +45 -668
  43. package/src/components/CnObjectSidebar/CnTagsTab.vue +258 -0
  44. package/src/components/CnObjectSidebar/CnTasksTab.vue +482 -0
  45. package/src/components/CnObjectSidebar/index.js +5 -0
  46. package/src/components/CnProgressBar/CnProgressBar.vue +262 -0
  47. package/src/components/CnProgressBar/index.js +1 -0
  48. package/src/components/CnSchemaFormDialog/CnSchemaPropertiesTab.vue +1 -1
  49. package/src/components/CnStatsBlock/CnStatsBlock.vue +27 -11
  50. package/src/components/CnStatsPanel/CnStatsPanel.vue +320 -0
  51. package/src/components/CnStatsPanel/index.js +1 -0
  52. package/src/components/CnStatusBadge/CnStatusBadge.vue +15 -2
  53. package/src/components/CnTabbedFormDialog/CnTabbedFormDialog.vue +5 -1
  54. package/src/components/CnTableWidget/CnTableWidget.vue +332 -0
  55. package/src/components/CnTableWidget/index.js +1 -0
  56. package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +36 -1
  57. package/src/components/index.js +11 -0
  58. package/src/composables/useDashboardView.js +58 -12
  59. package/src/composables/useDetailView.js +3 -2
  60. package/src/composables/useListView.js +7 -6
  61. package/src/composables/useSubResource.js +3 -3
  62. package/src/css/badge.css +32 -0
  63. package/src/css/card.css +1 -0
  64. package/src/css/detail-page.css +74 -7
  65. package/src/index.js +16 -0
  66. package/src/mixins/gridLayout.js +118 -0
  67. package/src/store/createCrudStore.js +360 -0
  68. package/src/store/createSubResourcePlugin.js +5 -15
  69. package/src/store/index.js +1 -0
  70. package/src/store/plugins/auditTrails.js +346 -6
  71. package/src/store/plugins/lifecycle.js +4 -4
  72. package/src/store/plugins/registerMapping.js +18 -8
  73. package/src/store/plugins/relations.js +1 -1
  74. package/src/store/plugins/search.js +21 -8
  75. package/src/store/useObjectStore.js +30 -36
  76. package/src/utils/getTheme.js +9 -0
  77. package/src/utils/headers.js +13 -3
  78. package/src/utils/index.js +1 -0
  79. package/src/utils/schema.js +3 -3
  80. package/src/utils/widgetVisibility.js +162 -0
  81. package/src/components/CnObjectCard/eslint-setup.md +0 -235
  82. package/src/components/CnObjectCard/package.json-or.json +0 -132
@@ -28,7 +28,7 @@ const _registry = {
28
28
  * Each key must be the PascalCase icon name matching the
29
29
  * vue-material-design-icons file name (e.g. "Sword" for Sword.vue).
30
30
  *
31
- * @param {Record<string, import('vue').Component>} icons
31
+ * @param {Record<string, import('vue').Component>} icons - Map of PascalCase icon names to Vue components
32
32
  *
33
33
  * @example
34
34
  * import { registerIcons } from '@conduction/nextcloud-vue'
@@ -28,6 +28,7 @@
28
28
  :view-mode="currentViewMode"
29
29
  :show-view-toggle="showViewToggle"
30
30
  :refreshing="refreshing"
31
+ :show-add="showAdd"
31
32
  @add="onAddClick"
32
33
  @refresh="$emit('refresh')"
33
34
  @show-import="showImportDialog = true"
@@ -52,6 +53,7 @@
52
53
  ref="massDeleteDialog"
53
54
  :items="selectedObjects"
54
55
  :name-field="massActionNameField"
56
+ :name-formatter="nameFormatter"
55
57
  @confirm="onMassDeleteConfirm"
56
58
  @close="showMassDeleteDialog = false" />
57
59
 
@@ -61,6 +63,7 @@
61
63
  ref="massCopyDialog"
62
64
  :items="selectedObjects"
63
65
  :name-field="massActionNameField"
66
+ :name-formatter="nameFormatter"
64
67
  @confirm="onMassCopyConfirm"
65
68
  @close="showMassCopyDialog = false" />
66
69
 
@@ -94,6 +97,7 @@
94
97
  ref="singleDeleteDialog"
95
98
  :item="actionTargetItem"
96
99
  :name-field="massActionNameField"
100
+ :name-formatter="nameFormatter"
97
101
  @confirm="onSingleDeleteConfirm"
98
102
  @close="closeSingleDelete" />
99
103
  </slot>
@@ -108,6 +112,7 @@
108
112
  ref="singleCopyDialog"
109
113
  :item="actionTargetItem"
110
114
  :name-field="massActionNameField"
115
+ :name-formatter="nameFormatter"
111
116
  @confirm="onSingleCopyConfirm"
112
117
  @close="closeSingleCopy" />
113
118
  </slot>
@@ -500,6 +505,11 @@ export default {
500
505
  type: String,
501
506
  default: 'title',
502
507
  },
508
+ /** Optional function to format item names in dialogs. Receives the item, returns a string. Overrides massActionNameField when provided. */
509
+ nameFormatter: {
510
+ type: Function,
511
+ default: null,
512
+ },
503
513
  /** Available export formats for the export dialog */
504
514
  exportFormats: {
505
515
  type: Array,
@@ -563,6 +573,11 @@ export default {
563
573
  type: Boolean,
564
574
  default: false,
565
575
  },
576
+ /** Whether to show the Add button in the actions bar */
577
+ showAdd: {
578
+ type: Boolean,
579
+ default: true,
580
+ },
566
581
  /**
567
582
  * Store instance for automatic save integration. When provided alongside
568
583
  * objectType, the form dialog saves directly to the store instead of
@@ -751,28 +766,40 @@ export default {
751
766
  this.$emit('mass-import', payload)
752
767
  },
753
768
 
754
- /** @public Forward result to mass delete dialog */
769
+ /**
770
+ * @param {*} resultData Result data to pass to the dialog
771
+ * @public
772
+ */
755
773
  setMassDeleteResult(resultData) {
756
774
  if (this.$refs.massDeleteDialog) {
757
775
  this.$refs.massDeleteDialog.setResult(resultData)
758
776
  }
759
777
  },
760
778
 
761
- /** @public Forward result to mass copy dialog */
779
+ /**
780
+ * @param {*} resultData Result data to pass to the dialog
781
+ * @public
782
+ */
762
783
  setMassCopyResult(resultData) {
763
784
  if (this.$refs.massCopyDialog) {
764
785
  this.$refs.massCopyDialog.setResult(resultData)
765
786
  }
766
787
  },
767
788
 
768
- /** @public Forward result to export dialog */
789
+ /**
790
+ * @param {*} resultData Result data to pass to the dialog
791
+ * @public
792
+ */
769
793
  setExportResult(resultData) {
770
794
  if (this.$refs.exportDialog) {
771
795
  this.$refs.exportDialog.setResult(resultData)
772
796
  }
773
797
  },
774
798
 
775
- /** @public Forward result to import dialog */
799
+ /**
800
+ * @param {*} resultData Result data to pass to the dialog
801
+ * @public
802
+ */
776
803
  setImportResult(resultData) {
777
804
  if (this.$refs.importDialog) {
778
805
  this.$refs.importDialog.setResult(resultData)
@@ -780,11 +807,17 @@ export default {
780
807
  },
781
808
 
782
809
  // --- Backward-compatible aliases ---
783
- /** @public @deprecated Use setMassDeleteResult instead */
810
+ /**
811
+ * @param {*} resultData Result data to pass to the dialog
812
+ * @public
813
+ */
784
814
  setDeleteResult(resultData) {
785
815
  this.setMassDeleteResult(resultData)
786
816
  },
787
- /** @public @deprecated Use setMassCopyResult instead */
817
+ /**
818
+ * @param {*} resultData Result data to pass to the dialog
819
+ * @public
820
+ */
788
821
  setCopyResult(resultData) {
789
822
  this.setMassCopyResult(resultData)
790
823
  },
@@ -837,21 +870,30 @@ export default {
837
870
  this.editItem = null
838
871
  },
839
872
 
840
- /** @public Forward result to single delete dialog */
873
+ /**
874
+ * @param {*} resultData Result data to pass to the dialog
875
+ * @public
876
+ */
841
877
  setSingleDeleteResult(resultData) {
842
878
  if (this.$refs.singleDeleteDialog) {
843
879
  this.$refs.singleDeleteDialog.setResult(resultData)
844
880
  }
845
881
  },
846
882
 
847
- /** @public Forward result to single copy dialog */
883
+ /**
884
+ * @param {*} resultData Result data to pass to the dialog
885
+ * @public
886
+ */
848
887
  setSingleCopyResult(resultData) {
849
888
  if (this.$refs.singleCopyDialog) {
850
889
  this.$refs.singleCopyDialog.setResult(resultData)
851
890
  }
852
891
  },
853
892
 
854
- /** @public Forward result to form dialog */
893
+ /**
894
+ * @param {*} resultData Result data to pass to the dialog
895
+ * @public
896
+ */
855
897
  setFormResult(resultData) {
856
898
  if (this.$refs.formDialog) {
857
899
  this.$refs.formDialog.setResult(resultData)
@@ -414,24 +414,36 @@ export default {
414
414
  },
415
415
 
416
416
  methods: {
417
- /** Handle tab change from NcAppSidebar */
417
+ /**
418
+ * Handle tab change from NcAppSidebar
419
+ * @param {string} tabId Tab identifier
420
+ */
418
421
  onTabChange(tabId) {
419
422
  this.internalActiveTab = tabId
420
423
  this.$emit('tab-change', tabId)
421
424
  },
422
425
 
423
- /** Check if a column is currently visible */
426
+ /**
427
+ * Check if a column is currently visible
428
+ * @param {string} key Column key
429
+ */
424
430
  isColumnVisible(key) {
425
431
  if (this.visibleColumns === null) return true
426
432
  return this.visibleColumns.includes(key)
427
433
  },
428
434
 
429
- /** Check if all columns in a group are visible */
435
+ /**
436
+ * Check if all columns in a group are visible
437
+ * @param {string[]} columns Array of column keys
438
+ */
430
439
  isGroupAllVisible(columns) {
431
440
  return columns.every((col) => this.isColumnVisible(col.key))
432
441
  },
433
442
 
434
- /** Toggle a single column's visibility */
443
+ /**
444
+ * Toggle a single column's visibility
445
+ * @param {string} key Column key
446
+ */
435
447
  toggleColumn(key) {
436
448
  let newVisible
437
449
  if (this.visibleColumns === null) {
@@ -444,7 +456,10 @@ export default {
444
456
  this.$emit('columns-change', newVisible)
445
457
  },
446
458
 
447
- /** Select or deselect all columns in a group */
459
+ /**
460
+ * Select or deselect all columns in a group
461
+ * @param {string[]} columns Array of column keys
462
+ */
448
463
  toggleGroupAll(columns) {
449
464
  const groupKeys = columns.map((c) => c.key)
450
465
  const allVisible = this.isGroupAllVisible(columns)
@@ -465,12 +480,18 @@ export default {
465
480
  this.$emit('columns-change', newVisible)
466
481
  },
467
482
 
468
- /** Toggle a group's expanded state */
483
+ /**
484
+ * Toggle a group's expanded state
485
+ * @param {string} groupId Filter group identifier
486
+ */
469
487
  toggleGroup(groupId) {
470
488
  this.$set(this.expandedGroups, groupId, !this.expandedGroups[groupId])
471
489
  },
472
490
 
473
- /** Get filter options for a filter definition */
491
+ /**
492
+ * Get filter options for a filter definition
493
+ * @param {object} filter Filter object
494
+ */
474
495
  getFilterOptions(filter) {
475
496
  const facet = this.facetData[filter.key]
476
497
  if (facet?.values?.length > 0) {
@@ -482,7 +503,10 @@ export default {
482
503
  return filter.options || []
483
504
  },
484
505
 
485
- /** Get currently selected options for a filter */
506
+ /**
507
+ * Get currently selected options for a filter
508
+ * @param {object} filter Filter object
509
+ */
486
510
  getSelectedFilterOptions(filter) {
487
511
  const value = this.activeFilters[filter.key]
488
512
  if (!value) return []
@@ -491,7 +515,11 @@ export default {
491
515
  return values.map((v) => options.find((o) => o.id === v) || { id: v, label: String(v) })
492
516
  },
493
517
 
494
- /** Handle filter select change */
518
+ /**
519
+ * Handle filter select change
520
+ * @param {string} key Filter key
521
+ * @param {Array} selected Selected values
522
+ */
495
523
  onFilterChange(key, selected) {
496
524
  const values = selected ? selected.map((o) => o.id) : []
497
525
  this.$emit('filter-change', { key, values })
@@ -0,0 +1,219 @@
1
+ <!--
2
+ CnInfoWidget — Renders label-value pairs in a responsive CSS grid.
3
+
4
+ Supports two modes:
5
+ 1. Manual: provide `fields` prop with `[{ label, value }]` objects
6
+ 2. Auto-generated: provide `object` + `schema` props to generate from JSON Schema properties
7
+
8
+ Used in dashboard and detail page grid layouts to display entity metadata.
9
+ -->
10
+ <template>
11
+ <div
12
+ class="cn-info-widget"
13
+ :style="gridStyle">
14
+ <div
15
+ v-for="(field, index) in displayFields"
16
+ :key="index"
17
+ class="cn-info-widget__field">
18
+ <dt class="cn-info-widget__label">
19
+ {{ field.label }}
20
+ </dt>
21
+ <dd class="cn-info-widget__value">
22
+ {{ field.value || '—' }}
23
+ </dd>
24
+ </div>
25
+ </div>
26
+ </template>
27
+
28
+ <script>
29
+ /**
30
+ * CnInfoWidget — Renders label-value pairs in a responsive CSS grid.
31
+ *
32
+ * @example Manual fields
33
+ * <CnInfoWidget :fields="[
34
+ * { label: 'Email', value: 'test@example.com' },
35
+ * { label: 'Phone', value: '+31 6 12345678' },
36
+ * ]" :columns="2" />
37
+ *
38
+ * @example Auto-generated from schema
39
+ * <CnInfoWidget :object="myObject" :schema="mySchema" :columns="3" />
40
+ */
41
+ export default {
42
+ name: 'CnInfoWidget',
43
+
44
+ props: {
45
+ /**
46
+ * Manual field definitions. Array of `{ label, value }` objects.
47
+ * Takes precedence over object+schema auto-generation.
48
+ *
49
+ * @type {{ label: string, value: string|number }[]}
50
+ */
51
+ fields: {
52
+ type: Array,
53
+ default: null,
54
+ },
55
+ /**
56
+ * Object data for auto-generation mode. Properties are extracted
57
+ * based on the schema definition.
58
+ *
59
+ * @type {object}
60
+ */
61
+ object: {
62
+ type: Object,
63
+ default: null,
64
+ },
65
+ /**
66
+ * JSON Schema for auto-generation mode. Each schema property
67
+ * generates a label:value pair using the property title as label.
68
+ *
69
+ * @type {object}
70
+ */
71
+ schema: {
72
+ type: Object,
73
+ default: null,
74
+ },
75
+ /**
76
+ * Number of columns for the grid layout.
77
+ *
78
+ * @type {number}
79
+ */
80
+ columns: {
81
+ type: Number,
82
+ default: 2,
83
+ },
84
+ /**
85
+ * Fields to include (by key). If provided, only these fields are shown.
86
+ * Only applies in auto-generation mode.
87
+ *
88
+ * @type {string[]}
89
+ */
90
+ includeFields: {
91
+ type: Array,
92
+ default: null,
93
+ },
94
+ /**
95
+ * Fields to exclude (by key). Only applies in auto-generation mode.
96
+ *
97
+ * @type {string[]}
98
+ */
99
+ excludeFields: {
100
+ type: Array,
101
+ default: () => [],
102
+ },
103
+ },
104
+
105
+ computed: {
106
+ /**
107
+ * Resolved fields to display. Manual fields take precedence;
108
+ * otherwise generates from object + schema.
109
+ *
110
+ * @return {{ label: string, value: string|number }[]}
111
+ */
112
+ displayFields() {
113
+ if (this.fields) {
114
+ return this.fields
115
+ }
116
+
117
+ if (this.object && this.schema && this.schema.properties) {
118
+ return this.generateFieldsFromSchema()
119
+ }
120
+
121
+ return []
122
+ },
123
+
124
+ /**
125
+ * CSS grid style based on column count.
126
+ *
127
+ * @return {object}
128
+ */
129
+ gridStyle() {
130
+ return {
131
+ gridTemplateColumns: `repeat(${this.columns}, 1fr)`,
132
+ }
133
+ },
134
+ },
135
+
136
+ methods: {
137
+ /**
138
+ * Generate label-value field definitions from object + schema.
139
+ *
140
+ * @return {{ label: string, value: string|number }[]}
141
+ */
142
+ generateFieldsFromSchema() {
143
+ const properties = this.schema.properties || {}
144
+ const keys = this.includeFields || Object.keys(properties)
145
+
146
+ return keys
147
+ .filter(key => !this.excludeFields.includes(key))
148
+ .filter(key => properties[key])
149
+ .map(key => ({
150
+ label: properties[key].title || key,
151
+ value: this.formatFieldValue(this.object[key], properties[key]),
152
+ }))
153
+ },
154
+
155
+ /**
156
+ * Format a field value for display based on its schema type.
157
+ *
158
+ * @param {*} value - The raw value.
159
+ * @param {object} schemaProp - The JSON Schema property definition.
160
+ * @return {string} Formatted display value.
161
+ */
162
+ formatFieldValue(value) {
163
+ if (value === null || value === undefined) {
164
+ return ''
165
+ }
166
+
167
+ if (Array.isArray(value)) {
168
+ return value.join(', ')
169
+ }
170
+
171
+ if (typeof value === 'object') {
172
+ return JSON.stringify(value)
173
+ }
174
+
175
+ if (typeof value === 'boolean') {
176
+ return value ? 'Yes' : 'No'
177
+ }
178
+
179
+ return String(value)
180
+ },
181
+ },
182
+ }
183
+ </script>
184
+
185
+ <style scoped>
186
+ .cn-info-widget {
187
+ display: grid;
188
+ gap: 12px 24px;
189
+ }
190
+
191
+ .cn-info-widget__field {
192
+ display: flex;
193
+ flex-direction: column;
194
+ gap: 2px;
195
+ }
196
+
197
+ .cn-info-widget__label {
198
+ font-size: 12px;
199
+ font-weight: 600;
200
+ color: var(--color-text-maxcontrast);
201
+ text-transform: uppercase;
202
+ letter-spacing: 0.5px;
203
+ margin: 0;
204
+ }
205
+
206
+ .cn-info-widget__value {
207
+ font-size: 14px;
208
+ color: var(--color-main-text);
209
+ margin: 0;
210
+ word-break: break-word;
211
+ }
212
+
213
+ /* Responsive: single column on small screens */
214
+ @media (max-width: 600px) {
215
+ .cn-info-widget {
216
+ grid-template-columns: 1fr !important;
217
+ }
218
+ }
219
+ </style>
@@ -0,0 +1 @@
1
+ export { default as CnInfoWidget } from './CnInfoWidget.vue'