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

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 +13575 -2374
  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 +13517 -2336
  5. package/dist/nextcloud-vue.esm.js.map +1 -1
  6. package/package.json +11 -7
  7. package/src/components/CnActionsBar/CnActionsBar.vue +20 -2
  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 +63 -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,9 @@
28
28
  :view-mode="currentViewMode"
29
29
  :show-view-toggle="showViewToggle"
30
30
  :refreshing="refreshing"
31
+ :refresh-disabled="refreshDisabled"
32
+ :add-disabled="addDisabled"
33
+ :show-add="showAdd"
31
34
  @add="onAddClick"
32
35
  @refresh="$emit('refresh')"
33
36
  @show-import="showImportDialog = true"
@@ -52,6 +55,7 @@
52
55
  ref="massDeleteDialog"
53
56
  :items="selectedObjects"
54
57
  :name-field="massActionNameField"
58
+ :name-formatter="nameFormatter"
55
59
  @confirm="onMassDeleteConfirm"
56
60
  @close="showMassDeleteDialog = false" />
57
61
 
@@ -61,6 +65,7 @@
61
65
  ref="massCopyDialog"
62
66
  :items="selectedObjects"
63
67
  :name-field="massActionNameField"
68
+ :name-formatter="nameFormatter"
64
69
  @confirm="onMassCopyConfirm"
65
70
  @close="showMassCopyDialog = false" />
66
71
 
@@ -94,6 +99,7 @@
94
99
  ref="singleDeleteDialog"
95
100
  :item="actionTargetItem"
96
101
  :name-field="massActionNameField"
102
+ :name-formatter="nameFormatter"
97
103
  @confirm="onSingleDeleteConfirm"
98
104
  @close="closeSingleDelete" />
99
105
  </slot>
@@ -108,6 +114,7 @@
108
114
  ref="singleCopyDialog"
109
115
  :item="actionTargetItem"
110
116
  :name-field="massActionNameField"
117
+ :name-formatter="nameFormatter"
111
118
  @confirm="onSingleCopyConfirm"
112
119
  @close="closeSingleCopy" />
113
120
  </slot>
@@ -500,6 +507,11 @@ export default {
500
507
  type: String,
501
508
  default: 'title',
502
509
  },
510
+ /** Optional function to format item names in dialogs. Receives the item, returns a string. Overrides massActionNameField when provided. */
511
+ nameFormatter: {
512
+ type: Function,
513
+ default: null,
514
+ },
503
515
  /** Available export formats for the export dialog */
504
516
  exportFormats: {
505
517
  type: Array,
@@ -563,6 +575,21 @@ export default {
563
575
  type: Boolean,
564
576
  default: false,
565
577
  },
578
+ /** Whether the refresh action is disabled (e.g. when required selections are missing) */
579
+ refreshDisabled: {
580
+ type: Boolean,
581
+ default: false,
582
+ },
583
+ /** Whether the Add button is disabled (e.g. when required selections are missing) */
584
+ addDisabled: {
585
+ type: Boolean,
586
+ default: false,
587
+ },
588
+ /** Whether to show the Add button in the actions bar */
589
+ showAdd: {
590
+ type: Boolean,
591
+ default: true,
592
+ },
566
593
  /**
567
594
  * Store instance for automatic save integration. When provided alongside
568
595
  * objectType, the form dialog saves directly to the store instead of
@@ -751,28 +778,40 @@ export default {
751
778
  this.$emit('mass-import', payload)
752
779
  },
753
780
 
754
- /** @public Forward result to mass delete dialog */
781
+ /**
782
+ * @param {*} resultData Result data to pass to the dialog
783
+ * @public
784
+ */
755
785
  setMassDeleteResult(resultData) {
756
786
  if (this.$refs.massDeleteDialog) {
757
787
  this.$refs.massDeleteDialog.setResult(resultData)
758
788
  }
759
789
  },
760
790
 
761
- /** @public Forward result to mass copy dialog */
791
+ /**
792
+ * @param {*} resultData Result data to pass to the dialog
793
+ * @public
794
+ */
762
795
  setMassCopyResult(resultData) {
763
796
  if (this.$refs.massCopyDialog) {
764
797
  this.$refs.massCopyDialog.setResult(resultData)
765
798
  }
766
799
  },
767
800
 
768
- /** @public Forward result to export dialog */
801
+ /**
802
+ * @param {*} resultData Result data to pass to the dialog
803
+ * @public
804
+ */
769
805
  setExportResult(resultData) {
770
806
  if (this.$refs.exportDialog) {
771
807
  this.$refs.exportDialog.setResult(resultData)
772
808
  }
773
809
  },
774
810
 
775
- /** @public Forward result to import dialog */
811
+ /**
812
+ * @param {*} resultData Result data to pass to the dialog
813
+ * @public
814
+ */
776
815
  setImportResult(resultData) {
777
816
  if (this.$refs.importDialog) {
778
817
  this.$refs.importDialog.setResult(resultData)
@@ -780,11 +819,17 @@ export default {
780
819
  },
781
820
 
782
821
  // --- Backward-compatible aliases ---
783
- /** @public @deprecated Use setMassDeleteResult instead */
822
+ /**
823
+ * @param {*} resultData Result data to pass to the dialog
824
+ * @public
825
+ */
784
826
  setDeleteResult(resultData) {
785
827
  this.setMassDeleteResult(resultData)
786
828
  },
787
- /** @public @deprecated Use setMassCopyResult instead */
829
+ /**
830
+ * @param {*} resultData Result data to pass to the dialog
831
+ * @public
832
+ */
788
833
  setCopyResult(resultData) {
789
834
  this.setMassCopyResult(resultData)
790
835
  },
@@ -837,21 +882,30 @@ export default {
837
882
  this.editItem = null
838
883
  },
839
884
 
840
- /** @public Forward result to single delete dialog */
885
+ /**
886
+ * @param {*} resultData Result data to pass to the dialog
887
+ * @public
888
+ */
841
889
  setSingleDeleteResult(resultData) {
842
890
  if (this.$refs.singleDeleteDialog) {
843
891
  this.$refs.singleDeleteDialog.setResult(resultData)
844
892
  }
845
893
  },
846
894
 
847
- /** @public Forward result to single copy dialog */
895
+ /**
896
+ * @param {*} resultData Result data to pass to the dialog
897
+ * @public
898
+ */
848
899
  setSingleCopyResult(resultData) {
849
900
  if (this.$refs.singleCopyDialog) {
850
901
  this.$refs.singleCopyDialog.setResult(resultData)
851
902
  }
852
903
  },
853
904
 
854
- /** @public Forward result to form dialog */
905
+ /**
906
+ * @param {*} resultData Result data to pass to the dialog
907
+ * @public
908
+ */
855
909
  setFormResult(resultData) {
856
910
  if (this.$refs.formDialog) {
857
911
  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'