@conduction/nextcloud-vue 2.121.0 → 2.123.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.
Files changed (102) hide show
  1. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +1 -0
  2. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  3. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  4. package/dist/esm/components/CnAttentionFlagWidget/CnAttentionFlagWidget.vue.js +1 -1
  5. package/dist/esm/components/CnAttentionFlagWidget/CnAttentionFlagWidget.vue2.js +18 -4
  6. package/dist/esm/components/CnAttentionFlagWidget/CnAttentionFlagWidget.vue2.js.map +1 -1
  7. package/dist/esm/components/CnAttentionFlagWidget/CnAttentionFlagWidget.vue3.js +63 -32
  8. package/dist/esm/components/CnAttentionFlagWidget/CnAttentionFlagWidget.vue3.js.map +1 -1
  9. package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
  10. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
  11. package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
  12. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +35 -0
  13. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  14. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +11 -5
  15. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  16. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
  17. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +13 -0
  18. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  19. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +37 -5
  20. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  21. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
  22. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +40 -0
  23. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  24. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +71 -65
  25. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  26. package/dist/esm/components/CnFlowDetail/CnFlowDetail.vue.js +1 -1
  27. package/dist/esm/components/CnFlowDetail/CnFlowDetail.vue2.js +20 -0
  28. package/dist/esm/components/CnFlowDetail/CnFlowDetail.vue2.js.map +1 -1
  29. package/dist/esm/components/CnFlowDetail/CnFlowDetail.vue3.js +2 -1
  30. package/dist/esm/components/CnFlowDetail/CnFlowDetail.vue3.js.map +1 -1
  31. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue.js +1 -1
  32. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue2.js +2 -0
  33. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue2.js.map +1 -1
  34. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue3.js +49 -44
  35. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue3.js.map +1 -1
  36. package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue2.js +10 -0
  37. package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue2.js.map +1 -1
  38. package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue3.js +2 -1
  39. package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue3.js.map +1 -1
  40. package/dist/esm/components/CnGraphCanvas/CnGraphCanvas.vue2.js +31 -0
  41. package/dist/esm/components/CnGraphCanvas/CnGraphCanvas.vue2.js.map +1 -1
  42. package/dist/esm/components/CnGraphCanvas/CnGraphCanvas.vue3.js +4 -3
  43. package/dist/esm/components/CnGraphCanvas/CnGraphCanvas.vue3.js.map +1 -1
  44. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +34 -2
  45. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  46. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  47. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
  48. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js +1 -0
  49. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
  50. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue.js +1 -1
  51. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +2 -0
  52. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  53. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +2 -1
  54. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  55. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue.js +1 -1
  56. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue2.js +31 -1
  57. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue2.js.map +1 -1
  58. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue3.js +14 -4
  59. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue3.js.map +1 -1
  60. package/dist/esm/composables/flowGraphLayout.js +36 -7
  61. package/dist/esm/composables/flowGraphLayout.js.map +1 -1
  62. package/dist/esm/composables/useFlowStore.js +7 -3
  63. package/dist/esm/composables/useFlowStore.js.map +1 -1
  64. package/dist/esm/l10n/en.json.js +3 -1
  65. package/dist/esm/l10n/en.json.js.map +1 -1
  66. package/dist/esm/l10n/nl.json.js +3 -1
  67. package/dist/esm/l10n/nl.json.js.map +1 -1
  68. package/dist/esm/library-style.js +1 -1
  69. package/dist/esm/nextcloud-vue.css +670 -316
  70. package/dist/esm/utils/manifestTranslate.js +12 -3
  71. package/dist/esm/utils/manifestTranslate.js.map +1 -1
  72. package/dist/esm/utils/routeFilters.js +3 -3
  73. package/dist/esm/utils/routeFilters.js.map +1 -1
  74. package/dist/nextcloud-vue.cjs.js +1218 -847
  75. package/dist/nextcloud-vue.cjs.js.map +2 -2
  76. package/dist/nextcloud-vue.css +670 -316
  77. package/l10n/en.json +3 -1
  78. package/l10n/nl.json +3 -1
  79. package/package.json +1 -1
  80. package/src/components/CnAppRoot/CnAppRoot.vue +1 -0
  81. package/src/components/CnAttentionFlagWidget/CnAttentionFlagWidget.vue +50 -5
  82. package/src/components/CnChartWidget/CnChartWidget.vue +10 -8
  83. package/src/components/CnDetailPage/CnDetailPage.md +10 -0
  84. package/src/components/CnDetailPage/CnDetailPage.vue +42 -1
  85. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +39 -0
  86. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +95 -37
  87. package/src/components/CnFlowDetail/CnFlowDetail.vue +21 -0
  88. package/src/components/CnFlowsPage/CnFlowBoard.vue +35 -18
  89. package/src/components/CnFlowsPage/CnFlowEditorPage.vue +11 -0
  90. package/src/components/CnGraphCanvas/CnGraphCanvas.vue +34 -2
  91. package/src/components/CnIndexPage/CnIndexPage.vue +34 -2
  92. package/src/components/CnIndexSidebar/CnIndexSidebar.vue +1 -0
  93. package/src/components/CnObjectSidebar/CnFilesTab.vue +3 -0
  94. package/src/components/CnSectionsWidget/CnSectionsWidget.vue +53 -2
  95. package/src/composables/flowGraphLayout.js +35 -6
  96. package/src/composables/useFlowStore.js +7 -3
  97. package/src/css/detail-page.css +6 -0
  98. package/src/css/look-board-detail.css +53 -6
  99. package/src/css/look-board-index.css +151 -12
  100. package/src/css/look-board.css +116 -4
  101. package/src/utils/manifestTranslate.js +12 -3
  102. package/src/utils/routeFilters.js +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"CnFlowBoard.vue3.js","sources":["../../../../src/components/CnFlowsPage/CnFlowBoard.vue?vue&type=template&id=d3a5e726&lang.js"],"sourcesContent":["<!--\n CnFlowBoard — the flow editor in the board look: a page header with the\n flow's name, path and actions, then the canvas in a card beside an\n inspector card. Optionally a palette card on the left (`palette: column`).\n The same page shows one run when the route carries `?run=`: the canvas\n paints each step's outcome and the inspector holds the run's card and logs.\n\n The canvas itself is CnFlowDetail; this component only lays it out and adds\n what the board draws around it.\n\n SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>\n SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<div class=\"cn-flow-board\" :class=\"{ 'cn-flow-board--with-palette': showPalette }\" data-testid=\"flow-board\">\n\t\t<header class=\"cn-flow-board__header\">\n\t\t\t<div class=\"cn-flow-board__heading\">\n\t\t\t\t<h2 class=\"cn-flow-board__title\" data-testid=\"flow-board-title\">\n\t\t\t\t\t{{ titleText }}\n\t\t\t\t</h2>\n\t\t\t\t<CnStatusBadge v-if=\"pillText\" :label=\"pillText\" variant=\"default\" />\n\t\t\t</div>\n\t\t\t<div v-if=\"!store.inRunView\" class=\"cn-flow-board__actions\">\n\t\t\t\t<NcCheckboxRadioSwitch v-if=\"store.flow.id\"\n\t\t\t\t\ttype=\"switch\"\n\t\t\t\t\t:modelValue=\"store.flow.enabled !== false\"\n\t\t\t\t\tdata-testid=\"flow-board-enabled\"\n\t\t\t\t\t@update:modelValue=\"onEnabledChange\">\n\t\t\t\t\t{{ store.flow.enabled !== false ? t('nextcloud-vue', 'Active') : t('nextcloud-vue', 'Paused') }}\n\t\t\t\t</NcCheckboxRadioSwitch>\n\t\t\t\t<NcButton :disabled=\"store.checking || !store.nodes.length\"\n\t\t\t\t\tdata-testid=\"flow-board-check\"\n\t\t\t\t\t@click=\"store.check()\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Check') }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton v-if=\"showRun\"\n\t\t\t\t\t:disabled=\"store.running || !store.flow.id\"\n\t\t\t\t\tdata-testid=\"flow-board-run\"\n\t\t\t\t\t@click=\"onRun\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Run') }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton v-if=\"testSubjectReady\"\n\t\t\t\t\t:disabled=\"store.running || !store.flow.id\"\n\t\t\t\t\tdata-testid=\"flow-board-test\"\n\t\t\t\t\t@click=\"testOpen = true\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<TestTube :size=\"16\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ testLabel }}\n\t\t\t\t</NcButton>\n\t\t\t\t<CnBuildiqEditButton />\n\t\t\t\t<NcButton variant=\"primary\"\n\t\t\t\t\t:disabled=\"store.saving || !store.flow.name || store.graphLocked\"\n\t\t\t\t\tdata-testid=\"flow-board-save\"\n\t\t\t\t\t@click=\"$emit('save')\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Save') }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\t\t</header>\n\n\t\t<CnDialog v-if=\"testOpen\"\n\t\t\t:name=\"testLabel\"\n\t\t\tdata-testid=\"flow-board-test-dialog\"\n\t\t\t@closing=\"testOpen = false\">\n\t\t\t<p class=\"cn-flow-board__test-intro\">\n\t\t\t\t{{ t('nextcloud-vue', 'The saved flow runs once with the record you pick as its subject.') }}\n\t\t\t</p>\n\t\t\t<CnResourceSelect v-model=\"testRecord\"\n\t\t\t\t:register=\"testSubject.register\"\n\t\t\t\t:schema=\"testSubject.schema\"\n\t\t\t\t:labelField=\"testSubject.labelField || 'title'\"\n\t\t\t\t:inputLabel=\"t('nextcloud-vue', 'Record')\"\n\t\t\t\tdata-testid=\"flow-board-test-record\" />\n\t\t\t<template #actions>\n\t\t\t\t<NcButton data-testid=\"flow-board-test-cancel\" @click=\"testOpen = false\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Cancel') }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton variant=\"primary\"\n\t\t\t\t\t:disabled=\"!testRecord || store.running\"\n\t\t\t\t\tdata-testid=\"flow-board-test-run\"\n\t\t\t\t\t@click=\"runTest\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Run test') }}\n\t\t\t\t</NcButton>\n\t\t\t</template>\n\t\t</CnDialog>\n\n\t\t<p class=\"cn-flow-board__path\" data-testid=\"flow-board-path\">\n\t\t\t<a :href=\"listHref\" class=\"cn-flow-board__crumb\" @click=\"onCrumbClick($event, detailRoute)\">{{ listText }}</a>\n\t\t\t<span aria-hidden=\"true\"> / </span>\n\t\t\t<template v-if=\"store.inRunView\">\n\t\t\t\t<a :href=\"flowHref\" class=\"cn-flow-board__crumb\" @click=\"onCrumbClick($event, flowPath)\">{{ flowName }}</a>\n\t\t\t\t<span aria-hidden=\"true\"> / </span>\n\t\t\t\t<span>{{ runCrumb }}</span>\n\t\t\t</template>\n\t\t\t<span v-else>{{ flowName }}</span>\n\t\t\t<span v-if=\"metaText\" class=\"cn-flow-board__meta\"> · {{ metaText }}</span>\n\t\t</p>\n\n\t\t<NcNoteCard v-if=\"notice && !store.inRunView\" type=\"info\" class=\"cn-flow-board__notice\">\n\t\t\t{{ notice }}\n\t\t</NcNoteCard>\n\n\t\t<div class=\"cn-flow-board__body\">\n\t\t\t<aside v-if=\"showPalette\" class=\"cn-flow-board__card cn-flow-board__palette\" data-testid=\"flow-board-palette\">\n\t\t\t\t<h3 class=\"cn-flow-board__card-title\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Steps') }}\n\t\t\t\t</h3>\n\t\t\t\t<section v-for=\"group in paletteGroups\"\n\t\t\t\t\t:key=\"group.app\"\n\t\t\t\t\tclass=\"cn-flow-board__palette-group\">\n\t\t\t\t\t<h4 class=\"cn-flow-board__palette-app\">\n\t\t\t\t\t\t{{ group.name }}\n\t\t\t\t\t</h4>\n\t\t\t\t\t<ul class=\"cn-flow-board__palette-list\">\n\t\t\t\t\t\t<li v-for=\"entry in group.entries\" :key=\"entry.id\">\n\t\t\t\t\t\t\t<button type=\"button\"\n\t\t\t\t\t\t\t\tclass=\"cn-flow-board__palette-step\"\n\t\t\t\t\t\t\t\t:disabled=\"store.graphLocked\"\n\t\t\t\t\t\t\t\t:data-testid=\"`flow-board-palette-${entry.id}`\"\n\t\t\t\t\t\t\t\t@click=\"store.addNode(entry.id)\">\n\t\t\t\t\t\t\t\t{{ entry.displayName || entry.label || entry.id }}\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t</li>\n\t\t\t\t\t</ul>\n\t\t\t\t</section>\n\t\t\t\t<p v-if=\"paletteFootnote\" class=\"cn-flow-board__palette-note\">\n\t\t\t\t\t{{ paletteFootnote }}\n\t\t\t\t</p>\n\t\t\t</aside>\n\n\t\t\t<section class=\"cn-flow-board__card cn-flow-board__canvas\" :aria-label=\"t('nextcloud-vue', 'Flow canvas')\">\n\t\t\t\t<CnFlowDetail :id=\"id\"\n\t\t\t\t\t:app=\"app\"\n\t\t\t\t\t:run=\"run\"\n\t\t\t\t\t:toolbarActions=\"false\"\n\t\t\t\t\t:addStep=\"!showPalette\"\n\t\t\t\t\t@save=\"$emit('save')\"\n\t\t\t\t\t@run=\"onRun\" />\n\t\t\t\t<div v-if=\"store.inRunView\"\n\t\t\t\t\tclass=\"cn-flow-board__legend\"\n\t\t\t\t\tdata-testid=\"flow-board-legend\">\n\t\t\t\t\t<span v-for=\"level in legend\" :key=\"level.id\" class=\"cn-flow-board__legend-item\">\n\t\t\t\t\t\t<CnStatusBadge :label=\"level.label\" :variant=\"level.variant\" size=\"small\" />\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t\t<p v-else\n\t\t\t\t\tclass=\"cn-flow-board__validity\"\n\t\t\t\t\t:class=\"`cn-flow-board__validity--${validity.tone}`\"\n\t\t\t\t\trole=\"status\"\n\t\t\t\t\tdata-testid=\"flow-board-validity\">\n\t\t\t\t\t{{ validity.text }}\n\t\t\t\t</p>\n\t\t\t</section>\n\n\t\t\t<aside class=\"cn-flow-board__card cn-flow-board__inspector\" data-testid=\"flow-board-inspector\">\n\t\t\t\t<template v-if=\"store.inRunView\">\n\t\t\t\t\t<CnFlowRunSummary :run=\"runRecord\"\n\t\t\t\t\t\t:stepsTotal=\"store.nodes.length\"\n\t\t\t\t\t\t:canRetry=\"canRetry\"\n\t\t\t\t\t\t@back=\"onBackToFlow\"\n\t\t\t\t\t\t@retry=\"onRetry\" />\n\t\t\t\t\t<div class=\"cn-flow-board__tabs\" role=\"tablist\">\n\t\t\t\t\t\t<button v-for=\"tab in runTabs\"\n\t\t\t\t\t\t\t:key=\"tab.id\"\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\trole=\"tab\"\n\t\t\t\t\t\t\tclass=\"cn-flow-board__tab\"\n\t\t\t\t\t\t\t:class=\"{ 'cn-flow-board__tab--active': runTab === tab.id }\"\n\t\t\t\t\t\t\t:aria-selected=\"runTab === tab.id ? 'true' : 'false'\"\n\t\t\t\t\t\t\t:data-testid=\"`flow-board-run-tab-${tab.id}`\"\n\t\t\t\t\t\t\t@click=\"runTab = tab.id\">\n\t\t\t\t\t\t\t{{ tab.label }}\n\t\t\t\t\t\t\t<span class=\"cn-flow-board__tab-count\">{{ tab.count }}</span>\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</div>\n\t\t\t\t\t<CnFlowStepLog :lines=\"runTab === 'step' ? selectedRunLines : allRunLines\"\n\t\t\t\t\t\t:emptyText=\"runTab === 'step' && !store.selectedNodeId ? t('nextcloud-vue', 'Select a step on the canvas to see what it did.') : ''\" />\n\t\t\t\t</template>\n\t\t\t\t<template v-else-if=\"store.selectedNode\">\n\t\t\t\t\t<p class=\"cn-flow-board__eyebrow\">\n\t\t\t\t\t\t{{ stepTypeLabel }}\n\t\t\t\t\t</p>\n\t\t\t\t\t<h3 class=\"cn-flow-board__card-title\" data-testid=\"flow-board-step-name\">\n\t\t\t\t\t\t{{ stepName }}\n\t\t\t\t\t</h3>\n\t\t\t\t\t<dl v-if=\"stepSettings.length > 0\" class=\"cn-flow-board__settings\">\n\t\t\t\t\t\t<div v-for=\"setting in stepSettings\"\n\t\t\t\t\t\t\t:key=\"setting.key\"\n\t\t\t\t\t\t\tclass=\"cn-flow-board__setting\"\n\t\t\t\t\t\t\tdata-testid=\"flow-board-step-setting\">\n\t\t\t\t\t\t\t<dt>{{ setting.label }}</dt>\n\t\t\t\t\t\t\t<dd>{{ setting.value }}</dd>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</dl>\n\t\t\t\t\t<div class=\"cn-flow-board__step-actions\">\n\t\t\t\t\t\t<NcButton data-testid=\"flow-board-edit-step\" @click=\"store.editingNodeId = store.selectedNodeId\">\n\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Edit step') }}\n\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t\t<NcButton v-if=\"lastRunUuid\"\n\t\t\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t\t\tdata-testid=\"flow-board-open-last-run\"\n\t\t\t\t\t\t\t@click=\"openRun(lastRunUuid)\">\n\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Open the last run on the canvas') }}\n\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t</div>\n\t\t\t\t\t<h4 class=\"cn-flow-board__subtitle\">\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Recent log lines of this step') }}\n\t\t\t\t\t</h4>\n\t\t\t\t\t<CnFlowStepLog :lines=\"recentStepLines\"\n\t\t\t\t\t\twithDay\n\t\t\t\t\t\t:emptyText=\"t('nextcloud-vue', 'This step has not run yet.')\" />\n\t\t\t\t\t<div class=\"cn-flow-board__step-footer\">\n\t\t\t\t\t\t<NcButton variant=\"tertiary\"\n\t\t\t\t\t\t\t:disabled=\"store.graphLocked\"\n\t\t\t\t\t\t\tdata-testid=\"flow-board-delete-step\"\n\t\t\t\t\t\t\t@click=\"store.removeNode(store.selectedNodeId)\">\n\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Delete step') }}\n\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t\t<NcButton data-testid=\"flow-board-done\" @click=\"store.selectedNodeId = null\">\n\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Done') }}\n\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t</div>\n\t\t\t\t</template>\n\t\t\t\t<template v-else-if=\"showRuns\">\n\t\t\t\t\t<NcButton variant=\"tertiary\"\n\t\t\t\t\t\tclass=\"cn-flow-board__runs-back\"\n\t\t\t\t\t\tdata-testid=\"flow-board-hide-runs\"\n\t\t\t\t\t\t@click=\"showRuns = false\">\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Back to the step settings') }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t\t<CnFlowSidebar embedded />\n\t\t\t\t</template>\n\t\t\t\t<div v-else class=\"cn-flow-board__inspector-empty\" data-testid=\"flow-board-inspector-empty\">\n\t\t\t\t\t<p>{{ t('nextcloud-vue', 'Select a step on the canvas to see its settings.') }}</p>\n\t\t\t\t\t<NcButton variant=\"secondary\"\n\t\t\t\t\t\tdata-testid=\"flow-board-show-runs\"\n\t\t\t\t\t\t@click=\"showRuns = true\">\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Show runs') }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t</div>\n\t\t\t</aside>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcCheckboxRadioSwitch, NcNoteCard } from '@nextcloud/vue'\nimport TestTube from 'vue-material-design-icons/TestTube.vue'\nimport CnBuildiqEditButton from '../CnBuildiqEditButton/CnBuildiqEditButton.vue'\nimport CnDialog from '../CnDialog/CnDialog.vue'\nimport CnFlowDetail from '../CnFlowDetail/CnFlowDetail.vue'\nimport CnFlowRunSummary from '../CnFlowDetail/CnFlowRunSummary.vue'\nimport CnFlowSidebar from '../CnFlowDetail/CnFlowSidebar.vue'\nimport CnFlowStepLog from '../CnFlowDetail/CnFlowStepLog.vue'\nimport CnResourceSelect from '../CnResourceSelect/CnResourceSelect.vue'\nimport { FLOW_RUN_ACTIVE_STATUSES, useFlowStore } from '../../composables/useFlowStore.js'\nimport { formatRelativeDate } from '../../utils/boardDate.js'\nimport { flowOutcomeLevels, flowRunDurationMs, flowRunLogLines, flowRunName, formatFlowDuration } from '../../utils/flowRunLog.js'\nimport { appDisplayNames } from '../../utils/nextcloudApps.js'\nimport { CnStatusBadge } from '../CnStatusBadge/index.js'\n\n/**\n * The board-look flow editor and run view. CnFlowEditorPage renders it when\n * the app or page is in the board look; the Nextcloud look keeps the canvas\n * on its own with the app sidebar.\n */\nexport default {\n\tname: 'CnFlowBoard',\n\n\tcomponents: { CnBuildiqEditButton, CnDialog, CnFlowDetail, CnResourceSelect, TestTube, CnFlowRunSummary, CnFlowSidebar, CnFlowStepLog, CnStatusBadge, NcButton, NcCheckboxRadioSwitch, NcNoteCard },\n\n\tprops: {\n\t\t/** The flow uuid, or `new`. */\n\t\tid: { type: String, default: '' },\n\t\t/** The owning app, for a new flow and the list scope. */\n\t\tapp: { type: String, default: null },\n\t\t/** The run to show, or ''. */\n\t\trun: { type: String, default: '' },\n\t\t/** Where the flow list lives; flows open at `<detailRoute>/<id>`. */\n\t\tdetailRoute: { type: String, default: '/flows' },\n\t\t/** The list's name in the path (\"Flows\", \"Settings / Flows\"). */\n\t\tlistLabel: { type: String, default: '' },\n\t\t/** `column` draws the step palette as a card on the left. */\n\t\tpalette: { type: String, default: 'picker' },\n\t\t/** Free text under the path in the palette card. */\n\t\tpaletteFootnote: { type: String, default: '' },\n\t\t/** An info notice under the path. */\n\t\tnotice: { type: String, default: '' },\n\t\t/** The pill beside the title (\"Case type flow\"). */\n\t\tpill: { type: String, default: '' },\n\t\t/**\n\t\t * The records a test run can take as its subject: `{ register, schema,\n\t\t * labelField?, label? }`. Draws a header button (\"Testen met een zaak\",\n\t\t * DqFlow) that runs the saved flow once with a picked record.\n\t\t */\n\t\ttestSubject: { type: Object, default: null },\n\t},\n\n\temits: ['save', 'run', 'retried'],\n\n\tsetup() {\n\t\treturn { store: useFlowStore() }\n\t},\n\n\tdata() {\n\t\treturn { runTab: 'step', testOpen: false, testRecord: null, appNames: {}, showRuns: false }\n\t},\n\n\tcomputed: {\n\t\t/** @return {boolean} Whether a test subject is declared well enough to pick one. */\n\t\ttestSubjectReady() {\n\t\t\treturn Boolean(this.testSubject && this.testSubject.register && this.testSubject.schema)\n\t\t},\n\n\t\t/** @return {string} The test button's label: the page's own, else the library's. */\n\t\ttestLabel() {\n\t\t\treturn (this.testSubject && this.testSubject.label) || t('nextcloud-vue', 'Test with a record')\n\t\t},\n\n\t\t/** @return {boolean} Whether the palette card is drawn. */\n\t\tshowPalette() {\n\t\t\treturn this.palette === 'column' && !this.store.inRunView\n\t\t},\n\n\t\t/** @return {string} The page title. */\n\t\ttitleText() {\n\t\t\treturn this.store.flow.name || this.t('nextcloud-vue', 'New flow')\n\t\t},\n\n\t\t/** @return {string} The flow's name in the path. */\n\t\tflowName() {\n\t\t\treturn this.store.flow.name || this.t('nextcloud-vue', 'New flow')\n\t\t},\n\n\t\t/** @return {string} The pill text: the page's, else the lifecycle. */\n\t\tpillText() {\n\t\t\tif (this.pill) {\n\t\t\t\treturn this.pill\n\t\t\t}\n\t\t\tconst lifecycle = String(this.store.flow.lifecycleStatus || '')\n\t\t\tif (lifecycle === 'draft') {\n\t\t\t\treturn this.t('nextcloud-vue', 'Draft')\n\t\t\t}\n\t\t\tif (lifecycle === 'deprecated') {\n\t\t\t\treturn this.t('nextcloud-vue', 'Retired')\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\t/** @return {string} The list label. */\n\t\tlistText() {\n\t\t\treturn this.listLabel || this.t('nextcloud-vue', 'Flows')\n\t\t},\n\n\t\t/** @return {string} The flow's own path. */\n\t\tflowPath() {\n\t\t\treturn `${this.detailRoute}/${this.store.flow.id || this.id}`\n\t\t},\n\n\t\t/** @return {string} The list href. */\n\t\tlistHref() {\n\t\t\treturn this.hrefOf(this.detailRoute)\n\t\t},\n\n\t\t/** @return {string} The flow href. */\n\t\tflowHref() {\n\t\t\treturn this.hrefOf(this.flowPath)\n\t\t},\n\n\t\t/**\n\t\t * What the path says after the name: the trigger, the step count and\n\t\t * the version in the editor; when the run started and how long it\n\t\t * took in the run view.\n\t\t *\n\t\t * @return {string} The meta text.\n\t\t */\n\t\tmetaText() {\n\t\t\tif (this.store.inRunView) {\n\t\t\t\tconst record = this.runRecord\n\t\t\t\tif (!record) {\n\t\t\t\t\treturn ''\n\t\t\t\t}\n\t\t\t\tconst parts = [formatRelativeDate(record.created, { withTime: true })]\n\t\t\t\tconst duration = formatFlowDuration(flowRunDurationMs(record))\n\t\t\t\tif (duration) {\n\t\t\t\t\tparts.push(this.t('nextcloud-vue', 'done in {duration}', { duration }))\n\t\t\t\t}\n\t\t\t\treturn parts.filter(Boolean).join(' · ')\n\t\t\t}\n\t\t\tconst parts = []\n\t\t\tconst trigger = this.triggerText\n\t\t\tif (trigger) {\n\t\t\t\tparts.push(this.t('nextcloud-vue', 'trigger {trigger}', { trigger }))\n\t\t\t}\n\t\t\tconst count = this.store.nodes.length\n\t\t\tparts.push(count === 1 ? this.t('nextcloud-vue', '1 step') : this.t('nextcloud-vue', '{count} steps', { count }))\n\t\t\tif (this.store.flow.version) {\n\t\t\t\tparts.push(this.t('nextcloud-vue', 'version {version}', { version: this.store.flow.version }))\n\t\t\t}\n\t\t\treturn parts.join(' · ')\n\t\t},\n\n\t\t/** @return {string} How the flow starts, as the catalogue names it. */\n\t\ttriggerText() {\n\t\t\tconst trigger = String(this.store.flow.trigger || '')\n\t\t\tif (trigger === 'schedule') {\n\t\t\t\treturn this.t('nextcloud-vue', 'On a schedule')\n\t\t\t}\n\t\t\tif (trigger === '' || trigger === 'manual') {\n\t\t\t\treturn this.t('nextcloud-vue', 'Run by hand')\n\t\t\t}\n\t\t\tconst entry = (this.store.eventCatalog || []).find((item) => item && item.id === trigger)\n\t\t\treturn (entry && entry.label) || trigger\n\t\t},\n\n\t\t/** @return {boolean} Whether Run is offered. */\n\t\tshowRun() {\n\t\t\treturn this.store.nodes.some((node) => node?.type === 'openregister.trigger-manual')\n\t\t},\n\n\t\t/**\n\t\t * The engine's verdict on the canvas as one line under it.\n\t\t *\n\t\t * @return {{tone: string, text: string}} The validity bar.\n\t\t */\n\t\tvalidity() {\n\t\t\tconst result = this.store.checkResult\n\t\t\tif (!result) {\n\t\t\t\treturn { tone: 'unknown', text: this.t('nextcloud-vue', 'Not checked yet. Check runs the flow past the engine without saving it.') }\n\t\t\t}\n\t\t\tconst blocking = Array.isArray(result.blocking) ? result.blocking.length : 0\n\t\t\tconst warnings = Array.isArray(result.warnings) ? result.warnings.length : 0\n\t\t\tif (result.valid === false || blocking > 0) {\n\t\t\t\treturn { tone: 'error', text: result.message || this.t('nextcloud-vue', 'The flow cannot run as it is. See the messages on the canvas.') }\n\t\t\t}\n\t\t\tif (warnings > 0) {\n\t\t\t\treturn { tone: 'warning', text: this.t('nextcloud-vue', 'The flow is valid, with {count} warnings.', { count: warnings }) }\n\t\t\t}\n\t\t\treturn { tone: 'success', text: this.t('nextcloud-vue', 'The flow is valid.') }\n\t\t},\n\n\t\t/**\n\t\t * The palette grouped by owning app, each group named by the app's\n\t\t * display name (its id when Nextcloud names none), the app that owns\n\t\t * this flow first.\n\t\t *\n\t\t * @return {Array<{app: string, name: string, entries: Array<object>}>} The groups.\n\t\t */\n\t\tpaletteGroups() {\n\t\t\tconst groups = {}\n\t\t\t;(this.store.nodeCatalog || []).forEach((entry) => {\n\t\t\t\tconst app = String(entry?.app || String(entry?.id || '').split('.')[0] || '')\n\t\t\t\tif (!groups[app]) {\n\t\t\t\t\tgroups[app] = { app, name: this.appNames[app] || app, entries: [] }\n\t\t\t\t}\n\t\t\t\tgroups[app].entries.push(entry)\n\t\t\t})\n\t\t\tconst list = Object.values(groups)\n\t\t\tconst host = list.findIndex((group) => group.app === this.app)\n\t\t\treturn host > 0 ? [list[host], ...list.slice(0, host), ...list.slice(host + 1)] : list\n\t\t},\n\n\t\t/** @return {object|null} The run on screen. */\n\t\trunRecord() {\n\t\t\treturn this.store.runDetail\n\t\t\t\t|| (this.store.runs || []).find((item) => item.uuid === this.store.inspectedRunUuid)\n\t\t\t\t|| null\n\t\t},\n\n\t\t/** @return {string} The run's place in the path. */\n\t\trunCrumb() {\n\t\t\treturn this.t('nextcloud-vue', 'Run {id}', { id: flowRunName(this.runRecord) })\n\t\t},\n\n\t\t/** @return {boolean} Whether Run again is offered. */\n\t\tcanRetry() {\n\t\t\treturn !!this.runRecord && !FLOW_RUN_ACTIVE_STATUSES.includes(this.runRecord.status)\n\t\t},\n\n\t\t/** @return {Array<object>} Every line of the run on screen. */\n\t\tallRunLines() {\n\t\t\treturn this.runRecord ? flowRunLogLines(this.runRecord) : []\n\t\t},\n\n\t\t/** @return {Array<object>} The selected step's lines in this run. */\n\t\tselectedRunLines() {\n\t\t\treturn this.runRecord && this.store.selectedNodeId\n\t\t\t\t? flowRunLogLines(this.runRecord, this.store.selectedNodeId)\n\t\t\t\t: []\n\t\t},\n\n\t\t/** @return {Array<object>} The two log tabs with their counts. */\n\t\trunTabs() {\n\t\t\treturn [\n\t\t\t\t{ id: 'step', label: this.t('nextcloud-vue', 'This step'), count: this.selectedRunLines.length },\n\t\t\t\t{ id: 'run', label: this.t('nextcloud-vue', 'Logs of the whole run'), count: this.allRunLines.length },\n\t\t\t]\n\t\t},\n\n\t\t/** @return {Array<object>} The legend of outcomes on the canvas. */\n\t\tlegend() {\n\t\t\tconst levels = flowOutcomeLevels()\n\t\t\treturn ['success', 'warning', 'error', 'info', 'skipped'].map((id) => ({ id, ...levels[id] }))\n\t\t},\n\n\t\t/** @return {string} The selected step's type, as the catalogue names it. */\n\t\tstepTypeLabel() {\n\t\t\tconst node = this.store.selectedNode\n\t\t\tconst entry = node ? this.store.catalogEntry(node.type) : null\n\t\t\treturn entry ? (entry.displayName || entry.id) : String(node?.type || '')\n\t\t},\n\n\t\t/**\n\t\t * The selected step's settings as its owner declares them in the\n\t\t * catalogue's `configForm`: label and value, a select's option label,\n\t\t * Yes or No for a switch. Unset settings are left out.\n\t\t *\n\t\t * @return {Array<{key: string, label: string, value: string}>} The settings.\n\t\t */\n\t\tstepSettings() {\n\t\t\tconst node = this.store.selectedNode\n\t\t\tconst entry = node ? this.store.catalogEntry(node.type) : null\n\t\t\tconst config = (node && node.config) || {}\n\t\t\tconst form = (entry && Array.isArray(entry.configForm)) ? entry.configForm : []\n\t\t\treturn form\n\t\t\t\t.filter((field) => field && field.key && config[field.key] !== undefined && config[field.key] !== null && config[field.key] !== '')\n\t\t\t\t.map((field) => {\n\t\t\t\t\tconst raw = config[field.key]\n\t\t\t\t\tconst option = Array.isArray(field.options) ? field.options.find((o) => o && o.value === raw) : null\n\t\t\t\t\tlet value\n\t\t\t\t\tif (option) {\n\t\t\t\t\t\tvalue = String(option.label ?? option.value)\n\t\t\t\t\t} else if (typeof raw === 'boolean') {\n\t\t\t\t\t\tvalue = raw ? this.t('nextcloud-vue', 'Yes') : this.t('nextcloud-vue', 'No')\n\t\t\t\t\t} else if (typeof raw === 'object') {\n\t\t\t\t\t\tvalue = JSON.stringify(raw)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tvalue = String(raw)\n\t\t\t\t\t}\n\t\t\t\t\treturn { key: field.key, label: field.label || field.key, value }\n\t\t\t\t})\n\t\t},\n\n\t\t/** @return {string} The selected step's name. */\n\t\tstepName() {\n\t\t\tconst node = this.store.selectedNode\n\t\t\treturn (node && (node.name || node.label)) || this.stepTypeLabel\n\t\t},\n\n\t\t/**\n\t\t * The selected step's lines from the flow's recent runs, newest run\n\t\t * first, at most ten.\n\t\t *\n\t\t * @return {Array<object>} The lines.\n\t\t */\n\t\trecentStepLines() {\n\t\t\tconst id = this.store.selectedNodeId\n\t\t\tif (!id) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\tconst lines = []\n\t\t\t;(this.store.runs || []).forEach((item) => {\n\t\t\t\tflowRunLogLines(item, id).forEach((line) => lines.push({ ...line, key: `${item.uuid}-${line.key}` }))\n\t\t\t})\n\t\t\treturn lines.slice(0, 10)\n\t\t},\n\n\t\t/** @return {string} The newest run's uuid, or ''. */\n\t\tlastRunUuid() {\n\t\t\treturn (this.store.runs && this.store.runs[0] && this.store.runs[0].uuid) || ''\n\t\t},\n\t},\n\n\twatch: {\n\t\t// A selected step takes the panel back from the run history.\n\t\t'store.selectedNodeId': function(id) {\n\t\t\tif (id) {\n\t\t\t\tthis.showRuns = false\n\t\t\t}\n\t\t},\n\t},\n\n\tasync created() {\n\t\t// The palette names its groups by app display name; only the column\n\t\t// palette draws them, so only it reads the names.\n\t\tif (this.palette !== 'column') {\n\t\t\treturn\n\t\t}\n\t\tconst { default: axios } = await import('@nextcloud/axios')\n\t\tthis.appNames = await appDisplayNames(axios)\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Run the flow, with the run history in the panel to follow it.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonRun() {\n\t\t\tthis.showRuns = true\n\t\t\tthis.$emit('run')\n\t\t},\n\n\t\t/**\n\t\t * Run the saved flow once with the picked record as its subject, then\n\t\t * close the dialog; the run shows on the canvas as any run does.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync runTest() {\n\t\t\tconst uuid = Array.isArray(this.testRecord) ? this.testRecord[0] : this.testRecord\n\t\t\tif (!uuid) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tawait this.store.run({ uuid, register: this.testSubject.register, schema: this.testSubject.schema })\n\t\t\tthis.testOpen = false\n\t\t\tthis.testRecord = null\n\t\t},\n\n\t\tt,\n\n\t\t/**\n\t\t * @param {string} path An app route path.\n\t\t * @return {string} Its href.\n\t\t */\n\t\threfOf(path) {\n\t\t\tif (this.$router && typeof this.$router.resolve === 'function') {\n\t\t\t\ttry {\n\t\t\t\t\tconst resolved = this.$router.resolve(path)\n\t\t\t\t\tif (resolved && resolved.href) {\n\t\t\t\t\t\treturn resolved.href\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\t// A path the router does not know stays a plain path.\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn path\n\t\t},\n\n\t\t/**\n\t\t * Follow a path link on the app's router; a modified click keeps the\n\t\t * browser's own behaviour.\n\t\t *\n\t\t * @param {MouseEvent} event The click.\n\t\t * @param {string} path The target path.\n\t\t * @return {void}\n\t\t */\n\t\tonCrumbClick(event, path) {\n\t\t\tif (!this.$router || event.ctrlKey || event.metaKey || event.shiftKey || event.button === 1) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tevent.preventDefault()\n\t\t\tthis.$router.push(path).catch(() => {})\n\t\t},\n\n\t\t/**\n\t\t * Switch the flow on or off. The change is saved with the flow.\n\t\t *\n\t\t * @param {boolean} on The new state.\n\t\t * @return {void}\n\t\t */\n\t\tonEnabledChange(on) {\n\t\t\tthis.store.flow.enabled = on === true\n\t\t\tthis.store.dirty = true\n\t\t},\n\n\t\t/**\n\t\t * Open a run of this flow on the canvas.\n\t\t *\n\t\t * @param {string} uuid The run.\n\t\t * @return {void}\n\t\t */\n\t\topenRun(uuid) {\n\t\t\tif (this.$router) {\n\t\t\t\tthis.$router.push({ path: this.flowPath, query: { run: uuid } }).catch(() => {})\n\t\t\t} else {\n\t\t\t\tthis.store.inspectRun(uuid)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Leave the run view for the flow.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonBackToFlow() {\n\t\t\tthis.store.closeRun()\n\t\t\tif (this.$router) {\n\t\t\t\tthis.$router.push({ path: this.flowPath }).catch(() => {})\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Start the shown run again and open the new one.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onRetry() {\n\t\t\tconst retried = await this.store.retryRun(this.runRecord?.uuid)\n\t\t\tthis.$emit('retried', retried)\n\t\t\tif (retried && retried.uuid) {\n\t\t\t\tthis.openRun(retried.uuid)\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n/* The board page's padding, so the title stands where every board page puts it (DqFlow). */\n.cn-flow-board {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n\tpadding: var(--cn-board-content-padding, 24px 28px);\n\tmin-height: 100%;\n\tbox-sizing: border-box;\n}\n\n.cn-flow-board__header {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 12px;\n}\n\n.cn-flow-board__heading {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 10px;\n\tmin-width: 0;\n}\n\n.cn-flow-board__title {\n\tmargin: 0;\n\tfont-size: 24px;\n\tfont-weight: 600;\n}\n\n.cn-flow-board__actions {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.cn-flow-board__test-intro {\n\tmargin: 0 0 12px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-flow-board__path {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-flow-board__crumb {\n\tcolor: var(--color-main-text);\n\ttext-decoration: underline;\n}\n\n.cn-flow-board__body {\n\tdisplay: grid;\n\tgrid-template-columns: minmax(0, 1fr) 340px;\n\tgap: 16px;\n\tflex: 1 1 auto;\n\tmin-height: 520px;\n}\n\n.cn-flow-board--with-palette .cn-flow-board__body {\n\tgrid-template-columns: 240px minmax(0, 1fr) 340px;\n}\n\n.cn-flow-board__card {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n\tmin-width: 0;\n\tpadding: 16px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: 12px;\n\tbackground: var(--color-main-background);\n}\n\n.cn-flow-board__canvas {\n\tpadding: 0;\n\toverflow: hidden;\n\tbackground-color: var(--color-background-hover);\n\tbackground-image: radial-gradient(var(--color-border-dark) 1px, transparent 1px);\n\tbackground-size: 16px 16px;\n}\n\n.cn-flow-board__canvas > :first-child {\n\tflex: 1 1 auto;\n\tmin-height: 0;\n}\n\n.cn-flow-board__card-title {\n\tmargin: 0;\n\tfont-size: 16px;\n\tfont-weight: 600;\n}\n\n.cn-flow-board__subtitle {\n\tmargin: 4px 0 0;\n\tfont-size: 14px;\n\tfont-weight: 600;\n}\n\n.cn-flow-board__eyebrow {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-flow-board__validity,\n.cn-flow-board__legend {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 8px;\n\tmargin: 0;\n\tpadding: 10px 16px;\n\tborder-top: 1px solid var(--color-border);\n\tbackground: var(--color-main-background);\n}\n\n.cn-flow-board__validity--success {\n\tcolor: var(--color-success-text);\n}\n\n.cn-flow-board__validity--warning {\n\tcolor: var(--color-warning-text);\n}\n\n.cn-flow-board__validity--error {\n\tcolor: var(--color-error-text);\n}\n\n.cn-flow-board__palette-group {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-flow-board__palette-app {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n\tfont-weight: 600;\n}\n\n.cn-flow-board__palette-list {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n/* A step is a plain 400 label (DqFlow \"Status zetten\"); the nldesign theme\n sets every button's weight with `!important` through its token. */\n.cn-flow-board__palette-step {\n\t--nldesign-component-button-font-weight: 400;\n\twidth: 100%;\n\tmin-height: 34px;\n\tpadding: 6px 10px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: 8px;\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-main-text);\n\tfont-weight: 400;\n\ttext-align: start;\n\tcursor: pointer;\n}\n\n.cn-flow-board__palette-step:hover {\n\tbackground: var(--color-background-hover);\n}\n\n.cn-flow-board__palette-note {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-flow-board__tabs {\n\tdisplay: flex;\n\tgap: 4px;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-flow-board__tab {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 8px 10px;\n\tmargin-bottom: -1px;\n\tborder: 1px solid transparent;\n\tborder-radius: 8px 8px 0 0;\n\tbackground: transparent;\n\tcolor: var(--color-main-text);\n\tcursor: pointer;\n}\n\n.cn-flow-board__tab--active {\n\tborder-color: var(--color-border);\n\tborder-bottom-color: var(--color-main-background);\n\tfont-weight: 600;\n}\n\n.cn-flow-board__tab-count {\n\tmin-width: 18px;\n\tpadding: 0 5px;\n\tborder-radius: 9px;\n\tbackground: var(--color-background-dark);\n\tfont-size: 12px;\n\tline-height: 18px;\n\ttext-align: center;\n}\n\n.cn-flow-board__settings {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 10px;\n\tmargin: 0;\n}\n\n.cn-flow-board__setting dt {\n\tfont-size: 13px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-flow-board__setting dd {\n\tmargin: 2px 0 0;\n\tfont-weight: 600;\n\toverflow-wrap: anywhere;\n}\n\n.cn-flow-board__inspector-empty {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: flex-start;\n\tgap: 12px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-flow-board__inspector-empty p {\n\tmargin: 0;\n}\n\n.cn-flow-board__step-actions,\n.cn-flow-board__step-footer {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 8px;\n}\n\n.cn-flow-board__step-footer {\n\tjustify-content: space-between;\n\tmargin-top: auto;\n}\n\n@media (max-width: 1024px) {\n\t.cn-flow-board__body,\n\t.cn-flow-board--with-palette .cn-flow-board__body {\n\t\tgrid-template-columns: minmax(0, 1fr);\n\t}\n}\n</style>\n"],"names":["_createElementBlock","_normalizeClass","_createElementVNode","_toDisplayString","_createBlock","_createTextVNode","_createVNode","_Fragment","_openBlock","_renderList"],"mappings":"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"}
1
+ {"version":3,"file":"CnFlowBoard.vue3.js","sources":["../../../../src/components/CnFlowsPage/CnFlowBoard.vue?vue&type=template&id=4510ff10&lang.js"],"sourcesContent":["<!--\n CnFlowBoard — the flow editor in the board look: a page header with the\n flow's name, path and actions, then the canvas in a card beside an\n inspector card. Optionally a palette card on the left (`palette: column`).\n The same page shows one run when the route carries `?run=`: the canvas\n paints each step's outcome and the inspector holds the run's card and logs.\n\n The canvas itself is CnFlowDetail; this component only lays it out and adds\n what the board draws around it.\n\n SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>\n SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<div class=\"cn-flow-board\" :class=\"{ 'cn-flow-board--with-palette': showPalette }\" data-testid=\"flow-board\">\n\t\t<header class=\"cn-flow-board__header\">\n\t\t\t<div class=\"cn-flow-board__heading\">\n\t\t\t\t<h2 class=\"cn-flow-board__title\" data-testid=\"flow-board-title\">\n\t\t\t\t\t{{ titleText }}\n\t\t\t\t</h2>\n\t\t\t\t<CnStatusBadge v-if=\"pillText\" :label=\"pillText\" variant=\"default\" />\n\t\t\t</div>\n\t\t\t<div v-if=\"!store.inRunView\" class=\"cn-flow-board__actions\">\n\t\t\t\t<NcCheckboxRadioSwitch v-if=\"store.flow.id\"\n\t\t\t\t\ttype=\"switch\"\n\t\t\t\t\t:modelValue=\"store.flow.enabled !== false\"\n\t\t\t\t\tdata-testid=\"flow-board-enabled\"\n\t\t\t\t\t@update:modelValue=\"onEnabledChange\">\n\t\t\t\t\t{{ store.flow.enabled !== false ? t('nextcloud-vue', 'Active') : t('nextcloud-vue', 'Paused') }}\n\t\t\t\t</NcCheckboxRadioSwitch>\n\t\t\t\t<NcButton :disabled=\"store.checking || !store.nodes.length\"\n\t\t\t\t\tdata-testid=\"flow-board-check\"\n\t\t\t\t\t@click=\"store.check()\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Check') }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton v-if=\"showRun\"\n\t\t\t\t\t:disabled=\"store.running || !store.flow.id\"\n\t\t\t\t\tdata-testid=\"flow-board-run\"\n\t\t\t\t\t@click=\"onRun\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Run') }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton v-if=\"testSubjectReady\"\n\t\t\t\t\t:disabled=\"store.running || !store.flow.id\"\n\t\t\t\t\tdata-testid=\"flow-board-test\"\n\t\t\t\t\t@click=\"testOpen = true\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<TestTube :size=\"16\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ testLabel }}\n\t\t\t\t</NcButton>\n\t\t\t\t<CnBuildiqEditButton />\n\t\t\t\t<NcButton variant=\"primary\"\n\t\t\t\t\t:disabled=\"store.saving || !store.flow.name || store.graphLocked\"\n\t\t\t\t\tdata-testid=\"flow-board-save\"\n\t\t\t\t\t@click=\"$emit('save')\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Save') }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\t\t</header>\n\n\t\t<CnDialog v-if=\"testOpen\"\n\t\t\t:name=\"testLabel\"\n\t\t\tdata-testid=\"flow-board-test-dialog\"\n\t\t\t@closing=\"testOpen = false\">\n\t\t\t<p class=\"cn-flow-board__test-intro\">\n\t\t\t\t{{ t('nextcloud-vue', 'The saved flow runs once with the record you pick as its subject.') }}\n\t\t\t</p>\n\t\t\t<CnResourceSelect v-model=\"testRecord\"\n\t\t\t\t:register=\"testSubject.register\"\n\t\t\t\t:schema=\"testSubject.schema\"\n\t\t\t\t:labelField=\"testSubject.labelField || 'title'\"\n\t\t\t\t:inputLabel=\"t('nextcloud-vue', 'Record')\"\n\t\t\t\tdata-testid=\"flow-board-test-record\" />\n\t\t\t<template #actions>\n\t\t\t\t<NcButton data-testid=\"flow-board-test-cancel\" @click=\"testOpen = false\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Cancel') }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton variant=\"primary\"\n\t\t\t\t\t:disabled=\"!testRecord || store.running\"\n\t\t\t\t\tdata-testid=\"flow-board-test-run\"\n\t\t\t\t\t@click=\"runTest\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Run test') }}\n\t\t\t\t</NcButton>\n\t\t\t</template>\n\t\t</CnDialog>\n\n\t\t<p class=\"cn-flow-board__path\" data-testid=\"flow-board-path\">\n\t\t\t<a :href=\"listHref\" class=\"cn-flow-board__crumb\" @click=\"onCrumbClick($event, detailRoute)\">{{ listText }}</a>\n\t\t\t<span aria-hidden=\"true\"> / </span>\n\t\t\t<template v-if=\"store.inRunView\">\n\t\t\t\t<a :href=\"flowHref\" class=\"cn-flow-board__crumb\" @click=\"onCrumbClick($event, flowPath)\">{{ flowName }}</a>\n\t\t\t\t<span aria-hidden=\"true\"> / </span>\n\t\t\t\t<span>{{ runCrumb }}</span>\n\t\t\t</template>\n\t\t\t<span v-else>{{ flowName }}</span>\n\t\t\t<span v-if=\"metaText\" class=\"cn-flow-board__meta\"> · {{ metaText }}</span>\n\t\t</p>\n\n\t\t<NcNoteCard v-if=\"notice && !store.inRunView\" type=\"info\" class=\"cn-flow-board__notice\">\n\t\t\t{{ notice }}\n\t\t</NcNoteCard>\n\n\t\t<div class=\"cn-flow-board__body\">\n\t\t\t<aside v-if=\"showPalette\"\n\t\t\t\tclass=\"cn-flow-board__card cn-flow-board__palette\"\n\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Steps')\"\n\t\t\t\tdata-testid=\"flow-board-palette\">\n\t\t\t\t<section v-for=\"group in paletteGroups\"\n\t\t\t\t\t:key=\"group.app\"\n\t\t\t\t\tclass=\"cn-flow-board__palette-group\">\n\t\t\t\t\t<h4 class=\"cn-flow-board__palette-app\">\n\t\t\t\t\t\t{{ group.name }}\n\t\t\t\t\t</h4>\n\t\t\t\t\t<ul class=\"cn-flow-board__palette-list\">\n\t\t\t\t\t\t<li v-for=\"entry in group.entries\" :key=\"entry.id\">\n\t\t\t\t\t\t\t<button type=\"button\"\n\t\t\t\t\t\t\t\tclass=\"cn-flow-board__palette-step\"\n\t\t\t\t\t\t\t\t:disabled=\"store.graphLocked\"\n\t\t\t\t\t\t\t\t:data-testid=\"`flow-board-palette-${entry.id}`\"\n\t\t\t\t\t\t\t\t@click=\"store.addNode(entry.id)\">\n\t\t\t\t\t\t\t\t{{ entry.displayName || entry.label || entry.id }}\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t</li>\n\t\t\t\t\t</ul>\n\t\t\t\t</section>\n\t\t\t\t<p v-if=\"paletteFootnote\" class=\"cn-flow-board__palette-note\">\n\t\t\t\t\t{{ paletteFootnote }}\n\t\t\t\t</p>\n\t\t\t</aside>\n\n\t\t\t<section class=\"cn-flow-board__card cn-flow-board__canvas\" :aria-label=\"t('nextcloud-vue', 'Flow canvas')\">\n\t\t\t\t<CnFlowDetail :id=\"id\"\n\t\t\t\t\t:app=\"app\"\n\t\t\t\t\t:run=\"run\"\n\t\t\t\t\t:layoutDirection=\"flowDirection === 'down' ? 'down' : 'right'\"\n\t\t\t\t\t:fitMaxZoom=\"flowDirection === 'down' ? 1 : 0\"\n\t\t\t\t\t:toolbarActions=\"false\"\n\t\t\t\t\t:addStep=\"!showPalette\"\n\t\t\t\t\t@save=\"$emit('save')\"\n\t\t\t\t\t@run=\"onRun\" />\n\t\t\t\t<div v-if=\"store.inRunView\"\n\t\t\t\t\tclass=\"cn-flow-board__legend\"\n\t\t\t\t\tdata-testid=\"flow-board-legend\">\n\t\t\t\t\t<span v-for=\"level in legend\" :key=\"level.id\" class=\"cn-flow-board__legend-item\">\n\t\t\t\t\t\t<CnStatusBadge :label=\"level.label\" :variant=\"level.variant\" size=\"small\" />\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t\t<p v-else\n\t\t\t\t\tclass=\"cn-flow-board__validity\"\n\t\t\t\t\t:class=\"`cn-flow-board__validity--${validity.tone}`\"\n\t\t\t\t\trole=\"status\"\n\t\t\t\t\tdata-testid=\"flow-board-validity\">\n\t\t\t\t\t{{ validity.text }}\n\t\t\t\t</p>\n\t\t\t</section>\n\n\t\t\t<aside class=\"cn-flow-board__card cn-flow-board__inspector\" data-testid=\"flow-board-inspector\">\n\t\t\t\t<template v-if=\"store.inRunView\">\n\t\t\t\t\t<CnFlowRunSummary :run=\"runRecord\"\n\t\t\t\t\t\t:stepsTotal=\"store.nodes.length\"\n\t\t\t\t\t\t:canRetry=\"canRetry\"\n\t\t\t\t\t\t@back=\"onBackToFlow\"\n\t\t\t\t\t\t@retry=\"onRetry\" />\n\t\t\t\t\t<div class=\"cn-flow-board__tabs\" role=\"tablist\">\n\t\t\t\t\t\t<button v-for=\"tab in runTabs\"\n\t\t\t\t\t\t\t:key=\"tab.id\"\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\trole=\"tab\"\n\t\t\t\t\t\t\tclass=\"cn-flow-board__tab\"\n\t\t\t\t\t\t\t:class=\"{ 'cn-flow-board__tab--active': runTab === tab.id }\"\n\t\t\t\t\t\t\t:aria-selected=\"runTab === tab.id ? 'true' : 'false'\"\n\t\t\t\t\t\t\t:data-testid=\"`flow-board-run-tab-${tab.id}`\"\n\t\t\t\t\t\t\t@click=\"runTab = tab.id\">\n\t\t\t\t\t\t\t{{ tab.label }}\n\t\t\t\t\t\t\t<span class=\"cn-flow-board__tab-count\">{{ tab.count }}</span>\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</div>\n\t\t\t\t\t<CnFlowStepLog :lines=\"runTab === 'step' ? selectedRunLines : allRunLines\"\n\t\t\t\t\t\t:emptyText=\"runTab === 'step' && !store.selectedNodeId ? t('nextcloud-vue', 'Select a step on the canvas to see what it did.') : ''\" />\n\t\t\t\t</template>\n\t\t\t\t<template v-else-if=\"store.selectedNode\">\n\t\t\t\t\t<h3 class=\"cn-flow-board__card-title\" data-testid=\"flow-board-step-name\">\n\t\t\t\t\t\t{{ stepName }}\n\t\t\t\t\t</h3>\n\t\t\t\t\t<!-- The step type as a pill under the name (DqFlow \"Beslistabel van OpenRegister\"). -->\n\t\t\t\t\t<CnStatusBadge :label=\"stepTypeLabel\"\n\t\t\t\t\t\tvariant=\"purple\"\n\t\t\t\t\t\tclass=\"cn-flow-board__step-type\"\n\t\t\t\t\t\tdata-testid=\"flow-board-step-type\" />\n\t\t\t\t\t<dl v-if=\"stepSettings.length > 0\" class=\"cn-flow-board__settings\">\n\t\t\t\t\t\t<div v-for=\"setting in stepSettings\"\n\t\t\t\t\t\t\t:key=\"setting.key\"\n\t\t\t\t\t\t\tclass=\"cn-flow-board__setting\"\n\t\t\t\t\t\t\tdata-testid=\"flow-board-step-setting\">\n\t\t\t\t\t\t\t<dt>{{ setting.label }}</dt>\n\t\t\t\t\t\t\t<dd>{{ setting.value }}</dd>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</dl>\n\t\t\t\t\t<div class=\"cn-flow-board__step-actions\">\n\t\t\t\t\t\t<NcButton data-testid=\"flow-board-edit-step\" @click=\"store.editingNodeId = store.selectedNodeId\">\n\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Edit step') }}\n\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t\t<NcButton v-if=\"lastRunUuid\"\n\t\t\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t\t\tdata-testid=\"flow-board-open-last-run\"\n\t\t\t\t\t\t\t@click=\"openRun(lastRunUuid)\">\n\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Open the last run on the canvas') }}\n\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t</div>\n\t\t\t\t\t<h4 class=\"cn-flow-board__subtitle\">\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Recent log lines of this step') }}\n\t\t\t\t\t</h4>\n\t\t\t\t\t<CnFlowStepLog :lines=\"recentStepLines\"\n\t\t\t\t\t\twithDay\n\t\t\t\t\t\t:emptyText=\"t('nextcloud-vue', 'This step has not run yet.')\" />\n\t\t\t\t\t<div class=\"cn-flow-board__step-footer\">\n\t\t\t\t\t\t<NcButton variant=\"tertiary\"\n\t\t\t\t\t\t\t:disabled=\"store.graphLocked\"\n\t\t\t\t\t\t\tdata-testid=\"flow-board-delete-step\"\n\t\t\t\t\t\t\t@click=\"store.removeNode(store.selectedNodeId)\">\n\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Delete step') }}\n\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t\t<NcButton data-testid=\"flow-board-done\" @click=\"store.selectedNodeId = null\">\n\t\t\t\t\t\t\t{{ t('nextcloud-vue', 'Done') }}\n\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t</div>\n\t\t\t\t</template>\n\t\t\t\t<template v-else-if=\"showRuns\">\n\t\t\t\t\t<NcButton variant=\"tertiary\"\n\t\t\t\t\t\tclass=\"cn-flow-board__runs-back\"\n\t\t\t\t\t\tdata-testid=\"flow-board-hide-runs\"\n\t\t\t\t\t\t@click=\"showRuns = false\">\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Back to the step settings') }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t\t<CnFlowSidebar embedded />\n\t\t\t\t</template>\n\t\t\t\t<div v-else class=\"cn-flow-board__inspector-empty\" data-testid=\"flow-board-inspector-empty\">\n\t\t\t\t\t<p>{{ t('nextcloud-vue', 'Select a step on the canvas to see its settings.') }}</p>\n\t\t\t\t\t<NcButton variant=\"secondary\"\n\t\t\t\t\t\tdata-testid=\"flow-board-show-runs\"\n\t\t\t\t\t\t@click=\"showRuns = true\">\n\t\t\t\t\t\t{{ t('nextcloud-vue', 'Show runs') }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t</div>\n\t\t\t</aside>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcCheckboxRadioSwitch, NcNoteCard } from '@nextcloud/vue'\nimport TestTube from 'vue-material-design-icons/TestTube.vue'\nimport CnBuildiqEditButton from '../CnBuildiqEditButton/CnBuildiqEditButton.vue'\nimport CnDialog from '../CnDialog/CnDialog.vue'\nimport CnFlowDetail from '../CnFlowDetail/CnFlowDetail.vue'\nimport CnFlowRunSummary from '../CnFlowDetail/CnFlowRunSummary.vue'\nimport CnFlowSidebar from '../CnFlowDetail/CnFlowSidebar.vue'\nimport CnFlowStepLog from '../CnFlowDetail/CnFlowStepLog.vue'\nimport CnResourceSelect from '../CnResourceSelect/CnResourceSelect.vue'\nimport { FLOW_RUN_ACTIVE_STATUSES, useFlowStore } from '../../composables/useFlowStore.js'\nimport { formatRelativeDate } from '../../utils/boardDate.js'\nimport { flowOutcomeLevels, flowRunDurationMs, flowRunLogLines, flowRunName, formatFlowDuration } from '../../utils/flowRunLog.js'\nimport { appDisplayNames } from '../../utils/nextcloudApps.js'\nimport { CnStatusBadge } from '../CnStatusBadge/index.js'\n\n/**\n * The board-look flow editor and run view. CnFlowEditorPage renders it when\n * the app or page is in the board look; the Nextcloud look keeps the canvas\n * on its own with the app sidebar.\n */\nexport default {\n\tname: 'CnFlowBoard',\n\n\tcomponents: { CnBuildiqEditButton, CnDialog, CnFlowDetail, CnResourceSelect, TestTube, CnFlowRunSummary, CnFlowSidebar, CnFlowStepLog, CnStatusBadge, NcButton, NcCheckboxRadioSwitch, NcNoteCard },\n\n\tprops: {\n\t\t/** The flow uuid, or `new`. */\n\t\tid: { type: String, default: '' },\n\t\t/** The owning app, for a new flow and the list scope. */\n\t\tapp: { type: String, default: null },\n\t\t/** The run to show, or ''. */\n\t\trun: { type: String, default: '' },\n\t\t/** Where the flow list lives; flows open at `<detailRoute>/<id>`. */\n\t\tdetailRoute: { type: String, default: '/flows' },\n\t\t/** The list's name in the path (\"Flows\", \"Settings / Flows\"). */\n\t\tlistLabel: { type: String, default: '' },\n\t\t/** `column` draws the step palette as a card on the left. */\n\t\tpalette: { type: String, default: 'picker' },\n\t\t/** Free text under the path in the palette card. */\n\t\tpaletteFootnote: { type: String, default: '' },\n\t\t/** `down` runs the steps top to bottom and opens them at their own size (DqFlow); default `right`, left to right and fitted. */\n\t\tflowDirection: { type: String, default: 'right' },\n\t\t/** An info notice under the path. */\n\t\tnotice: { type: String, default: '' },\n\t\t/** The pill beside the title (\"Case type flow\"). */\n\t\tpill: { type: String, default: '' },\n\t\t/**\n\t\t * The records a test run can take as its subject: `{ register, schema,\n\t\t * labelField?, label? }`. Draws a header button (\"Testen met een zaak\",\n\t\t * DqFlow) that runs the saved flow once with a picked record.\n\t\t */\n\t\ttestSubject: { type: Object, default: null },\n\t},\n\n\temits: ['save', 'run', 'retried'],\n\n\tsetup() {\n\t\treturn { store: useFlowStore() }\n\t},\n\n\tdata() {\n\t\treturn { runTab: 'step', testOpen: false, testRecord: null, appNames: {}, showRuns: false }\n\t},\n\n\tcomputed: {\n\t\t/** @return {boolean} Whether a test subject is declared well enough to pick one. */\n\t\ttestSubjectReady() {\n\t\t\treturn Boolean(this.testSubject && this.testSubject.register && this.testSubject.schema)\n\t\t},\n\n\t\t/** @return {string} The test button's label: the page's own, else the library's. */\n\t\ttestLabel() {\n\t\t\treturn (this.testSubject && this.testSubject.label) || t('nextcloud-vue', 'Test with a record')\n\t\t},\n\n\t\t/** @return {boolean} Whether the palette card is drawn. */\n\t\tshowPalette() {\n\t\t\treturn this.palette === 'column' && !this.store.inRunView\n\t\t},\n\n\t\t/** @return {string} The page title. */\n\t\ttitleText() {\n\t\t\treturn this.store.flow.name || this.t('nextcloud-vue', 'New flow')\n\t\t},\n\n\t\t/** @return {string} The flow's name in the path. */\n\t\tflowName() {\n\t\t\treturn this.store.flow.name || this.t('nextcloud-vue', 'New flow')\n\t\t},\n\n\t\t/** @return {string} The pill text: the page's, else the lifecycle. */\n\t\tpillText() {\n\t\t\tif (this.pill) {\n\t\t\t\treturn this.pill\n\t\t\t}\n\t\t\tconst lifecycle = String(this.store.flow.lifecycleStatus || '')\n\t\t\tif (lifecycle === 'draft') {\n\t\t\t\treturn this.t('nextcloud-vue', 'Draft')\n\t\t\t}\n\t\t\tif (lifecycle === 'deprecated') {\n\t\t\t\treturn this.t('nextcloud-vue', 'Retired')\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\t/** @return {string} The list label. */\n\t\tlistText() {\n\t\t\treturn this.listLabel || this.t('nextcloud-vue', 'Flows')\n\t\t},\n\n\t\t/** @return {string} The flow's own path. */\n\t\tflowPath() {\n\t\t\treturn `${this.detailRoute}/${this.store.flow.id || this.id}`\n\t\t},\n\n\t\t/** @return {string} The list href. */\n\t\tlistHref() {\n\t\t\treturn this.hrefOf(this.detailRoute)\n\t\t},\n\n\t\t/** @return {string} The flow href. */\n\t\tflowHref() {\n\t\t\treturn this.hrefOf(this.flowPath)\n\t\t},\n\n\t\t/**\n\t\t * What the path says after the name: the trigger, the step count and\n\t\t * the version in the editor; when the run started and how long it\n\t\t * took in the run view.\n\t\t *\n\t\t * @return {string} The meta text.\n\t\t */\n\t\tmetaText() {\n\t\t\tif (this.store.inRunView) {\n\t\t\t\tconst record = this.runRecord\n\t\t\t\tif (!record) {\n\t\t\t\t\treturn ''\n\t\t\t\t}\n\t\t\t\tconst parts = [formatRelativeDate(record.created, { withTime: true })]\n\t\t\t\tconst duration = formatFlowDuration(flowRunDurationMs(record))\n\t\t\t\tif (duration) {\n\t\t\t\t\tparts.push(this.t('nextcloud-vue', 'done in {duration}', { duration }))\n\t\t\t\t}\n\t\t\t\treturn parts.filter(Boolean).join(' · ')\n\t\t\t}\n\t\t\tconst parts = []\n\t\t\tconst trigger = this.triggerText\n\t\t\tif (trigger) {\n\t\t\t\tparts.push(this.t('nextcloud-vue', 'trigger {trigger}', { trigger }))\n\t\t\t}\n\t\t\tconst count = this.store.nodes.length\n\t\t\tparts.push(count === 1 ? this.t('nextcloud-vue', '1 step') : this.t('nextcloud-vue', '{count} steps', { count }))\n\t\t\tif (this.store.flow.version) {\n\t\t\t\tparts.push(this.t('nextcloud-vue', 'version {version}', { version: this.store.flow.version }))\n\t\t\t}\n\t\t\treturn parts.join(' · ')\n\t\t},\n\n\t\t/** @return {string} How the flow starts, as the catalogue names it. */\n\t\ttriggerText() {\n\t\t\tconst trigger = String(this.store.flow.trigger || '')\n\t\t\tif (trigger === 'schedule') {\n\t\t\t\treturn this.t('nextcloud-vue', 'On a schedule')\n\t\t\t}\n\t\t\tif (trigger === '' || trigger === 'manual') {\n\t\t\t\treturn this.t('nextcloud-vue', 'Run by hand')\n\t\t\t}\n\t\t\tconst entry = (this.store.eventCatalog || []).find((item) => item && item.id === trigger)\n\t\t\treturn (entry && entry.label) || trigger\n\t\t},\n\n\t\t/** @return {boolean} Whether Run is offered. */\n\t\tshowRun() {\n\t\t\treturn this.store.nodes.some((node) => node?.type === 'openregister.trigger-manual')\n\t\t},\n\n\t\t/**\n\t\t * The engine's verdict on the canvas as one line under it.\n\t\t *\n\t\t * @return {{tone: string, text: string}} The validity bar.\n\t\t */\n\t\tvalidity() {\n\t\t\tconst result = this.store.checkResult\n\t\t\tif (!result) {\n\t\t\t\treturn { tone: 'unknown', text: this.t('nextcloud-vue', 'Not checked yet. Check runs the flow past the engine without saving it.') }\n\t\t\t}\n\t\t\tconst blocking = Array.isArray(result.blocking) ? result.blocking.length : 0\n\t\t\tconst warnings = Array.isArray(result.warnings) ? result.warnings.length : 0\n\t\t\tif (result.valid === false || blocking > 0) {\n\t\t\t\treturn { tone: 'error', text: result.message || this.t('nextcloud-vue', 'The flow cannot run as it is. See the messages on the canvas.') }\n\t\t\t}\n\t\t\tif (warnings > 0) {\n\t\t\t\treturn { tone: 'warning', text: this.t('nextcloud-vue', 'The flow is valid, with {count} warnings.', { count: warnings }) }\n\t\t\t}\n\t\t\treturn { tone: 'success', text: this.t('nextcloud-vue', 'The flow is valid.') }\n\t\t},\n\n\t\t/**\n\t\t * The palette grouped by owning app, each group named by the app's\n\t\t * display name (its id when Nextcloud names none), the app that owns\n\t\t * this flow first.\n\t\t *\n\t\t * @return {Array<{app: string, name: string, entries: Array<object>}>} The groups.\n\t\t */\n\t\tpaletteGroups() {\n\t\t\tconst groups = {}\n\t\t\t;(this.store.nodeCatalog || []).forEach((entry) => {\n\t\t\t\tconst app = String(entry?.app || String(entry?.id || '').split('.')[0] || '')\n\t\t\t\tif (!groups[app]) {\n\t\t\t\t\tgroups[app] = { app, name: this.appNames[app] || app, entries: [] }\n\t\t\t\t}\n\t\t\t\tgroups[app].entries.push(entry)\n\t\t\t})\n\t\t\tconst list = Object.values(groups)\n\t\t\tconst host = list.findIndex((group) => group.app === this.app)\n\t\t\treturn host > 0 ? [list[host], ...list.slice(0, host), ...list.slice(host + 1)] : list\n\t\t},\n\n\t\t/** @return {object|null} The run on screen. */\n\t\trunRecord() {\n\t\t\treturn this.store.runDetail\n\t\t\t\t|| (this.store.runs || []).find((item) => item.uuid === this.store.inspectedRunUuid)\n\t\t\t\t|| null\n\t\t},\n\n\t\t/** @return {string} The run's place in the path. */\n\t\trunCrumb() {\n\t\t\treturn this.t('nextcloud-vue', 'Run {id}', { id: flowRunName(this.runRecord) })\n\t\t},\n\n\t\t/** @return {boolean} Whether Run again is offered. */\n\t\tcanRetry() {\n\t\t\treturn !!this.runRecord && !FLOW_RUN_ACTIVE_STATUSES.includes(this.runRecord.status)\n\t\t},\n\n\t\t/** @return {Array<object>} Every line of the run on screen. */\n\t\tallRunLines() {\n\t\t\treturn this.runRecord ? flowRunLogLines(this.runRecord) : []\n\t\t},\n\n\t\t/** @return {Array<object>} The selected step's lines in this run. */\n\t\tselectedRunLines() {\n\t\t\treturn this.runRecord && this.store.selectedNodeId\n\t\t\t\t? flowRunLogLines(this.runRecord, this.store.selectedNodeId)\n\t\t\t\t: []\n\t\t},\n\n\t\t/** @return {Array<object>} The two log tabs with their counts. */\n\t\trunTabs() {\n\t\t\treturn [\n\t\t\t\t{ id: 'step', label: this.t('nextcloud-vue', 'This step'), count: this.selectedRunLines.length },\n\t\t\t\t{ id: 'run', label: this.t('nextcloud-vue', 'Logs of the whole run'), count: this.allRunLines.length },\n\t\t\t]\n\t\t},\n\n\t\t/** @return {Array<object>} The legend of outcomes on the canvas. */\n\t\tlegend() {\n\t\t\tconst levels = flowOutcomeLevels()\n\t\t\treturn ['success', 'warning', 'error', 'info', 'skipped'].map((id) => ({ id, ...levels[id] }))\n\t\t},\n\n\t\t/** @return {string} The selected step's type, as the catalogue names it. */\n\t\tstepTypeLabel() {\n\t\t\tconst node = this.store.selectedNode\n\t\t\tconst entry = node ? this.store.catalogEntry(node.type) : null\n\t\t\treturn entry ? (entry.displayName || entry.id) : String(node?.type || '')\n\t\t},\n\n\t\t/**\n\t\t * The selected step's settings as its owner declares them in the\n\t\t * catalogue's `configForm`: label and value, a select's option label,\n\t\t * Yes or No for a switch. Unset settings are left out.\n\t\t *\n\t\t * @return {Array<{key: string, label: string, value: string}>} The settings.\n\t\t */\n\t\tstepSettings() {\n\t\t\tconst node = this.store.selectedNode\n\t\t\tconst entry = node ? this.store.catalogEntry(node.type) : null\n\t\t\tconst config = (node && node.config) || {}\n\t\t\tconst form = (entry && Array.isArray(entry.configForm)) ? entry.configForm : []\n\t\t\treturn form\n\t\t\t\t.filter((field) => field && field.key && config[field.key] !== undefined && config[field.key] !== null && config[field.key] !== '')\n\t\t\t\t.map((field) => {\n\t\t\t\t\tconst raw = config[field.key]\n\t\t\t\t\tconst option = Array.isArray(field.options) ? field.options.find((o) => o && o.value === raw) : null\n\t\t\t\t\tlet value\n\t\t\t\t\tif (option) {\n\t\t\t\t\t\tvalue = String(option.label ?? option.value)\n\t\t\t\t\t} else if (typeof raw === 'boolean') {\n\t\t\t\t\t\tvalue = raw ? this.t('nextcloud-vue', 'Yes') : this.t('nextcloud-vue', 'No')\n\t\t\t\t\t} else if (typeof raw === 'object') {\n\t\t\t\t\t\tvalue = JSON.stringify(raw)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tvalue = String(raw)\n\t\t\t\t\t}\n\t\t\t\t\treturn { key: field.key, label: field.label || field.key, value }\n\t\t\t\t})\n\t\t},\n\n\t\t/** @return {string} The selected step's name. */\n\t\tstepName() {\n\t\t\tconst node = this.store.selectedNode\n\t\t\treturn (node && (node.name || node.label)) || this.stepTypeLabel\n\t\t},\n\n\t\t/**\n\t\t * The selected step's lines from the flow's recent runs, newest run\n\t\t * first, at most ten.\n\t\t *\n\t\t * @return {Array<object>} The lines.\n\t\t */\n\t\trecentStepLines() {\n\t\t\tconst id = this.store.selectedNodeId\n\t\t\tif (!id) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\tconst lines = []\n\t\t\t;(this.store.runs || []).forEach((item) => {\n\t\t\t\tflowRunLogLines(item, id).forEach((line) => lines.push({ ...line, key: `${item.uuid}-${line.key}` }))\n\t\t\t})\n\t\t\treturn lines.slice(0, 10)\n\t\t},\n\n\t\t/** @return {string} The newest run's uuid, or ''. */\n\t\tlastRunUuid() {\n\t\t\treturn (this.store.runs && this.store.runs[0] && this.store.runs[0].uuid) || ''\n\t\t},\n\t},\n\n\twatch: {\n\t\t// A selected step takes the panel back from the run history.\n\t\t'store.selectedNodeId': function(id) {\n\t\t\tif (id) {\n\t\t\t\tthis.showRuns = false\n\t\t\t}\n\t\t},\n\t},\n\n\tasync created() {\n\t\t// The palette names its groups by app display name; only the column\n\t\t// palette draws them, so only it reads the names.\n\t\tif (this.palette !== 'column') {\n\t\t\treturn\n\t\t}\n\t\tconst { default: axios } = await import('@nextcloud/axios')\n\t\tthis.appNames = await appDisplayNames(axios)\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Run the flow, with the run history in the panel to follow it.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonRun() {\n\t\t\tthis.showRuns = true\n\t\t\tthis.$emit('run')\n\t\t},\n\n\t\t/**\n\t\t * Run the saved flow once with the picked record as its subject, then\n\t\t * close the dialog; the run shows on the canvas as any run does.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync runTest() {\n\t\t\tconst uuid = Array.isArray(this.testRecord) ? this.testRecord[0] : this.testRecord\n\t\t\tif (!uuid) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tawait this.store.run({ uuid, register: this.testSubject.register, schema: this.testSubject.schema })\n\t\t\tthis.testOpen = false\n\t\t\tthis.testRecord = null\n\t\t},\n\n\t\tt,\n\n\t\t/**\n\t\t * @param {string} path An app route path.\n\t\t * @return {string} Its href.\n\t\t */\n\t\threfOf(path) {\n\t\t\tif (this.$router && typeof this.$router.resolve === 'function') {\n\t\t\t\ttry {\n\t\t\t\t\tconst resolved = this.$router.resolve(path)\n\t\t\t\t\tif (resolved && resolved.href) {\n\t\t\t\t\t\treturn resolved.href\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\t// A path the router does not know stays a plain path.\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn path\n\t\t},\n\n\t\t/**\n\t\t * Follow a path link on the app's router; a modified click keeps the\n\t\t * browser's own behaviour.\n\t\t *\n\t\t * @param {MouseEvent} event The click.\n\t\t * @param {string} path The target path.\n\t\t * @return {void}\n\t\t */\n\t\tonCrumbClick(event, path) {\n\t\t\tif (!this.$router || event.ctrlKey || event.metaKey || event.shiftKey || event.button === 1) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tevent.preventDefault()\n\t\t\tthis.$router.push(path).catch(() => {})\n\t\t},\n\n\t\t/**\n\t\t * Switch the flow on or off. The change is saved with the flow.\n\t\t *\n\t\t * @param {boolean} on The new state.\n\t\t * @return {void}\n\t\t */\n\t\tonEnabledChange(on) {\n\t\t\tthis.store.flow.enabled = on === true\n\t\t\tthis.store.dirty = true\n\t\t},\n\n\t\t/**\n\t\t * Open a run of this flow on the canvas.\n\t\t *\n\t\t * @param {string} uuid The run.\n\t\t * @return {void}\n\t\t */\n\t\topenRun(uuid) {\n\t\t\tif (this.$router) {\n\t\t\t\tthis.$router.push({ path: this.flowPath, query: { run: uuid } }).catch(() => {})\n\t\t\t} else {\n\t\t\t\tthis.store.inspectRun(uuid)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Leave the run view for the flow.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonBackToFlow() {\n\t\t\tthis.store.closeRun()\n\t\t\tif (this.$router) {\n\t\t\t\tthis.$router.push({ path: this.flowPath }).catch(() => {})\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Start the shown run again and open the new one.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onRetry() {\n\t\t\tconst retried = await this.store.retryRun(this.runRecord?.uuid)\n\t\t\tthis.$emit('retried', retried)\n\t\t\tif (retried && retried.uuid) {\n\t\t\t\tthis.openRun(retried.uuid)\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n/* The board page's padding, so the title stands where every board page puts it (DqFlow). */\n.cn-flow-board {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n\tpadding: var(--cn-board-content-padding, 24px 28px);\n\tmin-height: 100%;\n\tbox-sizing: border-box;\n}\n\n.cn-flow-board__header {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 12px;\n}\n\n.cn-flow-board__heading {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 10px;\n\tmin-width: 0;\n}\n\n.cn-flow-board__title {\n\tmargin: 0;\n\tfont-size: 24px;\n\tfont-weight: 600;\n}\n\n.cn-flow-board__actions {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.cn-flow-board__test-intro {\n\tmargin: 0 0 12px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-flow-board__path {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-flow-board__crumb {\n\tcolor: var(--color-main-text);\n\ttext-decoration: underline;\n}\n\n.cn-flow-board__body {\n\tdisplay: grid;\n\tgrid-template-columns: minmax(0, 1fr) 340px;\n\tgap: 16px;\n\tflex: 1 1 auto;\n\tmin-height: 520px;\n}\n\n/* Palette, canvas and inspector at 220 / rest / 280 (DqFlow). */\n.cn-flow-board--with-palette .cn-flow-board__body {\n\tgrid-template-columns: 220px minmax(0, 1fr) 280px;\n}\n\n.cn-flow-board__card {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n\tmin-width: 0;\n\tpadding: 16px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: 12px;\n\tbackground: var(--color-main-background);\n}\n\n/* The canvas is the white card with the dot grid; the palette beside it sits\n on the page ground (DqFlow). */\n.cn-flow-board__canvas {\n\tpadding: 0;\n\toverflow: hidden;\n\tbackground-color: var(--color-main-background);\n\tbackground-image: radial-gradient(var(--color-border) 1px, transparent 1px);\n\tbackground-size: 16px 16px;\n}\n\n.cn-flow-board__palette {\n\tgap: 14px;\n\tpadding: 14px;\n\tbackground: transparent;\n}\n\n.cn-flow-board__canvas > :first-child {\n\tflex: 1 1 auto;\n\tmin-height: 0;\n}\n\n.cn-flow-board__card-title {\n\tmargin: 0;\n\tfont-size: 16px;\n\tfont-weight: 600;\n}\n\n.cn-flow-board__subtitle {\n\tmargin: 4px 0 0;\n\tfont-size: 14px;\n\tfont-weight: 600;\n}\n\n.cn-flow-board__step-type {\n\talign-self: flex-start;\n}\n\n.cn-flow-board__validity,\n.cn-flow-board__legend {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 8px;\n\tmargin: 0;\n\tpadding: 10px 16px;\n\tborder-top: 1px solid var(--color-border);\n\tbackground: var(--color-main-background);\n}\n\n.cn-flow-board__validity--success {\n\tcolor: var(--color-success-text);\n}\n\n.cn-flow-board__validity--warning {\n\tcolor: var(--color-warning-text);\n}\n\n.cn-flow-board__validity--error {\n\tcolor: var(--color-error-text);\n}\n\n.cn-flow-board__palette-group {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n}\n\n/* The providing app as a small caps label, no title row above the groups. */\n.cn-flow-board__palette-app {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 12px;\n\tfont-weight: 600;\n\tletter-spacing: 0.06em;\n\ttext-transform: uppercase;\n}\n\n.cn-flow-board__palette-list {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 6px;\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n/* A step is a plain 400 label (DqFlow \"Status zetten\"); the nldesign theme\n sets every button's weight with `!important` through its token. */\n.cn-flow-board__palette-step {\n\t--nldesign-component-button-font-weight: 400;\n\twidth: 100%;\n\tmin-height: 34px;\n\tpadding: 8px 10px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: 8px;\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-main-text);\n\tfont-size: 14px;\n\tfont-weight: 400;\n\ttext-align: start;\n\tcursor: pointer;\n}\n\n.cn-flow-board__palette-step:hover {\n\tbackground: var(--color-background-hover);\n}\n\n.cn-flow-board__palette-note {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-flow-board__tabs {\n\tdisplay: flex;\n\tgap: 4px;\n\tborder-bottom: 1px solid var(--color-border);\n}\n\n.cn-flow-board__tab {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 8px 10px;\n\tmargin-bottom: -1px;\n\tborder: 1px solid transparent;\n\tborder-radius: 8px 8px 0 0;\n\tbackground: transparent;\n\tcolor: var(--color-main-text);\n\tcursor: pointer;\n}\n\n.cn-flow-board__tab--active {\n\tborder-color: var(--color-border);\n\tborder-bottom-color: var(--color-main-background);\n\tfont-weight: 600;\n}\n\n.cn-flow-board__tab-count {\n\tmin-width: 18px;\n\tpadding: 0 5px;\n\tborder-radius: 9px;\n\tbackground: var(--color-background-dark);\n\tfont-size: 12px;\n\tline-height: 18px;\n\ttext-align: center;\n}\n\n.cn-flow-board__settings {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 10px;\n\tmargin: 0;\n}\n\n.cn-flow-board__setting dt {\n\tfont-size: 13px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-flow-board__setting dd {\n\tmargin: 2px 0 0;\n\tfont-weight: 600;\n\toverflow-wrap: anywhere;\n}\n\n.cn-flow-board__inspector-empty {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: flex-start;\n\tgap: 12px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-flow-board__inspector-empty p {\n\tmargin: 0;\n}\n\n.cn-flow-board__step-actions,\n.cn-flow-board__step-footer {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 8px;\n}\n\n.cn-flow-board__step-footer {\n\tjustify-content: space-between;\n\tmargin-top: auto;\n}\n\n@media (max-width: 1024px) {\n\t.cn-flow-board__body,\n\t.cn-flow-board--with-palette .cn-flow-board__body {\n\t\tgrid-template-columns: minmax(0, 1fr);\n\t}\n}\n</style>\n"],"names":["_createElementBlock","_normalizeClass","_createElementVNode","_toDisplayString","_createBlock","_createTextVNode","_createVNode","_Fragment","_openBlock","_renderList"],"mappings":"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"}
@@ -98,6 +98,16 @@ var script = {
98
98
  default: '',
99
99
  },
100
100
 
101
+ /**
102
+ * Board look: `down` lays the steps out top to bottom and opens them
103
+ * at their own size instead of zoomed to fit; `right` (default) lays
104
+ * them left to right. Manifest key `config.flowDirection`.
105
+ */
106
+ flowDirection: {
107
+ type: String,
108
+ default: 'right',
109
+ },
110
+
101
111
  /**
102
112
  * Board look: the records a test run can take as its subject,
103
113
  * `{ register, schema, labelField?, label? }`. Manifest key
@@ -1 +1 @@
1
- {"version":3,"file":"CnFlowEditorPage.vue2.js","sources":["../../../../src/components/CnFlowsPage/CnFlowEditorPage.vue"],"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":["CnFlowBoard","CnFlowDetail"],"mappings":";;;;;AA8BA;AACC;AACA;AACA;AACA;;AAEA;;;AAGA;AACA;AACA;AACA;AACA;;AAEA;;AAED,aAAe;CACd,IAAI,EAAE,kBAAkB;;AAExB,CAAA,UAAU,EAAE,eAAEA,UAAW,gBAAEC,cAAc;;AAEzC,CAAA,MAAM,EAAE;AACP,EAAA,WAAW,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,GAAG,KAAK,KAAK;EAC5C;;AAED,CAAA,KAAK,EAAE;AACN;;;;EAIA,GAAG,EAAE;GACJ,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,IAAI;GACb;;AAED;;;AAGC;;AAED,EAAA,EAAE,EAAE;GACH,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;;;EAIA,WAAW,EAAE;GACZ,IAAI,EAAE,MAAM;AACZ,GAAA,OAAO,EAAE,QAAQ;GACjB;;AAED;;;;;;EAMA,GAAG,EAAE;GACJ,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;;;EAIA,IAAI,EAAE;GACL,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;;;;;AAMA,EAAA,OAAO,EAAE;GACR,IAAI,EAAE,MAAM;AACZ,GAAA,OAAO,EAAE,QAAQ;GACjB;;AAED;;;;AAIA,EAAA,eAAe,EAAE;GAChB,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;;AAGC;;EAED,WAAW,EAAE;GACZ,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,IAAI;GACb;;AAED;;;;AAIA,EAAA,MAAM,EAAE;GACP,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;;AAGC;;EAED,IAAI,EAAE;GACL,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;;;EAIA,SAAS,EAAE;GACV,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;EACD;;AAED;;;AAGC;AACA;AACA;;CAED,KAAK,CAAC,KAAK,EAAE;EACZ,MAAM,EAAE,OAAQ,EAAA,GAAI,OAAO,CAAC,KAAK,EAAA;AACjC,EAAA,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,EAAE,OAAQ,EAAA;EACxC;;AAED,CAAA,QAAQ,EAAE;AACT;AACC;;AAED,EAAA,MAAM,GAAG;AACR,GAAA,OAAO,IAAI,CAAC,EAAG,IAAG,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,EAAG,IAAG,EAAC;GAC9C;;AAED;;;AAGA,EAAA,OAAO,GAAG;AACT,GAAA,OAAO,IAAI,CAAC,GAAI,IAAG,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,GAAI,IAAG,EAAC;GAC/C;EACD;;AAED,CAAA,OAAO,EAAE;AACR;;;EAGA,MAAM,MAAM,GAAG;AACd,GAAA,MAAM,KAAM,GAAE,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,GAAC;;;AAGpC,GAAA,IAAI,KAAK,EAAE,EAAC,IAAK,IAAI,CAAC,MAAO,KAAI,KAAK,EAAE;IACvC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC,EAAA;AACvD,IAAA;GACA;;AAED;;;EAGA,MAAM,KAAK,GAAG;GACb,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,EAAA;GACvB;EACD;AACF;;;;"}
1
+ {"version":3,"file":"CnFlowEditorPage.vue2.js","sources":["../../../../src/components/CnFlowsPage/CnFlowEditorPage.vue"],"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:flowDirection=\"flowDirection\"\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: `down` lays the steps out top to bottom and opens them\n\t\t * at their own size instead of zoomed to fit; `right` (default) lays\n\t\t * them left to right. Manifest key `config.flowDirection`.\n\t\t */\n\t\tflowDirection: {\n\t\t\ttype: String,\n\t\t\tdefault: 'right',\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":["CnFlowBoard","CnFlowDetail"],"mappings":";;;;;AA+BA;AACC;AACA;AACA;AACA;;AAEA;;;AAGA;AACA;AACA;AACA;AACA;;AAEA;;AAED,aAAe;CACd,IAAI,EAAE,kBAAkB;;AAExB,CAAA,UAAU,EAAE,eAAEA,UAAW,gBAAEC,cAAc;;AAEzC,CAAA,MAAM,EAAE;AACP,EAAA,WAAW,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,GAAG,KAAK,KAAK;EAC5C;;AAED,CAAA,KAAK,EAAE;AACN;;;;EAIA,GAAG,EAAE;GACJ,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,IAAI;GACb;;AAED;;;AAGC;;AAED,EAAA,EAAE,EAAE;GACH,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;;;EAIA,WAAW,EAAE;GACZ,IAAI,EAAE,MAAM;AACZ,GAAA,OAAO,EAAE,QAAQ;GACjB;;AAED;;;;;;EAMA,GAAG,EAAE;GACJ,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;;;EAIA,IAAI,EAAE;GACL,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;;;;;AAMA,EAAA,OAAO,EAAE;GACR,IAAI,EAAE,MAAM;AACZ,GAAA,OAAO,EAAE,QAAQ;GACjB;;AAED;;;;AAIA,EAAA,eAAe,EAAE;GAChB,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;;;;AAKA,EAAA,aAAa,EAAE;GACd,IAAI,EAAE,MAAM;AACZ,GAAA,OAAO,EAAE,OAAO;GAChB;;AAED;;;AAGC;;EAED,WAAW,EAAE;GACZ,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,IAAI;GACb;;AAED;;;;AAIA,EAAA,MAAM,EAAE;GACP,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;;AAGC;;EAED,IAAI,EAAE;GACL,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;;;EAIA,SAAS,EAAE;GACV,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;EACD;;AAED;;;AAGC;AACA;AACA;;CAED,KAAK,CAAC,KAAK,EAAE;EACZ,MAAM,EAAE,OAAQ,EAAA,GAAI,OAAO,CAAC,KAAK,EAAA;AACjC,EAAA,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,EAAE,OAAQ,EAAA;EACxC;;AAED,CAAA,QAAQ,EAAE;AACT;AACC;;AAED,EAAA,MAAM,GAAG;AACR,GAAA,OAAO,IAAI,CAAC,EAAG,IAAG,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,EAAG,IAAG,EAAC;GAC9C;;AAED;;;AAGA,EAAA,OAAO,GAAG;AACT,GAAA,OAAO,IAAI,CAAC,GAAI,IAAG,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,GAAI,IAAG,EAAC;GAC/C;EACD;;AAED,CAAA,OAAO,EAAE;AACR;;;EAGA,MAAM,MAAM,GAAG;AACd,GAAA,MAAM,KAAM,GAAE,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,GAAC;;;AAGpC,GAAA,IAAI,KAAK,EAAE,EAAC,IAAK,IAAI,CAAC,MAAO,KAAI,KAAK,EAAE;IACvC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC,EAAA;AACvD,IAAA;GACA;;AAED;;;EAGA,MAAM,KAAK,GAAG;GACb,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,EAAA;GACvB;EACD;AACF;;;;"}
@@ -14,12 +14,13 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
14
14
  listLabel: $props.listLabel ? $options.cnTranslate($props.listLabel) : '',
15
15
  palette: $props.palette,
16
16
  paletteFootnote: $props.paletteFootnote ? $options.cnTranslate($props.paletteFootnote) : '',
17
+ flowDirection: $props.flowDirection,
17
18
  notice: $props.notice ? $options.cnTranslate($props.notice) : '',
18
19
  pill: $props.pill ? $options.cnTranslate($props.pill) : '',
19
20
  testSubject: $props.testSubject ? { ...$props.testSubject, label: $props.testSubject.label ? $options.cnTranslate($props.testSubject.label) : '' } : null,
20
21
  onSave: $options.onSave,
21
22
  onRun: $options.onRun
22
- }, null, 8 /* PROPS */, ["id", "app", "run", "detailRoute", "listLabel", "palette", "paletteFootnote", "notice", "pill", "testSubject", "onSave", "onRun"]))
23
+ }, null, 8 /* PROPS */, ["id", "app", "run", "detailRoute", "listLabel", "palette", "paletteFootnote", "flowDirection", "notice", "pill", "testSubject", "onSave", "onRun"]))
23
24
  : (openBlock(), createBlock(_component_CnFlowDetail, {
24
25
  key: 1,
25
26
  id: $options.flowId,
@@ -1 +1 @@
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;;;;;;;;"}
1
+ {"version":3,"file":"CnFlowEditorPage.vue3.js","sources":["../../../../src/components/CnFlowsPage/CnFlowEditorPage.vue?vue&type=template&id=0ff1065e&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:flowDirection=\"flowDirection\"\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: `down` lays the steps out top to bottom and opens them\n\t\t * at their own size instead of zoomed to fit; `right` (default) lays\n\t\t * them left to right. Manifest key `config.flowDirection`.\n\t\t */\n\t\tflowDirection: {\n\t\t\ttype: String,\n\t\t\tdefault: 'right',\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,WAaW,CAAA,sBAAA,EAAA;;AAZV,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,MAAO,CAAA,OAAA;AAChB,QAAA,eAAe,EAAE,MAAA,CAAA,eAAe,GAAG,QAAA,CAAA,WAAM,CAAA,MAAA,CAAA,eAAA,CAAA,GAAA,EAAA;AACzC,QAAA,aAAa,EAAE,MAAa,CAAA,aAAA;AAC5B,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;;;;;;;;"}
@@ -74,6 +74,15 @@ var script = {
74
74
  default: true,
75
75
  },
76
76
 
77
+ /**
78
+ * The most that first frame may zoom in. 0 lets it go to `maxZoom`;
79
+ * 1 keeps a graph that fits at its own size.
80
+ */
81
+ fitMaxZoom: {
82
+ type: Number,
83
+ default: 0,
84
+ },
85
+
77
86
  /** Snap dragged nodes to the grid. */
78
87
  snapToGrid: {
79
88
  type: Boolean,
@@ -161,6 +170,13 @@ var script = {
161
170
  return { project, zoomIn, zoomOut, zoomTo, fitViewNow, viewport }
162
171
  },
163
172
 
173
+ data() {
174
+ return {
175
+ // Whether the capped first frame (`fitMaxZoom`) has been taken.
176
+ framedOnce: false,
177
+ }
178
+ },
179
+
164
180
  computed: {
165
181
  /**
166
182
  * @return {boolean} Whether the canvas accepts interaction at all.
@@ -183,6 +199,21 @@ var script = {
183
199
  },
184
200
 
185
201
  methods: {
202
+ /**
203
+ * Frame the graph once its nodes have a size, no closer than
204
+ * `fitMaxZoom`. Only the first time: a step added later must not move
205
+ * the view the author is working in.
206
+ *
207
+ * @return {void}
208
+ */
209
+ onNodesInitialized() {
210
+ if (!this.fitView || !this.fitMaxZoom || this.framedOnce) {
211
+ return
212
+ }
213
+ this.framedOnce = true;
214
+ this.fitViewNow({ maxZoom: this.fitMaxZoom, padding: 0.1 });
215
+ },
216
+
186
217
  /**
187
218
  * Forward Vue Flow's node changes.
188
219
  *
@@ -1 +1 @@
1
- {"version":3,"file":"CnGraphCanvas.vue2.js","sources":["../../../../src/components/CnGraphCanvas/CnGraphCanvas.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>\n - SPDX-License-Identifier: EUPL-1.2\n -->\n<template>\n\t<div class=\"cn-graph-canvas\" @drop=\"onDrop\" @dragover.prevent>\n\t\t<VueFlow\n\t\t\t:nodes=\"nodes\"\n\t\t\t:edges=\"edges\"\n\t\t\t:minZoom=\"minZoom\"\n\t\t\t:maxZoom=\"maxZoom\"\n\t\t\t:fitViewOnInit=\"fitView\"\n\t\t\t:snapToGrid=\"snapToGrid\"\n\t\t\t:snapGrid=\"snapGrid\"\n\t\t\t:nodesDraggable=\"interactive\"\n\t\t\t:nodesConnectable=\"interactive\"\n\t\t\t:elementsSelectable=\"interactive\"\n\t\t\t:connectionMode=\"connectionMode\"\n\t\t\t@nodesChange=\"onNodesChange\"\n\t\t\t@edgesChange=\"onEdgesChange\"\n\t\t\t@connect=\"onConnect\"\n\t\t\t@nodeClick=\"onNodeClick\"\n\t\t\t@edgeClick=\"onEdgeClick\"\n\t\t\t@paneClick=\"onPaneClick\">\n\t\t\t<!-- Per-type node components, Vue Flow's convention. A host that\n\t\t\t registers `type: 'trigger'` gets `#node-trigger`; anything\n\t\t\t unregistered falls back to CnFlowNode, which carries the\n\t\t\t keyboard contract. -->\n\t\t\t<template #node-default=\"nodeProps\">\n\t\t\t\t<!-- `@connect` routes a KEYBOARD-completed connection into the\n\t\t\t\t same handler as a pointer one, so `onConnect` is the single\n\t\t\t\t place a new edge leaves this component regardless of input.\n\t\t\t\t Without it the keyboard path wrote into Vue Flow's internal\n\t\t\t\t graph and the host never heard about it — the edge drew and\n\t\t\t\t the save dropped it. -->\n\t\t\t\t<CnFlowNode\n\t\t\t\t\tv-bind=\"nodeProps\"\n\t\t\t\t\t:deletable=\"!readOnly\"\n\t\t\t\t\t@connect=\"onConnect\"\n\t\t\t\t\t@remove=\"onNodeRemove\"\n\t\t\t\t\t@activate=\"onNodeActivate\"\n\t\t\t\t\t@menu=\"onNodeMenu\">\n\t\t\t\t\t<template #default=\"slotProps\">\n\t\t\t\t\t\t<!-- @slot node The body of a step, rendered inside the\n\t\t\t\t\t\t focusable node wrapper. Receives `{ node }` with the\n\t\t\t\t\t\t node's `id`, `data` and `selected`. The wrapper — and\n\t\t\t\t\t\t with it the keyboard contract and ARIA state — stays\n\t\t\t\t\t\t the canvas's, so a host cannot accidentally replace a\n\t\t\t\t\t\t focusable node with an inert div. -->\n\t\t\t\t\t\t<slot name=\"node\" v-bind=\"slotProps\" />\n\t\t\t\t\t</template>\n\t\t\t\t</CnFlowNode>\n\t\t\t</template>\n\n\t\t\t<!-- Per-type edge components, the same convention as nodes above.\n\t\t\t Registered on `default` because that is the type an edge gets\n\t\t\t when nothing else is asked for — which is every edge, now that\n\t\t\t the ROUTER moved off `type` and into `data.lineType`.\n\n\t\t\t That move is the edge half of the lesson the node slot records:\n\t\t\t `type` selects a COMPONENT, and a router is not a component. So\n\t\t\t long as `type` carried `smoothstep`, Vue Flow answered with its\n\t\t\t own built-in edge and no consumer could add anything to a line\n\t\t\t at all. -->\n\t\t\t<template #edge-default=\"edgeProps\">\n\t\t\t\t<CnFlowEdge\n\t\t\t\t\tv-bind=\"edgeProps\"\n\t\t\t\t\t:lineType=\"edgeProps.data && edgeProps.data.lineType\"\n\t\t\t\t\t:animated=\"!(edgeProps.data && edgeProps.data.animated === false)\"\n\t\t\t\t\t:label-aria-label=\"edgeProps.data && edgeProps.data.labelAriaLabel\"\n\t\t\t\t\t@labelMove=\"onEdgeLabelMove\"\n\t\t\t\t\t@labelClick=\"onEdgeLabelClick\"\n\t\t\t\t\t@labelContext=\"onEdgeLabelContext\">\n\t\t\t\t\t<template #label=\"slotProps\">\n\t\t\t\t\t\t<!-- @slot edge-label The chrome of a connection's\n\t\t\t\t\t\t label. Receives `{ edge }` with the edge's `id`,\n\t\t\t\t\t\t `data` and `selected`. Rendered inside a focusable\n\t\t\t\t\t\t control the canvas owns, so render inert content:\n\t\t\t\t\t\t the arrow keys, the focus ring and the ARIA name\n\t\t\t\t\t\t are already handled. An edge whose host renders\n\t\t\t\t\t\t nothing here draws no label control at all. -->\n\t\t\t\t\t\t<slot name=\"edge-label\" v-bind=\"slotProps\" />\n\t\t\t\t\t</template>\n\n\t\t\t\t\t<template #adornment=\"slotProps\">\n\t\t\t\t\t\t<!-- @slot edge-adornment A host's own controls beside a\n\t\t\t\t\t\t connection's label. Receives `{ edge }`. Unlike\n\t\t\t\t\t\t `edge-label` this is NOT inside the label's button,\n\t\t\t\t\t\t so what goes here may be interactive — a replay's\n\t\t\t\t\t\t payload control is the case it exists for. -->\n\t\t\t\t\t\t<slot name=\"edge-adornment\" v-bind=\"slotProps\" />\n\t\t\t\t\t</template>\n\t\t\t\t</CnFlowEdge>\n\t\t\t</template>\n\n\t\t\t<Background v-if=\"showBackground\" :gap=\"snapGrid[0]\" />\n\t\t\t<MiniMap v-if=\"showMiniMap\" />\n\t\t</VueFlow>\n\n\t\t<!-- OUR OWN CONTROLS, not `@vue-flow/controls`.\n\t\t The library's control bar renders bare <button> elements with an\n\t\t icon and no accessible name, which axe reports as `button-name` at\n\t\t SERIOUS impact — caught by the e2e in this change, not by the unit\n\t\t lane or the gates. Rather than ship a control bar a screen-reader\n\t\t user cannot identify, the buttons are ours and carry real labels. -->\n\t\t<div v-if=\"showControls\" class=\"cn-graph-canvas__controls\">\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclass=\"cn-graph-canvas__control\"\n\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Zoom in')\"\n\t\t\t\t@click=\"zoomIn()\">\n\t\t\t\t+\n\t\t\t</button>\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclass=\"cn-graph-canvas__control\"\n\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Zoom out')\"\n\t\t\t\t@click=\"zoomOut()\">\n\t\t\t\t−\n\t\t\t</button>\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclass=\"cn-graph-canvas__control\"\n\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Fit the whole flow in view')\"\n\t\t\t\t@click=\"fitViewNow()\">\n\t\t\t\t⤢\n\t\t\t</button>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { Background } from '@vue-flow/background'\nimport { useVueFlow, VueFlow } from '@vue-flow/core'\nimport { MiniMap } from '@vue-flow/minimap'\nimport CnFlowEdge from './CnFlowEdge.vue'\nimport CnFlowNode from './CnFlowNode.vue'\n\n/**\n * A graph canvas built on Vue Flow — the library n8n runs.\n *\n * WHY THIS IS NOT A WRAPPER AROUND THE OLD API\n * --------------------------------------------\n * This component used to be ~1,300 hand-written lines re-implementing pan,\n * zoom, drag, connect, resize and hit-testing on a bare SVG, because the fleet\n * was on Vue 2.7 and every maintained canvas library is Vue-3-only (ADR-065\n * §5). ADR-081 removed that constraint and named this library as the reason for\n * the migration.\n *\n * The props therefore MOVED rather than being translated. The graph page was\n * not in production, so there was no consumer to protect, and a shim can only\n * ever expose what the old API could already express — `fitView`, `<MiniMap>`,\n * `connectionMode` and the node resizer would all have stayed unreachable\n * behind props designed for a hand-drawn SVG.\n *\n * `nodeWidth` / `nodeHeight` are gone entirely. They existed only so hand-drawn\n * edges could guess where a node's centre was, and the old docblock admitted\n * the failure mode: \"set them to match what your node slot actually renders, or\n * edges will attach off-centre\". Vue Flow measures the rendered node, so the\n * whole class of bug went with the props.\n *\n * ⚠️ THE CANVAS STILL NEVER MUTATES THE GRAPH. Vue Flow is perfectly willing to\n * own node positions internally; here it does not. Changes are emitted and the\n * host decides what to persist — the same rule the previous implementation\n * held, and the reason a canvas can be dropped into a read-only surface without\n * auditing it first.\n *\n * ⚠️ KEYBOARD OPERATION LIVES IN {@see CnFlowNode}. Vue Flow is pointer-first;\n * the focus/ARIA/key contract is re-implemented in the node component, not\n * inherited from the library. See that file for why the multi-exit case is the\n * one that matters.\n */\nexport default {\n\tname: 'CnGraphCanvas',\n\n\tcomponents: { VueFlow, Background, MiniMap, CnFlowNode, CnFlowEdge },\n\n\tprops: {\n\t\t/** Nodes in Vue Flow's shape: `{ id, type, position: {x,y}, data }`. */\n\t\tnodes: {\n\t\t\ttype: Array,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** Edges in Vue Flow's shape: `{ id, source, target }`. */\n\t\tedges: {\n\t\t\ttype: Array,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/**\n\t\t * When true, every interaction is refused — drag, connect AND select.\n\t\t *\n\t\t * Expressed as three Vue Flow flags below. Missing one produces a canvas\n\t\t * that LOOKS locked and is not, so the three are set together and tested\n\t\t * together.\n\t\t */\n\t\treadOnly: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Frame the whole graph on first render. */\n\t\tfitView: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Snap dragged nodes to the grid. */\n\t\tsnapToGrid: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Grid spacing, as Vue Flow's `[x, y]`. */\n\t\tsnapGrid: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [16, 16],\n\t\t},\n\n\t\t/** Minimum zoom. */\n\t\tminZoom: {\n\t\t\ttype: Number,\n\t\t\tdefault: 0.2,\n\t\t},\n\n\t\t/** Maximum zoom. */\n\t\tmaxZoom: {\n\t\t\ttype: Number,\n\t\t\tdefault: 2,\n\t\t},\n\n\t\t/** Vue Flow's connection mode — `loose` or `strict`. */\n\t\tconnectionMode: {\n\t\t\ttype: String,\n\t\t\tdefault: 'loose',\n\t\t},\n\n\t\t/** Draw the dotted background. */\n\t\tshowBackground: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Draw the zoom/fit controls. */\n\t\tshowControls: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Draw the minimap. */\n\t\tshowMiniMap: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\t},\n\n\temits: [\n\t\t'nodes-change',\n\t\t'edges-change',\n\t\t'connect',\n\t\t'node-select',\n\t\t'edge-select',\n\t\t'edge-label-move',\n\t\t'edge-label-click',\n\t\t'edge-label-context',\n\t\t'canvas-click',\n\t\t'canvas-drop',\n\n\t\t'node-remove',\n\t\t'node-activate',\n\t\t'node-menu',\n\n\t\t/**\n\t\t * The canvas zoom changed, by any route — a control here, the host's own\n\t\t * toolbar, the wheel, or a fit-view.\n\t\t *\n\t\t * @type {number}\n\t\t */\n\t\t'zoom-change',\n\t],\n\n\tsetup() {\n\t\t// ⚠️ ALIASED. `fitView` is already a BOOLEAN PROP on this component\n\t\t// (frame the graph on first render), and Vue Flow's composable exports a\n\t\t// FUNCTION of the same name. Returning both puts two `fitView` bindings\n\t\t// on the instance — eslint's `vue/no-dupe-keys` catches it, but the\n\t\t// symptom would have been a fit button that tries to call `true`.\n\t\t// `zoomTo` and `viewport` so a HOST toolbar can drive the zoom and show\n\t\t// the real figure — including one the wheel or a fit-view changed.\n\t\tconst { project, zoomIn, zoomOut, zoomTo, fitView: fitViewNow, viewport } = useVueFlow()\n\n\t\treturn { project, zoomIn, zoomOut, zoomTo, fitViewNow, viewport }\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * @return {boolean} Whether the canvas accepts interaction at all.\n\t\t */\n\t\tinteractive() {\n\t\t\treturn this.readOnly === false\n\t\t},\n\t},\n\n\twatch: {\n\t\t// Reported rather than owned: Vue Flow holds the transform, so a host\n\t\t// that mirrors it in its own toolbar must hear every change, not only\n\t\t// the ones its own buttons caused.\n\t\t'viewport.zoom': {\n\t\t\timmediate: true,\n\t\t\thandler(value) {\n\t\t\t\tthis.$emit('zoom-change', value)\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Forward Vue Flow's node changes.\n\t\t *\n\t\t * @param {Array<object>} changes The changes.\n\t\t * @return {void}\n\t\t */\n\t\tonNodesChange(changes) {\n\t\t\t/**\n\t\t\t * @event nodes-change Vue Flow's node change stream — position,\n\t\t\t * selection, dimensions, removal. The canvas does NOT apply these;\n\t\t\t * the host decides what to persist. Drag frames arrive with\n\t\t\t * `dragging: true`, so a host that writes on every change writes\n\t\t\t * once per animation frame.\n\t\t\t */\n\t\t\tthis.$emit('nodes-change', changes)\n\t\t},\n\n\t\t/**\n\t\t * Forward Vue Flow's edge changes.\n\t\t *\n\t\t * @param {Array<object>} changes The changes.\n\t\t * @return {void}\n\t\t */\n\t\tonEdgesChange(changes) {\n\t\t\t/**\n\t\t\t * @event edges-change Vue Flow's edge change stream, same contract\n\t\t\t * as `nodes-change`: reported, never applied here.\n\t\t\t */\n\t\t\tthis.$emit('edges-change', changes)\n\t\t},\n\n\t\t/**\n\t\t * Forward a new connection.\n\t\t *\n\t\t * @param {object} connection The connection.\n\t\t * @return {void}\n\t\t */\n\t\t/**\n\t\t * Pass a node's removal request up to the host.\n\t\t *\n\t\t * @param {string} id The node that asked to be removed.\n\t\t * @return {void}\n\t\t */\n\t\tonNodeRemove(id) {\n\t\t\t/**\n\t\t\t * @event node-remove The focused node should be removed, by id.\n\t\t\t * Raised by Delete/Backspace on a node. The canvas removes\n\t\t\t * nothing itself — the host owns `nodes` and `edges`, and a node\n\t\t\t * dropped here would leave the edges pointing at it behind.\n\t\t\t * @type {string}\n\t\t\t */\n\t\t\tthis.$emit('node-remove', id)\n\t\t},\n\n\t\tonConnect(connection) {\n\t\t\t/**\n\t\t\t * @event connect A new connection was made, by pointer OR by\n\t\t\t * keyboard. Carries Vue Flow's\n\t\t\t * `{ source, target, sourceHandle, targetHandle }`, with the\n\t\t\t * handles reduced to the PORT the host declared — see\n\t\t\t * `portIdOf` below.\n\t\t\t */\n\t\t\tthis.$emit('connect', {\n\t\t\t\t...connection,\n\t\t\t\tsourceHandle: this.portIdOf(connection?.sourceHandle),\n\t\t\t\ttargetHandle: this.portIdOf(connection?.targetHandle),\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Reduce a HANDLE id to the PORT id the host declared.\n\t\t *\n\t\t * A node draws more handles than it has ports — one exit is offered on\n\t\t * both the right and the bottom edge, one entry on both the left and the\n\t\t * top — because a line has to be able to leave and arrive on the side\n\t\t * the graph reads best from. Vue Flow keys handles by id, so those\n\t\t * cannot share one, and the side is encoded into the id instead\n\t\t * (`yes__right`). See CnFlowNode's `exitHandles`.\n\t\t *\n\t\t * ⚠️ WHICH MEANS THE ENCODED ID MUST NEVER LEAVE THE CANVAS. A branch\n\t\t * recorded as `yes__bottom` names a port the engine has never heard of,\n\t\t * and a flow whose branch ids do not match its catalogue is one the\n\t\t * engine cannot route — it would take the default exit and the canvas\n\t\t * would keep showing the branch the author drew. So both routes into\n\t\t * this method (pointer via <VueFlow>, keyboard via CnFlowNode) are\n\t\t * stripped here, in the one place every connection passes through.\n\t\t *\n\t\t * An entry handle carries no port of its own — a node has exactly one\n\t\t * inbound port — so it reduces to null rather than to the literal `in`.\n\t\t *\n\t\t * @param {string|null|undefined} handleId The handle id Vue Flow reports.\n\t\t * @return {string|null} The port id, or null.\n\t\t */\n\t\tportIdOf(handleId) {\n\t\t\tif (typeof handleId !== 'string') {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tconst cut = handleId.lastIndexOf('__')\n\t\t\tconst port = cut === -1 ? handleId : handleId.slice(0, cut)\n\n\t\t\treturn port === 'in' ? null : port\n\t\t},\n\n\t\t/**\n\t\t * Forward Enter or Space on a node: the keyboard double-click.\n\t\t *\n\t\t * @param {string} id The node id.\n\t\t * @return {void}\n\t\t */\n\t\tonNodeActivate(id) {\n\t\t\tif (this.readOnly === true) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * @event node-activate Enter or Space on a node. Payload is the id.\n\t\t\t */\n\t\t\tthis.$emit('node-activate', id)\n\t\t},\n\n\t\t/**\n\t\t * Forward Shift+F10 or the menu key on a node: the keyboard click menu.\n\t\t *\n\t\t * @param {object} request `{ id, clientX, clientY }`, a point at the node.\n\t\t * @return {void}\n\t\t */\n\t\tonNodeMenu(request) {\n\t\t\tif (this.readOnly === true) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * @event node-menu Shift+F10 or the menu key on a node. Payload is\n\t\t\t * `{ id, clientX, clientY }`.\n\t\t\t */\n\t\t\tthis.$emit('node-menu', request)\n\t\t},\n\n\t\t/**\n\t\t * Forward a node click.\n\t\t *\n\t\t * @param {object} event Vue Flow's node event.\n\t\t * @return {void}\n\t\t */\n\t\tonNodeClick(event) {\n\t\t\t/**\n\t\t\t * @event node-select A node was clicked. Payload is Vue Flow's\n\t\t\t * `{ node }`, so a host tracking bare ids reads `event.node.id`.\n\t\t\t */\n\t\t\tthis.$emit('node-select', event)\n\t\t},\n\n\t\t/**\n\t\t * Forward an edge click.\n\t\t *\n\t\t * @param {object} event Vue Flow's edge event.\n\t\t * @return {void}\n\t\t */\n\t\tonEdgeClick(event) {\n\t\t\t/**\n\t\t\t * @event edge-select An edge was clicked.\n\t\t\t */\n\t\t\tthis.$emit('edge-select', event)\n\t\t},\n\n\t\t/**\n\t\t * Forward a label slid along its line.\n\t\t *\n\t\t * @param {{id: string, labelT: number}} payload The edge and its new\n\t\t * fraction, already clamped.\n\t\t * @return {void}\n\t\t */\n\t\tonEdgeLabelMove(payload) {\n\t\t\t/**\n\t\t\t * @event edge-label-move A connection's label was moved, by pointer\n\t\t\t * or by keyboard. Carries `{ id, labelT }`. As everywhere else in\n\t\t\t * this component the change is REPORTED, not applied — the host\n\t\t\t * stores `labelT` and feeds it back down.\n\t\t\t */\n\t\t\tthis.$emit('edge-label-move', payload)\n\t\t},\n\n\t\t/**\n\t\t * Forward an activated label.\n\t\t *\n\t\t * @param {string} id The edge id.\n\t\t * @return {void}\n\t\t */\n\t\tonEdgeLabelClick(id) {\n\t\t\t/**\n\t\t\t * @event edge-label-click A connection's label was activated.\n\t\t\t */\n\t\t\tthis.$emit('edge-label-click', id)\n\t\t},\n\n\t\t/**\n\t\t * Forward a right-click on a label.\n\t\t *\n\t\t * @param {{id: string, event: MouseEvent}} payload The edge and event.\n\t\t * @return {void}\n\t\t */\n\t\tonEdgeLabelContext(payload) {\n\t\t\t/**\n\t\t\t * @event edge-label-context A connection's label was right-clicked.\n\t\t\t * Hosts open the same menu they open for the line itself.\n\t\t\t */\n\t\t\tthis.$emit('edge-label-context', payload)\n\t\t},\n\n\t\t/**\n\t\t * Forward a click on the empty pane.\n\t\t *\n\t\t * @param {object} event The click event.\n\t\t * @return {void}\n\t\t */\n\t\tonPaneClick(event) {\n\t\t\t/**\n\t\t\t * @event canvas-click The empty pane was clicked — hosts use this to\n\t\t\t * clear a selection.\n\t\t\t */\n\t\t\tthis.$emit('canvas-click', event)\n\t\t},\n\n\t\t/**\n\t\t * A palette drop, with the point already converted to canvas space.\n\t\t *\n\t\t * Kept as a Conduction-level event because HTML5 drop plus coordinate\n\t\t * projection is glue every consumer would otherwise write. The\n\t\t * projection is Vue Flow's `project()` rather than the old hand-rolled\n\t\t * `(clientX - rect.left - panOffset.x) / zoom`.\n\t\t *\n\t\t * The canvas does not create the node — the host does, mirroring the\n\t\t * rule that it never mutates positions either.\n\t\t *\n\t\t * @param {DragEvent} event The drop event.\n\t\t * @return {void}\n\t\t */\n\t\tonDrop(event) {\n\t\t\tif (this.readOnly === true) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst bounds = this.$el.getBoundingClientRect()\n\t\t\tconst position = this.project({\n\t\t\t\tx: event.clientX - bounds.left,\n\t\t\t\ty: event.clientY - bounds.top,\n\t\t\t})\n\n\t\t\t/**\n\t\t\t * @event canvas-drop An HTML5 drop landed on the canvas, with\n\t\t\t * `position` already projected into canvas space and the native\n\t\t\t * `event` alongside so the host can read `dataTransfer`. The canvas\n\t\t\t * never creates the node itself.\n\t\t\t */\n\t\t\tthis.$emit('canvas-drop', { position, event })\n\t\t},\n\t},\n}\n</script>\n\n<style>\n/* Vue Flow's own stylesheet, themed through Nextcloud variables. Imported\n un-scoped because it styles elements Vue Flow renders outside this\n component's scope. */\n@import '@vue-flow/core/dist/style.css';\n@import '@vue-flow/core/dist/theme-default.css';\n\n/* ⚠️ EVERY VUE FLOW PLUGIN SHIPS ITS OWN STYLESHEET, AND ONLY CORE'S WAS HERE.\n\n `@vue-flow/node-resizer` was the one that mattered: its stylesheet is what\n gives a resize control `position: absolute` and a 5px handle. Without it the\n handles CnFlowNode renders have no size and no position, so a node that is\n resizable looks exactly like a node that is not — the component was mounted\n and the affordance was invisible.\n\n The minimap's own sheet is small (a background colour and the grab cursors);\n its geometry comes from `.vue-flow__panel` in core, which was already here.\n Imported for the cursors, and before our own rules so the theme below still\n wins. */\n@import '@vue-flow/node-resizer/dist/style.css';\n@import '@vue-flow/minimap/dist/style.css';\n\n.cn-graph-canvas {\n\tposition: relative;\n\twidth: 100%;\n\theight: 100%;\n\tmin-height: 320px;\n}\n\n/* ⚠️ VUE FLOW'S DEFAULT NODE DRAWS A BOX WE NEVER ASKED FOR.\n\n Every node this canvas renders goes through the `#node-default` slot, so\n Vue Flow wraps our CnFlowNode in `.vue-flow__node-default` — and\n `theme-default.css` gives that wrapper a 1px border, a white background and\n 10px of padding. The result was a box around our box: the wrapper's border,\n then `.cn-flow-node`'s own. Nothing failed and nothing warned; a node just\n quietly had one more outline than it was designed to have.\n\n Stripped, not restyled: `.cn-flow-node` is the node's chrome, and it is the\n ONLY border a node draws. `width` stays 150px because Vue Flow measures the\n wrapper to route edges and place handles — dropping it would move every\n node's footprint, which is a layout change, not a fix for a stray border. */\n.cn-graph-canvas .vue-flow__node-default {\n\tpadding: 0;\n\tborder: 0;\n\tborder-radius: 0;\n\tbackground: none;\n\n\t/* The handle colour reaches `.cn-flow-node__handle` through this variable.\n\t Vue Flow's own `.vue-flow__node-default .vue-flow__handle` rule outranks\n\t our single-class one, so a themed handle lost to a hard-coded #1a192b on\n\t every node — setting the variable themes it at the source instead of\n\t fighting the specificity. */\n\t--vf-handle: var(--color-primary-element, #0082c9);\n}\n\n/* The selected/focus/hover states re-assert the border and shadow the rule\n above just removed, so they have to be stripped too — otherwise clicking a\n node brought the extra box back. Selection is shown by `.cn-flow-node--selected`,\n and focus by `.cn-flow-node:focus-visible`, both on the node itself. */\n.cn-graph-canvas .vue-flow__node-default.selected,\n.cn-graph-canvas .vue-flow__node-default.selected:hover,\n.cn-graph-canvas .vue-flow__node-default.selectable:hover,\n.cn-graph-canvas .vue-flow__node-default:focus,\n.cn-graph-canvas .vue-flow__node-default:focus-visible {\n\tborder: 0;\n\tbox-shadow: none;\n}\n\n/* THEME, NOT OVERRIDE. Every colour comes from a Nextcloud variable with a\n fallback, so the canvas follows the user's theme — including dark — instead\n of being the one component that ignores it. */\n.cn-graph-canvas .vue-flow__background {\n\tbackground: var(--color-main-background, #fff);\n}\n\n.cn-graph-canvas .vue-flow__edge-path {\n\tstroke: var(--color-border-dark, #888);\n}\n\n.cn-graph-canvas .vue-flow__edge.selected .vue-flow__edge-path {\n\tstroke: var(--color-primary-element, #0082c9);\n}\n\n/* An edge says which way the data flows, and it can only say it with an\n arrowhead — the direction is the one thing a line cannot express on its own.\n\n ⚠️ `!important` IS LOAD-BEARING HERE, not laziness. Vue Flow writes the\n arrowhead's colour as an INLINE style on the polyline it generates, and an\n inline style beats any class selector. The alternative is passing a colour\n through the marker definition in JS, which would hard-code a hex and take\n the arrowhead out of the theme — it would stay dark grey in dark mode while\n the line it terminates went light. Themed here, it follows the user.\n\n `markerUnits` defaults to `strokeWidth`, so the size set alongside this in\n `useFlowStore.canvasEdges` is what keeps the arrow clear of the target\n node's port handle. */\n.cn-graph-canvas .vue-flow__arrowhead polyline,\n.cn-graph-canvas .vue-flow__arrowhead path {\n\tfill: var(--color-text-maxcontrast, #6b6b6b) !important;\n\tstroke: var(--color-text-maxcontrast, #6b6b6b) !important;\n}\n\n.cn-graph-canvas__controls {\n\tposition: absolute;\n\tbottom: 12px;\n\tleft: 12px;\n\tdisplay: flex;\n\tflex-direction: column;\n\tz-index: 5;\n}\n\n.cn-graph-canvas__control {\n\twidth: 28px;\n\theight: 28px;\n\tborder: 1px solid var(--color-border, #d0d0d0);\n\tbackground: var(--color-main-background, #fff);\n\tcolor: var(--color-main-text, #222);\n\tcursor: pointer;\n}\n\n.cn-graph-canvas__control:focus-visible {\n\toutline: 2px solid var(--color-primary-element, #0082c9);\n\toutline-offset: 1px;\n}\n\n.cn-graph-canvas .vue-flow__minimap {\n\tbackground: var(--color-background-dark, #ededed);\n}\n</style>\n"],"names":["VueFlow","Background","MiniMap","CnFlowNode","CnFlowEdge"],"mappings":"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"}
1
+ {"version":3,"file":"CnGraphCanvas.vue2.js","sources":["../../../../src/components/CnGraphCanvas/CnGraphCanvas.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>\n - SPDX-License-Identifier: EUPL-1.2\n -->\n<template>\n\t<div class=\"cn-graph-canvas\" @drop=\"onDrop\" @dragover.prevent>\n\t\t<VueFlow\n\t\t\t:nodes=\"nodes\"\n\t\t\t:edges=\"edges\"\n\t\t\t:minZoom=\"minZoom\"\n\t\t\t:maxZoom=\"maxZoom\"\n\t\t\t:fitViewOnInit=\"fitView && !fitMaxZoom\"\n\t\t\t:snapToGrid=\"snapToGrid\"\n\t\t\t:snapGrid=\"snapGrid\"\n\t\t\t:nodesDraggable=\"interactive\"\n\t\t\t:nodesConnectable=\"interactive\"\n\t\t\t:elementsSelectable=\"interactive\"\n\t\t\t:connectionMode=\"connectionMode\"\n\t\t\t@nodesChange=\"onNodesChange\"\n\t\t\t@edgesChange=\"onEdgesChange\"\n\t\t\t@connect=\"onConnect\"\n\t\t\t@nodeClick=\"onNodeClick\"\n\t\t\t@edgeClick=\"onEdgeClick\"\n\t\t\t@paneClick=\"onPaneClick\"\n\t\t\t@nodesInitialized=\"onNodesInitialized\">\n\t\t\t<!-- Per-type node components, Vue Flow's convention. A host that\n\t\t\t registers `type: 'trigger'` gets `#node-trigger`; anything\n\t\t\t unregistered falls back to CnFlowNode, which carries the\n\t\t\t keyboard contract. -->\n\t\t\t<template #node-default=\"nodeProps\">\n\t\t\t\t<!-- `@connect` routes a KEYBOARD-completed connection into the\n\t\t\t\t same handler as a pointer one, so `onConnect` is the single\n\t\t\t\t place a new edge leaves this component regardless of input.\n\t\t\t\t Without it the keyboard path wrote into Vue Flow's internal\n\t\t\t\t graph and the host never heard about it — the edge drew and\n\t\t\t\t the save dropped it. -->\n\t\t\t\t<CnFlowNode\n\t\t\t\t\tv-bind=\"nodeProps\"\n\t\t\t\t\t:deletable=\"!readOnly\"\n\t\t\t\t\t@connect=\"onConnect\"\n\t\t\t\t\t@remove=\"onNodeRemove\"\n\t\t\t\t\t@activate=\"onNodeActivate\"\n\t\t\t\t\t@menu=\"onNodeMenu\">\n\t\t\t\t\t<template #default=\"slotProps\">\n\t\t\t\t\t\t<!-- @slot node The body of a step, rendered inside the\n\t\t\t\t\t\t focusable node wrapper. Receives `{ node }` with the\n\t\t\t\t\t\t node's `id`, `data` and `selected`. The wrapper — and\n\t\t\t\t\t\t with it the keyboard contract and ARIA state — stays\n\t\t\t\t\t\t the canvas's, so a host cannot accidentally replace a\n\t\t\t\t\t\t focusable node with an inert div. -->\n\t\t\t\t\t\t<slot name=\"node\" v-bind=\"slotProps\" />\n\t\t\t\t\t</template>\n\t\t\t\t</CnFlowNode>\n\t\t\t</template>\n\n\t\t\t<!-- Per-type edge components, the same convention as nodes above.\n\t\t\t Registered on `default` because that is the type an edge gets\n\t\t\t when nothing else is asked for — which is every edge, now that\n\t\t\t the ROUTER moved off `type` and into `data.lineType`.\n\n\t\t\t That move is the edge half of the lesson the node slot records:\n\t\t\t `type` selects a COMPONENT, and a router is not a component. So\n\t\t\t long as `type` carried `smoothstep`, Vue Flow answered with its\n\t\t\t own built-in edge and no consumer could add anything to a line\n\t\t\t at all. -->\n\t\t\t<template #edge-default=\"edgeProps\">\n\t\t\t\t<CnFlowEdge\n\t\t\t\t\tv-bind=\"edgeProps\"\n\t\t\t\t\t:lineType=\"edgeProps.data && edgeProps.data.lineType\"\n\t\t\t\t\t:animated=\"!(edgeProps.data && edgeProps.data.animated === false)\"\n\t\t\t\t\t:label-aria-label=\"edgeProps.data && edgeProps.data.labelAriaLabel\"\n\t\t\t\t\t@labelMove=\"onEdgeLabelMove\"\n\t\t\t\t\t@labelClick=\"onEdgeLabelClick\"\n\t\t\t\t\t@labelContext=\"onEdgeLabelContext\">\n\t\t\t\t\t<template #label=\"slotProps\">\n\t\t\t\t\t\t<!-- @slot edge-label The chrome of a connection's\n\t\t\t\t\t\t label. Receives `{ edge }` with the edge's `id`,\n\t\t\t\t\t\t `data` and `selected`. Rendered inside a focusable\n\t\t\t\t\t\t control the canvas owns, so render inert content:\n\t\t\t\t\t\t the arrow keys, the focus ring and the ARIA name\n\t\t\t\t\t\t are already handled. An edge whose host renders\n\t\t\t\t\t\t nothing here draws no label control at all. -->\n\t\t\t\t\t\t<slot name=\"edge-label\" v-bind=\"slotProps\" />\n\t\t\t\t\t</template>\n\n\t\t\t\t\t<template #adornment=\"slotProps\">\n\t\t\t\t\t\t<!-- @slot edge-adornment A host's own controls beside a\n\t\t\t\t\t\t connection's label. Receives `{ edge }`. Unlike\n\t\t\t\t\t\t `edge-label` this is NOT inside the label's button,\n\t\t\t\t\t\t so what goes here may be interactive — a replay's\n\t\t\t\t\t\t payload control is the case it exists for. -->\n\t\t\t\t\t\t<slot name=\"edge-adornment\" v-bind=\"slotProps\" />\n\t\t\t\t\t</template>\n\t\t\t\t</CnFlowEdge>\n\t\t\t</template>\n\n\t\t\t<Background v-if=\"showBackground\" :gap=\"snapGrid[0]\" />\n\t\t\t<MiniMap v-if=\"showMiniMap\" />\n\t\t</VueFlow>\n\n\t\t<!-- OUR OWN CONTROLS, not `@vue-flow/controls`.\n\t\t The library's control bar renders bare <button> elements with an\n\t\t icon and no accessible name, which axe reports as `button-name` at\n\t\t SERIOUS impact — caught by the e2e in this change, not by the unit\n\t\t lane or the gates. Rather than ship a control bar a screen-reader\n\t\t user cannot identify, the buttons are ours and carry real labels. -->\n\t\t<div v-if=\"showControls\" class=\"cn-graph-canvas__controls\">\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclass=\"cn-graph-canvas__control\"\n\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Zoom in')\"\n\t\t\t\t@click=\"zoomIn()\">\n\t\t\t\t+\n\t\t\t</button>\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclass=\"cn-graph-canvas__control\"\n\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Zoom out')\"\n\t\t\t\t@click=\"zoomOut()\">\n\t\t\t\t−\n\t\t\t</button>\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclass=\"cn-graph-canvas__control\"\n\t\t\t\t:aria-label=\"t('nextcloud-vue', 'Fit the whole flow in view')\"\n\t\t\t\t@click=\"fitViewNow()\">\n\t\t\t\t⤢\n\t\t\t</button>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { Background } from '@vue-flow/background'\nimport { useVueFlow, VueFlow } from '@vue-flow/core'\nimport { MiniMap } from '@vue-flow/minimap'\nimport CnFlowEdge from './CnFlowEdge.vue'\nimport CnFlowNode from './CnFlowNode.vue'\n\n/**\n * A graph canvas built on Vue Flow — the library n8n runs.\n *\n * WHY THIS IS NOT A WRAPPER AROUND THE OLD API\n * --------------------------------------------\n * This component used to be ~1,300 hand-written lines re-implementing pan,\n * zoom, drag, connect, resize and hit-testing on a bare SVG, because the fleet\n * was on Vue 2.7 and every maintained canvas library is Vue-3-only (ADR-065\n * §5). ADR-081 removed that constraint and named this library as the reason for\n * the migration.\n *\n * The props therefore MOVED rather than being translated. The graph page was\n * not in production, so there was no consumer to protect, and a shim can only\n * ever expose what the old API could already express — `fitView`, `<MiniMap>`,\n * `connectionMode` and the node resizer would all have stayed unreachable\n * behind props designed for a hand-drawn SVG.\n *\n * `nodeWidth` / `nodeHeight` are gone entirely. They existed only so hand-drawn\n * edges could guess where a node's centre was, and the old docblock admitted\n * the failure mode: \"set them to match what your node slot actually renders, or\n * edges will attach off-centre\". Vue Flow measures the rendered node, so the\n * whole class of bug went with the props.\n *\n * ⚠️ THE CANVAS STILL NEVER MUTATES THE GRAPH. Vue Flow is perfectly willing to\n * own node positions internally; here it does not. Changes are emitted and the\n * host decides what to persist — the same rule the previous implementation\n * held, and the reason a canvas can be dropped into a read-only surface without\n * auditing it first.\n *\n * ⚠️ KEYBOARD OPERATION LIVES IN {@see CnFlowNode}. Vue Flow is pointer-first;\n * the focus/ARIA/key contract is re-implemented in the node component, not\n * inherited from the library. See that file for why the multi-exit case is the\n * one that matters.\n */\nexport default {\n\tname: 'CnGraphCanvas',\n\n\tcomponents: { VueFlow, Background, MiniMap, CnFlowNode, CnFlowEdge },\n\n\tprops: {\n\t\t/** Nodes in Vue Flow's shape: `{ id, type, position: {x,y}, data }`. */\n\t\tnodes: {\n\t\t\ttype: Array,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** Edges in Vue Flow's shape: `{ id, source, target }`. */\n\t\tedges: {\n\t\t\ttype: Array,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/**\n\t\t * When true, every interaction is refused — drag, connect AND select.\n\t\t *\n\t\t * Expressed as three Vue Flow flags below. Missing one produces a canvas\n\t\t * that LOOKS locked and is not, so the three are set together and tested\n\t\t * together.\n\t\t */\n\t\treadOnly: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Frame the whole graph on first render. */\n\t\tfitView: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * The most that first frame may zoom in. 0 lets it go to `maxZoom`;\n\t\t * 1 keeps a graph that fits at its own size.\n\t\t */\n\t\tfitMaxZoom: {\n\t\t\ttype: Number,\n\t\t\tdefault: 0,\n\t\t},\n\n\t\t/** Snap dragged nodes to the grid. */\n\t\tsnapToGrid: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Grid spacing, as Vue Flow's `[x, y]`. */\n\t\tsnapGrid: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [16, 16],\n\t\t},\n\n\t\t/** Minimum zoom. */\n\t\tminZoom: {\n\t\t\ttype: Number,\n\t\t\tdefault: 0.2,\n\t\t},\n\n\t\t/** Maximum zoom. */\n\t\tmaxZoom: {\n\t\t\ttype: Number,\n\t\t\tdefault: 2,\n\t\t},\n\n\t\t/** Vue Flow's connection mode — `loose` or `strict`. */\n\t\tconnectionMode: {\n\t\t\ttype: String,\n\t\t\tdefault: 'loose',\n\t\t},\n\n\t\t/** Draw the dotted background. */\n\t\tshowBackground: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Draw the zoom/fit controls. */\n\t\tshowControls: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Draw the minimap. */\n\t\tshowMiniMap: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\t},\n\n\temits: [\n\t\t'nodes-change',\n\t\t'edges-change',\n\t\t'connect',\n\t\t'node-select',\n\t\t'edge-select',\n\t\t'edge-label-move',\n\t\t'edge-label-click',\n\t\t'edge-label-context',\n\t\t'canvas-click',\n\t\t'canvas-drop',\n\n\t\t'node-remove',\n\t\t'node-activate',\n\t\t'node-menu',\n\n\t\t/**\n\t\t * The canvas zoom changed, by any route — a control here, the host's own\n\t\t * toolbar, the wheel, or a fit-view.\n\t\t *\n\t\t * @type {number}\n\t\t */\n\t\t'zoom-change',\n\t],\n\n\tsetup() {\n\t\t// ⚠️ ALIASED. `fitView` is already a BOOLEAN PROP on this component\n\t\t// (frame the graph on first render), and Vue Flow's composable exports a\n\t\t// FUNCTION of the same name. Returning both puts two `fitView` bindings\n\t\t// on the instance — eslint's `vue/no-dupe-keys` catches it, but the\n\t\t// symptom would have been a fit button that tries to call `true`.\n\t\t// `zoomTo` and `viewport` so a HOST toolbar can drive the zoom and show\n\t\t// the real figure — including one the wheel or a fit-view changed.\n\t\tconst { project, zoomIn, zoomOut, zoomTo, fitView: fitViewNow, viewport } = useVueFlow()\n\n\t\treturn { project, zoomIn, zoomOut, zoomTo, fitViewNow, viewport }\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\t// Whether the capped first frame (`fitMaxZoom`) has been taken.\n\t\t\tframedOnce: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * @return {boolean} Whether the canvas accepts interaction at all.\n\t\t */\n\t\tinteractive() {\n\t\t\treturn this.readOnly === false\n\t\t},\n\t},\n\n\twatch: {\n\t\t// Reported rather than owned: Vue Flow holds the transform, so a host\n\t\t// that mirrors it in its own toolbar must hear every change, not only\n\t\t// the ones its own buttons caused.\n\t\t'viewport.zoom': {\n\t\t\timmediate: true,\n\t\t\thandler(value) {\n\t\t\t\tthis.$emit('zoom-change', value)\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Frame the graph once its nodes have a size, no closer than\n\t\t * `fitMaxZoom`. Only the first time: a step added later must not move\n\t\t * the view the author is working in.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonNodesInitialized() {\n\t\t\tif (!this.fitView || !this.fitMaxZoom || this.framedOnce) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.framedOnce = true\n\t\t\tthis.fitViewNow({ maxZoom: this.fitMaxZoom, padding: 0.1 })\n\t\t},\n\n\t\t/**\n\t\t * Forward Vue Flow's node changes.\n\t\t *\n\t\t * @param {Array<object>} changes The changes.\n\t\t * @return {void}\n\t\t */\n\t\tonNodesChange(changes) {\n\t\t\t/**\n\t\t\t * @event nodes-change Vue Flow's node change stream — position,\n\t\t\t * selection, dimensions, removal. The canvas does NOT apply these;\n\t\t\t * the host decides what to persist. Drag frames arrive with\n\t\t\t * `dragging: true`, so a host that writes on every change writes\n\t\t\t * once per animation frame.\n\t\t\t */\n\t\t\tthis.$emit('nodes-change', changes)\n\t\t},\n\n\t\t/**\n\t\t * Forward Vue Flow's edge changes.\n\t\t *\n\t\t * @param {Array<object>} changes The changes.\n\t\t * @return {void}\n\t\t */\n\t\tonEdgesChange(changes) {\n\t\t\t/**\n\t\t\t * @event edges-change Vue Flow's edge change stream, same contract\n\t\t\t * as `nodes-change`: reported, never applied here.\n\t\t\t */\n\t\t\tthis.$emit('edges-change', changes)\n\t\t},\n\n\t\t/**\n\t\t * Forward a new connection.\n\t\t *\n\t\t * @param {object} connection The connection.\n\t\t * @return {void}\n\t\t */\n\t\t/**\n\t\t * Pass a node's removal request up to the host.\n\t\t *\n\t\t * @param {string} id The node that asked to be removed.\n\t\t * @return {void}\n\t\t */\n\t\tonNodeRemove(id) {\n\t\t\t/**\n\t\t\t * @event node-remove The focused node should be removed, by id.\n\t\t\t * Raised by Delete/Backspace on a node. The canvas removes\n\t\t\t * nothing itself — the host owns `nodes` and `edges`, and a node\n\t\t\t * dropped here would leave the edges pointing at it behind.\n\t\t\t * @type {string}\n\t\t\t */\n\t\t\tthis.$emit('node-remove', id)\n\t\t},\n\n\t\tonConnect(connection) {\n\t\t\t/**\n\t\t\t * @event connect A new connection was made, by pointer OR by\n\t\t\t * keyboard. Carries Vue Flow's\n\t\t\t * `{ source, target, sourceHandle, targetHandle }`, with the\n\t\t\t * handles reduced to the PORT the host declared — see\n\t\t\t * `portIdOf` below.\n\t\t\t */\n\t\t\tthis.$emit('connect', {\n\t\t\t\t...connection,\n\t\t\t\tsourceHandle: this.portIdOf(connection?.sourceHandle),\n\t\t\t\ttargetHandle: this.portIdOf(connection?.targetHandle),\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Reduce a HANDLE id to the PORT id the host declared.\n\t\t *\n\t\t * A node draws more handles than it has ports — one exit is offered on\n\t\t * both the right and the bottom edge, one entry on both the left and the\n\t\t * top — because a line has to be able to leave and arrive on the side\n\t\t * the graph reads best from. Vue Flow keys handles by id, so those\n\t\t * cannot share one, and the side is encoded into the id instead\n\t\t * (`yes__right`). See CnFlowNode's `exitHandles`.\n\t\t *\n\t\t * ⚠️ WHICH MEANS THE ENCODED ID MUST NEVER LEAVE THE CANVAS. A branch\n\t\t * recorded as `yes__bottom` names a port the engine has never heard of,\n\t\t * and a flow whose branch ids do not match its catalogue is one the\n\t\t * engine cannot route — it would take the default exit and the canvas\n\t\t * would keep showing the branch the author drew. So both routes into\n\t\t * this method (pointer via <VueFlow>, keyboard via CnFlowNode) are\n\t\t * stripped here, in the one place every connection passes through.\n\t\t *\n\t\t * An entry handle carries no port of its own — a node has exactly one\n\t\t * inbound port — so it reduces to null rather than to the literal `in`.\n\t\t *\n\t\t * @param {string|null|undefined} handleId The handle id Vue Flow reports.\n\t\t * @return {string|null} The port id, or null.\n\t\t */\n\t\tportIdOf(handleId) {\n\t\t\tif (typeof handleId !== 'string') {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tconst cut = handleId.lastIndexOf('__')\n\t\t\tconst port = cut === -1 ? handleId : handleId.slice(0, cut)\n\n\t\t\treturn port === 'in' ? null : port\n\t\t},\n\n\t\t/**\n\t\t * Forward Enter or Space on a node: the keyboard double-click.\n\t\t *\n\t\t * @param {string} id The node id.\n\t\t * @return {void}\n\t\t */\n\t\tonNodeActivate(id) {\n\t\t\tif (this.readOnly === true) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * @event node-activate Enter or Space on a node. Payload is the id.\n\t\t\t */\n\t\t\tthis.$emit('node-activate', id)\n\t\t},\n\n\t\t/**\n\t\t * Forward Shift+F10 or the menu key on a node: the keyboard click menu.\n\t\t *\n\t\t * @param {object} request `{ id, clientX, clientY }`, a point at the node.\n\t\t * @return {void}\n\t\t */\n\t\tonNodeMenu(request) {\n\t\t\tif (this.readOnly === true) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * @event node-menu Shift+F10 or the menu key on a node. Payload is\n\t\t\t * `{ id, clientX, clientY }`.\n\t\t\t */\n\t\t\tthis.$emit('node-menu', request)\n\t\t},\n\n\t\t/**\n\t\t * Forward a node click.\n\t\t *\n\t\t * @param {object} event Vue Flow's node event.\n\t\t * @return {void}\n\t\t */\n\t\tonNodeClick(event) {\n\t\t\t/**\n\t\t\t * @event node-select A node was clicked. Payload is Vue Flow's\n\t\t\t * `{ node }`, so a host tracking bare ids reads `event.node.id`.\n\t\t\t */\n\t\t\tthis.$emit('node-select', event)\n\t\t},\n\n\t\t/**\n\t\t * Forward an edge click.\n\t\t *\n\t\t * @param {object} event Vue Flow's edge event.\n\t\t * @return {void}\n\t\t */\n\t\tonEdgeClick(event) {\n\t\t\t/**\n\t\t\t * @event edge-select An edge was clicked.\n\t\t\t */\n\t\t\tthis.$emit('edge-select', event)\n\t\t},\n\n\t\t/**\n\t\t * Forward a label slid along its line.\n\t\t *\n\t\t * @param {{id: string, labelT: number}} payload The edge and its new\n\t\t * fraction, already clamped.\n\t\t * @return {void}\n\t\t */\n\t\tonEdgeLabelMove(payload) {\n\t\t\t/**\n\t\t\t * @event edge-label-move A connection's label was moved, by pointer\n\t\t\t * or by keyboard. Carries `{ id, labelT }`. As everywhere else in\n\t\t\t * this component the change is REPORTED, not applied — the host\n\t\t\t * stores `labelT` and feeds it back down.\n\t\t\t */\n\t\t\tthis.$emit('edge-label-move', payload)\n\t\t},\n\n\t\t/**\n\t\t * Forward an activated label.\n\t\t *\n\t\t * @param {string} id The edge id.\n\t\t * @return {void}\n\t\t */\n\t\tonEdgeLabelClick(id) {\n\t\t\t/**\n\t\t\t * @event edge-label-click A connection's label was activated.\n\t\t\t */\n\t\t\tthis.$emit('edge-label-click', id)\n\t\t},\n\n\t\t/**\n\t\t * Forward a right-click on a label.\n\t\t *\n\t\t * @param {{id: string, event: MouseEvent}} payload The edge and event.\n\t\t * @return {void}\n\t\t */\n\t\tonEdgeLabelContext(payload) {\n\t\t\t/**\n\t\t\t * @event edge-label-context A connection's label was right-clicked.\n\t\t\t * Hosts open the same menu they open for the line itself.\n\t\t\t */\n\t\t\tthis.$emit('edge-label-context', payload)\n\t\t},\n\n\t\t/**\n\t\t * Forward a click on the empty pane.\n\t\t *\n\t\t * @param {object} event The click event.\n\t\t * @return {void}\n\t\t */\n\t\tonPaneClick(event) {\n\t\t\t/**\n\t\t\t * @event canvas-click The empty pane was clicked — hosts use this to\n\t\t\t * clear a selection.\n\t\t\t */\n\t\t\tthis.$emit('canvas-click', event)\n\t\t},\n\n\t\t/**\n\t\t * A palette drop, with the point already converted to canvas space.\n\t\t *\n\t\t * Kept as a Conduction-level event because HTML5 drop plus coordinate\n\t\t * projection is glue every consumer would otherwise write. The\n\t\t * projection is Vue Flow's `project()` rather than the old hand-rolled\n\t\t * `(clientX - rect.left - panOffset.x) / zoom`.\n\t\t *\n\t\t * The canvas does not create the node — the host does, mirroring the\n\t\t * rule that it never mutates positions either.\n\t\t *\n\t\t * @param {DragEvent} event The drop event.\n\t\t * @return {void}\n\t\t */\n\t\tonDrop(event) {\n\t\t\tif (this.readOnly === true) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst bounds = this.$el.getBoundingClientRect()\n\t\t\tconst position = this.project({\n\t\t\t\tx: event.clientX - bounds.left,\n\t\t\t\ty: event.clientY - bounds.top,\n\t\t\t})\n\n\t\t\t/**\n\t\t\t * @event canvas-drop An HTML5 drop landed on the canvas, with\n\t\t\t * `position` already projected into canvas space and the native\n\t\t\t * `event` alongside so the host can read `dataTransfer`. The canvas\n\t\t\t * never creates the node itself.\n\t\t\t */\n\t\t\tthis.$emit('canvas-drop', { position, event })\n\t\t},\n\t},\n}\n</script>\n\n<style>\n/* Vue Flow's own stylesheet, themed through Nextcloud variables. Imported\n un-scoped because it styles elements Vue Flow renders outside this\n component's scope. */\n@import '@vue-flow/core/dist/style.css';\n@import '@vue-flow/core/dist/theme-default.css';\n\n/* ⚠️ EVERY VUE FLOW PLUGIN SHIPS ITS OWN STYLESHEET, AND ONLY CORE'S WAS HERE.\n\n `@vue-flow/node-resizer` was the one that mattered: its stylesheet is what\n gives a resize control `position: absolute` and a 5px handle. Without it the\n handles CnFlowNode renders have no size and no position, so a node that is\n resizable looks exactly like a node that is not — the component was mounted\n and the affordance was invisible.\n\n The minimap's own sheet is small (a background colour and the grab cursors);\n its geometry comes from `.vue-flow__panel` in core, which was already here.\n Imported for the cursors, and before our own rules so the theme below still\n wins. */\n@import '@vue-flow/node-resizer/dist/style.css';\n@import '@vue-flow/minimap/dist/style.css';\n\n.cn-graph-canvas {\n\tposition: relative;\n\twidth: 100%;\n\theight: 100%;\n\tmin-height: 320px;\n}\n\n/* ⚠️ VUE FLOW'S DEFAULT NODE DRAWS A BOX WE NEVER ASKED FOR.\n\n Every node this canvas renders goes through the `#node-default` slot, so\n Vue Flow wraps our CnFlowNode in `.vue-flow__node-default` — and\n `theme-default.css` gives that wrapper a 1px border, a white background and\n 10px of padding. The result was a box around our box: the wrapper's border,\n then `.cn-flow-node`'s own. Nothing failed and nothing warned; a node just\n quietly had one more outline than it was designed to have.\n\n Stripped, not restyled: `.cn-flow-node` is the node's chrome, and it is the\n ONLY border a node draws. `width` stays 150px because Vue Flow measures the\n wrapper to route edges and place handles — dropping it would move every\n node's footprint, which is a layout change, not a fix for a stray border. */\n.cn-graph-canvas .vue-flow__node-default {\n\tpadding: 0;\n\tborder: 0;\n\tborder-radius: 0;\n\tbackground: none;\n\n\t/* The handle colour reaches `.cn-flow-node__handle` through this variable.\n\t Vue Flow's own `.vue-flow__node-default .vue-flow__handle` rule outranks\n\t our single-class one, so a themed handle lost to a hard-coded #1a192b on\n\t every node — setting the variable themes it at the source instead of\n\t fighting the specificity. */\n\t--vf-handle: var(--color-primary-element, #0082c9);\n}\n\n/* The selected/focus/hover states re-assert the border and shadow the rule\n above just removed, so they have to be stripped too — otherwise clicking a\n node brought the extra box back. Selection is shown by `.cn-flow-node--selected`,\n and focus by `.cn-flow-node:focus-visible`, both on the node itself. */\n.cn-graph-canvas .vue-flow__node-default.selected,\n.cn-graph-canvas .vue-flow__node-default.selected:hover,\n.cn-graph-canvas .vue-flow__node-default.selectable:hover,\n.cn-graph-canvas .vue-flow__node-default:focus,\n.cn-graph-canvas .vue-flow__node-default:focus-visible {\n\tborder: 0;\n\tbox-shadow: none;\n}\n\n/* THEME, NOT OVERRIDE. Every colour comes from a Nextcloud variable with a\n fallback, so the canvas follows the user's theme — including dark — instead\n of being the one component that ignores it. */\n.cn-graph-canvas .vue-flow__background {\n\tbackground: var(--color-main-background, #fff);\n}\n\n.cn-graph-canvas .vue-flow__edge-path {\n\tstroke: var(--color-border-dark, #888);\n}\n\n.cn-graph-canvas .vue-flow__edge.selected .vue-flow__edge-path {\n\tstroke: var(--color-primary-element, #0082c9);\n}\n\n/* An edge says which way the data flows, and it can only say it with an\n arrowhead — the direction is the one thing a line cannot express on its own.\n\n ⚠️ `!important` IS LOAD-BEARING HERE, not laziness. Vue Flow writes the\n arrowhead's colour as an INLINE style on the polyline it generates, and an\n inline style beats any class selector. The alternative is passing a colour\n through the marker definition in JS, which would hard-code a hex and take\n the arrowhead out of the theme — it would stay dark grey in dark mode while\n the line it terminates went light. Themed here, it follows the user.\n\n `markerUnits` defaults to `strokeWidth`, so the size set alongside this in\n `useFlowStore.canvasEdges` is what keeps the arrow clear of the target\n node's port handle. */\n.cn-graph-canvas .vue-flow__arrowhead polyline,\n.cn-graph-canvas .vue-flow__arrowhead path {\n\tfill: var(--color-text-maxcontrast, #6b6b6b) !important;\n\tstroke: var(--color-text-maxcontrast, #6b6b6b) !important;\n}\n\n.cn-graph-canvas__controls {\n\tposition: absolute;\n\tbottom: 12px;\n\tleft: 12px;\n\tdisplay: flex;\n\tflex-direction: column;\n\tz-index: 5;\n}\n\n.cn-graph-canvas__control {\n\twidth: 28px;\n\theight: 28px;\n\tborder: 1px solid var(--color-border, #d0d0d0);\n\tbackground: var(--color-main-background, #fff);\n\tcolor: var(--color-main-text, #222);\n\tcursor: pointer;\n}\n\n.cn-graph-canvas__control:focus-visible {\n\toutline: 2px solid var(--color-primary-element, #0082c9);\n\toutline-offset: 1px;\n}\n\n.cn-graph-canvas .vue-flow__minimap {\n\tbackground: var(--color-background-dark, #ededed);\n}\n</style>\n"],"names":["VueFlow","Background","MiniMap","CnFlowNode","CnFlowEdge"],"mappings":"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"}
@@ -25,7 +25,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
25
25
  edges: $props.edges,
26
26
  minZoom: $props.minZoom,
27
27
  maxZoom: $props.maxZoom,
28
- fitViewOnInit: $props.fitView,
28
+ fitViewOnInit: $props.fitView && !$props.fitMaxZoom,
29
29
  snapToGrid: $props.snapToGrid,
30
30
  snapGrid: $props.snapGrid,
31
31
  nodesDraggable: $options.interactive,
@@ -37,7 +37,8 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
37
37
  onConnect: $options.onConnect,
38
38
  onNodeClick: $options.onNodeClick,
39
39
  onEdgeClick: $options.onEdgeClick,
40
- onPaneClick: $options.onPaneClick
40
+ onPaneClick: $options.onPaneClick,
41
+ onNodesInitialized: $options.onNodesInitialized
41
42
  }, {
42
43
  "node-default": withCtx((nodeProps) => [
43
44
  createVNode(_component_CnFlowNode, mergeProps(nodeProps, {
@@ -83,7 +84,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
83
84
  : createCommentVNode("v-if", true)
84
85
  ]),
85
86
  _: 3 /* FORWARDED */
86
- }, 8 /* PROPS */, ["nodes", "edges", "minZoom", "maxZoom", "fitViewOnInit", "snapToGrid", "snapGrid", "nodesDraggable", "nodesConnectable", "elementsSelectable", "connectionMode", "onNodesChange", "onEdgesChange", "onConnect", "onNodeClick", "onEdgeClick", "onPaneClick"]),
87
+ }, 8 /* PROPS */, ["nodes", "edges", "minZoom", "maxZoom", "fitViewOnInit", "snapToGrid", "snapGrid", "nodesDraggable", "nodesConnectable", "elementsSelectable", "connectionMode", "onNodesChange", "onEdgesChange", "onConnect", "onNodeClick", "onEdgeClick", "onPaneClick", "onNodesInitialized"]),
87
88
  ($props.showControls)
88
89
  ? (openBlock(), createElementBlock("div", _hoisted_1, [
89
90
  createElementVNode("button", {