@conduction/nextcloud-vue 2.83.0 → 2.85.0
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/CnAuditTrailCard/CnAuditTrailCard.vue.js +1 -1
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js +1 -1
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js.map +1 -1
- package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.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/CnBodySections/CnBodySections.vue.js +1 -1
- package/dist/esm/components/CnBodySections/CnBodySections.vue2.js +22 -1
- package/dist/esm/components/CnBodySections/CnBodySections.vue2.js.map +1 -1
- package/dist/esm/components/CnBodySections/CnBodySections.vue3.js.map +1 -1
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue.js +1 -1
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue2.js +47 -4
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue.js +1 -1
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js +239 -3
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js +5 -5
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +50 -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/CnDeltaWidget/CnDeltaWidget.vue.js +1 -1
- package/dist/esm/components/CnDeltaWidget/CnDeltaWidget.vue2.js +1 -1
- package/dist/esm/components/CnDeltaWidget/CnDeltaWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnDeltaWidget/CnDeltaWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +226 -8
- 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/CnFieldListWidget/CnFieldListWidget.vue.js +1 -1
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js +38 -1
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js +45 -5
- package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnGaugeWidget/CnGaugeWidget.vue.js +1 -1
- package/dist/esm/components/CnGaugeWidget/CnGaugeWidget.vue2.js +1 -1
- package/dist/esm/components/CnGaugeWidget/CnGaugeWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnGaugeWidget/CnGaugeWidget.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/CnInteractionFormWidget/CnInteractionFormWidget.vue2.js +29 -6
- package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue.js +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +126 -2
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js +24 -2
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +106 -3
- 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/CnReportsPage/CnReportsPage.vue.js +1 -1
- package/dist/esm/components/CnReportsPage/CnReportsPage.vue2.js +135 -2
- package/dist/esm/components/CnReportsPage/CnReportsPage.vue2.js.map +1 -1
- package/dist/esm/components/CnReportsPage/CnReportsPage.vue3.js +95 -43
- package/dist/esm/components/CnReportsPage/CnReportsPage.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/CnStagesWidget/CnStagesWidget.vue.js +1 -1
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js +72 -5
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js +33 -8
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue.js +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +135 -11
- 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/CnStatsBlockWidget/CnStatsBlockWidget.vue.js +1 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js +17 -2
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js +1 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +39 -6
- 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/CnWalkthrough/CnWalkthrough.vue.js +1 -1
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js +16 -0
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js.map +1 -1
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js.map +1 -1
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue.js +1 -1
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js +2 -1
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.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/dialogs/CnTransitionInputDialog.vue.js +1 -1
- package/dist/esm/dialogs/CnTransitionInputDialog.vue2.js +68 -6
- package/dist/esm/dialogs/CnTransitionInputDialog.vue2.js.map +1 -1
- package/dist/esm/dialogs/CnTransitionInputDialog.vue3.js +133 -105
- package/dist/esm/dialogs/CnTransitionInputDialog.vue3.js.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +38 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +78 -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 +1030 -533
- 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 +41 -4
- package/dist/esm/utils/buildManifest.js.map +1 -1
- package/dist/esm/utils/detailWidgetVisibility.js +149 -0
- package/dist/esm/utils/detailWidgetVisibility.js.map +1 -0
- package/dist/esm/utils/formatMetric.js +39 -3
- package/dist/esm/utils/formatMetric.js.map +1 -1
- package/dist/esm/utils/gridStack.js +40 -1
- package/dist/esm/utils/gridStack.js.map +1 -1
- package/dist/esm/utils/humaniseKey.js +41 -0
- package/dist/esm/utils/humaniseKey.js.map +1 -0
- package/dist/esm/utils/notificationRuleLabel.js +6 -23
- package/dist/esm/utils/notificationRuleLabel.js.map +1 -1
- package/dist/esm/utils/resolveManifestTokens.js +1 -0
- package/dist/esm/utils/resolveManifestTokens.js.map +1 -1
- package/dist/esm/utils/sentinelTokens.js +9 -0
- package/dist/esm/utils/sentinelTokens.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 +17159 -13422
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +1030 -533
- package/l10n/en.json +38 -1
- package/l10n/nl.json +78 -1
- package/package.json +1 -1
- package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +160 -8
- package/src/components/CnAuditTrailCard/CnAuditTrailCard.vue +1 -1
- package/src/components/CnBoardView/CnBoardView.vue +262 -6
- package/src/components/CnBodySections/CnBodySections.vue +22 -1
- package/src/components/CnCountdownWidget/CnCountdownWidget.vue +47 -4
- package/src/components/CnDashboardGrid/CnDashboardGrid.vue +244 -8
- package/src/components/CnDashboardPage/CnDashboardPage.vue +62 -2
- package/src/components/CnDeltaWidget/CnDeltaWidget.vue +1 -1
- package/src/components/CnDetailPage/CnDetailPage.md +3 -1
- package/src/components/CnDetailPage/CnDetailPage.vue +226 -8
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +3 -1
- package/src/components/CnFactsWidget/CnFactsWidget.vue +18 -5
- package/src/components/CnFieldListWidget/CnFieldListWidget.vue +63 -1
- package/src/components/CnGaugeWidget/CnGaugeWidget.vue +1 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +586 -25
- package/src/components/CnIndexPage/useSelfFetchList.js +10 -1
- package/src/components/CnInteractionFormWidget/CnInteractionFormWidget.vue +29 -6
- package/src/components/CnObjectDataWidget/CnObjectDataWidget.md +1 -1
- package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +154 -5
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +106 -3
- package/src/components/CnRegisterMapping/CnRegisterMapping.md +5 -0
- package/src/components/CnRegisterMapping/CnRegisterMapping.vue +231 -96
- package/src/components/CnReportsPage/CnReportsPage.vue +179 -1
- package/src/components/CnSettingsPage/CnSettingsPage.vue +5 -1
- package/src/components/CnStagesWidget/CnStagesWidget.vue +103 -6
- package/src/components/CnStatWidget/CnStatWidget.vue +137 -12
- package/src/components/CnStatsBlockWidget/CnStatsBlockWidget.vue +34 -3
- package/src/components/CnTabsWidget/CnTabsWidget.vue +39 -6
- 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/CnWalkthrough/CnWalkthrough.vue +16 -0
- package/src/components/CnWeekStripWidget/CnWeekStripWidget.vue +2 -1
- 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/form-field.css +14 -0
- package/src/css/form-widget.css +11 -0
- package/src/css/index-page.css +49 -0
- package/src/css/kpi-card.css +13 -0
- package/src/css/look-board-reports.css +58 -0
- package/src/css/look-board.css +108 -7
- package/src/dialogs/CnTransitionInputDialog.vue +87 -7
- package/src/index.js +2 -0
- package/src/schemas/app-manifest-v2.schema.json +137 -6
- 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 +40 -3
- package/src/utils/detailWidgetVisibility.js +145 -0
- package/src/utils/formatMetric.js +38 -2
- package/src/utils/gridStack.js +39 -0
- package/src/utils/humaniseKey.js +38 -0
- package/src/utils/notificationRuleLabel.js +5 -23
- package/src/utils/resolveManifestTokens.js +1 -0
- package/src/utils/sentinelTokens.js +9 -0
- 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
|
|
@@ -183,7 +183,7 @@ export default {
|
|
|
183
183
|
/** Channel options `{ value, label }`. */
|
|
184
184
|
channelOptions() {
|
|
185
185
|
return Array.isArray(this.content.channels) && this.content.channels.length > 0
|
|
186
|
-
? this.content.channels
|
|
186
|
+
? this.content.channels.map((channel) => ({ ...channel, label: this.copy(channel && channel.label) }))
|
|
187
187
|
: [
|
|
188
188
|
{ value: 'telefoon', label: t('nextcloud-vue', 'Phone') },
|
|
189
189
|
{ value: 'email', label: t('nextcloud-vue', 'Email') },
|
|
@@ -204,10 +204,11 @@ export default {
|
|
|
204
204
|
const prop = properties[this.content.outcomeField || 'outcome']
|
|
205
205
|
if (prop && Array.isArray(prop.enum) && prop.enum.length > 0) {
|
|
206
206
|
const labels = prop['x-enum-labels'] || prop.enumLabels || {}
|
|
207
|
-
|
|
208
|
-
return prop.enum.map((value) => ({ value, label: tr(labels[value] || String(value)) }))
|
|
207
|
+
return prop.enum.map((value) => ({ value, label: this.copy(labels[value] || String(value)) }))
|
|
209
208
|
}
|
|
210
|
-
return Array.isArray(this.content.outcomes)
|
|
209
|
+
return Array.isArray(this.content.outcomes)
|
|
210
|
+
? this.content.outcomes.map((outcome) => ({ ...outcome, label: this.copy(outcome && outcome.label) }))
|
|
211
|
+
: []
|
|
211
212
|
},
|
|
212
213
|
|
|
213
214
|
/**
|
|
@@ -280,8 +281,7 @@ export default {
|
|
|
280
281
|
// `content.submitLabel` names what the form creates, e.g. "Save contact moment".
|
|
281
282
|
registerLabel() {
|
|
282
283
|
if (this.content.submitLabel) {
|
|
283
|
-
|
|
284
|
-
return tr(this.content.submitLabel)
|
|
284
|
+
return this.copy(this.content.submitLabel)
|
|
285
285
|
}
|
|
286
286
|
return t('nextcloud-vue', 'Register')
|
|
287
287
|
},
|
|
@@ -359,6 +359,29 @@ export default {
|
|
|
359
359
|
},
|
|
360
360
|
|
|
361
361
|
/** The default channel value (first configured channel, else `telefoon`). */
|
|
362
|
+
/**
|
|
363
|
+
* A label the manifest supplies (a channel, an outcome, the submit
|
|
364
|
+
* label), translated: the app's own translation first, since the words
|
|
365
|
+
* are the app's, then the library's catalogue, then the text as
|
|
366
|
+
* written. Before, a channel label was shown as written and the submit
|
|
367
|
+
* label only reached the app's translate, so pipelinq showed "Phone"
|
|
368
|
+
* and "Save contact moment" in English to a Dutch reader.
|
|
369
|
+
*
|
|
370
|
+
* @spec openspec/changes/interaction-form-fits-and-reads-dutch/specs/interaction-form-widget/spec.md#requirement-the-interaction-form-fits-its-card-and-reads-in-the-readers-language
|
|
371
|
+
* @param {string} key The label as the manifest writes it.
|
|
372
|
+
* @return {string} The translated label.
|
|
373
|
+
*/
|
|
374
|
+
copy(key) {
|
|
375
|
+
if (typeof key !== 'string' || key === '') {
|
|
376
|
+
return key
|
|
377
|
+
}
|
|
378
|
+
const host = typeof this.cnTranslate === 'function' ? this.cnTranslate(key) : key
|
|
379
|
+
if (typeof host === 'string' && host !== '' && host !== key) {
|
|
380
|
+
return host
|
|
381
|
+
}
|
|
382
|
+
return t('nextcloud-vue', key)
|
|
383
|
+
},
|
|
384
|
+
|
|
362
385
|
firstChannel() {
|
|
363
386
|
const ch = (this.content && this.content.channels) || []
|
|
364
387
|
return (Array.isArray(ch) && ch.length > 0 && ch[0].value) || 'telefoon'
|
|
@@ -121,7 +121,7 @@ export default {
|
|
|
121
121
|
| `icon` | Object\|Function | `null` | MDI icon component shown in the card header |
|
|
122
122
|
| `objectType` | String | `''` | Registered object type slug used by `objectStore.saveObject()` |
|
|
123
123
|
| `store` | Object | `null` | Explicit Pinia objectStore instance; auto-detected via Pinia when omitted |
|
|
124
|
-
| `overrides` | Object | `{}` | Per-property config: `{ key: { order, gridColumn, gridRow, hidden, editable, label, widget } }` |
|
|
124
|
+
| `overrides` | Object | `{}` | Per-property config: `{ key: { order, gridColumn, gridRow, hidden, editable, label, widget, visibleWhen } }`. `visibleWhen` shows the field only when the shared visibility predicate holds for this object, in its local mode: `{ field, op, value }` (ops `eq`, `neq`, `in`, `empty`, ...), or `all` / `any` of those. It reads unsaved changes too. A rule that needs a request (`endpoint`, `source`) is ignored. Read grid only: the Edit form still offers the field. |
|
|
125
125
|
| `columns` | Number | `3` | Number of CSS grid columns in the widget |
|
|
126
126
|
| `collapsedFields` (`collapsed-fields`) | Number | `null` | Number of fields shown while collapsed, with a "Show all N fields" button for the rest. `null` fits the cell: the whole rows that fit stay, and the button only appears when the cell overflows |
|
|
127
127
|
| `hideEmpty` | Boolean | `false` | Hide fields with no value instead of rendering an em dash. For discriminated supertypes (one `ticket` schema holding request/complaint/contactmoment), so the grid is type-aware without the schema enumerating which fields belong to which variant. Read grid only — the field being edited, a field with unsaved changes, and the Edit form stay visible; `false`/`0` are values and are never hidden. |
|
|
@@ -339,16 +339,30 @@
|
|
|
339
339
|
{{ stringifyCell(v) }}
|
|
340
340
|
</span>
|
|
341
341
|
</div>
|
|
342
|
-
<!-- Single plain OBJECT → key: value definition list
|
|
342
|
+
<!-- Single plain OBJECT → key: value definition list; a
|
|
343
|
+
nested plain object (e.g. a per-language map) gets
|
|
344
|
+
its own list one level down. -->
|
|
343
345
|
<dl
|
|
344
346
|
v-else-if="fieldValueKind(field) === 'object'"
|
|
345
347
|
class="cn-object-data-widget__deflist">
|
|
346
348
|
<template v-for="(pair, pi) in objectEntries(rawOf(field))" :key="pi">
|
|
347
349
|
<dt>
|
|
348
|
-
{{ pair[0] }}
|
|
350
|
+
{{ nestedLabel(field, [pair[0]]) }}
|
|
349
351
|
</dt>
|
|
350
352
|
<dd>
|
|
351
|
-
|
|
353
|
+
<dl
|
|
354
|
+
v-if="isPlainObject(pair[1])"
|
|
355
|
+
class="cn-object-data-widget__deflist cn-object-data-widget__deflist--nested">
|
|
356
|
+
<template v-for="(sub, si) in objectEntries(pair[1])" :key="si">
|
|
357
|
+
<dt>
|
|
358
|
+
{{ nestedLabel(field, [pair[0], sub[0]]) }}
|
|
359
|
+
</dt>
|
|
360
|
+
<dd>
|
|
361
|
+
<span class="cn-object-data-widget__deflist-text">{{ stringifyNested(sub[1]) }}</span>
|
|
362
|
+
</dd>
|
|
363
|
+
</template>
|
|
364
|
+
</dl>
|
|
365
|
+
<span v-else class="cn-object-data-widget__deflist-text">{{ stringifyNested(pair[1]) }}</span>
|
|
352
366
|
</dd>
|
|
353
367
|
</template>
|
|
354
368
|
</dl>
|
|
@@ -413,15 +427,20 @@ import CnFormDialog from '../CnFormDialog/CnFormDialog.vue'
|
|
|
413
427
|
import { normalizeLook } from '../../composables/useLook.js'
|
|
414
428
|
import { useObjectStore } from '../../store/index.js'
|
|
415
429
|
import { groupDisplayName, loadGroupDisplayName, searchNextcloudGroups } from '../../utils/groupAutocomplete.js'
|
|
430
|
+
import { humaniseKey } from '../../utils/humaniseKey.js'
|
|
416
431
|
import { PANEL_ACTION_SINK } from '../../utils/panelActions.js'
|
|
417
432
|
import { resolveFilterTokens } from '../../utils/resolveFilterTokens.js'
|
|
418
433
|
import { fieldsFromSchema, formatValue } from '../../utils/schema.js'
|
|
419
434
|
import { schemaRefSlug } from '../../utils/schemaRefSlug.js'
|
|
420
435
|
import { resolveNextcloudUser, searchNextcloudUsers } from '../../utils/userAutocomplete.js'
|
|
436
|
+
import { evaluateVisibleWhenLocal, isLocallyDecidableVisibleWhen } from '../../utils/visibleWhen.js'
|
|
421
437
|
import { CnIcon } from '../CnIcon/index.js'
|
|
422
438
|
import { CnObjectMetadataModal } from '../CnObjectMetadataModal/index.js'
|
|
423
439
|
import { CnWidgetWrapper } from '../CnWidgetWrapper/index.js'
|
|
424
440
|
|
|
441
|
+
// Widgets whose value the inline text fallback would corrupt.
|
|
442
|
+
const STRUCTURED_WIDGETS = ['json', 'code', 'file', 'sub-objects', 'child-records']
|
|
443
|
+
|
|
425
444
|
/**
|
|
426
445
|
* CnObjectDataWidget — Schema-driven editable data grid widget.
|
|
427
446
|
*
|
|
@@ -939,13 +958,18 @@ export default {
|
|
|
939
958
|
gridRow: (this.resolvedOverrides[field.key] && this.resolvedOverrides[field.key].gridRow) || 1,
|
|
940
959
|
}))
|
|
941
960
|
|
|
961
|
+
// Per-field condition: `overrides[key].visibleWhen` shows a field only
|
|
962
|
+
// for the objects it belongs to, such as a person's preferred name on
|
|
963
|
+
// a client that can also be an organisation.
|
|
964
|
+
const shown = withSpans.filter((field) => this.isVisibleByCondition(field))
|
|
965
|
+
|
|
942
966
|
if (!this.hideEmpty) {
|
|
943
|
-
return
|
|
967
|
+
return shown
|
|
944
968
|
}
|
|
945
969
|
|
|
946
970
|
// Keep a field the user is currently editing or has pending changes for,
|
|
947
971
|
// otherwise it would vanish mid-edit the moment its value is cleared.
|
|
948
|
-
return
|
|
972
|
+
return shown.filter((field) => field.key === this.editingField
|
|
949
973
|
|| field.key in this.dirtyFields
|
|
950
974
|
|| !this.isEmptyValue((this.objectData || {})[field.key]))
|
|
951
975
|
},
|
|
@@ -1357,6 +1381,59 @@ export default {
|
|
|
1357
1381
|
return (raw && typeof raw === 'object') ? Object.entries(raw) : []
|
|
1358
1382
|
},
|
|
1359
1383
|
|
|
1384
|
+
/**
|
|
1385
|
+
* Whether a value is a plain object (not an array, not null).
|
|
1386
|
+
*
|
|
1387
|
+
* @param {unknown} v The value.
|
|
1388
|
+
* @return {boolean} True for a plain object.
|
|
1389
|
+
*/
|
|
1390
|
+
isPlainObject(v) {
|
|
1391
|
+
return v !== null && typeof v === 'object' && !Array.isArray(v)
|
|
1392
|
+
},
|
|
1393
|
+
|
|
1394
|
+
/**
|
|
1395
|
+
* Label for a key inside an object value: the sub-schema's `title`
|
|
1396
|
+
* (translated) when the schema describes it, else the key, made
|
|
1397
|
+
* readable when it is identifier-shaped (`shortDescription` → "Short
|
|
1398
|
+
* description"; `nl` stays `nl`).
|
|
1399
|
+
*
|
|
1400
|
+
* @param {object} field Resolved field definition.
|
|
1401
|
+
* @param {string[]} path Keys from the field's value down to this entry.
|
|
1402
|
+
* @return {string} The label.
|
|
1403
|
+
* @spec openspec/changes/data-widget-nested-objects/specs/data-widget-nested-objects/spec.md#requirement-a-nested-object-reads-as-a-list-per-key
|
|
1404
|
+
*/
|
|
1405
|
+
nestedLabel(field, path) {
|
|
1406
|
+
let prop = ((this.schema && this.schema.properties) || {})[field.key]
|
|
1407
|
+
for (const key of path) {
|
|
1408
|
+
if (!this.isPlainObject(prop)) {
|
|
1409
|
+
prop = null
|
|
1410
|
+
break
|
|
1411
|
+
}
|
|
1412
|
+
prop = (prop.properties && prop.properties[key])
|
|
1413
|
+
|| (this.isPlainObject(prop.additionalProperties) ? prop.additionalProperties : null)
|
|
1414
|
+
}
|
|
1415
|
+
if (prop && typeof prop.title === 'string' && prop.title) {
|
|
1416
|
+
return this.cnTranslate(prop.title)
|
|
1417
|
+
}
|
|
1418
|
+
const key = String(path[path.length - 1])
|
|
1419
|
+
return /[A-Z_-]/.test(key) ? humaniseKey(key) : key
|
|
1420
|
+
},
|
|
1421
|
+
|
|
1422
|
+
/**
|
|
1423
|
+
* Stringify a value inside a definition list: a list of scalars reads
|
|
1424
|
+
* as a comma-separated line, anything else as `stringifyCell`.
|
|
1425
|
+
*
|
|
1426
|
+
* @param {unknown} v The value.
|
|
1427
|
+
* @return {string} The display string.
|
|
1428
|
+
* @spec openspec/changes/data-widget-nested-objects/specs/data-widget-nested-objects/spec.md#requirement-a-nested-object-reads-as-a-list-per-key
|
|
1429
|
+
*/
|
|
1430
|
+
stringifyNested(v) {
|
|
1431
|
+
if (Array.isArray(v) && v.length && v.every((item) => item === null || typeof item !== 'object')) {
|
|
1432
|
+
return v.map((item) => this.stringifyCell(item)).join(', ')
|
|
1433
|
+
}
|
|
1434
|
+
return this.stringifyCell(v)
|
|
1435
|
+
},
|
|
1436
|
+
|
|
1360
1437
|
/**
|
|
1361
1438
|
* Stringify a scalar cell value; a nested object/array collapses to
|
|
1362
1439
|
* compact JSON (never "[object Object]").
|
|
@@ -1684,15 +1761,53 @@ export default {
|
|
|
1684
1761
|
}))
|
|
1685
1762
|
},
|
|
1686
1763
|
|
|
1764
|
+
/**
|
|
1765
|
+
* Whether a field's value cannot be edited inline: a structured widget
|
|
1766
|
+
* (json, code, file, sub-objects, child-records), or a plain-object /
|
|
1767
|
+
* list-of-objects value that is not a relation. Scalar widgets without
|
|
1768
|
+
* a dedicated editor (duration, language, …) keep the text fallback.
|
|
1769
|
+
*
|
|
1770
|
+
* @param {object} field Resolved field definition.
|
|
1771
|
+
* @return {boolean} True when only the form dialog can edit it.
|
|
1772
|
+
* @spec openspec/changes/data-widget-nested-objects/specs/data-widget-nested-objects/spec.md#requirement-a-structured-value-is-never-edited-inline
|
|
1773
|
+
*/
|
|
1774
|
+
hasNoInlineEditor(field) {
|
|
1775
|
+
if (STRUCTURED_WIDGETS.includes(field.widget)) {
|
|
1776
|
+
return true
|
|
1777
|
+
}
|
|
1778
|
+
const prop = ((this.schema && this.schema.properties) || {})[field.key]
|
|
1779
|
+
if (this.isRelationField(prop)) {
|
|
1780
|
+
return false
|
|
1781
|
+
}
|
|
1782
|
+
// A list of objects by schema, also while empty (it resolves to `tags`).
|
|
1783
|
+
if (prop && prop.type === 'array' && prop.items && prop.items.type === 'object') {
|
|
1784
|
+
return true
|
|
1785
|
+
}
|
|
1786
|
+
// Classify the raw value itself: fieldValueKind reports a formatter
|
|
1787
|
+
// field as 'scalar' whatever it holds.
|
|
1788
|
+
const raw = this.rawOf(field)
|
|
1789
|
+
if (Array.isArray(raw)) {
|
|
1790
|
+
return raw.some((v) => this.isPlainObject(v))
|
|
1791
|
+
}
|
|
1792
|
+
return this.isPlainObject(raw)
|
|
1793
|
+
},
|
|
1794
|
+
|
|
1687
1795
|
/**
|
|
1688
1796
|
* Check if a field is editable.
|
|
1689
1797
|
*
|
|
1690
1798
|
* @param {object} field - Resolved field definition from resolvedFields
|
|
1799
|
+
* @spec openspec/changes/data-widget-nested-objects/specs/data-widget-nested-objects/spec.md#requirement-a-structured-value-is-never-edited-inline
|
|
1691
1800
|
*/
|
|
1692
1801
|
isEditable(field) {
|
|
1693
1802
|
if (!this.editable) {
|
|
1694
1803
|
return false
|
|
1695
1804
|
}
|
|
1805
|
+
// No inline editor exists for structured values: the text fallback
|
|
1806
|
+
// would commit "[object Object]". Those edit through the form dialog,
|
|
1807
|
+
// unless the consumer supplies its own `field-<key>` editor slot.
|
|
1808
|
+
if (!this.$slots['field-' + field.key] && this.hasNoInlineEditor(field)) {
|
|
1809
|
+
return false
|
|
1810
|
+
}
|
|
1696
1811
|
// Per-field override takes priority
|
|
1697
1812
|
const override = this.resolvedOverrides[field.key]
|
|
1698
1813
|
if (override && typeof override.editable === 'boolean') {
|
|
@@ -1709,6 +1824,29 @@ export default {
|
|
|
1709
1824
|
return !field.readOnly
|
|
1710
1825
|
},
|
|
1711
1826
|
|
|
1827
|
+
/**
|
|
1828
|
+
* Evaluate a field's `visibleWhen` override against the object.
|
|
1829
|
+
*
|
|
1830
|
+
* The rule is the shared visibility predicate in its LOCAL mode
|
|
1831
|
+
* (`{ field, op, value }`, `all` / `any`, `appInstalled`), evaluated by
|
|
1832
|
+
* `evaluateVisibleWhenLocal` against the object with its unsaved
|
|
1833
|
+
* changes, so a field appears or goes the moment the field it depends
|
|
1834
|
+
* on changes. A rule that needs a request (`endpoint`, `source`) cannot
|
|
1835
|
+
* be decided here and is ignored: the field shows, as it did before
|
|
1836
|
+
* the rule existed.
|
|
1837
|
+
*
|
|
1838
|
+
* @param {object} field - Resolved field definition.
|
|
1839
|
+
* @return {boolean} True when the field is shown.
|
|
1840
|
+
*/
|
|
1841
|
+
isVisibleByCondition(field) {
|
|
1842
|
+
const override = this.resolvedOverrides[field.key]
|
|
1843
|
+
const rule = override ? override.visibleWhen : undefined
|
|
1844
|
+
if (rule === undefined || rule === null || !isLocallyDecidableVisibleWhen(rule)) {
|
|
1845
|
+
return true
|
|
1846
|
+
}
|
|
1847
|
+
return evaluateVisibleWhenLocal(rule, { ...(this.objectData || {}), ...this.dirtyFields })
|
|
1848
|
+
},
|
|
1849
|
+
|
|
1712
1850
|
/**
|
|
1713
1851
|
* Evaluate a field's conditional read-only rule against the object data.
|
|
1714
1852
|
*
|
|
@@ -2410,6 +2548,17 @@ export default {
|
|
|
2410
2548
|
overflow-wrap: anywhere;
|
|
2411
2549
|
}
|
|
2412
2550
|
|
|
2551
|
+
/* A long description keeps its own paragraphs. Set on an inline span with no
|
|
2552
|
+
surrounding template whitespace, so only the value's own line breaks show. */
|
|
2553
|
+
.cn-object-data-widget__deflist-text {
|
|
2554
|
+
white-space: pre-wrap;
|
|
2555
|
+
}
|
|
2556
|
+
|
|
2557
|
+
.cn-object-data-widget__deflist--nested {
|
|
2558
|
+
padding-inline-start: 8px;
|
|
2559
|
+
border-inline-start: 2px solid var(--color-border);
|
|
2560
|
+
}
|
|
2561
|
+
|
|
2413
2562
|
/* Responsive: collapse to single column on narrow widths */
|
|
2414
2563
|
@media (max-width: 600px) {
|
|
2415
2564
|
.cn-object-data-widget__grid {
|
|
@@ -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}"`).
|
|
@@ -212,7 +215,11 @@ export default {
|
|
|
212
215
|
/**
|
|
213
216
|
* The list config: `{ register, schema, filter?, sort?, limit?, extend?,
|
|
214
217
|
* presentation?, title?, meta?, text?, label?, pill?, trailing?, icon?,
|
|
215
|
-
* rowRoute?, rowIdField?, headerLink?, headerNote?, emptyText?, errorText
|
|
218
|
+
* rowRoute?, rowIdField?, headerLink?, headerNote?, emptyText?, errorText?,
|
|
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
|
|
222
|
+
* the templates show.
|
|
216
223
|
*
|
|
217
224
|
* @type {object}
|
|
218
225
|
*/
|
|
@@ -236,6 +243,28 @@ export default {
|
|
|
236
243
|
|
|
237
244
|
emits: ['header'],
|
|
238
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
|
+
|
|
239
268
|
data() {
|
|
240
269
|
return {
|
|
241
270
|
rows: [],
|
|
@@ -292,6 +321,22 @@ export default {
|
|
|
292
321
|
return { config: unwrapAppConfig(this.cnAppConfig) }
|
|
293
322
|
},
|
|
294
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
|
+
|
|
295
340
|
/** @return {number} The fetch cap. */
|
|
296
341
|
limit() {
|
|
297
342
|
const value = Number(this.content?.limit)
|
|
@@ -396,6 +441,10 @@ export default {
|
|
|
396
441
|
requestKey() {
|
|
397
442
|
this.fetchRows()
|
|
398
443
|
},
|
|
444
|
+
|
|
445
|
+
endpointKey() {
|
|
446
|
+
this.fetchRows()
|
|
447
|
+
},
|
|
399
448
|
},
|
|
400
449
|
|
|
401
450
|
mounted() {
|
|
@@ -441,7 +490,40 @@ export default {
|
|
|
441
490
|
* @return {string} The text, '' when a token is empty.
|
|
442
491
|
*/
|
|
443
492
|
fill(template, row) {
|
|
444
|
-
return typeof template === 'string' && template !== '' ? fillTemplate(this.translate(template), row, new Date(), this.templateContext) : ''
|
|
493
|
+
return typeof template === 'string' && template !== '' ? fillTemplate(this.translate(template), this.labelledRow(row), new Date(), this.templateContext) : ''
|
|
494
|
+
},
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* The row as the templates read it: a field named in `valueLabels`
|
|
498
|
+
* (`{ channel: { phone: "Phone", telefoon: "Phone" } }`) shows the
|
|
499
|
+
* translated label of its stored value, so "{channel}" reads "Telefoon"
|
|
500
|
+
* for both spellings the data holds. A value without a label stays as
|
|
501
|
+
* stored. The pill, the badges and the sort read the stored value.
|
|
502
|
+
*
|
|
503
|
+
* @param {object} row The row.
|
|
504
|
+
* @return {object} The row, or a shallow copy with labelled fields.
|
|
505
|
+
* @spec openspec/changes/screens-record-list-value-labels/specs/record-list-value-labels/spec.md#requirement-a-record-list-template-can-show-a-label-for-a-stored-value
|
|
506
|
+
*/
|
|
507
|
+
labelledRow(row) {
|
|
508
|
+
const map = this.content?.valueLabels
|
|
509
|
+
if (!row || typeof row !== 'object' || !map || typeof map !== 'object') {
|
|
510
|
+
return row
|
|
511
|
+
}
|
|
512
|
+
let out = row
|
|
513
|
+
for (const [field, labels] of Object.entries(map)) {
|
|
514
|
+
const raw = row[field]
|
|
515
|
+
if (!labels || typeof labels !== 'object' || (typeof raw !== 'string' && typeof raw !== 'number')) {
|
|
516
|
+
continue
|
|
517
|
+
}
|
|
518
|
+
const label = labels[String(raw)]
|
|
519
|
+
if (typeof label === 'string' && label !== '') {
|
|
520
|
+
if (out === row) {
|
|
521
|
+
out = { ...row }
|
|
522
|
+
}
|
|
523
|
+
out[field] = this.translate(label)
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
return out
|
|
445
527
|
},
|
|
446
528
|
|
|
447
529
|
/**
|
|
@@ -819,6 +901,23 @@ export default {
|
|
|
819
901
|
return { rows, total: typeof head.total === 'number' ? head.total : rows.length }
|
|
820
902
|
},
|
|
821
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
|
+
|
|
822
921
|
/**
|
|
823
922
|
* Fetch the list. Sends nothing while a required token is unresolved
|
|
824
923
|
* or the source is incomplete.
|
|
@@ -827,6 +926,10 @@ export default {
|
|
|
827
926
|
*/
|
|
828
927
|
async fetchRows() {
|
|
829
928
|
const c = this.content || {}
|
|
929
|
+
if (this.endpointActive) {
|
|
930
|
+
this.takeEndpointRows()
|
|
931
|
+
return
|
|
932
|
+
}
|
|
830
933
|
if (!c.register || !c.schema || this.waiting) {
|
|
831
934
|
this.rows = []
|
|
832
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) |
|