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

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 (153) hide show
  1. package/README.md +226 -226
  2. package/css/index.css +5 -0
  3. package/dist/nextcloud-vue.cjs.js +60455 -8755
  4. package/dist/nextcloud-vue.cjs.js.map +1 -1
  5. package/dist/nextcloud-vue.css +2062 -528
  6. package/dist/nextcloud-vue.esm.js +60411 -8731
  7. package/dist/nextcloud-vue.esm.js.map +1 -1
  8. package/package.json +75 -61
  9. package/src/components/CnActionsBar/CnActionsBar.vue +235 -225
  10. package/src/components/CnActionsBar/index.js +1 -1
  11. package/src/components/CnAdvancedFormDialog/CnAdvancedFormDialog.vue +579 -0
  12. package/src/components/CnAdvancedFormDialog/CnDataTab.vue +217 -0
  13. package/src/components/CnAdvancedFormDialog/CnMetadataTab.vue +121 -0
  14. package/src/components/CnAdvancedFormDialog/CnPropertiesTab.vue +418 -0
  15. package/src/components/CnAdvancedFormDialog/CnPropertyValueCell.vue +247 -0
  16. package/src/components/CnAdvancedFormDialog/index.js +1 -0
  17. package/src/components/CnCardGrid/CnCardGrid.vue +152 -152
  18. package/src/components/CnCardGrid/index.js +1 -1
  19. package/src/components/CnCellRenderer/CnCellRenderer.vue +132 -132
  20. package/src/components/CnCellRenderer/index.js +1 -1
  21. package/src/components/CnChartWidget/CnChartWidget.vue +320 -0
  22. package/src/components/CnChartWidget/index.js +1 -0
  23. package/src/components/CnConfigurationCard/CnConfigurationCard.vue +77 -77
  24. package/src/components/CnConfigurationCard/index.js +1 -1
  25. package/src/components/CnDashboardGrid/CnDashboardGrid.vue +225 -0
  26. package/src/components/CnDashboardGrid/index.js +1 -0
  27. package/src/components/CnDashboardPage/CnDashboardPage.vue +390 -0
  28. package/src/components/CnDashboardPage/index.js +1 -0
  29. package/src/components/CnDataTable/CnDataTable.vue +349 -349
  30. package/src/components/CnDataTable/index.js +1 -1
  31. package/src/components/CnDetailCard/CnDetailCard.vue +214 -0
  32. package/src/components/CnDetailCard/index.js +1 -0
  33. package/src/components/CnDetailPage/CnDetailPage.vue +281 -0
  34. package/src/components/CnDetailPage/index.js +1 -0
  35. package/src/components/CnFacetSidebar/CnFacetSidebar.vue +231 -223
  36. package/src/components/CnFacetSidebar/index.js +1 -1
  37. package/src/components/CnFilterBar/CnFilterBar.vue +152 -152
  38. package/src/components/CnFilterBar/index.js +1 -1
  39. package/src/components/CnIcon/CnIcon.vue +89 -89
  40. package/src/components/CnIcon/index.js +1 -1
  41. package/src/components/CnIndexPage/CnIndexPage.vue +874 -816
  42. package/src/components/CnIndexPage/index.js +1 -1
  43. package/src/components/CnIndexSidebar/CnIndexSidebar.vue +503 -484
  44. package/src/components/CnIndexSidebar/index.js +1 -1
  45. package/src/components/CnItemCard/CnItemCard.vue +132 -0
  46. package/src/components/CnItemCard/index.js +1 -0
  47. package/src/components/CnKpiGrid/CnKpiGrid.vue +89 -89
  48. package/src/components/CnKpiGrid/index.js +1 -1
  49. package/src/components/CnMassActionBar/CnMassActionBar.vue +160 -160
  50. package/src/components/CnMassActionBar/index.js +1 -1
  51. package/src/components/CnMassCopyDialog/CnMassCopyDialog.vue +320 -320
  52. package/src/components/CnMassCopyDialog/index.js +1 -1
  53. package/src/components/CnMassDeleteDialog/CnMassDeleteDialog.vue +238 -238
  54. package/src/components/CnMassDeleteDialog/index.js +1 -1
  55. package/src/components/CnMassExportDialog/CnMassExportDialog.vue +190 -190
  56. package/src/components/CnMassExportDialog/index.js +1 -1
  57. package/src/components/CnMassImportDialog/CnMassImportDialog.vue +491 -491
  58. package/src/components/CnMassImportDialog/index.js +1 -1
  59. package/src/components/CnNoteCard/CnNoteCard.vue +149 -0
  60. package/src/components/CnNoteCard/index.js +1 -0
  61. package/src/components/CnNotesCard/CnNotesCard.vue +413 -0
  62. package/src/components/CnNotesCard/index.js +1 -0
  63. package/src/components/CnObjectCard/CnObjectCard.vue +292 -292
  64. package/src/components/CnObjectCard/index.js +1 -1
  65. package/src/components/CnObjectSidebar/CnObjectSidebar.vue +876 -0
  66. package/src/components/CnObjectSidebar/index.js +1 -0
  67. package/src/components/CnPageHeader/CnPageHeader.vue +57 -57
  68. package/src/components/CnPageHeader/index.js +1 -1
  69. package/src/components/CnPagination/CnPagination.vue +252 -252
  70. package/src/components/CnPagination/index.js +1 -1
  71. package/src/components/CnRowActions/CnRowActions.vue +73 -73
  72. package/src/components/CnRowActions/index.js +1 -1
  73. package/src/components/CnSchemaFormDialog/CnSchemaConfigurationTab.vue +226 -0
  74. package/src/components/CnSchemaFormDialog/CnSchemaFormDialog.vue +787 -0
  75. package/src/components/CnSchemaFormDialog/CnSchemaPropertiesTab.vue +305 -0
  76. package/src/components/CnSchemaFormDialog/CnSchemaPropertyActions.vue +1398 -0
  77. package/src/components/CnSchemaFormDialog/CnSchemaSecurityTab.vue +236 -0
  78. package/src/components/CnSchemaFormDialog/index.js +1 -0
  79. package/src/components/CnSettingsCard/CnSettingsCard.vue +92 -92
  80. package/src/components/CnSettingsCard/index.js +1 -1
  81. package/src/components/CnSettingsSection/CnSettingsSection.vue +266 -266
  82. package/src/components/CnSettingsSection/index.js +1 -1
  83. package/src/components/CnStatsBlock/CnStatsBlock.vue +420 -366
  84. package/src/components/CnStatsBlock/index.js +1 -1
  85. package/src/components/CnStatusBadge/CnStatusBadge.vue +77 -77
  86. package/src/components/CnStatusBadge/index.js +1 -1
  87. package/src/components/CnTabbedFormDialog/CnTabbedFormDialog.vue +540 -0
  88. package/src/components/CnTabbedFormDialog/index.js +1 -0
  89. package/src/components/CnTasksCard/CnTasksCard.vue +373 -0
  90. package/src/components/CnTasksCard/index.js +1 -0
  91. package/src/components/CnTileWidget/CnTileWidget.vue +159 -0
  92. package/src/components/CnTileWidget/index.js +1 -0
  93. package/src/components/CnTimelineStages/CnTimelineStages.vue +292 -0
  94. package/src/components/CnTimelineStages/index.js +1 -0
  95. package/src/components/CnUserActionMenu/CnUserActionMenu.vue +435 -0
  96. package/src/components/CnUserActionMenu/index.js +1 -0
  97. package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +312 -312
  98. package/src/components/CnVersionInfoCard/index.js +1 -1
  99. package/src/components/CnWidgetRenderer/CnWidgetRenderer.vue +180 -0
  100. package/src/components/CnWidgetRenderer/index.js +1 -0
  101. package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +211 -0
  102. package/src/components/CnWidgetWrapper/index.js +1 -0
  103. package/src/components/index.js +43 -29
  104. package/src/composables/index.js +4 -3
  105. package/src/composables/useDashboardView.js +240 -0
  106. package/src/composables/useDetailView.js +289 -132
  107. package/src/composables/useListView.js +363 -153
  108. package/src/composables/useSubResource.js +142 -142
  109. package/src/constants/metadata.js +30 -30
  110. package/src/css/CnSchemaFormDialog.css +546 -0
  111. package/src/css/__sample_nextcloud_tokens.css +110 -0
  112. package/src/css/actions-bar.css +48 -48
  113. package/src/css/badge.css +51 -51
  114. package/src/css/card.css +128 -128
  115. package/src/css/dashboard.css +70 -0
  116. package/src/css/detail-page.css +168 -0
  117. package/src/css/detail.css +68 -68
  118. package/src/css/index-page.css +44 -32
  119. package/src/css/index-sidebar.css +193 -187
  120. package/src/css/index.css +16 -12
  121. package/src/css/layout.css +90 -90
  122. package/src/css/page-header.css +33 -33
  123. package/src/css/pagination.css +72 -72
  124. package/src/css/table.css +142 -142
  125. package/src/css/timeline-stages.css +218 -0
  126. package/src/css/utilities.css +46 -46
  127. package/src/index.js +72 -53
  128. package/src/store/createSubResourcePlugin.js +135 -135
  129. package/src/store/index.js +3 -3
  130. package/src/store/plugins/auditTrails.js +17 -17
  131. package/src/store/plugins/files.js +250 -186
  132. package/src/store/plugins/index.js +7 -5
  133. package/src/store/plugins/lifecycle.js +180 -180
  134. package/src/store/plugins/relations.js +68 -68
  135. package/src/store/plugins/search.js +372 -0
  136. package/src/store/plugins/selection.js +104 -0
  137. package/src/store/useObjectStore.js +829 -686
  138. package/src/types/auditTrail.d.ts +32 -32
  139. package/src/types/file.d.ts +23 -23
  140. package/src/types/index.d.ts +35 -35
  141. package/src/types/notification.d.ts +36 -36
  142. package/src/types/object.d.ts +40 -40
  143. package/src/types/organisation.d.ts +41 -41
  144. package/src/types/register.d.ts +25 -25
  145. package/src/types/schema.d.ts +39 -39
  146. package/src/types/shared.d.ts +79 -79
  147. package/src/types/source.d.ts +14 -14
  148. package/src/types/task.d.ts +31 -31
  149. package/src/utils/errors.js +96 -96
  150. package/src/utils/headers.js +68 -50
  151. package/src/utils/id.js +13 -0
  152. package/src/utils/index.js +3 -3
  153. package/src/utils/schema.js +422 -419
@@ -0,0 +1,372 @@
1
+ import { buildHeaders, buildQueryString, prefixUrl } from '../../utils/headers.js'
2
+
3
+ /**
4
+ * The type slug used internally by the search plugin.
5
+ * Consumers can import this constant to avoid hard-coding the string 'search'.
6
+ *
7
+ * @type {string}
8
+ */
9
+ export const SEARCH_TYPE = 'search'
10
+
11
+ /**
12
+ * Build the API URL for fetching a register by ID.
13
+ *
14
+ * @param {string} registerId OpenRegister register ID
15
+ * @return {string} Full API URL path
16
+ */
17
+ export function getRegisterApiUrl(registerId) {
18
+ return prefixUrl(`/apps/openregister/api/registers/${registerId}`)
19
+ }
20
+
21
+ /**
22
+ * Build the API URL for fetching a schema by ID.
23
+ *
24
+ * @param {string} schemaId OpenRegister schema ID
25
+ * @return {string} Full API URL path
26
+ */
27
+ export function getSchemaApiUrl(schemaId) {
28
+ return prefixUrl(`/apps/openregister/api/schemas/${schemaId}`)
29
+ }
30
+
31
+ /**
32
+ * Search plugin for the object store.
33
+ *
34
+ * Adds a dedicated search context to the store: a separate collection, pagination,
35
+ * loading state, schema, register, and facet cache — all scoped to the current
36
+ * `searchParams`. Unlike the main type-keyed collections, the search collection is
37
+ * a single slot that is refetched whenever `refetchSearchCollection` is called.
38
+ *
39
+ * The plugin owns two URL-builder helpers (`getRegisterApiUrl`, `getSchemaApiUrl`)
40
+ * that are also exported at module level for use outside the store.
41
+ *
42
+ * State added:
43
+ * - `searchParams` — Query parameters for the current search (register, schema, filters, …)
44
+ * - `searchVisibleColumns` — Column keys visible in the search results table
45
+ *
46
+ * Getters added:
47
+ * - `searchCollection` — Array of search result objects
48
+ * - `searchPagination` — `{ total, page, pages, limit }` for the last fetch
49
+ * - `searchLoading` — `true` while a fetch is in progress
50
+ * - `searchSchema` — Schema object for the current search register/schema pair
51
+ * - `searchRegister` — Register object for the current search register/schema pair
52
+ * - `searchFacets` — Facet data in CnIndexSidebar-compatible format
53
+ *
54
+ * Actions added:
55
+ * - `setSearchParams(params)` — Update search params (clears stale schema/register cache)
56
+ * - `setSearchVisibleColumns(columns)` — Replace the visible columns list
57
+ * - `clearSearchCollection()` — Reset collection, pagination, and facets
58
+ * - `refetchSearchCollection()` — Fetch collection using current `searchParams`
59
+ *
60
+ * @example
61
+ * import { createObjectStore, searchPlugin, SEARCH_TYPE } from '@conduction/nextcloud-vue'
62
+ *
63
+ * const useMyStore = createObjectStore('myapp', {
64
+ * plugins: [searchPlugin()],
65
+ * })
66
+ *
67
+ * const store = useMyStore()
68
+ *
69
+ * // Set params and fetch
70
+ * store.setSearchParams({ register: 'reg-1', schema: 'schema-1', _search: 'foo' })
71
+ * await store.refetchSearchCollection()
72
+ *
73
+ * // Read results
74
+ * console.log(store.searchCollection) // [{ id: '…', … }, …]
75
+ * console.log(store.searchPagination) // { total: 42, page: 1, pages: 3, limit: 20 }
76
+ * console.log(store.searchSchema) // { title: '…', properties: { … } }
77
+ *
78
+ * // Clear
79
+ * store.clearSearchCollection()
80
+ *
81
+ * @return {object} Plugin definition
82
+ */
83
+ export function searchPlugin() {
84
+ return {
85
+ name: 'search',
86
+
87
+ state: () => ({
88
+ /**
89
+ * Query parameters for the active search.
90
+ * Must include `register` and `schema` for `refetchSearchCollection` to work.
91
+ * All other keys are forwarded as query-string parameters (e.g. `_search`, `_page`).
92
+ * @type {object}
93
+ */
94
+ searchParams: {},
95
+
96
+ /**
97
+ * Column keys that are visible in the search results table.
98
+ * @type {string[]}
99
+ */
100
+ searchVisibleColumns: [],
101
+
102
+ /** @private @type {Array} */
103
+ _searchCollection: [],
104
+
105
+ /** @private @type {{ total: number, page: number, pages: number, limit: number }} */
106
+ _searchPagination: { total: 0, page: 1, pages: 1, limit: 20 },
107
+
108
+ /** @private @type {boolean} */
109
+ _searchLoading: false,
110
+
111
+ /** @private @type {object|null} */
112
+ _searchSchema: null,
113
+
114
+ /** @private @type {object|null} */
115
+ _searchRegister: null,
116
+
117
+ /** @private @type {object} */
118
+ _searchFacets: {},
119
+
120
+ /** @private @type {number} Request sequence counter to prevent race conditions */
121
+ _searchRequestId: 0,
122
+ }),
123
+
124
+ getters: {
125
+ /**
126
+ * The current search result objects.
127
+ * @param {object} state
128
+ * @return {Array}
129
+ */
130
+ searchCollection: (state) => state._searchCollection,
131
+
132
+ /**
133
+ * Pagination state for the last search fetch.
134
+ * @param {object} state
135
+ * @return {{ total: number, page: number, pages: number, limit: number }}
136
+ */
137
+ searchPagination: (state) => state._searchPagination,
138
+
139
+ /**
140
+ * True while a search fetch is in progress.
141
+ * @param {object} state
142
+ * @return {boolean}
143
+ */
144
+ searchLoading: (state) => state._searchLoading,
145
+
146
+ /**
147
+ * The schema object for the current search register/schema pair.
148
+ * Populated automatically by `refetchSearchCollection`.
149
+ * @param {object} state
150
+ * @return {object|null}
151
+ */
152
+ searchSchema: (state) => state._searchSchema,
153
+
154
+ /**
155
+ * The register object for the current search register/schema pair.
156
+ * Populated automatically by `refetchSearchCollection`.
157
+ * @param {object} state
158
+ * @return {object|null}
159
+ */
160
+ searchRegister: (state) => state._searchRegister,
161
+
162
+ /**
163
+ * Facet data from the last search fetch, in CnIndexSidebar-compatible format:
164
+ * `{ fieldName: { values: [{ value, count }] } }`.
165
+ * @param {object} state
166
+ * @return {object}
167
+ */
168
+ searchFacets: (state) => state._searchFacets,
169
+ },
170
+
171
+ actions: {
172
+ /**
173
+ * Update the search parameters.
174
+ * If `register` or `schema` changes, the cached register/schema objects are
175
+ * cleared so the next `refetchSearchCollection` re-fetches them.
176
+ *
177
+ * @param {object} params New search params. Must include `register` and `schema`.
178
+ */
179
+ setSearchParams(params) {
180
+ if (params.register !== this.searchParams.register) {
181
+ this._searchRegister = null
182
+ }
183
+ if (params.schema !== this.searchParams.schema) {
184
+ this._searchSchema = null
185
+ }
186
+ this.searchParams = { ...params }
187
+ },
188
+
189
+ /**
190
+ * Merge the given properties into the current search parameters.
191
+ * Only the provided keys are overwritten; all other keys remain unchanged.
192
+ * If `register` or `schema` changes, the cached register/schema objects are
193
+ * cleared so the next `refetchSearchCollection` re-fetches them.
194
+ *
195
+ * @param {object} params Partial search params to merge
196
+ */
197
+ updateSearchParams(params) {
198
+ if ('register' in params && params.register !== this.searchParams.register) {
199
+ this._searchRegister = null
200
+ }
201
+ if ('schema' in params && params.schema !== this.searchParams.schema) {
202
+ this._searchSchema = null
203
+ }
204
+ this.searchParams = { ...this.searchParams, ...params }
205
+ },
206
+
207
+ /**
208
+ * Replace the list of visible column keys for the search results table.
209
+ *
210
+ * @param {string[]} columns Column key array
211
+ */
212
+ setSearchVisibleColumns(columns) {
213
+ this.searchVisibleColumns = Array.isArray(columns) ? columns : []
214
+ },
215
+
216
+ /**
217
+ * Clear the search collection, pagination, and facets.
218
+ * Does not reset `searchParams` or `searchVisibleColumns`.
219
+ */
220
+ clearSearchCollection() {
221
+ this._searchCollection = []
222
+ this._searchPagination = { total: 0, page: 1, pages: 1, limit: 20 }
223
+ this._searchFacets = {}
224
+ },
225
+
226
+ /**
227
+ * Fetch the search collection using the current `searchParams`.
228
+ * `searchParams` must include `register` and `schema`; all other keys are
229
+ * forwarded as query-string parameters to the objects endpoint.
230
+ *
231
+ * Side-effects:
232
+ * - Fetches and caches `_searchSchema` and `_searchRegister` (non-blocking,
233
+ * only when not already cached).
234
+ * - Updates `_searchCollection`, `_searchPagination`, and `_searchFacets`.
235
+ *
236
+ * @return {Promise<Array>} The fetched collection (empty array on error)
237
+ */
238
+ async refetchSearchCollection() {
239
+ const { register, schema, ...queryParams } = this.searchParams
240
+
241
+ if (!register || !schema) {
242
+ console.warn('[searchPlugin] refetchSearchCollection called without register/schema in searchParams')
243
+ return []
244
+ }
245
+
246
+ // Increment request counter to detect stale responses
247
+ const requestId = ++this._searchRequestId
248
+
249
+ this._searchLoading = true
250
+
251
+ // Auto-register the type so saveObject/deleteObject work
252
+ const type = this.createObjectTypeSlug(register, schema)
253
+ if (type && !this.objectTypes.includes(type)) {
254
+ this.registerObjectType(type, schema, register)
255
+ }
256
+
257
+ // Kick off schema/register fetches in parallel (non-blocking)
258
+ if (!this._searchSchema) {
259
+ this._fetchSearchSchema(schema)
260
+ }
261
+ if (!this._searchRegister) {
262
+ this._fetchSearchRegister(register)
263
+ }
264
+
265
+ try {
266
+ const baseUrl = this._options?.baseUrl || '/apps/openregister/api/objects'
267
+ const url = `${baseUrl}/${register}/${schema}` + buildQueryString(queryParams)
268
+
269
+ const response = await fetch(url, {
270
+ method: 'GET',
271
+ headers: buildHeaders(),
272
+ })
273
+
274
+ // A newer request was fired while this one was in-flight — discard
275
+ if (requestId !== this._searchRequestId) {
276
+ return []
277
+ }
278
+
279
+ if (!response.ok) {
280
+ console.error('[searchPlugin] Failed to fetch search collection:', response.status)
281
+ return []
282
+ }
283
+
284
+ const data = await response.json()
285
+
286
+ // Re-check after JSON parsing (also async)
287
+ if (requestId !== this._searchRequestId) {
288
+ return []
289
+ }
290
+
291
+ const results = data.results || data
292
+
293
+ this._searchCollection = results
294
+ this._searchPagination = {
295
+ total: data.total || results.length,
296
+ page: data.page || 1,
297
+ pages: data.pages || 1,
298
+ limit: queryParams._limit || 20,
299
+ }
300
+
301
+ if (data.facets) {
302
+ const transformed = {}
303
+ for (const [key, facet] of Object.entries(data.facets)) {
304
+ if (facet.buckets || facet.data?.buckets) {
305
+ const buckets = facet.buckets || facet.data.buckets
306
+ transformed[key] = {
307
+ values: buckets.map((b) => ({
308
+ value: b.key ?? b.value,
309
+ count: b.count || 0,
310
+ })),
311
+ }
312
+ }
313
+ }
314
+ this._searchFacets = transformed
315
+ }
316
+
317
+ return results
318
+ } catch (error) {
319
+ console.error('[searchPlugin] Error fetching search collection:', error)
320
+ return []
321
+ } finally {
322
+ // Only clear loading if this is still the latest request
323
+ if (requestId === this._searchRequestId) {
324
+ this._searchLoading = false
325
+ }
326
+ }
327
+ },
328
+
329
+ /**
330
+ * Fetch and cache the schema object for the given schema ID.
331
+ * Internal — called by `refetchSearchCollection`.
332
+ *
333
+ * @param {string} schemaId OpenRegister schema ID
334
+ * @return {Promise<void>}
335
+ */
336
+ async _fetchSearchSchema(schemaId) {
337
+ try {
338
+ const response = await fetch(getSchemaApiUrl(schemaId), {
339
+ method: 'GET',
340
+ headers: buildHeaders(),
341
+ })
342
+ if (response.ok) {
343
+ this._searchSchema = await response.json()
344
+ }
345
+ } catch {
346
+ // Non-critical — searchSchema stays null
347
+ }
348
+ },
349
+
350
+ /**
351
+ * Fetch and cache the register object for the given register ID.
352
+ * Internal — called by `refetchSearchCollection`.
353
+ *
354
+ * @param {string} registerId OpenRegister register ID
355
+ * @return {Promise<void>}
356
+ */
357
+ async _fetchSearchRegister(registerId) {
358
+ try {
359
+ const response = await fetch(getRegisterApiUrl(registerId), {
360
+ method: 'GET',
361
+ headers: buildHeaders(),
362
+ })
363
+ if (response.ok) {
364
+ this._searchRegister = await response.json()
365
+ }
366
+ } catch {
367
+ // Non-critical — searchRegister stays null
368
+ }
369
+ },
370
+ },
371
+ }
372
+ }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Selection plugin for the object store.
3
+ *
4
+ * Adds selection management across all object types: select individual
5
+ * objects by ID, check whether an entire collection is selected, and
6
+ * toggle all-selected in one call.
7
+ *
8
+ * State added:
9
+ * - `selectedObjects` — Array of selected object IDs (strings)
10
+ *
11
+ * Getters added:
12
+ * - `isAllSelected(type)` — true when every object in the collection is selected
13
+ *
14
+ * Actions added:
15
+ * - `setSelectedObjects(ids)` — replace selection with an array of IDs
16
+ * - `clearSelectedObjects()` — deselect everything
17
+ * - `toggleSelectAllObjects(type)` — toggle between all-selected and none-selected
18
+ *
19
+ * @example
20
+ * import { createObjectStore, selectionPlugin } from '@conduction/nextcloud-vue'
21
+ *
22
+ * const useMyStore = createObjectStore('myapp', {
23
+ * plugins: [selectionPlugin()],
24
+ * })
25
+ *
26
+ * const store = useMyStore()
27
+ * store.setSelectedObjects(['abc', 'def'])
28
+ * store.toggleSelectAllObjects('invoice')
29
+ * console.log(store.isAllSelected('invoice')) // true | false
30
+ *
31
+ * @return {object} Plugin definition
32
+ */
33
+ export function selectionPlugin() {
34
+ return {
35
+ name: 'selection',
36
+
37
+ state: () => ({
38
+ /**
39
+ * IDs of currently selected objects.
40
+ * @type {string[]}
41
+ */
42
+ selectedObjects: [],
43
+ }),
44
+
45
+ getters: {
46
+ /**
47
+ * Check if all objects in a type's collection are selected.
48
+ * Returns false when the collection is empty.
49
+ *
50
+ * @param {object} state pinia injected state
51
+ *
52
+ * @return {Function} (type: string) => boolean
53
+ */
54
+ isAllSelected: (state) => (type) => {
55
+ const collection = state.collections?.[type] || []
56
+ if (!collection.length) return false
57
+ return collection.every((r) => {
58
+ const id = r.id ?? r['@self']?.id
59
+ return id != null && state.selectedObjects.includes(id)
60
+ })
61
+ },
62
+ },
63
+
64
+ actions: {
65
+ /**
66
+ * Replace the selection with the given array of IDs.
67
+ *
68
+ * @param {string[]} ids Object IDs to select
69
+ */
70
+ setSelectedObjects(ids) {
71
+ this.selectedObjects = Array.isArray(ids) ? ids : []
72
+ },
73
+
74
+ /**
75
+ * Clear all selected objects.
76
+ */
77
+ clearSelectedObjects() {
78
+ this.selectedObjects = []
79
+ },
80
+
81
+ /**
82
+ * Toggle selection of all objects in a type's collection.
83
+ * If all objects are already selected the selection is cleared;
84
+ * otherwise every object in the collection is selected.
85
+ *
86
+ * @param {string} type The registered type slug
87
+ */
88
+ toggleSelectAllObjects(type) {
89
+ const collection = this.getCollection(type)
90
+ const ids = collection.map((r) => r.id ?? r['@self']?.id).filter(Boolean)
91
+ if (this.isAllSelected(type)) {
92
+ // Remove only this type's IDs, keep other types' selections
93
+ const idsSet = new Set(ids)
94
+ this.selectedObjects = this.selectedObjects.filter((id) => !idsSet.has(id))
95
+ } else {
96
+ // Add this type's IDs to existing selection (deduplicated)
97
+ const existing = new Set(this.selectedObjects)
98
+ for (const id of ids) existing.add(id)
99
+ this.selectedObjects = [...existing]
100
+ }
101
+ },
102
+ },
103
+ }
104
+ }