@conduction/nextcloud-vue 0.1.0-beta.1 → 0.1.0-beta.11

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 (208) hide show
  1. package/README.md +226 -0
  2. package/css/index.css +5 -0
  3. package/dist/nextcloud-vue.cjs.js +79416 -7715
  4. package/dist/nextcloud-vue.cjs.js.map +1 -1
  5. package/dist/nextcloud-vue.css +3583 -504
  6. package/dist/nextcloud-vue.esm.js +79343 -7692
  7. package/dist/nextcloud-vue.esm.js.map +1 -1
  8. package/l10n/en.json +164 -0
  9. package/l10n/nl.json +164 -0
  10. package/package.json +104 -63
  11. package/src/components/CnActionsBar/CnActionsBar.vue +254 -0
  12. package/src/components/CnActionsBar/index.js +1 -0
  13. package/src/components/CnAdvancedFormDialog/CnAdvancedFormDialog.vue +570 -0
  14. package/src/components/CnAdvancedFormDialog/CnDataTab.vue +217 -0
  15. package/src/components/CnAdvancedFormDialog/CnMetadataTab.vue +121 -0
  16. package/src/components/CnAdvancedFormDialog/CnPropertiesTab.vue +422 -0
  17. package/src/components/CnAdvancedFormDialog/CnPropertyValueCell.vue +247 -0
  18. package/src/components/CnAdvancedFormDialog/index.js +1 -0
  19. package/src/components/CnCard/CnCard.vue +415 -0
  20. package/src/components/CnCard/index.js +1 -0
  21. package/src/components/CnCardGrid/CnCardGrid.vue +156 -152
  22. package/src/components/CnCardGrid/index.js +1 -1
  23. package/src/components/CnCellRenderer/CnCellRenderer.vue +132 -132
  24. package/src/components/CnCellRenderer/index.js +1 -1
  25. package/src/components/CnChartWidget/CnChartWidget.vue +346 -0
  26. package/src/components/CnChartWidget/index.js +1 -0
  27. package/src/components/CnConfigurationCard/CnConfigurationCard.vue +77 -77
  28. package/src/components/CnConfigurationCard/index.js +1 -1
  29. package/src/components/CnContextMenu/CnContextMenu.vue +142 -0
  30. package/src/components/CnContextMenu/index.js +1 -0
  31. package/src/components/CnCopyDialog/CnCopyDialog.vue +266 -0
  32. package/src/components/CnCopyDialog/index.js +1 -0
  33. package/src/components/CnDashboardGrid/CnDashboardGrid.vue +229 -0
  34. package/src/components/CnDashboardGrid/index.js +1 -0
  35. package/src/components/CnDashboardPage/CnDashboardPage.vue +397 -0
  36. package/src/components/CnDashboardPage/index.js +1 -0
  37. package/src/components/CnDataTable/CnDataTable.vue +362 -354
  38. package/src/components/CnDataTable/index.js +1 -1
  39. package/src/components/CnDeleteDialog/CnDeleteDialog.vue +177 -0
  40. package/src/components/CnDeleteDialog/index.js +1 -0
  41. package/src/components/CnDetailCard/CnDetailCard.vue +225 -0
  42. package/src/components/CnDetailCard/index.js +1 -0
  43. package/src/components/CnDetailGrid/CnDetailGrid.vue +256 -0
  44. package/src/components/CnDetailGrid/index.js +1 -0
  45. package/src/components/CnDetailPage/CnDetailPage.vue +432 -0
  46. package/src/components/CnDetailPage/index.js +1 -0
  47. package/src/components/CnFacetSidebar/CnFacetSidebar.vue +234 -223
  48. package/src/components/CnFacetSidebar/index.js +1 -1
  49. package/src/components/CnFilterBar/CnFilterBar.vue +153 -152
  50. package/src/components/CnFilterBar/index.js +1 -1
  51. package/src/components/CnFormDialog/CnFormDialog.vue +1047 -0
  52. package/src/components/CnFormDialog/index.js +1 -0
  53. package/src/components/CnIcon/CnIcon.vue +89 -0
  54. package/src/components/CnIcon/index.js +1 -0
  55. package/src/components/CnIndexPage/CnIndexPage.vue +980 -682
  56. package/src/components/CnIndexPage/index.js +1 -1
  57. package/src/components/CnIndexSidebar/CnIndexSidebar.vue +536 -0
  58. package/src/components/CnIndexSidebar/index.js +1 -0
  59. package/src/components/CnInfoWidget/CnInfoWidget.vue +219 -0
  60. package/src/components/CnInfoWidget/index.js +1 -0
  61. package/src/components/CnItemCard/CnItemCard.vue +134 -0
  62. package/src/components/CnItemCard/index.js +1 -0
  63. package/src/components/CnJsonViewer/CnJsonViewer.vue +312 -0
  64. package/src/components/CnJsonViewer/index.js +1 -0
  65. package/src/components/CnKpiGrid/CnKpiGrid.vue +93 -89
  66. package/src/components/CnKpiGrid/index.js +1 -1
  67. package/src/components/CnMassActionBar/CnMassActionBar.vue +161 -160
  68. package/src/components/CnMassActionBar/index.js +1 -1
  69. package/src/components/CnMassCopyDialog/CnMassCopyDialog.vue +327 -320
  70. package/src/components/CnMassCopyDialog/index.js +1 -1
  71. package/src/components/CnMassDeleteDialog/CnMassDeleteDialog.vue +245 -238
  72. package/src/components/CnMassDeleteDialog/index.js +1 -1
  73. package/src/components/CnMassExportDialog/CnMassExportDialog.vue +191 -190
  74. package/src/components/CnMassExportDialog/index.js +1 -1
  75. package/src/components/CnMassImportDialog/CnMassImportDialog.vue +494 -491
  76. package/src/components/CnMassImportDialog/index.js +1 -1
  77. package/src/components/CnNoteCard/CnNoteCard.vue +149 -0
  78. package/src/components/CnNoteCard/index.js +1 -0
  79. package/src/components/CnNotesCard/CnNotesCard.vue +416 -0
  80. package/src/components/CnNotesCard/index.js +1 -0
  81. package/src/components/CnObjectCard/CnObjectCard.vue +294 -292
  82. package/src/components/CnObjectCard/index.js +1 -1
  83. package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +854 -0
  84. package/src/components/CnObjectDataWidget/index.js +1 -0
  85. package/src/components/CnObjectMetadataWidget/CnObjectMetadataWidget.vue +289 -0
  86. package/src/components/CnObjectMetadataWidget/index.js +1 -0
  87. package/src/components/CnObjectSidebar/CnAuditTrailTab.vue +369 -0
  88. package/src/components/CnObjectSidebar/CnFilesTab.vue +287 -0
  89. package/src/components/CnObjectSidebar/CnNotesTab.vue +250 -0
  90. package/src/components/CnObjectSidebar/CnObjectSidebar.vue +255 -0
  91. package/src/components/CnObjectSidebar/CnTagsTab.vue +259 -0
  92. package/src/components/CnObjectSidebar/CnTasksTab.vue +483 -0
  93. package/src/components/CnObjectSidebar/index.js +6 -0
  94. package/src/components/CnPageHeader/CnPageHeader.vue +61 -0
  95. package/src/components/CnPageHeader/index.js +1 -0
  96. package/src/components/CnPagination/CnPagination.vue +253 -252
  97. package/src/components/CnPagination/index.js +1 -1
  98. package/src/components/CnProgressBar/CnProgressBar.vue +262 -0
  99. package/src/components/CnProgressBar/index.js +1 -0
  100. package/src/components/CnRegisterMapping/CnRegisterMapping.vue +793 -0
  101. package/src/components/CnRegisterMapping/index.js +1 -0
  102. package/src/components/CnRowActions/CnRowActions.vue +95 -73
  103. package/src/components/CnRowActions/index.js +1 -1
  104. package/src/components/CnSchemaFormDialog/CnSchemaConfigurationTab.vue +226 -0
  105. package/src/components/CnSchemaFormDialog/CnSchemaFormDialog.vue +788 -0
  106. package/src/components/CnSchemaFormDialog/CnSchemaPropertiesTab.vue +305 -0
  107. package/src/components/CnSchemaFormDialog/CnSchemaPropertyActions.vue +1398 -0
  108. package/src/components/CnSchemaFormDialog/CnSchemaSecurityTab.vue +236 -0
  109. package/src/components/CnSchemaFormDialog/index.js +1 -0
  110. package/src/components/CnSettingsCard/CnSettingsCard.vue +92 -92
  111. package/src/components/CnSettingsCard/index.js +1 -1
  112. package/src/components/CnSettingsSection/CnSettingsSection.vue +267 -266
  113. package/src/components/CnSettingsSection/index.js +1 -1
  114. package/src/components/CnStatsBlock/CnStatsBlock.vue +437 -366
  115. package/src/components/CnStatsBlock/index.js +1 -1
  116. package/src/components/CnStatsPanel/CnStatsPanel.vue +321 -0
  117. package/src/components/CnStatsPanel/index.js +1 -0
  118. package/src/components/CnStatusBadge/CnStatusBadge.vue +90 -77
  119. package/src/components/CnStatusBadge/index.js +1 -1
  120. package/src/components/CnTabbedFormDialog/CnTabbedFormDialog.vue +545 -0
  121. package/src/components/CnTabbedFormDialog/index.js +1 -0
  122. package/src/components/CnTableWidget/CnTableWidget.vue +333 -0
  123. package/src/components/CnTableWidget/index.js +1 -0
  124. package/src/components/CnTasksCard/CnTasksCard.vue +374 -0
  125. package/src/components/CnTasksCard/index.js +1 -0
  126. package/src/components/CnTileWidget/CnTileWidget.vue +159 -0
  127. package/src/components/CnTileWidget/index.js +1 -0
  128. package/src/components/CnTimelineStages/CnTimelineStages.vue +294 -0
  129. package/src/components/CnTimelineStages/index.js +1 -0
  130. package/src/components/CnUserActionMenu/CnUserActionMenu.vue +436 -0
  131. package/src/components/CnUserActionMenu/index.js +1 -0
  132. package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +313 -312
  133. package/src/components/CnVersionInfoCard/index.js +1 -1
  134. package/src/components/CnWidgetRenderer/CnWidgetRenderer.vue +180 -0
  135. package/src/components/CnWidgetRenderer/index.js +1 -0
  136. package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +248 -0
  137. package/src/components/CnWidgetWrapper/index.js +1 -0
  138. package/src/components/index.js +57 -25
  139. package/src/composables/index.js +5 -3
  140. package/src/composables/useContextMenu.js +126 -0
  141. package/src/composables/useDashboardView.js +286 -0
  142. package/src/composables/useDetailView.js +290 -132
  143. package/src/composables/useListView.js +364 -153
  144. package/src/composables/useSubResource.js +142 -142
  145. package/src/constants/metadata.js +30 -0
  146. package/src/css/CnSchemaFormDialog.css +546 -0
  147. package/src/css/__sample_nextcloud_tokens.css +110 -0
  148. package/src/css/actions-bar.css +54 -0
  149. package/src/css/badge.css +83 -51
  150. package/src/css/card.css +129 -128
  151. package/src/css/context-menu.css +20 -0
  152. package/src/css/dashboard.css +70 -0
  153. package/src/css/detail-page.css +235 -0
  154. package/src/css/detail.css +68 -68
  155. package/src/css/index-page.css +44 -0
  156. package/src/css/index-sidebar.css +193 -0
  157. package/src/css/index.css +17 -8
  158. package/src/css/layout.css +90 -90
  159. package/src/css/page-header.css +35 -0
  160. package/src/css/pagination.css +72 -72
  161. package/src/css/table.css +142 -143
  162. package/src/css/timeline-stages.css +220 -0
  163. package/src/css/utilities.css +46 -46
  164. package/src/index.js +95 -50
  165. package/src/l10n/index.js +12 -0
  166. package/src/mixins/gridLayout.js +118 -0
  167. package/src/store/createCrudStore.d.ts +350 -0
  168. package/src/store/createCrudStore.js +413 -0
  169. package/src/store/createSubResourcePlugin.js +125 -135
  170. package/src/store/index.js +4 -3
  171. package/src/store/pluginMerge.js +55 -0
  172. package/src/store/plugins/auditTrails.js +357 -17
  173. package/src/store/plugins/files.js +250 -186
  174. package/src/store/plugins/index.js +8 -4
  175. package/src/store/plugins/lifecycle.js +180 -180
  176. package/src/store/plugins/logs.d.ts +22 -0
  177. package/src/store/plugins/logs.js +172 -0
  178. package/src/store/plugins/registerMapping.js +195 -0
  179. package/src/store/plugins/relations.js +68 -68
  180. package/src/store/plugins/search.js +385 -0
  181. package/src/store/plugins/selection.js +104 -0
  182. package/src/store/useObjectStore.js +793 -625
  183. package/src/types/auditTrail.d.ts +32 -32
  184. package/src/types/file.d.ts +23 -23
  185. package/src/types/index.d.ts +67 -35
  186. package/src/types/notification.d.ts +36 -36
  187. package/src/types/object.d.ts +40 -40
  188. package/src/types/organisation.d.ts +41 -41
  189. package/src/types/register.d.ts +25 -25
  190. package/src/types/schema.d.ts +39 -39
  191. package/src/types/shared.d.ts +79 -79
  192. package/src/types/source.d.ts +14 -14
  193. package/src/types/task.d.ts +31 -31
  194. package/src/utils/errors.js +96 -96
  195. package/src/utils/getTheme.js +9 -0
  196. package/src/utils/headers.js +80 -44
  197. package/src/utils/id.js +13 -0
  198. package/src/utils/index.js +4 -3
  199. package/src/utils/schema.js +423 -287
  200. package/src/utils/widgetVisibility.js +162 -0
  201. package/src/components/CnDetailViewLayout/CnDetailViewLayout.vue +0 -88
  202. package/src/components/CnDetailViewLayout/index.js +0 -1
  203. package/src/components/CnEmptyState/CnEmptyState.vue +0 -78
  204. package/src/components/CnEmptyState/index.js +0 -1
  205. package/src/components/CnListViewLayout/CnListViewLayout.vue +0 -80
  206. package/src/components/CnListViewLayout/index.js +0 -1
  207. package/src/components/CnViewModeToggle/CnViewModeToggle.vue +0 -77
  208. package/src/components/CnViewModeToggle/index.js +0 -1
@@ -0,0 +1,266 @@
1
+ <template>
2
+ <NcDialog
3
+ :name="dialogTitle"
4
+ size="small"
5
+ :can-close="!loading"
6
+ @closing="$emit('close')">
7
+ <!-- Result phase -->
8
+ <div v-if="result !== null" class="cn-copy__result">
9
+ <NcNoteCard v-if="result.success" type="success">
10
+ {{ successText }}
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-copy__form">
19
+ <div class="cn-copy__pattern">
20
+ <label for="cn-copy-pattern">{{ patternLabel }}</label>
21
+ <NcSelect
22
+ input-id="cn-copy-pattern"
23
+ :options="patternOptions"
24
+ :value="selectedPattern"
25
+ :clearable="false"
26
+ @input="selectedPattern = $event" />
27
+ </div>
28
+
29
+ <div class="cn-copy__preview">
30
+ <div class="cn-copy__preview-row">
31
+ <span class="cn-copy__preview-original">{{ itemName }}</span>
32
+ <span class="cn-copy__preview-arrow">&rarr;</span>
33
+ <span class="cn-copy__preview-new">{{ newName }}</span>
34
+ </div>
35
+ </div>
36
+ </div>
37
+
38
+ <template #actions>
39
+ <NcButton @click="$emit('close')">
40
+ {{ result !== null ? closeLabel : cancelLabel }}
41
+ </NcButton>
42
+ <NcButton
43
+ v-if="result === null"
44
+ type="primary"
45
+ :disabled="loading"
46
+ @click="executeCopy">
47
+ <template #icon>
48
+ <NcLoadingIcon v-if="loading" :size="20" />
49
+ <ContentCopy v-else :size="20" />
50
+ </template>
51
+ {{ confirmLabel }}
52
+ </NcButton>
53
+ </template>
54
+ </NcDialog>
55
+ </template>
56
+
57
+ <script>
58
+ import { translate as t } from '@nextcloud/l10n'
59
+ import { NcDialog, NcButton, NcNoteCard, NcLoadingIcon, NcSelect } from '@nextcloud/vue'
60
+ import ContentCopy from 'vue-material-design-icons/ContentCopy.vue'
61
+
62
+ /**
63
+ * CnCopyDialog — Single-item copy confirmation dialog with naming pattern.
64
+ *
65
+ * Two-phase UI: form (with name preview) then result. The dialog does NOT
66
+ * perform the copy itself — it emits a `confirm` event with the item ID
67
+ * and the new name. The parent performs the actual API call and calls
68
+ * `setResult()` via a ref.
69
+ *
70
+ * @example
71
+ * <CnCopyDialog
72
+ * v-if="showCopyDialog"
73
+ * ref="copyDialog"
74
+ * :item="itemToCopy"
75
+ * @confirm="onCopyConfirm"
76
+ * @close="showCopyDialog = false" />
77
+ *
78
+ * // In methods:
79
+ * async onCopyConfirm({ id, newName }) {
80
+ * try {
81
+ * await store.copyItem(id, { title: newName })
82
+ * this.$refs.copyDialog.setResult({ success: true })
83
+ * } catch (e) {
84
+ * this.$refs.copyDialog.setResult({ error: e.message })
85
+ * }
86
+ * }
87
+ */
88
+ export default {
89
+ name: 'CnCopyDialog',
90
+
91
+ components: {
92
+ NcDialog,
93
+ NcButton,
94
+ NcNoteCard,
95
+ NcLoadingIcon,
96
+ NcSelect,
97
+ ContentCopy,
98
+ },
99
+
100
+ props: {
101
+ /** The item to copy. Must have an `id` property. */
102
+ item: {
103
+ type: Object,
104
+ required: true,
105
+ },
106
+ /** Property name used for display (e.g., 'title', 'name') */
107
+ nameField: {
108
+ type: String,
109
+ default: 'title',
110
+ },
111
+ /** Optional function to format the item name. Receives the item, returns a string. Overrides nameField when provided. */
112
+ nameFormatter: {
113
+ type: Function,
114
+ default: null,
115
+ },
116
+ /** Dialog title */
117
+ dialogTitle: {
118
+ type: String,
119
+ default: () => t('nextcloud-vue', 'Copy item'),
120
+ },
121
+ /** Label for the naming pattern selector */
122
+ patternLabel: {
123
+ type: String,
124
+ default: () => t('nextcloud-vue', 'Naming pattern'),
125
+ },
126
+ /** Success message */
127
+ successText: {
128
+ type: String,
129
+ default: () => t('nextcloud-vue', 'Item successfully copied.'),
130
+ },
131
+ cancelLabel: { type: String, default: () => t('nextcloud-vue', 'Cancel') },
132
+ closeLabel: { type: String, default: () => t('nextcloud-vue', 'Close') },
133
+ confirmLabel: { type: String, default: () => t('nextcloud-vue', 'Copy') },
134
+ },
135
+
136
+ data() {
137
+ return {
138
+ loading: false,
139
+ result: null,
140
+ closeTimeout: null,
141
+ selectedPatternId: 'copy-of',
142
+ }
143
+ },
144
+
145
+ computed: {
146
+ itemName() {
147
+ if (this.nameFormatter) return this.nameFormatter(this.item)
148
+ return this.item[this.nameField] || this.item.name || this.item.title || this.item.id
149
+ },
150
+
151
+ patternOptions() {
152
+ return [
153
+ { id: 'copy-of', label: t('nextcloud-vue', 'Copy of {name}') },
154
+ { id: 'name-copy', label: t('nextcloud-vue', '{name} - Copy') },
155
+ { id: 'name-parens', label: t('nextcloud-vue', '{name} (Copy)') },
156
+ ]
157
+ },
158
+
159
+ selectedPattern: {
160
+ get() {
161
+ return this.patternOptions.find((p) => p.id === this.selectedPatternId) || this.patternOptions[0]
162
+ },
163
+ set(pattern) {
164
+ this.selectedPatternId = pattern ? pattern.id : 'copy-of'
165
+ },
166
+ },
167
+
168
+ newName() {
169
+ return this.applyPattern(this.itemName, this.selectedPatternId)
170
+ },
171
+ },
172
+
173
+ beforeDestroy() {
174
+ if (this.closeTimeout) clearTimeout(this.closeTimeout)
175
+ },
176
+
177
+ /**
178
+ * @event confirm Emitted when the user confirms copying. Payload: `{ id, newName }`.
179
+ * @event close Emitted when the dialog should be closed (cancel, close button, or auto-close after success).
180
+ */
181
+
182
+ methods: {
183
+ applyPattern(name, patternId) {
184
+ switch (patternId) {
185
+ case 'copy-of':
186
+ return t('nextcloud-vue', 'Copy of {name}', { name })
187
+ case 'name-copy':
188
+ return t('nextcloud-vue', '{name} - Copy', { name })
189
+ case 'name-parens':
190
+ return t('nextcloud-vue', '{name} (Copy)', { name })
191
+ default:
192
+ return t('nextcloud-vue', 'Copy of {name}', { name })
193
+ }
194
+ },
195
+
196
+ executeCopy() {
197
+ this.loading = true
198
+ this.$emit('confirm', {
199
+ id: this.item.id,
200
+ newName: this.newName,
201
+ })
202
+ },
203
+
204
+ /**
205
+ * Set the result of the copy operation. Call this from the parent
206
+ * after the API call completes.
207
+ *
208
+ * @param {{ success?: boolean, error?: string }} resultData - Result data to pass to the dialog
209
+ * @public
210
+ */
211
+ setResult(resultData) {
212
+ this.loading = false
213
+ this.result = resultData
214
+ if (resultData.success) {
215
+ this.closeTimeout = setTimeout(() => {
216
+ this.$emit('close')
217
+ }, 2000)
218
+ }
219
+ },
220
+ },
221
+ }
222
+ </script>
223
+
224
+ <style scoped>
225
+ .cn-copy__pattern {
226
+ margin-bottom: 16px;
227
+ }
228
+
229
+ .cn-copy__pattern label {
230
+ display: block;
231
+ font-weight: 600;
232
+ margin-bottom: 4px;
233
+ }
234
+
235
+ .cn-copy__preview {
236
+ margin-top: 12px;
237
+ }
238
+
239
+ .cn-copy__preview-row {
240
+ display: flex;
241
+ align-items: center;
242
+ gap: 8px;
243
+ padding: 8px 12px;
244
+ background-color: var(--color-background-hover);
245
+ border-radius: var(--border-radius);
246
+ }
247
+
248
+ .cn-copy__preview-original {
249
+ color: var(--color-text-maxcontrast);
250
+ overflow: hidden;
251
+ text-overflow: ellipsis;
252
+ white-space: nowrap;
253
+ }
254
+
255
+ .cn-copy__preview-arrow {
256
+ flex-shrink: 0;
257
+ color: var(--color-text-maxcontrast);
258
+ }
259
+
260
+ .cn-copy__preview-new {
261
+ font-weight: 500;
262
+ overflow: hidden;
263
+ text-overflow: ellipsis;
264
+ white-space: nowrap;
265
+ }
266
+ </style>
@@ -0,0 +1 @@
1
+ export { default as CnCopyDialog } from './CnCopyDialog.vue'
@@ -0,0 +1,229 @@
1
+ <!--
2
+ CnDashboardGrid — GridStack-powered drag-and-drop dashboard grid.
3
+
4
+ Renders widgets in a configurable grid layout. Supports drag, resize,
5
+ and dynamic item addition/removal. Emits layout changes for persistence.
6
+ -->
7
+ <template>
8
+ <div ref="gridContainer" class="cn-dashboard-grid">
9
+ <div class="grid-stack">
10
+ <div
11
+ v-for="item in layout"
12
+ :key="item.id"
13
+ class="grid-stack-item"
14
+ :gs-id="item.id"
15
+ :gs-x="item.gridX"
16
+ :gs-y="item.gridY"
17
+ :gs-w="item.gridWidth"
18
+ :gs-h="item.gridHeight"
19
+ :gs-min-w="minWidth"
20
+ :gs-min-h="minHeight">
21
+ <div class="grid-stack-item-content">
22
+ <slot name="widget" :item="item">
23
+ <!-- Default: render nothing; CnDashboardPage provides content -->
24
+ </slot>
25
+ </div>
26
+ </div>
27
+ </div>
28
+ </div>
29
+ </template>
30
+
31
+ <script>
32
+ import { GridStack } from 'gridstack'
33
+ import 'gridstack/dist/gridstack.min.css'
34
+
35
+ /**
36
+ * CnDashboardGrid — Low-level grid layout engine powered by GridStack.
37
+ *
38
+ * Manages the drag-and-drop grid, syncs positions, and emits layout
39
+ * changes. Does NOT handle widget rendering — that's done by the parent
40
+ * via the `#widget` scoped slot.
41
+ *
42
+ * @example
43
+ * <CnDashboardGrid
44
+ * :layout="placements"
45
+ * :editable="isEditing"
46
+ * @layout-change="onLayoutChange">
47
+ * <template #widget="{ item }">
48
+ * <MyWidget :config="item" />
49
+ * </template>
50
+ * </CnDashboardGrid>
51
+ */
52
+ export default {
53
+ name: 'CnDashboardGrid',
54
+
55
+ props: {
56
+ /** Array of layout items: { id, gridX, gridY, gridWidth, gridHeight, ...extra } */
57
+ layout: {
58
+ type: Array,
59
+ required: true,
60
+ },
61
+ /** Whether drag and resize are enabled */
62
+ editable: {
63
+ type: Boolean,
64
+ default: false,
65
+ },
66
+ /** Number of grid columns */
67
+ columns: {
68
+ type: Number,
69
+ default: 12,
70
+ },
71
+ /** Cell height in pixels */
72
+ cellHeight: {
73
+ type: Number,
74
+ default: 80,
75
+ },
76
+ /** Grid margin in pixels */
77
+ margin: {
78
+ type: Number,
79
+ default: 12,
80
+ },
81
+ /** Minimum widget width in grid units */
82
+ minWidth: {
83
+ type: Number,
84
+ default: 2,
85
+ },
86
+ /** Minimum widget height in grid units */
87
+ minHeight: {
88
+ type: Number,
89
+ default: 2,
90
+ },
91
+ },
92
+
93
+ emits: ['layout-change'],
94
+
95
+ data() {
96
+ return {
97
+ grid: null,
98
+ }
99
+ },
100
+
101
+ watch: {
102
+ editable(val) {
103
+ if (!this.grid) return
104
+ if (val) {
105
+ this.grid.enable()
106
+ } else {
107
+ this.grid.disable()
108
+ }
109
+ },
110
+
111
+ layout: {
112
+ deep: true,
113
+ handler(newLayout) {
114
+ if (this.grid) {
115
+ this.syncGridItems(newLayout)
116
+ }
117
+ },
118
+ },
119
+ },
120
+
121
+ mounted() {
122
+ this.initGrid()
123
+ },
124
+
125
+ beforeDestroy() {
126
+ if (this.grid) {
127
+ this.grid.destroy(false)
128
+ }
129
+ },
130
+
131
+ methods: {
132
+ initGrid() {
133
+ const el = this.$refs.gridContainer.querySelector('.grid-stack')
134
+ this.grid = GridStack.init({
135
+ column: this.columns,
136
+ cellHeight: this.cellHeight,
137
+ margin: this.margin,
138
+ float: true,
139
+ animate: true,
140
+ disableDrag: !this.editable,
141
+ disableResize: !this.editable,
142
+ removable: false,
143
+ }, el)
144
+
145
+ this.grid.on('change', (_event, items) => {
146
+ this.handleGridChange(items)
147
+ })
148
+ },
149
+
150
+ handleGridChange(items) {
151
+ if (!items || items.length === 0) return
152
+
153
+ const updated = this.layout.map(item => {
154
+ const gridItem = items.find(gi => String(gi.id) === String(item.id))
155
+ if (gridItem) {
156
+ return {
157
+ ...item,
158
+ gridX: gridItem.x,
159
+ gridY: gridItem.y,
160
+ gridWidth: gridItem.w,
161
+ gridHeight: gridItem.h,
162
+ }
163
+ }
164
+ return item
165
+ })
166
+
167
+ this.$emit('layout-change', updated)
168
+ },
169
+
170
+ syncGridItems(newLayout) {
171
+ // Add items that don't exist in grid yet
172
+ for (const item of newLayout) {
173
+ const exists = this.grid.engine.nodes.find(
174
+ n => String(n.id) === String(item.id),
175
+ )
176
+ if (!exists) {
177
+ this.$nextTick(() => {
178
+ const el = this.$refs.gridContainer.querySelector(`[gs-id="${item.id}"]`)
179
+ if (el) {
180
+ this.grid.makeWidget(el)
181
+ }
182
+ })
183
+ }
184
+ }
185
+
186
+ // Remove items no longer in layout
187
+ const ids = newLayout.map(i => String(i.id))
188
+ const toRemove = this.grid.engine.nodes.filter(
189
+ n => !ids.includes(String(n.id)),
190
+ )
191
+ for (const node of toRemove) {
192
+ const el = this.$refs.gridContainer.querySelector(`[gs-id="${node.id}"]`)
193
+ if (el) {
194
+ this.grid.removeWidget(el, false)
195
+ }
196
+ }
197
+ },
198
+ },
199
+ }
200
+ </script>
201
+
202
+ <style scoped>
203
+ .cn-dashboard-grid {
204
+ width: 100%;
205
+ min-height: 200px;
206
+ }
207
+
208
+ .grid-stack {
209
+ background: transparent;
210
+ }
211
+
212
+ :deep(.grid-stack-item-content) {
213
+ background: var(--color-main-background);
214
+ border-radius: 0;
215
+ border: none;
216
+ box-shadow: none;
217
+ overflow: hidden;
218
+ }
219
+
220
+ :deep(.grid-stack-item-content:has(.cn-widget-wrapper--borderless)) {
221
+ background: transparent;
222
+ }
223
+
224
+ :deep(.grid-stack-placeholder > .placeholder-content) {
225
+ background: var(--color-primary-element-light);
226
+ border: 2px dashed var(--color-primary-element);
227
+ border-radius: var(--border-radius-large);
228
+ }
229
+ </style>
@@ -0,0 +1 @@
1
+ export { default as CnDashboardGrid } from './CnDashboardGrid.vue'