@conduction/nextcloud-vue 2.99.0 → 2.101.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 +6 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +2 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue.js +1 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js +22 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js.map +1 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js +26 -16
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +51 -7
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +104 -73
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +25 -8
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +5 -3
- 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 +11 -8
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +13 -4
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue.js +9 -0
- package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue.js.map +1 -0
- package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue2.js +139 -0
- package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue3.js +76 -0
- package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +54 -3
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js +16 -2
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +160 -2
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +206 -94
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/filesReview.js +61 -0
- package/dist/esm/components/CnFilesBrowser/filesReview.js.map +1 -0
- package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue.js +1 -1
- package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue2.js +42 -7
- package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue2.js.map +1 -1
- package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue3.js +121 -57
- package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue3.js.map +1 -1
- package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue2.js +10 -0
- package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue2.js.map +1 -1
- package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue3.js +2 -1
- package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue3.js.map +1 -1
- package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue.js +9 -0
- package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue.js.map +1 -0
- package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue2.js +279 -0
- package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue3.js +122 -0
- package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnGroupedCardsWidget/index.js +29 -0
- package/dist/esm/components/CnGroupedCardsWidget/index.js.map +1 -0
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +387 -78
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +317 -209
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/useNamedSource.js +4 -1
- package/dist/esm/components/CnIndexPage/useNamedSource.js.map +1 -1
- package/dist/esm/components/CnMapWidget/CnMapWidget.vue.js +1 -1
- package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js +15 -0
- 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/CnObjectListWidget/CnObjectListWidget.vue.js +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +14 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +14 -6
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue.js +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +10 -0
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +6 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue.js +1 -1
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js +91 -4
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js.map +1 -1
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js +63 -38
- package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +68 -7
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +256 -240
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue.js +9 -0
- package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue.js.map +1 -0
- package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue2.js +175 -0
- package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue3.js +77 -0
- package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue.js +1 -1
- package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue2.js +38 -9
- package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue2.js.map +1 -1
- package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue3.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue.js +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +51 -4
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +40 -25
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/builtInWidgets.js +4 -0
- package/dist/esm/components/CnWidgetGrid/builtInWidgets.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue.js +1 -1
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js +7 -7
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js.map +1 -1
- package/dist/esm/composables/indexSources.js +167 -20
- package/dist/esm/composables/indexSources.js.map +1 -1
- package/dist/esm/composables/useEndpointSource.js +28 -6
- package/dist/esm/composables/useEndpointSource.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 +43 -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 +1101 -396
- package/dist/esm/utils/languageMapText.js +50 -0
- package/dist/esm/utils/languageMapText.js.map +1 -0
- package/dist/esm/utils/libraryWidgetKeys.js +3 -0
- package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
- package/dist/esm/utils/mapList.js +71 -0
- package/dist/esm/utils/mapList.js.map +1 -0
- 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 +9996 -7378
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +1101 -396
- package/l10n/en.json +43 -1
- package/l10n/nl.json +41 -1
- package/package.json +1 -1
- package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +7 -1
- package/src/components/CnChoiceCards/CnChoiceCards.md +39 -0
- package/src/components/CnChoiceCards/CnChoiceCards.vue +93 -16
- package/src/components/CnDashboardPage/CnDashboardPage.vue +89 -20
- package/src/components/CnDetailPage/CnDetailPage.vue +28 -9
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +13 -4
- package/src/components/CnExportsWidget/CnExportsWidget.vue +233 -0
- package/src/components/CnExportsWidget/index.js +7 -0
- package/src/components/CnFactsWidget/CnFactsWidget.vue +65 -3
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +324 -0
- package/src/components/CnFilesBrowser/filesReview.js +57 -0
- package/src/components/CnFlowsPage/CnFlowBoard.vue +77 -2
- package/src/components/CnFlowsPage/CnFlowEditorPage.vue +11 -0
- package/src/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue +433 -0
- package/src/components/CnGroupedCardsWidget/index.js +28 -0
- package/src/components/CnIndexPage/CnIndexPage.vue +376 -11
- package/src/components/CnIndexPage/useNamedSource.js +4 -1
- package/src/components/CnMapWidget/CnMapWidget.vue +37 -0
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +29 -1
- package/src/components/CnObjectSidebar/CnFilesTab.vue +15 -0
- package/src/components/CnOrderedListField/CnOrderedListField.vue +151 -5
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +114 -7
- package/src/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue +272 -0
- package/src/components/CnSavedViewAlertWidget/index.js +7 -0
- package/src/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue +38 -9
- package/src/components/CnSetupWizard/CnSetupWizard.vue +72 -5
- package/src/components/CnWidgetGrid/builtInWidgets.js +4 -0
- package/src/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
- package/src/components/CnWizardDialog/CnWizardDialog.vue +7 -7
- package/src/components/index.js +3 -0
- package/src/composables/indexSources.js +166 -19
- package/src/composables/useEndpointSource.js +28 -6
- package/src/css/detail-page.css +15 -0
- package/src/css/dialog.css +48 -0
- package/src/css/index-page.css +117 -0
- package/src/css/look-board.css +127 -1
- package/src/index.js +1 -0
- package/src/schemas/app-manifest-v2.schema.json +55 -1
- package/src/utils/languageMapText.js +46 -0
- package/src/utils/libraryWidgetKeys.js +3 -0
- package/src/utils/mapList.js +67 -0
- package/src/utils/validateManifestV2.compiled.js +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnFlowEditorPage.vue3.js","sources":["../../../../src/components/CnFlowsPage/CnFlowEditorPage.vue?vue&type=template&id=
|
|
1
|
+
{"version":3,"file":"CnFlowEditorPage.vue3.js","sources":["../../../../src/components/CnFlowsPage/CnFlowEditorPage.vue?vue&type=template&id=4509996d&lang.js"],"sourcesContent":["<!-- SPDX-License-Identifier: EUPL-1.2 -->\n<!-- Copyright (C) 2026 Conduction B.V. -->\n<template>\n\t<CnFlowBoard v-if=\"isBoard\"\n\t\t:id=\"flowId\"\n\t\t:app=\"app\"\n\t\t:run=\"runUuid\"\n\t\t:detailRoute=\"detailRoute\"\n\t\t:listLabel=\"listLabel ? cnTranslate(listLabel) : ''\"\n\t\t:palette=\"palette\"\n\t\t:paletteFootnote=\"paletteFootnote ? cnTranslate(paletteFootnote) : ''\"\n\t\t:notice=\"notice ? cnTranslate(notice) : ''\"\n\t\t:pill=\"pill ? cnTranslate(pill) : ''\"\n\t\t:testSubject=\"testSubject ? { ...testSubject, label: testSubject.label ? cnTranslate(testSubject.label) : '' } : null\"\n\t\t@save=\"onSave\"\n\t\t@run=\"onRun\" />\n\t<CnFlowDetail v-else\n\t\t:id=\"flowId\"\n\t\t:app=\"app\"\n\t\t:run=\"runUuid\"\n\t\t@save=\"onSave\"\n\t\t@run=\"onRun\" />\n</template>\n\n<script>\nimport CnFlowDetail from '../CnFlowDetail/CnFlowDetail.vue'\nimport CnFlowBoard from './CnFlowBoard.vue'\nimport { useFlowStore } from '../../composables/useFlowStore.js'\nimport { useLook } from '../../composables/useLook.js'\n\n/**\n * The flow canvas — manifest page type `flow-detail`.\n *\n * Mounts the shared `CnFlowDetail` node/edge canvas over OpenRegister's one\n * native flow store (ADR-065, ADR-098), scoped to the owning app. Pair it with\n * a `type: \"flows\"` page at `/flows`; this one takes `/flows/:id`.\n *\n * Controls belong in Nextcloud's app sidebar so the canvas keeps the full\n * width — declare `\"sidebarComponent\"` on the manifest page, or mount\n * `CnFlowSidebar` yourself. Canvas and sidebar share `useFlowStore`, which is\n * why neither needs props from the other.\n *\n * Manifest declaration:\n *\n * { \"id\": \"FlowDetail\", \"route\": \"/flows/:id\", \"type\": \"flow-detail\",\n * \"title\": \"Flow\", \"config\": { \"app\": \"dossiq\" } }\n */\nexport default {\n\tname: 'CnFlowEditorPage',\n\n\tcomponents: { CnFlowBoard, CnFlowDetail },\n\n\tinject: {\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The owning app id, stamped on a flow created here so the app-scoped\n\t\t * list finds it again. Omit only on the fleet-wide surface.\n\t\t */\n\t\tapp: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * The flow id to open. Defaults to the `id` route param, which is the\n\t\t * normal case; pass it explicitly only when mounting outside a route.\n\t\t * The literal `new` opens an unsaved flow.\n\t\t */\n\t\tid: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Route path the editor lives under, used to swap the URL once a new\n\t\t * flow gets its server-assigned id.\n\t\t */\n\t\tdetailRoute: {\n\t\t\ttype: String,\n\t\t\tdefault: '/flows',\n\t\t},\n\n\t\t/**\n\t\t * A run to open on arrival. Defaults to the `run` query parameter,\n\t\t * which is where a run's own address lands: `/flow-runs/{uuid}` reads\n\t\t * the run, finds its flow and redirects to `/flows/{id}?run={uuid}`.\n\t\t * Pass it explicitly only when mounting outside a route.\n\t\t */\n\t\trun: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Draw this page in the `board` look (or `nextcloud`) whatever the app\n\t\t * does. Empty follows the app's look. Manifest key `config.look`.\n\t\t */\n\t\tlook: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Board look: where steps come from. `picker` (default) keeps \"Add a\n\t\t * step\" in the canvas toolbar; `column` draws the palette as a card\n\t\t * beside the canvas, grouped by the app that provides each step.\n\t\t * Manifest key `config.palette`.\n\t\t */\n\t\tpalette: {\n\t\t\ttype: String,\n\t\t\tdefault: 'picker',\n\t\t},\n\n\t\t/**\n\t\t * Board look: a line under the palette card. Manifest key\n\t\t * `config.paletteFootnote`, translated with the app's catalogue.\n\t\t */\n\t\tpaletteFootnote: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Board look: the records a test run can take as its subject,\n\t\t * `{ register, schema, labelField?, label? }`. Manifest key\n\t\t * `config.testSubject`; `label` is translated with the app's catalogue.\n\t\t */\n\t\ttestSubject: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Board look: an info notice under the path. Manifest key\n\t\t * `config.notice`, translated with the app's catalogue.\n\t\t */\n\t\tnotice: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Board look: the pill beside the flow's name (\"Case type flow\").\n\t\t * Empty shows the lifecycle for a draft or retired flow. Manifest key\n\t\t * `config.pill`.\n\t\t */\n\t\tpill: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Board look: the list's name at the start of the path (\"Settings /\n\t\t * Flows\"). Empty reads \"Flows\". Manifest key `config.listLabel`.\n\t\t */\n\t\tlistLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\t/**\n\t * Share the one flow store with the toolbar's save/run handlers, and\n\t * resolve the look (`look` is read here, through `useLook`).\n\t *\n\t * @param {object} props The component props.\n\t * @return {object} The setup bindings.\n\t */\n\tsetup(props) {\n\t\tconst { isBoard } = useLook(props)\n\t\treturn { store: useFlowStore(), isBoard }\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * @return {string} The flow id being edited.\n\t\t */\n\t\tflowId() {\n\t\t\treturn this.id || this.$route?.params?.id || ''\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The run to open on arrival, or an empty string.\n\t\t */\n\t\trunUuid() {\n\t\t\treturn this.run || this.$route?.query?.run || ''\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onSave() {\n\t\t\tconst saved = await this.store.save()\n\t\t\t// A newly created flow gets its id from the server, so the route has\n\t\t\t// to catch up or a reload would land back on `new` and lose it.\n\t\t\tif (saved?.id && this.flowId === 'new') {\n\t\t\t\tthis.$router.replace(`${this.detailRoute}/${saved.id}`)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onRun() {\n\t\t\tawait this.store.run({})\n\t\t},\n\t},\n}\n</script>\n"],"names":["_createBlock"],"mappings":";;;;;;UAKW,MAAA,CAAA,OAAA;oBAATA,WAYW,CAAA,sBAAA,EAAA;;AAXV,QAAA,EAAE,EAAE,QAAM,CAAA,MAAA;AACV,QAAA,GAAG,EAAE,MAAG,CAAA,GAAA;AACR,QAAA,GAAG,EAAE,QAAO,CAAA,OAAA;AACZ,QAAA,WAAW,EAAE,MAAG,CAAA,WAAA;AAChB,QAAA,SAAS,EAAE,MAAA,CAAA,SAAS,GAAG,QAAA,CAAA,WAAU,CAAE,MAAS,CAAA,SAAA,CAAA,GAAA,EAAA;AAC5C,QAAA,OAAO,EAAE,MAAM,CAAA,OAAA;AACf,QAAA,eAAe,EAAE,MAAA,CAAA,eAAc,GAAI,QAAA,CAAA,WAAA,CAAA,MAAA,CAAA,eAAA,CAAA,GAAA,EAAA;AACnC,QAAA,MAAM,EAAE,MAAA,CAAA,MAAM,GAAG,QAAA,CAAA,WAAY,CAAA,MAAM,CAAA,MAAA,CAAA,GAAA,EAAA;AACnC,QAAA,IAAI,EAAE,MAAA,CAAA,IAAI,GAAE,QAAA,CAAA,WAAA,CAAA,MAAA,CAAA,IAAA,CAAA,GAAA,EAAA;AACZ,QAAA,WAAU,EAAG,MAAA,CAAA,WAAA,GAAA,EAAA,GAAA,MAAA,CAAA,WAAA,EAAA,KAAA,EAAA,MAAA,CAAA,WAAA,CAAA,KAAA,GAAA,QAAA,CAAA,WAAA,CAAA,MAAA,CAAA,WAAA,CAAA,KAAA,CAAA,GAAA,EAAA,EAAA,GAAA,IAAA;AACb,QAAA,MAAI,EAAE,QAAM,CAAA,MAAA;AACZ,QAAA,KAAG,EAAE,QAAK,CAAA,KAAA;;oBACXA;;AACC,QAAA,EAAE,EAAE,QAAM,CAAA,MAAA;AACV,QAAA,GAAG,EAAE,MAAG,CAAA,GAAA;AACR,QAAA,GAAG,EAAE,QAAO,CAAA,OAAA;;;;;;;;"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import script from './CnGroupedCardsWidget.vue2.js';
|
|
2
|
+
import { render } from './CnGroupedCardsWidget.vue3.js';
|
|
3
|
+
|
|
4
|
+
script.render = render;
|
|
5
|
+
script.__scopeId = "data-v-cf2cd50a";
|
|
6
|
+
const __sfc_main = script;
|
|
7
|
+
|
|
8
|
+
export { __sfc_main as default };
|
|
9
|
+
//# sourceMappingURL=CnGroupedCardsWidget.vue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CnGroupedCardsWidget.vue.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;"}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { translate } from '@nextcloud/l10n';
|
|
2
|
+
import { NcLoadingIcon } from '@nextcloud/vue';
|
|
3
|
+
import { inject, ref } from 'vue';
|
|
4
|
+
import { useEndpointSource } from '../../composables/useEndpointSource.js';
|
|
5
|
+
import { fillTemplate } from '../../utils/fillTemplate.js';
|
|
6
|
+
import { unwrapAppConfig } from '../../utils/formatMetric.js';
|
|
7
|
+
import { languageMapText } from '../../utils/languageMapText.js';
|
|
8
|
+
import { readPath } from '../../utils/readPath.js';
|
|
9
|
+
|
|
10
|
+
let uid = 0;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Unwrap a value that may be a ref.
|
|
14
|
+
*
|
|
15
|
+
* @param {unknown} value The value.
|
|
16
|
+
* @return {unknown} The plain value.
|
|
17
|
+
*/
|
|
18
|
+
function unwrap(value) {
|
|
19
|
+
return value && typeof value === 'object' && 'value' in value ? value.value : value
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A row as the templates read it: a language map (`{ nl, en }`) becomes its
|
|
24
|
+
* text in the user's language, and a list of plain values is joined with ", ".
|
|
25
|
+
* Nested objects stay as they are, for a template that reads a deeper path.
|
|
26
|
+
*
|
|
27
|
+
* @param {object} row The row.
|
|
28
|
+
* @return {object} A shallow copy with readable values.
|
|
29
|
+
*/
|
|
30
|
+
function readable(row) {
|
|
31
|
+
if (!row || typeof row !== 'object') {
|
|
32
|
+
return {}
|
|
33
|
+
}
|
|
34
|
+
const out = {};
|
|
35
|
+
for (const [key, value] of Object.entries(row)) {
|
|
36
|
+
if (Array.isArray(value)) {
|
|
37
|
+
const plain = value.filter((entry) => typeof entry === 'string' || typeof entry === 'number');
|
|
38
|
+
out[key] = plain.length === value.length ? plain.join(', ') : value;
|
|
39
|
+
} else if (value && typeof value === 'object' && Object.values(value).every((entry) => typeof entry === 'string')) {
|
|
40
|
+
out[key] = languageMapText(value);
|
|
41
|
+
} else {
|
|
42
|
+
out[key] = value;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return out
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* CnGroupedCardsWidget — groups of cards side by side, read from an app
|
|
50
|
+
* endpoint: a case type's phases, each with the tasks it runs, and a dashed
|
|
51
|
+
* box of what is available in every phase (board DqZaaktype, Fasen en taken).
|
|
52
|
+
*
|
|
53
|
+
* Content-only on a card surface: the detail host or the dashboard draws the
|
|
54
|
+
* titled card around it.
|
|
55
|
+
*/
|
|
56
|
+
var script = {
|
|
57
|
+
name: 'CnGroupedCardsWidget',
|
|
58
|
+
|
|
59
|
+
components: { NcLoadingIcon },
|
|
60
|
+
|
|
61
|
+
inject: {
|
|
62
|
+
/** The host app's translate function. */
|
|
63
|
+
cnTranslate: { default: () => (key) => key },
|
|
64
|
+
/** The page's app config map, for money tokens in a template. */
|
|
65
|
+
cnAppConfig: { default: () => ({}) },
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
props: {
|
|
69
|
+
/**
|
|
70
|
+
* The widget config: `{ endpointSource, groupsPath?, itemsKey?,
|
|
71
|
+
* groupTitle?, groupCount?, itemTitle?, itemMeta?, columns?,
|
|
72
|
+
* extraGroup?, emptyText?, groupEmptyText?, errorText? }`.
|
|
73
|
+
*
|
|
74
|
+
* - `endpointSource` (`{ url, params?, responsePath? }`) reads the payload; tokens
|
|
75
|
+
* such as `@objectId` resolve as on every widget.
|
|
76
|
+
* - `groupsPath` (default `groups`) is the list of groups in the payload, and
|
|
77
|
+
* `itemsKey` (default `items`) the list of cards in each group.
|
|
78
|
+
* - `groupTitle` and `itemTitle` are templates (`"Phase {order}: {name}"`,
|
|
79
|
+
* `"{title}"`); `itemMeta` is a template or a list of templates joined
|
|
80
|
+
* with " · ", a part whose token is empty being left out.
|
|
81
|
+
* - `groupCount` (`{ one, other }`, `{count}` filled in) draws a count pill.
|
|
82
|
+
* - `extraGroup` (`{ title, path, itemText, join? }`) closes the grid with a
|
|
83
|
+
* dashed box whose entries, read at `path` of the payload, are one line.
|
|
84
|
+
* - `columns` (default 2) is the number of groups side by side.
|
|
85
|
+
*
|
|
86
|
+
* A language map (`{ nl, en }`) reads in the user's language, and a list
|
|
87
|
+
* of plain values reads joined with ", ". Texts go through the app's
|
|
88
|
+
* translate.
|
|
89
|
+
*
|
|
90
|
+
* @type {object}
|
|
91
|
+
*/
|
|
92
|
+
content: {
|
|
93
|
+
type: Object,
|
|
94
|
+
default: () => ({}),
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
/** The current record's id, for the `@objectId` token. */
|
|
98
|
+
objectId: {
|
|
99
|
+
type: [String, Number],
|
|
100
|
+
default: null,
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
/** The current record, for `@object.<field>` tokens. */
|
|
104
|
+
objectData: {
|
|
105
|
+
type: Object,
|
|
106
|
+
default: null,
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
setup(props) {
|
|
111
|
+
const objectCtxRaw = inject('cnObjectContext', null);
|
|
112
|
+
const workspaceRaw = inject('cnWorkspaceContext', null);
|
|
113
|
+
const appConfigRaw = inject('cnAppConfig', ref({}));
|
|
114
|
+
const { data, loading, error } = useEndpointSource(
|
|
115
|
+
() => props.content?.endpointSource || null,
|
|
116
|
+
{
|
|
117
|
+
ctx: () => {
|
|
118
|
+
const injected = unwrap(objectCtxRaw) || {};
|
|
119
|
+
return {
|
|
120
|
+
...injected,
|
|
121
|
+
objectId: props.objectId ?? injected.objectId ?? props.objectData?.id ?? null,
|
|
122
|
+
object: props.objectData || injected.object || null,
|
|
123
|
+
workspace: unwrap(workspaceRaw) || {},
|
|
124
|
+
config: unwrapAppConfig(appConfigRaw) || {},
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
);
|
|
129
|
+
uid += 1;
|
|
130
|
+
return { payload: data, loading, error, idPrefix: `cn-grouped-cards-${uid}` }
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
computed: {
|
|
134
|
+
/** @return {number} The groups side by side. */
|
|
135
|
+
columns() {
|
|
136
|
+
const value = Number(this.content?.columns);
|
|
137
|
+
return Number.isFinite(value) && value >= 1 ? Math.min(4, Math.floor(value)) : 2
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* The groups ready to draw.
|
|
142
|
+
*
|
|
143
|
+
* @return {Array<{key: string, headingId: string, title: string, count: string, items: Array<object>}>} The groups.
|
|
144
|
+
* @spec openspec/changes/grouped-cards-widget/specs/grouped-cards-widget/spec.md#requirement-a-widget-draws-groups-of-cards-from-an-endpoint
|
|
145
|
+
*/
|
|
146
|
+
groups() {
|
|
147
|
+
const c = this.content || {};
|
|
148
|
+
const list = readPath(this.payload, c.groupsPath || 'groups');
|
|
149
|
+
if (!Array.isArray(list)) {
|
|
150
|
+
return []
|
|
151
|
+
}
|
|
152
|
+
return list.filter((group) => group && typeof group === 'object').map((raw, index) => {
|
|
153
|
+
const group = readable(raw);
|
|
154
|
+
const itemList = Array.isArray(raw[c.itemsKey || 'items']) ? raw[c.itemsKey || 'items'] : [];
|
|
155
|
+
const items = itemList.filter((item) => item && typeof item === 'object').map((rawItem, itemIndex) => {
|
|
156
|
+
const item = readable(rawItem);
|
|
157
|
+
return {
|
|
158
|
+
key: String(rawItem.id ?? `item-${itemIndex}`),
|
|
159
|
+
title: this.fill(c.itemTitle || '{title}', item),
|
|
160
|
+
meta: this.metaOf(item),
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
return {
|
|
164
|
+
key: String(raw.id ?? `group-${index}`),
|
|
165
|
+
headingId: `${this.idPrefix}-group-${index}`,
|
|
166
|
+
title: this.fill(c.groupTitle || '{name}', group),
|
|
167
|
+
count: this.countOf(items.length),
|
|
168
|
+
items,
|
|
169
|
+
}
|
|
170
|
+
})
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* The closing box of entries available everywhere, or null.
|
|
175
|
+
*
|
|
176
|
+
* @return {{headingId: string, title: string, text: string}|null} The box.
|
|
177
|
+
* @spec openspec/changes/grouped-cards-widget/specs/grouped-cards-widget/spec.md#requirement-a-widget-draws-groups-of-cards-from-an-endpoint
|
|
178
|
+
*/
|
|
179
|
+
extra() {
|
|
180
|
+
const spec = this.content?.extraGroup;
|
|
181
|
+
if (!spec || typeof spec !== 'object' || !spec.path) {
|
|
182
|
+
return null
|
|
183
|
+
}
|
|
184
|
+
const list = readPath(this.payload, spec.path);
|
|
185
|
+
if (!Array.isArray(list) || list.length === 0) {
|
|
186
|
+
return null
|
|
187
|
+
}
|
|
188
|
+
const parts = list
|
|
189
|
+
.map((entry) => (entry && typeof entry === 'object' ? this.fill(spec.itemText || '{label}', readable(entry)) : languageMapText(entry)))
|
|
190
|
+
.filter((text) => text !== '');
|
|
191
|
+
if (parts.length === 0) {
|
|
192
|
+
return null
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
headingId: `${this.idPrefix}-extra`,
|
|
196
|
+
title: this.translate(spec.title || 'Always available'),
|
|
197
|
+
text: parts.join(typeof spec.join === 'string' ? spec.join : ' · '),
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
/** @return {string} The empty text. */
|
|
202
|
+
emptyLabel() {
|
|
203
|
+
return this.content?.emptyText ? this.translate(this.content.emptyText) : translate('nextcloud-vue', 'Nothing to show yet')
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
/** @return {string} The text of a group without cards. */
|
|
207
|
+
groupEmptyLabel() {
|
|
208
|
+
return this.content?.groupEmptyText ? this.translate(this.content.groupEmptyText) : translate('nextcloud-vue', 'Nothing here yet')
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
/** @return {string} The error text. */
|
|
212
|
+
errorLabel() {
|
|
213
|
+
return this.content?.errorText ? this.translate(this.content.errorText) : translate('nextcloud-vue', 'This list could not be loaded.')
|
|
214
|
+
},
|
|
215
|
+
|
|
216
|
+
/** @return {string} The loading label. */
|
|
217
|
+
loadingLabel() {
|
|
218
|
+
return translate('nextcloud-vue', 'Loading')
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
|
|
222
|
+
methods: {
|
|
223
|
+
/**
|
|
224
|
+
* Translate a text through the host app's catalogue.
|
|
225
|
+
*
|
|
226
|
+
* @param {string} text The text.
|
|
227
|
+
* @return {string} The translated text.
|
|
228
|
+
*/
|
|
229
|
+
translate(text) {
|
|
230
|
+
const fn = typeof this.cnTranslate === 'function' ? this.cnTranslate : (key) => key;
|
|
231
|
+
return fn(text)
|
|
232
|
+
},
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Fill a template from a row, after translating it.
|
|
236
|
+
*
|
|
237
|
+
* @param {string} template The template.
|
|
238
|
+
* @param {object} row The row.
|
|
239
|
+
* @return {string} The text, '' when a token is empty.
|
|
240
|
+
*/
|
|
241
|
+
fill(template, row) {
|
|
242
|
+
if (typeof template !== 'string' || template === '') {
|
|
243
|
+
return ''
|
|
244
|
+
}
|
|
245
|
+
return fillTemplate(this.translate(template), row, new Date(), { config: unwrapAppConfig(this.cnAppConfig) })
|
|
246
|
+
},
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* A card's meta line: one template, or several joined with " · ", a
|
|
250
|
+
* part whose token is empty being left out with its separator.
|
|
251
|
+
*
|
|
252
|
+
* @param {object} item The card's row.
|
|
253
|
+
* @return {string} The line.
|
|
254
|
+
*/
|
|
255
|
+
metaOf(item) {
|
|
256
|
+
const spec = this.content?.itemMeta;
|
|
257
|
+
const parts = Array.isArray(spec) ? spec : (typeof spec === 'string' ? [spec] : []);
|
|
258
|
+
return parts.map((part) => this.fill(part, item)).filter((text) => text !== '').join(' · ')
|
|
259
|
+
},
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* The count pill of a group (`groupCount: { one, other }`).
|
|
263
|
+
*
|
|
264
|
+
* @param {number} count The number of cards.
|
|
265
|
+
* @return {string} The text, '' without `groupCount`.
|
|
266
|
+
*/
|
|
267
|
+
countOf(count) {
|
|
268
|
+
const spec = this.content?.groupCount;
|
|
269
|
+
if (!spec || typeof spec !== 'object') {
|
|
270
|
+
return ''
|
|
271
|
+
}
|
|
272
|
+
const template = count === 1 ? spec.one : spec.other;
|
|
273
|
+
return typeof template === 'string' && template !== '' ? this.translate(template).replace(/\{count\}/g, String(count)) : ''
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
export { script as default };
|
|
279
|
+
//# sourceMappingURL=CnGroupedCardsWidget.vue2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CnGroupedCardsWidget.vue2.js","sources":["../../../../src/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div class=\"cn-grouped-cards\" data-testid=\"cn-grouped-cards\">\n\t\t<p v-if=\"error\" class=\"cn-grouped-cards__state\" data-testid=\"cn-grouped-cards-error\">\n\t\t\t{{ errorLabel }}\n\t\t</p>\n\t\t<div v-else-if=\"loading && groups.length === 0\" class=\"cn-grouped-cards__loading\">\n\t\t\t<NcLoadingIcon :size=\"20\" :name=\"loadingLabel\" />\n\t\t</div>\n\t\t<p v-else-if=\"groups.length === 0 && !extra\" class=\"cn-grouped-cards__state\" data-testid=\"cn-grouped-cards-empty\">\n\t\t\t{{ emptyLabel }}\n\t\t</p>\n\t\t<div\n\t\t\tv-else\n\t\t\tclass=\"cn-grouped-cards__grid\"\n\t\t\t:style=\"{ '--cn-grouped-cards-columns': columns }\">\n\t\t\t<section\n\t\t\t\tv-for=\"group in groups\"\n\t\t\t\t:key=\"group.key\"\n\t\t\t\tclass=\"cn-grouped-cards__group\"\n\t\t\t\t:aria-labelledby=\"group.headingId\"\n\t\t\t\tdata-testid=\"cn-grouped-cards-group\">\n\t\t\t\t<header class=\"cn-grouped-cards__group-header\">\n\t\t\t\t\t<h4 :id=\"group.headingId\" class=\"cn-grouped-cards__group-title\">\n\t\t\t\t\t\t{{ group.title }}\n\t\t\t\t\t</h4>\n\t\t\t\t\t<span v-if=\"group.count\" class=\"cn-grouped-cards__count\" data-testid=\"cn-grouped-cards-count\">{{ group.count }}</span>\n\t\t\t\t</header>\n\t\t\t\t<ul v-if=\"group.items.length > 0\" class=\"cn-grouped-cards__items\">\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"item in group.items\"\n\t\t\t\t\t\t:key=\"item.key\"\n\t\t\t\t\t\tclass=\"cn-grouped-cards__item\"\n\t\t\t\t\t\tdata-testid=\"cn-grouped-cards-item\">\n\t\t\t\t\t\t<span class=\"cn-grouped-cards__item-title\">{{ item.title }}</span>\n\t\t\t\t\t\t<span v-if=\"item.meta\" class=\"cn-grouped-cards__item-meta\">{{ item.meta }}</span>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t\t<p v-else class=\"cn-grouped-cards__group-empty\">\n\t\t\t\t\t{{ groupEmptyLabel }}\n\t\t\t\t</p>\n\t\t\t</section>\n\t\t\t<section\n\t\t\t\tv-if=\"extra\"\n\t\t\t\tclass=\"cn-grouped-cards__group cn-grouped-cards__group--extra\"\n\t\t\t\t:aria-labelledby=\"extra.headingId\"\n\t\t\t\tdata-testid=\"cn-grouped-cards-extra\">\n\t\t\t\t<header class=\"cn-grouped-cards__group-header\">\n\t\t\t\t\t<h4 :id=\"extra.headingId\" class=\"cn-grouped-cards__group-title\">\n\t\t\t\t\t\t{{ extra.title }}\n\t\t\t\t\t</h4>\n\t\t\t\t</header>\n\t\t\t\t<p class=\"cn-grouped-cards__extra-text\">\n\t\t\t\t\t{{ extra.text }}\n\t\t\t\t</p>\n\t\t\t</section>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport { inject, ref } from 'vue'\nimport { useEndpointSource } from '../../composables/useEndpointSource.js'\nimport { fillTemplate } from '../../utils/fillTemplate.js'\nimport { unwrapAppConfig } from '../../utils/formatMetric.js'\nimport { languageMapText } from '../../utils/languageMapText.js'\nimport { readPath } from '../../utils/readPath.js'\n\nlet uid = 0\n\n/**\n * Unwrap a value that may be a ref.\n *\n * @param {unknown} value The value.\n * @return {unknown} The plain value.\n */\nfunction unwrap(value) {\n\treturn value && typeof value === 'object' && 'value' in value ? value.value : value\n}\n\n/**\n * A row as the templates read it: a language map (`{ nl, en }`) becomes its\n * text in the user's language, and a list of plain values is joined with \", \".\n * Nested objects stay as they are, for a template that reads a deeper path.\n *\n * @param {object} row The row.\n * @return {object} A shallow copy with readable values.\n */\nfunction readable(row) {\n\tif (!row || typeof row !== 'object') {\n\t\treturn {}\n\t}\n\tconst out = {}\n\tfor (const [key, value] of Object.entries(row)) {\n\t\tif (Array.isArray(value)) {\n\t\t\tconst plain = value.filter((entry) => typeof entry === 'string' || typeof entry === 'number')\n\t\t\tout[key] = plain.length === value.length ? plain.join(', ') : value\n\t\t} else if (value && typeof value === 'object' && Object.values(value).every((entry) => typeof entry === 'string')) {\n\t\t\tout[key] = languageMapText(value)\n\t\t} else {\n\t\t\tout[key] = value\n\t\t}\n\t}\n\treturn out\n}\n\n/**\n * CnGroupedCardsWidget — groups of cards side by side, read from an app\n * endpoint: a case type's phases, each with the tasks it runs, and a dashed\n * box of what is available in every phase (board DqZaaktype, Fasen en taken).\n *\n * Content-only on a card surface: the detail host or the dashboard draws the\n * titled card around it.\n */\nexport default {\n\tname: 'CnGroupedCardsWidget',\n\n\tcomponents: { NcLoadingIcon },\n\n\tinject: {\n\t\t/** The host app's translate function. */\n\t\tcnTranslate: { default: () => (key) => key },\n\t\t/** The page's app config map, for money tokens in a template. */\n\t\tcnAppConfig: { default: () => ({}) },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The widget config: `{ endpointSource, groupsPath?, itemsKey?,\n\t\t * groupTitle?, groupCount?, itemTitle?, itemMeta?, columns?,\n\t\t * extraGroup?, emptyText?, groupEmptyText?, errorText? }`.\n\t\t *\n\t\t * - `endpointSource` (`{ url, params?, responsePath? }`) reads the payload; tokens\n\t\t * such as `@objectId` resolve as on every widget.\n\t\t * - `groupsPath` (default `groups`) is the list of groups in the payload, and\n\t\t * `itemsKey` (default `items`) the list of cards in each group.\n\t\t * - `groupTitle` and `itemTitle` are templates (`\"Phase {order}: {name}\"`,\n\t\t * `\"{title}\"`); `itemMeta` is a template or a list of templates joined\n\t\t * with \" · \", a part whose token is empty being left out.\n\t\t * - `groupCount` (`{ one, other }`, `{count}` filled in) draws a count pill.\n\t\t * - `extraGroup` (`{ title, path, itemText, join? }`) closes the grid with a\n\t\t * dashed box whose entries, read at `path` of the payload, are one line.\n\t\t * - `columns` (default 2) is the number of groups side by side.\n\t\t *\n\t\t * A language map (`{ nl, en }`) reads in the user's language, and a list\n\t\t * of plain values reads joined with \", \". Texts go through the app's\n\t\t * translate.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** The current record's id, for the `@objectId` token. */\n\t\tobjectId: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** The current record, for `@object.<field>` tokens. */\n\t\tobjectData: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tsetup(props) {\n\t\tconst objectCtxRaw = inject('cnObjectContext', null)\n\t\tconst workspaceRaw = inject('cnWorkspaceContext', null)\n\t\tconst appConfigRaw = inject('cnAppConfig', ref({}))\n\t\tconst { data, loading, error } = useEndpointSource(\n\t\t\t() => props.content?.endpointSource || null,\n\t\t\t{\n\t\t\t\tctx: () => {\n\t\t\t\t\tconst injected = unwrap(objectCtxRaw) || {}\n\t\t\t\t\treturn {\n\t\t\t\t\t\t...injected,\n\t\t\t\t\t\tobjectId: props.objectId ?? injected.objectId ?? props.objectData?.id ?? null,\n\t\t\t\t\t\tobject: props.objectData || injected.object || null,\n\t\t\t\t\t\tworkspace: unwrap(workspaceRaw) || {},\n\t\t\t\t\t\tconfig: unwrapAppConfig(appConfigRaw) || {},\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t)\n\t\tuid += 1\n\t\treturn { payload: data, loading, error, idPrefix: `cn-grouped-cards-${uid}` }\n\t},\n\n\tcomputed: {\n\t\t/** @return {number} The groups side by side. */\n\t\tcolumns() {\n\t\t\tconst value = Number(this.content?.columns)\n\t\t\treturn Number.isFinite(value) && value >= 1 ? Math.min(4, Math.floor(value)) : 2\n\t\t},\n\n\t\t/**\n\t\t * The groups ready to draw.\n\t\t *\n\t\t * @return {Array<{key: string, headingId: string, title: string, count: string, items: Array<object>}>} The groups.\n\t\t * @spec openspec/changes/grouped-cards-widget/specs/grouped-cards-widget/spec.md#requirement-a-widget-draws-groups-of-cards-from-an-endpoint\n\t\t */\n\t\tgroups() {\n\t\t\tconst c = this.content || {}\n\t\t\tconst list = readPath(this.payload, c.groupsPath || 'groups')\n\t\t\tif (!Array.isArray(list)) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn list.filter((group) => group && typeof group === 'object').map((raw, index) => {\n\t\t\t\tconst group = readable(raw)\n\t\t\t\tconst itemList = Array.isArray(raw[c.itemsKey || 'items']) ? raw[c.itemsKey || 'items'] : []\n\t\t\t\tconst items = itemList.filter((item) => item && typeof item === 'object').map((rawItem, itemIndex) => {\n\t\t\t\t\tconst item = readable(rawItem)\n\t\t\t\t\treturn {\n\t\t\t\t\t\tkey: String(rawItem.id ?? `item-${itemIndex}`),\n\t\t\t\t\t\ttitle: this.fill(c.itemTitle || '{title}', item),\n\t\t\t\t\t\tmeta: this.metaOf(item),\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\treturn {\n\t\t\t\t\tkey: String(raw.id ?? `group-${index}`),\n\t\t\t\t\theadingId: `${this.idPrefix}-group-${index}`,\n\t\t\t\t\ttitle: this.fill(c.groupTitle || '{name}', group),\n\t\t\t\t\tcount: this.countOf(items.length),\n\t\t\t\t\titems,\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * The closing box of entries available everywhere, or null.\n\t\t *\n\t\t * @return {{headingId: string, title: string, text: string}|null} The box.\n\t\t * @spec openspec/changes/grouped-cards-widget/specs/grouped-cards-widget/spec.md#requirement-a-widget-draws-groups-of-cards-from-an-endpoint\n\t\t */\n\t\textra() {\n\t\t\tconst spec = this.content?.extraGroup\n\t\t\tif (!spec || typeof spec !== 'object' || !spec.path) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst list = readPath(this.payload, spec.path)\n\t\t\tif (!Array.isArray(list) || list.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst parts = list\n\t\t\t\t.map((entry) => (entry && typeof entry === 'object' ? this.fill(spec.itemText || '{label}', readable(entry)) : languageMapText(entry)))\n\t\t\t\t.filter((text) => text !== '')\n\t\t\tif (parts.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn {\n\t\t\t\theadingId: `${this.idPrefix}-extra`,\n\t\t\t\ttitle: this.translate(spec.title || 'Always available'),\n\t\t\t\ttext: parts.join(typeof spec.join === 'string' ? spec.join : ' · '),\n\t\t\t}\n\t\t},\n\n\t\t/** @return {string} The empty text. */\n\t\temptyLabel() {\n\t\t\treturn this.content?.emptyText ? this.translate(this.content.emptyText) : t('nextcloud-vue', 'Nothing to show yet')\n\t\t},\n\n\t\t/** @return {string} The text of a group without cards. */\n\t\tgroupEmptyLabel() {\n\t\t\treturn this.content?.groupEmptyText ? this.translate(this.content.groupEmptyText) : t('nextcloud-vue', 'Nothing here yet')\n\t\t},\n\n\t\t/** @return {string} The error text. */\n\t\terrorLabel() {\n\t\t\treturn this.content?.errorText ? this.translate(this.content.errorText) : t('nextcloud-vue', 'This list could not be loaded.')\n\t\t},\n\n\t\t/** @return {string} The loading label. */\n\t\tloadingLabel() {\n\t\t\treturn t('nextcloud-vue', 'Loading')\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Translate a text through the host app's catalogue.\n\t\t *\n\t\t * @param {string} text The text.\n\t\t * @return {string} The translated text.\n\t\t */\n\t\ttranslate(text) {\n\t\t\tconst fn = typeof this.cnTranslate === 'function' ? this.cnTranslate : (key) => key\n\t\t\treturn fn(text)\n\t\t},\n\n\t\t/**\n\t\t * Fill a template from a row, after translating it.\n\t\t *\n\t\t * @param {string} template The template.\n\t\t * @param {object} row The row.\n\t\t * @return {string} The text, '' when a token is empty.\n\t\t */\n\t\tfill(template, row) {\n\t\t\tif (typeof template !== 'string' || template === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn fillTemplate(this.translate(template), row, new Date(), { config: unwrapAppConfig(this.cnAppConfig) })\n\t\t},\n\n\t\t/**\n\t\t * A card's meta line: one template, or several joined with \" · \", a\n\t\t * part whose token is empty being left out with its separator.\n\t\t *\n\t\t * @param {object} item The card's row.\n\t\t * @return {string} The line.\n\t\t */\n\t\tmetaOf(item) {\n\t\t\tconst spec = this.content?.itemMeta\n\t\t\tconst parts = Array.isArray(spec) ? spec : (typeof spec === 'string' ? [spec] : [])\n\t\t\treturn parts.map((part) => this.fill(part, item)).filter((text) => text !== '').join(' · ')\n\t\t},\n\n\t\t/**\n\t\t * The count pill of a group (`groupCount: { one, other }`).\n\t\t *\n\t\t * @param {number} count The number of cards.\n\t\t * @return {string} The text, '' without `groupCount`.\n\t\t */\n\t\tcountOf(count) {\n\t\t\tconst spec = this.content?.groupCount\n\t\t\tif (!spec || typeof spec !== 'object') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst template = count === 1 ? spec.one : spec.other\n\t\t\treturn typeof template === 'string' && template !== '' ? this.translate(template).replace(/\\{count\\}/g, String(count)) : ''\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-grouped-cards__grid {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(var(--cn-grouped-cards-columns, 2), minmax(0, 1fr));\n\tgap: 16px;\n}\n\n@media (max-width: 700px) {\n\t.cn-grouped-cards__grid {\n\t\tgrid-template-columns: minmax(0, 1fr);\n\t}\n}\n\n.cn-grouped-cards__group {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n\tpadding: 12px 14px 14px;\n\tborder-radius: var(--border-radius-large, 10px);\n\tbackground: var(--color-background-hover);\n}\n\n.cn-grouped-cards__group--extra {\n\tbackground: transparent;\n\tborder: 1px dashed var(--color-border-dark);\n}\n\n.cn-grouped-cards__group-header {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 8px;\n}\n\n.cn-grouped-cards__group-title {\n\tmargin: 0;\n\tfont-size: 15px;\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-grouped-cards__count {\n\tpadding: 2px 8px;\n\tborder-radius: var(--border-radius-pill, 12px);\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 12px;\n\tfont-weight: 600;\n\twhite-space: nowrap;\n}\n\n.cn-grouped-cards__items {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n.cn-grouped-cards__item {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tpadding: 10px 12px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large, 8px);\n\tbackground: var(--color-main-background);\n}\n\n.cn-grouped-cards__item-title {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-grouped-cards__item-meta,\n.cn-grouped-cards__extra-text,\n.cn-grouped-cards__group-empty,\n.cn-grouped-cards__state {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-grouped-cards__loading {\n\tdisplay: flex;\n\tjustify-content: center;\n\tpadding: 8px 0;\n}\n</style>\n"],"names":["t"],"mappings":"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"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { resolveComponent, openBlock, createElementBlock, toDisplayString, createVNode, normalizeStyle, Fragment, renderList, createElementVNode, createCommentVNode } from 'vue';
|
|
2
|
+
|
|
3
|
+
const _hoisted_1 = {
|
|
4
|
+
class: "cn-grouped-cards",
|
|
5
|
+
"data-testid": "cn-grouped-cards"
|
|
6
|
+
};
|
|
7
|
+
const _hoisted_2 = {
|
|
8
|
+
key: 0,
|
|
9
|
+
class: "cn-grouped-cards__state",
|
|
10
|
+
"data-testid": "cn-grouped-cards-error"
|
|
11
|
+
};
|
|
12
|
+
const _hoisted_3 = {
|
|
13
|
+
key: 1,
|
|
14
|
+
class: "cn-grouped-cards__loading"
|
|
15
|
+
};
|
|
16
|
+
const _hoisted_4 = {
|
|
17
|
+
key: 2,
|
|
18
|
+
class: "cn-grouped-cards__state",
|
|
19
|
+
"data-testid": "cn-grouped-cards-empty"
|
|
20
|
+
};
|
|
21
|
+
const _hoisted_5 = ["aria-labelledby"];
|
|
22
|
+
const _hoisted_6 = { class: "cn-grouped-cards__group-header" };
|
|
23
|
+
const _hoisted_7 = ["id"];
|
|
24
|
+
const _hoisted_8 = {
|
|
25
|
+
key: 0,
|
|
26
|
+
class: "cn-grouped-cards__count",
|
|
27
|
+
"data-testid": "cn-grouped-cards-count"
|
|
28
|
+
};
|
|
29
|
+
const _hoisted_9 = {
|
|
30
|
+
key: 0,
|
|
31
|
+
class: "cn-grouped-cards__items"
|
|
32
|
+
};
|
|
33
|
+
const _hoisted_10 = { class: "cn-grouped-cards__item-title" };
|
|
34
|
+
const _hoisted_11 = {
|
|
35
|
+
key: 0,
|
|
36
|
+
class: "cn-grouped-cards__item-meta"
|
|
37
|
+
};
|
|
38
|
+
const _hoisted_12 = {
|
|
39
|
+
key: 1,
|
|
40
|
+
class: "cn-grouped-cards__group-empty"
|
|
41
|
+
};
|
|
42
|
+
const _hoisted_13 = ["aria-labelledby"];
|
|
43
|
+
const _hoisted_14 = { class: "cn-grouped-cards__group-header" };
|
|
44
|
+
const _hoisted_15 = ["id"];
|
|
45
|
+
const _hoisted_16 = { class: "cn-grouped-cards__extra-text" };
|
|
46
|
+
|
|
47
|
+
function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
48
|
+
const _component_NcLoadingIcon = resolveComponent("NcLoadingIcon");
|
|
49
|
+
|
|
50
|
+
return (openBlock(), createElementBlock("div", _hoisted_1, [
|
|
51
|
+
($setup.error)
|
|
52
|
+
? (openBlock(), createElementBlock("p", _hoisted_2, toDisplayString($options.errorLabel), 1 /* TEXT */))
|
|
53
|
+
: ($setup.loading && $options.groups.length === 0)
|
|
54
|
+
? (openBlock(), createElementBlock("div", _hoisted_3, [
|
|
55
|
+
createVNode(_component_NcLoadingIcon, {
|
|
56
|
+
size: 20,
|
|
57
|
+
name: $options.loadingLabel
|
|
58
|
+
}, null, 8 /* PROPS */, ["name"])
|
|
59
|
+
]))
|
|
60
|
+
: ($options.groups.length === 0 && !$options.extra)
|
|
61
|
+
? (openBlock(), createElementBlock("p", _hoisted_4, toDisplayString($options.emptyLabel), 1 /* TEXT */))
|
|
62
|
+
: (openBlock(), createElementBlock("div", {
|
|
63
|
+
key: 3,
|
|
64
|
+
class: "cn-grouped-cards__grid",
|
|
65
|
+
style: normalizeStyle({ '--cn-grouped-cards-columns': $options.columns })
|
|
66
|
+
}, [
|
|
67
|
+
(openBlock(true), createElementBlock(Fragment, null, renderList($options.groups, (group) => {
|
|
68
|
+
return (openBlock(), createElementBlock("section", {
|
|
69
|
+
key: group.key,
|
|
70
|
+
class: "cn-grouped-cards__group",
|
|
71
|
+
"aria-labelledby": group.headingId,
|
|
72
|
+
"data-testid": "cn-grouped-cards-group"
|
|
73
|
+
}, [
|
|
74
|
+
createElementVNode("header", _hoisted_6, [
|
|
75
|
+
createElementVNode("h4", {
|
|
76
|
+
id: group.headingId,
|
|
77
|
+
class: "cn-grouped-cards__group-title"
|
|
78
|
+
}, toDisplayString(group.title), 9 /* TEXT, PROPS */, _hoisted_7),
|
|
79
|
+
(group.count)
|
|
80
|
+
? (openBlock(), createElementBlock("span", _hoisted_8, toDisplayString(group.count), 1 /* TEXT */))
|
|
81
|
+
: createCommentVNode("v-if", true)
|
|
82
|
+
]),
|
|
83
|
+
(group.items.length > 0)
|
|
84
|
+
? (openBlock(), createElementBlock("ul", _hoisted_9, [
|
|
85
|
+
(openBlock(true), createElementBlock(Fragment, null, renderList(group.items, (item) => {
|
|
86
|
+
return (openBlock(), createElementBlock("li", {
|
|
87
|
+
key: item.key,
|
|
88
|
+
class: "cn-grouped-cards__item",
|
|
89
|
+
"data-testid": "cn-grouped-cards-item"
|
|
90
|
+
}, [
|
|
91
|
+
createElementVNode("span", _hoisted_10, toDisplayString(item.title), 1 /* TEXT */),
|
|
92
|
+
(item.meta)
|
|
93
|
+
? (openBlock(), createElementBlock("span", _hoisted_11, toDisplayString(item.meta), 1 /* TEXT */))
|
|
94
|
+
: createCommentVNode("v-if", true)
|
|
95
|
+
]))
|
|
96
|
+
}), 128 /* KEYED_FRAGMENT */))
|
|
97
|
+
]))
|
|
98
|
+
: (openBlock(), createElementBlock("p", _hoisted_12, toDisplayString($options.groupEmptyLabel), 1 /* TEXT */))
|
|
99
|
+
], 8 /* PROPS */, _hoisted_5))
|
|
100
|
+
}), 128 /* KEYED_FRAGMENT */)),
|
|
101
|
+
($options.extra)
|
|
102
|
+
? (openBlock(), createElementBlock("section", {
|
|
103
|
+
key: 0,
|
|
104
|
+
class: "cn-grouped-cards__group cn-grouped-cards__group--extra",
|
|
105
|
+
"aria-labelledby": $options.extra.headingId,
|
|
106
|
+
"data-testid": "cn-grouped-cards-extra"
|
|
107
|
+
}, [
|
|
108
|
+
createElementVNode("header", _hoisted_14, [
|
|
109
|
+
createElementVNode("h4", {
|
|
110
|
+
id: $options.extra.headingId,
|
|
111
|
+
class: "cn-grouped-cards__group-title"
|
|
112
|
+
}, toDisplayString($options.extra.title), 9 /* TEXT, PROPS */, _hoisted_15)
|
|
113
|
+
]),
|
|
114
|
+
createElementVNode("p", _hoisted_16, toDisplayString($options.extra.text), 1 /* TEXT */)
|
|
115
|
+
], 8 /* PROPS */, _hoisted_13))
|
|
116
|
+
: createCommentVNode("v-if", true)
|
|
117
|
+
], 4 /* STYLE */))
|
|
118
|
+
]))
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export { render };
|
|
122
|
+
//# sourceMappingURL=CnGroupedCardsWidget.vue3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CnGroupedCardsWidget.vue3.js","sources":["../../../../src/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue?vue&type=template&id=cf2cd50a&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div class=\"cn-grouped-cards\" data-testid=\"cn-grouped-cards\">\n\t\t<p v-if=\"error\" class=\"cn-grouped-cards__state\" data-testid=\"cn-grouped-cards-error\">\n\t\t\t{{ errorLabel }}\n\t\t</p>\n\t\t<div v-else-if=\"loading && groups.length === 0\" class=\"cn-grouped-cards__loading\">\n\t\t\t<NcLoadingIcon :size=\"20\" :name=\"loadingLabel\" />\n\t\t</div>\n\t\t<p v-else-if=\"groups.length === 0 && !extra\" class=\"cn-grouped-cards__state\" data-testid=\"cn-grouped-cards-empty\">\n\t\t\t{{ emptyLabel }}\n\t\t</p>\n\t\t<div\n\t\t\tv-else\n\t\t\tclass=\"cn-grouped-cards__grid\"\n\t\t\t:style=\"{ '--cn-grouped-cards-columns': columns }\">\n\t\t\t<section\n\t\t\t\tv-for=\"group in groups\"\n\t\t\t\t:key=\"group.key\"\n\t\t\t\tclass=\"cn-grouped-cards__group\"\n\t\t\t\t:aria-labelledby=\"group.headingId\"\n\t\t\t\tdata-testid=\"cn-grouped-cards-group\">\n\t\t\t\t<header class=\"cn-grouped-cards__group-header\">\n\t\t\t\t\t<h4 :id=\"group.headingId\" class=\"cn-grouped-cards__group-title\">\n\t\t\t\t\t\t{{ group.title }}\n\t\t\t\t\t</h4>\n\t\t\t\t\t<span v-if=\"group.count\" class=\"cn-grouped-cards__count\" data-testid=\"cn-grouped-cards-count\">{{ group.count }}</span>\n\t\t\t\t</header>\n\t\t\t\t<ul v-if=\"group.items.length > 0\" class=\"cn-grouped-cards__items\">\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"item in group.items\"\n\t\t\t\t\t\t:key=\"item.key\"\n\t\t\t\t\t\tclass=\"cn-grouped-cards__item\"\n\t\t\t\t\t\tdata-testid=\"cn-grouped-cards-item\">\n\t\t\t\t\t\t<span class=\"cn-grouped-cards__item-title\">{{ item.title }}</span>\n\t\t\t\t\t\t<span v-if=\"item.meta\" class=\"cn-grouped-cards__item-meta\">{{ item.meta }}</span>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t\t<p v-else class=\"cn-grouped-cards__group-empty\">\n\t\t\t\t\t{{ groupEmptyLabel }}\n\t\t\t\t</p>\n\t\t\t</section>\n\t\t\t<section\n\t\t\t\tv-if=\"extra\"\n\t\t\t\tclass=\"cn-grouped-cards__group cn-grouped-cards__group--extra\"\n\t\t\t\t:aria-labelledby=\"extra.headingId\"\n\t\t\t\tdata-testid=\"cn-grouped-cards-extra\">\n\t\t\t\t<header class=\"cn-grouped-cards__group-header\">\n\t\t\t\t\t<h4 :id=\"extra.headingId\" class=\"cn-grouped-cards__group-title\">\n\t\t\t\t\t\t{{ extra.title }}\n\t\t\t\t\t</h4>\n\t\t\t\t</header>\n\t\t\t\t<p class=\"cn-grouped-cards__extra-text\">\n\t\t\t\t\t{{ extra.text }}\n\t\t\t\t</p>\n\t\t\t</section>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport { inject, ref } from 'vue'\nimport { useEndpointSource } from '../../composables/useEndpointSource.js'\nimport { fillTemplate } from '../../utils/fillTemplate.js'\nimport { unwrapAppConfig } from '../../utils/formatMetric.js'\nimport { languageMapText } from '../../utils/languageMapText.js'\nimport { readPath } from '../../utils/readPath.js'\n\nlet uid = 0\n\n/**\n * Unwrap a value that may be a ref.\n *\n * @param {unknown} value The value.\n * @return {unknown} The plain value.\n */\nfunction unwrap(value) {\n\treturn value && typeof value === 'object' && 'value' in value ? value.value : value\n}\n\n/**\n * A row as the templates read it: a language map (`{ nl, en }`) becomes its\n * text in the user's language, and a list of plain values is joined with \", \".\n * Nested objects stay as they are, for a template that reads a deeper path.\n *\n * @param {object} row The row.\n * @return {object} A shallow copy with readable values.\n */\nfunction readable(row) {\n\tif (!row || typeof row !== 'object') {\n\t\treturn {}\n\t}\n\tconst out = {}\n\tfor (const [key, value] of Object.entries(row)) {\n\t\tif (Array.isArray(value)) {\n\t\t\tconst plain = value.filter((entry) => typeof entry === 'string' || typeof entry === 'number')\n\t\t\tout[key] = plain.length === value.length ? plain.join(', ') : value\n\t\t} else if (value && typeof value === 'object' && Object.values(value).every((entry) => typeof entry === 'string')) {\n\t\t\tout[key] = languageMapText(value)\n\t\t} else {\n\t\t\tout[key] = value\n\t\t}\n\t}\n\treturn out\n}\n\n/**\n * CnGroupedCardsWidget — groups of cards side by side, read from an app\n * endpoint: a case type's phases, each with the tasks it runs, and a dashed\n * box of what is available in every phase (board DqZaaktype, Fasen en taken).\n *\n * Content-only on a card surface: the detail host or the dashboard draws the\n * titled card around it.\n */\nexport default {\n\tname: 'CnGroupedCardsWidget',\n\n\tcomponents: { NcLoadingIcon },\n\n\tinject: {\n\t\t/** The host app's translate function. */\n\t\tcnTranslate: { default: () => (key) => key },\n\t\t/** The page's app config map, for money tokens in a template. */\n\t\tcnAppConfig: { default: () => ({}) },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The widget config: `{ endpointSource, groupsPath?, itemsKey?,\n\t\t * groupTitle?, groupCount?, itemTitle?, itemMeta?, columns?,\n\t\t * extraGroup?, emptyText?, groupEmptyText?, errorText? }`.\n\t\t *\n\t\t * - `endpointSource` (`{ url, params?, responsePath? }`) reads the payload; tokens\n\t\t * such as `@objectId` resolve as on every widget.\n\t\t * - `groupsPath` (default `groups`) is the list of groups in the payload, and\n\t\t * `itemsKey` (default `items`) the list of cards in each group.\n\t\t * - `groupTitle` and `itemTitle` are templates (`\"Phase {order}: {name}\"`,\n\t\t * `\"{title}\"`); `itemMeta` is a template or a list of templates joined\n\t\t * with \" · \", a part whose token is empty being left out.\n\t\t * - `groupCount` (`{ one, other }`, `{count}` filled in) draws a count pill.\n\t\t * - `extraGroup` (`{ title, path, itemText, join? }`) closes the grid with a\n\t\t * dashed box whose entries, read at `path` of the payload, are one line.\n\t\t * - `columns` (default 2) is the number of groups side by side.\n\t\t *\n\t\t * A language map (`{ nl, en }`) reads in the user's language, and a list\n\t\t * of plain values reads joined with \", \". Texts go through the app's\n\t\t * translate.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** The current record's id, for the `@objectId` token. */\n\t\tobjectId: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** The current record, for `@object.<field>` tokens. */\n\t\tobjectData: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tsetup(props) {\n\t\tconst objectCtxRaw = inject('cnObjectContext', null)\n\t\tconst workspaceRaw = inject('cnWorkspaceContext', null)\n\t\tconst appConfigRaw = inject('cnAppConfig', ref({}))\n\t\tconst { data, loading, error } = useEndpointSource(\n\t\t\t() => props.content?.endpointSource || null,\n\t\t\t{\n\t\t\t\tctx: () => {\n\t\t\t\t\tconst injected = unwrap(objectCtxRaw) || {}\n\t\t\t\t\treturn {\n\t\t\t\t\t\t...injected,\n\t\t\t\t\t\tobjectId: props.objectId ?? injected.objectId ?? props.objectData?.id ?? null,\n\t\t\t\t\t\tobject: props.objectData || injected.object || null,\n\t\t\t\t\t\tworkspace: unwrap(workspaceRaw) || {},\n\t\t\t\t\t\tconfig: unwrapAppConfig(appConfigRaw) || {},\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t)\n\t\tuid += 1\n\t\treturn { payload: data, loading, error, idPrefix: `cn-grouped-cards-${uid}` }\n\t},\n\n\tcomputed: {\n\t\t/** @return {number} The groups side by side. */\n\t\tcolumns() {\n\t\t\tconst value = Number(this.content?.columns)\n\t\t\treturn Number.isFinite(value) && value >= 1 ? Math.min(4, Math.floor(value)) : 2\n\t\t},\n\n\t\t/**\n\t\t * The groups ready to draw.\n\t\t *\n\t\t * @return {Array<{key: string, headingId: string, title: string, count: string, items: Array<object>}>} The groups.\n\t\t * @spec openspec/changes/grouped-cards-widget/specs/grouped-cards-widget/spec.md#requirement-a-widget-draws-groups-of-cards-from-an-endpoint\n\t\t */\n\t\tgroups() {\n\t\t\tconst c = this.content || {}\n\t\t\tconst list = readPath(this.payload, c.groupsPath || 'groups')\n\t\t\tif (!Array.isArray(list)) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn list.filter((group) => group && typeof group === 'object').map((raw, index) => {\n\t\t\t\tconst group = readable(raw)\n\t\t\t\tconst itemList = Array.isArray(raw[c.itemsKey || 'items']) ? raw[c.itemsKey || 'items'] : []\n\t\t\t\tconst items = itemList.filter((item) => item && typeof item === 'object').map((rawItem, itemIndex) => {\n\t\t\t\t\tconst item = readable(rawItem)\n\t\t\t\t\treturn {\n\t\t\t\t\t\tkey: String(rawItem.id ?? `item-${itemIndex}`),\n\t\t\t\t\t\ttitle: this.fill(c.itemTitle || '{title}', item),\n\t\t\t\t\t\tmeta: this.metaOf(item),\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\treturn {\n\t\t\t\t\tkey: String(raw.id ?? `group-${index}`),\n\t\t\t\t\theadingId: `${this.idPrefix}-group-${index}`,\n\t\t\t\t\ttitle: this.fill(c.groupTitle || '{name}', group),\n\t\t\t\t\tcount: this.countOf(items.length),\n\t\t\t\t\titems,\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * The closing box of entries available everywhere, or null.\n\t\t *\n\t\t * @return {{headingId: string, title: string, text: string}|null} The box.\n\t\t * @spec openspec/changes/grouped-cards-widget/specs/grouped-cards-widget/spec.md#requirement-a-widget-draws-groups-of-cards-from-an-endpoint\n\t\t */\n\t\textra() {\n\t\t\tconst spec = this.content?.extraGroup\n\t\t\tif (!spec || typeof spec !== 'object' || !spec.path) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst list = readPath(this.payload, spec.path)\n\t\t\tif (!Array.isArray(list) || list.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst parts = list\n\t\t\t\t.map((entry) => (entry && typeof entry === 'object' ? this.fill(spec.itemText || '{label}', readable(entry)) : languageMapText(entry)))\n\t\t\t\t.filter((text) => text !== '')\n\t\t\tif (parts.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn {\n\t\t\t\theadingId: `${this.idPrefix}-extra`,\n\t\t\t\ttitle: this.translate(spec.title || 'Always available'),\n\t\t\t\ttext: parts.join(typeof spec.join === 'string' ? spec.join : ' · '),\n\t\t\t}\n\t\t},\n\n\t\t/** @return {string} The empty text. */\n\t\temptyLabel() {\n\t\t\treturn this.content?.emptyText ? this.translate(this.content.emptyText) : t('nextcloud-vue', 'Nothing to show yet')\n\t\t},\n\n\t\t/** @return {string} The text of a group without cards. */\n\t\tgroupEmptyLabel() {\n\t\t\treturn this.content?.groupEmptyText ? this.translate(this.content.groupEmptyText) : t('nextcloud-vue', 'Nothing here yet')\n\t\t},\n\n\t\t/** @return {string} The error text. */\n\t\terrorLabel() {\n\t\t\treturn this.content?.errorText ? this.translate(this.content.errorText) : t('nextcloud-vue', 'This list could not be loaded.')\n\t\t},\n\n\t\t/** @return {string} The loading label. */\n\t\tloadingLabel() {\n\t\t\treturn t('nextcloud-vue', 'Loading')\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Translate a text through the host app's catalogue.\n\t\t *\n\t\t * @param {string} text The text.\n\t\t * @return {string} The translated text.\n\t\t */\n\t\ttranslate(text) {\n\t\t\tconst fn = typeof this.cnTranslate === 'function' ? this.cnTranslate : (key) => key\n\t\t\treturn fn(text)\n\t\t},\n\n\t\t/**\n\t\t * Fill a template from a row, after translating it.\n\t\t *\n\t\t * @param {string} template The template.\n\t\t * @param {object} row The row.\n\t\t * @return {string} The text, '' when a token is empty.\n\t\t */\n\t\tfill(template, row) {\n\t\t\tif (typeof template !== 'string' || template === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn fillTemplate(this.translate(template), row, new Date(), { config: unwrapAppConfig(this.cnAppConfig) })\n\t\t},\n\n\t\t/**\n\t\t * A card's meta line: one template, or several joined with \" · \", a\n\t\t * part whose token is empty being left out with its separator.\n\t\t *\n\t\t * @param {object} item The card's row.\n\t\t * @return {string} The line.\n\t\t */\n\t\tmetaOf(item) {\n\t\t\tconst spec = this.content?.itemMeta\n\t\t\tconst parts = Array.isArray(spec) ? spec : (typeof spec === 'string' ? [spec] : [])\n\t\t\treturn parts.map((part) => this.fill(part, item)).filter((text) => text !== '').join(' · ')\n\t\t},\n\n\t\t/**\n\t\t * The count pill of a group (`groupCount: { one, other }`).\n\t\t *\n\t\t * @param {number} count The number of cards.\n\t\t * @return {string} The text, '' without `groupCount`.\n\t\t */\n\t\tcountOf(count) {\n\t\t\tconst spec = this.content?.groupCount\n\t\t\tif (!spec || typeof spec !== 'object') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst template = count === 1 ? spec.one : spec.other\n\t\t\treturn typeof template === 'string' && template !== '' ? this.translate(template).replace(/\\{count\\}/g, String(count)) : ''\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-grouped-cards__grid {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(var(--cn-grouped-cards-columns, 2), minmax(0, 1fr));\n\tgap: 16px;\n}\n\n@media (max-width: 700px) {\n\t.cn-grouped-cards__grid {\n\t\tgrid-template-columns: minmax(0, 1fr);\n\t}\n}\n\n.cn-grouped-cards__group {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n\tpadding: 12px 14px 14px;\n\tborder-radius: var(--border-radius-large, 10px);\n\tbackground: var(--color-background-hover);\n}\n\n.cn-grouped-cards__group--extra {\n\tbackground: transparent;\n\tborder: 1px dashed var(--color-border-dark);\n}\n\n.cn-grouped-cards__group-header {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 8px;\n}\n\n.cn-grouped-cards__group-title {\n\tmargin: 0;\n\tfont-size: 15px;\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-grouped-cards__count {\n\tpadding: 2px 8px;\n\tborder-radius: var(--border-radius-pill, 12px);\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 12px;\n\tfont-weight: 600;\n\twhite-space: nowrap;\n}\n\n.cn-grouped-cards__items {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n.cn-grouped-cards__item {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tpadding: 10px 12px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large, 8px);\n\tbackground: var(--color-main-background);\n}\n\n.cn-grouped-cards__item-title {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-grouped-cards__item-meta,\n.cn-grouped-cards__extra-text,\n.cn-grouped-cards__group-empty,\n.cn-grouped-cards__state {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-grouped-cards__loading {\n\tdisplay: flex;\n\tjustify-content: center;\n\tpadding: 8px 0;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_toDisplayString","_createVNode","_Fragment","_renderList","_createElementVNode"],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import __sfc_main from './CnGroupedCardsWidget.vue.js';
|
|
2
|
+
import { registerDashboardWidget } from '../CnWidgetGrid/dashboardWidgetRegistry.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
6
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
7
|
+
*
|
|
8
|
+
* CnGroupedCardsWidget — self-registers the `grouped-cards` widget type
|
|
9
|
+
* (groups of cards side by side, read from an app endpoint) into the shared
|
|
10
|
+
* dashboardWidgetRegistry at module load.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
registerDashboardWidget('grouped-cards', {
|
|
15
|
+
renderer: __sfc_main,
|
|
16
|
+
form: null,
|
|
17
|
+
defaultContent: {
|
|
18
|
+
endpointSource: { url: '' },
|
|
19
|
+
groupsPath: 'groups',
|
|
20
|
+
itemsKey: 'items',
|
|
21
|
+
columns: 2,
|
|
22
|
+
},
|
|
23
|
+
displayName: 'Grouped cards',
|
|
24
|
+
icon: 'ViewGridOutline',
|
|
25
|
+
surfaces: ['detail-page', 'app-dashboard'],
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export { __sfc_main as CnGroupedCardsWidget, __sfc_main as default };
|
|
29
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../../src/components/CnGroupedCardsWidget/index.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n *\n * CnGroupedCardsWidget — self-registers the `grouped-cards` widget type\n * (groups of cards side by side, read from an app endpoint) into the shared\n * dashboardWidgetRegistry at module load.\n */\n\nimport CnGroupedCardsWidget from './CnGroupedCardsWidget.vue'\nimport { registerDashboardWidget } from '../CnWidgetGrid/dashboardWidgetRegistry.js'\n\nregisterDashboardWidget('grouped-cards', {\n\trenderer: CnGroupedCardsWidget,\n\tform: null,\n\tdefaultContent: {\n\t\tendpointSource: { url: '' },\n\t\tgroupsPath: 'groups',\n\t\titemsKey: 'items',\n\t\tcolumns: 2,\n\t},\n\tdisplayName: 'Grouped cards',\n\ticon: 'ViewGridOutline',\n\tsurfaces: ['detail-page', 'app-dashboard'],\n})\n\nexport { CnGroupedCardsWidget }\nexport default CnGroupedCardsWidget\n"],"names":["CnGroupedCardsWidget"],"mappings":";;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA,uBAAuB,CAAC,eAAe,EAAE;AACzC,CAAC,QAAQ,EAAEA,UAAoB;AAC/B,CAAC,IAAI,EAAE,IAAI;AACX,CAAC,cAAc,EAAE;AACjB,EAAE,cAAc,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE;AAC7B,EAAE,UAAU,EAAE,QAAQ;AACtB,EAAE,QAAQ,EAAE,OAAO;AACnB,EAAE,OAAO,EAAE,CAAC;AACZ,EAAE;AACF,CAAC,WAAW,EAAE,eAAe;AAC7B,CAAC,IAAI,EAAE,iBAAiB;AACxB,CAAC,QAAQ,EAAE,CAAC,aAAa,EAAE,eAAe,CAAC;AAC3C,CAAC;;;;"}
|