@conduction/nextcloud-vue 2.84.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/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/CnDashboardGrid/CnDashboardGrid.vue.js +1 -1
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js +114 -0
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js +2 -2
- 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 +45 -0
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +22 -12
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +23 -4
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +3 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +18 -5
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +548 -77
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +191 -82
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/useSelfFetchList.js +11 -2
- package/dist/esm/components/CnIndexPage/useSelfFetchList.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue.js +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +95 -0
- 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 +70 -2
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue.js +1 -1
- package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue2.js +67 -5
- package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue2.js.map +1 -1
- package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue3.js +181 -124
- package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue3.js.map +1 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue.js +1 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js +5 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js.map +1 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue3.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue.js +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +134 -10
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +6 -2
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +6 -3
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue.js +1 -1
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js +140 -7
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue.js +1 -1
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js +37 -9
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js.map +1 -1
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js +6 -5
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js.map +1 -1
- package/dist/esm/components/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/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +23 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +41 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/library-style.js +1 -1
- package/dist/esm/nextcloud-vue.css +742 -397
- package/dist/esm/store/useObjectStore.js +16 -2
- package/dist/esm/store/useObjectStore.js.map +1 -1
- package/dist/esm/utils/boardColumns.js +8 -1
- package/dist/esm/utils/boardColumns.js.map +1 -1
- package/dist/esm/utils/boardReferenceColumns.js +278 -0
- package/dist/esm/utils/boardReferenceColumns.js.map +1 -0
- package/dist/esm/utils/buildManifest.js +4 -1
- package/dist/esm/utils/buildManifest.js.map +1 -1
- package/dist/esm/utils/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/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 +15075 -12590
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +742 -397
- package/l10n/en.json +23 -1
- package/l10n/nl.json +41 -1
- package/package.json +1 -1
- package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +160 -8
- package/src/components/CnBoardView/CnBoardView.vue +262 -6
- package/src/components/CnDashboardGrid/CnDashboardGrid.vue +116 -2
- package/src/components/CnDashboardPage/CnDashboardPage.vue +57 -2
- package/src/components/CnDetailPage/CnDetailPage.md +1 -1
- package/src/components/CnDetailPage/CnDetailPage.vue +23 -4
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +3 -1
- package/src/components/CnFactsWidget/CnFactsWidget.vue +18 -5
- package/src/components/CnIndexPage/CnIndexPage.vue +586 -25
- package/src/components/CnIndexPage/useSelfFetchList.js +10 -1
- package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +123 -3
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +70 -2
- package/src/components/CnRegisterMapping/CnRegisterMapping.md +5 -0
- package/src/components/CnRegisterMapping/CnRegisterMapping.vue +231 -96
- package/src/components/CnSettingsPage/CnSettingsPage.vue +5 -1
- package/src/components/CnStatWidget/CnStatWidget.vue +136 -11
- package/src/components/CnTabsWidget/CnTabsWidget.vue +6 -3
- package/src/components/CnToggleTableWidget/CnToggleTableWidget.vue +140 -7
- package/src/components/CnVersionInfoCard/CnVersionInfoCard.md +2 -1
- package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +42 -13
- package/src/components/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/index-page.css +49 -0
- package/src/css/kpi-card.css +13 -0
- package/src/css/look-board.css +96 -7
- package/src/index.js +2 -0
- package/src/schemas/app-manifest-v2.schema.json +115 -5
- package/src/store/useObjectStore.js +16 -2
- package/src/utils/boardColumns.js +8 -1
- package/src/utils/boardReferenceColumns.js +274 -0
- package/src/utils/buildManifest.js +4 -1
- package/src/utils/gridStack.js +39 -0
- package/src/utils/humaniseKey.js +38 -0
- package/src/utils/notificationRuleLabel.js +5 -23
- 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
|
/**
|
|
@@ -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
|
|
@@ -941,11 +941,14 @@ export default {
|
|
|
941
941
|
const from = tab && typeof tab === 'object' ? tab.countFrom : null
|
|
942
942
|
let source = null
|
|
943
943
|
if (from === 'widget') {
|
|
944
|
-
// 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`.
|
|
945
946
|
const lists = this.tabWidgetIds(tab)
|
|
946
947
|
.map((id) => this.availableWidgets.find((w) => w && w.id === id))
|
|
947
|
-
.
|
|
948
|
-
|
|
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
|
|
949
952
|
} else if (from && typeof from === 'object') {
|
|
950
953
|
source = from
|
|
951
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) {
|
|
@@ -87,9 +87,10 @@ With `title`, `description`, `docUrl`, `cardTitle`, `loading`, and `labels`:
|
|
|
87
87
|
| `title` | String | `'Version information'` | Section heading (passed to the wrapping CnSettingsSection) |
|
|
88
88
|
| `description` | String | `'Information about the current application installation'` | Section sub-heading |
|
|
89
89
|
| `docUrl` | String | `''` | Documentation URL — shows an info icon link next to the section title |
|
|
90
|
+
| `wide` | Boolean | `true` | Under the board look the card spans the whole settings grid, as the boards draw it. `false` keeps it one card in the grid. No effect without the board look. |
|
|
90
91
|
| `cardTitle` | String | `'Application information'` | Heading inside the gray version card |
|
|
91
92
|
| `loading` | Boolean | `false` | Show a loading spinner and hide the card content while data is fetching |
|
|
92
|
-
| `labels` | Object | `{
|
|
93
|
+
| `labels` | Object | `{}` | Custom labels for the standard row headings (`appName`, `version`, `configuredVersion`); a label left out keeps the translated default |
|
|
93
94
|
|
|
94
95
|
|
|
95
96
|
`look` is documented in the prop table of the component reference (board look, screens-chrome-parity).
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
:description="description"
|
|
5
5
|
:docUrl="docUrl"
|
|
6
6
|
:loading="loading"
|
|
7
|
-
|
|
7
|
+
:wide="isBoard && wide"
|
|
8
|
+
:loadingMessage="t('nextcloud-vue', 'Loading version information…')">
|
|
8
9
|
<!-- Actions slot (top right). Under the board look the same buttons sit
|
|
9
10
|
in the footer row of the card instead. -->
|
|
10
11
|
<template v-if="!isBoard" #actions>
|
|
@@ -34,19 +35,19 @@
|
|
|
34
35
|
<div class="cn-version-info__details">
|
|
35
36
|
<!-- Application Name -->
|
|
36
37
|
<div class="cn-version-info__item">
|
|
37
|
-
<span class="cn-version-info__label">{{
|
|
38
|
+
<span class="cn-version-info__label">{{ fieldLabels.appName }}:</span>
|
|
38
39
|
<span class="cn-version-info__value">{{ appName }}</span>
|
|
39
40
|
</div>
|
|
40
41
|
|
|
41
42
|
<!-- Version -->
|
|
42
43
|
<div class="cn-version-info__item">
|
|
43
|
-
<span class="cn-version-info__label">{{
|
|
44
|
+
<span class="cn-version-info__label">{{ fieldLabels.version }}:</span>
|
|
44
45
|
<span class="cn-version-info__value">{{ appVersion }}</span>
|
|
45
46
|
</div>
|
|
46
47
|
|
|
47
48
|
<!-- Configured Version (if provided) -->
|
|
48
49
|
<div v-if="configuredVersion" class="cn-version-info__item">
|
|
49
|
-
<span class="cn-version-info__label">{{
|
|
50
|
+
<span class="cn-version-info__label">{{ fieldLabels.configuredVersion }}:</span>
|
|
50
51
|
<span class="cn-version-info__value">{{ configuredVersion }}</span>
|
|
51
52
|
</div>
|
|
52
53
|
|
|
@@ -157,6 +158,18 @@ export default {
|
|
|
157
158
|
default: '',
|
|
158
159
|
},
|
|
159
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Under the board look the card spans the whole settings grid, as the
|
|
163
|
+
* boards draw the version card. Set to `false` to keep it one card in
|
|
164
|
+
* the grid. Without the board look it has no effect.
|
|
165
|
+
*
|
|
166
|
+
* @spec openspec/changes/board-version-card-full-width/specs/settings-board-look/spec.md#requirement-the-board-version-card-spans-the-settings-grid
|
|
167
|
+
*/
|
|
168
|
+
wide: {
|
|
169
|
+
type: Boolean,
|
|
170
|
+
default: true,
|
|
171
|
+
},
|
|
172
|
+
|
|
160
173
|
/** Section title */
|
|
161
174
|
title: {
|
|
162
175
|
type: String,
|
|
@@ -232,14 +245,13 @@ export default {
|
|
|
232
245
|
default: false,
|
|
233
246
|
},
|
|
234
247
|
|
|
235
|
-
/**
|
|
248
|
+
/**
|
|
249
|
+
* Custom labels for the standard fields, `{ appName?, version?, configuredVersion? }`.
|
|
250
|
+
* A label left out keeps the translated default.
|
|
251
|
+
*/
|
|
236
252
|
labels: {
|
|
237
253
|
type: Object,
|
|
238
|
-
default: () => ({
|
|
239
|
-
appName: 'Application Name',
|
|
240
|
-
version: 'Version',
|
|
241
|
-
configuredVersion: 'Configured Version',
|
|
242
|
-
}),
|
|
254
|
+
default: () => ({}),
|
|
243
255
|
},
|
|
244
256
|
},
|
|
245
257
|
|
|
@@ -251,6 +263,21 @@ export default {
|
|
|
251
263
|
},
|
|
252
264
|
|
|
253
265
|
computed: {
|
|
266
|
+
/**
|
|
267
|
+
* The field labels: the app's, else the library's in the reader's language.
|
|
268
|
+
*
|
|
269
|
+
* @return {{appName: string, version: string, configuredVersion: string}}
|
|
270
|
+
* @spec openspec/changes/version-card-dutch-and-header/specs/settings-board-look/spec.md#requirement-the-admin-chrome-reads-in-the-readers-language
|
|
271
|
+
*/
|
|
272
|
+
fieldLabels() {
|
|
273
|
+
return {
|
|
274
|
+
appName: t('nextcloud-vue', 'Application name'),
|
|
275
|
+
version: t('nextcloud-vue', 'Version'),
|
|
276
|
+
configuredVersion: t('nextcloud-vue', 'Configured version'),
|
|
277
|
+
...(this.labels || {}),
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
|
|
254
281
|
/** @return {string} Button type based on update status */
|
|
255
282
|
updateButtonType() {
|
|
256
283
|
if (this.isUpToDate) {
|
|
@@ -267,16 +294,18 @@ export default {
|
|
|
267
294
|
/** @return {string} Update button label text */
|
|
268
295
|
updateButtonText() {
|
|
269
296
|
if (this.updating) {
|
|
270
|
-
return 'Updating
|
|
297
|
+
return t('nextcloud-vue', 'Updating…')
|
|
271
298
|
}
|
|
272
299
|
if (this.isUpToDate) {
|
|
273
|
-
return 'Up to date'
|
|
300
|
+
return t('nextcloud-vue', 'Up to date')
|
|
274
301
|
}
|
|
275
|
-
return 'Update'
|
|
302
|
+
return t('nextcloud-vue', 'Update')
|
|
276
303
|
},
|
|
277
304
|
},
|
|
278
305
|
|
|
279
306
|
methods: {
|
|
307
|
+
t,
|
|
308
|
+
|
|
280
309
|
handleUpdateClick() {
|
|
281
310
|
if (!this.updateButtonDisabled) {
|
|
282
311
|
this.$emit('update')
|
|
@@ -89,7 +89,8 @@ const DEFAULT_LIMIT = 100
|
|
|
89
89
|
*
|
|
90
90
|
* It renders the five working days by default (`days: 7` adds the weekend),
|
|
91
91
|
* highlights today, marks items that are late, and says so when a day has
|
|
92
|
-
* nothing. On a narrow screen the strip scrolls sideways
|
|
92
|
+
* nothing. On a narrow screen the strip scrolls sideways; under the board
|
|
93
|
+
* look (`.cn-look-board`) the days wrap into rows instead.
|
|
93
94
|
*
|
|
94
95
|
* Items come from an OpenRegister source (one request for the whole week) or
|
|
95
96
|
* from static `items`. Resolved by its registry type key `week-strip`.
|