@conduction/nextcloud-vue 2.56.0-beta.36 → 2.56.0-beta.37
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.
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js +131 -3
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +54 -18
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue.js +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +232 -4
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +95 -51
- package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +45 -0
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +22 -12
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +23 -4
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +3 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +18 -5
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +548 -77
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +191 -82
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/useSelfFetchList.js +11 -2
- package/dist/esm/components/CnIndexPage/useSelfFetchList.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +70 -2
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue.js +1 -1
- package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue2.js +67 -5
- package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue2.js.map +1 -1
- package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue3.js +181 -124
- package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue3.js.map +1 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue.js +1 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js +5 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js.map +1 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue3.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue.js +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +134 -10
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +6 -2
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +6 -3
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue.js +1 -1
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js +140 -7
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue.js +1 -1
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js +37 -9
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js.map +1 -1
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js +6 -5
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetCardGrid/CnWidgetCardGrid.vue2.js +53 -1
- package/dist/esm/components/CnWidgetCardGrid/CnWidgetCardGrid.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetCardGrid/CnWidgetCardGrid.vue3.js +3 -2
- package/dist/esm/components/CnWidgetCardGrid/CnWidgetCardGrid.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue.js +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +135 -0
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue.js +1 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js +19 -0
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js +37 -19
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js.map +1 -1
- package/dist/esm/composables/useListView.js +21 -0
- package/dist/esm/composables/useListView.js.map +1 -1
- package/dist/esm/l10n/en.json.js +23 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +41 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/library-style.js +1 -1
- package/dist/esm/nextcloud-vue.css +622 -332
- package/dist/esm/store/useObjectStore.js +16 -2
- package/dist/esm/store/useObjectStore.js.map +1 -1
- package/dist/esm/utils/boardColumns.js +8 -1
- package/dist/esm/utils/boardColumns.js.map +1 -1
- package/dist/esm/utils/boardReferenceColumns.js +278 -0
- package/dist/esm/utils/boardReferenceColumns.js.map +1 -0
- package/dist/esm/utils/buildManifest.js +4 -1
- package/dist/esm/utils/buildManifest.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/esm/utils/widgetDispatch.js +8 -2
- package/dist/esm/utils/widgetDispatch.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +27964 -25769
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +622 -332
- package/l10n/en.json +23 -1
- package/l10n/nl.json +41 -1
- package/package.json +1 -1
- package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +160 -8
- package/src/components/CnBoardView/CnBoardView.vue +262 -6
- package/src/components/CnDashboardPage/CnDashboardPage.vue +57 -2
- package/src/components/CnDetailPage/CnDetailPage.md +1 -1
- package/src/components/CnDetailPage/CnDetailPage.vue +23 -4
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +3 -1
- package/src/components/CnFactsWidget/CnFactsWidget.vue +18 -5
- package/src/components/CnIndexPage/CnIndexPage.vue +586 -25
- package/src/components/CnIndexPage/useSelfFetchList.js +10 -1
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +70 -2
- package/src/components/CnRegisterMapping/CnRegisterMapping.md +5 -0
- package/src/components/CnRegisterMapping/CnRegisterMapping.vue +231 -96
- package/src/components/CnSettingsPage/CnSettingsPage.vue +5 -1
- package/src/components/CnStatWidget/CnStatWidget.vue +136 -11
- package/src/components/CnTabsWidget/CnTabsWidget.vue +6 -3
- package/src/components/CnToggleTableWidget/CnToggleTableWidget.vue +140 -7
- package/src/components/CnVersionInfoCard/CnVersionInfoCard.md +2 -1
- package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +42 -13
- package/src/components/CnWidgetCardGrid/CnWidgetCardGrid.vue +55 -2
- package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +135 -0
- package/src/components/CnWidgetWrapper/CnWidgetWrapper.md +10 -0
- package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +44 -1
- package/src/composables/useListView.js +21 -0
- package/src/css/board.css +65 -4
- package/src/css/dialog.css +4 -2
- package/src/css/index-page.css +49 -0
- package/src/css/look-board.css +77 -7
- package/src/schemas/app-manifest-v2.schema.json +115 -5
- package/src/store/useObjectStore.js +16 -2
- package/src/utils/boardColumns.js +8 -1
- package/src/utils/boardReferenceColumns.js +274 -0
- package/src/utils/buildManifest.js +4 -1
- package/src/utils/validateManifestV2.compiled.js +3 -3
- package/src/utils/widgetDispatch.js +8 -2
|
@@ -26,6 +26,15 @@ function resolveInitialQuickFilterIndex(quickFilters) {
|
|
|
26
26
|
* @param {Array<object>} filterMaps Already route-resolved filter maps.
|
|
27
27
|
* @return {object} The merged filter (scalar when a key has one value, array when many).
|
|
28
28
|
*/
|
|
29
|
+
/**
|
|
30
|
+
* Rows one board fetch asks for. A board shows every matching row at once,
|
|
31
|
+
* so it loads far more than a table page (OpenRegister serves up to 1000);
|
|
32
|
+
* past this the board offers "Load more".
|
|
33
|
+
*
|
|
34
|
+
* @type {number}
|
|
35
|
+
*/
|
|
36
|
+
export const BOARD_PAGE_SIZE = 500
|
|
37
|
+
|
|
29
38
|
/**
|
|
30
39
|
* The sort a quick filter sets while it is active (`sort: { key, order }`, or a
|
|
31
40
|
* list of those for a multi-column order), as sort keys, or null when it sets
|
|
@@ -236,7 +245,7 @@ export function useSelfFetchList(props, instance, inject, extras = {}) {
|
|
|
236
245
|
defaultSortKeys: initialSortKeys,
|
|
237
246
|
defaultActiveFilters: initialActiveFilters,
|
|
238
247
|
defaultSearchTerm: initialSearchTerm,
|
|
239
|
-
defaultPageSize: (props.pagination && props.pagination.limit) || undefined,
|
|
248
|
+
defaultPageSize: props.viewMode === 'board' ? BOARD_PAGE_SIZE : ((props.pagination && props.pagination.limit) || undefined),
|
|
240
249
|
defaultVisibleColumns: configuredColumnKeys.length ? configuredColumnKeys : null,
|
|
241
250
|
// `pages[].config.extend` → OpenRegister's repeated `_extend[]`. A
|
|
242
251
|
// getter so a reactive change re-scopes the next fetch, matching how
|
|
@@ -141,9 +141,11 @@
|
|
|
141
141
|
import { subscribe, unsubscribe } from '@nextcloud/event-bus'
|
|
142
142
|
import { translate as t } from '@nextcloud/l10n'
|
|
143
143
|
import { NcLoadingIcon } from '@nextcloud/vue'
|
|
144
|
+
import { inject, ref } from 'vue'
|
|
144
145
|
import ChevronRight from 'vue-material-design-icons/ChevronRight.vue'
|
|
145
146
|
import CnIcon from '../CnIcon/CnIcon.vue'
|
|
146
147
|
import CnStatusBadge from '../CnStatusBadge/CnStatusBadge.vue'
|
|
148
|
+
import { useEndpointSource } from '../../composables/useEndpointSource.js'
|
|
147
149
|
import { actionLink, dispatchAction, interpolateActionTarget } from '../../utils/actionsDispatcher.js'
|
|
148
150
|
import { isBadgeVariant } from '../../utils/badgeVariants.js'
|
|
149
151
|
import { ageInDays } from '../../utils/boardDate.js'
|
|
@@ -180,7 +182,8 @@ function unwrap(value) {
|
|
|
180
182
|
* objects filtered on a reference to this record. The source keys are those of
|
|
181
183
|
* `object-list` (`register`, `schema`, `filter` with the `@objectId` /
|
|
182
184
|
* `@object.<field>` / `@workspace.<key>` tokens, `sort`, `limit`, `extend`,
|
|
183
|
-
* `rowRoute`, `pageControls`)
|
|
185
|
+
* `rowRoute`, `pageControls`), or `endpointSource` (an app endpoint whose
|
|
186
|
+
* payload, after `responsePath`, is the array of rows, as on the object table); `avatar` puts a circle with the initials of a name
|
|
184
187
|
* template before the title; what differs is the drawing: `presentation` is `rows`, `feed`
|
|
185
188
|
* or `inline`, and each line is a template (`"{identifier} · {caseTypeName}"`,
|
|
186
189
|
* `"Termijn {deadline|date}"`).
|
|
@@ -213,7 +216,9 @@ export default {
|
|
|
213
216
|
* The list config: `{ register, schema, filter?, sort?, limit?, extend?,
|
|
214
217
|
* presentation?, title?, meta?, text?, label?, pill?, trailing?, icon?,
|
|
215
218
|
* rowRoute?, rowIdField?, headerLink?, headerNote?, emptyText?, errorText?,
|
|
216
|
-
* valueLabels? }`. `
|
|
219
|
+
* valueLabels?, endpointSource? }`. `endpointSource` (`{ url, method?,
|
|
220
|
+
* params?, responsePath? }`) reads the rows from an app endpoint instead
|
|
221
|
+
* of `register` / `schema`. `valueLabels` maps a field's stored values to the labels
|
|
217
222
|
* the templates show.
|
|
218
223
|
*
|
|
219
224
|
* @type {object}
|
|
@@ -238,6 +243,28 @@ export default {
|
|
|
238
243
|
|
|
239
244
|
emits: ['header'],
|
|
240
245
|
|
|
246
|
+
setup(props) {
|
|
247
|
+
const objectCtxRaw = inject('cnObjectContext', null)
|
|
248
|
+
const workspaceRaw = inject('cnWorkspaceContext', null)
|
|
249
|
+
const appConfigRaw = inject('cnAppConfig', ref({}))
|
|
250
|
+
const { data, loading, error } = useEndpointSource(
|
|
251
|
+
() => props.content?.endpointSource || null,
|
|
252
|
+
{
|
|
253
|
+
ctx: () => {
|
|
254
|
+
const injected = unwrap(objectCtxRaw) || {}
|
|
255
|
+
return {
|
|
256
|
+
...injected,
|
|
257
|
+
objectId: props.objectId ?? injected.objectId ?? props.objectData?.id ?? null,
|
|
258
|
+
object: props.objectData || injected.object || null,
|
|
259
|
+
workspace: unwrap(workspaceRaw) || {},
|
|
260
|
+
config: unwrapAppConfig(appConfigRaw) || {},
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
)
|
|
265
|
+
return { epData: data, epLoading: loading, epError: error }
|
|
266
|
+
},
|
|
267
|
+
|
|
241
268
|
data() {
|
|
242
269
|
return {
|
|
243
270
|
rows: [],
|
|
@@ -294,6 +321,22 @@ export default {
|
|
|
294
321
|
return { config: unwrapAppConfig(this.cnAppConfig) }
|
|
295
322
|
},
|
|
296
323
|
|
|
324
|
+
/**
|
|
325
|
+
* Whether the rows come from `endpointSource` rather than OpenRegister.
|
|
326
|
+
*
|
|
327
|
+
* @return {boolean}
|
|
328
|
+
* @spec openspec/changes/endpoint-source-card-grid-record-list/specs/dashboard-page/spec.md#requirement-a-record-list-and-a-card-grid-can-read-an-endpoint
|
|
329
|
+
*/
|
|
330
|
+
endpointActive() {
|
|
331
|
+
const source = this.content?.endpointSource
|
|
332
|
+
return Boolean(source && typeof source === 'object' && source.url)
|
|
333
|
+
},
|
|
334
|
+
|
|
335
|
+
/** @return {string} The endpoint read's state, to copy into the list when it changes. */
|
|
336
|
+
endpointKey() {
|
|
337
|
+
return this.endpointActive ? JSON.stringify([this.epData, this.epLoading, this.epError]) : ''
|
|
338
|
+
},
|
|
339
|
+
|
|
297
340
|
/** @return {number} The fetch cap. */
|
|
298
341
|
limit() {
|
|
299
342
|
const value = Number(this.content?.limit)
|
|
@@ -398,6 +441,10 @@ export default {
|
|
|
398
441
|
requestKey() {
|
|
399
442
|
this.fetchRows()
|
|
400
443
|
},
|
|
444
|
+
|
|
445
|
+
endpointKey() {
|
|
446
|
+
this.fetchRows()
|
|
447
|
+
},
|
|
401
448
|
},
|
|
402
449
|
|
|
403
450
|
mounted() {
|
|
@@ -854,6 +901,23 @@ export default {
|
|
|
854
901
|
return { rows, total: typeof head.total === 'number' ? head.total : rows.length }
|
|
855
902
|
},
|
|
856
903
|
|
|
904
|
+
/**
|
|
905
|
+
* Copy the endpoint read into the list: its payload must be an array of
|
|
906
|
+
* rows (anything else is an empty list), and a failed read is an error.
|
|
907
|
+
*
|
|
908
|
+
* @spec openspec/changes/endpoint-source-card-grid-record-list/specs/dashboard-page/spec.md#requirement-a-record-list-and-a-card-grid-can-read-an-endpoint
|
|
909
|
+
*/
|
|
910
|
+
takeEndpointRows() {
|
|
911
|
+
this.rows = Array.isArray(this.epData) ? this.epData : []
|
|
912
|
+
this.total = this.rows.length
|
|
913
|
+
this.loading = this.epLoading === true
|
|
914
|
+
this.error = Boolean(this.epError)
|
|
915
|
+
this.lenses = {}
|
|
916
|
+
if (!this.loading) {
|
|
917
|
+
this.emitHeader()
|
|
918
|
+
}
|
|
919
|
+
},
|
|
920
|
+
|
|
857
921
|
/**
|
|
858
922
|
* Fetch the list. Sends nothing while a required token is unresolved
|
|
859
923
|
* or the source is incomplete.
|
|
@@ -862,6 +926,10 @@ export default {
|
|
|
862
926
|
*/
|
|
863
927
|
async fetchRows() {
|
|
864
928
|
const c = this.content || {}
|
|
929
|
+
if (this.endpointActive) {
|
|
930
|
+
this.takeEndpointRows()
|
|
931
|
+
return
|
|
932
|
+
}
|
|
865
933
|
if (!c.register || !c.schema || this.waiting) {
|
|
866
934
|
this.rows = []
|
|
867
935
|
this.total = 0
|
|
@@ -111,6 +111,10 @@ export default {
|
|
|
111
111
|
</script>
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
+
## Several groups
|
|
115
|
+
|
|
116
|
+
With more than one group each group starts closed: its heading is a button that shows the group name and how many of its types are configured ("2/8 configured"), and opens the register select and type list on a click or Enter. A page with several registers is a short card until the administrator opens the group they need.
|
|
117
|
+
|
|
114
118
|
## Additional props
|
|
115
119
|
|
|
116
120
|
| Prop | Type | Default | Description |
|
|
@@ -122,5 +126,6 @@ export default {
|
|
|
122
126
|
| `reimporting` | Boolean | `false` | Disables the reimport button and shows a spinner while reimport is in progress |
|
|
123
127
|
| `saveButtonText` | String | `'Save configuration'` | Label for the save button |
|
|
124
128
|
| `reimportButtonText` | String | `'Re-import configuration'` | Label for the reimport button |
|
|
129
|
+
| `collapseGroups` | Boolean | `null` | Draw each group as a heading button that opens and closes it, with its configured count shown while closed; the groups start closed. `null` does this when there is more than one group, `true` or `false` forces it |
|
|
125
130
|
| `autoMatch` | Boolean | `true` | Automatically match schema titles to type slugs when a register is selected |
|
|
126
131
|
| `labels` | Object | *(see below)* | UI label overrides for all user-visible strings (register, schema, notConfigured, noSchemas, selectRegister, selectSchema, allConfigured, partiallyConfigured) |
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
:description="description"
|
|
5
5
|
:docUrl="docUrl"
|
|
6
6
|
:loading="registersLoading"
|
|
7
|
-
loadingMessage="Loading registers
|
|
7
|
+
:loadingMessage="t('nextcloud-vue', 'Loading registers…')"
|
|
8
8
|
:error="!!registersError"
|
|
9
9
|
:errorMessage="registersError || ''"
|
|
10
10
|
:onRetry="loadRegisters">
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
<NcLoadingIcon v-if="saving" :size="20" />
|
|
20
20
|
<ContentSave v-else :size="20" />
|
|
21
21
|
</template>
|
|
22
|
-
{{ saving ? 'Saving
|
|
22
|
+
{{ saving ? t('nextcloud-vue', 'Saving…') : saveButtonText }}
|
|
23
23
|
</NcButton>
|
|
24
24
|
<NcButton
|
|
25
25
|
v-if="showReimportButton"
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
<NcLoadingIcon v-if="reimporting" :size="20" />
|
|
31
31
|
<Refresh v-else :size="20" />
|
|
32
32
|
</template>
|
|
33
|
-
{{ reimporting ? 'Importing
|
|
33
|
+
{{ reimporting ? t('nextcloud-vue', 'Importing…') : reimportButtonText }}
|
|
34
34
|
</NcButton>
|
|
35
35
|
<slot name="actions" />
|
|
36
36
|
</template>
|
|
@@ -40,9 +40,39 @@
|
|
|
40
40
|
<div
|
|
41
41
|
v-for="(group, groupIdx) in groups"
|
|
42
42
|
:key="groupIdx"
|
|
43
|
-
class="cn-register-mapping__group"
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
class="cn-register-mapping__group"
|
|
44
|
+
:class="{ 'cn-register-mapping__group--collapsed': !isGroupOpen(groupIdx) }"
|
|
45
|
+
data-testid="cn-register-mapping-group">
|
|
46
|
+
<!-- Group header. With collapsible groups a heading holds the button
|
|
47
|
+
that opens and closes the group (the WAI-ARIA accordion); the
|
|
48
|
+
count stays visible while it is closed. -->
|
|
49
|
+
<h4 v-if="groupsCollapsible" class="cn-register-mapping__group-heading">
|
|
50
|
+
<button
|
|
51
|
+
type="button"
|
|
52
|
+
class="cn-register-mapping__group-toggle"
|
|
53
|
+
:aria-expanded="String(isGroupOpen(groupIdx))"
|
|
54
|
+
:aria-controls="groupBodyId(groupIdx)"
|
|
55
|
+
data-testid="cn-register-mapping-group-toggle"
|
|
56
|
+
@click="toggleGroup(groupIdx)">
|
|
57
|
+
<span class="cn-register-mapping__group-chevron" aria-hidden="true">
|
|
58
|
+
<ChevronUp v-if="isGroupOpen(groupIdx)" :size="20" />
|
|
59
|
+
<ChevronDown v-else :size="20" />
|
|
60
|
+
</span>
|
|
61
|
+
<slot name="group-header"
|
|
62
|
+
:group="group"
|
|
63
|
+
:configuredCount="configuredCount(groupIdx)"
|
|
64
|
+
:totalCount="group.types.length">
|
|
65
|
+
<span class="cn-register-mapping__group-header">
|
|
66
|
+
<span class="cn-register-mapping__group-title">{{ group.name }}</span>
|
|
67
|
+
<span class="cn-register-mapping__group-status" data-testid="cn-register-mapping-group-count">
|
|
68
|
+
{{ configuredCount(groupIdx) }}/{{ group.types.length }} {{ labels.partiallyConfigured }}
|
|
69
|
+
</span>
|
|
70
|
+
</span>
|
|
71
|
+
</slot>
|
|
72
|
+
</button>
|
|
73
|
+
</h4>
|
|
74
|
+
<slot v-else
|
|
75
|
+
name="group-header"
|
|
46
76
|
:group="group"
|
|
47
77
|
:configuredCount="configuredCount(groupIdx)"
|
|
48
78
|
:totalCount="group.types.length">
|
|
@@ -56,90 +86,95 @@
|
|
|
56
86
|
</div>
|
|
57
87
|
</slot>
|
|
58
88
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
<!-- Header row -->
|
|
81
|
-
<div class="cn-register-mapping__type-list-header">
|
|
82
|
-
<span>Name</span>
|
|
83
|
-
<span>Schema</span>
|
|
84
|
-
<span />
|
|
85
|
-
<span />
|
|
89
|
+
<div
|
|
90
|
+
v-show="isGroupOpen(groupIdx)"
|
|
91
|
+
:id="groupBodyId(groupIdx)"
|
|
92
|
+
class="cn-register-mapping__group-body"
|
|
93
|
+
data-testid="cn-register-mapping-group-body">
|
|
94
|
+
<!-- Group description -->
|
|
95
|
+
<p v-if="group.description" class="cn-register-mapping__group-description">
|
|
96
|
+
{{ group.description }}
|
|
97
|
+
</p>
|
|
98
|
+
|
|
99
|
+
<!-- Register selector -->
|
|
100
|
+
<div class="cn-register-mapping__register-select">
|
|
101
|
+
<NcSelect
|
|
102
|
+
:modelValue="selectedRegister(groupIdx)"
|
|
103
|
+
:options="registerSelectOptions"
|
|
104
|
+
:placeholder="labels.selectRegister"
|
|
105
|
+
:inputLabel="labels.register"
|
|
106
|
+
:loading="registersLoading"
|
|
107
|
+
label="label"
|
|
108
|
+
trackBy="value"
|
|
109
|
+
@update:modelValue="handleRegisterChange(groupIdx, $event)" />
|
|
86
110
|
</div>
|
|
87
111
|
|
|
88
|
-
<!-- Type
|
|
89
|
-
<
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
<span
|
|
95
|
-
<span
|
|
96
|
-
{{ schemaLabel(groupIdx, type) || labels.notConfigured }}
|
|
97
|
-
</span>
|
|
98
|
-
<span class="cn-register-mapping__type-status">
|
|
99
|
-
<span
|
|
100
|
-
class="cn-register-mapping__status-dot"
|
|
101
|
-
:class="schemaValue(groupIdx, type)
|
|
102
|
-
? 'cn-register-mapping__status-dot--configured'
|
|
103
|
-
: 'cn-register-mapping__status-dot--unconfigured'" />
|
|
104
|
-
</span>
|
|
105
|
-
<span class="cn-register-mapping__type-chevron">
|
|
106
|
-
<ChevronUp v-if="isExpanded(groupIdx, type.slug)" :size="20" />
|
|
107
|
-
<ChevronDown v-else :size="20" />
|
|
108
|
-
</span>
|
|
112
|
+
<!-- Type list -->
|
|
113
|
+
<div v-if="selectedRegister(groupIdx)" class="cn-register-mapping__type-list">
|
|
114
|
+
<!-- Header row -->
|
|
115
|
+
<div class="cn-register-mapping__type-list-header">
|
|
116
|
+
<span>{{ t('nextcloud-vue', 'Object type') }}</span>
|
|
117
|
+
<span>{{ labels.schema }}</span>
|
|
118
|
+
<span />
|
|
119
|
+
<span />
|
|
109
120
|
</div>
|
|
110
121
|
|
|
111
|
-
<!--
|
|
112
|
-
<
|
|
122
|
+
<!-- Type rows -->
|
|
123
|
+
<template v-for="type in group.types" :key="type.slug + '-row'">
|
|
113
124
|
<div
|
|
114
|
-
|
|
115
|
-
class="cn-register-mapping__type-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
125
|
+
class="cn-register-mapping__type-row"
|
|
126
|
+
:class="{ 'cn-register-mapping__type-row--expanded': isExpanded(groupIdx, type.slug) }"
|
|
127
|
+
@click="toggleExpand(groupIdx, type.slug)">
|
|
128
|
+
<span class="cn-register-mapping__type-name">{{ type.label }}</span>
|
|
129
|
+
<span class="cn-register-mapping__type-schema">
|
|
130
|
+
{{ schemaLabel(groupIdx, type) || labels.notConfigured }}
|
|
131
|
+
</span>
|
|
132
|
+
<span class="cn-register-mapping__type-status">
|
|
133
|
+
<span
|
|
134
|
+
class="cn-register-mapping__status-dot"
|
|
135
|
+
:class="schemaValue(groupIdx, type)
|
|
136
|
+
? 'cn-register-mapping__status-dot--configured'
|
|
137
|
+
: 'cn-register-mapping__status-dot--unconfigured'" />
|
|
138
|
+
</span>
|
|
139
|
+
<span class="cn-register-mapping__type-chevron">
|
|
140
|
+
<ChevronUp v-if="isExpanded(groupIdx, type.slug)" :size="20" />
|
|
141
|
+
<ChevronDown v-else :size="20" />
|
|
142
|
+
</span>
|
|
127
143
|
</div>
|
|
128
|
-
</transition>
|
|
129
|
-
</template>
|
|
130
|
-
</div>
|
|
131
144
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
145
|
+
<!-- Expanded detail -->
|
|
146
|
+
<transition name="slide">
|
|
147
|
+
<div
|
|
148
|
+
v-if="isExpanded(groupIdx, type.slug)"
|
|
149
|
+
class="cn-register-mapping__type-detail">
|
|
150
|
+
<p v-if="type.description" class="cn-register-mapping__type-description">
|
|
151
|
+
{{ type.description }}
|
|
152
|
+
</p>
|
|
153
|
+
<NcSelect
|
|
154
|
+
:modelValue="selectedSchema(groupIdx, type)"
|
|
155
|
+
:options="schemaSelectOptions(groupIdx)"
|
|
156
|
+
:placeholder="labels.selectSchema"
|
|
157
|
+
:inputLabel="labels.schema"
|
|
158
|
+
label="label"
|
|
159
|
+
trackBy="value"
|
|
160
|
+
@update:modelValue="handleSchemaChange(groupIdx, type, $event)" />
|
|
161
|
+
</div>
|
|
162
|
+
</transition>
|
|
163
|
+
</template>
|
|
164
|
+
</div>
|
|
165
|
+
|
|
166
|
+
<!-- No register selected -->
|
|
167
|
+
<NcNoteCard v-else-if="!registersLoading" type="info">
|
|
168
|
+
{{ labels.selectRegister }}
|
|
169
|
+
</NcNoteCard>
|
|
170
|
+
|
|
171
|
+
<!-- No schemas available -->
|
|
172
|
+
<NcNoteCard
|
|
173
|
+
v-if="selectedRegister(groupIdx) && schemaSelectOptions(groupIdx).length === 0 && !registersLoading"
|
|
174
|
+
variant="warning">
|
|
175
|
+
{{ labels.noSchemas }}
|
|
176
|
+
</NcNoteCard>
|
|
177
|
+
</div>
|
|
143
178
|
</div>
|
|
144
179
|
</div>
|
|
145
180
|
|
|
@@ -160,6 +195,8 @@ import Refresh from 'vue-material-design-icons/Refresh.vue'
|
|
|
160
195
|
import { buildHeaders, prefixUrl } from '../../utils/headers.js'
|
|
161
196
|
import { CnSettingsSection } from '../CnSettingsSection/index.js'
|
|
162
197
|
|
|
198
|
+
let mappingCount = 0
|
|
199
|
+
|
|
163
200
|
/**
|
|
164
201
|
* CnRegisterMapping - OpenRegister register/schema configuration component.
|
|
165
202
|
*
|
|
@@ -284,6 +321,20 @@ export default {
|
|
|
284
321
|
default: () => t('nextcloud-vue', 'Re-import configuration'),
|
|
285
322
|
},
|
|
286
323
|
|
|
324
|
+
/**
|
|
325
|
+
* Draw each group as a header that opens and closes it, showing its
|
|
326
|
+
* configured count while closed. The groups start closed. `null` (the
|
|
327
|
+
* default) collapses them when there is more than one group, so a page
|
|
328
|
+
* with several registers is not one long card.
|
|
329
|
+
*
|
|
330
|
+
* @type {boolean|null}
|
|
331
|
+
* @spec openspec/changes/register-mapping-collapsed-groups/specs/register-mapping/spec.md#requirement-register-groups-start-closed-with-their-count
|
|
332
|
+
*/
|
|
333
|
+
collapseGroups: {
|
|
334
|
+
type: Boolean,
|
|
335
|
+
default: null,
|
|
336
|
+
},
|
|
337
|
+
|
|
287
338
|
/** Auto-match schema titles to type slugs on register change */
|
|
288
339
|
autoMatch: {
|
|
289
340
|
type: Boolean,
|
|
@@ -319,6 +370,9 @@ export default {
|
|
|
319
370
|
// Local state
|
|
320
371
|
localConfig: {},
|
|
321
372
|
expandedRows: {},
|
|
373
|
+
// The groups opened by the reader, by index.
|
|
374
|
+
openGroups: {},
|
|
375
|
+
uid: ++mappingCount,
|
|
322
376
|
}
|
|
323
377
|
},
|
|
324
378
|
|
|
@@ -326,11 +380,21 @@ export default {
|
|
|
326
380
|
/** Register options for NcSelect */
|
|
327
381
|
registerSelectOptions() {
|
|
328
382
|
return this.registers.map((r) => ({
|
|
329
|
-
label: r.title || r.slug ||
|
|
383
|
+
label: r.title || r.slug || t('nextcloud-vue', 'Register {id}', { id: r.id }),
|
|
330
384
|
value: String(r.id),
|
|
331
385
|
}))
|
|
332
386
|
},
|
|
333
387
|
|
|
388
|
+
/**
|
|
389
|
+
* Whether the groups open and close.
|
|
390
|
+
*
|
|
391
|
+
* @return {boolean}
|
|
392
|
+
* @spec openspec/changes/register-mapping-collapsed-groups/specs/register-mapping/spec.md#requirement-register-groups-start-closed-with-their-count
|
|
393
|
+
*/
|
|
394
|
+
groupsCollapsible() {
|
|
395
|
+
return this.collapseGroups === null ? this.groups.length > 1 : this.collapseGroups
|
|
396
|
+
},
|
|
397
|
+
|
|
334
398
|
/** Whether local config differs from prop */
|
|
335
399
|
hasChanges() {
|
|
336
400
|
return JSON.stringify(this.localConfig) !== JSON.stringify(this.configuration)
|
|
@@ -353,6 +417,39 @@ export default {
|
|
|
353
417
|
},
|
|
354
418
|
|
|
355
419
|
methods: {
|
|
420
|
+
t,
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Whether a group's body shows.
|
|
424
|
+
*
|
|
425
|
+
* @param {number} groupIdx Group index
|
|
426
|
+
* @return {boolean}
|
|
427
|
+
*/
|
|
428
|
+
isGroupOpen(groupIdx) {
|
|
429
|
+
return !this.groupsCollapsible || this.openGroups[groupIdx] === true
|
|
430
|
+
},
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Open a closed group, or close an open one.
|
|
434
|
+
*
|
|
435
|
+
* @param {number} groupIdx Group index
|
|
436
|
+
* @return {void}
|
|
437
|
+
* @spec openspec/changes/register-mapping-collapsed-groups/specs/register-mapping/spec.md#requirement-register-groups-start-closed-with-their-count
|
|
438
|
+
*/
|
|
439
|
+
toggleGroup(groupIdx) {
|
|
440
|
+
this.openGroups = { ...this.openGroups, [groupIdx]: !this.isGroupOpen(groupIdx) }
|
|
441
|
+
},
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* The id of a group's body, for the header's `aria-controls`.
|
|
445
|
+
*
|
|
446
|
+
* @param {number} groupIdx Group index
|
|
447
|
+
* @return {string}
|
|
448
|
+
*/
|
|
449
|
+
groupBodyId(groupIdx) {
|
|
450
|
+
return `cn-register-mapping-${this.uid}-group-${groupIdx}`
|
|
451
|
+
},
|
|
452
|
+
|
|
356
453
|
/**
|
|
357
454
|
* Get the config key for a group's register.
|
|
358
455
|
*
|
|
@@ -437,7 +534,7 @@ export default {
|
|
|
437
534
|
return selected.label
|
|
438
535
|
}
|
|
439
536
|
const value = this.schemaValue(groupIdx, type)
|
|
440
|
-
return value ?
|
|
537
|
+
return value ? t('nextcloud-vue', 'Schema {id}', { id: value }) : ''
|
|
441
538
|
},
|
|
442
539
|
|
|
443
540
|
/**
|
|
@@ -453,7 +550,7 @@ export default {
|
|
|
453
550
|
}
|
|
454
551
|
const schemas = this.schemasByRegister[reg.value] || []
|
|
455
552
|
return schemas.map((s) => ({
|
|
456
|
-
label: s.title || s.slug ||
|
|
553
|
+
label: s.title || s.slug || t('nextcloud-vue', 'Schema {id}', { id: s.id }),
|
|
457
554
|
value: String(s.id),
|
|
458
555
|
}))
|
|
459
556
|
},
|
|
@@ -591,7 +688,7 @@ export default {
|
|
|
591
688
|
})
|
|
592
689
|
|
|
593
690
|
if (!response.ok) {
|
|
594
|
-
this.registersError =
|
|
691
|
+
this.registersError = t('nextcloud-vue', 'The registers could not be loaded: {reason}', { reason: response.statusText || response.status })
|
|
595
692
|
return
|
|
596
693
|
}
|
|
597
694
|
|
|
@@ -612,7 +709,7 @@ export default {
|
|
|
612
709
|
}
|
|
613
710
|
}
|
|
614
711
|
} catch (error) {
|
|
615
|
-
this.registersError = error.message || '
|
|
712
|
+
this.registersError = error.message || t('nextcloud-vue', 'The registers could not be loaded: {reason}', { reason: t('nextcloud-vue', 'no connection') })
|
|
616
713
|
} finally {
|
|
617
714
|
this.registersLoading = false
|
|
618
715
|
}
|
|
@@ -671,6 +768,52 @@ export default {
|
|
|
671
768
|
margin-bottom: 12px;
|
|
672
769
|
}
|
|
673
770
|
|
|
771
|
+
.cn-register-mapping__group-heading {
|
|
772
|
+
margin: 0 0 12px;
|
|
773
|
+
font: inherit;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
.cn-register-mapping__group--collapsed .cn-register-mapping__group-heading {
|
|
777
|
+
margin-bottom: 0;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
.cn-register-mapping__group-toggle {
|
|
781
|
+
box-sizing: border-box;
|
|
782
|
+
display: flex;
|
|
783
|
+
align-items: center;
|
|
784
|
+
gap: 8px;
|
|
785
|
+
width: 100%;
|
|
786
|
+
min-height: 44px;
|
|
787
|
+
margin: 0;
|
|
788
|
+
padding: 0;
|
|
789
|
+
border: 0;
|
|
790
|
+
background: transparent;
|
|
791
|
+
color: inherit;
|
|
792
|
+
font: inherit;
|
|
793
|
+
text-align: start;
|
|
794
|
+
cursor: pointer;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
.cn-register-mapping__group-toggle:focus-visible {
|
|
798
|
+
outline: 2px solid var(--color-primary-element);
|
|
799
|
+
outline-offset: 2px;
|
|
800
|
+
border-radius: var(--border-radius);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
.cn-register-mapping__group-chevron {
|
|
804
|
+
display: inline-flex;
|
|
805
|
+
flex: none;
|
|
806
|
+
color: var(--color-text-maxcontrast);
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
.cn-register-mapping__group-toggle .cn-register-mapping__group-header {
|
|
810
|
+
flex: 1 1 auto;
|
|
811
|
+
flex-wrap: wrap;
|
|
812
|
+
gap: 4px 12px;
|
|
813
|
+
min-width: 0;
|
|
814
|
+
margin-bottom: 0;
|
|
815
|
+
}
|
|
816
|
+
|
|
674
817
|
.cn-register-mapping__group-title {
|
|
675
818
|
font-size: 16px;
|
|
676
819
|
font-weight: 600;
|
|
@@ -689,14 +832,6 @@ export default {
|
|
|
689
832
|
margin: 0 0 12px 0;
|
|
690
833
|
}
|
|
691
834
|
|
|
692
|
-
.cn-register-mapping__label {
|
|
693
|
-
display: block;
|
|
694
|
-
font-weight: 500;
|
|
695
|
-
font-size: 13px;
|
|
696
|
-
color: var(--color-text-maxcontrast);
|
|
697
|
-
margin-bottom: 4px;
|
|
698
|
-
}
|
|
699
|
-
|
|
700
835
|
.cn-register-mapping__register-select {
|
|
701
836
|
margin-bottom: 16px;
|
|
702
837
|
max-width: 400px;
|
|
@@ -303,6 +303,7 @@ import {
|
|
|
303
303
|
import ContentSave from 'vue-material-design-icons/ContentSave.vue'
|
|
304
304
|
import CnBuildiqEditButton from '../CnBuildiqEditButton/CnBuildiqEditButton.vue'
|
|
305
305
|
import CnRegisterMapping from '../CnRegisterMapping/CnRegisterMapping.vue'
|
|
306
|
+
import CnToggleTableWidget from '../CnToggleTableWidget/CnToggleTableWidget.vue'
|
|
306
307
|
import CnVersionInfoCard from '../CnVersionInfoCard/CnVersionInfoCard.vue'
|
|
307
308
|
import { useLook } from '../../composables/useLook.js'
|
|
308
309
|
import { prefixUrl } from '../../utils/headers.js'
|
|
@@ -340,6 +341,8 @@ const COMPONENT_DISCRIMINATOR = Symbol('cn-settings-component-widget')
|
|
|
340
341
|
const BUILTIN_SETTINGS_WIDGETS = Object.freeze({
|
|
341
342
|
'version-info': CnVersionInfoCard,
|
|
342
343
|
'register-mapping': CnRegisterMapping,
|
|
344
|
+
// Rows with a switch: objects (`bind.mode: 'each'`) or a config map (`'map'`).
|
|
345
|
+
'toggle-table': CnToggleTableWidget,
|
|
343
346
|
component: COMPONENT_DISCRIMINATOR,
|
|
344
347
|
})
|
|
345
348
|
|
|
@@ -367,7 +370,8 @@ const BUILTIN_SETTINGS_WIDGETS = Object.freeze({
|
|
|
367
370
|
* - `widgets[]` — ordered list of widgets. Each entry has a `type`
|
|
368
371
|
* string and optional `props`. Built-in widget types resolve
|
|
369
372
|
* first (`version-info` → `CnVersionInfoCard`, `register-mapping`
|
|
370
|
-
* → `CnRegisterMapping`);
|
|
373
|
+
* → `CnRegisterMapping`, `toggle-table` → `CnToggleTableWidget`);
|
|
374
|
+
* unknown types fall back to the
|
|
371
375
|
* `customComponents` registry.
|
|
372
376
|
*
|
|
373
377
|
* Widget events (e.g. CnRegisterMapping's `@save`, CnVersionInfoCard's
|