@revenexx/studio-integrations 0.5.1 → 0.7.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/CHANGELOG.md +428 -0
- package/README.md +48 -4
- package/dist/module.d.mts +17 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +10 -2
- package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +43 -41
- package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +123 -50
- package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue +108 -71
- package/dist/runtime/components/Integrations/CredentialTypePicker.vue +12 -15
- package/dist/runtime/components/Integrations/DeadLettersPanel.vue +18 -17
- package/dist/runtime/components/Integrations/EventTypeCombobox.d.vue.ts +1 -0
- package/dist/runtime/components/Integrations/EventTypeCombobox.vue +7 -4
- package/dist/runtime/components/Integrations/EventTypeCombobox.vue.d.ts +1 -0
- package/dist/runtime/components/Integrations/ExpressionInput.d.vue.ts +4 -0
- package/dist/runtime/components/Integrations/ExpressionInput.vue +67 -37
- package/dist/runtime/components/Integrations/ExpressionInput.vue.d.ts +4 -0
- package/dist/runtime/components/Integrations/GroupFrameView.vue +30 -20
- package/dist/runtime/components/Integrations/Layout.vue +48 -4
- package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue +23 -23
- package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue +11 -13
- package/dist/runtime/components/Integrations/NodeConfigField.d.vue.ts +2 -0
- package/dist/runtime/components/Integrations/NodeConfigField.vue +187 -93
- package/dist/runtime/components/Integrations/NodeConfigField.vue.d.ts +2 -0
- package/dist/runtime/components/Integrations/NodeInspector.d.vue.ts +6 -0
- package/dist/runtime/components/Integrations/NodeInspector.vue +1157 -376
- package/dist/runtime/components/Integrations/NodeInspector.vue.d.ts +6 -0
- package/dist/runtime/components/Integrations/NodeLogo.d.vue.ts +12 -0
- package/dist/runtime/components/Integrations/NodeLogo.vue +2 -30
- package/dist/runtime/components/Integrations/NodeLogo.vue.d.ts +12 -0
- package/dist/runtime/components/Integrations/NodeMark.d.vue.ts +15 -0
- package/dist/runtime/components/Integrations/NodeMark.vue +79 -0
- package/dist/runtime/components/Integrations/NodeMark.vue.d.ts +15 -0
- package/dist/runtime/components/Integrations/NodeOutputSchema.d.vue.ts +21 -0
- package/dist/runtime/components/Integrations/NodeOutputSchema.vue +43 -0
- package/dist/runtime/components/Integrations/NodeOutputSchema.vue.d.ts +21 -0
- package/dist/runtime/components/Integrations/NodePalette.vue +122 -55
- package/dist/runtime/components/Integrations/QueryBuilder.vue +46 -38
- package/dist/runtime/components/Integrations/RunActionsMenu.vue +38 -12
- package/dist/runtime/components/Integrations/RunDetailPanel.vue +114 -62
- package/dist/runtime/components/Integrations/RunPayloadInput.vue +27 -13
- package/dist/runtime/components/Integrations/RunResponseModal.vue +11 -10
- package/dist/runtime/components/Integrations/RunsTable.d.vue.ts +0 -1
- package/dist/runtime/components/Integrations/RunsTable.vue +38 -16
- package/dist/runtime/components/Integrations/RunsTable.vue.d.ts +0 -1
- package/dist/runtime/components/Integrations/SftpAccountImportDialog.vue +8 -7
- package/dist/runtime/components/Integrations/Sidebar.vue +14 -10
- package/dist/runtime/components/Integrations/StickyNoteView.d.vue.ts +14 -4
- package/dist/runtime/components/Integrations/StickyNoteView.vue +38 -23
- package/dist/runtime/components/Integrations/StickyNoteView.vue.d.ts +14 -4
- package/dist/runtime/components/Integrations/TemplateCard.d.vue.ts +18 -0
- package/dist/runtime/components/Integrations/TemplateCard.vue +119 -0
- package/dist/runtime/components/Integrations/TemplateCard.vue.d.ts +18 -0
- package/dist/runtime/components/Integrations/TemplateFlowPreview.d.vue.ts +2 -2
- package/dist/runtime/components/Integrations/TemplateFlowPreview.vue +16 -18
- package/dist/runtime/components/Integrations/TemplateFlowPreview.vue.d.ts +2 -2
- package/dist/runtime/components/Integrations/WorkflowContextMenu.vue +34 -33
- package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +52 -67
- package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue +8 -7
- package/dist/runtime/components/Integrations/WorkflowEdgeView.d.vue.ts +18 -4
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +29 -11
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue.d.ts +18 -4
- package/dist/runtime/components/Integrations/WorkflowEditor.d.vue.ts +20 -1
- package/dist/runtime/components/Integrations/WorkflowEditor.vue +240 -116
- package/dist/runtime/components/Integrations/WorkflowEditor.vue.d.ts +20 -1
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +28 -43
- package/dist/runtime/components/Integrations/WorkflowIconPicker.d.vue.ts +19 -1
- package/dist/runtime/components/Integrations/WorkflowIconPicker.vue +17 -7
- package/dist/runtime/components/Integrations/WorkflowIconPicker.vue.d.ts +19 -1
- package/dist/runtime/components/Integrations/WorkflowNodeView.d.vue.ts +4 -0
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue +198 -56
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue.d.ts +4 -0
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +56 -59
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue +28 -21
- package/dist/runtime/components/Integrations/WorkflowTagsInput.d.vue.ts +1 -0
- package/dist/runtime/components/Integrations/WorkflowTagsInput.vue +15 -9
- package/dist/runtime/components/Integrations/WorkflowTagsInput.vue.d.ts +1 -0
- package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +15 -10
- package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +50 -36
- package/dist/runtime/components/Integrations/WorkflowsTable.vue +126 -48
- package/dist/runtime/composables/integrationsApi.js +4 -2
- package/dist/runtime/composables/useCredentialTypes.js +8 -6
- package/dist/runtime/composables/useIntegrationsAudit.js +4 -3
- package/dist/runtime/composables/useIntegrationsCredentials.js +10 -8
- package/dist/runtime/composables/useIntegrationsDeadLetters.js +7 -6
- package/dist/runtime/composables/useIntegrationsFocus.d.ts +12 -0
- package/dist/runtime/composables/useIntegrationsLocale.d.ts +26 -0
- package/dist/runtime/composables/useIntegrationsLocale.js +8 -0
- package/dist/runtime/composables/useIntegrationsNodes.js +7 -5
- package/dist/runtime/composables/useIntegrationsRealtime.d.ts +149 -0
- package/dist/runtime/composables/useIntegrationsRealtime.js +80 -0
- package/dist/runtime/composables/useIntegrationsRevisions.js +9 -7
- package/dist/runtime/composables/useIntegrationsRuns.d.ts +1 -1
- package/dist/runtime/composables/useIntegrationsRuns.js +14 -7
- package/dist/runtime/composables/useIntegrationsSchedules.d.ts +4 -0
- package/dist/runtime/composables/useIntegrationsSchedules.js +10 -3
- package/dist/runtime/composables/useIntegrationsSecrets.js +14 -9
- package/dist/runtime/composables/useIntegrationsT.d.ts +35 -0
- package/dist/runtime/composables/useIntegrationsT.js +9 -0
- package/dist/runtime/composables/useIntegrationsTemplates.d.ts +3 -3
- package/dist/runtime/composables/useIntegrationsTemplates.js +28 -16
- package/dist/runtime/composables/useIntegrationsTriggers.js +10 -8
- package/dist/runtime/composables/useIntegrationsWorkflows.js +13 -7
- package/dist/runtime/composables/useLoadGate.d.ts +17 -0
- package/dist/runtime/composables/useLoadGate.js +8 -0
- package/dist/runtime/composables/useNodeConfigResolve.js +2 -1
- package/dist/runtime/data/branchReorder.d.ts +37 -0
- package/dist/runtime/data/branchReorder.js +71 -0
- package/dist/runtime/data/handleId.d.ts +23 -0
- package/dist/runtime/data/handleId.js +6 -0
- package/dist/runtime/data/integrationNodeCatalog.d.ts +41 -0
- package/dist/runtime/data/integrationNodeCatalog.js +14 -0
- package/dist/runtime/data/integrationTemplates.d.ts +48 -0
- package/dist/runtime/data/integrationTemplates.js +21 -0
- package/dist/runtime/data/integrationWorkflowSamples.js +1 -1
- package/dist/runtime/data/nodeIcons.d.ts +2 -0
- package/dist/runtime/data/nodeIcons.js +73 -0
- package/dist/runtime/data/nodeInputData.d.ts +16 -4
- package/dist/runtime/data/nodeInputData.js +4 -0
- package/dist/runtime/data/payloadForm.d.ts +9 -2
- package/dist/runtime/data/payloadForm.js +29 -4
- package/dist/runtime/data/portTone.d.ts +17 -0
- package/dist/runtime/data/portTone.js +7 -0
- package/dist/runtime/data/webhookAuth.d.ts +18 -0
- package/dist/runtime/data/webhookAuth.js +19 -0
- package/dist/runtime/data/workflowIconCatalog.js +4 -6
- package/dist/runtime/i18n/messages/de.json +1207 -0
- package/dist/runtime/i18n/messages/en.json +1207 -0
- package/dist/runtime/i18n/translate.d.ts +83 -0
- package/dist/runtime/i18n/translate.js +41 -0
- package/dist/runtime/pages/integrations/audit.vue +140 -37
- package/dist/runtime/pages/integrations/credentials/oauth-complete.vue +9 -6
- package/dist/runtime/pages/integrations/credentials.vue +180 -64
- package/dist/runtime/pages/integrations/index.vue +91 -92
- package/dist/runtime/pages/integrations/runs.vue +33 -21
- package/dist/runtime/pages/integrations/schedules.vue +88 -23
- package/dist/runtime/pages/integrations/secrets.vue +57 -35
- package/dist/runtime/pages/integrations/templates/[slug].vue +119 -88
- package/dist/runtime/pages/integrations/templates/index.vue +82 -103
- package/dist/runtime/pages/integrations/workflows/[id].vue +344 -91
- package/dist/runtime/pages/integrations/workflows/index.vue +23 -5
- package/dist/runtime/plugins/locale.d.ts +10 -0
- package/dist/runtime/plugins/locale.js +6 -0
- package/dist/runtime/plugins/realtime.client.d.ts +32 -0
- package/dist/runtime/plugins/realtime.client.js +4 -0
- package/dist/runtime/types/integrations.d.ts +65 -6
- package/dist/runtime/utils/apiError.d.ts +47 -0
- package/dist/runtime/utils/apiError.js +32 -0
- package/dist/runtime/utils/cockpitSources.d.ts +153 -0
- package/dist/runtime/utils/cockpitSources.js +14 -0
- package/dist/runtime/utils/credentialState.d.ts +24 -1
- package/dist/runtime/utils/credentialState.js +26 -11
- package/dist/runtime/utils/loadGate.d.ts +61 -0
- package/dist/runtime/utils/loadGate.js +22 -0
- package/dist/runtime/utils/localize.d.ts +46 -4
- package/dist/runtime/utils/localize.js +34 -7
- package/dist/runtime/utils/notify.d.ts +59 -0
- package/dist/runtime/utils/notify.js +21 -0
- package/dist/runtime/utils/pollBudget.d.ts +72 -0
- package/dist/runtime/utils/pollBudget.js +17 -0
- package/dist/runtime/utils/revenexxCredentialSource.d.ts +26 -0
- package/dist/runtime/utils/revenexxCredentialSource.js +8 -0
- package/dist/runtime/utils/schedule.d.ts +116 -0
- package/dist/runtime/utils/schedule.js +219 -0
- package/dist/runtime/utils/statusStyle.d.ts +26 -2
- package/dist/runtime/utils/statusStyle.js +9 -1
- package/dist/runtime/utils/validationFindings.d.ts +101 -0
- package/dist/runtime/utils/validationFindings.js +54 -0
- package/dist/runtime/utils/whenIdle.d.ts +42 -0
- package/dist/runtime/utils/whenIdle.js +12 -0
- package/dist/types.d.mts +2 -6
- package/package.json +10 -3
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { computed, inject, ref, watch } from "vue";
|
|
2
|
+
import { computed, inject, nextTick, ref, watch } from "vue";
|
|
3
3
|
import { Handle, Position, useVueFlow } from "@vue-flow/core";
|
|
4
4
|
import { NodeToolbar } from "@vue-flow/node-toolbar";
|
|
5
5
|
import * as Lucide from "lucide-vue-next";
|
|
6
|
+
import { Copy, TrashBinMinimalistic as TrashBin } from "#solar-icons";
|
|
6
7
|
import { NODE_CATEGORIES, findManifest } from "../../data/integrationNodeCatalog";
|
|
8
|
+
import { isTerminalManifest } from "../../data/nodeInputData";
|
|
9
|
+
import { resolveDisplayName, resolveLocalized } from "../../utils/localize";
|
|
7
10
|
const props = defineProps({
|
|
8
11
|
id: { type: String, required: true },
|
|
9
12
|
data: { type: Object, required: true },
|
|
@@ -11,44 +14,28 @@ const props = defineProps({
|
|
|
11
14
|
});
|
|
12
15
|
defineOptions({ inheritAttrs: false });
|
|
13
16
|
defineEmits(["updateNodeInternals"]);
|
|
17
|
+
const t = useIntegrationsT();
|
|
14
18
|
const manifest = computed(() => findManifest(props.data.nodeSlug, props.data.nodeVersion));
|
|
15
19
|
const category = computed(() => NODE_CATEGORIES.find((c) => c.key === manifest.value?.category));
|
|
16
|
-
const hasLogo = computed(() => (manifest.value?.images ?? []).some((i) => i.category === "logo"));
|
|
17
|
-
function iconFor(name) {
|
|
18
|
-
if (!name) return Lucide.Box;
|
|
19
|
-
const cleaned = (name.includes(":") ? name.split(":").pop() : name).split("-").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("");
|
|
20
|
-
return Lucide[cleaned] || Lucide.Box;
|
|
21
|
-
}
|
|
22
|
-
function labelText(value) {
|
|
23
|
-
if (!value) return "";
|
|
24
|
-
return typeof value === "string" ? value : value.en ?? Object.values(value)[0] ?? "";
|
|
25
|
-
}
|
|
26
20
|
const title = computed(
|
|
27
|
-
() =>
|
|
21
|
+
() => resolveDisplayName(props.data.name) || resolveLocalized(manifest.value?.name) || props.data.nodeSlug
|
|
28
22
|
);
|
|
29
23
|
const inputRequired = computed(() => manifest.value?.input?.required !== false);
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
return v.filter((x) => ["string", "number", "boolean"].includes(typeof x)).map((x) => String(x).trim()).filter(Boolean).join(", ");
|
|
41
|
-
}
|
|
42
|
-
return ["string", "number", "boolean"].includes(typeof v) ? String(v).trim() : "";
|
|
43
|
-
}
|
|
44
|
-
const subtitle = computed(
|
|
45
|
-
() => subtitleKeys.value.map((k) => scalarText(props.data.config?.[k])).filter((s) => s !== "").join(" \xB7 ")
|
|
46
|
-
);
|
|
24
|
+
const invalidFields = computed(() => props.data.__invalidFields ?? []);
|
|
25
|
+
const invalidLabel = computed(() => t("integration.canvas.node.badge.invalidConfig", invalidFields.value.length, { fields: invalidFields.value.join(", ") }));
|
|
26
|
+
const notes = computed(() => (props.data.notes ?? "").trim());
|
|
27
|
+
const noteOpen = ref(false);
|
|
28
|
+
const noteReady = ref(false);
|
|
29
|
+
const notePanel = ref(null);
|
|
30
|
+
const notePlacement = ref(Position.Bottom);
|
|
31
|
+
const noteMaxHeight = ref(0);
|
|
32
|
+
let noteTimer = null;
|
|
33
|
+
const notePanelId = computed(() => `node-note-${props.id}`);
|
|
47
34
|
function portsFromOutputs(list) {
|
|
48
35
|
const ports = [];
|
|
49
36
|
for (const o of list) {
|
|
50
37
|
if (o.name) {
|
|
51
|
-
ports.push({ name: o.name, kind: o.kind, label:
|
|
38
|
+
ports.push({ name: o.name, kind: o.kind, label: resolveLocalized(o.label) || o.name });
|
|
52
39
|
} else if (o.sourceFromConfig) {
|
|
53
40
|
const raw = props.data.config?.[o.sourceFromConfig];
|
|
54
41
|
const values = Array.isArray(raw) ? raw : [];
|
|
@@ -60,7 +47,7 @@ function portsFromOutputs(list) {
|
|
|
60
47
|
ports.push({
|
|
61
48
|
name: o.fallback.name,
|
|
62
49
|
kind: o.kind,
|
|
63
|
-
label:
|
|
50
|
+
label: resolveLocalized(o.fallback.label) || o.fallback.name
|
|
64
51
|
});
|
|
65
52
|
}
|
|
66
53
|
}
|
|
@@ -69,24 +56,48 @@ function portsFromOutputs(list) {
|
|
|
69
56
|
}
|
|
70
57
|
const resolvePending = computed(() => (manifest.value?.outputs ?? []).some((o) => o.resolveOutputs === true) && !Array.isArray(props.data.__resolvedOutputs));
|
|
71
58
|
const outputs = computed(() => {
|
|
59
|
+
const m = manifest.value;
|
|
60
|
+
if (isTerminalManifest(m)) return [];
|
|
72
61
|
const resolved = props.data.__resolvedOutputs;
|
|
73
62
|
if (Array.isArray(resolved) && resolved.length) {
|
|
74
63
|
const ports2 = portsFromOutputs(resolved);
|
|
75
|
-
if (!ports2.length) ports2.push({ name: "default", kind: "default", label: "
|
|
64
|
+
if (!ports2.length) ports2.push({ name: "default", kind: "default", label: t("integration.canvas.node.port.outputFallback") });
|
|
76
65
|
return ports2;
|
|
77
66
|
}
|
|
78
|
-
|
|
79
|
-
if (!m) return [{ name: "default", kind: "default", label: "out" }];
|
|
67
|
+
if (!m) return [{ name: "default", kind: "default", label: t("integration.canvas.node.port.outputFallback") }];
|
|
80
68
|
const ports = portsFromOutputs(m.outputs ?? []);
|
|
81
|
-
if (!ports.length) ports.push({ name: "default", kind: "default", label: "
|
|
69
|
+
if (!ports.length) ports.push({ name: "default", kind: "default", label: t("integration.canvas.node.port.outputFallback") });
|
|
82
70
|
return ports;
|
|
83
71
|
});
|
|
72
|
+
function portSemantic(p) {
|
|
73
|
+
if (p.name === "true") return "branch-true";
|
|
74
|
+
if (p.name === "false") return "branch-false";
|
|
75
|
+
if (p.name === "error" || p.kind === "error") return "error";
|
|
76
|
+
if (p.name === "done") return "success";
|
|
77
|
+
if (p.kind === "branch") return "branch";
|
|
78
|
+
const portDef = (manifest.value?.outputs ?? []).find((o) => o.name === p.name);
|
|
79
|
+
return portDef ? "success" : "default";
|
|
80
|
+
}
|
|
84
81
|
function portTone(p) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
82
|
+
switch (portSemantic(p)) {
|
|
83
|
+
case "success":
|
|
84
|
+
case "branch-true":
|
|
85
|
+
// Switch cases / iterator `item`: a taken branch is a normal continuation,
|
|
86
|
+
// so it reads green like any other output rather than a special colour.
|
|
87
|
+
case "branch":
|
|
88
|
+
return { handle: "!bg-emerald-500", label: "text-emerald-600" };
|
|
89
|
+
// `false` is the neutral "didn't match" route, not a warning — muted slate
|
|
90
|
+
// so it recedes behind the green primary path (matches `default`).
|
|
91
|
+
case "branch-false":
|
|
92
|
+
return { handle: "!bg-slate-500", label: "text-slate-500" };
|
|
93
|
+
// Soft rose dot marks the error port; the LABEL stays neutral slate (not
|
|
94
|
+
// rose) so it's legible without a red-alert pull that would out-shout the
|
|
95
|
+
// green success label. Colour lives on the dot, the label just names it.
|
|
96
|
+
case "error":
|
|
97
|
+
return { handle: "!bg-rose-300", label: "text-slate-500" };
|
|
98
|
+
default:
|
|
99
|
+
return { handle: "!bg-slate-500", label: "text-slate-500" };
|
|
100
|
+
}
|
|
90
101
|
}
|
|
91
102
|
const showPortLabels = computed(() => resolvePending.value || outputs.value.length > 1 || outputs.value.some((p) => p.kind !== "default"));
|
|
92
103
|
const IN_SIDES = ["top"];
|
|
@@ -112,38 +123,107 @@ function onHoverLeave() {
|
|
|
112
123
|
hovered.value = false;
|
|
113
124
|
}, 200);
|
|
114
125
|
}
|
|
115
|
-
watch(
|
|
126
|
+
watch(() => Boolean(props.data.nodeVersion), () => flow.updateNodeInternals([props.id]));
|
|
127
|
+
const NOTE_GAP = 6;
|
|
128
|
+
const NOTE_TOOLBAR_CLEARANCE = 42;
|
|
129
|
+
const canvasBottomInset = inject("workflow-canvas-bottom-inset", null);
|
|
130
|
+
const noteTopOffset = ref(NOTE_GAP);
|
|
131
|
+
async function openNote(ev) {
|
|
132
|
+
holdNote();
|
|
133
|
+
if (noteOpen.value) return;
|
|
134
|
+
const card = ev.currentTarget?.closest(".vue-flow__node");
|
|
135
|
+
noteTopOffset.value = nodeActions && (hovered.value || props.selected) ? NOTE_TOOLBAR_CLEARANCE : NOTE_GAP;
|
|
136
|
+
noteOpen.value = true;
|
|
137
|
+
noteReady.value = false;
|
|
138
|
+
await nextTick();
|
|
139
|
+
const bounds = flow.vueFlowRef.value?.getBoundingClientRect();
|
|
140
|
+
if (notePanel.value && card && bounds) {
|
|
141
|
+
const rect = card.getBoundingClientRect();
|
|
142
|
+
const floor = Math.min(bounds.bottom, window.innerHeight) - (canvasBottomInset?.() ?? 0);
|
|
143
|
+
const ceiling = Math.max(bounds.top, 0);
|
|
144
|
+
const below = floor - rect.bottom - NOTE_GAP;
|
|
145
|
+
const above = rect.top - ceiling - noteTopOffset.value;
|
|
146
|
+
const flip = notePanel.value.offsetHeight > below && above > below;
|
|
147
|
+
notePlacement.value = flip ? Position.Top : Position.Bottom;
|
|
148
|
+
noteMaxHeight.value = Math.max(80, Math.floor(flip ? above : below));
|
|
149
|
+
}
|
|
150
|
+
noteReady.value = true;
|
|
151
|
+
}
|
|
152
|
+
function holdNote() {
|
|
153
|
+
if (noteTimer) clearTimeout(noteTimer);
|
|
154
|
+
noteTimer = null;
|
|
155
|
+
}
|
|
156
|
+
function releaseNote() {
|
|
157
|
+
if (noteTimer) clearTimeout(noteTimer);
|
|
158
|
+
noteTimer = setTimeout(() => {
|
|
159
|
+
noteOpen.value = false;
|
|
160
|
+
}, 250);
|
|
161
|
+
}
|
|
116
162
|
const nodeActions = inject("workflow-node-actions", null);
|
|
163
|
+
const canConfigure = computed(() => typeof nodeActions?.configure === "function");
|
|
164
|
+
function configure() {
|
|
165
|
+
nodeActions?.configure?.(props.id);
|
|
166
|
+
}
|
|
117
167
|
function duplicate() {
|
|
118
168
|
nodeActions?.duplicate(props.id);
|
|
119
169
|
}
|
|
120
170
|
function remove() {
|
|
121
|
-
|
|
122
|
-
else flow.removeNodes([props.id]);
|
|
171
|
+
nodeActions?.remove(props.id);
|
|
123
172
|
}
|
|
124
173
|
</script>
|
|
125
174
|
|
|
126
175
|
<template>
|
|
127
|
-
|
|
176
|
+
<!-- No node-actions injected → read-only canvas (e.g. run replay): the
|
|
177
|
+
duplicate/delete affordance would be dead, so don't offer it. -->
|
|
178
|
+
<!-- Icon-only, so each action carries a name — and it names the node, not
|
|
179
|
+
just the verb: the editor header has its own Duplicate (the workflow),
|
|
180
|
+
and "Duplicate" alone would be the same announcement for both. -->
|
|
181
|
+
<NodeToolbar v-if="nodeActions" :is-visible="hovered || selected" :position="Position.Top" :offset="8" class="flex items-center gap-1 rounded-md border border-black/[0.08] bg-white px-1 py-1 shadow-md" @mouseenter="onHoverEnter" @mouseleave="onHoverLeave">
|
|
128
182
|
<button
|
|
129
183
|
type="button"
|
|
130
184
|
class="flex h-6 items-center gap-1 rounded px-1.5 text-sm text-foreground/80 hover:bg-muted/50"
|
|
131
|
-
title="
|
|
185
|
+
:title="t('integration.canvas.node.action.duplicate')"
|
|
186
|
+
:aria-label="t('integration.canvas.node.action.duplicateAria')"
|
|
132
187
|
@click="duplicate"
|
|
133
188
|
>
|
|
134
|
-
<
|
|
189
|
+
<Copy :size="11" />
|
|
135
190
|
</button>
|
|
136
191
|
<div class="h-4 w-px bg-black/10" />
|
|
137
192
|
<button
|
|
138
193
|
type="button"
|
|
139
194
|
class="flex h-6 items-center gap-1 rounded px-1.5 text-sm text-rose-600 hover:bg-rose-50"
|
|
140
|
-
title="
|
|
195
|
+
:title="t('integration.common.action.delete')"
|
|
196
|
+
:aria-label="t('integration.canvas.node.action.deleteAria')"
|
|
141
197
|
@click="remove"
|
|
142
198
|
>
|
|
143
|
-
<
|
|
199
|
+
<TrashBin :size="11" />
|
|
144
200
|
</button>
|
|
145
201
|
</NodeToolbar>
|
|
146
202
|
|
|
203
|
+
<!-- The note itself, hanging off the corner the badge sits in — below the card
|
|
204
|
+
by default, above it when a long note would otherwise run past the bottom
|
|
205
|
+
of the canvas. A NodeToolbar rather than an in-card panel: it escapes the
|
|
206
|
+
node's stacking context, so the text can't end up behind a neighbour. -->
|
|
207
|
+
<NodeToolbar
|
|
208
|
+
v-if="notes"
|
|
209
|
+
:is-visible="noteOpen"
|
|
210
|
+
:position="notePlacement"
|
|
211
|
+
align="end"
|
|
212
|
+
:offset="notePlacement === Position.Top ? noteTopOffset : NOTE_GAP"
|
|
213
|
+
class="nowheel max-w-[240px] overflow-y-auto overscroll-contain rounded-lg border border-black/[0.08] bg-white px-2.5 py-2 text-left shadow-lg"
|
|
214
|
+
:class="noteReady ? '' : 'invisible'"
|
|
215
|
+
:style="noteMaxHeight ? { maxHeight: `${noteMaxHeight}px` } : void 0"
|
|
216
|
+
@mouseenter="holdNote"
|
|
217
|
+
@mouseleave="releaseNote"
|
|
218
|
+
>
|
|
219
|
+
<div ref="notePanel" role="tooltip">
|
|
220
|
+
<div class="mb-1 text-[8px] font-semibold uppercase tracking-[0.08em] text-muted-foreground/60">
|
|
221
|
+
{{ t("integration.canvas.node.note.heading") }}
|
|
222
|
+
</div>
|
|
223
|
+
<p class="whitespace-pre-wrap break-words text-[11px] leading-snug text-foreground/80">{{ notes }}</p>
|
|
224
|
+
</div>
|
|
225
|
+
</NodeToolbar>
|
|
226
|
+
|
|
147
227
|
<!-- Input: ONE logical input, attachable from the left or the top — both
|
|
148
228
|
dots feed the same `__in` port. Clearly visible, ring + shadow. -->
|
|
149
229
|
<template v-if="inputRequired">
|
|
@@ -153,7 +233,7 @@ function remove() {
|
|
|
153
233
|
:key="`in-${side}`"
|
|
154
234
|
type="target"
|
|
155
235
|
:position="sideToPosition(side)"
|
|
156
|
-
title="
|
|
236
|
+
:title="t('integration.canvas.node.handle.input')"
|
|
157
237
|
class="!size-3 !rounded-full !border-2 !border-white !bg-slate-500 !shadow-sm transition-all hover:!size-3.5 hover:!bg-slate-700"
|
|
158
238
|
/>
|
|
159
239
|
</template>
|
|
@@ -181,10 +261,66 @@ function remove() {
|
|
|
181
261
|
@mouseenter="onHoverEnter"
|
|
182
262
|
@mouseleave="onHoverLeave"
|
|
183
263
|
:class="[
|
|
184
|
-
selected ? 'border-emerald-500/50 shadow-[0_0_0_2px_rgba(16,185,129,0.18)]' : 'border-black/[0.08] shadow-sm hover:shadow-md hover:border-black/[0.16]',
|
|
264
|
+
selected ? 'border-emerald-500/50 shadow-[0_0_0_2px_rgba(16,185,129,0.18)]' : invalidFields.length ? 'border-amber-500/70 shadow-[0_0_0_2px_rgba(245,158,11,0.22)] hover:shadow-md' : 'border-black/[0.08] shadow-sm hover:shadow-md hover:border-black/[0.16]',
|
|
185
265
|
showPortLabels ? 'pb-3.5' : ''
|
|
186
266
|
]"
|
|
187
267
|
>
|
|
268
|
+
<!-- Incomplete settings at the last save: the count, amber, at the corner
|
|
269
|
+
the input handles leave free. Hoverable on purpose — the count alone
|
|
270
|
+
doesn't say what is wrong, so the title spells it out and lists the
|
|
271
|
+
fields; `nodrag` keeps that hover from starting a card drag. -->
|
|
272
|
+
<span
|
|
273
|
+
v-if="invalidFields.length"
|
|
274
|
+
role="img"
|
|
275
|
+
:aria-label="invalidLabel"
|
|
276
|
+
:title="invalidLabel"
|
|
277
|
+
class="nodrag absolute -left-2 -top-2 z-10 flex cursor-help items-center gap-0.5 rounded-full bg-amber-500 px-1.5 py-0.5 text-[9px] font-bold leading-none text-white shadow-sm"
|
|
278
|
+
>
|
|
279
|
+
<Lucide.TriangleAlert :size="9" :stroke-width="2.5" />
|
|
280
|
+
{{ invalidFields.length }}
|
|
281
|
+
</span>
|
|
282
|
+
<!-- Always-visible edit affordance: single click opens the config lightbox.
|
|
283
|
+
(Double-click on the whole card works too.) Hidden in read-only hosts
|
|
284
|
+
that don't provide `configure`. `nodrag` so pressing it never starts a
|
|
285
|
+
node drag; `.stop` so it doesn't bubble to pane/canvas. -->
|
|
286
|
+
<button
|
|
287
|
+
v-if="canConfigure"
|
|
288
|
+
type="button"
|
|
289
|
+
class="nodrag absolute right-1.5 top-1.5 z-10 flex size-5 items-center justify-center rounded text-muted-foreground/50 transition-colors hover:bg-muted hover:text-foreground"
|
|
290
|
+
:title="t('integration.canvas.node.action.configure')"
|
|
291
|
+
:aria-label="t('integration.canvas.node.action.configure')"
|
|
292
|
+
@click.stop="configure"
|
|
293
|
+
@mousedown.stop
|
|
294
|
+
@dblclick.stop
|
|
295
|
+
>
|
|
296
|
+
<Lucide.Pencil :size="11" />
|
|
297
|
+
</button>
|
|
298
|
+
<!-- Note badge, mirroring the edit affordance into the opposite corner. Its
|
|
299
|
+
hit area is the whole 20px square, not the 12px glyph. Sits clear of the
|
|
300
|
+
port-label strip when there is one; `nodrag` + `.stop` so grabbing it
|
|
301
|
+
neither drags the node nor opens the config. -->
|
|
302
|
+
<button
|
|
303
|
+
v-if="notes"
|
|
304
|
+
type="button"
|
|
305
|
+
class="nodrag absolute right-1.5 z-10 flex size-5 items-center justify-center rounded text-muted-foreground/45 transition-colors hover:bg-muted hover:text-foreground"
|
|
306
|
+
:class="showPortLabels ? 'bottom-4' : 'bottom-1.5'"
|
|
307
|
+
:aria-label="t('integration.canvas.node.note.heading')"
|
|
308
|
+
:aria-describedby="notePanelId"
|
|
309
|
+
@mouseenter="openNote"
|
|
310
|
+
@mouseleave="releaseNote"
|
|
311
|
+
@focus="openNote"
|
|
312
|
+
@blur="releaseNote"
|
|
313
|
+
@click.stop
|
|
314
|
+
@mousedown.stop
|
|
315
|
+
@dblclick.stop
|
|
316
|
+
>
|
|
317
|
+
<Lucide.MessageSquareText :size="12" :stroke-width="2.25" />
|
|
318
|
+
</button>
|
|
319
|
+
<!-- The badge's description. `sr-only` keeps it out of the picture and out of
|
|
320
|
+
the layout (it is absolutely positioned, so the card's height — which
|
|
321
|
+
feeds the handle positions — is untouched), while keeping it in the
|
|
322
|
+
accessibility tree, where the visible panel cannot be relied on. -->
|
|
323
|
+
<span v-if="notes" :id="notePanelId" class="sr-only">{{ notes }}</span>
|
|
188
324
|
<!-- Port labels sit ABOVE their bottom dots, inside the card's label
|
|
189
325
|
strip, whenever ports are distinguishable (true/false, error, cases). -->
|
|
190
326
|
<template v-if="showPortLabels">
|
|
@@ -195,7 +331,7 @@ function remove() {
|
|
|
195
331
|
class="pointer-events-none absolute bottom-1 left-1/2 flex -translate-x-1/2 select-none items-center gap-1 whitespace-nowrap text-[9px] font-semibold uppercase tracking-wide text-muted-foreground"
|
|
196
332
|
>
|
|
197
333
|
<Lucide.LoaderCircle :size="9" class="animate-spin" />
|
|
198
|
-
|
|
334
|
+
{{ t("integration.canvas.node.port.resolving") }}
|
|
199
335
|
</span>
|
|
200
336
|
<template v-else>
|
|
201
337
|
<span
|
|
@@ -212,17 +348,23 @@ function remove() {
|
|
|
212
348
|
<div class="flex items-center gap-2.5 px-3 py-2.5">
|
|
213
349
|
<span
|
|
214
350
|
class="flex size-8 shrink-0 items-center justify-center rounded-md"
|
|
215
|
-
:class="
|
|
351
|
+
:class="category?.color ?? 'bg-muted text-foreground'"
|
|
216
352
|
>
|
|
217
|
-
<
|
|
218
|
-
<component :is="iconFor(manifest?.icon)" v-else :size="15" :stroke-width="2.25" />
|
|
353
|
+
<IntegrationsNodeMark :manifest="manifest" :size="18" />
|
|
219
354
|
</span>
|
|
220
355
|
<div class="min-w-0 flex-1">
|
|
221
356
|
<div class="text-sm font-semibold leading-tight text-foreground truncate">
|
|
222
357
|
{{ title }}
|
|
223
358
|
</div>
|
|
224
|
-
|
|
225
|
-
|
|
359
|
+
<!-- Meta line: the pinned version, small + muted but always visible so
|
|
360
|
+
you can tell which version an instance runs without opening the
|
|
361
|
+
inspector. Left-aligned and on the second line, clear of the
|
|
362
|
+
top-right edit affordance (PO-192). -->
|
|
363
|
+
<div v-if="data.nodeVersion" class="mt-0.5 flex items-center gap-1.5 text-sm text-muted-foreground">
|
|
364
|
+
<span
|
|
365
|
+
class="shrink-0 text-[10px] font-medium leading-none tabular-nums text-muted-foreground/55"
|
|
366
|
+
:title="t('integration.canvas.node.meta.versionTitle', { version: data.nodeVersion })"
|
|
367
|
+
>v{{ data.nodeVersion }}</span>
|
|
226
368
|
</div>
|
|
227
369
|
</div>
|
|
228
370
|
</div>
|
|
@@ -5,6 +5,10 @@ interface NodeData extends WorkflowNode {
|
|
|
5
5
|
* editor without persisting to the blob. When present and non-empty, these
|
|
6
6
|
* drive the rendered handles instead of the static manifest computation. */
|
|
7
7
|
__resolvedOutputs?: NodeManifestOutput[];
|
|
8
|
+
/** Config keys the backend reported as incomplete at the last save (PO-209),
|
|
9
|
+
* injected by the editor the same non-persisting way. Read-only canvases
|
|
10
|
+
* (run replay, history preview) never pass them, so nothing is marked there. */
|
|
11
|
+
__invalidFields?: string[];
|
|
8
12
|
}
|
|
9
13
|
type __VLS_Props = {
|
|
10
14
|
id: string;
|