@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
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
</span>
|
|
83
83
|
</div>
|
|
84
84
|
|
|
85
|
-
<div class="cn-kpi-card__value-row cn-stat-widget__value-row">
|
|
85
|
+
<div ref="valueRow" class="cn-kpi-card__value-row cn-stat-widget__value-row">
|
|
86
86
|
<NcLoadingIcon v-if="displayLoading" :size="22" />
|
|
87
87
|
<span v-else-if="displayError" class="cn-stat-widget__error" :title="displayError">—</span>
|
|
88
88
|
<template v-else>
|
|
@@ -96,6 +96,7 @@
|
|
|
96
96
|
:variant="badgeVariant" />
|
|
97
97
|
<span
|
|
98
98
|
v-else
|
|
99
|
+
ref="value"
|
|
99
100
|
class="cn-kpi-card__value cn-stat-widget__value"
|
|
100
101
|
:data-testid="isCountdown ? 'cn-stat-widget-countdown' : null"
|
|
101
102
|
:class="{ 'cn-kpi-card__value--text': isTextValue || shownValueIsText }"
|
|
@@ -165,7 +166,7 @@ import { resolveObjectOpType } from '../../utils/actionsDispatcher.js'
|
|
|
165
166
|
import { resolveObjectTokenContext } from '../../utils/detailObjectContext.js'
|
|
166
167
|
import { aggFilterParam } from '../../utils/fetchAggregate.js'
|
|
167
168
|
import { fetchListTotal, needsListEndpoint } from '../../utils/fetchFilterCounts.js'
|
|
168
|
-
import { formatMetricValue, unwrapAppConfig } from '../../utils/formatMetric.js'
|
|
169
|
+
import { formatMetricValue, metricLocale, unwrapAppConfig } from '../../utils/formatMetric.js'
|
|
169
170
|
import { dropOptionalUnresolved, resolveFilterTokens } from '../../utils/resolveFilterTokens.js'
|
|
170
171
|
import { STATUS_TEXT_COLORS } from '../../utils/statusColors.js'
|
|
171
172
|
import { evaluateVisibleWhenLocal, readVisibleWhenPath } from '../../utils/visibleWhen.js'
|
|
@@ -223,6 +224,9 @@ const DATE_ONLY = /^(\d{4})-(\d{2})-(\d{2})$/
|
|
|
223
224
|
/** Milliseconds in a day, used only between two UTC-normalised midnights. */
|
|
224
225
|
const DAY_MS = 86400000
|
|
225
226
|
|
|
227
|
+
/** The smallest size `fitValue` steps a number down to, in px; below it the number wraps. */
|
|
228
|
+
const MIN_FIT_FONT_SIZE = 14
|
|
229
|
+
|
|
226
230
|
/**
|
|
227
231
|
* The local calendar day a value names, as a UTC midnight timestamp.
|
|
228
232
|
*
|
|
@@ -517,7 +521,7 @@ export default {
|
|
|
517
521
|
* counted between LOCAL CALENDAR DAYS, not as elapsed milliseconds, so
|
|
518
522
|
* the time of day on a deadline never moves the answer.
|
|
519
523
|
*
|
|
520
|
-
* @type {{label?: string, icon?: string, iconColor?: string, valueColor?: string, caption?: string, route?: (object|string), clickRoute?: (object|string), link?: string, format?: {style?: string, currency?: string, decimals?: number, prefix?: string, suffix?: string}, source?: {kind?: string, register?: string, schema?: string, metric?: string, field?: string, filter?: object, url?: string, path?: string, params?: object}, endpointSource?: {url: string, method?: string, params?: object, responsePath?: string}, valueField?: string, limitField?: string, limit?: number, dateRange?: {presets?: Array<{id: string, label?: string, from?: string, to?: string}>, default?: string}, previousField?: string, deltaField?: string, goodDirection?: ('up'|'down'), variant?: ('default'|'primary'|'success'|'warning'|'error'|'danger'), variantWhen?: Array<{op: string, value: unknown, variant: string, icon?: string}>, objectField?: (string|{field: string, resolve?: {register: string, schema: string, labelField?: string, variantField?: string, variantMap?: {[key: string]: string}}}), display?: ('text'|'badge'|'countdown'), countdown?: {unit?: 'days', warnAt?: number, dangerAt?: number, futureLabel?: string, todayLabel?: string, pastLabel?: string, emptyText?: string}, emptyText?: string, captionVariant?: ('default'|'primary'|'success'|'warning'|'error'|'danger'), captionVariantWhen?: Array<{op: string, value: unknown, variant: string}>, captionSource?: {register?: string, schema?: string, metric?: string, field?: string, filter?: object, via?: string, zeroText?: string}, overrides?: Array<{when: {field: string, op?: string, value?: unknown}, label?: string, variant?: string, icon?: string, caption?: string, captionVariant?: string}>}}
|
|
524
|
+
* @type {{label?: string, icon?: string, iconColor?: string, valueColor?: string, caption?: string, route?: (object|string), clickRoute?: (object|string), link?: string, format?: {style?: string, currency?: string, decimals?: number, prefix?: string, suffix?: string}, source?: {kind?: string, register?: string, schema?: string, metric?: string, field?: string, filter?: object, url?: string, path?: string, params?: object}, endpointSource?: {url: string, method?: string, params?: object, responsePath?: string}, valueField?: string, limitField?: string, limit?: number, dateRange?: {presets?: Array<{id: string, label?: string, from?: string, to?: string}>, default?: string}, previousField?: string, deltaField?: string, goodDirection?: ('up'|'down'), variant?: ('default'|'primary'|'success'|'warning'|'error'|'danger'), variantWhen?: Array<{op: string, value: unknown, variant: string, icon?: string}>, objectField?: (string|{field: string, resolve?: {register: string, schema: string, labelField?: string, variantField?: string, variantMap?: {[key: string]: string}}}), display?: ('text'|'badge'|'countdown'), countdown?: {unit?: 'days', warnAt?: number, dangerAt?: number, futureLabel?: string, todayLabel?: string, pastLabel?: string, emptyText?: string}, emptyText?: string, captionVariant?: ('default'|'primary'|'success'|'warning'|'error'|'danger'), captionVariantWhen?: Array<{op: string, value: unknown, variant: string}>, captionSource?: {kind?: 'ratio', register?: string, schema?: string, metric?: string, field?: string, filter?: object, via?: string, zeroText?: string, numerator?: {metric?: string, field?: string, filter?: object, via?: string}, denominator?: {metric?: string, field?: string, filter?: object, via?: string}, decimals?: number}, overrides?: Array<{when: {field: string, op?: string, value?: unknown}, label?: string, variant?: string, icon?: string, caption?: string, captionVariant?: string}>}}
|
|
521
525
|
*/
|
|
522
526
|
content: {
|
|
523
527
|
type: Object,
|
|
@@ -592,6 +596,9 @@ export default {
|
|
|
592
596
|
|
|
593
597
|
data() {
|
|
594
598
|
return {
|
|
599
|
+
// The value's font size while the number would not fit its row at
|
|
600
|
+
// its own size; null when it fits.
|
|
601
|
+
fitFontSize: null,
|
|
595
602
|
// Resolved display label for an `objectField` that holds a reference
|
|
596
603
|
// uuid. Null until resolved, and null forever when it cannot be.
|
|
597
604
|
referenceLabel: null,
|
|
@@ -609,6 +616,9 @@ export default {
|
|
|
609
616
|
value: null,
|
|
610
617
|
// The caption's own count (`content.captionSource`), null until read.
|
|
611
618
|
captionValue: null,
|
|
619
|
+
// The parts of a ratio caption (`captionSource.kind: "ratio"`): the
|
|
620
|
+
// numerator read (null: the tile's own value) and the denominator.
|
|
621
|
+
captionRatioParts: null,
|
|
612
622
|
loading: false,
|
|
613
623
|
error: '',
|
|
614
624
|
}
|
|
@@ -684,8 +694,9 @@ export default {
|
|
|
684
694
|
// nothing to say while it is not read, and at zero the `zeroText`
|
|
685
695
|
// (or nothing) instead of "0 waiting".
|
|
686
696
|
const counted = this.hasCaptionSource
|
|
687
|
-
|
|
688
|
-
|
|
697
|
+
const count = this.captionCount
|
|
698
|
+
if (counted && !(Number(count) > 0)) {
|
|
699
|
+
const zero = count !== null && this.content.captionSource.zeroText
|
|
689
700
|
return typeof zero === 'string' && zero !== '' ? this.effectiveTranslate(zero) : ''
|
|
690
701
|
}
|
|
691
702
|
const translated = this.effectiveTranslate(caption)
|
|
@@ -694,9 +705,10 @@ export default {
|
|
|
694
705
|
}
|
|
695
706
|
const payload = this.endpointMode ? this.epData : null
|
|
696
707
|
return translated.replace(/\{([A-Za-z0-9_.]+)\}/g, (whole, path) => {
|
|
697
|
-
// `{count}` is the caption's own count
|
|
698
|
-
|
|
699
|
-
|
|
708
|
+
// `{count}` is the caption's own count; `{ratio}` the same
|
|
709
|
+
// for a ratio caption, as a percentage.
|
|
710
|
+
if ((path === 'count' || (path === 'ratio' && this.isCaptionRatio)) && counted) {
|
|
711
|
+
return this.isCaptionRatio ? this.formatCaptionRatio(count) : String(count)
|
|
700
712
|
}
|
|
701
713
|
// `{value}` is the tile's own number, so a caption can read
|
|
702
714
|
// "{value} due today" on a register-counted tile that has no
|
|
@@ -709,6 +721,42 @@ export default {
|
|
|
709
721
|
}).replace(/\s{2,}/g, ' ').trim()
|
|
710
722
|
},
|
|
711
723
|
|
|
724
|
+
/**
|
|
725
|
+
* Whether the caption is a share (`captionSource.kind: "ratio"`).
|
|
726
|
+
*
|
|
727
|
+
* @return {boolean}
|
|
728
|
+
* @spec openspec/changes/stat-caption-ratio/specs/stat-widget/spec.md#requirement-a-stat-tiles-caption-can-show-a-share
|
|
729
|
+
*/
|
|
730
|
+
isCaptionRatio() {
|
|
731
|
+
const own = this.content.captionSource
|
|
732
|
+
return !!own && typeof own === 'object' && own.kind === 'ratio'
|
|
733
|
+
},
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* The caption's own number: its count, or for a ratio caption the
|
|
737
|
+
* numerator (the tile's own value when it names none) over the
|
|
738
|
+
* denominator, times 100. Null while unread, and for a zero or failed
|
|
739
|
+
* denominator.
|
|
740
|
+
*
|
|
741
|
+
* @return {number|null}
|
|
742
|
+
* @spec openspec/changes/stat-caption-ratio/specs/stat-widget/spec.md#requirement-a-stat-tiles-caption-can-show-a-share
|
|
743
|
+
*/
|
|
744
|
+
captionCount() {
|
|
745
|
+
if (!this.isCaptionRatio) {
|
|
746
|
+
return this.captionValue
|
|
747
|
+
}
|
|
748
|
+
const parts = this.captionRatioParts
|
|
749
|
+
if (!parts) {
|
|
750
|
+
return null
|
|
751
|
+
}
|
|
752
|
+
const num = parts.num === null ? this.displayValue : parts.num
|
|
753
|
+
const den = Number(parts.den)
|
|
754
|
+
if (num === null || num === undefined || num === '' || !Number.isFinite(Number(num)) || !Number.isFinite(den) || den === 0) {
|
|
755
|
+
return null
|
|
756
|
+
}
|
|
757
|
+
return (Number(num) / den) * 100
|
|
758
|
+
},
|
|
759
|
+
|
|
712
760
|
/**
|
|
713
761
|
* Whether the caption has a count of its own (`content.captionSource`
|
|
714
762
|
* with a register and schema, its own or the tile's).
|
|
@@ -737,7 +785,7 @@ export default {
|
|
|
737
785
|
return null
|
|
738
786
|
}
|
|
739
787
|
// A caption with its own count is coloured by that count.
|
|
740
|
-
const current = this.hasCaptionSource ? this.
|
|
788
|
+
const current = this.hasCaptionSource ? this.captionCount : this.displayValue
|
|
741
789
|
if (current === null || current === undefined) {
|
|
742
790
|
return null
|
|
743
791
|
}
|
|
@@ -1447,7 +1495,16 @@ export default {
|
|
|
1447
1495
|
/** Inline style for the value text (variant rule wins over valueColor). */
|
|
1448
1496
|
valueStyle() {
|
|
1449
1497
|
const color = this.variantColor || this.content.valueColor
|
|
1450
|
-
|
|
1498
|
+
const style = color ? { color } : {}
|
|
1499
|
+
if (this.fitFontSize !== null) {
|
|
1500
|
+
style.fontSize = `${this.fitFontSize}px`
|
|
1501
|
+
// At the smallest size a number that still does not fit wraps rather than clips.
|
|
1502
|
+
if (this.fitFontSize <= MIN_FIT_FONT_SIZE) {
|
|
1503
|
+
style.whiteSpace = 'normal'
|
|
1504
|
+
style.overflowWrap = 'anywhere'
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
return style
|
|
1451
1508
|
},
|
|
1452
1509
|
|
|
1453
1510
|
/**
|
|
@@ -1460,7 +1517,7 @@ export default {
|
|
|
1460
1517
|
* @return {string}
|
|
1461
1518
|
*/
|
|
1462
1519
|
formattedValue() {
|
|
1463
|
-
return formatMetricValue(this.displayValue, this.content.format, this.configCtx, this.objectCtx?.object || null)
|
|
1520
|
+
return formatMetricValue(this.displayValue, this.content.format, this.configCtx, this.objectCtx?.object || null, this.effectiveTranslate)
|
|
1464
1521
|
},
|
|
1465
1522
|
|
|
1466
1523
|
/**
|
|
@@ -1556,6 +1613,10 @@ export default {
|
|
|
1556
1613
|
this.fetchValue()
|
|
1557
1614
|
},
|
|
1558
1615
|
|
|
1616
|
+
shownValue() {
|
|
1617
|
+
this.$nextTick(() => this.fitValue())
|
|
1618
|
+
},
|
|
1619
|
+
|
|
1559
1620
|
objectFieldRaw: {
|
|
1560
1621
|
immediate: true,
|
|
1561
1622
|
handler() {
|
|
@@ -1576,13 +1637,50 @@ export default {
|
|
|
1576
1637
|
payload?.waitUntil?.(done)
|
|
1577
1638
|
}
|
|
1578
1639
|
subscribe(PAGE_REFRESH_CHANNEL, this._onPageRefresh)
|
|
1640
|
+
if (typeof ResizeObserver === 'function' && this.$refs.valueRow) {
|
|
1641
|
+
this._fitObserver = new ResizeObserver(() => this.fitValue())
|
|
1642
|
+
this._fitObserver.observe(this.$refs.valueRow)
|
|
1643
|
+
}
|
|
1644
|
+
this.$nextTick(() => this.fitValue())
|
|
1579
1645
|
},
|
|
1580
1646
|
|
|
1581
1647
|
beforeUnmount() {
|
|
1582
1648
|
unsubscribe(PAGE_REFRESH_CHANNEL, this._onPageRefresh)
|
|
1649
|
+
this._fitObserver?.disconnect()
|
|
1583
1650
|
},
|
|
1584
1651
|
|
|
1585
1652
|
methods: {
|
|
1653
|
+
/**
|
|
1654
|
+
* Step the number's font down until it fits its row, instead of the
|
|
1655
|
+
* card clipping it ("€ 1.012.4" in a narrow tile). A value that fits
|
|
1656
|
+
* keeps its own size; text values wrap and clamp on their own.
|
|
1657
|
+
*
|
|
1658
|
+
* @return {void}
|
|
1659
|
+
* @spec openspec/changes/stat-value-fits/specs/stat-tile-board-look/spec.md#requirement-a-stat-value-shrinks-instead-of-clipping
|
|
1660
|
+
*/
|
|
1661
|
+
fitValue() {
|
|
1662
|
+
const row = this.$refs.valueRow
|
|
1663
|
+
const el = this.$refs.value
|
|
1664
|
+
if (!row || !el || el.classList.contains('cn-kpi-card__value--text') || typeof getComputedStyle !== 'function') {
|
|
1665
|
+
this.fitFontSize = null
|
|
1666
|
+
return
|
|
1667
|
+
}
|
|
1668
|
+
const applied = el.style.fontSize
|
|
1669
|
+
const whiteSpace = el.style.whiteSpace
|
|
1670
|
+
el.style.fontSize = ''
|
|
1671
|
+
el.style.whiteSpace = ''
|
|
1672
|
+
const natural = parseFloat(getComputedStyle(el).fontSize)
|
|
1673
|
+
const overflow = row.scrollWidth - row.clientWidth
|
|
1674
|
+
const width = el.offsetWidth
|
|
1675
|
+
el.style.fontSize = applied
|
|
1676
|
+
el.style.whiteSpace = whiteSpace
|
|
1677
|
+
if (!(overflow > 0.5) || !(width > 0) || !(natural > 0)) {
|
|
1678
|
+
this.fitFontSize = null
|
|
1679
|
+
return
|
|
1680
|
+
}
|
|
1681
|
+
this.fitFontSize = Math.max(MIN_FIT_FONT_SIZE, Math.floor(natural * ((width - overflow) / width) * 10) / 10)
|
|
1682
|
+
},
|
|
1683
|
+
|
|
1586
1684
|
/**
|
|
1587
1685
|
* Resolve an `objectField` that holds a reference uuid to the referenced
|
|
1588
1686
|
* object's label, through the shared object store (per-schema caching and
|
|
@@ -1843,7 +1941,9 @@ export default {
|
|
|
1843
1941
|
/**
|
|
1844
1942
|
* Read the caption's own count (`content.captionSource`:
|
|
1845
1943
|
* `{ register?, schema?, metric?, field?, filter?, via?, zeroText? }`,
|
|
1846
|
-
* register and schema default to the tile's source).
|
|
1944
|
+
* register and schema default to the tile's source). With `kind:
|
|
1945
|
+
* "ratio"` it reads the `denominator` part, and the `numerator` part
|
|
1946
|
+
* when one is named, as the tile's `ratio` source does. A failed read
|
|
1847
1947
|
* leaves the caption out; it never shows a zero it did not count.
|
|
1848
1948
|
*
|
|
1849
1949
|
* @spec openspec/changes/screens-stat-caption-source/specs/stat-widget/spec.md#requirement-a-stat-tiles-caption-can-carry-its-own-count
|
|
@@ -1852,6 +1952,7 @@ export default {
|
|
|
1852
1952
|
async fetchCaptionValue() {
|
|
1853
1953
|
if (!this.hasCaptionSource) {
|
|
1854
1954
|
this.captionValue = null
|
|
1955
|
+
this.captionRatioParts = null
|
|
1855
1956
|
return
|
|
1856
1957
|
}
|
|
1857
1958
|
const own = this.content.captionSource
|
|
@@ -1862,13 +1963,37 @@ export default {
|
|
|
1862
1963
|
import('@nextcloud/axios'),
|
|
1863
1964
|
import('@nextcloud/router'),
|
|
1864
1965
|
])
|
|
1966
|
+
if (own.kind === 'ratio') {
|
|
1967
|
+
// The parts of the tile's own `ratio` source: each names its
|
|
1968
|
+
// metric, field, filter and `via`.
|
|
1969
|
+
const part = (p) => this.fetchAggregate(axios, generateUrl, source, (p && p.metric) || own.metric, (p && p.field) || own.field, (p && p.filter) || {}, (p && p.via) || own.via)
|
|
1970
|
+
const num = own.numerator ? await part(own.numerator) : null
|
|
1971
|
+
const den = await part(own.denominator)
|
|
1972
|
+
this.captionRatioParts = { num: num === undefined ? null : num, den }
|
|
1973
|
+
return
|
|
1974
|
+
}
|
|
1865
1975
|
const found = await this.fetchAggregate(axios, generateUrl, source, own.metric, own.field, own.filter || {}, own.via)
|
|
1866
1976
|
this.captionValue = found === null || found === undefined || Number.isNaN(Number(found)) ? null : Number(found)
|
|
1867
1977
|
} catch {
|
|
1868
1978
|
this.captionValue = null
|
|
1979
|
+
this.captionRatioParts = null
|
|
1869
1980
|
}
|
|
1870
1981
|
},
|
|
1871
1982
|
|
|
1983
|
+
/**
|
|
1984
|
+
* A ratio caption's share as the reader's number, with
|
|
1985
|
+
* `captionSource.decimals` fraction digits (0 by default).
|
|
1986
|
+
*
|
|
1987
|
+
* @param {number} share The percentage.
|
|
1988
|
+
* @return {string}
|
|
1989
|
+
* @spec openspec/changes/stat-caption-ratio/specs/stat-widget/spec.md#requirement-a-stat-tiles-caption-can-show-a-share
|
|
1990
|
+
*/
|
|
1991
|
+
formatCaptionRatio(share) {
|
|
1992
|
+
const raw = Number(this.content.captionSource && this.content.captionSource.decimals)
|
|
1993
|
+
const digits = Number.isInteger(raw) && raw >= 0 && raw <= 4 ? raw : 0
|
|
1994
|
+
return new Intl.NumberFormat(metricLocale(), { minimumFractionDigits: digits, maximumFractionDigits: digits }).format(share)
|
|
1995
|
+
},
|
|
1996
|
+
|
|
1872
1997
|
/**
|
|
1873
1998
|
* Resolve the widget's value from its `source`. Supports three source
|
|
1874
1999
|
* kinds (ADR-041): a plain `aggregate` (count/sum/avg/min/max with
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
<template>
|
|
18
18
|
<div
|
|
19
19
|
class="cn-stats-block-widget"
|
|
20
|
-
:class="[iconClass, { 'cn-stats-block-widget--multi': hasEntries && !isRows && !isGrid, 'cn-stats-block-widget--rows': isRows, 'cn-stats-block-widget--grid': isGrid }]">
|
|
20
|
+
:class="[iconClass, { 'cn-stats-block-widget--multi': hasEntries && !isRows && !isGrid, 'cn-stats-block-widget--rows': isRows, 'cn-stats-block-widget--grid': isGrid, 'cn-stats-block-widget--tiles-box': isGrid && tiles === 'box' }]">
|
|
21
21
|
<!-- Grid layout: the `stats` (each a stat tile's content, so every
|
|
22
22
|
source kind a stat takes: ratio, elapsed, computed...) in `columns`
|
|
23
23
|
columns, inside this one card (the board's "Je week tot nu toe"
|
|
@@ -288,6 +288,21 @@ export default {
|
|
|
288
288
|
default: 2,
|
|
289
289
|
},
|
|
290
290
|
|
|
291
|
+
/**
|
|
292
|
+
* How the grid layout draws its tiles: `'box'` gives each tile a
|
|
293
|
+
* surface of its own (a grey ground, 10px radius, 14px 16px padding),
|
|
294
|
+
* as board DcRapportages draws its efficiency figures. Empty (the
|
|
295
|
+
* default) keeps the tiles as parts of the one card, without a box
|
|
296
|
+
* (board PqMijnWerk "Je week tot nu toe").
|
|
297
|
+
*
|
|
298
|
+
* @type {'box'|null}
|
|
299
|
+
* @spec openspec/changes/screens-stats-block-tile-box/specs/stats-block-grid/spec.md#requirement-a-stats-block-grid-can-box-its-tiles
|
|
300
|
+
*/
|
|
301
|
+
tiles: {
|
|
302
|
+
type: String,
|
|
303
|
+
default: null,
|
|
304
|
+
},
|
|
305
|
+
|
|
291
306
|
/**
|
|
292
307
|
* How the single-source value is shown: a style name (`'currency'`,
|
|
293
308
|
* `'number'`, `'percent'`, `'decimal'`, `'duration-hours'`) or
|
|
@@ -566,7 +581,7 @@ export default {
|
|
|
566
581
|
if (!fmt || this.loading) {
|
|
567
582
|
return null
|
|
568
583
|
}
|
|
569
|
-
return formatMetricValue(this.resolvedCount, fmt, this.configCtx, this.pageObject)
|
|
584
|
+
return formatMetricValue(this.resolvedCount, fmt, this.configCtx, this.pageObject, this.tl)
|
|
570
585
|
},
|
|
571
586
|
|
|
572
587
|
/**
|
|
@@ -598,7 +613,7 @@ export default {
|
|
|
598
613
|
return {
|
|
599
614
|
// The value as the entry's `format` says, money in the
|
|
600
615
|
// currency the entry names; null keeps the plain count.
|
|
601
|
-
formatted: fmt && resolved ? formatMetricValue(raw, fmt, this.configCtx, this.pageObject) : null,
|
|
616
|
+
formatted: fmt && resolved ? formatMetricValue(raw, fmt, this.configCtx, this.pageObject, this.tl) : null,
|
|
602
617
|
entry: entry || {},
|
|
603
618
|
key: `${(entry && entry.register) || ''}/${(entry && entry.schema) || ''}/${i}`,
|
|
604
619
|
count: resolved ? raw : 0,
|
|
@@ -961,7 +976,10 @@ export default {
|
|
|
961
976
|
color: var(--color-text-maxcontrast);
|
|
962
977
|
}
|
|
963
978
|
|
|
979
|
+
/* `width: 100%` plus this padding overflowed the card by 48px: the last
|
|
980
|
+
column ran past the card's right edge (visible once the tiles have a box). */
|
|
964
981
|
.cn-stats-block-widget--grid {
|
|
982
|
+
box-sizing: border-box;
|
|
965
983
|
padding: 4px 24px 20px;
|
|
966
984
|
}
|
|
967
985
|
|
|
@@ -977,6 +995,19 @@ export default {
|
|
|
977
995
|
min-width: 0;
|
|
978
996
|
}
|
|
979
997
|
|
|
998
|
+
/* `tiles: "box"`: each tile on a grey ground of its own (board DcRapportages,
|
|
999
|
+
"Vergaderingen en efficiëntie"), 12px apart. */
|
|
1000
|
+
.cn-stats-block-widget--tiles-box .cn-stats-block-widget__grid {
|
|
1001
|
+
gap: 12px;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
.cn-stats-block-widget--tiles-box .cn-stats-block-widget__grid-stat {
|
|
1005
|
+
box-sizing: border-box;
|
|
1006
|
+
padding: 14px 16px;
|
|
1007
|
+
border-radius: 10px;
|
|
1008
|
+
background: var(--color-background-hover);
|
|
1009
|
+
}
|
|
1010
|
+
|
|
980
1011
|
.cn-stats-block-widget__rows {
|
|
981
1012
|
display: flex;
|
|
982
1013
|
flex-direction: column;
|
|
@@ -311,6 +311,12 @@ export default {
|
|
|
311
311
|
cnLook: { default: 'nextcloud' },
|
|
312
312
|
/** The tab list's name under the board look, provided by CnDetailPage (`config.tabsLabel`). */
|
|
313
313
|
cnDetailTabsLabel: { default: null },
|
|
314
|
+
/**
|
|
315
|
+
* The records the detail page's `visibilityContext` names, keyed
|
|
316
|
+
* by name (a case's case type), provided by CnDetailPage. A tab's
|
|
317
|
+
* `visibleWhen` reads them as `@context.<name>`. Null on any other page.
|
|
318
|
+
*/
|
|
319
|
+
cnDetailVisibilityRows: { default: null },
|
|
314
320
|
/**
|
|
315
321
|
* The host's translate (CnAppRoot provides `cnTranslate`), so a tab
|
|
316
322
|
* label written in the manifest reads in the user's language.
|
|
@@ -475,6 +481,29 @@ export default {
|
|
|
475
481
|
},
|
|
476
482
|
|
|
477
483
|
computed: {
|
|
484
|
+
/**
|
|
485
|
+
* What a tab's `visibleWhen` reads: the record, with the records the
|
|
486
|
+
* detail page's `visibilityContext` names under `@context.<name>`. Null while
|
|
487
|
+
* the record, or one of those records, is not there yet.
|
|
488
|
+
*
|
|
489
|
+
* @return {object|null}
|
|
490
|
+
* @spec openspec/changes/detail-widgets-visible-when/specs/detail-widgets-visible-when/spec.md#requirement-a-detail-page-can-load-records-a-condition-names
|
|
491
|
+
*/
|
|
492
|
+
conditionData() {
|
|
493
|
+
if (!this.objectData) {
|
|
494
|
+
return null
|
|
495
|
+
}
|
|
496
|
+
const holder = this.cnDetailVisibilityRows
|
|
497
|
+
if (!holder) {
|
|
498
|
+
return this.objectData
|
|
499
|
+
}
|
|
500
|
+
const rows = holder.value
|
|
501
|
+
if (!rows) {
|
|
502
|
+
return null
|
|
503
|
+
}
|
|
504
|
+
return { ...this.objectData, '@context': rows }
|
|
505
|
+
},
|
|
506
|
+
|
|
478
507
|
/**
|
|
479
508
|
* The open panel's own menu items, or an empty list.
|
|
480
509
|
*
|
|
@@ -859,10 +888,11 @@ export default {
|
|
|
859
888
|
return 'visible'
|
|
860
889
|
}
|
|
861
890
|
if (isLocallyDecidableVisibleWhen(cond)) {
|
|
862
|
-
|
|
891
|
+
const data = this.conditionData
|
|
892
|
+
if (!data) {
|
|
863
893
|
return 'pending'
|
|
864
894
|
}
|
|
865
|
-
return evaluateVisibleWhenLocal(cond,
|
|
895
|
+
return evaluateVisibleWhenLocal(cond, data) ? 'visible' : 'hidden'
|
|
866
896
|
}
|
|
867
897
|
const answer = this.sourceVisibility[index]
|
|
868
898
|
if (answer === undefined) {
|
|
@@ -883,7 +913,7 @@ export default {
|
|
|
883
913
|
async refreshSourceVisibility() {
|
|
884
914
|
const tabs = Array.isArray(this.content?.tabs) ? this.content.tabs : []
|
|
885
915
|
const seq = ++this.visibilitySeq
|
|
886
|
-
const ctx = { objectId: this.objectId, object: this.objectData || undefined }
|
|
916
|
+
const ctx = { objectId: this.objectId, object: this.conditionData || this.objectData || undefined }
|
|
887
917
|
await Promise.all(tabs.map(async (tab, index) => {
|
|
888
918
|
const cond = tab && typeof tab === 'object' ? tab.visibleWhen : null
|
|
889
919
|
if (!cond || isLocallyDecidableVisibleWhen(cond)) {
|
|
@@ -911,11 +941,14 @@ export default {
|
|
|
911
941
|
const from = tab && typeof tab === 'object' ? tab.countFrom : null
|
|
912
942
|
let source = null
|
|
913
943
|
if (from === 'widget') {
|
|
914
|
-
// A stack counts its first widget that has a list
|
|
944
|
+
// A stack counts its first widget that has a list: flat
|
|
945
|
+
// (`content.register`) or a dashboard table's `content.source`.
|
|
915
946
|
const lists = this.tabWidgetIds(tab)
|
|
916
947
|
.map((id) => this.availableWidgets.find((w) => w && w.id === id))
|
|
917
|
-
.
|
|
918
|
-
|
|
948
|
+
.map((w) => (w && w.content && typeof w.content === 'object' ? w.content : null))
|
|
949
|
+
.map((content) => (content && content.source && typeof content.source === 'object' && content.source.register ? content.source : content))
|
|
950
|
+
.filter((content) => content && content.register && content.schema)
|
|
951
|
+
source = lists.length ? lists[0] : null
|
|
919
952
|
} else if (from && typeof from === 'object') {
|
|
920
953
|
source = from
|
|
921
954
|
}
|
|
@@ -79,6 +79,13 @@ import { serverErrorMessage } from '../../utils/serverErrorMessage.js'
|
|
|
79
79
|
* the keys of the rows that are in it; the switch is on when the row's key is
|
|
80
80
|
* in the list, or NOT in it with `bind.invert` (a list of hidden items shown
|
|
81
81
|
* as "visible" switches).
|
|
82
|
+
* - `bind.mode: 'each'` — every row IS an object (read by `source`, its key the
|
|
83
|
+
* object id); the switch is that object's own boolean field (`bind.field`)
|
|
84
|
+
* and a toggle PATCHes that object (`bind.register` / `bind.schema`, else the
|
|
85
|
+
* surface's). No current object is needed, so it serves a settings page.
|
|
86
|
+
* - `bind.mode: 'map'` — a config map holds a boolean per row key: read from
|
|
87
|
+
* `bind.url` (at `bind.path`) and written through `save`, or one object field
|
|
88
|
+
* of the current object (`bind.field`) written by its dot-path.
|
|
82
89
|
*
|
|
83
90
|
* A toggle saves at once with ONE PATCH of that field; a refused PATCH puts the
|
|
84
91
|
* switch back and shows the server's words.
|
|
@@ -98,7 +105,7 @@ export default {
|
|
|
98
105
|
/**
|
|
99
106
|
* The widget configuration.
|
|
100
107
|
*
|
|
101
|
-
* - `bind` — `{ mode: 'flag' }` (default)
|
|
108
|
+
* - `bind` — `{ mode: 'flag' }` (default), `{ mode: 'list', field, invert? }`, `{ mode: 'each', field, register?, schema?, invert? }` or `{ mode: 'map', url?, path?, field?, invert? }`.
|
|
102
109
|
* - `rows` — static rows `[{ key, label, hint?, field?, ...extra }]`.
|
|
103
110
|
* - `source` — `{ url, path?, key, label, hint? }`: rows read from an endpoint (tokens `@objectId`, `@object.<field>`), in addition to `rows`.
|
|
104
111
|
* - `columns` — extra columns `[{ header, path, map? }]` read off each row.
|
|
@@ -149,13 +156,33 @@ export default {
|
|
|
149
156
|
notice: '',
|
|
150
157
|
/** Per row, how often a refused toggle forced the switch to be drawn again from the stored value. */
|
|
151
158
|
undone: {},
|
|
159
|
+
/** `each` and `map`: the value written per row key. */
|
|
160
|
+
rowSaved: {},
|
|
161
|
+
/** `map` with `bind.url`: the map as read. */
|
|
162
|
+
map: null,
|
|
152
163
|
}
|
|
153
164
|
},
|
|
154
165
|
|
|
155
166
|
computed: {
|
|
167
|
+
/**
|
|
168
|
+
* How a switch is stored.
|
|
169
|
+
*
|
|
170
|
+
* @return {object} The normalised `bind`.
|
|
171
|
+
* @spec openspec/changes/toggle-table-objects-and-map/specs/toggle-table/spec.md#requirement-a-toggle-table-can-switch-objects-or-a-config-map
|
|
172
|
+
*/
|
|
156
173
|
bind() {
|
|
157
174
|
const bind = this.content.bind || {}
|
|
158
|
-
|
|
175
|
+
const invert = bind.invert === true
|
|
176
|
+
if (bind.mode === 'list') {
|
|
177
|
+
return { mode: 'list', field: bind.field, invert }
|
|
178
|
+
}
|
|
179
|
+
if (bind.mode === 'each') {
|
|
180
|
+
return { mode: 'each', field: bind.field || '', invert, register: bind.register || this.register, schema: bind.schema || this.schema }
|
|
181
|
+
}
|
|
182
|
+
if (bind.mode === 'map') {
|
|
183
|
+
return { mode: 'map', field: bind.field || '', url: bind.url || '', path: bind.path || '', invert }
|
|
184
|
+
}
|
|
185
|
+
return { mode: 'flag' }
|
|
159
186
|
},
|
|
160
187
|
|
|
161
188
|
/** The drawn rows: configured first, then sourced, each with a key, title and hint. */
|
|
@@ -200,12 +227,15 @@ export default {
|
|
|
200
227
|
watch: {
|
|
201
228
|
objectData() {
|
|
202
229
|
this.saved = {}
|
|
230
|
+
this.rowSaved = {}
|
|
203
231
|
this.loadSource()
|
|
232
|
+
this.loadMap()
|
|
204
233
|
},
|
|
205
234
|
},
|
|
206
235
|
|
|
207
236
|
created() {
|
|
208
237
|
this.loadSource()
|
|
238
|
+
this.loadMap()
|
|
209
239
|
},
|
|
210
240
|
|
|
211
241
|
methods: {
|
|
@@ -233,6 +263,10 @@ export default {
|
|
|
233
263
|
* @return {boolean}
|
|
234
264
|
*/
|
|
235
265
|
isOn(row) {
|
|
266
|
+
if (this.bind.mode === 'each' || this.bind.mode === 'map') {
|
|
267
|
+
const on = this.rowValue(row) === true
|
|
268
|
+
return this.bind.invert ? !on : on
|
|
269
|
+
}
|
|
236
270
|
if (this.bind.mode === 'list') {
|
|
237
271
|
const list = this.stored(this.bind.field)
|
|
238
272
|
const inList = Array.isArray(list) && list.map(String).includes(row.key)
|
|
@@ -241,6 +275,92 @@ export default {
|
|
|
241
275
|
return row.field ? this.stored(row.field) === true : false
|
|
242
276
|
},
|
|
243
277
|
|
|
278
|
+
/**
|
|
279
|
+
* `each` and `map`: the stored value of a row's switch, as last written,
|
|
280
|
+
* else read off the row's object or the map.
|
|
281
|
+
*
|
|
282
|
+
* @param {object} row The row.
|
|
283
|
+
* @return {unknown}
|
|
284
|
+
* @spec openspec/changes/toggle-table-objects-and-map/specs/toggle-table/spec.md#requirement-a-toggle-table-can-switch-objects-or-a-config-map
|
|
285
|
+
*/
|
|
286
|
+
rowValue(row) {
|
|
287
|
+
if (Object.hasOwn(this.rowSaved, row.key)) {
|
|
288
|
+
return this.rowSaved[row.key]
|
|
289
|
+
}
|
|
290
|
+
if (this.bind.mode === 'each') {
|
|
291
|
+
return this.bind.field ? readPath(row.item || row, this.bind.field) : undefined
|
|
292
|
+
}
|
|
293
|
+
const map = this.bind.url ? this.map : (this.bind.field ? this.stored(this.bind.field) : null)
|
|
294
|
+
return map && typeof map === 'object' ? map[row.key] : undefined
|
|
295
|
+
},
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Resolve `@objectId` and `@object.<path>` in a url.
|
|
299
|
+
*
|
|
300
|
+
* @param {string} url The url.
|
|
301
|
+
* @return {string}
|
|
302
|
+
*/
|
|
303
|
+
fillUrl(url) {
|
|
304
|
+
const object = this.objectData || {}
|
|
305
|
+
return String(url)
|
|
306
|
+
.replace(/@objectId/g, () => encodeURIComponent(String(this.objectId || object.id || '')))
|
|
307
|
+
.replace(/@object\.([\w.@]+)/g, (m, path) => encodeURIComponent(String(readPath(object, path) ?? '')))
|
|
308
|
+
},
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* `map` with `bind.url`: read the map.
|
|
312
|
+
*
|
|
313
|
+
* @return {Promise<void>}
|
|
314
|
+
* @spec openspec/changes/toggle-table-objects-and-map/specs/toggle-table/spec.md#requirement-a-toggle-table-can-switch-objects-or-a-config-map
|
|
315
|
+
*/
|
|
316
|
+
async loadMap() {
|
|
317
|
+
if (this.bind.mode !== 'map' || !this.bind.url) {
|
|
318
|
+
return
|
|
319
|
+
}
|
|
320
|
+
try {
|
|
321
|
+
const { default: axios } = await import('@nextcloud/axios')
|
|
322
|
+
const { data } = await axios.get(prefixUrl(this.fillUrl(this.bind.url)))
|
|
323
|
+
const map = this.bind.path ? readPath(data, this.bind.path) : data
|
|
324
|
+
this.map = map && typeof map === 'object' ? map : {}
|
|
325
|
+
} catch (e) {
|
|
326
|
+
this.notice = serverErrorMessage(e)
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* `each` and `map`: write one row's switch.
|
|
332
|
+
*
|
|
333
|
+
* @param {object} row The row.
|
|
334
|
+
* @param {boolean} value The value to store.
|
|
335
|
+
* @return {Promise<unknown>} The value the server holds now.
|
|
336
|
+
* @spec openspec/changes/toggle-table-objects-and-map/specs/toggle-table/spec.md#requirement-a-toggle-table-can-switch-objects-or-a-config-map
|
|
337
|
+
*/
|
|
338
|
+
async writeRow(row, value) {
|
|
339
|
+
const save = this.content.save
|
|
340
|
+
const hasSave = Boolean(save && save.url)
|
|
341
|
+
if (this.bind.mode === 'each') {
|
|
342
|
+
const url = objectUrlOf(this.bind.register, this.bind.schema, row.key)
|
|
343
|
+
if ((!url && !hasSave) || !this.bind.field) {
|
|
344
|
+
throw new Error(this.tr('This switch cannot be saved here.'))
|
|
345
|
+
}
|
|
346
|
+
const data = await writeFields(url, { [this.bind.field]: value }, { save, object: row.item || row })
|
|
347
|
+
const answered = hasSave ? undefined : readPath(data, this.bind.field)
|
|
348
|
+
return answered !== undefined ? answered : value
|
|
349
|
+
}
|
|
350
|
+
if (this.bind.url) {
|
|
351
|
+
if (!hasSave) {
|
|
352
|
+
throw new Error(this.tr('This switch cannot be saved here.'))
|
|
353
|
+
}
|
|
354
|
+
await writeFields('', { [row.key]: value }, { save, object: this.objectData })
|
|
355
|
+
return value
|
|
356
|
+
}
|
|
357
|
+
if ((!this.objectUrl && !hasSave) || !this.bind.field) {
|
|
358
|
+
throw new Error(this.tr('This switch cannot be saved here.'))
|
|
359
|
+
}
|
|
360
|
+
await writeFields(this.objectUrl, { [`${this.bind.field}.${row.key}`]: value }, { save, object: this.objectData })
|
|
361
|
+
return value
|
|
362
|
+
},
|
|
363
|
+
|
|
244
364
|
/**
|
|
245
365
|
* The text of an extra column for a row.
|
|
246
366
|
*
|
|
@@ -265,10 +385,7 @@ export default {
|
|
|
265
385
|
if (!source || !source.url || !source.key) {
|
|
266
386
|
return
|
|
267
387
|
}
|
|
268
|
-
const
|
|
269
|
-
const url = String(source.url)
|
|
270
|
-
.replace(/@objectId/g, () => encodeURIComponent(String(this.objectId || object.id || '')))
|
|
271
|
-
.replace(/@object\.([\w.@]+)/g, (m, path) => encodeURIComponent(String(readPath(object, path) ?? '')))
|
|
388
|
+
const url = this.fillUrl(source.url)
|
|
272
389
|
try {
|
|
273
390
|
const { default: axios } = await import('@nextcloud/axios')
|
|
274
391
|
const { data } = await axios.get(prefixUrl(url))
|
|
@@ -277,6 +394,7 @@ export default {
|
|
|
277
394
|
.filter((item) => item && readPath(item, source.key) !== undefined)
|
|
278
395
|
.map((item) => ({
|
|
279
396
|
...item,
|
|
397
|
+
item,
|
|
280
398
|
key: String(readPath(item, source.key)),
|
|
281
399
|
title: String(readPath(item, source.label || source.key) ?? readPath(item, source.key)),
|
|
282
400
|
hint: source.hint ? String(readPath(item, source.hint) ?? '') : '',
|
|
@@ -295,6 +413,21 @@ export default {
|
|
|
295
413
|
* @spec openspec/changes/abstract-toggle-table/specs/toggle-table/spec.md#requirement-a-toggle-saves-one-field
|
|
296
414
|
*/
|
|
297
415
|
async toggle(row, on) {
|
|
416
|
+
if (this.bind.mode === 'each' || this.bind.mode === 'map') {
|
|
417
|
+
this.notice = ''
|
|
418
|
+
this.busy = row.key
|
|
419
|
+
try {
|
|
420
|
+
const written = await this.writeRow(row, this.bind.invert ? !on : on)
|
|
421
|
+
this.rowSaved = { ...this.rowSaved, [row.key]: written }
|
|
422
|
+
this.$emit('saved', { field: this.bind.mode === 'each' ? this.bind.field : row.key, key: row.key, value: written })
|
|
423
|
+
} catch (e) {
|
|
424
|
+
this.notice = serverErrorMessage(e)
|
|
425
|
+
this.undone = { ...this.undone, [row.key]: (this.undone[row.key] || 0) + 1 }
|
|
426
|
+
} finally {
|
|
427
|
+
this.busy = ''
|
|
428
|
+
}
|
|
429
|
+
return
|
|
430
|
+
}
|
|
298
431
|
const field = this.bind.mode === 'list' ? this.bind.field : row.field
|
|
299
432
|
if ((!this.objectUrl && !(this.content.save && this.content.save.url)) || !field) {
|
|
300
433
|
this.notice = this.tr('This switch cannot be saved here.')
|
|
@@ -317,7 +450,7 @@ export default {
|
|
|
317
450
|
this.saved = { ...this.saved, [field]: written }
|
|
318
451
|
/**
|
|
319
452
|
* @event saved A switch was written.
|
|
320
|
-
* @type {{field: string, value: unknown}}
|
|
453
|
+
* @type {{field: string, key?: string, value: unknown}} `key` is the row, in the `each` and `map` modes.
|
|
321
454
|
*/
|
|
322
455
|
this.$emit('saved', { field, value: written })
|
|
323
456
|
} catch (e) {
|