@conduction/nextcloud-vue 2.71.0 → 2.73.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/components/CnFlowDetail/CnFlowDetail.vue2.js +25 -0
- package/dist/esm/components/CnFlowDetail/CnFlowDetail.vue2.js.map +1 -1
- package/dist/esm/components/CnFlowDetail/CnFlowDetail.vue3.js +3 -1
- package/dist/esm/components/CnFlowDetail/CnFlowDetail.vue3.js.map +1 -1
- package/dist/esm/components/CnFlowDetail/CnRunDetailSidebar.vue2.js +2 -0
- package/dist/esm/components/CnFlowDetail/CnRunDetailSidebar.vue2.js.map +1 -1
- package/dist/esm/components/CnFlowDetail/CnRunDetailSidebar.vue3.js +7 -0
- package/dist/esm/components/CnFlowDetail/CnRunDetailSidebar.vue3.js.map +1 -1
- package/dist/esm/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue.js +10 -0
- package/dist/esm/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue.js.map +1 -0
- package/dist/esm/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue2.js +150 -0
- package/dist/esm/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue2.js.map +1 -0
- package/dist/esm/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue3.js +54 -0
- package/dist/esm/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue3.js.map +1 -0
- package/dist/esm/components/CnGraphCanvas/CnFlowNode.vue2.js +36 -1
- package/dist/esm/components/CnGraphCanvas/CnFlowNode.vue2.js.map +1 -1
- package/dist/esm/components/CnGraphCanvas/CnFlowNode.vue3.js.map +1 -1
- package/dist/esm/components/CnGraphCanvas/CnGraphCanvas.vue2.js +37 -0
- package/dist/esm/components/CnGraphCanvas/CnGraphCanvas.vue2.js.map +1 -1
- package/dist/esm/components/CnGraphCanvas/CnGraphCanvas.vue3.js +4 -2
- package/dist/esm/components/CnGraphCanvas/CnGraphCanvas.vue3.js.map +1 -1
- package/dist/esm/dialogs/CnFlowNodeEditModal.vue2.js +7 -10
- package/dist/esm/dialogs/CnFlowNodeEditModal.vue2.js.map +1 -1
- package/dist/esm/dialogs/CnFlowNodeEditModal.vue3.js +23 -13
- package/dist/esm/dialogs/CnFlowNodeEditModal.vue3.js.map +1 -1
- package/dist/esm/dialogs/CnRunNodeDialog.vue2.js +8 -6
- package/dist/esm/dialogs/CnRunNodeDialog.vue2.js.map +1 -1
- package/dist/esm/dialogs/CnRunNodeDialog.vue3.js.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +15 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +15 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +64 -0
- package/dist/esm/utils/selectOptions.js +57 -0
- package/dist/esm/utils/selectOptions.js.map +1 -0
- package/dist/nextcloud-vue.cjs.js +6050 -5649
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +64 -0
- package/l10n/en.json +15 -1
- package/l10n/nl.json +15 -1
- package/package.json +1 -1
- package/scripts/openregister-reliance.js +206 -0
- package/src/components/CnFlowDetail/CnFlowDetail.vue +27 -0
- package/src/components/CnFlowDetail/CnRunDetailSidebar.vue +6 -0
- package/src/components/CnFlowStepOutcomes/CnFlowStepOutcomes.vue +250 -0
- package/src/components/CnFlowStepOutcomes/index.js +8 -0
- package/src/components/CnGraphCanvas/CnFlowNode.vue +36 -1
- package/src/components/CnGraphCanvas/CnGraphCanvas.vue +40 -1
- package/src/components/index.js +1 -0
- package/src/dialogs/CnFlowNodeEditModal.vue +41 -20
- package/src/dialogs/CnRunNodeDialog.vue +8 -6
- package/src/index.js +1 -0
- package/src/utils/selectOptions.js +54 -0
|
@@ -224,6 +224,19 @@ export default {
|
|
|
224
224
|
* edges, its undo stack, its persistence — so nothing is removed here.
|
|
225
225
|
*/
|
|
226
226
|
'remove',
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* @event activate Enter or Space on the focused node: open this step.
|
|
230
|
+
* Carries the node id. The pointer equivalent is a double-click.
|
|
231
|
+
*/
|
|
232
|
+
'activate',
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* @event menu Shift+F10 or the menu key on the focused node: show this
|
|
236
|
+
* step's actions. Carries `{ id, clientX, clientY }`, a point at the
|
|
237
|
+
* node, so a host positions the menu as it would for a click.
|
|
238
|
+
*/
|
|
239
|
+
'menu',
|
|
227
240
|
],
|
|
228
241
|
|
|
229
242
|
setup() {
|
|
@@ -478,7 +491,8 @@ export default {
|
|
|
478
491
|
* The keyboard contract.
|
|
479
492
|
*
|
|
480
493
|
* Arrow keys move; `c` arms an exit and, on a second node, completes the
|
|
481
|
-
* connection; `Escape` cancels.
|
|
494
|
+
* connection; `Escape` cancels. Enter or Space opens the step, Shift+F10
|
|
495
|
+
* or the menu key opens its actions. Repeated `c` on the SAME node steps
|
|
482
496
|
* through its exits rather than re-arming the first — the behaviour a
|
|
483
497
|
* mouse gets for free by pointing.
|
|
484
498
|
*
|
|
@@ -527,6 +541,27 @@ export default {
|
|
|
527
541
|
return
|
|
528
542
|
}
|
|
529
543
|
|
|
544
|
+
// Opening the step, and its menu, from the keyboard. Only for a key
|
|
545
|
+
// pressed ON the node: a port inside it is a control of its own.
|
|
546
|
+
if (event.target === event.currentTarget) {
|
|
547
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
548
|
+
event.preventDefault()
|
|
549
|
+
this.$emit('activate', this.id)
|
|
550
|
+
return
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if ((event.key === 'F10' && event.shiftKey === true) || event.key === 'ContextMenu') {
|
|
554
|
+
event.preventDefault()
|
|
555
|
+
const rect = event.currentTarget.getBoundingClientRect()
|
|
556
|
+
this.$emit('menu', {
|
|
557
|
+
id: this.id,
|
|
558
|
+
clientX: rect.left + rect.width / 2,
|
|
559
|
+
clientY: rect.bottom,
|
|
560
|
+
})
|
|
561
|
+
return
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
530
565
|
// Delete and Backspace both, because which one removes a thing is a
|
|
531
566
|
// platform habit rather than a preference: Backspace is the delete
|
|
532
567
|
// key on a Mac keyboard, and a user who learned one does not think
|
|
@@ -37,7 +37,9 @@
|
|
|
37
37
|
v-bind="nodeProps"
|
|
38
38
|
:deletable="!readOnly"
|
|
39
39
|
@connect="onConnect"
|
|
40
|
-
@remove="onNodeRemove"
|
|
40
|
+
@remove="onNodeRemove"
|
|
41
|
+
@activate="onNodeActivate"
|
|
42
|
+
@menu="onNodeMenu">
|
|
41
43
|
<template #default="slotProps">
|
|
42
44
|
<!-- @slot node The body of a step, rendered inside the
|
|
43
45
|
focusable node wrapper. Receives `{ node }` with the
|
|
@@ -266,6 +268,8 @@ export default {
|
|
|
266
268
|
'canvas-drop',
|
|
267
269
|
|
|
268
270
|
'node-remove',
|
|
271
|
+
'node-activate',
|
|
272
|
+
'node-menu',
|
|
269
273
|
|
|
270
274
|
/**
|
|
271
275
|
* The canvas zoom changed, by any route — a control here, the host's own
|
|
@@ -415,6 +419,41 @@ export default {
|
|
|
415
419
|
return port === 'in' ? null : port
|
|
416
420
|
},
|
|
417
421
|
|
|
422
|
+
/**
|
|
423
|
+
* Forward Enter or Space on a node: the keyboard double-click.
|
|
424
|
+
*
|
|
425
|
+
* @param {string} id The node id.
|
|
426
|
+
* @return {void}
|
|
427
|
+
*/
|
|
428
|
+
onNodeActivate(id) {
|
|
429
|
+
if (this.readOnly === true) {
|
|
430
|
+
return
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* @event node-activate Enter or Space on a node. Payload is the id.
|
|
435
|
+
*/
|
|
436
|
+
this.$emit('node-activate', id)
|
|
437
|
+
},
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Forward Shift+F10 or the menu key on a node: the keyboard click menu.
|
|
441
|
+
*
|
|
442
|
+
* @param {object} request `{ id, clientX, clientY }`, a point at the node.
|
|
443
|
+
* @return {void}
|
|
444
|
+
*/
|
|
445
|
+
onNodeMenu(request) {
|
|
446
|
+
if (this.readOnly === true) {
|
|
447
|
+
return
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* @event node-menu Shift+F10 or the menu key on a node. Payload is
|
|
452
|
+
* `{ id, clientX, clientY }`.
|
|
453
|
+
*/
|
|
454
|
+
this.$emit('node-menu', request)
|
|
455
|
+
},
|
|
456
|
+
|
|
418
457
|
/**
|
|
419
458
|
* Forward a node click.
|
|
420
459
|
*
|
package/src/components/index.js
CHANGED
|
@@ -191,6 +191,7 @@ export { CnDeltaWidgetForm } from './CnDeltaWidgetForm/index.js'
|
|
|
191
191
|
export { CnGaugeWidget } from './CnGaugeWidget/index.js'
|
|
192
192
|
export { CnGaugeWidgetForm } from './CnGaugeWidgetForm/index.js'
|
|
193
193
|
export { CnFlowRunsWidget } from './CnFlowRunsWidget/index.js'
|
|
194
|
+
export { CnFlowStepOutcomes } from './CnFlowStepOutcomes/index.js'
|
|
194
195
|
export { CnFlowRunsWidgetForm } from './CnFlowRunsWidgetForm/index.js'
|
|
195
196
|
export { CnTasksWidget } from './CnTasksWidget/index.js'
|
|
196
197
|
export { CnTasksWidgetForm } from './CnTasksWidgetForm/index.js'
|
|
@@ -40,16 +40,27 @@
|
|
|
40
40
|
:placeholder="t('nextcloud-vue', 'GET')"
|
|
41
41
|
@update:modelValue="setKey(key, $event)" />
|
|
42
42
|
|
|
43
|
-
<!-- A `select` field
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
:
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
43
|
+
<!-- A `select` field renders as a picker, never a bare text box.
|
|
44
|
+
Its choices come from the URL the OWNING APP declared
|
|
45
|
+
(`optionsFrom`) or from the form itself (`options`), for a
|
|
46
|
+
short fixed vocabulary with no endpoint of its own. -->
|
|
47
|
+
<template v-else-if="widgetFor(key) === 'select'">
|
|
48
|
+
<NcSelect :modelValue="selectedOption(key)"
|
|
49
|
+
class="cn-flow-node-edit__select"
|
|
50
|
+
:options="selectOptions[key] || []"
|
|
51
|
+
:inputLabel="labelFor(key)"
|
|
52
|
+
:loading="selectLoading[key] === true"
|
|
53
|
+
:placeholder="t('nextcloud-vue', 'Pick one…')"
|
|
54
|
+
@update:modelValue="setKey(key, $event ? $event.id : '')" />
|
|
55
|
+
<!-- NcSelect has no helper text of its own, so the help
|
|
56
|
+
follows the control, the next thing a screen reader
|
|
57
|
+
reads after it. -->
|
|
58
|
+
<p v-if="hintFor(key)"
|
|
59
|
+
class="cn-flow-node-edit__field-help"
|
|
60
|
+
:data-testid="`flow-node-help-${key}`">
|
|
61
|
+
{{ hintFor(key) }}
|
|
62
|
+
</p>
|
|
63
|
+
</template>
|
|
53
64
|
|
|
54
65
|
<NcTextArea v-else-if="widgetFor(key) === 'textarea'"
|
|
55
66
|
:modelValue="String(draft.config[key] ?? '')"
|
|
@@ -170,6 +181,7 @@ import {
|
|
|
170
181
|
} from '@nextcloud/vue'
|
|
171
182
|
import CnCronField from '../components/CnCronField/CnCronField.vue'
|
|
172
183
|
import { useFlowStore } from '../composables/useFlowStore.js'
|
|
184
|
+
import { hasStaticOptions, normaliseSelectOptions } from '../utils/selectOptions.js'
|
|
173
185
|
|
|
174
186
|
/** The verbs an HTTP-shaped `method` option can take. */
|
|
175
187
|
/**
|
|
@@ -287,7 +299,7 @@ export default {
|
|
|
287
299
|
|
|
288
300
|
/**
|
|
289
301
|
* The node's per-field declarations from the catalogue's `configForm`
|
|
290
|
-
* ({key, label, type, help, required, optionsFrom}), keyed by config
|
|
302
|
+
* ({key, label, type, help, required, optionsFrom, options}), keyed by config
|
|
291
303
|
* key. `configKeys` is the degraded form. Either way the node's OWNER
|
|
292
304
|
* declares the vocabulary — this dialog never invents fields.
|
|
293
305
|
*
|
|
@@ -398,7 +410,9 @@ export default {
|
|
|
398
410
|
created() {
|
|
399
411
|
// Select fields need their options; everything else is local.
|
|
400
412
|
for (const [key, spec] of Object.entries(this.fieldSpecs)) {
|
|
401
|
-
if (spec
|
|
413
|
+
if (hasStaticOptions(spec)) {
|
|
414
|
+
this.selectOptions = { ...this.selectOptions, [key]: normaliseSelectOptions(spec.options) }
|
|
415
|
+
} else if (spec?.type === 'select' && spec?.optionsFrom) {
|
|
402
416
|
this.loadSelectOptions(key, spec)
|
|
403
417
|
}
|
|
404
418
|
}
|
|
@@ -634,7 +648,7 @@ export default {
|
|
|
634
648
|
*/
|
|
635
649
|
widgetFor(key) {
|
|
636
650
|
const spec = this.fieldSpecs[key]
|
|
637
|
-
if (spec?.type === 'select' && spec?.optionsFrom) {
|
|
651
|
+
if (spec?.type === 'select' && (spec?.optionsFrom || hasStaticOptions(spec))) {
|
|
638
652
|
return 'select'
|
|
639
653
|
}
|
|
640
654
|
if (spec?.type === 'boolean') {
|
|
@@ -730,13 +744,7 @@ export default {
|
|
|
730
744
|
? generateUrl(url)
|
|
731
745
|
: url)
|
|
732
746
|
const rows = Array.isArray(response.data) ? response.data : (response.data?.results || [])
|
|
733
|
-
const options = rows
|
|
734
|
-
const id = row.id ?? row.value ?? row['@self']?.uuid ?? row.uuid
|
|
735
|
-
return {
|
|
736
|
-
id,
|
|
737
|
-
label: row.label || row.name || row.title || String(id),
|
|
738
|
-
}
|
|
739
|
-
}).filter((o) => o.id !== undefined && o.id !== null && o.id !== '')
|
|
747
|
+
const options = normaliseSelectOptions(rows)
|
|
740
748
|
this.selectOptions = { ...this.selectOptions, [key]: options }
|
|
741
749
|
} catch (error) {
|
|
742
750
|
// A picker that could not load degrades to showing the stored
|
|
@@ -930,6 +938,19 @@ export default {
|
|
|
930
938
|
color: var(--color-text-maxcontrast);
|
|
931
939
|
}
|
|
932
940
|
|
|
941
|
+
/* Full width, like the text fields around it: at its natural width the
|
|
942
|
+
picker cut every option label in half. */
|
|
943
|
+
.cn-flow-node-edit__select {
|
|
944
|
+
width: 100%;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
.cn-flow-node-edit__field-help {
|
|
948
|
+
margin: 4px 0 0;
|
|
949
|
+
padding-inline-start: 8px;
|
|
950
|
+
font-size: 0.85em;
|
|
951
|
+
color: var(--color-text-maxcontrast);
|
|
952
|
+
}
|
|
953
|
+
|
|
933
954
|
.cn-flow-node-edit__advanced summary {
|
|
934
955
|
cursor: pointer;
|
|
935
956
|
color: var(--color-text-maxcontrast);
|
|
@@ -88,6 +88,7 @@
|
|
|
88
88
|
import { translate as t } from '@nextcloud/l10n'
|
|
89
89
|
import { NcButton, NcCheckboxRadioSwitch, NcDialog, NcLoadingIcon, NcNoteCard, NcSelect, NcTextArea, NcTextField } from '@nextcloud/vue'
|
|
90
90
|
import { prefixUrl } from '../utils/headers.js'
|
|
91
|
+
import { hasStaticOptions, normaliseSelectOptions } from '../utils/selectOptions.js'
|
|
91
92
|
|
|
92
93
|
/**
|
|
93
94
|
* The dialog `run-node` opens to collect a directly-invoked flow node's own
|
|
@@ -128,7 +129,9 @@ export default {
|
|
|
128
129
|
title: { type: String, default: '' },
|
|
129
130
|
/**
|
|
130
131
|
* The node's declared config form — `IFlowNodeConfigForm::configForm()`'s
|
|
131
|
-
* shape, unchanged: `[{key, label, type, help?, required?, optionsFrom?}]`.
|
|
132
|
+
* shape, unchanged: `[{key, label, type, help?, required?, optionsFrom?, options?}]`.
|
|
133
|
+
* A `select` takes its choices from `optionsFrom` (a url) or `options`
|
|
134
|
+
* (strings, or `{value, label}` pairs declared in the form itself).
|
|
132
135
|
*
|
|
133
136
|
* @type {Array<object>}
|
|
134
137
|
*/
|
|
@@ -177,7 +180,9 @@ export default {
|
|
|
177
180
|
}
|
|
178
181
|
this.values = seed
|
|
179
182
|
for (const field of (fields || [])) {
|
|
180
|
-
if (field
|
|
183
|
+
if (hasStaticOptions(field)) {
|
|
184
|
+
this.options = { ...this.options, [field.key]: normaliseSelectOptions(field.options, (s) => this.tr(s)) }
|
|
185
|
+
} else if (field.type === 'select' && field.optionsFrom) {
|
|
181
186
|
this.loadOptions(field)
|
|
182
187
|
}
|
|
183
188
|
}
|
|
@@ -274,10 +279,7 @@ export default {
|
|
|
274
279
|
const url = prefixUrl(raw)
|
|
275
280
|
const response = await axios.get(url)
|
|
276
281
|
const rows = Array.isArray(response.data) ? response.data : (response.data?.results || [])
|
|
277
|
-
const opts = rows
|
|
278
|
-
const id = row.id ?? row.value ?? row['@self']?.uuid ?? row.uuid
|
|
279
|
-
return { id, label: row.label || row.name || row.title || String(id) }
|
|
280
|
-
}).filter((o) => o.id !== undefined && o.id !== null && o.id !== '')
|
|
282
|
+
const opts = normaliseSelectOptions(rows)
|
|
281
283
|
this.options = { ...this.options, [field.key]: opts }
|
|
282
284
|
} catch (error) {
|
|
283
285
|
// A picker that fails to load degrades to an empty list rather
|
package/src/index.js
CHANGED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>
|
|
3
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
*
|
|
5
|
+
* The choices a node's `select` field offers, in the `{id, label}` shape
|
|
6
|
+
* NcSelect takes.
|
|
7
|
+
*
|
|
8
|
+
* A field gets its choices one of two ways. `optionsFrom` names a url the
|
|
9
|
+
* owning app answers. `options` lists them in the form itself, for a short
|
|
10
|
+
* fixed vocabulary that has no endpoint of its own. Both dialogs that render a
|
|
11
|
+
* node's configForm read the same shapes through this module, so the two
|
|
12
|
+
* cannot come to accept different ones.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Normalise rows into `{id, label}` options.
|
|
17
|
+
*
|
|
18
|
+
* Accepts plain strings and numbers, `{id, label}`, `{value, label}`, and
|
|
19
|
+
* OpenRegister objects (uuid from `@self`, label from name or title). A row
|
|
20
|
+
* without an id is dropped rather than shown as a blank option.
|
|
21
|
+
*
|
|
22
|
+
* @param {Array<unknown>} rows The raw rows.
|
|
23
|
+
* @param {(label: string) => string} [translate] Applied to each declared label.
|
|
24
|
+
* @return {Array<{id: string|number, label: string}>} The options.
|
|
25
|
+
*/
|
|
26
|
+
export function normaliseSelectOptions(rows, translate = (s) => s) {
|
|
27
|
+
if (!Array.isArray(rows)) {
|
|
28
|
+
return []
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return rows.map((row) => {
|
|
32
|
+
if (typeof row === 'string' || typeof row === 'number') {
|
|
33
|
+
return { id: row, label: String(row) }
|
|
34
|
+
}
|
|
35
|
+
if (!row || typeof row !== 'object') {
|
|
36
|
+
return { id: undefined, label: '' }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const id = row.id ?? row.value ?? row['@self']?.uuid ?? row.uuid
|
|
40
|
+
const declared = row.label || row.name || row.title
|
|
41
|
+
|
|
42
|
+
return { id, label: declared ? String(translate(declared)) : String(id) }
|
|
43
|
+
}).filter((option) => option.id !== undefined && option.id !== null && option.id !== '')
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Whether a field declares its choices in the form itself.
|
|
48
|
+
*
|
|
49
|
+
* @param {object} field The field declaration.
|
|
50
|
+
* @return {boolean} True for a `select` with an `options` list.
|
|
51
|
+
*/
|
|
52
|
+
export function hasStaticOptions(field) {
|
|
53
|
+
return field?.type === 'select' && Array.isArray(field?.options)
|
|
54
|
+
}
|