@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
|
@@ -14,10 +14,11 @@
|
|
|
14
14
|
:title="title"
|
|
15
15
|
:widgetId="widgetId"
|
|
16
16
|
:documentationUrl="documentationUrl"
|
|
17
|
+
:refreshing="epLoading"
|
|
17
18
|
flush>
|
|
18
19
|
<div class="cn-widget-card-grid">
|
|
19
20
|
<CnObjectCard
|
|
20
|
-
v-for="(item, index) in
|
|
21
|
+
v-for="(item, index) in cards"
|
|
21
22
|
:key="item.id || index"
|
|
22
23
|
:object="item" />
|
|
23
24
|
</div>
|
|
@@ -26,7 +27,10 @@
|
|
|
26
27
|
|
|
27
28
|
<script>
|
|
28
29
|
import { translate as t } from '@nextcloud/l10n'
|
|
30
|
+
import { inject, ref } from 'vue'
|
|
29
31
|
import CnObjectCard from '../CnObjectCard/CnObjectCard.vue'
|
|
32
|
+
import { useEndpointSource } from '../../composables/useEndpointSource.js'
|
|
33
|
+
import { resolveObjectTokenContext } from '../../utils/detailObjectContext.js'
|
|
30
34
|
import { CnWidgetWrapper } from '../CnWidgetWrapper/index.js'
|
|
31
35
|
|
|
32
36
|
/**
|
|
@@ -35,7 +39,9 @@ import { CnWidgetWrapper } from '../CnWidgetWrapper/index.js'
|
|
|
35
39
|
* Renders the card grid on the shared CnWidgetWrapper chrome, which supplies
|
|
36
40
|
* the widget title and the standard overflow Actions menu (Refresh /
|
|
37
41
|
* Documentation / Request a feature). Each entry in `objects` is rendered as a
|
|
38
|
-
* CnObjectCard.
|
|
42
|
+
* CnObjectCard. Without `objects`, an `endpointSource` (`{ url, method?,
|
|
43
|
+
* params?, responsePath? }`, as on the object table) supplies them: the
|
|
44
|
+
* payload after `responsePath` is the array of objects.
|
|
39
45
|
*/
|
|
40
46
|
export default {
|
|
41
47
|
name: 'CnWidgetCardGrid',
|
|
@@ -75,6 +81,53 @@ export default {
|
|
|
75
81
|
type: Array,
|
|
76
82
|
default: () => [],
|
|
77
83
|
},
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Read the objects from an app endpoint: `{ url, method?, params?,
|
|
87
|
+
* responsePath? }`, with the token grammar of widget filters. Ignored
|
|
88
|
+
* while `objects` holds any.
|
|
89
|
+
*
|
|
90
|
+
* @type {object|null}
|
|
91
|
+
*/
|
|
92
|
+
endpointSource: {
|
|
93
|
+
type: Object,
|
|
94
|
+
default: null,
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
setup(props) {
|
|
99
|
+
const objectCtxRaw = inject('cnObjectContext', null)
|
|
100
|
+
const detailCtxRaw = inject('cnDetailObjectContext', null)
|
|
101
|
+
const workspaceRaw = inject('cnWorkspaceContext', ref(null))
|
|
102
|
+
const appConfigRaw = inject('cnAppConfig', ref({}))
|
|
103
|
+
const unwrap = (v) => ((v && typeof v === 'object' && 'value' in v) ? v.value : v)
|
|
104
|
+
const { data, loading } = useEndpointSource(
|
|
105
|
+
() => ((props.objects && props.objects.length > 0) ? null : props.endpointSource),
|
|
106
|
+
{
|
|
107
|
+
ctx: () => ({
|
|
108
|
+
...(resolveObjectTokenContext(objectCtxRaw, detailCtxRaw) || {}),
|
|
109
|
+
workspace: unwrap(workspaceRaw) || {},
|
|
110
|
+
config: unwrap(appConfigRaw) || {},
|
|
111
|
+
}),
|
|
112
|
+
widgetId: () => props.widgetId,
|
|
113
|
+
},
|
|
114
|
+
)
|
|
115
|
+
return { epData: data, epLoading: loading }
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
computed: {
|
|
119
|
+
/**
|
|
120
|
+
* The objects drawn: `objects` when given, else the endpoint's array.
|
|
121
|
+
*
|
|
122
|
+
* @return {Array<object>}
|
|
123
|
+
* @spec openspec/changes/endpoint-source-card-grid-record-list/specs/dashboard-page/spec.md#requirement-a-record-list-and-a-card-grid-can-read-an-endpoint
|
|
124
|
+
*/
|
|
125
|
+
cards() {
|
|
126
|
+
if (this.objects && this.objects.length > 0) {
|
|
127
|
+
return this.objects
|
|
128
|
+
}
|
|
129
|
+
return Array.isArray(this.epData) ? this.epData : []
|
|
130
|
+
},
|
|
78
131
|
},
|
|
79
132
|
}
|
|
80
133
|
</script>
|
|
@@ -88,6 +88,7 @@ import { dispatchAction, resolveObjectOpType } from '../../utils/actionsDispatch
|
|
|
88
88
|
import { resolveObjectTokenContext } from '../../utils/detailObjectContext.js'
|
|
89
89
|
import { pageFilter, pageSort } from '../../utils/pageControls.js'
|
|
90
90
|
import { dropOptionalUnresolved, hasUnresolvedTokens, resolveFilterTokens } from '../../utils/resolveFilterTokens.js'
|
|
91
|
+
import { fieldsFromSchema } from '../../utils/schema.js'
|
|
91
92
|
import { compareVisibleWhen, readVisibleWhenPath } from '../../utils/visibleWhen.js'
|
|
92
93
|
import { CnIcon } from '../CnIcon/index.js'
|
|
93
94
|
import { CnRowActions } from '../CnRowActions/index.js'
|
|
@@ -231,6 +232,12 @@ export default {
|
|
|
231
232
|
* the object form (`{ key, label, sortable, width, cellClass,
|
|
232
233
|
* formatter, widget, format, aggregate }`) both pass straight through.
|
|
233
234
|
*
|
|
235
|
+
* A column that holds a reference shows the referenced object's label
|
|
236
|
+
* with `labelField` (the property to show) and/or `resolve`
|
|
237
|
+
* (`{ register, schema, labelField? }`, where the reference points).
|
|
238
|
+
* Without `resolve` the target is read off the source schema's `$ref`
|
|
239
|
+
* for that property. Such a column renders as an `fkResolve` cell.
|
|
240
|
+
*
|
|
234
241
|
* @type {Array<object|string>}
|
|
235
242
|
*/
|
|
236
243
|
columns: {
|
|
@@ -469,6 +476,8 @@ export default {
|
|
|
469
476
|
confirmAction: null,
|
|
470
477
|
/** @type {object|null} The row the pending confirm-gated action targets. */
|
|
471
478
|
confirmRow: null,
|
|
479
|
+
/** Where each label column's references point, read off the source schema, by column key. */
|
|
480
|
+
refTargets: {},
|
|
472
481
|
}
|
|
473
482
|
},
|
|
474
483
|
|
|
@@ -638,6 +647,57 @@ export default {
|
|
|
638
647
|
return params
|
|
639
648
|
},
|
|
640
649
|
|
|
650
|
+
/**
|
|
651
|
+
* Columns that ask for the label of the object they reference
|
|
652
|
+
* (`labelField` or `resolve`) and draw no cell widget of their own.
|
|
653
|
+
*
|
|
654
|
+
* @return {Array<object>}
|
|
655
|
+
* @spec openspec/changes/dashboard-table-reference-labels/specs/dashboard-page/spec.md#requirement-a-dashboard-table-shows-a-referenced-objects-label
|
|
656
|
+
*/
|
|
657
|
+
refLabelColumns() {
|
|
658
|
+
return (this.columns || []).filter((col) => col && typeof col === 'object' && !col.widget
|
|
659
|
+
&& ((typeof col.labelField === 'string' && col.labelField !== '') || (col.resolve && typeof col.resolve === 'object')))
|
|
660
|
+
},
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* The columns handed to CnDataTable: a label column whose target is
|
|
664
|
+
* known renders as an `fkResolve` cell; every other column as given.
|
|
665
|
+
*
|
|
666
|
+
* @return {Array<object|string>}
|
|
667
|
+
* @spec openspec/changes/dashboard-table-reference-labels/specs/dashboard-page/spec.md#requirement-a-dashboard-table-shows-a-referenced-objects-label
|
|
668
|
+
*/
|
|
669
|
+
renderedColumns() {
|
|
670
|
+
if (this.refLabelColumns.length === 0) {
|
|
671
|
+
return this.columns
|
|
672
|
+
}
|
|
673
|
+
return this.columns.map((col) => {
|
|
674
|
+
if (!this.refLabelColumns.includes(col)) {
|
|
675
|
+
return col
|
|
676
|
+
}
|
|
677
|
+
const target = this.refTargetOf(col)
|
|
678
|
+
if (!target) {
|
|
679
|
+
return col
|
|
680
|
+
}
|
|
681
|
+
const labelField = (col.resolve && typeof col.resolve.labelField === 'string' && col.resolve.labelField) || col.labelField || 'name'
|
|
682
|
+
return { ...col, widget: 'fkResolve', widgetProps: { ...(col.widgetProps || {}), register: target.register, schema: target.schema, labelField } }
|
|
683
|
+
})
|
|
684
|
+
},
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* The label columns whose target has to be read off the source schema.
|
|
688
|
+
*
|
|
689
|
+
* @return {string[]} Their keys.
|
|
690
|
+
*/
|
|
691
|
+
schemaRefKeys() {
|
|
692
|
+
return this.refLabelColumns.filter((col) => !this.explicitTarget(col)).map((col) => col.key)
|
|
693
|
+
},
|
|
694
|
+
|
|
695
|
+
/** @return {string} The label columns to look up and the source schema, to read again when either changes. */
|
|
696
|
+
refTargetsKey() {
|
|
697
|
+
const src = this.source || {}
|
|
698
|
+
return `${src.register || ''}/${src.schema || ''}:${this.schemaRefKeys.join('|')}`
|
|
699
|
+
},
|
|
700
|
+
|
|
641
701
|
/**
|
|
642
702
|
* `$props` minus the chrome props (`title`, `documentationUrl`,
|
|
643
703
|
* `widgetId`, `hideWrapper`) and the widget-only props (`source`,
|
|
@@ -669,6 +729,9 @@ export default {
|
|
|
669
729
|
if (this.compiledRowClass) {
|
|
670
730
|
inner.rowClass = this.compiledRowClass
|
|
671
731
|
}
|
|
732
|
+
if (this.refLabelColumns.length > 0) {
|
|
733
|
+
inner.columns = this.renderedColumns
|
|
734
|
+
}
|
|
672
735
|
if (this.selfFetchActive) {
|
|
673
736
|
inner.register = this.source.register
|
|
674
737
|
inner.schemaId = this.source.schema
|
|
@@ -804,6 +867,16 @@ export default {
|
|
|
804
867
|
},
|
|
805
868
|
},
|
|
806
869
|
|
|
870
|
+
watch: {
|
|
871
|
+
refTargetsKey() {
|
|
872
|
+
this.loadRefTargets()
|
|
873
|
+
},
|
|
874
|
+
},
|
|
875
|
+
|
|
876
|
+
mounted() {
|
|
877
|
+
this.loadRefTargets()
|
|
878
|
+
},
|
|
879
|
+
|
|
807
880
|
methods: {
|
|
808
881
|
/**
|
|
809
882
|
* Entry point for every declarative action trigger (row menu or
|
|
@@ -914,6 +987,68 @@ export default {
|
|
|
914
987
|
}
|
|
915
988
|
},
|
|
916
989
|
|
|
990
|
+
/**
|
|
991
|
+
* Where a label column's references point when the column says so
|
|
992
|
+
* itself (`resolve.register` and `resolve.schema`).
|
|
993
|
+
*
|
|
994
|
+
* @param {object} col The column.
|
|
995
|
+
* @return {{register: string, schema: string}|null}
|
|
996
|
+
*/
|
|
997
|
+
explicitTarget(col) {
|
|
998
|
+
const r = col && col.resolve
|
|
999
|
+
if (r && typeof r === 'object' && typeof r.register === 'string' && r.register !== '' && typeof r.schema === 'string' && r.schema !== '') {
|
|
1000
|
+
return { register: r.register, schema: r.schema }
|
|
1001
|
+
}
|
|
1002
|
+
return null
|
|
1003
|
+
},
|
|
1004
|
+
|
|
1005
|
+
/**
|
|
1006
|
+
* Where a label column's references point: the column's `resolve`, else
|
|
1007
|
+
* the source schema's `$ref` for the property, else null (the column
|
|
1008
|
+
* then shows the raw value).
|
|
1009
|
+
*
|
|
1010
|
+
* @param {object} col The column.
|
|
1011
|
+
* @return {{register: string, schema: string}|null}
|
|
1012
|
+
*/
|
|
1013
|
+
refTargetOf(col) {
|
|
1014
|
+
return this.explicitTarget(col) || this.refTargets[col.key] || null
|
|
1015
|
+
},
|
|
1016
|
+
|
|
1017
|
+
/**
|
|
1018
|
+
* Read the source schema once and note, for each label column without a
|
|
1019
|
+
* `resolve`, the register and schema its property's `$ref` names. A
|
|
1020
|
+
* failed or missing schema leaves those columns showing raw values.
|
|
1021
|
+
*
|
|
1022
|
+
* @return {Promise<void>}
|
|
1023
|
+
* @spec openspec/changes/dashboard-table-reference-labels/specs/dashboard-page/spec.md#requirement-a-dashboard-table-shows-a-referenced-objects-label
|
|
1024
|
+
*/
|
|
1025
|
+
async loadRefTargets() {
|
|
1026
|
+
const keys = this.schemaRefKeys
|
|
1027
|
+
const src = this.source
|
|
1028
|
+
if (keys.length === 0 || !src || typeof src.register !== 'string' || src.register === '' || src.register.startsWith('@') || typeof src.schema !== 'string' || src.schema === '') {
|
|
1029
|
+
return
|
|
1030
|
+
}
|
|
1031
|
+
const store = this.getObjectStore()
|
|
1032
|
+
if (!store || typeof store.fetchSchema !== 'function') {
|
|
1033
|
+
return
|
|
1034
|
+
}
|
|
1035
|
+
const type = resolveObjectOpType(store, { register: src.register, schema: src.schema }, { exactSchema: true })
|
|
1036
|
+
const schema = await store.fetchSchema(type).catch(() => null)
|
|
1037
|
+
if (!schema || typeof schema !== 'object') {
|
|
1038
|
+
return
|
|
1039
|
+
}
|
|
1040
|
+
const fields = fieldsFromSchema(schema, { includeReadOnly: true })
|
|
1041
|
+
const found = {}
|
|
1042
|
+
for (const key of keys) {
|
|
1043
|
+
const field = fields.find((f) => f.key === key)
|
|
1044
|
+
const ref = field && field.reference
|
|
1045
|
+
if (ref && ref.schema !== undefined && ref.schema !== null) {
|
|
1046
|
+
found[key] = { register: ref.register || src.register, schema: String(ref.schema) }
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
this.refTargets = { ...this.refTargets, ...found }
|
|
1050
|
+
},
|
|
1051
|
+
|
|
917
1052
|
/**
|
|
918
1053
|
* The shared object store instance, or null when no Pinia is active
|
|
919
1054
|
* (`dispatchAction` then warns and no-ops).
|
|
@@ -12,6 +12,16 @@ Basic — widget card with title and content:
|
|
|
12
12
|
</div>
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
+
With an `eyebrow`, a short muted line above the title:
|
|
16
|
+
|
|
17
|
+
```vue
|
|
18
|
+
<div style="width: 320px; height: 160px; position: relative; border: 1px solid var(--color-border); border-radius: 8px; overflow: hidden;">
|
|
19
|
+
<CnWidgetWrapper title="Forecast 13 weeks" eyebrow="Balance per week" :show-title="true">
|
|
20
|
+
<p style="margin: 0; padding: 0 16px;">Week 41</p>
|
|
21
|
+
</CnWidgetWrapper>
|
|
22
|
+
</div>
|
|
23
|
+
```
|
|
24
|
+
|
|
15
25
|
With footer buttons:
|
|
16
26
|
|
|
17
27
|
```vue
|
|
@@ -52,7 +52,15 @@
|
|
|
52
52
|
v-else-if="iconClass"
|
|
53
53
|
:class="iconClass"
|
|
54
54
|
class="cn-widget-wrapper__icon" />
|
|
55
|
-
|
|
55
|
+
<!-- An eyebrow (`eyebrow`) is a muted line above the title:
|
|
56
|
+
"Saldo per week" above "Prognose 13 weken". -->
|
|
57
|
+
<div v-if="resolvedEyebrow" class="cn-widget-wrapper__title-stack">
|
|
58
|
+
<span class="cn-widget-wrapper__eyebrow" data-testid="cn-widget-wrapper-eyebrow">{{ resolvedEyebrow }}</span>
|
|
59
|
+
<h3 :id="titleId" class="cn-widget-wrapper__title">
|
|
60
|
+
{{ resolvedTitle }}
|
|
61
|
+
</h3>
|
|
62
|
+
</div>
|
|
63
|
+
<h3 v-else :id="titleId" class="cn-widget-wrapper__title">
|
|
56
64
|
{{ resolvedTitle }}
|
|
57
65
|
</h3>
|
|
58
66
|
<!-- @slot title-meta Rendered inside the header's left group,
|
|
@@ -234,6 +242,15 @@ export default {
|
|
|
234
242
|
default: () => t('nextcloud-vue', 'Widget'),
|
|
235
243
|
},
|
|
236
244
|
|
|
245
|
+
/**
|
|
246
|
+
* A short muted line above the title ("Saldo per week" above
|
|
247
|
+
* "Prognose 13 weken"), through the host translate. Empty draws none.
|
|
248
|
+
*/
|
|
249
|
+
eyebrow: {
|
|
250
|
+
type: String,
|
|
251
|
+
default: '',
|
|
252
|
+
},
|
|
253
|
+
|
|
237
254
|
/** Whether to show the header with title */
|
|
238
255
|
showTitle: {
|
|
239
256
|
type: Boolean,
|
|
@@ -717,6 +734,16 @@ export default {
|
|
|
717
734
|
return label ? this.effectiveTranslate(label) : ''
|
|
718
735
|
},
|
|
719
736
|
|
|
737
|
+
/**
|
|
738
|
+
* The eyebrow through the host translate, or ''.
|
|
739
|
+
*
|
|
740
|
+
* @return {string}
|
|
741
|
+
* @spec openspec/changes/widget-title-eyebrow/specs/dashboard-page/spec.md#requirement-a-widget-card-can-carry-a-line-above-its-title
|
|
742
|
+
*/
|
|
743
|
+
resolvedEyebrow() {
|
|
744
|
+
return this.eyebrow ? this.effectiveTranslate(this.eyebrow) : ''
|
|
745
|
+
},
|
|
746
|
+
|
|
720
747
|
/**
|
|
721
748
|
* The RENDERED widget title — `displayTitle` run through the host
|
|
722
749
|
* translate function. Kept separate from `displayTitle` on purpose:
|
|
@@ -1152,6 +1179,22 @@ export default {
|
|
|
1152
1179
|
color: var(--cn-widget-icon-color, var(--color-primary-element));
|
|
1153
1180
|
}
|
|
1154
1181
|
|
|
1182
|
+
.cn-widget-wrapper__title-stack {
|
|
1183
|
+
display: flex;
|
|
1184
|
+
flex-direction: column;
|
|
1185
|
+
gap: 2px;
|
|
1186
|
+
min-width: 0;
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
.cn-widget-wrapper__eyebrow {
|
|
1190
|
+
font-size: 13px;
|
|
1191
|
+
line-height: 1.3;
|
|
1192
|
+
color: var(--color-text-maxcontrast);
|
|
1193
|
+
white-space: nowrap;
|
|
1194
|
+
overflow: hidden;
|
|
1195
|
+
text-overflow: ellipsis;
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1155
1198
|
.cn-widget-wrapper__title {
|
|
1156
1199
|
font-weight: 600;
|
|
1157
1200
|
font-size: 14px;
|
|
@@ -291,6 +291,26 @@ export function useListView(objectTypeOrOptions, options) {
|
|
|
291
291
|
refresh(page)
|
|
292
292
|
}
|
|
293
293
|
|
|
294
|
+
/**
|
|
295
|
+
* Add the next page after the rows already loaded, for a view that shows
|
|
296
|
+
* every row at once (the board). Does nothing on the last page. A refresh
|
|
297
|
+
* started while the page is in flight drops it.
|
|
298
|
+
*
|
|
299
|
+
* @return {Promise<void>}
|
|
300
|
+
*/
|
|
301
|
+
async function loadMore() {
|
|
302
|
+
const current = pagination.value
|
|
303
|
+
const next = Number(current.page || 1) + 1
|
|
304
|
+
if (next > Number(current.pages || 1)) {
|
|
305
|
+
return
|
|
306
|
+
}
|
|
307
|
+
const seq = refreshSeq
|
|
308
|
+
await objectStore.fetchCollection(getType(), buildParams(next), {
|
|
309
|
+
append: true,
|
|
310
|
+
isCurrent: () => seq === refreshSeq,
|
|
311
|
+
})
|
|
312
|
+
}
|
|
313
|
+
|
|
294
314
|
/**
|
|
295
315
|
* Handle page-size change. Resets to page 1.
|
|
296
316
|
*
|
|
@@ -402,6 +422,7 @@ export function useListView(objectTypeOrOptions, options) {
|
|
|
402
422
|
onPageSizeChange,
|
|
403
423
|
// Explicit fetch
|
|
404
424
|
refresh,
|
|
425
|
+
loadMore,
|
|
405
426
|
// The query the list sends (search, sort, facet filters, fixed filters),
|
|
406
427
|
// built from the current state, for an export that must follow the list.
|
|
407
428
|
buildParams,
|
package/src/css/board.css
CHANGED
|
@@ -96,19 +96,24 @@
|
|
|
96
96
|
gap: 8px;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
|
|
99
|
+
/* The title is a link drawn as a heading (board DqWerkbord): main text, no
|
|
100
|
+
underline. `!important` because a theme's `a { color; text-decoration }`
|
|
101
|
+
carries it (thematiq), and a bare `a` with `!important` beats any class. */
|
|
102
|
+
.cn-board-view.cn-board-view--board .cn-board-view__card-title,
|
|
103
|
+
.cn-board-view.cn-board-view--board .cn-board-view__card-title:visited {
|
|
100
104
|
flex: 1 1 auto;
|
|
101
105
|
min-width: 0;
|
|
102
106
|
font-size: 15px;
|
|
103
107
|
font-weight: 700;
|
|
104
108
|
line-height: 1.3;
|
|
105
|
-
color: var(--color-main-text);
|
|
106
|
-
text-decoration: none;
|
|
109
|
+
color: var(--color-main-text) !important;
|
|
110
|
+
text-decoration: none !important;
|
|
107
111
|
overflow-wrap: anywhere;
|
|
108
112
|
}
|
|
109
113
|
|
|
110
114
|
.cn-board-view.cn-board-view--board .cn-board-view__card-title:hover {
|
|
111
|
-
|
|
115
|
+
color: var(--color-main-text) !important;
|
|
116
|
+
text-decoration: underline !important;
|
|
112
117
|
}
|
|
113
118
|
|
|
114
119
|
.cn-board-view.cn-board-view--board .cn-board-view__card-title:focus-visible {
|
|
@@ -267,3 +272,59 @@
|
|
|
267
272
|
outline: 2px solid var(--color-primary-element);
|
|
268
273
|
outline-offset: 2px;
|
|
269
274
|
}
|
|
275
|
+
|
|
276
|
+
/*
|
|
277
|
+
* screens-board-reference-columns: a wide board wraps its columns onto more
|
|
278
|
+
* rows (`config.board.wrap`), as many to a row as fit at 240px, instead of
|
|
279
|
+
* one row that scrolls sideways.
|
|
280
|
+
*/
|
|
281
|
+
.cn-board-view.cn-board-view--wrap .cn-board-view__columns {
|
|
282
|
+
display: grid;
|
|
283
|
+
grid-auto-flow: row;
|
|
284
|
+
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
|
285
|
+
overflow-x: visible;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.cn-board-view.cn-board-view--wrap:not(.cn-board-view--board) .cn-board-view__columns {
|
|
289
|
+
gap: 12px;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/* A column the dragged card cannot reach fades; the label stays readable. */
|
|
293
|
+
.cn-board-view .cn-board-view__column--unreachable {
|
|
294
|
+
opacity: 0.45;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/* The selection checkbox: in the board look's card head, before the title. */
|
|
298
|
+
.cn-board-view .cn-board-view__select {
|
|
299
|
+
flex: none;
|
|
300
|
+
width: 16px;
|
|
301
|
+
height: 16px;
|
|
302
|
+
margin: 2px 0 0;
|
|
303
|
+
accent-color: var(--color-primary-element);
|
|
304
|
+
cursor: pointer;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/* Board look: the board draws no checkbox. It shows on a hovered card, on a
|
|
308
|
+
card holding focus (so Tab still reaches it), once anything is selected, and
|
|
309
|
+
always where there is no hover. Collapsed rather than hidden, so it stays in
|
|
310
|
+
the tab order. */
|
|
311
|
+
@media (hover: hover) {
|
|
312
|
+
.cn-board-view.cn-board-view--board:not(.cn-board-view--selecting) .cn-board-view__card:not(:hover, :focus-within) .cn-board-view__select {
|
|
313
|
+
width: 0;
|
|
314
|
+
margin-inline-end: -8px;
|
|
315
|
+
opacity: 0;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/* A menu target the card cannot reach: still listed, greyed, with its note. */
|
|
320
|
+
.cn-board-view.cn-board-view--board .cn-board-view__menu-item.cn-board-view__menu-item--blocked {
|
|
321
|
+
color: var(--color-text-maxcontrast);
|
|
322
|
+
cursor: not-allowed;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.cn-board-view .cn-board-view__menu-note {
|
|
326
|
+
margin-inline-start: auto;
|
|
327
|
+
padding-inline-start: 12px;
|
|
328
|
+
font-size: 12px;
|
|
329
|
+
color: var(--color-text-maxcontrast);
|
|
330
|
+
}
|
package/src/css/dialog.css
CHANGED
|
@@ -35,8 +35,10 @@
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
/* The header is ours: NcModal's own close button and NcDialog's heading give
|
|
38
|
-
way (the heading stays as the accessible name, visually hidden).
|
|
39
|
-
|
|
38
|
+
way (the heading stays as the accessible name, visually hidden). The mask
|
|
39
|
+
class raises the rule above NcButton's `.button-vue[data-v-…]`, whose
|
|
40
|
+
stylesheet a spawned dialog can load after this one. */
|
|
41
|
+
.modal-mask.cn-dialog-board .modal-container__close {
|
|
40
42
|
display: none;
|
|
41
43
|
}
|
|
42
44
|
|
package/src/css/index-page.css
CHANGED
|
@@ -104,6 +104,19 @@
|
|
|
104
104
|
margin-top: 0;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
/* The board view's footer: how many rows it holds of how many match, and
|
|
108
|
+
"Load more" while the list is not complete. */
|
|
109
|
+
.cn-index-page__board-load {
|
|
110
|
+
display: flex;
|
|
111
|
+
flex-wrap: wrap;
|
|
112
|
+
align-items: center;
|
|
113
|
+
justify-content: space-between;
|
|
114
|
+
gap: 8px 16px;
|
|
115
|
+
padding: 12px 16px;
|
|
116
|
+
color: var(--color-text-maxcontrast);
|
|
117
|
+
font-size: 14px;
|
|
118
|
+
}
|
|
119
|
+
|
|
107
120
|
/* Table view owns both scroll axes. With the page column scrolling instead, the
|
|
108
121
|
table's horizontal scrollbar sat at the bottom of the (taller-than-viewport)
|
|
109
122
|
table, so it only appeared after scrolling down, and the pagination was
|
|
@@ -369,3 +382,39 @@
|
|
|
369
382
|
outline: 2px solid var(--color-primary-element);
|
|
370
383
|
outline-offset: 2px;
|
|
371
384
|
}
|
|
385
|
+
|
|
386
|
+
/*
|
|
387
|
+
* screens-board-reference-columns: the board view's controls after the
|
|
388
|
+
* search (board DqWerkbord): "Case type:" and "Columns:" as a 600 label and a
|
|
389
|
+
* 36px select, then the hint in the secondary text colour.
|
|
390
|
+
*/
|
|
391
|
+
.cn-index-page__board-control {
|
|
392
|
+
display: inline-flex;
|
|
393
|
+
align-items: center;
|
|
394
|
+
gap: 8px;
|
|
395
|
+
font-size: 14px;
|
|
396
|
+
font-weight: 600;
|
|
397
|
+
color: var(--color-main-text);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
.cn-index-page__board-select {
|
|
401
|
+
height: 36px;
|
|
402
|
+
min-height: 36px;
|
|
403
|
+
margin: 0;
|
|
404
|
+
padding: 0 10px;
|
|
405
|
+
border: 1px solid var(--color-border-maxcontrast);
|
|
406
|
+
border-radius: 8px;
|
|
407
|
+
background-color: var(--color-main-background);
|
|
408
|
+
color: var(--color-main-text);
|
|
409
|
+
font: inherit;
|
|
410
|
+
font-weight: 400;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
.cn-index-page__board-select--strong {
|
|
414
|
+
font-weight: 500;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.cn-index-page__board-hint {
|
|
418
|
+
font-size: 14px;
|
|
419
|
+
color: var(--color-text-maxcontrast);
|
|
420
|
+
}
|
package/src/css/kpi-card.css
CHANGED
|
@@ -444,6 +444,19 @@ a.cn-kpi-card--stacked:focus-visible {
|
|
|
444
444
|
color: var(--color-main-text);
|
|
445
445
|
}
|
|
446
446
|
|
|
447
|
+
/* A board tile whose label wraps grows into its cell instead of clipping its
|
|
448
|
+
own last line. dossiq's "Afgehandeld deze maand" at 1100 wide held 129px of
|
|
449
|
+
content in a 114px tile and lost its caption's bottom. The tile still fills
|
|
450
|
+
a cell it fits (`min-height: 100%`), so every tile that fits keeps its
|
|
451
|
+
size; one that does not takes the height of its content, and the card-fit
|
|
452
|
+
cell's `safe center` keeps its top in view.
|
|
453
|
+
@spec openspec/changes/board-stat-tile-grows/specs/stat-tile-board-look/spec.md#requirement-a-board-stat-tile-shows-all-its-lines */
|
|
454
|
+
.cn-kpi-card--board.cn-kpi-card--stacked {
|
|
455
|
+
flex: 0 0 auto;
|
|
456
|
+
height: auto;
|
|
457
|
+
min-height: 100%;
|
|
458
|
+
}
|
|
459
|
+
|
|
447
460
|
.cn-kpi-card--board.cn-kpi-card--stacked .cn-kpi-card__caption-line,
|
|
448
461
|
.cn-kpi-card--board.cn-kpi-card--stacked .cn-stats-block__count-label {
|
|
449
462
|
font-size: 13px;
|