@revenexx/studio-integrations 0.1.4 → 0.3.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/README.md +31 -0
- package/dist/module.d.mts +2 -2
- package/dist/module.json +1 -1
- package/dist/runtime/components/Integrations/CredentialCreateDialog.d.vue.ts +13 -0
- package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +279 -0
- package/dist/runtime/components/Integrations/CredentialCreateDialog.vue.d.ts +13 -0
- package/dist/runtime/components/Integrations/CredentialDetailDialog.d.vue.ts +20 -0
- package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +342 -0
- package/dist/runtime/components/Integrations/CredentialDetailDialog.vue.d.ts +20 -0
- package/dist/runtime/components/Integrations/CredentialLogo.d.vue.ts +14 -0
- package/dist/runtime/components/Integrations/CredentialLogo.vue +37 -0
- package/dist/runtime/components/Integrations/CredentialLogo.vue.d.ts +14 -0
- package/dist/runtime/components/Integrations/CredentialRevenexxSource.d.vue.ts +12 -0
- package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue +364 -0
- package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue.d.ts +12 -0
- package/dist/runtime/components/Integrations/CredentialTypePicker.d.vue.ts +15 -0
- package/dist/runtime/components/Integrations/CredentialTypePicker.vue +71 -0
- package/dist/runtime/components/Integrations/CredentialTypePicker.vue.d.ts +15 -0
- package/dist/runtime/components/Integrations/DeadLettersPanel.d.vue.ts +5 -0
- package/dist/runtime/components/Integrations/DeadLettersPanel.vue +151 -0
- package/dist/runtime/components/Integrations/DeadLettersPanel.vue.d.ts +5 -0
- package/dist/runtime/components/Integrations/EditorTip.d.vue.ts +17 -0
- package/dist/runtime/components/Integrations/EditorTip.vue +46 -0
- package/dist/runtime/components/Integrations/EditorTip.vue.d.ts +17 -0
- package/dist/runtime/components/Integrations/EventTypeCombobox.d.vue.ts +9 -0
- package/dist/runtime/components/Integrations/EventTypeCombobox.vue +64 -0
- package/dist/runtime/components/Integrations/EventTypeCombobox.vue.d.ts +9 -0
- package/dist/runtime/components/Integrations/ExpressionInput.d.vue.ts +19 -0
- package/dist/runtime/components/Integrations/ExpressionInput.vue +236 -0
- package/dist/runtime/components/Integrations/ExpressionInput.vue.d.ts +19 -0
- package/dist/runtime/components/Integrations/GroupFrameView.d.vue.ts +28 -0
- package/dist/runtime/components/Integrations/GroupFrameView.vue +222 -0
- package/dist/runtime/components/Integrations/GroupFrameView.vue.d.ts +28 -0
- package/dist/runtime/components/Integrations/NodeConfigDynamicField.d.vue.ts +14 -0
- package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue +184 -0
- package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue.d.ts +14 -0
- package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.d.vue.ts +16 -0
- package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue +102 -0
- package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue.d.ts +16 -0
- package/dist/runtime/components/Integrations/NodeConfigField.d.vue.ts +23 -0
- package/dist/runtime/components/Integrations/NodeConfigField.vue +587 -0
- package/dist/runtime/components/Integrations/NodeConfigField.vue.d.ts +23 -0
- package/dist/runtime/components/Integrations/NodeDynamicOutputsResolver.d.vue.ts +14 -0
- package/dist/runtime/components/Integrations/NodeDynamicOutputsResolver.vue +80 -0
- package/dist/runtime/components/Integrations/NodeDynamicOutputsResolver.vue.d.ts +14 -0
- package/dist/runtime/components/Integrations/NodeInspector.d.vue.ts +22 -3
- package/dist/runtime/components/Integrations/NodeInspector.vue +1074 -294
- package/dist/runtime/components/Integrations/NodeInspector.vue.d.ts +22 -3
- package/dist/runtime/components/Integrations/NodeLogo.d.vue.ts +9 -0
- package/dist/runtime/components/Integrations/NodeLogo.vue +38 -0
- package/dist/runtime/components/Integrations/NodeLogo.vue.d.ts +9 -0
- package/dist/runtime/components/Integrations/NodePalette.d.vue.ts +22 -1
- package/dist/runtime/components/Integrations/NodePalette.vue +411 -90
- package/dist/runtime/components/Integrations/NodePalette.vue.d.ts +22 -1
- package/dist/runtime/components/Integrations/QueryBuilder.d.vue.ts +9 -0
- package/dist/runtime/components/Integrations/QueryBuilder.vue +239 -0
- package/dist/runtime/components/Integrations/QueryBuilder.vue.d.ts +9 -0
- package/dist/runtime/components/Integrations/{RunDetailDialog.d.vue.ts → RunActionsMenu.d.vue.ts} +3 -4
- package/dist/runtime/components/Integrations/RunActionsMenu.vue +86 -0
- package/dist/runtime/components/Integrations/{RunDetailDialog.vue.d.ts → RunActionsMenu.vue.d.ts} +3 -4
- package/dist/runtime/components/Integrations/RunDetailPanel.d.vue.ts +14 -0
- package/dist/runtime/components/Integrations/RunDetailPanel.vue +777 -0
- package/dist/runtime/components/Integrations/RunDetailPanel.vue.d.ts +14 -0
- package/dist/runtime/components/Integrations/RunPayloadInput.d.vue.ts +13 -0
- package/dist/runtime/components/Integrations/RunPayloadInput.vue +175 -0
- package/dist/runtime/components/Integrations/RunPayloadInput.vue.d.ts +13 -0
- package/dist/runtime/components/Integrations/RunResponseModal.d.vue.ts +27 -0
- package/dist/runtime/components/Integrations/RunResponseModal.vue +84 -0
- package/dist/runtime/components/Integrations/RunResponseModal.vue.d.ts +27 -0
- package/dist/runtime/components/Integrations/RunsTable.d.vue.ts +21 -0
- package/dist/runtime/components/Integrations/RunsTable.vue +114 -0
- package/dist/runtime/components/Integrations/RunsTable.vue.d.ts +21 -0
- package/dist/runtime/components/Integrations/SftpAccountImportDialog.d.vue.ts +11 -0
- package/dist/runtime/components/Integrations/SftpAccountImportDialog.vue +91 -0
- package/dist/runtime/components/Integrations/SftpAccountImportDialog.vue.d.ts +11 -0
- package/dist/runtime/components/Integrations/StickyNoteView.d.vue.ts +28 -0
- package/dist/runtime/components/Integrations/StickyNoteView.vue +208 -0
- package/dist/runtime/components/Integrations/StickyNoteView.vue.d.ts +28 -0
- package/dist/runtime/components/Integrations/TemplateFlowPreview.d.vue.ts +29 -0
- package/dist/runtime/components/Integrations/TemplateFlowPreview.vue +201 -0
- package/dist/runtime/components/Integrations/TemplateFlowPreview.vue.d.ts +29 -0
- package/dist/runtime/components/Integrations/WorkflowContextMenu.d.vue.ts +32 -0
- package/dist/runtime/components/Integrations/WorkflowContextMenu.vue +138 -0
- package/dist/runtime/components/Integrations/WorkflowContextMenu.vue.d.ts +32 -0
- package/dist/runtime/components/Integrations/WorkflowCreateDialog.d.vue.ts +9 -0
- package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +322 -0
- package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue.d.ts +9 -0
- package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.d.vue.ts +18 -0
- package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue +63 -0
- package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue.d.ts +18 -0
- package/dist/runtime/components/Integrations/WorkflowEdgeView.d.vue.ts +12 -1
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +68 -6
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue.d.ts +12 -1
- package/dist/runtime/components/Integrations/WorkflowEditor.d.vue.ts +8 -1
- package/dist/runtime/components/Integrations/WorkflowEditor.vue +1424 -144
- package/dist/runtime/components/Integrations/WorkflowEditor.vue.d.ts +8 -1
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.d.vue.ts +12 -0
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +417 -0
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue.d.ts +12 -0
- package/dist/runtime/components/Integrations/WorkflowIconPicker.d.vue.ts +10 -0
- package/dist/runtime/components/Integrations/WorkflowIconPicker.vue +89 -0
- package/dist/runtime/components/Integrations/WorkflowIconPicker.vue.d.ts +10 -0
- package/dist/runtime/components/Integrations/WorkflowIconView.d.vue.ts +10 -0
- package/dist/runtime/components/Integrations/WorkflowIconView.vue +35 -0
- package/dist/runtime/components/Integrations/WorkflowIconView.vue.d.ts +10 -0
- package/dist/runtime/components/Integrations/WorkflowNodeView.d.vue.ts +5 -1
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue +114 -84
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue.d.ts +5 -1
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.d.vue.ts +12 -0
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +654 -0
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue.d.ts +12 -0
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.d.vue.ts +36 -0
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue +136 -0
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue.d.ts +36 -0
- package/dist/runtime/components/Integrations/WorkflowTagsInput.d.vue.ts +14 -0
- package/dist/runtime/components/Integrations/WorkflowTagsInput.vue +63 -0
- package/dist/runtime/components/Integrations/WorkflowTagsInput.vue.d.ts +14 -0
- package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +35 -15
- package/dist/runtime/components/Integrations/WorkflowVariablesDialog.d.vue.ts +13 -0
- package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +265 -0
- package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue.d.ts +13 -0
- package/dist/runtime/components/Integrations/WorkflowsTable.d.vue.ts +4 -1
- package/dist/runtime/components/Integrations/WorkflowsTable.vue +122 -39
- package/dist/runtime/components/Integrations/WorkflowsTable.vue.d.ts +4 -1
- package/dist/runtime/components/Integrations/workflowLayout.d.ts +5 -0
- package/dist/runtime/components/Integrations/workflowLayout.js +5 -3
- package/dist/runtime/composables/integrationsApi.d.ts +6 -7
- package/dist/runtime/composables/integrationsApi.js +6 -12
- package/dist/runtime/composables/useCanvasShortcuts.d.ts +37 -0
- package/dist/runtime/composables/useCanvasShortcuts.js +62 -0
- package/dist/runtime/composables/useCredentialTypes.js +2 -1
- package/dist/runtime/composables/useIntegrationsAudit.js +2 -1
- package/dist/runtime/composables/useIntegrationsCredentials.js +2 -1
- package/dist/runtime/composables/useIntegrationsDeadLetters.d.ts +22 -0
- package/dist/runtime/composables/useIntegrationsDeadLetters.js +50 -0
- package/dist/runtime/composables/useIntegrationsNodes.d.ts +4 -0
- package/dist/runtime/composables/useIntegrationsNodes.js +8 -4
- package/dist/runtime/composables/useIntegrationsRevisions.d.ts +40 -0
- package/dist/runtime/composables/useIntegrationsRevisions.js +52 -0
- package/dist/runtime/composables/useIntegrationsRuns.d.ts +21 -1
- package/dist/runtime/composables/useIntegrationsRuns.js +94 -5
- package/dist/runtime/composables/useIntegrationsSecrets.js +2 -1
- package/dist/runtime/composables/useIntegrationsTemplates.d.ts +1 -0
- package/dist/runtime/composables/useIntegrationsTemplates.js +34 -5
- package/dist/runtime/composables/useIntegrationsTriggers.js +2 -1
- package/dist/runtime/composables/useIntegrationsWorkflows.d.ts +1 -0
- package/dist/runtime/composables/useIntegrationsWorkflows.js +59 -2
- package/dist/runtime/composables/useMiniMapLayout.d.ts +34 -0
- package/dist/runtime/composables/useMiniMapLayout.js +119 -0
- package/dist/runtime/composables/useNodeConfigResolve.d.ts +43 -0
- package/dist/runtime/composables/useNodeConfigResolve.js +124 -0
- package/dist/runtime/composables/useNodeDrag.d.ts +22 -0
- package/dist/runtime/composables/useNodeDrag.js +10 -0
- package/dist/runtime/composables/usePaletteLayout.d.ts +41 -0
- package/dist/runtime/composables/usePaletteLayout.js +113 -0
- package/dist/runtime/data/expressionEditor.d.ts +22 -0
- package/dist/runtime/data/expressionEditor.js +40 -0
- package/dist/runtime/data/integrationNodeCatalog.js +8 -8
- package/dist/runtime/data/nodeInputData.d.ts +37 -0
- package/dist/runtime/data/nodeInputData.js +62 -0
- package/dist/runtime/data/nodeRunData.d.ts +56 -0
- package/dist/runtime/data/nodeRunData.js +91 -0
- package/dist/runtime/data/payloadForm.d.ts +44 -0
- package/dist/runtime/data/payloadForm.js +126 -0
- package/dist/runtime/data/workflowIconCatalog.d.ts +34 -0
- package/dist/runtime/data/workflowIconCatalog.js +116 -0
- package/dist/runtime/pages/integrations/audit.vue +25 -35
- package/dist/runtime/pages/integrations/credentials.vue +117 -122
- package/dist/runtime/pages/integrations/index.vue +133 -36
- package/dist/runtime/pages/integrations/runs.vue +125 -75
- package/dist/runtime/pages/integrations/schedules.vue +5 -16
- package/dist/runtime/pages/integrations/secrets.vue +1 -3
- package/dist/runtime/pages/integrations/workflows/[id].vue +599 -194
- package/dist/runtime/pages/integrations/workflows/index.vue +10 -8
- package/dist/runtime/types/integrations.d.ts +190 -4
- package/dist/runtime/utils/apiError.d.ts +17 -0
- package/dist/runtime/utils/apiError.js +8 -0
- package/dist/runtime/utils/credentialState.d.ts +28 -0
- package/dist/runtime/utils/credentialState.js +29 -0
- package/dist/runtime/utils/localize.d.ts +3 -1
- package/dist/runtime/utils/localize.js +1 -2
- package/dist/runtime/utils/statusStyle.d.ts +6 -0
- package/dist/runtime/utils/statusStyle.js +15 -0
- package/package.json +20 -3
- package/dist/runtime/components/Integrations/RunDetailDialog.vue +0 -169
- package/dist/runtime/pages/integrations/workflows/new.d.vue.ts +0 -2
- package/dist/runtime/pages/integrations/workflows/new.vue +0 -153
- package/dist/runtime/pages/integrations/workflows/new.vue.d.ts +0 -2
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import '@vue-flow/core/dist/style.css';
|
|
2
2
|
import '@vue-flow/core/dist/theme-default.css';
|
|
3
|
-
import '@vue-flow/controls/dist/style.css';
|
|
4
3
|
import '@vue-flow/minimap/dist/style.css';
|
|
5
4
|
import type { Workflow, WorkflowBlob } from '../../types/integrations.js';
|
|
6
5
|
type __VLS_Props = {
|
|
7
6
|
workflow: Workflow;
|
|
7
|
+
/** Busy flag for the control-pane Play button (a run is being triggered). */
|
|
8
|
+
runBusy?: boolean;
|
|
8
9
|
};
|
|
9
10
|
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
10
11
|
change: (blob: WorkflowBlob) => any;
|
|
12
|
+
run: () => any;
|
|
13
|
+
toggleActive: () => any;
|
|
14
|
+
openSettings: () => any;
|
|
11
15
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
12
16
|
onChange?: ((blob: WorkflowBlob) => any) | undefined;
|
|
17
|
+
onRun?: (() => any) | undefined;
|
|
18
|
+
onToggleActive?: (() => any) | undefined;
|
|
19
|
+
onOpenSettings?: (() => any) | undefined;
|
|
13
20
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
14
21
|
export default _default;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import '@vue-flow/core/dist/style.css';
|
|
2
|
+
import '@vue-flow/core/dist/theme-default.css';
|
|
3
|
+
import type { Workflow } from '../../types/integrations.js';
|
|
4
|
+
type __VLS_Props = {
|
|
5
|
+
workflow: Workflow;
|
|
6
|
+
};
|
|
7
|
+
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
8
|
+
restored: () => any;
|
|
9
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
10
|
+
onRestored?: (() => any) | undefined;
|
|
11
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
12
|
+
export default _default;
|
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed, markRaw, nextTick, onMounted, provide, ref, watch } from "vue";
|
|
3
|
+
import {
|
|
4
|
+
MarkerType,
|
|
5
|
+
PanOnScrollMode,
|
|
6
|
+
VueFlow,
|
|
7
|
+
useVueFlow
|
|
8
|
+
} from "@vue-flow/core";
|
|
9
|
+
import { Background } from "@vue-flow/background";
|
|
10
|
+
import { History as HistoryIcon, RefreshCircle as Loader2 } from "#solar-icons";
|
|
11
|
+
import { Badge, Button } from "@revenexx/studio";
|
|
12
|
+
import { toast } from "vue-sonner";
|
|
13
|
+
import "@vue-flow/core/dist/style.css";
|
|
14
|
+
import "@vue-flow/core/dist/theme-default.css";
|
|
15
|
+
import WorkflowNodeView from "./WorkflowNodeView.vue";
|
|
16
|
+
import WorkflowTriggerView from "./WorkflowTriggerView.vue";
|
|
17
|
+
import StickyNoteView from "./StickyNoteView.vue";
|
|
18
|
+
import GroupFrameView from "./GroupFrameView.vue";
|
|
19
|
+
import WorkflowEdgeView from "./WorkflowEdgeView.vue";
|
|
20
|
+
import NodeDynamicOutputsResolver from "./NodeDynamicOutputsResolver.vue";
|
|
21
|
+
import { findManifest } from "../../data/integrationNodeCatalog";
|
|
22
|
+
import { useIntegrationsRevisions } from "../../composables/useIntegrationsRevisions";
|
|
23
|
+
const props = defineProps({
|
|
24
|
+
workflow: { type: Object, required: true }
|
|
25
|
+
});
|
|
26
|
+
const emit = defineEmits(["restored"]);
|
|
27
|
+
const revisionsApi = useIntegrationsRevisions();
|
|
28
|
+
const entries = ref([]);
|
|
29
|
+
const page = ref(1);
|
|
30
|
+
const lastPage = ref(1);
|
|
31
|
+
const listLoading = ref(false);
|
|
32
|
+
const selected = ref(null);
|
|
33
|
+
const detailLoading = ref(false);
|
|
34
|
+
const restoring = ref(null);
|
|
35
|
+
const detailCache = /* @__PURE__ */ new Map();
|
|
36
|
+
async function loadList(reset = false) {
|
|
37
|
+
if (listLoading.value) return;
|
|
38
|
+
listLoading.value = true;
|
|
39
|
+
try {
|
|
40
|
+
const target = reset ? 1 : page.value;
|
|
41
|
+
const res = await revisionsApi.list(props.workflow.id, target);
|
|
42
|
+
entries.value = reset ? res.revisions : [...entries.value, ...res.revisions];
|
|
43
|
+
page.value = res.meta.current_page;
|
|
44
|
+
lastPage.value = res.meta.last_page;
|
|
45
|
+
} catch {
|
|
46
|
+
} finally {
|
|
47
|
+
listLoading.value = false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function loadMore() {
|
|
51
|
+
if (page.value >= lastPage.value) return;
|
|
52
|
+
page.value += 1;
|
|
53
|
+
void loadList();
|
|
54
|
+
}
|
|
55
|
+
const previewBlob = ref(props.workflow.blob);
|
|
56
|
+
const dynamicOutputs = ref({});
|
|
57
|
+
const dynamicOutputNodes = computed(() => previewBlob.value.nodes.filter((n) => {
|
|
58
|
+
const m = findManifest(n.nodeSlug, n.nodeVersion);
|
|
59
|
+
return (m?.outputs ?? []).some((o) => o.resolveOutputs === true);
|
|
60
|
+
}));
|
|
61
|
+
function onResolvedOutputs(nodeId, outputs) {
|
|
62
|
+
dynamicOutputs.value = { ...dynamicOutputs.value, [nodeId]: outputs };
|
|
63
|
+
buildPreview(previewBlob.value);
|
|
64
|
+
}
|
|
65
|
+
const previewNodes = ref([]);
|
|
66
|
+
const previewEdges = ref([]);
|
|
67
|
+
const preview = useVueFlow("workflow-history-preview");
|
|
68
|
+
provide("workflow-node-actions", { duplicate: () => {
|
|
69
|
+
}, remove: () => {
|
|
70
|
+
} });
|
|
71
|
+
function edgeColorByKind(kind) {
|
|
72
|
+
switch (kind) {
|
|
73
|
+
case "success":
|
|
74
|
+
return "#10b981";
|
|
75
|
+
case "error":
|
|
76
|
+
return "#f43f5e";
|
|
77
|
+
case "branch-true":
|
|
78
|
+
return "#10b981";
|
|
79
|
+
case "branch-false":
|
|
80
|
+
return "#f59e0b";
|
|
81
|
+
case "branch":
|
|
82
|
+
return "#8b5cf6";
|
|
83
|
+
default:
|
|
84
|
+
return "#94a3b8";
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const GROUP_PAD = 24;
|
|
88
|
+
const GROUP_LABEL_ROOM = 26;
|
|
89
|
+
function previewGroupNodes(blob, nodePos) {
|
|
90
|
+
return (blob.groups ?? []).map((g, i) => {
|
|
91
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
92
|
+
let minY = Number.POSITIVE_INFINITY;
|
|
93
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
94
|
+
let maxY = Number.NEGATIVE_INFINITY;
|
|
95
|
+
for (const id of g.nodeIds) {
|
|
96
|
+
const pos = nodePos(id);
|
|
97
|
+
minX = Math.min(minX, pos.x);
|
|
98
|
+
minY = Math.min(minY, pos.y);
|
|
99
|
+
maxX = Math.max(maxX, pos.x + 220);
|
|
100
|
+
maxY = Math.max(maxY, pos.y + 64);
|
|
101
|
+
}
|
|
102
|
+
if (!Number.isFinite(minX)) return null;
|
|
103
|
+
return {
|
|
104
|
+
id: g.id,
|
|
105
|
+
type: "group",
|
|
106
|
+
position: { x: Math.round(minX - GROUP_PAD), y: Math.round(minY - GROUP_PAD - GROUP_LABEL_ROOM) },
|
|
107
|
+
zIndex: -50 + i,
|
|
108
|
+
draggable: false,
|
|
109
|
+
selectable: false,
|
|
110
|
+
data: {
|
|
111
|
+
group: { ...g, collapsed: false },
|
|
112
|
+
rect: {
|
|
113
|
+
width: Math.round(maxX - minX + GROUP_PAD * 2),
|
|
114
|
+
height: Math.round(maxY - minY + GROUP_PAD * 2 + GROUP_LABEL_ROOM)
|
|
115
|
+
},
|
|
116
|
+
memberCount: g.nodeIds.length
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
}).filter((n) => n !== null);
|
|
120
|
+
}
|
|
121
|
+
function stickyArea(s) {
|
|
122
|
+
return (s.size?.width ?? 240) * (s.size?.height ?? 160);
|
|
123
|
+
}
|
|
124
|
+
function buildPreview(blob) {
|
|
125
|
+
const triggerPos = (id, i) => blob.triggerPositions?.[id] ?? { x: -40, y: 60 + i * 140 };
|
|
126
|
+
const nodePos = (id) => {
|
|
127
|
+
const wn = blob.nodes.find((n) => n.id === id);
|
|
128
|
+
if (wn) return { x: wn.position?.x ?? 0, y: wn.position?.y ?? 0 };
|
|
129
|
+
const ti = blob.triggers.findIndex((t) => t.id === id);
|
|
130
|
+
if (ti >= 0) return triggerPos(id, ti);
|
|
131
|
+
return { x: 0, y: 0 };
|
|
132
|
+
};
|
|
133
|
+
const pickSourceSide = (_sourceId, _targetId) => "bottom";
|
|
134
|
+
const opposite = (s) => s === "top" ? "bottom" : s === "right" ? "left" : s === "bottom" ? "top" : "right";
|
|
135
|
+
const edgeKind = (sourceNodeId, output) => {
|
|
136
|
+
const wn = blob.nodes.find((n) => n.id === sourceNodeId);
|
|
137
|
+
if (!wn) return "default";
|
|
138
|
+
const manifest = findManifest(wn.nodeSlug, wn.nodeVersion);
|
|
139
|
+
if (!manifest) return "default";
|
|
140
|
+
if (output === "true") return "branch-true";
|
|
141
|
+
if (output === "false") return "branch-false";
|
|
142
|
+
if (output === "error") return "error";
|
|
143
|
+
const outs = [...manifest.outputs ?? [], ...dynamicOutputs.value[sourceNodeId] ?? []];
|
|
144
|
+
const portDef = outs.find((o) => o.name === output);
|
|
145
|
+
if (portDef) {
|
|
146
|
+
if (portDef.kind === "error") return "error";
|
|
147
|
+
if (portDef.kind === "branch") return "branch";
|
|
148
|
+
return "success";
|
|
149
|
+
}
|
|
150
|
+
for (const o of manifest.outputs ?? []) {
|
|
151
|
+
if (o.sourceFromConfig) {
|
|
152
|
+
if (o.kind === "error") return "error";
|
|
153
|
+
if (o.kind === "branch") return "branch";
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return "default";
|
|
157
|
+
};
|
|
158
|
+
const stickies = [...blob.stickies ?? []].sort((a, b) => stickyArea(b) - stickyArea(a));
|
|
159
|
+
previewNodes.value = [
|
|
160
|
+
...previewGroupNodes(blob, nodePos),
|
|
161
|
+
...stickies.map((s, i) => ({
|
|
162
|
+
id: s.id,
|
|
163
|
+
type: "sticky",
|
|
164
|
+
position: { x: s.position.x, y: s.position.y },
|
|
165
|
+
zIndex: -100 + i,
|
|
166
|
+
draggable: false,
|
|
167
|
+
data: { sticky: s, onUpdate: () => {
|
|
168
|
+
}, onRemove: () => {
|
|
169
|
+
} }
|
|
170
|
+
})),
|
|
171
|
+
...blob.triggers.map((t, i) => ({
|
|
172
|
+
id: t.id,
|
|
173
|
+
type: "trigger",
|
|
174
|
+
position: triggerPos(t.id, i),
|
|
175
|
+
draggable: false,
|
|
176
|
+
data: t
|
|
177
|
+
})),
|
|
178
|
+
...blob.nodes.map((n) => ({
|
|
179
|
+
id: n.id,
|
|
180
|
+
type: "workflow",
|
|
181
|
+
position: n.position ?? { x: 240, y: 80 },
|
|
182
|
+
draggable: false,
|
|
183
|
+
data: { ...n, __resolvedOutputs: dynamicOutputs.value[n.id] }
|
|
184
|
+
}))
|
|
185
|
+
];
|
|
186
|
+
previewEdges.value = blob.edges.map((e) => {
|
|
187
|
+
const kind = edgeKind(e.from.nodeId, e.from.output);
|
|
188
|
+
const wn = blob.nodes.find((n) => n.id === e.from.nodeId);
|
|
189
|
+
const manifest = wn ? findManifest(wn.nodeSlug, wn.nodeVersion) : null;
|
|
190
|
+
const isBranchPort = (manifest?.outputs ?? []).some((o) => o.sourceFromConfig) || ["true", "false", "error"].includes(e.from.output);
|
|
191
|
+
const srcSide = pickSourceSide(e.from.nodeId, e.to.nodeId);
|
|
192
|
+
return {
|
|
193
|
+
id: e.id,
|
|
194
|
+
source: e.from.nodeId,
|
|
195
|
+
target: e.to.nodeId,
|
|
196
|
+
sourceHandle: `${e.from.output}__${srcSide}`,
|
|
197
|
+
targetHandle: `__in__${opposite(srcSide)}`,
|
|
198
|
+
type: "flow",
|
|
199
|
+
selectable: false,
|
|
200
|
+
data: { kind, label: isBranchPort ? e.from.output : void 0 },
|
|
201
|
+
markerEnd: {
|
|
202
|
+
type: MarkerType.ArrowClosed,
|
|
203
|
+
width: 16,
|
|
204
|
+
height: 16,
|
|
205
|
+
color: edgeColorByKind(kind)
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
});
|
|
209
|
+
void nextTick(() => {
|
|
210
|
+
preview.updateNodeInternals(previewNodes.value.map((n) => n.id));
|
|
211
|
+
void preview.fitView({ padding: 0.2, duration: 250, maxZoom: 1 });
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
async function select(revision) {
|
|
215
|
+
selected.value = revision;
|
|
216
|
+
if (revision === null) {
|
|
217
|
+
previewBlob.value = props.workflow.blob;
|
|
218
|
+
buildPreview(props.workflow.blob);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
const cached = detailCache.get(revision);
|
|
222
|
+
if (cached) {
|
|
223
|
+
previewBlob.value = cached;
|
|
224
|
+
buildPreview(cached);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
detailLoading.value = true;
|
|
228
|
+
try {
|
|
229
|
+
const detail = await revisionsApi.get(props.workflow.id, revision);
|
|
230
|
+
detailCache.set(revision, detail.blob);
|
|
231
|
+
if (selected.value === revision) {
|
|
232
|
+
previewBlob.value = detail.blob;
|
|
233
|
+
buildPreview(detail.blob);
|
|
234
|
+
}
|
|
235
|
+
} catch (e) {
|
|
236
|
+
toast.error(e?.message ?? "Could not load the revision.");
|
|
237
|
+
} finally {
|
|
238
|
+
detailLoading.value = false;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
async function restoreRevision(rev) {
|
|
242
|
+
if (!confirm(`Restore revision ${rev.revision}? The current version is archived first, then the workflow is rebuilt from this snapshot.`)) return;
|
|
243
|
+
restoring.value = rev.revision;
|
|
244
|
+
try {
|
|
245
|
+
await revisionsApi.restore(props.workflow.id, rev.revision);
|
|
246
|
+
toast.success(`Revision ${rev.revision} restored`);
|
|
247
|
+
detailCache.clear();
|
|
248
|
+
emit("restored");
|
|
249
|
+
await loadList(true);
|
|
250
|
+
void select(null);
|
|
251
|
+
} catch (e) {
|
|
252
|
+
toast.error(e?.message ?? "Could not restore the revision.");
|
|
253
|
+
} finally {
|
|
254
|
+
restoring.value = null;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
watch(() => props.workflow.revision, () => {
|
|
258
|
+
if (selected.value === null) buildPreview(props.workflow.blob);
|
|
259
|
+
});
|
|
260
|
+
function formatDateTime(iso) {
|
|
261
|
+
if (!iso) return "\u2014";
|
|
262
|
+
try {
|
|
263
|
+
return new Date(iso).toLocaleString();
|
|
264
|
+
} catch {
|
|
265
|
+
return String(iso);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
const nodeTypes = {
|
|
269
|
+
workflow: markRaw(WorkflowNodeView),
|
|
270
|
+
trigger: markRaw(WorkflowTriggerView),
|
|
271
|
+
sticky: markRaw(StickyNoteView),
|
|
272
|
+
group: markRaw(GroupFrameView)
|
|
273
|
+
};
|
|
274
|
+
const edgeTypes = {
|
|
275
|
+
flow: markRaw(WorkflowEdgeView)
|
|
276
|
+
};
|
|
277
|
+
const previewTitle = computed(() => selected.value === null ? `Current version \xB7 revision ${props.workflow.revision}` : `Revision ${selected.value} (read-only)`);
|
|
278
|
+
onMounted(() => {
|
|
279
|
+
void loadList(true);
|
|
280
|
+
void select(null);
|
|
281
|
+
});
|
|
282
|
+
</script>
|
|
283
|
+
|
|
284
|
+
<template>
|
|
285
|
+
<div class="flex h-full min-h-0 flex-1">
|
|
286
|
+
<!-- Read-only canvas preview -->
|
|
287
|
+
<div class="relative flex-1 min-w-0 bg-[#f7f6fb]">
|
|
288
|
+
<VueFlow
|
|
289
|
+
id="workflow-history-preview"
|
|
290
|
+
v-model:nodes="previewNodes"
|
|
291
|
+
v-model:edges="previewEdges"
|
|
292
|
+
:node-types="nodeTypes"
|
|
293
|
+
:edge-types="edgeTypes"
|
|
294
|
+
:default-edge-options="{ type: 'flow' }"
|
|
295
|
+
:nodes-draggable="false"
|
|
296
|
+
:nodes-connectable="false"
|
|
297
|
+
:elements-selectable="false"
|
|
298
|
+
:edges-updatable="false"
|
|
299
|
+
:pan-on-scroll-mode="PanOnScrollMode.Free"
|
|
300
|
+
:delete-key-code="null"
|
|
301
|
+
:min-zoom="0.25"
|
|
302
|
+
:max-zoom="2"
|
|
303
|
+
>
|
|
304
|
+
<Background pattern-color="#cbd5e1" :gap="20" :size="1.2" />
|
|
305
|
+
</VueFlow>
|
|
306
|
+
|
|
307
|
+
<!-- Headless dynamic-output resolvers — render nothing. -->
|
|
308
|
+
<NodeDynamicOutputsResolver
|
|
309
|
+
v-for="n in dynamicOutputNodes"
|
|
310
|
+
:key="`dyn-out-${n.id}`"
|
|
311
|
+
:node-id="n.id"
|
|
312
|
+
:slug="n.nodeSlug"
|
|
313
|
+
:version="n.nodeVersion"
|
|
314
|
+
:config="n.config ?? {}"
|
|
315
|
+
:static-outputs="findManifest(n.nodeSlug, n.nodeVersion)?.outputs ?? []"
|
|
316
|
+
@resolved="onResolvedOutputs"
|
|
317
|
+
/>
|
|
318
|
+
|
|
319
|
+
<!-- Which version is on the canvas -->
|
|
320
|
+
<div class="absolute top-3 left-3 z-10 flex items-center gap-2 rounded-lg border border-black/[0.08] bg-white/95 px-2.5 py-1.5 text-sm shadow-sm backdrop-blur-sm">
|
|
321
|
+
<HistoryIcon :size="13" class="text-muted-foreground" />
|
|
322
|
+
<span class="font-medium">{{ previewTitle }}</span>
|
|
323
|
+
<Loader2 v-if="detailLoading" :size="13" class="animate-spin text-muted-foreground" />
|
|
324
|
+
</div>
|
|
325
|
+
</div>
|
|
326
|
+
|
|
327
|
+
<!-- Revision list -->
|
|
328
|
+
<aside class="flex w-80 shrink-0 flex-col border-l border-black/[0.06] bg-white">
|
|
329
|
+
<div class="border-b border-black/[0.06] px-3 py-2">
|
|
330
|
+
<div class="text-sm font-semibold">Version history</div>
|
|
331
|
+
<p class="text-sm text-muted-foreground">
|
|
332
|
+
Every save archives the previous revision; the newest one is what runs while the workflow is active.
|
|
333
|
+
</p>
|
|
334
|
+
</div>
|
|
335
|
+
|
|
336
|
+
<div class="flex-1 overflow-y-auto p-2 space-y-1">
|
|
337
|
+
<!-- Current (live) version -->
|
|
338
|
+
<button
|
|
339
|
+
type="button"
|
|
340
|
+
class="group w-full rounded-lg border p-2.5 text-left transition"
|
|
341
|
+
:class="selected === null ? 'border-primary/40 bg-primary/[0.04]' : 'border-transparent hover:border-black/[0.08] hover:bg-muted/30'"
|
|
342
|
+
@click="select(null)"
|
|
343
|
+
>
|
|
344
|
+
<div class="flex items-center justify-between gap-2">
|
|
345
|
+
<span class="text-sm font-medium tabular-nums">{{ formatDateTime(workflow.updated_at) }}</span>
|
|
346
|
+
<Badge variant="outline" class="text-xs font-medium uppercase border-emerald-500/30 text-emerald-700">Current</Badge>
|
|
347
|
+
<Badge
|
|
348
|
+
variant="outline"
|
|
349
|
+
class="text-xs font-medium uppercase"
|
|
350
|
+
:class="workflow.active ? 'border-emerald-500/30 bg-emerald-500/10 text-emerald-700' : 'border-black/[0.08] text-muted-foreground'"
|
|
351
|
+
:title="workflow.active ? 'Triggers are armed; this revision is what runs.' : 'Workflow is paused; activating runs this revision.'"
|
|
352
|
+
>
|
|
353
|
+
{{ workflow.active ? "Active" : "Paused" }}
|
|
354
|
+
</Badge>
|
|
355
|
+
</div>
|
|
356
|
+
<div class="mt-0.5 text-sm text-muted-foreground">Revision {{ workflow.revision }}</div>
|
|
357
|
+
</button>
|
|
358
|
+
|
|
359
|
+
<!-- Archived revisions -->
|
|
360
|
+
<div
|
|
361
|
+
v-for="rev in entries"
|
|
362
|
+
:key="rev.id"
|
|
363
|
+
class="group relative w-full rounded-lg border transition"
|
|
364
|
+
:class="selected === rev.revision ? 'border-primary/40 bg-primary/[0.04]' : 'border-transparent hover:border-black/[0.08] hover:bg-muted/30'"
|
|
365
|
+
>
|
|
366
|
+
<button type="button" class="w-full p-2.5 text-left" @click="select(rev.revision)">
|
|
367
|
+
<div class="flex items-center justify-between gap-2">
|
|
368
|
+
<span class="text-sm font-medium tabular-nums">{{ formatDateTime(rev.archived_at) }}</span>
|
|
369
|
+
</div>
|
|
370
|
+
<div class="mt-0.5 text-sm text-muted-foreground">Revision {{ rev.revision }}</div>
|
|
371
|
+
</button>
|
|
372
|
+
<div class="absolute right-2 top-1/2 -translate-y-1/2 opacity-0 transition group-hover:opacity-100">
|
|
373
|
+
<Button
|
|
374
|
+
variant="outline"
|
|
375
|
+
size="sm"
|
|
376
|
+
class="h-6 bg-white text-sm"
|
|
377
|
+
:disabled="restoring !== null"
|
|
378
|
+
@click.stop="restoreRevision(rev)"
|
|
379
|
+
>
|
|
380
|
+
<Loader2 v-if="restoring === rev.revision" :size="11" class="mr-1 animate-spin" />
|
|
381
|
+
Restore
|
|
382
|
+
</Button>
|
|
383
|
+
</div>
|
|
384
|
+
</div>
|
|
385
|
+
|
|
386
|
+
<p v-if="!entries.length && !listLoading" class="px-2 py-6 text-center text-sm text-muted-foreground">
|
|
387
|
+
No archived revisions yet — save a change to create the first snapshot.
|
|
388
|
+
</p>
|
|
389
|
+
<p v-if="revisionsApi.error.value" class="px-2 py-2 text-sm text-rose-600">
|
|
390
|
+
{{ revisionsApi.error.value }}
|
|
391
|
+
</p>
|
|
392
|
+
|
|
393
|
+
<div v-if="listLoading && !entries.length" class="space-y-2 px-2 py-2">
|
|
394
|
+
<div v-for="n in 5" :key="`sk-rev-${n}`" class="flex items-center gap-3 py-1.5">
|
|
395
|
+
<div class="size-7 shrink-0 rounded-md bg-black/[0.07] animate-pulse" />
|
|
396
|
+
<div class="min-w-0 flex-1 space-y-1.5">
|
|
397
|
+
<div class="h-3 w-1/3 rounded bg-black/[0.07] animate-pulse" />
|
|
398
|
+
<div class="h-3 w-1/2 rounded bg-black/[0.05] animate-pulse" />
|
|
399
|
+
</div>
|
|
400
|
+
</div>
|
|
401
|
+
</div>
|
|
402
|
+
<div v-else-if="listLoading" class="flex justify-center py-3">
|
|
403
|
+
<Loader2 :size="15" class="animate-spin text-muted-foreground" />
|
|
404
|
+
</div>
|
|
405
|
+
<Button
|
|
406
|
+
v-else-if="page < lastPage"
|
|
407
|
+
variant="ghost"
|
|
408
|
+
size="sm"
|
|
409
|
+
class="w-full text-sm"
|
|
410
|
+
@click="loadMore"
|
|
411
|
+
>
|
|
412
|
+
Load older revisions
|
|
413
|
+
</Button>
|
|
414
|
+
</div>
|
|
415
|
+
</aside>
|
|
416
|
+
</div>
|
|
417
|
+
</template>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import '@vue-flow/core/dist/style.css';
|
|
2
|
+
import '@vue-flow/core/dist/theme-default.css';
|
|
3
|
+
import type { Workflow } from '../../types/integrations.js';
|
|
4
|
+
type __VLS_Props = {
|
|
5
|
+
workflow: Workflow;
|
|
6
|
+
};
|
|
7
|
+
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
8
|
+
restored: () => any;
|
|
9
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
10
|
+
onRestored?: (() => any) | undefined;
|
|
11
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
12
|
+
export default _default;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { WorkflowIcon } from '../../types/integrations.js';
|
|
2
|
+
type __VLS_PublicProps = {
|
|
3
|
+
modelValue?: WorkflowIcon | null;
|
|
4
|
+
};
|
|
5
|
+
declare const _default: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
6
|
+
"update:modelValue": (value: WorkflowIcon | null) => any;
|
|
7
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
8
|
+
"onUpdate:modelValue"?: ((value: WorkflowIcon | null) => any) | undefined;
|
|
9
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
10
|
+
export default _default;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed, ref } from "vue";
|
|
3
|
+
import { AltArrowDown as ChevronDown } from "#solar-icons";
|
|
4
|
+
import { Popover, PopoverContent, PopoverTrigger } from "@revenexx/studio";
|
|
5
|
+
import { WORKFLOW_SOLAR_ICONS, imageIdFromSrc, nodeLogoOptions } from "../../data/workflowIconCatalog";
|
|
6
|
+
import WorkflowIconView from "./WorkflowIconView.vue";
|
|
7
|
+
const model = defineModel({ type: [Object, null], ...{ default: null } });
|
|
8
|
+
const open = ref(false);
|
|
9
|
+
const logos = computed(() => nodeLogoOptions());
|
|
10
|
+
function pick(icon) {
|
|
11
|
+
model.value = icon;
|
|
12
|
+
open.value = false;
|
|
13
|
+
}
|
|
14
|
+
function isSolar(name) {
|
|
15
|
+
return model.value?.kind === "solar" && model.value.name === name;
|
|
16
|
+
}
|
|
17
|
+
function isImage(id) {
|
|
18
|
+
if (model.value?.kind !== "image") return false;
|
|
19
|
+
return (imageIdFromSrc(model.value.src) ?? model.value.src) === id;
|
|
20
|
+
}
|
|
21
|
+
const selectedLabel = computed(() => {
|
|
22
|
+
if (!model.value) return "Default icon";
|
|
23
|
+
if (model.value.kind === "solar") return model.value.name ?? "Icon";
|
|
24
|
+
const id = imageIdFromSrc(model.value.src) ?? model.value.src;
|
|
25
|
+
return logos.value.find((l) => l.id === id)?.label ?? "Image";
|
|
26
|
+
});
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<template>
|
|
30
|
+
<Popover v-model:open="open">
|
|
31
|
+
<PopoverTrigger as-child>
|
|
32
|
+
<button
|
|
33
|
+
type="button"
|
|
34
|
+
class="flex h-9 w-full items-center gap-2 rounded-md border border-black/[0.08] bg-white px-2.5 text-left text-sm transition hover:border-black/20"
|
|
35
|
+
>
|
|
36
|
+
<span class="flex size-6 shrink-0 items-center justify-center rounded-md bg-slate-100 text-slate-500">
|
|
37
|
+
<WorkflowIconView :icon="model" :size="14" />
|
|
38
|
+
</span>
|
|
39
|
+
<span class="min-w-0 flex-1 truncate" :class="model ? '' : 'text-muted-foreground'">
|
|
40
|
+
{{ selectedLabel }}
|
|
41
|
+
</span>
|
|
42
|
+
<ChevronDown :size="14" class="shrink-0 text-muted-foreground" />
|
|
43
|
+
</button>
|
|
44
|
+
</PopoverTrigger>
|
|
45
|
+
<PopoverContent align="start" class="w-80 rounded-lg border border-black/[0.06] bg-white p-2 shadow-lg">
|
|
46
|
+
<div class="max-h-64 space-y-2 overflow-y-auto pr-1">
|
|
47
|
+
<div class="flex flex-wrap gap-1">
|
|
48
|
+
<button
|
|
49
|
+
type="button"
|
|
50
|
+
class="flex size-8 items-center justify-center rounded-md border text-sm text-muted-foreground transition hover:bg-muted/50"
|
|
51
|
+
:class="!model ? 'border-emerald-500/40 bg-emerald-500/10' : 'border-transparent'"
|
|
52
|
+
title="No icon (default glyph)"
|
|
53
|
+
@click="pick(null)"
|
|
54
|
+
>
|
|
55
|
+
—
|
|
56
|
+
</button>
|
|
57
|
+
<button
|
|
58
|
+
v-for="(comp, name) in WORKFLOW_SOLAR_ICONS"
|
|
59
|
+
:key="name"
|
|
60
|
+
type="button"
|
|
61
|
+
class="flex size-8 items-center justify-center rounded-md border text-slate-600 transition hover:bg-muted/50"
|
|
62
|
+
:class="isSolar(String(name)) ? 'border-emerald-500/40 bg-emerald-500/10' : 'border-transparent'"
|
|
63
|
+
:title="String(name)"
|
|
64
|
+
@click="pick({ kind: 'solar', name: String(name) })"
|
|
65
|
+
>
|
|
66
|
+
<component :is="comp" :size="16" />
|
|
67
|
+
</button>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<template v-if="logos.length">
|
|
71
|
+
<div class="text-mono text-muted-foreground">Integrations</div>
|
|
72
|
+
<div class="flex flex-wrap gap-1">
|
|
73
|
+
<button
|
|
74
|
+
v-for="l in logos"
|
|
75
|
+
:key="l.id"
|
|
76
|
+
type="button"
|
|
77
|
+
class="flex size-8 items-center justify-center rounded-md border transition hover:bg-muted/50"
|
|
78
|
+
:class="isImage(l.id) ? 'border-emerald-500/40 bg-emerald-500/10' : 'border-black/[0.06] bg-white'"
|
|
79
|
+
:title="l.label"
|
|
80
|
+
@click="pick({ kind: 'image', src: l.id })"
|
|
81
|
+
>
|
|
82
|
+
<img :src="l.src" class="size-4 object-contain" alt="">
|
|
83
|
+
</button>
|
|
84
|
+
</div>
|
|
85
|
+
</template>
|
|
86
|
+
</div>
|
|
87
|
+
</PopoverContent>
|
|
88
|
+
</Popover>
|
|
89
|
+
</template>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { WorkflowIcon } from '../../types/integrations.js';
|
|
2
|
+
type __VLS_PublicProps = {
|
|
3
|
+
modelValue?: WorkflowIcon | null;
|
|
4
|
+
};
|
|
5
|
+
declare const _default: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
6
|
+
"update:modelValue": (value: WorkflowIcon | null) => any;
|
|
7
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
8
|
+
"onUpdate:modelValue"?: ((value: WorkflowIcon | null) => any) | undefined;
|
|
9
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
10
|
+
export default _default;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { WorkflowIcon } from '../../types/integrations.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
icon?: WorkflowIcon | null;
|
|
4
|
+
size?: number;
|
|
5
|
+
};
|
|
6
|
+
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
7
|
+
icon: WorkflowIcon | null;
|
|
8
|
+
size: number;
|
|
9
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
10
|
+
export default _default;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed, onMounted } from "vue";
|
|
3
|
+
import { freshImageSrc, imageIdFromSrc, solarIconFor } from "../../data/workflowIconCatalog";
|
|
4
|
+
import { useIntegrationsNodes } from "../../composables/useIntegrationsNodes";
|
|
5
|
+
const props = defineProps({
|
|
6
|
+
icon: { type: [Object, null], required: false, default: null },
|
|
7
|
+
size: { type: Number, required: false, default: 14 }
|
|
8
|
+
});
|
|
9
|
+
const { load } = useIntegrationsNodes();
|
|
10
|
+
onMounted(() => {
|
|
11
|
+
if (props.icon?.kind === "image") void load();
|
|
12
|
+
});
|
|
13
|
+
const resolvedSrc = computed(() => {
|
|
14
|
+
if (props.icon?.kind !== "image" || !props.icon.src) return null;
|
|
15
|
+
const fresh = freshImageSrc(props.icon.src);
|
|
16
|
+
if (fresh) return fresh;
|
|
17
|
+
if (imageIdFromSrc(props.icon.src)) return null;
|
|
18
|
+
return /^https?:\/\//.test(props.icon.src) ? props.icon.src : null;
|
|
19
|
+
});
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<template>
|
|
23
|
+
<img
|
|
24
|
+
v-if="resolvedSrc"
|
|
25
|
+
:src="resolvedSrc"
|
|
26
|
+
:style="{ width: `${props.size}px`, height: `${props.size}px` }"
|
|
27
|
+
class="object-contain"
|
|
28
|
+
alt=""
|
|
29
|
+
>
|
|
30
|
+
<component
|
|
31
|
+
:is="solarIconFor(props.icon?.kind === 'solar' ? props.icon.name : void 0)"
|
|
32
|
+
v-else
|
|
33
|
+
:size="props.size"
|
|
34
|
+
/>
|
|
35
|
+
</template>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { WorkflowIcon } from '../../types/integrations.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
icon?: WorkflowIcon | null;
|
|
4
|
+
size?: number;
|
|
5
|
+
};
|
|
6
|
+
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
7
|
+
icon: WorkflowIcon | null;
|
|
8
|
+
size: number;
|
|
9
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
10
|
+
export default _default;
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import type { WorkflowNode } from '../../types/integrations.js';
|
|
1
|
+
import type { NodeManifestOutput, WorkflowNode } from '../../types/integrations.js';
|
|
2
2
|
interface NodeData extends WorkflowNode {
|
|
3
3
|
__selected?: boolean;
|
|
4
|
+
/** Server-resolved + merged output ports (PO-143 Phase 3), injected by the
|
|
5
|
+
* editor without persisting to the blob. When present and non-empty, these
|
|
6
|
+
* drive the rendered handles instead of the static manifest computation. */
|
|
7
|
+
__resolvedOutputs?: NodeManifestOutput[];
|
|
4
8
|
}
|
|
5
9
|
type __VLS_Props = {
|
|
6
10
|
id: string;
|