@conduction/nextcloud-vue 2.106.0 → 2.107.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/CnActionDialog/CnActionDialog.vue.js +9 -0
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue.js.map +1 -0
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js +338 -0
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js.map +1 -0
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js +249 -0
- package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js.map +1 -0
- package/dist/esm/components/CnAdvancedFormDialog/CnPropertiesTab.vue2.js +2 -2
- package/dist/esm/components/CnAdvancedFormDialog/CnPropertiesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +145 -138
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +102 -2
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +83 -6
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +4 -2
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIcon/semanticIcons.js +2 -2
- package/dist/esm/components/CnIcon/semanticIcons.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +389 -20
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +68 -35
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/useSelfFetchList.js +22 -2
- package/dist/esm/components/CnIndexPage/useSelfFetchList.js.map +1 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js +69 -5
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js +38 -5
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
- package/dist/esm/components/CnMapWidget/CnMapWidget.vue.js +1 -1
- package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js +7 -1
- package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnMapWidget/CnMapWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue.js +8 -0
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue.js.map +1 -0
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue2.js +297 -0
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue2.js.map +1 -0
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue3.js +24 -0
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue3.js.map +1 -0
- package/dist/esm/components/CnSavedViewEditDialog/CnSavedViewEditDialog.vue.js +9 -0
- package/dist/esm/components/CnSavedViewEditDialog/CnSavedViewEditDialog.vue.js.map +1 -0
- package/dist/esm/components/CnSavedViewEditDialog/CnSavedViewEditDialog.vue2.js +95 -0
- package/dist/esm/components/CnSavedViewEditDialog/CnSavedViewEditDialog.vue2.js.map +1 -0
- package/dist/esm/components/CnSavedViewEditDialog/CnSavedViewEditDialog.vue3.js +123 -0
- package/dist/esm/components/CnSavedViewEditDialog/CnSavedViewEditDialog.vue3.js.map +1 -0
- package/dist/esm/components/CnSavedViewShareDialog/CnSavedViewShareDialog.vue.js +1 -1
- package/dist/esm/components/CnSavedViewShareDialog/CnSavedViewShareDialog.vue2.js +13 -5
- package/dist/esm/components/CnSavedViewShareDialog/CnSavedViewShareDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnSavedViewShareDialog/CnSavedViewShareDialog.vue3.js +3 -1
- package/dist/esm/components/CnSavedViewShareDialog/CnSavedViewShareDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnSavedViewShareFields/CnSavedViewShareFields.vue.js +1 -1
- package/dist/esm/components/CnSavedViewShareFields/CnSavedViewShareFields.vue2.js +49 -23
- package/dist/esm/components/CnSavedViewShareFields/CnSavedViewShareFields.vue2.js.map +1 -1
- package/dist/esm/components/CnSavedViewShareFields/CnSavedViewShareFields.vue3.js +74 -27
- package/dist/esm/components/CnSavedViewShareFields/CnSavedViewShareFields.vue3.js.map +1 -1
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue.js +0 -1
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue.js.map +1 -1
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js +18 -413
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js.map +1 -1
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js +27 -214
- package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js.map +1 -1
- package/dist/esm/components/CnSavedViewsPanel/CnSavedViewsPanel.vue.js +9 -0
- package/dist/esm/components/CnSavedViewsPanel/CnSavedViewsPanel.vue.js.map +1 -0
- package/dist/esm/components/CnSavedViewsPanel/CnSavedViewsPanel.vue2.js +190 -0
- package/dist/esm/components/CnSavedViewsPanel/CnSavedViewsPanel.vue2.js.map +1 -0
- package/dist/esm/components/CnSavedViewsPanel/CnSavedViewsPanel.vue3.js +270 -0
- package/dist/esm/components/CnSavedViewsPanel/CnSavedViewsPanel.vue3.js.map +1 -0
- package/dist/esm/index.js +4 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +56 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +56 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/library-style.js +1 -1
- package/dist/esm/mixins/savedViewsList.js +235 -0
- package/dist/esm/mixins/savedViewsList.js.map +1 -0
- package/dist/esm/nextcloud-vue.css +348 -108
- package/dist/esm/utils/actionsDispatcher.js +2 -2
- package/dist/esm/utils/actionsDispatcher.js.map +1 -1
- package/dist/esm/utils/builtInModals.js +39 -0
- package/dist/esm/utils/builtInModals.js.map +1 -0
- package/dist/esm/utils/mapMarkerColor.js +121 -0
- package/dist/esm/utils/mapMarkerColor.js.map +1 -0
- package/dist/esm/utils/mergeManifestDelta.js +4 -0
- package/dist/esm/utils/mergeManifestDelta.js.map +1 -1
- package/dist/esm/utils/savedViewHelpers.js +59 -7
- package/dist/esm/utils/savedViewHelpers.js.map +1 -1
- package/dist/esm/utils/savedViewMenu.js +154 -0
- package/dist/esm/utils/savedViewMenu.js.map +1 -0
- package/dist/esm/utils/searchGroupSharees.js +50 -15
- package/dist/esm/utils/searchGroupSharees.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 +13351 -10855
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +348 -108
- package/l10n/en.json +56 -1
- package/l10n/nl.json +56 -1
- package/package.json +1 -1
- package/src/components/CnActionDialog/CnActionDialog.vue +545 -0
- package/src/components/CnActionDialog/index.js +12 -0
- package/src/components/CnAppNav/CnAppNav.vue +17 -0
- package/src/components/CnAppRoot/CnAppRoot.vue +102 -2
- package/src/components/CnDashboardPage/CnDashboardPage.vue +86 -7
- package/src/components/CnIndexPage/CnIndexPage.vue +409 -23
- package/src/components/CnIndexPage/useSelfFetchList.js +21 -1
- package/src/components/CnIndexSidebar/CnIndexSidebar.md +2 -0
- package/src/components/CnIndexSidebar/CnIndexSidebar.vue +91 -1
- package/src/components/CnMapWidget/CnMapWidget.vue +16 -1
- package/src/components/CnPartyPicker/CnPartyPicker.vue +317 -0
- package/src/components/CnPartyPicker/index.js +11 -0
- package/src/components/CnSavedViewEditDialog/CnSavedViewEditDialog.vue +192 -0
- package/src/components/CnSavedViewEditDialog/index.js +11 -0
- package/src/components/CnSavedViewShareDialog/CnSavedViewShareDialog.vue +21 -6
- package/src/components/CnSavedViewShareFields/CnSavedViewShareFields.vue +130 -37
- package/src/components/CnSavedViewsControl/CnSavedViewsControl.vue +36 -599
- package/src/components/CnSavedViewsPanel/CnSavedViewsPanel.vue +459 -0
- package/src/components/CnSavedViewsPanel/index.js +11 -0
- package/src/components/index.js +4 -0
- package/src/css/index-page.css +16 -0
- package/src/css/look-board-index.css +5 -3
- package/src/css/look-board.css +18 -3
- package/src/index.js +4 -0
- package/src/mixins/savedViewsList.js +231 -0
- package/src/schemas/app-manifest-v2.schema.json +98 -3
- package/src/utils/actionsDispatcher.js +2 -2
- package/src/utils/builtInModals.js +35 -0
- package/src/utils/mapMarkerColor.js +117 -0
- package/src/utils/mergeManifestDelta.js +4 -0
- package/src/utils/savedViewHelpers.js +58 -6
- package/src/utils/savedViewMenu.js +150 -0
- package/src/utils/searchGroupSharees.js +49 -14
- package/src/utils/validateManifestV2.compiled.js +3 -3
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
*
|
|
5
|
+
* CnActionDialog — the generic "summary, checklist, reason, confirm" dialog
|
|
6
|
+
* for an act on a record, configured from the manifest.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import CnActionDialog from './CnActionDialog.vue'
|
|
10
|
+
|
|
11
|
+
export { CnActionDialog }
|
|
12
|
+
export default CnActionDialog
|
|
@@ -196,6 +196,15 @@
|
|
|
196
196
|
</NcActionLink>
|
|
197
197
|
</template>
|
|
198
198
|
</NcAppNavigationCaption>
|
|
199
|
+
<!-- `type: "hint"`: a muted line, no link, as the empty "My
|
|
200
|
+
filters" group says how to add a filter. -->
|
|
201
|
+
<li
|
|
202
|
+
v-else-if="item.type === 'hint'"
|
|
203
|
+
:key="item.id"
|
|
204
|
+
class="cn-app-nav__hint"
|
|
205
|
+
:data-testid="`cn-nav-hint-${item.id}`">
|
|
206
|
+
{{ resolveLabel(item) }}
|
|
207
|
+
</li>
|
|
199
208
|
<NcAppNavigationItem
|
|
200
209
|
v-else
|
|
201
210
|
:key="item.id"
|
|
@@ -2474,6 +2483,14 @@ export default {
|
|
|
2474
2483
|
</script>
|
|
2475
2484
|
|
|
2476
2485
|
<style>
|
|
2486
|
+
.cn-app-nav__hint {
|
|
2487
|
+
list-style: none;
|
|
2488
|
+
padding: 4px 12px 8px 16px;
|
|
2489
|
+
font-size: 13px;
|
|
2490
|
+
line-height: 1.4;
|
|
2491
|
+
color: var(--color-text-maxcontrast);
|
|
2492
|
+
}
|
|
2493
|
+
|
|
2477
2494
|
/* The emblem beside the app name (`nav.brand.emblem`). A theme sets
|
|
2478
2495
|
--nldesign-emblem-url for `emblem: true`, and --cn-nav-emblem-size for both. */
|
|
2479
2496
|
.cn-app-nav__brand-emblem {
|
|
@@ -749,6 +749,7 @@ import { useObjectStore } from '../../store/index.js'
|
|
|
749
749
|
import { isAppInstalled } from '../../utils/appInstalled.js'
|
|
750
750
|
import { appShownName } from '../../utils/appShownName.js'
|
|
751
751
|
import { BUILT_IN_FORMATTERS } from '../../utils/builtInFormatters.js'
|
|
752
|
+
import { resolveModalEntry } from '../../utils/builtInModals.js'
|
|
752
753
|
import { addDiagnosticsListener, clearReportedDiagnostics, reportDiagnostic } from '../../utils/diagnostics.js'
|
|
753
754
|
import { DEFAULT_FORGE, resolveForge } from '../../utils/forge.js'
|
|
754
755
|
import { BUILT_IN_KB_PROVIDERS } from '../../utils/kbSearchProviders.js'
|
|
@@ -757,6 +758,7 @@ import { installModalStack, uninstallModalStack } from '../../utils/modalStack.j
|
|
|
757
758
|
import { OBJECTS_CHANGED_EVENT } from '../../utils/objectSignals.js'
|
|
758
759
|
import { resolveBrand } from '../../utils/resolveBrand.js'
|
|
759
760
|
import { resolveFilterTokens } from '../../utils/resolveFilterTokens.js'
|
|
761
|
+
import { normalizeSavedViewMenu, SAVED_VIEW_MENU_KEY, syncSavedViewMenu, toggleSavedViewMenuEntry, withSavedViewMenu } from '../../utils/savedViewMenu.js'
|
|
760
762
|
import { passesContextPredicates } from '../../utils/visibleIfContext.js'
|
|
761
763
|
|
|
762
764
|
/**
|
|
@@ -892,6 +894,10 @@ export default {
|
|
|
892
894
|
return self.userPreferences
|
|
893
895
|
},
|
|
894
896
|
|
|
897
|
+
// The saved views this user added to the menu, and the way to
|
|
898
|
+
// change them, for an index page's sidebar.
|
|
899
|
+
cnSavedViewMenu: self.savedViewMenu,
|
|
900
|
+
|
|
895
901
|
// The same manifest as a REF. The getter above is NOT enough on its
|
|
896
902
|
// own: Options-API `inject` resolves a provided value once, at the
|
|
897
903
|
// descendant's creation, so a renderer created before edit mode gets
|
|
@@ -968,7 +974,7 @@ export default {
|
|
|
968
974
|
* @param {object} props Props forwarded to the modal component.
|
|
969
975
|
*/
|
|
970
976
|
cnOpenModal: (key, props = {}) => {
|
|
971
|
-
const entry = this.registry
|
|
977
|
+
const entry = resolveModalEntry(this.registry, key)
|
|
972
978
|
if (!entry || entry.kind !== 'modal') {
|
|
973
979
|
// eslint-disable-next-line no-console
|
|
974
980
|
console.warn(`[CnAppRoot] cnOpenModal: "${key}" is not a registered modal (kind must be "modal").`)
|
|
@@ -1910,6 +1916,16 @@ export default {
|
|
|
1910
1916
|
data() {
|
|
1911
1917
|
const willCheck = Array.isArray(this.requiresApps) && this.requiresApps.length > 0
|
|
1912
1918
|
return {
|
|
1919
|
+
// Saved views this user added to the menu (per-user preference).
|
|
1920
|
+
// One object for the whole session, so the provide hands out a
|
|
1921
|
+
// live reference: `entries` changes, the holder does not.
|
|
1922
|
+
savedViewMenuLoaded: false,
|
|
1923
|
+
savedViewMenu: {
|
|
1924
|
+
entries: [],
|
|
1925
|
+
toggle: (entry) => this.toggleSavedViewMenu(entry),
|
|
1926
|
+
sync: (page, views) => this.syncSavedViewMenu(page, views),
|
|
1927
|
+
},
|
|
1928
|
+
|
|
1913
1929
|
// Nextcloud's own header, present on a server-rendered page; the
|
|
1914
1930
|
// brand block goes into it (CnBrandBar). Read once at creation so
|
|
1915
1931
|
// the navigation never draws a block that then moves.
|
|
@@ -2377,6 +2393,16 @@ export default {
|
|
|
2377
2393
|
return groups.some((g) => owners.includes(g))
|
|
2378
2394
|
},
|
|
2379
2395
|
|
|
2396
|
+
/**
|
|
2397
|
+
* Whether any page of the manifest offers saved views.
|
|
2398
|
+
*
|
|
2399
|
+
* @return {boolean}
|
|
2400
|
+
*/
|
|
2401
|
+
offersSavedViews() {
|
|
2402
|
+
const pages = this.manifest && Array.isArray(this.manifest.pages) ? this.manifest.pages : []
|
|
2403
|
+
return pages.some((page) => page && page.config && page.config.allowSavedViews === true)
|
|
2404
|
+
},
|
|
2405
|
+
|
|
2380
2406
|
/**
|
|
2381
2407
|
* The manifest the default `<CnAppNav>` renders — the editor's working
|
|
2382
2408
|
* `source` while in-app editing, else the live `manifest` prop. Passed to
|
|
@@ -2408,6 +2434,12 @@ export default {
|
|
|
2408
2434
|
if (this.manifestEditor && this.manifestEditor.editing.value && m && typeof m === 'object') {
|
|
2409
2435
|
return { ...m }
|
|
2410
2436
|
}
|
|
2437
|
+
// An app that offers saved views ends its menu with the "My views"
|
|
2438
|
+
// group: the user's pinned views, or one line saying how to add one.
|
|
2439
|
+
// Any other app gets its live manifest by identity.
|
|
2440
|
+
if (m && typeof m === 'object' && Array.isArray(m.menu) && (this.savedViewMenu.entries.length > 0 || this.offersSavedViews)) {
|
|
2441
|
+
return { ...m, menu: withSavedViewMenu(m.menu, this.savedViewMenu.entries, { showEmpty: this.offersSavedViews }) }
|
|
2442
|
+
}
|
|
2411
2443
|
return m
|
|
2412
2444
|
},
|
|
2413
2445
|
|
|
@@ -3245,12 +3277,24 @@ export default {
|
|
|
3245
3277
|
if (!this.activeModalKey) {
|
|
3246
3278
|
return null
|
|
3247
3279
|
}
|
|
3248
|
-
const entry = (this.registry
|
|
3280
|
+
const entry = resolveModalEntry(this.registry, this.activeModalKey)
|
|
3249
3281
|
return (entry && entry.component) ? entry.component : null
|
|
3250
3282
|
},
|
|
3251
3283
|
},
|
|
3252
3284
|
|
|
3253
3285
|
watch: {
|
|
3286
|
+
// Only an app with a page that offers saved views reads the list,
|
|
3287
|
+
// once, when such a page first appears in the manifest.
|
|
3288
|
+
offersSavedViews: {
|
|
3289
|
+
immediate: true,
|
|
3290
|
+
handler(offers) {
|
|
3291
|
+
if (offers && !this.savedViewMenuLoaded) {
|
|
3292
|
+
this.savedViewMenuLoaded = true
|
|
3293
|
+
this.loadSavedViewMenu()
|
|
3294
|
+
}
|
|
3295
|
+
},
|
|
3296
|
+
},
|
|
3297
|
+
|
|
3254
3298
|
/**
|
|
3255
3299
|
* Close a registry modal when the route changes.
|
|
3256
3300
|
*
|
|
@@ -3392,6 +3436,62 @@ export default {
|
|
|
3392
3436
|
},
|
|
3393
3437
|
|
|
3394
3438
|
methods: {
|
|
3439
|
+
/**
|
|
3440
|
+
* Read the saved views this user added to the menu.
|
|
3441
|
+
*
|
|
3442
|
+
* @return {Promise<void>}
|
|
3443
|
+
* @spec openspec/changes/saved-views-manage-and-defaults/tasks.md#task-7
|
|
3444
|
+
*/
|
|
3445
|
+
async loadSavedViewMenu() {
|
|
3446
|
+
const read = this.userPreferences && this.userPreferences.read
|
|
3447
|
+
if (typeof read !== 'function') {
|
|
3448
|
+
return
|
|
3449
|
+
}
|
|
3450
|
+
try {
|
|
3451
|
+
this.savedViewMenu.entries = normalizeSavedViewMenu(await read(SAVED_VIEW_MENU_KEY, []))
|
|
3452
|
+
} catch {
|
|
3453
|
+
// No menu entries; the views themselves are unaffected.
|
|
3454
|
+
}
|
|
3455
|
+
},
|
|
3456
|
+
|
|
3457
|
+
/**
|
|
3458
|
+
* Add a saved view to the menu, or remove it, and store the list.
|
|
3459
|
+
*
|
|
3460
|
+
* @param {{id: string, page: string, name: string}} entry The view.
|
|
3461
|
+
* @return {Promise<void>}
|
|
3462
|
+
* @spec openspec/changes/saved-views-manage-and-defaults/tasks.md#task-7
|
|
3463
|
+
*/
|
|
3464
|
+
async toggleSavedViewMenu(entry) {
|
|
3465
|
+
this.savedViewMenu.entries = toggleSavedViewMenuEntry(this.savedViewMenu.entries, entry)
|
|
3466
|
+
await this.writeSavedViewMenu()
|
|
3467
|
+
},
|
|
3468
|
+
|
|
3469
|
+
/**
|
|
3470
|
+
* Bring one page's menu entries in line with the views it lists, and
|
|
3471
|
+
* store the list only when that changed something.
|
|
3472
|
+
*
|
|
3473
|
+
* @param {string} page The page's route name.
|
|
3474
|
+
* @param {Array<object>} views The views the page lists.
|
|
3475
|
+
* @return {Promise<void>}
|
|
3476
|
+
* @spec openspec/changes/saved-views-manage-and-defaults/tasks.md#task-7
|
|
3477
|
+
*/
|
|
3478
|
+
async syncSavedViewMenu(page, views) {
|
|
3479
|
+
const next = syncSavedViewMenu(this.savedViewMenu.entries, page, views)
|
|
3480
|
+
if (JSON.stringify(next) === JSON.stringify(this.savedViewMenu.entries)) {
|
|
3481
|
+
return
|
|
3482
|
+
}
|
|
3483
|
+
this.savedViewMenu.entries = next
|
|
3484
|
+
await this.writeSavedViewMenu()
|
|
3485
|
+
},
|
|
3486
|
+
|
|
3487
|
+
/** @return {Promise<void>} Store the list in the user's preferences. */
|
|
3488
|
+
async writeSavedViewMenu() {
|
|
3489
|
+
const write = this.userPreferences && this.userPreferences.write
|
|
3490
|
+
if (typeof write === 'function') {
|
|
3491
|
+
await write(SAVED_VIEW_MENU_KEY, this.savedViewMenu.entries)
|
|
3492
|
+
}
|
|
3493
|
+
},
|
|
3494
|
+
|
|
3395
3495
|
/** Listen for the host's `diagnostics` function while it is set; adds this root's page id. */
|
|
3396
3496
|
syncDiagnostics() {
|
|
3397
3497
|
if (this.removeDiagnostics) {
|
|
@@ -351,7 +351,9 @@
|
|
|
351
351
|
<NcSelect
|
|
352
352
|
:modelValue="selectedPageFilterOption(pf)"
|
|
353
353
|
:options="pageFilterSegments(pf)"
|
|
354
|
-
:clearable="
|
|
354
|
+
:clearable="pf.clearable === true"
|
|
355
|
+
:placeholder="pf.placeholder ? effectiveTranslate(pf.placeholder) : undefined"
|
|
356
|
+
:loading="pageFilterLoading[pf.key] === true"
|
|
355
357
|
:inputLabel="pf.label ? effectiveTranslate(pf.label) : pf.key"
|
|
356
358
|
label="label"
|
|
357
359
|
:data-testid="'cn-page-filter-' + pf.key"
|
|
@@ -1036,6 +1038,7 @@ import CnWidgetIcon from '../CnWidgetGrid/CnWidgetIcon.vue'
|
|
|
1036
1038
|
import CnWidgetRefItem from '../CnWidgetRefItem/CnWidgetRefItem.vue'
|
|
1037
1039
|
import CnWidgetRenderer from '../CnWidgetRenderer/CnWidgetRenderer.vue'
|
|
1038
1040
|
import CnWidgetWrapper from '../CnWidgetWrapper/CnWidgetWrapper.vue'
|
|
1041
|
+
import { fetchEndpointSource } from '../../composables/useEndpointSource.js'
|
|
1039
1042
|
import { useIntegrationRegistry } from '../../composables/useIntegrationRegistry.js'
|
|
1040
1043
|
import { normalizeLook } from '../../composables/useLook.js'
|
|
1041
1044
|
import { pageViews } from '../../mixins/pageViews.js'
|
|
@@ -1043,6 +1046,7 @@ import { mergeUserLayout, resolveUserLayoutApi } from '../../store/plugins/dashb
|
|
|
1043
1046
|
import { isAppInstalled } from '../../utils/appInstalled.js'
|
|
1044
1047
|
import { contentHeaderLink } from '../../utils/contentHeaderLink.js'
|
|
1045
1048
|
import { pickDeclaredProps } from '../../utils/declaredProps.js'
|
|
1049
|
+
import { readPath } from '../../utils/readPath.js'
|
|
1046
1050
|
import { userLocale } from '../../utils/userLocale.js'
|
|
1047
1051
|
import { compareVisibleWhen, evaluateWorkspaceCondition, isWorkspaceCondition, readVisibleWhenValue } from '../../utils/visibleWhen.js'
|
|
1048
1052
|
import { canonicalWidgetType } from '../../utils/widgetTypeAliases.js'
|
|
@@ -1177,6 +1181,32 @@ const CHART_PROP_KEYS = [
|
|
|
1177
1181
|
* }]
|
|
1178
1182
|
* ```
|
|
1179
1183
|
*/
|
|
1184
|
+
|
|
1185
|
+
/**
|
|
1186
|
+
* One row of a page filter's `optionsFrom` as a `{ value, label }` option:
|
|
1187
|
+
* `valueField` (default `id`, else the OpenRegister uuid) and `labelField`
|
|
1188
|
+
* (default `title`, else `name`, else the value). Null for a row without a
|
|
1189
|
+
* value.
|
|
1190
|
+
*
|
|
1191
|
+
* @param {unknown} row The row.
|
|
1192
|
+
* @param {{valueField?: string, labelField?: string}} from The `optionsFrom` block.
|
|
1193
|
+
* @return {{value: (string|number), label: string}|null}
|
|
1194
|
+
*/
|
|
1195
|
+
function pageFilterOptionOf(row, from) {
|
|
1196
|
+
if (row === null || row === undefined) {
|
|
1197
|
+
return null
|
|
1198
|
+
}
|
|
1199
|
+
if (typeof row !== 'object') {
|
|
1200
|
+
return { value: row, label: String(row) }
|
|
1201
|
+
}
|
|
1202
|
+
const value = from.valueField ? readPath(row, from.valueField) : (row.id ?? row['@self']?.id ?? row.uuid)
|
|
1203
|
+
if (value === null || value === undefined || value === '') {
|
|
1204
|
+
return null
|
|
1205
|
+
}
|
|
1206
|
+
const label = from.labelField ? readPath(row, from.labelField) : (row.title || row.name)
|
|
1207
|
+
return { value, label: label === null || label === undefined || label === '' ? String(value) : String(label) }
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1180
1210
|
export default {
|
|
1181
1211
|
name: 'CnDashboardPage',
|
|
1182
1212
|
|
|
@@ -1715,10 +1745,16 @@ export default {
|
|
|
1715
1745
|
* group on the header's description line, after the period group
|
|
1716
1746
|
* (DqProcesanalyse "Gemiddeld | Mediaan"). An endpoint widget can read
|
|
1717
1747
|
* the value in its `responsePath` too (`phaseDays.@workspace.measure`).
|
|
1718
|
-
*
|
|
1719
|
-
*
|
|
1748
|
+
* `optionsFrom` reads the options instead of listing them: `{ url,
|
|
1749
|
+
* params?, responsePath?, valueField?, labelField? }` from an app
|
|
1750
|
+
* endpoint, or `{ register, schema, filter?, valueField?, labelField? }`
|
|
1751
|
+
* from OpenRegister objects (value `id`, label `title` or `name` by
|
|
1752
|
+
* default; tokens resolve). `clearable: true` lets the reader clear the
|
|
1753
|
+
* select back to "all" (`placeholder`, e.g. "All case types"), which
|
|
1754
|
+
* writes null and seeds nothing. Empty (the default) renders no
|
|
1755
|
+
* controls and leaves the context untouched.
|
|
1720
1756
|
*
|
|
1721
|
-
* @type {Array<{key: string, label?: string, type?: string, control?: ('select'|'segmented'), placement?: ('row'|'description'), options
|
|
1757
|
+
* @type {Array<{key: string, label?: string, type?: string, control?: ('select'|'segmented'), placement?: ('row'|'description'), options?: Array<{value: (string|number), label: string}>, optionsFrom?: object, clearable?: boolean, placeholder?: string, default?: (string|number)}>}
|
|
1722
1758
|
*/
|
|
1723
1759
|
pageFilters: {
|
|
1724
1760
|
type: Array,
|
|
@@ -1981,6 +2017,10 @@ export default {
|
|
|
1981
2017
|
|
|
1982
2018
|
data() {
|
|
1983
2019
|
return {
|
|
2020
|
+
/** Options read through a page filter's `optionsFrom`, by filter key. */
|
|
2021
|
+
pageFilterFetched: {},
|
|
2022
|
+
/** Whether a page filter's `optionsFrom` read is in flight, by filter key. */
|
|
2023
|
+
pageFilterLoading: {},
|
|
1984
2024
|
isEditing: false,
|
|
1985
2025
|
/**
|
|
1986
2026
|
* This user's arranged layout, when `userLayout` is on.
|
|
@@ -2840,6 +2880,7 @@ export default {
|
|
|
2840
2880
|
this.pushAiContext()
|
|
2841
2881
|
this.initDateRange()
|
|
2842
2882
|
this.initPageFilters()
|
|
2883
|
+
this.loadPageFilterOptions()
|
|
2843
2884
|
this.evaluateWidgetConditions()
|
|
2844
2885
|
this.loadUserLayout()
|
|
2845
2886
|
},
|
|
@@ -2869,7 +2910,7 @@ export default {
|
|
|
2869
2910
|
if (this.workspaceContext[pf.key] !== undefined) {
|
|
2870
2911
|
continue
|
|
2871
2912
|
}
|
|
2872
|
-
const fallback = (pf.options && pf.options.length) ? pf.options[0].value : undefined
|
|
2913
|
+
const fallback = (pf.options && pf.options.length && pf.clearable !== true) ? pf.options[0].value : undefined
|
|
2873
2914
|
const value = pf.default !== undefined ? pf.default : fallback
|
|
2874
2915
|
if (value !== undefined) {
|
|
2875
2916
|
this.workspaceContext[pf.key] = value
|
|
@@ -2901,7 +2942,45 @@ export default {
|
|
|
2901
2942
|
* @return {Array<{value: (string|number), label: string}>}
|
|
2902
2943
|
*/
|
|
2903
2944
|
pageFilterSegments(pf) {
|
|
2904
|
-
|
|
2945
|
+
const options = Array.isArray(pf.options) ? pf.options : (this.pageFilterFetched[pf.key] || [])
|
|
2946
|
+
return options.map((o) => ({ value: o.value, label: o.label ? this.effectiveTranslate(o.label) : String(o.value) }))
|
|
2947
|
+
},
|
|
2948
|
+
|
|
2949
|
+
/**
|
|
2950
|
+
* Read the options of every page filter with `optionsFrom`, then seed
|
|
2951
|
+
* a filter that is not clearable with its first option when it has no
|
|
2952
|
+
* value yet.
|
|
2953
|
+
*
|
|
2954
|
+
* @spec openspec/changes/page-filter-options-from/specs/dashboard-page/spec.md#requirement-a-page-filter-can-read-its-options
|
|
2955
|
+
* @return {Promise<void>}
|
|
2956
|
+
*/
|
|
2957
|
+
async loadPageFilterOptions() {
|
|
2958
|
+
const ctx = { workspace: this.workspaceContext || {} }
|
|
2959
|
+
await Promise.all((this.pageFilters || [])
|
|
2960
|
+
.filter((pf) => pf && pf.key && !Array.isArray(pf.options) && pf.optionsFrom && typeof pf.optionsFrom === 'object')
|
|
2961
|
+
.map(async (pf) => {
|
|
2962
|
+
const from = pf.optionsFrom
|
|
2963
|
+
const source = from.register && from.schema
|
|
2964
|
+
? { url: `/apps/openregister/api/objects/${from.register}/${from.schema}`, params: { _limit: 200, ...(from.filter || {}) }, responsePath: 'results' }
|
|
2965
|
+
: { url: from.url, params: from.params, responsePath: from.responsePath }
|
|
2966
|
+
if (!source.url) {
|
|
2967
|
+
return
|
|
2968
|
+
}
|
|
2969
|
+
this.pageFilterLoading = { ...this.pageFilterLoading, [pf.key]: true }
|
|
2970
|
+
let rows
|
|
2971
|
+
try {
|
|
2972
|
+
const data = await fetchEndpointSource(source, ctx, { fresh: true })
|
|
2973
|
+
rows = Array.isArray(data) ? data : []
|
|
2974
|
+
} catch {
|
|
2975
|
+
rows = []
|
|
2976
|
+
}
|
|
2977
|
+
const options = rows.map((row) => pageFilterOptionOf(row, from)).filter(Boolean)
|
|
2978
|
+
this.pageFilterFetched = { ...this.pageFilterFetched, [pf.key]: options }
|
|
2979
|
+
this.pageFilterLoading = { ...this.pageFilterLoading, [pf.key]: false }
|
|
2980
|
+
if (pf.clearable !== true && pf.default === undefined && this.workspaceContext[pf.key] === undefined && options.length > 0) {
|
|
2981
|
+
this.workspaceContext[pf.key] = options[0].value
|
|
2982
|
+
}
|
|
2983
|
+
}))
|
|
2905
2984
|
},
|
|
2906
2985
|
|
|
2907
2986
|
/**
|
|
@@ -2916,7 +2995,7 @@ export default {
|
|
|
2916
2995
|
if (!pf || !pf.key) {
|
|
2917
2996
|
return
|
|
2918
2997
|
}
|
|
2919
|
-
const value = option && typeof option === 'object' ? option.value : option
|
|
2998
|
+
const value = option && typeof option === 'object' ? option.value : (option ?? null)
|
|
2920
2999
|
this.workspaceContext[pf.key] = value
|
|
2921
3000
|
/**
|
|
2922
3001
|
* @event page-filter-change Emitted when a page-level filter selection changes.
|