@conduction/nextcloud-vue 2.65.0 → 2.66.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/CnDashboardPage/CnDashboardPage.vue2.js +99 -2
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +438 -383
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js +9 -3
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js +1 -1
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +78 -21
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +129 -96
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnFkResolveCell/CnFkResolveCell.vue2.js +3 -1
- package/dist/esm/components/CnFkResolveCell/CnFkResolveCell.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +144 -4
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js +63 -7
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js +2 -1
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js +11 -0
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js.map +1 -1
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js +2 -1
- package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js.map +1 -1
- package/dist/esm/l10n/en.json.js +3 -0
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +3 -0
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/mixins/pageViews.js +400 -0
- package/dist/esm/mixins/pageViews.js.map +1 -0
- package/dist/esm/nextcloud-vue.css +18 -0
- package/dist/esm/utils/actionsDispatcher.js +11 -2
- package/dist/esm/utils/actionsDispatcher.js.map +1 -1
- package/dist/esm/utils/columnFilters.js +27 -3
- package/dist/esm/utils/columnFilters.js.map +1 -1
- package/dist/esm/utils/schema.js +40 -2
- package/dist/esm/utils/schema.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +1521 -596
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +18 -0
- package/l10n/en.json +3 -0
- package/l10n/nl.json +3 -0
- package/package.json +1 -1
- package/src/components/CnDashboardPage/CnDashboardPage.vue +165 -8
- package/src/components/CnDataTable/CnColumnFilterPopover.vue +11 -5
- package/src/components/CnDetailPage/CnDetailPage.vue +94 -5
- package/src/components/CnFkResolveCell/CnFkResolveCell.vue +3 -1
- package/src/components/CnFormDialog/CnFormDialog.vue +144 -4
- package/src/components/CnHeaderWidget/CnHeaderWidget.vue +67 -8
- package/src/components/CnSegmentedControl/CnSegmentedControl.md +1 -1
- package/src/components/CnSegmentedControl/CnSegmentedControl.vue +12 -0
- package/src/css/detail-page.css +5 -0
- package/src/mixins/pageViews.js +396 -0
- package/src/schemas/app-manifest-v2.schema.json +29 -1
- package/src/utils/actionsDispatcher.js +11 -2
- package/src/utils/columnFilters.js +29 -2
- package/src/utils/schema.js +40 -2
- package/src/utils/validateManifestV2.compiled.js +2 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
[diffend] Oversized file quarantined before diffing.
|
|
2
2
|
name: package/dist/nextcloud-vue.cjs.js.map
|
|
3
|
-
size:
|
|
4
|
-
sha256:
|
|
3
|
+
size: 35355767 bytes
|
|
4
|
+
sha256: e048d3627c1eba03f90228c9f300e9341baf3086530119fe40b4af2106f5b7b2
|
package/dist/nextcloud-vue.css
CHANGED
|
@@ -3279,6 +3279,11 @@ a.cn-kpi-card--stacked:focus-visible {
|
|
|
3279
3279
|
font-size: 0.9em;
|
|
3280
3280
|
white-space: nowrap;
|
|
3281
3281
|
}
|
|
3282
|
+
|
|
3283
|
+
/* A page view the switch shows with nothing to draw (view-switch-containers). */
|
|
3284
|
+
.cn-detail-page .cn-page-view-region--empty {
|
|
3285
|
+
padding: 40px 20px;
|
|
3286
|
+
}
|
|
3282
3287
|
/* CnReferencePreview — a reference that shows what it points at. */
|
|
3283
3288
|
|
|
3284
3289
|
.cn-reference-preview {
|
|
@@ -30567,6 +30572,19 @@ to {
|
|
|
30567
30572
|
.cn-dashboard-page__empty[data-v-824052fe] {
|
|
30568
30573
|
padding: 60px 20px;
|
|
30569
30574
|
}
|
|
30575
|
+
|
|
30576
|
+
/* The page's view switch on a page without a header row: at the end of its
|
|
30577
|
+
own row, above the grids, where the header would have put it. */
|
|
30578
|
+
.cn-dashboard-page__view-switch[data-v-824052fe] {
|
|
30579
|
+
display: flex;
|
|
30580
|
+
justify-content: flex-end;
|
|
30581
|
+
margin-bottom: 12px;
|
|
30582
|
+
}
|
|
30583
|
+
|
|
30584
|
+
/* A view the switch shows with nothing to draw. */
|
|
30585
|
+
.cn-page-view-region--empty[data-v-824052fe] {
|
|
30586
|
+
padding: 40px 20px;
|
|
30587
|
+
}
|
|
30570
30588
|
.cn-dashboard-page__page-filters[data-v-824052fe] {
|
|
30571
30589
|
display: flex;
|
|
30572
30590
|
flex-wrap: wrap;
|
package/l10n/en.json
CHANGED
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"This integration is not available.": "This integration is not available.",
|
|
50
50
|
"Integrations": "Integrations",
|
|
51
51
|
"View": "View",
|
|
52
|
+
"Views": "Views",
|
|
52
53
|
"current step": "current step",
|
|
53
54
|
"{app} not available": "{app} not available",
|
|
54
55
|
"{app} not configured": "{app} not configured",
|
|
@@ -408,6 +409,7 @@
|
|
|
408
409
|
"Any": "Any",
|
|
409
410
|
"To": "To",
|
|
410
411
|
"Equals": "Equals",
|
|
412
|
+
"Contains": "Contains",
|
|
411
413
|
"Clear": "Clear",
|
|
412
414
|
"Apply": "Apply",
|
|
413
415
|
"This column cannot list its objects.": "This column cannot list its objects.",
|
|
@@ -1088,6 +1090,7 @@
|
|
|
1088
1090
|
"The actions of an attention card are declared in the manifest.": "The actions of an attention card are declared in the manifest.",
|
|
1089
1091
|
"An attention card needs a title": "An attention card needs a title",
|
|
1090
1092
|
"Good morning, {name}": "Good morning, {name}",
|
|
1093
|
+
"This view has no widgets yet.": "This view has no widgets yet.",
|
|
1091
1094
|
"Good morning": "Good morning",
|
|
1092
1095
|
"Good afternoon, {name}": "Good afternoon, {name}",
|
|
1093
1096
|
"Good afternoon": "Good afternoon",
|
package/l10n/nl.json
CHANGED
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"This integration is not available.": "Deze integratie is niet beschikbaar.",
|
|
50
50
|
"Integrations": "Integraties",
|
|
51
51
|
"View": "Weergave",
|
|
52
|
+
"Views": "Weergaven",
|
|
52
53
|
"current step": "huidige stap",
|
|
53
54
|
"{app} not available": "{app} niet beschikbaar",
|
|
54
55
|
"{app} not configured": "{app} niet geconfigureerd",
|
|
@@ -408,6 +409,7 @@
|
|
|
408
409
|
"Any": "Alle",
|
|
409
410
|
"To": "Tot",
|
|
410
411
|
"Equals": "Is gelijk aan",
|
|
412
|
+
"Contains": "Bevat",
|
|
411
413
|
"Clear": "Wissen",
|
|
412
414
|
"Apply": "Toepassen",
|
|
413
415
|
"This column cannot list its objects.": "Deze kolom kan zijn objecten niet tonen.",
|
|
@@ -1132,6 +1134,7 @@
|
|
|
1132
1134
|
"The actions of an attention card are declared in the manifest.": "De acties van een aandachtskaart staan in het manifest.",
|
|
1133
1135
|
"An attention card needs a title": "Een aandachtskaart heeft een titel nodig",
|
|
1134
1136
|
"Good morning, {name}": "Goedemorgen, {name}",
|
|
1137
|
+
"This view has no widgets yet.": "Deze weergave heeft nog geen widgets.",
|
|
1135
1138
|
"Good morning": "Goedemorgen",
|
|
1136
1139
|
"Good afternoon, {name}": "Goedemiddag, {name}",
|
|
1137
1140
|
"Good afternoon": "Goedemiddag",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@conduction/nextcloud-vue",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.66.0",
|
|
4
4
|
"description": "Shared Vue component library for Conduction Nextcloud apps — complements @nextcloud/vue with higher-level components, OpenRegister integration, and NL Design System support",
|
|
5
5
|
"homepage": "https://github.com/ConductionNL/nextcloud-vue#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -32,6 +32,18 @@
|
|
|
32
32
|
</p>
|
|
33
33
|
</div>
|
|
34
34
|
<div class="cn-dashboard-page__header-actions">
|
|
35
|
+
<!-- The page's view switch (`views`), unless a greeting header
|
|
36
|
+
widget on the page draws it. Its options control the view
|
|
37
|
+
region below the grid. -->
|
|
38
|
+
<CnSegmentedControl
|
|
39
|
+
v-if="showsPageViewSwitch"
|
|
40
|
+
class="cn-page-view-switch"
|
|
41
|
+
:options="viewSwitchOptions"
|
|
42
|
+
:modelValue="activeViewId"
|
|
43
|
+
:ariaLabel="viewSwitchLabel"
|
|
44
|
+
:controls="viewRegionId"
|
|
45
|
+
data-testid="cn-dashboard-page-view-switch"
|
|
46
|
+
@update:modelValue="selectView" />
|
|
35
47
|
<!-- Declarative header actions (#91 Wave 3): a manifest
|
|
36
48
|
`headerActions[]` renders as buttons (open-form / api-call /
|
|
37
49
|
toggle / navigate / refresh) with visibleWhen gating,
|
|
@@ -218,7 +230,7 @@
|
|
|
218
230
|
sections (before-grid / after-grid / end): a page can legitimately
|
|
219
231
|
have no grid widgets yet still show bodyWidget content, so
|
|
220
232
|
"No widgets configured" would be a false negative. -->
|
|
221
|
-
<div v-else-if="!hasWidgets && !hasBodyWidgets" class="cn-dashboard-page__empty">
|
|
233
|
+
<div v-else-if="!hasWidgets && !hasBodyWidgets && !hasViews" class="cn-dashboard-page__empty">
|
|
222
234
|
<!-- @slot empty Replaces the default empty state shown when the dashboard has no widgets. Defaults to an `NcEmptyContent` block. -->
|
|
223
235
|
<slot name="empty">
|
|
224
236
|
<NcEmptyContent :description="emptyLabel">
|
|
@@ -253,15 +265,34 @@
|
|
|
253
265
|
to the layout/widgets arrays — in-place pushes (Add widget) keep
|
|
254
266
|
the same array identity and can't re-render it. One remount per
|
|
255
267
|
edit flip re-subscribes against the now-reactive graph. -->
|
|
268
|
+
<!-- The view switch for a page without a header row, when no greeting
|
|
269
|
+
header widget draws it. -->
|
|
270
|
+
<div
|
|
271
|
+
v-if="gridSectionsShown && !showHeader && showsPageViewSwitch"
|
|
272
|
+
class="cn-dashboard-page__view-switch">
|
|
273
|
+
<CnSegmentedControl
|
|
274
|
+
class="cn-page-view-switch"
|
|
275
|
+
:options="viewSwitchOptions"
|
|
276
|
+
:modelValue="activeViewId"
|
|
277
|
+
:ariaLabel="viewSwitchLabel"
|
|
278
|
+
:controls="viewRegionId"
|
|
279
|
+
data-testid="cn-dashboard-page-view-switch"
|
|
280
|
+
@update:modelValue="selectView" />
|
|
281
|
+
</div>
|
|
282
|
+
<!-- One grid per section: the page's own layout, then the chosen
|
|
283
|
+
view (`views`) in a region the view switch controls. Both are the
|
|
284
|
+
same grid with the same widget rendering below. A view grid is
|
|
285
|
+
keyed on its view, so switching mounts the other view's grid. -->
|
|
256
286
|
<CnDashboardGrid
|
|
257
|
-
v-
|
|
258
|
-
:key="`cn-dashboard-grid-${gridEditable ? 'editing' : 'live'}`"
|
|
259
|
-
|
|
260
|
-
:
|
|
287
|
+
v-for="section in gridSections"
|
|
288
|
+
:key="`cn-dashboard-grid-${section.key}-${gridEditable ? 'editing' : 'live'}`"
|
|
289
|
+
v-bind="section.attrs"
|
|
290
|
+
:layout="section.layout"
|
|
291
|
+
:editable="section.editable"
|
|
261
292
|
:columns="columns"
|
|
262
293
|
:cellHeight="cellHeight"
|
|
263
294
|
:margin="gridMargin"
|
|
264
|
-
@layoutChange="onLayoutChange">
|
|
295
|
+
@layoutChange="section.view ? onViewLayoutChange($event) : onLayoutChange($event)">
|
|
265
296
|
<template #widget="{ item }">
|
|
266
297
|
<!-- In-app edit overlay (ADR-041): a single launchpad-style
|
|
267
298
|
configure cog opens the per-widget style/config editor,
|
|
@@ -655,6 +686,22 @@
|
|
|
655
686
|
</template>
|
|
656
687
|
</CnDashboardGrid>
|
|
657
688
|
|
|
689
|
+
<!-- The chosen view has nothing to draw: say so in the region, never
|
|
690
|
+
leave a blank area under the switch. -->
|
|
691
|
+
<div
|
|
692
|
+
v-if="gridSectionsShown && viewIsEmpty"
|
|
693
|
+
:id="viewRegionId"
|
|
694
|
+
role="region"
|
|
695
|
+
:aria-label="activeView ? activeView.label : null"
|
|
696
|
+
class="cn-page-view-region cn-page-view-region--empty"
|
|
697
|
+
data-testid="cn-dashboard-page-view-empty">
|
|
698
|
+
<NcEmptyContent :description="viewEmptyText">
|
|
699
|
+
<template #icon>
|
|
700
|
+
<ViewDashboardOutline :size="48" />
|
|
701
|
+
</template>
|
|
702
|
+
</NcEmptyContent>
|
|
703
|
+
</div>
|
|
704
|
+
|
|
658
705
|
<!-- Declarative in-body sections, `placement: "after-grid"` — host-app
|
|
659
706
|
section components rendered BELOW the widget grid. -->
|
|
660
707
|
<CnBodySections
|
|
@@ -714,6 +761,7 @@ import CnBuildiqEditButton from '../CnBuildiqEditButton/CnBuildiqEditButton.vue'
|
|
|
714
761
|
import CnChartWidget from '../CnChartWidget/CnChartWidget.vue'
|
|
715
762
|
import CnDashboardGrid from '../CnDashboardGrid/CnDashboardGrid.vue'
|
|
716
763
|
import CnDateRangePicker, { DEFAULT_DATE_RANGE_PRESETS, resolvePresetWindow } from '../CnDateRangePicker/CnDateRangePicker.vue'
|
|
764
|
+
import CnSegmentedControl from '../CnSegmentedControl/CnSegmentedControl.vue'
|
|
717
765
|
import CnStatsBlockWidget from '../CnStatsBlockWidget/CnStatsBlockWidget.vue'
|
|
718
766
|
import CnTileWidget from '../CnTileWidget/CnTileWidget.vue'
|
|
719
767
|
import CnWidgetRefItem from '../CnWidgetRefItem/CnWidgetRefItem.vue'
|
|
@@ -721,6 +769,7 @@ import CnWidgetRenderer from '../CnWidgetRenderer/CnWidgetRenderer.vue'
|
|
|
721
769
|
import CnWidgetWrapper from '../CnWidgetWrapper/CnWidgetWrapper.vue'
|
|
722
770
|
import { useIntegrationRegistry } from '../../composables/useIntegrationRegistry.js'
|
|
723
771
|
import { readUserPreference, writeUserPreference } from '../../composables/useUserPreferences.js'
|
|
772
|
+
import { pageViews } from '../../mixins/pageViews.js'
|
|
724
773
|
import { dashboardLayoutKey, mergeUserLayout } from '../../store/plugins/dashboardLayouts.js'
|
|
725
774
|
import { isAppInstalled } from '../../utils/appInstalled.js'
|
|
726
775
|
import { compareVisibleWhen, readVisibleWhenValue } from '../../utils/visibleWhen.js'
|
|
@@ -875,8 +924,11 @@ export default {
|
|
|
875
924
|
CnBuildiqEditButton,
|
|
876
925
|
CnWidgetStyleEditorModal,
|
|
877
926
|
CnLeafMountHost,
|
|
927
|
+
CnSegmentedControl,
|
|
878
928
|
},
|
|
879
929
|
|
|
930
|
+
mixins: [pageViews],
|
|
931
|
+
|
|
880
932
|
inject: {
|
|
881
933
|
/**
|
|
882
934
|
* Buildiq in-app edit state (ADR-041), provided by CnAppRoot as a ref.
|
|
@@ -1879,6 +1931,89 @@ export default {
|
|
|
1879
1931
|
return this.layout.length > 0 || this.widgetRefItems.length > 0
|
|
1880
1932
|
},
|
|
1881
1933
|
|
|
1934
|
+
/**
|
|
1935
|
+
* Whether the grid sections render at all: not while loading, not
|
|
1936
|
+
* while the widget predicates settle, not on the empty page and not
|
|
1937
|
+
* on a widget-ref content page.
|
|
1938
|
+
*
|
|
1939
|
+
* @return {boolean}
|
|
1940
|
+
* @spec openspec/changes/view-switch-containers/specs/view-switch-containers/spec.md#requirement-a-page-declares-views-that-each-hold-a-widget-grid
|
|
1941
|
+
*/
|
|
1942
|
+
gridSectionsShown() {
|
|
1943
|
+
if (this.loading || (!this.widgetConditionsSettled && !this.gridEditable)) {
|
|
1944
|
+
return false
|
|
1945
|
+
}
|
|
1946
|
+
if (!this.hasWidgets && !this.hasBodyWidgets && !this.hasViews) {
|
|
1947
|
+
return false
|
|
1948
|
+
}
|
|
1949
|
+
return this.widgetRefItems.length === 0
|
|
1950
|
+
},
|
|
1951
|
+
|
|
1952
|
+
/**
|
|
1953
|
+
* The chosen view's layout as the grid draws it: in live mode the
|
|
1954
|
+
* widgets that would draw nothing leave and the rest close up, as
|
|
1955
|
+
* for the page's own layout.
|
|
1956
|
+
*
|
|
1957
|
+
* @return {Array<object>}
|
|
1958
|
+
* @spec openspec/changes/view-switch-containers/specs/view-switch-containers/spec.md#requirement-a-view-with-nothing-to-draw-says-so
|
|
1959
|
+
*/
|
|
1960
|
+
viewDisplayLayout() {
|
|
1961
|
+
const items = this.activeView ? this.activeView.layout : []
|
|
1962
|
+
if (this.gridEditable) {
|
|
1963
|
+
return items
|
|
1964
|
+
}
|
|
1965
|
+
const visible = items.filter((item) => !this.isCollapsedWidget(item))
|
|
1966
|
+
return visible.length === items.length ? items : this.compactDisplayLayout(visible)
|
|
1967
|
+
},
|
|
1968
|
+
|
|
1969
|
+
/**
|
|
1970
|
+
* Whether the chosen view has nothing to draw.
|
|
1971
|
+
*
|
|
1972
|
+
* @return {boolean}
|
|
1973
|
+
* @spec openspec/changes/view-switch-containers/specs/view-switch-containers/spec.md#requirement-a-view-with-nothing-to-draw-says-so
|
|
1974
|
+
*/
|
|
1975
|
+
viewIsEmpty() {
|
|
1976
|
+
return this.hasViews && this.viewDisplayLayout.length === 0
|
|
1977
|
+
},
|
|
1978
|
+
|
|
1979
|
+
/**
|
|
1980
|
+
* The grids this page draws. Without views: the page's own layout,
|
|
1981
|
+
* as it always was. With views: the page's own layout when it has
|
|
1982
|
+
* items, then the chosen view's layout in the region the switch
|
|
1983
|
+
* controls (left out when the view is empty; the empty state takes
|
|
1984
|
+
* the region then).
|
|
1985
|
+
*
|
|
1986
|
+
* @return {Array<{key: string, view: boolean, layout: Array<object>, editable: boolean, attrs: object}>}
|
|
1987
|
+
* @spec openspec/changes/view-switch-containers/specs/view-switch-containers/spec.md#requirement-a-page-declares-views-that-each-hold-a-widget-grid
|
|
1988
|
+
*/
|
|
1989
|
+
gridSections() {
|
|
1990
|
+
if (!this.gridSectionsShown) {
|
|
1991
|
+
return []
|
|
1992
|
+
}
|
|
1993
|
+
const page = { key: 'page', view: false, layout: this.displayLayout, editable: this.gridEditable, attrs: {} }
|
|
1994
|
+
if (!this.hasViews) {
|
|
1995
|
+
return [page]
|
|
1996
|
+
}
|
|
1997
|
+
const sections = this.displayLayout.length > 0 ? [page] : []
|
|
1998
|
+
if (!this.viewIsEmpty) {
|
|
1999
|
+
sections.push({
|
|
2000
|
+
key: `view-${this.activeViewId}`,
|
|
2001
|
+
view: true,
|
|
2002
|
+
layout: this.viewDisplayLayout,
|
|
2003
|
+
// A user arrangement covers the page's own layout only.
|
|
2004
|
+
editable: this.gridEditable && !this.userLayout,
|
|
2005
|
+
attrs: {
|
|
2006
|
+
id: this.viewRegionId,
|
|
2007
|
+
role: 'region',
|
|
2008
|
+
'aria-label': this.activeView.label,
|
|
2009
|
+
class: 'cn-page-view-region',
|
|
2010
|
+
'data-testid': 'cn-dashboard-page-view-region',
|
|
2011
|
+
},
|
|
2012
|
+
})
|
|
2013
|
+
}
|
|
2014
|
+
return sections
|
|
2015
|
+
},
|
|
2016
|
+
|
|
1882
2017
|
/**
|
|
1883
2018
|
* The widget settings object handed to CnWidgetStyleEditorModal,
|
|
1884
2019
|
* built from the widget definition currently being configured
|
|
@@ -2009,6 +2144,14 @@ export default {
|
|
|
2009
2144
|
this.evaluateWidgetConditions()
|
|
2010
2145
|
},
|
|
2011
2146
|
},
|
|
2147
|
+
|
|
2148
|
+
/** A view's widgets carry predicates too. */
|
|
2149
|
+
views: {
|
|
2150
|
+
deep: true,
|
|
2151
|
+
handler() {
|
|
2152
|
+
this.evaluateWidgetConditions()
|
|
2153
|
+
},
|
|
2154
|
+
},
|
|
2012
2155
|
},
|
|
2013
2156
|
|
|
2014
2157
|
created() {
|
|
@@ -2873,7 +3016,8 @@ export default {
|
|
|
2873
3016
|
* @spec openspec/changes/live-check-follow-ups/specs/live-check-follow-ups/spec.md#requirement-an-attention-card-says-when-it-could-not-check
|
|
2874
3017
|
*/
|
|
2875
3018
|
async evaluateWidgetConditions() {
|
|
2876
|
-
const
|
|
3019
|
+
const defs = [...(this.widgets || []), ...this.normalizedViews.flatMap((view) => view.widgets)]
|
|
3020
|
+
const conditional = defs.filter((def) => def && def.id && def.type
|
|
2877
3021
|
&& this.widgetDisplayConfig(def).visibleWhen)
|
|
2878
3022
|
// Skip when the CONDITIONAL SET is unchanged: the deep `widgets`
|
|
2879
3023
|
// watch fires on any def edit, but only an added/removed/edited
|
|
@@ -3007,7 +3151,7 @@ export default {
|
|
|
3007
3151
|
*/
|
|
3008
3152
|
getWidgetDef(widgetId) {
|
|
3009
3153
|
const list = Array.isArray(this.widgets) ? this.widgets : []
|
|
3010
|
-
return list.find((w) => w && w.id === widgetId) ||
|
|
3154
|
+
return list.find((w) => w && w.id === widgetId) || this.findViewWidget(widgetId)
|
|
3011
3155
|
},
|
|
3012
3156
|
|
|
3013
3157
|
/**
|
|
@@ -4021,6 +4165,19 @@ export default {
|
|
|
4021
4165
|
padding: 60px 20px;
|
|
4022
4166
|
}
|
|
4023
4167
|
|
|
4168
|
+
/* The page's view switch on a page without a header row: at the end of its
|
|
4169
|
+
own row, above the grids, where the header would have put it. */
|
|
4170
|
+
.cn-dashboard-page__view-switch {
|
|
4171
|
+
display: flex;
|
|
4172
|
+
justify-content: flex-end;
|
|
4173
|
+
margin-bottom: 12px;
|
|
4174
|
+
}
|
|
4175
|
+
|
|
4176
|
+
/* A view the switch shows with nothing to draw. */
|
|
4177
|
+
.cn-page-view-region--empty {
|
|
4178
|
+
padding: 40px 20px;
|
|
4179
|
+
}
|
|
4180
|
+
|
|
4024
4181
|
.cn-dashboard-page__page-filters {
|
|
4025
4182
|
display: flex;
|
|
4026
4183
|
flex-wrap: wrap;
|
|
@@ -91,9 +91,9 @@
|
|
|
91
91
|
</label>
|
|
92
92
|
</div>
|
|
93
93
|
|
|
94
|
-
<!-- string:
|
|
94
|
+
<!-- string: contains (OpenRegister `[like]`, case-insensitive) -->
|
|
95
95
|
<label v-else class="cn-column-filter__field">
|
|
96
|
-
<span class="cn-column-filter__label">{{
|
|
96
|
+
<span class="cn-column-filter__label">{{ containsLabel }}</span>
|
|
97
97
|
<input
|
|
98
98
|
v-model="draft.value"
|
|
99
99
|
class="cn-column-filter__input"
|
|
@@ -131,7 +131,7 @@ let popoverSeq = 0
|
|
|
131
131
|
*
|
|
132
132
|
* Renders the control that fits the column: a checkbox list for an enum, yes,
|
|
133
133
|
* no or any for a boolean, from and to for a number or a date, a searchable
|
|
134
|
-
* list of referenced objects for a reference, and
|
|
134
|
+
* list of referenced objects for a reference, and a contains box for text.
|
|
135
135
|
* Real inputs throughout, so every control works from the keyboard. Escape
|
|
136
136
|
* closes it and the host returns focus to the filter button.
|
|
137
137
|
*
|
|
@@ -265,8 +265,14 @@ export default {
|
|
|
265
265
|
return t('nextcloud-vue', 'To')
|
|
266
266
|
},
|
|
267
267
|
|
|
268
|
-
|
|
269
|
-
|
|
268
|
+
/**
|
|
269
|
+
* Label of the text box: the text filter matches on contains.
|
|
270
|
+
*
|
|
271
|
+
* @return {string}
|
|
272
|
+
* @spec openspec/changes/header-filter-contains/specs/cn-data-table/spec.md#requirement-a-text-header-filter-matches-on-contains
|
|
273
|
+
*/
|
|
274
|
+
containsLabel() {
|
|
275
|
+
return t('nextcloud-vue', 'Contains')
|
|
270
276
|
},
|
|
271
277
|
|
|
272
278
|
clearLabel() {
|
|
@@ -145,6 +145,18 @@
|
|
|
145
145
|
</div>
|
|
146
146
|
</slot>
|
|
147
147
|
<div class="cn-detail-page__header-actions">
|
|
148
|
+
<!-- The page's view switch (`views`), unless a greeting header
|
|
149
|
+
widget on the page draws it. Its options control the view
|
|
150
|
+
region in the body. -->
|
|
151
|
+
<CnSegmentedControl
|
|
152
|
+
v-if="showsPageViewSwitch"
|
|
153
|
+
class="cn-page-view-switch"
|
|
154
|
+
:options="viewSwitchOptions"
|
|
155
|
+
:modelValue="activeViewId"
|
|
156
|
+
:ariaLabel="viewSwitchLabel"
|
|
157
|
+
:controls="viewRegionId"
|
|
158
|
+
data-testid="cn-detail-page-view-switch"
|
|
159
|
+
@update:modelValue="selectView" />
|
|
148
160
|
<!-- Next and previous inside the list this record was opened
|
|
149
161
|
from. Rendered only when the address names that list: a
|
|
150
162
|
record reached by a bare link offers neither, rather than
|
|
@@ -609,15 +621,19 @@
|
|
|
609
621
|
subscribed to the layout/widgets arrays — in-place pushes
|
|
610
622
|
(Add widget) keep the same array identity and can't re-render
|
|
611
623
|
it. One remount per edit flip re-subscribes it. -->
|
|
624
|
+
<!-- One grid per section: the body grid, then the chosen view
|
|
625
|
+
(`views`) in a region the view switch controls. A view grid is
|
|
626
|
+
keyed on its view, so switching mounts the other view's grid. -->
|
|
612
627
|
<CnDashboardGrid
|
|
613
|
-
v-
|
|
614
|
-
:key="`cn-detail-grid-${editingBody ? 'editing' : 'live'}`"
|
|
615
|
-
|
|
628
|
+
v-for="section in bodyGridSections"
|
|
629
|
+
:key="`cn-detail-grid-${section.key}-${editingBody ? 'editing' : 'live'}`"
|
|
630
|
+
v-bind="section.attrs"
|
|
631
|
+
:layout="section.layout"
|
|
616
632
|
:editable="editingBody"
|
|
617
633
|
:columns="12"
|
|
618
634
|
:columnOpts="columnOpts"
|
|
619
635
|
class="cn-detail-page__grid"
|
|
620
|
-
@layoutChange="onBodyLayoutChange">
|
|
636
|
+
@layoutChange="section.view ? onViewLayoutChange($event) : onBodyLayoutChange($event)">
|
|
621
637
|
<template #widget="{ item }">
|
|
622
638
|
<div
|
|
623
639
|
class="cn-detail-page__grid-item"
|
|
@@ -727,6 +743,22 @@
|
|
|
727
743
|
</template>
|
|
728
744
|
</CnDashboardGrid>
|
|
729
745
|
|
|
746
|
+
<!-- The chosen view has nothing to draw: say so in the region,
|
|
747
|
+
never leave a blank area under the switch. -->
|
|
748
|
+
<div
|
|
749
|
+
v-if="viewIsEmpty"
|
|
750
|
+
:id="viewRegionId"
|
|
751
|
+
role="region"
|
|
752
|
+
:aria-label="activeView ? activeView.label : null"
|
|
753
|
+
class="cn-page-view-region cn-page-view-region--empty"
|
|
754
|
+
data-testid="cn-detail-page-view-empty">
|
|
755
|
+
<NcEmptyContent :description="viewEmptyText">
|
|
756
|
+
<template #icon>
|
|
757
|
+
<ViewDashboardOutline :size="48" />
|
|
758
|
+
</template>
|
|
759
|
+
</NcEmptyContent>
|
|
760
|
+
</div>
|
|
761
|
+
|
|
730
762
|
<!-- Statistics table -->
|
|
731
763
|
<div v-if="hasStats" class="cn-detail-page__stats">
|
|
732
764
|
<!--
|
|
@@ -1036,6 +1068,7 @@ import InformationOutline from 'vue-material-design-icons/InformationOutline.vue
|
|
|
1036
1068
|
import Pencil from 'vue-material-design-icons/Pencil.vue'
|
|
1037
1069
|
import Plus from 'vue-material-design-icons/Plus.vue'
|
|
1038
1070
|
import Refresh from 'vue-material-design-icons/Refresh.vue'
|
|
1071
|
+
import ViewDashboardOutline from 'vue-material-design-icons/ViewDashboardOutline.vue'
|
|
1039
1072
|
import CnRelationLinkModal from '../../dialogs/CnRelationLinkModal.vue'
|
|
1040
1073
|
import CnWidgetStyleEditorModal from '../../dialogs/CnWidgetStyleEditorModal.vue'
|
|
1041
1074
|
import CnActionsMenu from '../CnActionsMenu/CnActionsMenu.vue'
|
|
@@ -1048,6 +1081,7 @@ import CnLifecycleActions from '../CnLifecycleActions/CnLifecycleActions.vue'
|
|
|
1048
1081
|
import CnLockedBanner from '../CnLockedBanner/CnLockedBanner.vue'
|
|
1049
1082
|
import CnNextStepCard from '../CnNextStepCard/CnNextStepCard.vue'
|
|
1050
1083
|
import CnRelatedCollections from '../CnRelatedCollections/CnRelatedCollections.vue'
|
|
1084
|
+
import CnSegmentedControl from '../CnSegmentedControl/CnSegmentedControl.vue'
|
|
1051
1085
|
import CnSummaryAggregates from '../CnSummaryAggregates/CnSummaryAggregates.vue'
|
|
1052
1086
|
import CnTranslatedBadge from '../CnTranslatedBadge/CnTranslatedBadge.vue'
|
|
1053
1087
|
import { useIntegrationRegistry } from '../../composables/useIntegrationRegistry.js'
|
|
@@ -1055,6 +1089,7 @@ import { useObjectLock } from '../../composables/useObjectLock.js'
|
|
|
1055
1089
|
import { useObjectPresence } from '../../composables/useObjectPresence.js'
|
|
1056
1090
|
import { useObjectSubscription } from '../../composables/useObjectSubscription.js'
|
|
1057
1091
|
import { gridLayout } from '../../mixins/gridLayout.js'
|
|
1092
|
+
import { pageViews } from '../../mixins/pageViews.js'
|
|
1058
1093
|
import { useObjectStore } from '../../store/index.js'
|
|
1059
1094
|
import { isAppInstalled } from '../../utils/appInstalled.js'
|
|
1060
1095
|
import { compactLayoutRows } from '../../utils/dashboardPlacement.js'
|
|
@@ -1227,6 +1262,8 @@ export default {
|
|
|
1227
1262
|
CnDetailWidgetHost,
|
|
1228
1263
|
CnFormDialog,
|
|
1229
1264
|
CnDashboardGrid,
|
|
1265
|
+
CnSegmentedControl,
|
|
1266
|
+
ViewDashboardOutline,
|
|
1230
1267
|
CnLifecycleActions,
|
|
1231
1268
|
CnActionButtons,
|
|
1232
1269
|
CnBreadcrumbs,
|
|
@@ -1242,7 +1279,7 @@ export default {
|
|
|
1242
1279
|
Pencil,
|
|
1243
1280
|
},
|
|
1244
1281
|
|
|
1245
|
-
mixins: [gridLayout],
|
|
1282
|
+
mixins: [gridLayout, pageViews],
|
|
1246
1283
|
|
|
1247
1284
|
inject: {
|
|
1248
1285
|
/**
|
|
@@ -3462,6 +3499,7 @@ export default {
|
|
|
3462
3499
|
&& this.currentObject
|
|
3463
3500
|
&& !this.hasDefaultSlotContent
|
|
3464
3501
|
&& !this.hasGridLayout
|
|
3502
|
+
&& !this.hasViews
|
|
3465
3503
|
},
|
|
3466
3504
|
|
|
3467
3505
|
/**
|
|
@@ -3541,6 +3579,55 @@ export default {
|
|
|
3541
3579
|
return this.bodyGridLayout.length > 0
|
|
3542
3580
|
},
|
|
3543
3581
|
|
|
3582
|
+
/**
|
|
3583
|
+
* The chosen view's layout as the grid draws it: items that draw
|
|
3584
|
+
* nothing leave and their rows close, as for the body grid.
|
|
3585
|
+
*
|
|
3586
|
+
* @return {Array<object>}
|
|
3587
|
+
* @spec openspec/changes/view-switch-containers/specs/view-switch-containers/spec.md#requirement-a-view-with-nothing-to-draw-says-so
|
|
3588
|
+
*/
|
|
3589
|
+
viewDisplayLayout() {
|
|
3590
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions -- reading the flag IS the effect, as in bodyGridLayout
|
|
3591
|
+
this.editingBody
|
|
3592
|
+
return this.activeView ? this.closeDroppedRows(this.activeView.layout) : []
|
|
3593
|
+
},
|
|
3594
|
+
|
|
3595
|
+
/**
|
|
3596
|
+
* Whether the chosen view has nothing to draw.
|
|
3597
|
+
*
|
|
3598
|
+
* @return {boolean}
|
|
3599
|
+
* @spec openspec/changes/view-switch-containers/specs/view-switch-containers/spec.md#requirement-a-view-with-nothing-to-draw-says-so
|
|
3600
|
+
*/
|
|
3601
|
+
viewIsEmpty() {
|
|
3602
|
+
return this.hasViews && this.viewDisplayLayout.length === 0
|
|
3603
|
+
},
|
|
3604
|
+
|
|
3605
|
+
/**
|
|
3606
|
+
* The grids the body draws: the body grid when it has items, then the
|
|
3607
|
+
* chosen view's grid in the region the view switch controls (left out
|
|
3608
|
+
* when the view is empty; the empty state takes the region then).
|
|
3609
|
+
*
|
|
3610
|
+
* @return {Array<{key: string, view: boolean, layout: Array<object>, attrs: object}>}
|
|
3611
|
+
* @spec openspec/changes/view-switch-containers/specs/view-switch-containers/spec.md#requirement-a-page-declares-views-that-each-hold-a-widget-grid
|
|
3612
|
+
*/
|
|
3613
|
+
bodyGridSections() {
|
|
3614
|
+
const sections = this.hasBodyGrid ? [{ key: 'body', view: false, layout: this.bodyGridLayout, attrs: {} }] : []
|
|
3615
|
+
if (this.hasViews && !this.viewIsEmpty) {
|
|
3616
|
+
sections.push({
|
|
3617
|
+
key: `view-${this.activeViewId}`,
|
|
3618
|
+
view: true,
|
|
3619
|
+
layout: this.viewDisplayLayout,
|
|
3620
|
+
attrs: {
|
|
3621
|
+
id: this.viewRegionId,
|
|
3622
|
+
role: 'region',
|
|
3623
|
+
'aria-label': this.activeView.label,
|
|
3624
|
+
'data-testid': 'cn-detail-page-view-region',
|
|
3625
|
+
},
|
|
3626
|
+
})
|
|
3627
|
+
}
|
|
3628
|
+
return sections
|
|
3629
|
+
},
|
|
3630
|
+
|
|
3544
3631
|
/**
|
|
3545
3632
|
* Whether the sidebar is rendered externally (via objectSidebarState inject)
|
|
3546
3633
|
* rather than inline. When external, CnDetailPage only manages state —
|
|
@@ -4762,6 +4849,8 @@ export default {
|
|
|
4762
4849
|
*/
|
|
4763
4850
|
findWidget(item) {
|
|
4764
4851
|
return this.bodyGridWidgets.find((w) => w.id === item.widgetId)
|
|
4852
|
+
|| this.findViewWidget(item.widgetId)
|
|
4853
|
+
|| undefined
|
|
4765
4854
|
},
|
|
4766
4855
|
|
|
4767
4856
|
/**
|
|
@@ -135,7 +135,9 @@ export default {
|
|
|
135
135
|
if (!store) {
|
|
136
136
|
return
|
|
137
137
|
}
|
|
138
|
-
|
|
138
|
+
// `schema` is a slug the manifest author wrote, not a `$ref` title:
|
|
139
|
+
// use it exactly as given (a camelCase slug must not be kebab-cased).
|
|
140
|
+
const type = resolveObjectOpType(store, { register: this.register, schema: this.schema }, { exactSchema: true })
|
|
139
141
|
await Promise.all(this.ids.map(async (id) => {
|
|
140
142
|
if (this.labels[id]) {
|
|
141
143
|
return
|