@conduction/nextcloud-vue 2.56.0-beta.47 → 2.56.0-beta.49
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/CnActionsMenu/CnActionsMenu.vue2.js +1 -1
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js +2 -1
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js +28 -8
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +37 -2
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +46 -28
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +17 -10
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnBrandBar/CnBrandBar.vue2.js.map +1 -1
- package/dist/esm/components/CnBrandBar/CnBrandBar.vue3.js.map +1 -1
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue.js +1 -1
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue2.js +139 -3
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue3.js +60 -12
- package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +91 -36
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +115 -72
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +32 -3
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +7 -7
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +56 -16
- 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/CnDeviceFramesWidget/CnDeviceFramesWidget.vue.js +9 -0
- package/dist/esm/components/CnDeviceFramesWidget/CnDeviceFramesWidget.vue.js.map +1 -0
- package/dist/esm/components/CnDeviceFramesWidget/CnDeviceFramesWidget.vue2.js +240 -0
- package/dist/esm/components/CnDeviceFramesWidget/CnDeviceFramesWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnDeviceFramesWidget/CnDeviceFramesWidget.vue3.js +65 -0
- package/dist/esm/components/CnDeviceFramesWidget/CnDeviceFramesWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnDeviceFramesWidget/index.js +30 -0
- package/dist/esm/components/CnDeviceFramesWidget/index.js.map +1 -0
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +17 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js +30 -31
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnHeaderSearchLabel/CnHeaderSearchLabel.vue.js +6 -0
- package/dist/esm/components/CnHeaderSearchLabel/CnHeaderSearchLabel.vue.js.map +1 -0
- package/dist/esm/components/CnHeaderSearchLabel/CnHeaderSearchLabel.vue2.js +90 -0
- package/dist/esm/components/CnHeaderSearchLabel/CnHeaderSearchLabel.vue2.js.map +1 -0
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +141 -7
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +5 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue.js +8 -0
- package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue.js.map +1 -0
- package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue2.js +114 -0
- package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue2.js.map +1 -0
- package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue3.js +45 -0
- package/dist/esm/components/CnPageHeader/CnHeaderTrail.vue3.js.map +1 -0
- package/dist/esm/components/CnPageHeader/CnPageHeader.vue2.js +38 -2
- package/dist/esm/components/CnPageHeader/CnPageHeader.vue2.js.map +1 -1
- package/dist/esm/components/CnPageHeader/CnPageHeader.vue3.js +12 -2
- package/dist/esm/components/CnPageHeader/CnPageHeader.vue3.js.map +1 -1
- package/dist/esm/components/CnPagination/CnPagination.vue2.js.map +1 -1
- package/dist/esm/components/CnPagination/CnPagination.vue3.js +56 -51
- package/dist/esm/components/CnPagination/CnPagination.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +51 -4
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +20 -2
- 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 +230 -25
- package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue2.js.map +1 -1
- package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue3.js +447 -221
- 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 +24 -0
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js.map +1 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue3.js +3 -1
- package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue3.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue.js +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue2.js +39 -2
- package/dist/esm/components/CnTabs/CnTabs.vue2.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue3.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +85 -8
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +37 -9
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue.js +1 -1
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js.map +1 -1
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js +4 -3
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue.js +1 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js +20 -0
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js.map +1 -1
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js +31 -20
- package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js.map +1 -1
- package/dist/esm/composables/indexSources.js +9 -0
- package/dist/esm/composables/indexSources.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 +18 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +18 -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 +712 -330
- package/dist/esm/utils/dueText.js +49 -8
- package/dist/esm/utils/dueText.js.map +1 -1
- package/dist/esm/utils/libraryWidgetKeys.js +1 -0
- package/dist/esm/utils/libraryWidgetKeys.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/nextcloud-vue.cjs.js +8275 -6302
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +712 -330
- package/l10n/en.json +18 -1
- package/l10n/nl.json +18 -1
- package/package.json +1 -1
- package/src/components/CnActionsMenu/CnActionsMenu.vue +3 -1
- package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +39 -1
- package/src/components/CnAppRoot/CnAppRoot.vue +20 -0
- package/src/components/CnBrandBar/CnBrandBar.vue +5 -4
- package/src/components/CnCountdownWidget/CnCountdownWidget.vue +222 -6
- package/src/components/CnDashboardPage/CnDashboardPage.vue +96 -2
- package/src/components/CnDataTable/CnDataTable.md +1 -0
- package/src/components/CnDataTable/CnDataTable.vue +39 -10
- package/src/components/CnDetailPage/CnDetailPage.vue +55 -15
- package/src/components/CnDeviceFramesWidget/CnDeviceFramesWidget.vue +360 -0
- package/src/components/CnDeviceFramesWidget/index.js +29 -0
- package/src/components/CnFactsWidget/CnFactsWidget.vue +46 -2
- package/src/components/CnHeaderSearchLabel/CnHeaderSearchLabel.vue +107 -0
- package/src/components/CnIndexPage/CnIndexPage.vue +144 -7
- package/src/components/CnPageHeader/CnHeaderTrail.vue +140 -0
- package/src/components/CnPageHeader/CnPageHeader.md +11 -0
- package/src/components/CnPageHeader/CnPageHeader.vue +42 -0
- package/src/components/CnPagination/CnPagination.vue +10 -2
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +84 -4
- package/src/components/CnRegisterMapping/CnRegisterMapping.md +10 -0
- package/src/components/CnRegisterMapping/CnRegisterMapping.vue +511 -28
- package/src/components/CnSettingsPage/CnSettingsPage.vue +26 -0
- package/src/components/CnTabs/CnTabs.vue +39 -2
- package/src/components/CnTabsWidget/CnTabsWidget.vue +106 -13
- package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +4 -3
- package/src/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
- package/src/components/CnWidgetWrapper/CnWidgetWrapper.md +8 -0
- package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +31 -2
- package/src/components/index.js +1 -0
- package/src/composables/indexSources.js +9 -0
- package/src/css/dialog.css +14 -0
- package/src/css/look-board-index.css +27 -1
- package/src/css/look-board.css +27 -0
- package/src/css/page-header.css +16 -0
- package/src/index.js +1 -0
- package/src/schemas/app-manifest-v2.schema.json +27 -15
- package/src/utils/dueText.js +49 -8
- package/src/utils/libraryWidgetKeys.js +1 -0
- package/src/utils/validateManifestV2.compiled.js +3 -3
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
:title="title"
|
|
22
22
|
:description="headerDescription"
|
|
23
23
|
:icon="showTitleIcon && !isBoardLook ? resolvedIcon : ''"
|
|
24
|
+
:breadcrumb="breadcrumb"
|
|
25
|
+
:meta="headerMetaText"
|
|
24
26
|
:visuallyHidden="!showTitle">
|
|
25
27
|
<!-- The board's header buttons (`headerButtons`), beside the
|
|
26
28
|
title. Declaring them takes the Views and Actions menus
|
|
@@ -734,6 +736,7 @@
|
|
|
734
736
|
:columnOverrides="columnOverrides"
|
|
735
737
|
:rowClass="rowClass"
|
|
736
738
|
:rowTitle="rowTitle"
|
|
739
|
+
:showUnread="showUnreadRows"
|
|
737
740
|
:filterable="tableHeaderFilters"
|
|
738
741
|
:activeFilters="effectiveActiveFilters"
|
|
739
742
|
:filterRegister="typeof register === 'string' ? register : ''"
|
|
@@ -1609,6 +1612,31 @@ export default {
|
|
|
1609
1612
|
required: true,
|
|
1610
1613
|
},
|
|
1611
1614
|
|
|
1615
|
+
/**
|
|
1616
|
+
* A breadcrumb under the title (manifest `config.breadcrumb`): one
|
|
1617
|
+
* crumb `{ label, route?, params?, href?, icon?, separator? }` or a
|
|
1618
|
+
* list; the title follows as the current crumb. Drawn as the detail
|
|
1619
|
+
* header's breadcrumb row. Null (the default) draws none.
|
|
1620
|
+
*
|
|
1621
|
+
* @type {object|Array<object>|null}
|
|
1622
|
+
* @spec openspec/changes/page-header-breadcrumb/specs/page-header/spec.md#requirement-a-list-dashboard-or-settings-page-can-show-a-breadcrumb-and-a-meta-note
|
|
1623
|
+
*/
|
|
1624
|
+
breadcrumb: {
|
|
1625
|
+
type: [Object, Array],
|
|
1626
|
+
default: null,
|
|
1627
|
+
},
|
|
1628
|
+
|
|
1629
|
+
/**
|
|
1630
|
+
* A note after the breadcrumb and a middle dot (manifest `config.headerMeta`),
|
|
1631
|
+
* such as "{total} open": `{total}` and `{shown}` are the list's counts.
|
|
1632
|
+
*
|
|
1633
|
+
* @spec openspec/changes/page-header-breadcrumb/specs/page-header/spec.md#requirement-a-list-dashboard-or-settings-page-can-show-a-breadcrumb-and-a-meta-note
|
|
1634
|
+
*/
|
|
1635
|
+
headerMeta: {
|
|
1636
|
+
type: String,
|
|
1637
|
+
default: '',
|
|
1638
|
+
},
|
|
1639
|
+
|
|
1612
1640
|
/** Optional description shown below the title */
|
|
1613
1641
|
description: {
|
|
1614
1642
|
type: String,
|
|
@@ -2232,7 +2260,7 @@ export default {
|
|
|
2232
2260
|
* can open is an ordinary segment; one it cannot (no board columns, no
|
|
2233
2261
|
* map configured) is drawn disabled, named "<mode>: not available on
|
|
2234
2262
|
* this list". Fed from `pages[].config.viewSwitch`. Ignored without the
|
|
2235
|
-
* board look
|
|
2263
|
+
* board look; absent, the board look draws all four.
|
|
2236
2264
|
*
|
|
2237
2265
|
* @type {Array<'table' | 'cards' | 'board' | 'map'>}
|
|
2238
2266
|
* @spec openspec/changes/screens-view-switch-parity/specs/view-switch-board-look/spec.md#requirement-the-manifest-can-draw-segments-the-page-cannot-open
|
|
@@ -3555,6 +3583,8 @@ export default {
|
|
|
3555
3583
|
data() {
|
|
3556
3584
|
return {
|
|
3557
3585
|
currentViewMode: this.viewMode,
|
|
3586
|
+
/** Whether `?_view=` names a mode the page did not offer yet, to open once it does. */
|
|
3587
|
+
routeViewPending: false,
|
|
3558
3588
|
/** The column field the reader picked in the board's column switch; '' = the default. */
|
|
3559
3589
|
boardColumnFieldChoice: '',
|
|
3560
3590
|
/** The rows a reference column field is built from, by field. */
|
|
@@ -4118,11 +4148,14 @@ export default {
|
|
|
4118
4148
|
* @spec openspec/changes/screens-view-switch-parity/specs/view-switch-board-look/spec.md#requirement-the-manifest-can-draw-segments-the-page-cannot-open
|
|
4119
4149
|
*/
|
|
4120
4150
|
viewSwitchDisabledModes() {
|
|
4121
|
-
if (!this.isBoardLook
|
|
4151
|
+
if (!this.isBoardLook) {
|
|
4122
4152
|
return []
|
|
4123
4153
|
}
|
|
4154
|
+
// The index boards draw all four segments, so the board look does
|
|
4155
|
+
// too unless the manifest names its own list.
|
|
4156
|
+
const listed = Array.isArray(this.viewSwitch) ? this.viewSwitch : ['table', 'cards', 'board', 'map']
|
|
4124
4157
|
const offered = [...this.effectiveToggleModes, ...(this.showMapSegment ? ['map'] : [])]
|
|
4125
|
-
return
|
|
4158
|
+
return listed.filter((mode) => ['table', 'cards', 'board', 'map'].includes(mode) && !offered.includes(mode))
|
|
4126
4159
|
},
|
|
4127
4160
|
|
|
4128
4161
|
/**
|
|
@@ -4160,6 +4193,17 @@ export default {
|
|
|
4160
4193
|
})
|
|
4161
4194
|
},
|
|
4162
4195
|
|
|
4196
|
+
/**
|
|
4197
|
+
* The modes `?_view=` can open: the toggle's modes plus the map when
|
|
4198
|
+
* the page offers it.
|
|
4199
|
+
*
|
|
4200
|
+
* @return {Array<string>} The modes.
|
|
4201
|
+
* @spec openspec/changes/screens-index-board-gaps/specs/index-list-board-look/spec.md#requirement-the-address-opens-a-view-mode-that-arrives-later
|
|
4202
|
+
*/
|
|
4203
|
+
routeViewModes() {
|
|
4204
|
+
return [...this.effectiveToggleModes, ...(this.showMapSegment ? ['map'] : [])]
|
|
4205
|
+
},
|
|
4206
|
+
|
|
4163
4207
|
/**
|
|
4164
4208
|
* The column fields the board offers (`board.columnFields`), for the
|
|
4165
4209
|
* column switch (screens-board-reference-columns).
|
|
@@ -5380,6 +5424,21 @@ export default {
|
|
|
5380
5424
|
return this.isSelfFetchMode ? this.list.pagination.value : this.pagination
|
|
5381
5425
|
},
|
|
5382
5426
|
|
|
5427
|
+
/**
|
|
5428
|
+
* Whether the table marks unread rows. The boards draw every row alike,
|
|
5429
|
+
* so under the board look only a page that offers the Unread lens
|
|
5430
|
+
* (`personalLenses`) marks them; the Nextcloud look always does.
|
|
5431
|
+
*
|
|
5432
|
+
* @spec openspec/changes/screens-index-board-gaps/specs/index-list-board-look/spec.md#requirement-unread-rows-are-marked-only-where-the-reader-asked-for-them
|
|
5433
|
+
* @return {boolean} True when unread rows read in bold with the dot.
|
|
5434
|
+
*/
|
|
5435
|
+
showUnreadRows() {
|
|
5436
|
+
if (!this.isBoardLook) {
|
|
5437
|
+
return true
|
|
5438
|
+
}
|
|
5439
|
+
return Array.isArray(this.personalLenses) && this.personalLenses.includes('unread')
|
|
5440
|
+
},
|
|
5441
|
+
|
|
5383
5442
|
/** Resolved schema OBJECT (for column generation / icons / labels). */
|
|
5384
5443
|
/**
|
|
5385
5444
|
* Whether the table shows its header filters. A self-fetching page
|
|
@@ -6002,8 +6061,9 @@ export default {
|
|
|
6002
6061
|
}
|
|
6003
6062
|
// The board's header: Export reads "Download" with its icon and
|
|
6004
6063
|
// the Actions menu reads "Actions", without the manifest saying so.
|
|
6005
|
-
|
|
6006
|
-
|
|
6064
|
+
// A labelled Export keeps the icon too, unless it names its own.
|
|
6065
|
+
if (this.isBoardLook && button.action === 'export') {
|
|
6066
|
+
label = label || t('nextcloud-vue', 'Download')
|
|
6007
6067
|
icon = icon || 'Download'
|
|
6008
6068
|
}
|
|
6009
6069
|
if (label === '' && button.action === 'actions-menu') {
|
|
@@ -6286,6 +6346,22 @@ export default {
|
|
|
6286
6346
|
* @spec openspec/changes/screens-table-footer-and-system-dates-parity/specs/index-list-board-look/spec.md#requirement-the-count-reads-in-the-users-language
|
|
6287
6347
|
* @return {string}
|
|
6288
6348
|
*/
|
|
6349
|
+
/**
|
|
6350
|
+
* The header meta note with the list's counts filled in.
|
|
6351
|
+
*
|
|
6352
|
+
* @spec openspec/changes/page-header-breadcrumb/specs/page-header/spec.md#requirement-a-list-dashboard-or-settings-page-can-show-a-breadcrumb-and-a-meta-note
|
|
6353
|
+
* @return {string}
|
|
6354
|
+
*/
|
|
6355
|
+
headerMetaText() {
|
|
6356
|
+
if (!this.headerMeta) {
|
|
6357
|
+
return ''
|
|
6358
|
+
}
|
|
6359
|
+
const total = this.effectivePagination?.total
|
|
6360
|
+
return this.cnTranslate(this.headerMeta)
|
|
6361
|
+
.replace('{shown}', String(this.effectiveObjects.length))
|
|
6362
|
+
.replace('{total}', typeof total === 'number' ? String(total) : '')
|
|
6363
|
+
},
|
|
6364
|
+
|
|
6289
6365
|
headerDescription() {
|
|
6290
6366
|
const total = this.effectivePagination?.total
|
|
6291
6367
|
// Board look: the count line is a template over the rows on this page
|
|
@@ -6476,6 +6552,12 @@ export default {
|
|
|
6476
6552
|
this.applyViewModeFromRoute()
|
|
6477
6553
|
},
|
|
6478
6554
|
|
|
6555
|
+
routeViewModes() {
|
|
6556
|
+
if (this.routeViewPending) {
|
|
6557
|
+
this.applyViewModeFromRoute()
|
|
6558
|
+
}
|
|
6559
|
+
},
|
|
6560
|
+
|
|
6479
6561
|
boardFetchKey: {
|
|
6480
6562
|
immediate: true,
|
|
6481
6563
|
handler(key) {
|
|
@@ -6908,6 +6990,10 @@ export default {
|
|
|
6908
6990
|
*/
|
|
6909
6991
|
async loadFilterCounts() {
|
|
6910
6992
|
const requestKey = this.countRequestKey
|
|
6993
|
+
if (this.isNamedSource) {
|
|
6994
|
+
await this.loadNamedSourceCounts(requestKey)
|
|
6995
|
+
return
|
|
6996
|
+
}
|
|
6911
6997
|
const schema = this.exportSchemaSlug
|
|
6912
6998
|
if (requestKey === '' || !this.register || !schema) {
|
|
6913
6999
|
this.tabCounts = null
|
|
@@ -6939,6 +7025,46 @@ export default {
|
|
|
6939
7025
|
this.savedViewCounts = viewCounts
|
|
6940
7026
|
},
|
|
6941
7027
|
|
|
7028
|
+
/**
|
|
7029
|
+
* The quick-filter counts of a named source (`entitySource`): each tab
|
|
7030
|
+
* that asks is counted by the source's own `count(config)`, with the
|
|
7031
|
+
* tab's filter over `sourceConfig`. A source without `count` shows
|
|
7032
|
+
* none; a failed count leaves that tab without a number.
|
|
7033
|
+
*
|
|
7034
|
+
* @param {string} requestKey The `countRequestKey` this load answers.
|
|
7035
|
+
* @return {Promise<void>}
|
|
7036
|
+
* @spec openspec/changes/screens-index-board-gaps/specs/index-list-board-look/spec.md#requirement-a-named-source-counts-its-quick-filters
|
|
7037
|
+
*/
|
|
7038
|
+
async loadNamedSourceCounts(requestKey) {
|
|
7039
|
+
const count = this.namedSource ? this.namedSource.count : null
|
|
7040
|
+
if (requestKey === '' || typeof count !== 'function') {
|
|
7041
|
+
this.tabCounts = null
|
|
7042
|
+
this.savedViewCounts = null
|
|
7043
|
+
return
|
|
7044
|
+
}
|
|
7045
|
+
const params = (this.$route && this.$route.params) || {}
|
|
7046
|
+
const ctx = typeof this.selfFetchTokenCtx === 'function' ? this.selfFetchTokenCtx() : {}
|
|
7047
|
+
const tabCounts = {}
|
|
7048
|
+
await Promise.all(this.countEntries
|
|
7049
|
+
.filter((entry) => entry.key.startsWith('tab:'))
|
|
7050
|
+
.map(async (entry) => {
|
|
7051
|
+
const config = resolveFilterMap({ ...(this.sourceConfig || {}), ...(entry.filter || {}) }, params, ctx)
|
|
7052
|
+
try {
|
|
7053
|
+
const total = await count(config)
|
|
7054
|
+
if (Number.isFinite(total)) {
|
|
7055
|
+
tabCounts[Number(entry.key.slice(4))] = total
|
|
7056
|
+
}
|
|
7057
|
+
} catch {
|
|
7058
|
+
// No number for this tab.
|
|
7059
|
+
}
|
|
7060
|
+
}))
|
|
7061
|
+
if (requestKey !== this.countRequestKey) {
|
|
7062
|
+
return
|
|
7063
|
+
}
|
|
7064
|
+
this.tabCounts = tabCounts
|
|
7065
|
+
this.savedViewCounts = null
|
|
7066
|
+
},
|
|
7067
|
+
|
|
6942
7068
|
// ── Split view (case-page-and-list-as-a-place) ──────────────────
|
|
6943
7069
|
|
|
6944
7070
|
/**
|
|
@@ -9203,6 +9329,7 @@ export default {
|
|
|
9203
9329
|
* @param {string} mode 'table' or 'cards'
|
|
9204
9330
|
*/
|
|
9205
9331
|
onViewModeChange(mode) {
|
|
9332
|
+
this.routeViewPending = false
|
|
9206
9333
|
this.currentViewMode = mode
|
|
9207
9334
|
this.$emit('view-mode-change', mode)
|
|
9208
9335
|
},
|
|
@@ -9212,16 +9339,26 @@ export default {
|
|
|
9212
9339
|
* offers it. The key starts with `_`, like `_search` and `_page`, so it
|
|
9213
9340
|
* is never read as a list filter. A menu entry's `query` can so open a
|
|
9214
9341
|
* list as its board, on the one page that lists the schema
|
|
9215
|
-
* (screens-board-reference-columns).
|
|
9342
|
+
* (screens-board-reference-columns). A mode the page offers only later
|
|
9343
|
+
* (the map, once rows with a geometry are in) opens when it arrives,
|
|
9344
|
+
* unless the reader picked a view first.
|
|
9216
9345
|
*
|
|
9217
9346
|
* @return {void}
|
|
9218
9347
|
* @spec openspec/changes/screens-board-reference-columns/specs/board-view/spec.md#requirement-the-address-can-open-a-list-in-a-view-mode
|
|
9348
|
+
* @spec openspec/changes/screens-index-board-gaps/specs/index-list-board-look/spec.md#requirement-the-address-opens-a-view-mode-that-arrives-later
|
|
9219
9349
|
*/
|
|
9220
9350
|
applyViewModeFromRoute() {
|
|
9221
9351
|
const wanted = this.$route?.query?._view
|
|
9222
|
-
if (typeof wanted
|
|
9352
|
+
if (typeof wanted !== 'string' || wanted === '' || wanted === this.currentViewMode) {
|
|
9353
|
+
this.routeViewPending = false
|
|
9354
|
+
return
|
|
9355
|
+
}
|
|
9356
|
+
if (this.routeViewModes.includes(wanted)) {
|
|
9223
9357
|
this.currentViewMode = wanted
|
|
9358
|
+
this.routeViewPending = false
|
|
9359
|
+
return
|
|
9224
9360
|
}
|
|
9361
|
+
this.routeViewPending = true
|
|
9225
9362
|
},
|
|
9226
9363
|
|
|
9227
9364
|
/**
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
-->
|
|
5
|
+
<template>
|
|
6
|
+
<div
|
|
7
|
+
v-if="crumbs.length > 0 || meta"
|
|
8
|
+
class="cn-header-trail cn-detail-page__header-row2"
|
|
9
|
+
data-testid="cn-header-trail">
|
|
10
|
+
<CnBreadcrumbs
|
|
11
|
+
v-if="crumbs.length > 0"
|
|
12
|
+
class="cn-detail-page__breadcrumbs cn-detail-page__breadcrumbs--row2"
|
|
13
|
+
:crumbs="crumbs"
|
|
14
|
+
:rootText="crumbs[0].icon === undefined"
|
|
15
|
+
:separator="separator"
|
|
16
|
+
data-testid="cn-header-trail-breadcrumbs" />
|
|
17
|
+
<span
|
|
18
|
+
v-if="crumbs.length > 0 && meta"
|
|
19
|
+
class="cn-detail-page__header-dot"
|
|
20
|
+
aria-hidden="true">·</span>
|
|
21
|
+
<span
|
|
22
|
+
v-if="meta"
|
|
23
|
+
class="cn-detail-page__header-meta"
|
|
24
|
+
data-testid="cn-header-trail-meta">{{ meta }}</span>
|
|
25
|
+
</div>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<script>
|
|
29
|
+
import { CnBreadcrumbs } from '../CnBreadcrumbs/index.js'
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The breadcrumb and meta line under a page title: "Instellingen / Zaaktypen
|
|
33
|
+
* · 12 zaaktypen". It draws the same markup and classes as the board look's
|
|
34
|
+
* detail header row, so a list, dashboard or settings page carries the
|
|
35
|
+
* detail page's breadcrumb style rather than one of its own.
|
|
36
|
+
*
|
|
37
|
+
* `breadcrumb` is one crumb or a list, each `{ label, route?, params?, href?,
|
|
38
|
+
* icon?, separator? }`. The page title follows as the current crumb.
|
|
39
|
+
*/
|
|
40
|
+
export default {
|
|
41
|
+
name: 'CnHeaderTrail',
|
|
42
|
+
|
|
43
|
+
components: { CnBreadcrumbs },
|
|
44
|
+
|
|
45
|
+
inject: {
|
|
46
|
+
cnTranslate: { default: () => (key) => key },
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
props: {
|
|
50
|
+
/**
|
|
51
|
+
* The crumbs before the current page, one object or a list.
|
|
52
|
+
*
|
|
53
|
+
* @type {object|Array<object>|null}
|
|
54
|
+
* @spec openspec/changes/page-header-breadcrumb/specs/page-header/spec.md#requirement-a-list-dashboard-or-settings-page-can-show-a-breadcrumb-and-a-meta-note
|
|
55
|
+
*/
|
|
56
|
+
breadcrumb: {
|
|
57
|
+
type: [Object, Array],
|
|
58
|
+
default: null,
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
/** The current crumb: the page title, already translated. */
|
|
62
|
+
current: {
|
|
63
|
+
type: String,
|
|
64
|
+
default: '',
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The note after the crumbs and a middle dot, already resolved.
|
|
69
|
+
*
|
|
70
|
+
* @spec openspec/changes/page-header-breadcrumb/specs/page-header/spec.md#requirement-a-list-dashboard-or-settings-page-can-show-a-breadcrumb-and-a-meta-note
|
|
71
|
+
*/
|
|
72
|
+
meta: {
|
|
73
|
+
type: String,
|
|
74
|
+
default: '',
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Translate function for the crumb labels; falls back to the injected
|
|
79
|
+
* `cnTranslate`.
|
|
80
|
+
*
|
|
81
|
+
* @type {((key: string) => string)|null}
|
|
82
|
+
*/
|
|
83
|
+
translate: {
|
|
84
|
+
type: Function,
|
|
85
|
+
default: null,
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
computed: {
|
|
90
|
+
/**
|
|
91
|
+
* The declared crumbs that carry a label.
|
|
92
|
+
*
|
|
93
|
+
* @return {Array<object>}
|
|
94
|
+
*/
|
|
95
|
+
declared() {
|
|
96
|
+
const list = Array.isArray(this.breadcrumb) ? this.breadcrumb : (this.breadcrumb ? [this.breadcrumb] : [])
|
|
97
|
+
return list.filter((crumb) => crumb && typeof crumb.label === 'string' && crumb.label !== '')
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* The crumbs for CnBreadcrumbs: each declared crumb as a link, then the
|
|
102
|
+
* current page. Empty without a declared crumb.
|
|
103
|
+
*
|
|
104
|
+
* @spec openspec/changes/page-header-breadcrumb/specs/page-header/spec.md#requirement-a-list-dashboard-or-settings-page-can-show-a-breadcrumb-and-a-meta-note
|
|
105
|
+
* @return {Array<object>}
|
|
106
|
+
*/
|
|
107
|
+
crumbs() {
|
|
108
|
+
if (this.declared.length === 0) {
|
|
109
|
+
return []
|
|
110
|
+
}
|
|
111
|
+
const out = this.declared.map((crumb) => {
|
|
112
|
+
const entry = { label: (this.translate ?? this.cnTranslate)(crumb.label) }
|
|
113
|
+
if (typeof crumb.icon === 'string' && crumb.icon !== '') {
|
|
114
|
+
entry.icon = crumb.icon
|
|
115
|
+
}
|
|
116
|
+
if (crumb.route) {
|
|
117
|
+
entry.to = { name: crumb.route, ...(crumb.params ? { params: crumb.params } : {}) }
|
|
118
|
+
} else if (crumb.href) {
|
|
119
|
+
entry.href = crumb.href
|
|
120
|
+
}
|
|
121
|
+
return entry
|
|
122
|
+
})
|
|
123
|
+
if (this.current) {
|
|
124
|
+
out.push({ label: this.current })
|
|
125
|
+
}
|
|
126
|
+
return out
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* The text between the crumbs: the first crumb's `separator`, else `/`.
|
|
131
|
+
*
|
|
132
|
+
* @return {string}
|
|
133
|
+
*/
|
|
134
|
+
separator() {
|
|
135
|
+
const declared = this.declared[0] && this.declared[0].separator
|
|
136
|
+
return typeof declared === 'string' && declared !== '' ? declared : '/'
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
}
|
|
140
|
+
</script>
|
|
@@ -81,3 +81,14 @@ With extra content (e.g., a badge or status indicator):
|
|
|
81
81
|
</template>
|
|
82
82
|
</CnPageHeader>
|
|
83
83
|
```
|
|
84
|
+
|
|
85
|
+
Breadcrumb and meta: `breadcrumb` (one crumb `{ label, route?, params?, href?, icon?, separator? }` or a list) draws a
|
|
86
|
+
line under the title in the detail header's breadcrumb style, with the title as the current crumb; `meta` follows after a
|
|
87
|
+
middle dot:
|
|
88
|
+
|
|
89
|
+
```vue
|
|
90
|
+
<CnPageHeader
|
|
91
|
+
title="Zaaktypen"
|
|
92
|
+
:breadcrumb="{ label: 'Instellingen', href: '#instellingen' }"
|
|
93
|
+
meta="14 zaaktypen" />
|
|
94
|
+
```
|
|
@@ -13,6 +13,12 @@
|
|
|
13
13
|
<h1 class="cn-page-header__title" data-testid="cn-page-title">
|
|
14
14
|
{{ resolvedTitle }}
|
|
15
15
|
</h1>
|
|
16
|
+
<CnHeaderTrail
|
|
17
|
+
v-if="!visuallyHidden"
|
|
18
|
+
:breadcrumb="breadcrumb"
|
|
19
|
+
:current="resolvedTitle"
|
|
20
|
+
:meta="resolvedMeta"
|
|
21
|
+
:translate="effectiveTranslate" />
|
|
16
22
|
<p v-if="description && !visuallyHidden" class="cn-page-header__description" data-testid="cn-page-description">
|
|
17
23
|
{{ resolvedDescription }}
|
|
18
24
|
</p>
|
|
@@ -22,6 +28,7 @@
|
|
|
22
28
|
</template>
|
|
23
29
|
|
|
24
30
|
<script>
|
|
31
|
+
import CnHeaderTrail from './CnHeaderTrail.vue'
|
|
25
32
|
import { CnIcon } from '../CnIcon/index.js'
|
|
26
33
|
|
|
27
34
|
/**
|
|
@@ -64,6 +71,7 @@ export default {
|
|
|
64
71
|
name: 'CnPageHeader',
|
|
65
72
|
|
|
66
73
|
components: {
|
|
74
|
+
CnHeaderTrail,
|
|
67
75
|
CnIcon,
|
|
68
76
|
},
|
|
69
77
|
|
|
@@ -90,6 +98,31 @@ export default {
|
|
|
90
98
|
default: '',
|
|
91
99
|
},
|
|
92
100
|
|
|
101
|
+
/**
|
|
102
|
+
* A breadcrumb under the title: one crumb or a list, each
|
|
103
|
+
* `{ label, route?, params?, href?, icon?, separator? }`. The title
|
|
104
|
+
* follows as the current crumb. Drawn as the board look's detail
|
|
105
|
+
* header row.
|
|
106
|
+
*
|
|
107
|
+
* @type {object|Array<object>|null}
|
|
108
|
+
* @spec openspec/changes/page-header-breadcrumb/specs/page-header/spec.md#requirement-a-list-dashboard-or-settings-page-can-show-a-breadcrumb-and-a-meta-note
|
|
109
|
+
*/
|
|
110
|
+
breadcrumb: {
|
|
111
|
+
type: [Object, Array],
|
|
112
|
+
default: null,
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* A note after the breadcrumb and a middle dot, such as "12 open". Run
|
|
117
|
+
* through the host translate function.
|
|
118
|
+
*
|
|
119
|
+
* @spec openspec/changes/page-header-breadcrumb/specs/page-header/spec.md#requirement-a-list-dashboard-or-settings-page-can-show-a-breadcrumb-and-a-meta-note
|
|
120
|
+
*/
|
|
121
|
+
meta: {
|
|
122
|
+
type: String,
|
|
123
|
+
default: '',
|
|
124
|
+
},
|
|
125
|
+
|
|
93
126
|
/** Optional MDI icon name (rendered via CnIcon) */
|
|
94
127
|
icon: {
|
|
95
128
|
type: String,
|
|
@@ -153,6 +186,15 @@ export default {
|
|
|
153
186
|
resolvedDescription() {
|
|
154
187
|
return this.description ? this.effectiveTranslate(this.description) : this.description
|
|
155
188
|
},
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* The meta note run through the host translate function.
|
|
192
|
+
*
|
|
193
|
+
* @return {string}
|
|
194
|
+
*/
|
|
195
|
+
resolvedMeta() {
|
|
196
|
+
return this.meta ? this.effectiveTranslate(this.meta) : ''
|
|
197
|
+
},
|
|
156
198
|
},
|
|
157
199
|
}
|
|
158
200
|
</script>
|
|
@@ -8,13 +8,13 @@
|
|
|
8
8
|
Board variant (`variant: "board"`): the footer of the table card as the
|
|
9
9
|
screens draw it. The count text at the start, numbered 34px links at
|
|
10
10
|
the end, "Previous" only after the first page and "Next" only before
|
|
11
|
-
the last. No First, no Last, no page-size select.
|
|
11
|
+
the last (inert on a single page). No First, no Last, no page-size select.
|
|
12
12
|
-->
|
|
13
13
|
<template v-if="isBoardVariant">
|
|
14
14
|
<span class="cn-pagination__page-info cn-pagination__board-count" data-testid="cn-pagination-board-count">
|
|
15
15
|
{{ boardCountText }}
|
|
16
16
|
</span>
|
|
17
|
-
<nav
|
|
17
|
+
<nav class="cn-pagination__board-pages" :aria-label="t('nextcloud-vue', 'Pagination')">
|
|
18
18
|
<button
|
|
19
19
|
v-if="currentPage > 1"
|
|
20
20
|
type="button"
|
|
@@ -43,6 +43,14 @@
|
|
|
43
43
|
@click="changePage(currentPage + 1)">
|
|
44
44
|
{{ nextLabel }}
|
|
45
45
|
</button>
|
|
46
|
+
<!-- A single page still draws its "1" and an inert Next, as the boards do. -->
|
|
47
|
+
<span
|
|
48
|
+
v-else-if="totalPages <= 1"
|
|
49
|
+
class="cn-pagination__board-link cn-pagination__board-link--wide cn-pagination__board-link--disabled"
|
|
50
|
+
aria-disabled="true"
|
|
51
|
+
data-testid="cn-pagination-next">
|
|
52
|
+
{{ nextLabel }}
|
|
53
|
+
</span>
|
|
46
54
|
</nav>
|
|
47
55
|
</template>
|
|
48
56
|
|
|
@@ -169,6 +169,20 @@
|
|
|
169
169
|
{{ item.title }}
|
|
170
170
|
</component>
|
|
171
171
|
</div>
|
|
172
|
+
<!-- `bar`: a thin track under the row, filled to the row's value
|
|
173
|
+
against its own maximum or against the largest row. -->
|
|
174
|
+
<span
|
|
175
|
+
v-if="presentation === 'rows' && item.bar"
|
|
176
|
+
class="cn-record-list__bar"
|
|
177
|
+
:class="{ 'cn-record-list__bar--over': item.bar.over }"
|
|
178
|
+
role="meter"
|
|
179
|
+
:aria-label="item.title || null"
|
|
180
|
+
:aria-valuenow="item.bar.value"
|
|
181
|
+
aria-valuemin="0"
|
|
182
|
+
:aria-valuemax="item.bar.max"
|
|
183
|
+
data-testid="cn-record-list-bar">
|
|
184
|
+
<span class="cn-record-list__bar-fill" :style="{ width: `${item.bar.share}%` }" />
|
|
185
|
+
</span>
|
|
172
186
|
</li>
|
|
173
187
|
</ul>
|
|
174
188
|
</div>
|
|
@@ -254,7 +268,11 @@ export default {
|
|
|
254
268
|
* The list config: `{ register, schema, filter?, sort?, limit?, extend?,
|
|
255
269
|
* presentation?, title?, meta?, text?, label?, pill?, trailing?, icon?,
|
|
256
270
|
* rowRoute?, rowIdField?, headerLink?, headerNote?, emptyText?, errorText?,
|
|
257
|
-
* valueLabels?, endpointSource? }`. `
|
|
271
|
+
* valueLabels?, endpointSource?, bar? }`. `bar` (`{ valueField, maxField?,
|
|
272
|
+
* overVariant? }`) draws a thin track under each row: filled to
|
|
273
|
+
* `valueField` against `maxField` (drawn in the error colour past it
|
|
274
|
+
* with `overVariant: "error"`), or without `maxField` against the
|
|
275
|
+
* largest row. `endpointSource` (`{ url, method?,
|
|
258
276
|
* params?, responsePath? }`) reads the rows from an app endpoint instead
|
|
259
277
|
* of `register` / `schema`. `valueLabels` maps a field's stored values to the labels
|
|
260
278
|
* the templates show.
|
|
@@ -402,6 +420,7 @@ export default {
|
|
|
402
420
|
/** @return {Array<object>} The rows ready to draw. */
|
|
403
421
|
items() {
|
|
404
422
|
const c = this.content || {}
|
|
423
|
+
const barScale = this.barScale
|
|
405
424
|
return this.rows.slice(0, this.limit).map((row, index) => {
|
|
406
425
|
const link = this.rowLink(row)
|
|
407
426
|
return {
|
|
@@ -420,10 +439,26 @@ export default {
|
|
|
420
439
|
badges: this.badgesOf(row),
|
|
421
440
|
due: this.dueOf(row),
|
|
422
441
|
icon: this.iconOf(row),
|
|
442
|
+
bar: this.barOf(row, barScale),
|
|
423
443
|
}
|
|
424
444
|
})
|
|
425
445
|
},
|
|
426
446
|
|
|
447
|
+
/**
|
|
448
|
+
* The largest `bar.valueField` over the rows shown, the scale of a
|
|
449
|
+
* `bar` without `maxField`. 0 without a `bar`.
|
|
450
|
+
*
|
|
451
|
+
* @return {number} The largest value.
|
|
452
|
+
* @spec openspec/changes/record-list-row-bar/specs/record-list-row-bar/spec.md#requirement-a-record-list-row-can-carry-a-bar
|
|
453
|
+
*/
|
|
454
|
+
barScale() {
|
|
455
|
+
const field = this.content?.bar?.valueField
|
|
456
|
+
if (!field) {
|
|
457
|
+
return 0
|
|
458
|
+
}
|
|
459
|
+
return Math.max(0, ...this.rows.slice(0, this.limit).map((row) => Number(readPath(row, field)) || 0))
|
|
460
|
+
},
|
|
461
|
+
|
|
427
462
|
/** @return {object|null} The header link with `{total}` filled and tokens resolved. */
|
|
428
463
|
headerLink() {
|
|
429
464
|
const link = this.content?.headerLink
|
|
@@ -518,6 +553,32 @@ export default {
|
|
|
518
553
|
return typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text
|
|
519
554
|
},
|
|
520
555
|
|
|
556
|
+
/**
|
|
557
|
+
* A row's `bar`: its value, the maximum it is measured against (the
|
|
558
|
+
* row's `maxField`, else the largest row), the filled share and
|
|
559
|
+
* whether it is over that maximum. Null without `bar` or a value.
|
|
560
|
+
*
|
|
561
|
+
* @param {object} row The row.
|
|
562
|
+
* @param {number} scale The largest value over the rows shown.
|
|
563
|
+
* @return {{value: number, max: number, share: number, over: boolean}|null} The bar.
|
|
564
|
+
* @spec openspec/changes/record-list-row-bar/specs/record-list-row-bar/spec.md#requirement-a-record-list-row-can-carry-a-bar
|
|
565
|
+
*/
|
|
566
|
+
barOf(row, scale) {
|
|
567
|
+
const bar = this.content?.bar
|
|
568
|
+
if (!bar || typeof bar !== 'object' || !bar.valueField) {
|
|
569
|
+
return null
|
|
570
|
+
}
|
|
571
|
+
const value = Number(readPath(row, bar.valueField))
|
|
572
|
+
if (!Number.isFinite(value)) {
|
|
573
|
+
return null
|
|
574
|
+
}
|
|
575
|
+
const ownMax = bar.maxField ? Number(readPath(row, bar.maxField)) : NaN
|
|
576
|
+
const max = bar.maxField ? (Number.isFinite(ownMax) && ownMax > 0 ? ownMax : 0) : scale
|
|
577
|
+
const share = max > 0 ? Math.min(100, Math.max(0, (value / max) * 100)) : 0
|
|
578
|
+
const over = Boolean(bar.maxField) && bar.overVariant === 'error' && max > 0 && value > max
|
|
579
|
+
return { value, max, share, over }
|
|
580
|
+
},
|
|
581
|
+
|
|
521
582
|
/**
|
|
522
583
|
* The initials of the row's avatar (`avatar`: a name template such as
|
|
523
584
|
* `"{client.name}"`, or `{ text }`), or '' without one.
|
|
@@ -727,9 +788,9 @@ export default {
|
|
|
727
788
|
},
|
|
728
789
|
|
|
729
790
|
/**
|
|
730
|
-
* The row's due text (`due`: `{ field, todayLabel?, overdueLabel?, emptyText? }`):
|
|
731
|
-
* "3 dagen te laat" in the danger tone, "Vandaag", the date
|
|
732
|
-
* `emptyText` when the field is empty.
|
|
791
|
+
* The row's due text (`due`: `{ field, todayLabel?, overdueLabel?, upcomingLabel?, upcomingWeeksLabel?, upcomingWeeksFrom?, emptyText? }`):
|
|
792
|
+
* "3 dagen te laat" in the danger tone, "Vandaag", the date or the time
|
|
793
|
+
* left ("nog 4 dagen") otherwise, `emptyText` when the field is empty.
|
|
733
794
|
*
|
|
734
795
|
* @param {object} row The row.
|
|
735
796
|
* @return {{text: string, tone: string}|null} The text and tone, or null.
|
|
@@ -1075,6 +1136,25 @@ export default {
|
|
|
1075
1136
|
border-top: 1px solid var(--color-border);
|
|
1076
1137
|
}
|
|
1077
1138
|
|
|
1139
|
+
.cn-record-list__bar {
|
|
1140
|
+
background: var(--color-border);
|
|
1141
|
+
border-radius: 3px;
|
|
1142
|
+
display: block;
|
|
1143
|
+
height: 6px;
|
|
1144
|
+
margin: -6px 0 12px;
|
|
1145
|
+
overflow: hidden;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
.cn-record-list__bar-fill {
|
|
1149
|
+
background: var(--color-primary-element);
|
|
1150
|
+
display: block;
|
|
1151
|
+
height: 100%;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
.cn-record-list__bar--over .cn-record-list__bar-fill {
|
|
1155
|
+
background: var(--color-error);
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1078
1158
|
.cn-record-list__row {
|
|
1079
1159
|
align-items: center;
|
|
1080
1160
|
color: var(--color-main-text);
|
|
@@ -115,6 +115,14 @@ export default {
|
|
|
115
115
|
|
|
116
116
|
With more than one group each group starts closed: its heading is a button that shows the group name and how many of its types are configured ("2/8 configured"), and opens the register select and type list on a click or Enter. A page with several registers is a short card until the administrator opens the group they need.
|
|
117
117
|
|
|
118
|
+
## Board look
|
|
119
|
+
|
|
120
|
+
In the board look (`look="board"`, or the `cnLook` that `CnAppRoot` or `CnAdminSettingsShell` provides) one group is open: its name, a status chip, the register select and a type/schema table with a check icon on each configured type. It lists `boardRowLimit` types and counts the rest in a line with a button that lists them all. The other groups are rows with their type count and a status chip; activating a row opens that group. The save button closes the section instead of sitting in its header.
|
|
121
|
+
|
|
122
|
+
## Loading
|
|
123
|
+
|
|
124
|
+
The register select lists every register without its schemas. Only the registers the configuration maps have their schemas fetched (`registers/{id}/schemas`), and a register chosen in the select is fetched when chosen.
|
|
125
|
+
|
|
118
126
|
## Additional props
|
|
119
127
|
|
|
120
128
|
| Prop | Type | Default | Description |
|
|
@@ -127,5 +135,7 @@ With more than one group each group starts closed: its heading is a button that
|
|
|
127
135
|
| `saveButtonText` | String | `'Save configuration'` | Label for the save button |
|
|
128
136
|
| `reimportButtonText` | String | `'Re-import configuration'` | Label for the reimport button |
|
|
129
137
|
| `collapseGroups` | Boolean | `null` | Draw each group as a heading button that opens and closes it, with its configured count shown while closed; the groups start closed. `null` does this when there is more than one group, `true` or `false` forces it |
|
|
138
|
+
| `look` | String | `''` | `board` or `nextcloud`; empty follows the provided `cnLook` |
|
|
139
|
+
| `boardRowLimit` | Number | `4` | Board look: how many types the open group lists before the "show all" line; `0` lists every type |
|
|
130
140
|
| `autoMatch` | Boolean | `true` | Automatically match schema titles to type slugs when a register is selected |
|
|
131
141
|
| `labels` | Object | *(see below)* | UI label overrides for all user-visible strings (register, schema, notConfigured, noSchemas, selectRegister, selectSchema, allConfigured, partiallyConfigured) |
|