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

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 (197) hide show
  1. package/README.md +226 -0
  2. package/css/index.css +5 -0
  3. package/dist/nextcloud-vue.cjs +67614 -0
  4. package/dist/nextcloud-vue.cjs.js +76311 -5905
  5. package/dist/nextcloud-vue.cjs.js.map +1 -1
  6. package/dist/nextcloud-vue.cjs.map +1 -0
  7. package/dist/nextcloud-vue.css +3279 -203
  8. package/dist/nextcloud-vue.esm.js +76240 -5882
  9. package/dist/nextcloud-vue.esm.js.map +1 -1
  10. package/package.json +89 -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 +569 -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 +23 -20
  22. package/src/components/CnCardGrid/index.js +1 -1
  23. package/src/components/CnCellRenderer/index.js +1 -1
  24. package/src/components/CnChartWidget/CnChartWidget.vue +318 -0
  25. package/src/components/CnChartWidget/index.js +1 -0
  26. package/src/components/CnConfigurationCard/index.js +1 -1
  27. package/src/components/CnContextMenu/CnContextMenu.vue +142 -0
  28. package/src/components/CnContextMenu/index.js +1 -0
  29. package/src/components/CnCopyDialog/CnCopyDialog.vue +257 -0
  30. package/src/components/CnCopyDialog/index.js +1 -0
  31. package/src/components/CnDashboardGrid/CnDashboardGrid.vue +229 -0
  32. package/src/components/CnDashboardGrid/index.js +1 -0
  33. package/src/components/CnDashboardPage/CnDashboardPage.vue +396 -0
  34. package/src/components/CnDashboardPage/index.js +1 -0
  35. package/src/components/CnDataTable/CnDataTable.vue +24 -16
  36. package/src/components/CnDataTable/index.js +1 -1
  37. package/src/components/CnDeleteDialog/CnDeleteDialog.vue +177 -0
  38. package/src/components/CnDeleteDialog/index.js +1 -0
  39. package/src/components/CnDetailCard/CnDetailCard.vue +225 -0
  40. package/src/components/CnDetailCard/index.js +1 -0
  41. package/src/components/CnDetailGrid/CnDetailGrid.vue +254 -0
  42. package/src/components/CnDetailGrid/index.js +1 -0
  43. package/src/components/CnDetailPage/CnDetailPage.vue +431 -0
  44. package/src/components/CnDetailPage/index.js +1 -0
  45. package/src/components/CnFacetSidebar/CnFacetSidebar.vue +12 -2
  46. package/src/components/CnFacetSidebar/index.js +1 -1
  47. package/src/components/CnFilterBar/index.js +1 -1
  48. package/src/components/CnFormDialog/CnFormDialog.vue +934 -0
  49. package/src/components/CnFormDialog/index.js +1 -0
  50. package/src/components/CnIcon/CnIcon.vue +89 -0
  51. package/src/components/CnIcon/index.js +1 -0
  52. package/src/components/CnIndexPage/CnIndexPage.vue +589 -291
  53. package/src/components/CnIndexPage/index.js +1 -1
  54. package/src/components/CnIndexSidebar/CnIndexSidebar.vue +535 -0
  55. package/src/components/CnIndexSidebar/index.js +1 -0
  56. package/src/components/CnInfoWidget/CnInfoWidget.vue +219 -0
  57. package/src/components/CnInfoWidget/index.js +1 -0
  58. package/src/components/CnItemCard/CnItemCard.vue +134 -0
  59. package/src/components/CnItemCard/index.js +1 -0
  60. package/src/components/CnJsonViewer/CnJsonViewer.vue +283 -0
  61. package/src/components/CnJsonViewer/index.js +1 -0
  62. package/src/components/CnKpiGrid/CnKpiGrid.vue +5 -1
  63. package/src/components/CnKpiGrid/index.js +1 -1
  64. package/src/components/CnMassActionBar/CnMassActionBar.vue +6 -5
  65. package/src/components/CnMassActionBar/index.js +1 -1
  66. package/src/components/CnMassCopyDialog/CnMassCopyDialog.vue +16 -9
  67. package/src/components/CnMassCopyDialog/index.js +1 -1
  68. package/src/components/CnMassDeleteDialog/CnMassDeleteDialog.vue +16 -9
  69. package/src/components/CnMassDeleteDialog/index.js +1 -1
  70. package/src/components/CnMassExportDialog/CnMassExportDialog.vue +8 -7
  71. package/src/components/CnMassExportDialog/index.js +1 -1
  72. package/src/components/CnMassImportDialog/CnMassImportDialog.vue +20 -17
  73. package/src/components/CnMassImportDialog/index.js +1 -1
  74. package/src/components/CnNoteCard/CnNoteCard.vue +149 -0
  75. package/src/components/CnNoteCard/index.js +1 -0
  76. package/src/components/CnNotesCard/CnNotesCard.vue +415 -0
  77. package/src/components/CnNotesCard/index.js +1 -0
  78. package/src/components/CnObjectCard/CnObjectCard.vue +3 -1
  79. package/src/components/CnObjectCard/index.js +1 -1
  80. package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +853 -0
  81. package/src/components/CnObjectDataWidget/index.js +1 -0
  82. package/src/components/CnObjectMetadataWidget/CnObjectMetadataWidget.vue +288 -0
  83. package/src/components/CnObjectMetadataWidget/index.js +1 -0
  84. package/src/components/CnObjectSidebar/CnAuditTrailTab.vue +368 -0
  85. package/src/components/CnObjectSidebar/CnFilesTab.vue +286 -0
  86. package/src/components/CnObjectSidebar/CnNotesTab.vue +249 -0
  87. package/src/components/CnObjectSidebar/CnObjectSidebar.vue +254 -0
  88. package/src/components/CnObjectSidebar/CnTagsTab.vue +258 -0
  89. package/src/components/CnObjectSidebar/CnTasksTab.vue +482 -0
  90. package/src/components/CnObjectSidebar/index.js +6 -0
  91. package/src/components/CnPageHeader/CnPageHeader.vue +61 -0
  92. package/src/components/CnPageHeader/index.js +1 -0
  93. package/src/components/CnPagination/CnPagination.vue +7 -6
  94. package/src/components/CnPagination/index.js +1 -1
  95. package/src/components/CnProgressBar/CnProgressBar.vue +262 -0
  96. package/src/components/CnProgressBar/index.js +1 -0
  97. package/src/components/CnRegisterMapping/CnRegisterMapping.vue +792 -0
  98. package/src/components/CnRegisterMapping/index.js +1 -0
  99. package/src/components/CnRowActions/CnRowActions.vue +25 -3
  100. package/src/components/CnRowActions/index.js +1 -1
  101. package/src/components/CnSchemaFormDialog/CnSchemaConfigurationTab.vue +226 -0
  102. package/src/components/CnSchemaFormDialog/CnSchemaFormDialog.vue +787 -0
  103. package/src/components/CnSchemaFormDialog/CnSchemaPropertiesTab.vue +305 -0
  104. package/src/components/CnSchemaFormDialog/CnSchemaPropertyActions.vue +1398 -0
  105. package/src/components/CnSchemaFormDialog/CnSchemaSecurityTab.vue +236 -0
  106. package/src/components/CnSchemaFormDialog/index.js +1 -0
  107. package/src/components/CnSettingsCard/index.js +1 -1
  108. package/src/components/CnSettingsSection/index.js +1 -1
  109. package/src/components/CnStatsBlock/CnStatsBlock.vue +89 -19
  110. package/src/components/CnStatsBlock/index.js +1 -1
  111. package/src/components/CnStatsPanel/CnStatsPanel.vue +320 -0
  112. package/src/components/CnStatsPanel/index.js +1 -0
  113. package/src/components/CnStatusBadge/CnStatusBadge.vue +15 -2
  114. package/src/components/CnStatusBadge/index.js +1 -1
  115. package/src/components/CnTabbedFormDialog/CnTabbedFormDialog.vue +544 -0
  116. package/src/components/CnTabbedFormDialog/index.js +1 -0
  117. package/src/components/CnTableWidget/CnTableWidget.vue +332 -0
  118. package/src/components/CnTableWidget/index.js +1 -0
  119. package/src/components/CnTasksCard/CnTasksCard.vue +373 -0
  120. package/src/components/CnTasksCard/index.js +1 -0
  121. package/src/components/CnTileWidget/CnTileWidget.vue +159 -0
  122. package/src/components/CnTileWidget/index.js +1 -0
  123. package/src/components/CnTimelineStages/CnTimelineStages.vue +292 -0
  124. package/src/components/CnTimelineStages/index.js +1 -0
  125. package/src/components/CnUserActionMenu/CnUserActionMenu.vue +435 -0
  126. package/src/components/CnUserActionMenu/index.js +1 -0
  127. package/src/components/CnVersionInfoCard/index.js +1 -1
  128. package/src/components/CnWidgetRenderer/CnWidgetRenderer.vue +180 -0
  129. package/src/components/CnWidgetRenderer/index.js +1 -0
  130. package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +246 -0
  131. package/src/components/CnWidgetWrapper/index.js +1 -0
  132. package/src/components/index.js +57 -25
  133. package/src/composables/index.js +5 -3
  134. package/src/composables/useContextMenu.js +126 -0
  135. package/src/composables/useDashboardView.js +286 -0
  136. package/src/composables/useDetailView.js +290 -132
  137. package/src/composables/useListView.js +364 -153
  138. package/src/composables/useSubResource.js +142 -142
  139. package/src/constants/metadata.js +30 -0
  140. package/src/css/CnSchemaFormDialog.css +546 -0
  141. package/src/css/__sample_nextcloud_tokens.css +110 -0
  142. package/src/css/actions-bar.css +54 -0
  143. package/src/css/badge.css +83 -51
  144. package/src/css/card.css +129 -128
  145. package/src/css/context-menu.css +20 -0
  146. package/src/css/dashboard.css +70 -0
  147. package/src/css/detail-page.css +235 -0
  148. package/src/css/detail.css +68 -68
  149. package/src/css/index-page.css +44 -0
  150. package/src/css/index-sidebar.css +193 -0
  151. package/src/css/index.css +17 -8
  152. package/src/css/layout.css +90 -90
  153. package/src/css/page-header.css +35 -0
  154. package/src/css/pagination.css +72 -72
  155. package/src/css/table.css +142 -143
  156. package/src/css/timeline-stages.css +220 -0
  157. package/src/css/utilities.css +46 -46
  158. package/src/index.js +91 -50
  159. package/src/mixins/gridLayout.js +118 -0
  160. package/src/store/createCrudStore.js +360 -0
  161. package/src/store/createSubResourcePlugin.js +125 -135
  162. package/src/store/index.js +4 -3
  163. package/src/store/plugins/auditTrails.js +357 -17
  164. package/src/store/plugins/files.js +250 -186
  165. package/src/store/plugins/index.js +7 -4
  166. package/src/store/plugins/lifecycle.js +180 -180
  167. package/src/store/plugins/registerMapping.js +195 -0
  168. package/src/store/plugins/relations.js +68 -68
  169. package/src/store/plugins/search.js +385 -0
  170. package/src/store/plugins/selection.js +104 -0
  171. package/src/store/useObjectStore.js +823 -625
  172. package/src/types/auditTrail.d.ts +32 -32
  173. package/src/types/file.d.ts +23 -23
  174. package/src/types/index.d.ts +35 -35
  175. package/src/types/notification.d.ts +36 -36
  176. package/src/types/object.d.ts +40 -40
  177. package/src/types/organisation.d.ts +41 -41
  178. package/src/types/register.d.ts +25 -25
  179. package/src/types/schema.d.ts +39 -39
  180. package/src/types/shared.d.ts +79 -79
  181. package/src/types/source.d.ts +14 -14
  182. package/src/types/task.d.ts +31 -31
  183. package/src/utils/errors.js +96 -96
  184. package/src/utils/getTheme.js +9 -0
  185. package/src/utils/headers.js +80 -44
  186. package/src/utils/id.js +13 -0
  187. package/src/utils/index.js +4 -3
  188. package/src/utils/schema.js +422 -287
  189. package/src/utils/widgetVisibility.js +162 -0
  190. package/src/components/CnDetailViewLayout/CnDetailViewLayout.vue +0 -88
  191. package/src/components/CnDetailViewLayout/index.js +0 -1
  192. package/src/components/CnEmptyState/CnEmptyState.vue +0 -78
  193. package/src/components/CnEmptyState/index.js +0 -1
  194. package/src/components/CnListViewLayout/CnListViewLayout.vue +0 -80
  195. package/src/components/CnListViewLayout/index.js +0 -1
  196. package/src/components/CnViewModeToggle/CnViewModeToggle.vue +0 -77
  197. package/src/components/CnViewModeToggle/index.js +0 -1
@@ -0,0 +1,257 @@
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
+ selectedPattern: { id: 'copy-of', label: 'Copy of {name}' },
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
+ newName() {
160
+ return this.applyPattern(this.itemName, this.selectedPattern.id)
161
+ },
162
+ },
163
+
164
+ beforeDestroy() {
165
+ if (this.closeTimeout) clearTimeout(this.closeTimeout)
166
+ },
167
+
168
+ /**
169
+ * @event confirm Emitted when the user confirms copying. Payload: `{ id, newName }`.
170
+ * @event close Emitted when the dialog should be closed (cancel, close button, or auto-close after success).
171
+ */
172
+
173
+ methods: {
174
+ applyPattern(name, patternId) {
175
+ switch (patternId) {
176
+ case 'copy-of':
177
+ return `Copy of ${name}`
178
+ case 'name-copy':
179
+ return `${name} - Copy`
180
+ case 'name-parens':
181
+ return `${name} (Copy)`
182
+ default:
183
+ return `Copy of ${name}`
184
+ }
185
+ },
186
+
187
+ executeCopy() {
188
+ this.loading = true
189
+ this.$emit('confirm', {
190
+ id: this.item.id,
191
+ newName: this.newName,
192
+ })
193
+ },
194
+
195
+ /**
196
+ * Set the result of the copy operation. Call this from the parent
197
+ * after the API call completes.
198
+ *
199
+ * @param {{ success?: boolean, error?: string }} resultData - Result data to pass to the dialog
200
+ * @public
201
+ */
202
+ setResult(resultData) {
203
+ this.loading = false
204
+ this.result = resultData
205
+ if (resultData.success) {
206
+ this.closeTimeout = setTimeout(() => {
207
+ this.$emit('close')
208
+ }, 2000)
209
+ }
210
+ },
211
+ },
212
+ }
213
+ </script>
214
+
215
+ <style scoped>
216
+ .cn-copy__pattern {
217
+ margin-bottom: 16px;
218
+ }
219
+
220
+ .cn-copy__pattern label {
221
+ display: block;
222
+ font-weight: 600;
223
+ margin-bottom: 4px;
224
+ }
225
+
226
+ .cn-copy__preview {
227
+ margin-top: 12px;
228
+ }
229
+
230
+ .cn-copy__preview-row {
231
+ display: flex;
232
+ align-items: center;
233
+ gap: 8px;
234
+ padding: 8px 12px;
235
+ background-color: var(--color-background-hover);
236
+ border-radius: var(--border-radius);
237
+ }
238
+
239
+ .cn-copy__preview-original {
240
+ color: var(--color-text-maxcontrast);
241
+ overflow: hidden;
242
+ text-overflow: ellipsis;
243
+ white-space: nowrap;
244
+ }
245
+
246
+ .cn-copy__preview-arrow {
247
+ flex-shrink: 0;
248
+ color: var(--color-text-maxcontrast);
249
+ }
250
+
251
+ .cn-copy__preview-new {
252
+ font-weight: 500;
253
+ overflow: hidden;
254
+ text-overflow: ellipsis;
255
+ white-space: nowrap;
256
+ }
257
+ </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'