@revenexx/studio-integrations 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/components/Integrations/CredentialCreateDialog.d.vue.ts +13 -0
  3. package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +279 -0
  4. package/dist/runtime/components/Integrations/CredentialCreateDialog.vue.d.ts +13 -0
  5. package/dist/runtime/components/Integrations/CredentialDetailDialog.d.vue.ts +20 -0
  6. package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +342 -0
  7. package/dist/runtime/components/Integrations/CredentialDetailDialog.vue.d.ts +20 -0
  8. package/dist/runtime/components/Integrations/CredentialLogo.d.vue.ts +14 -0
  9. package/dist/runtime/components/Integrations/CredentialLogo.vue +37 -0
  10. package/dist/runtime/components/Integrations/CredentialLogo.vue.d.ts +14 -0
  11. package/dist/runtime/components/Integrations/CredentialRevenexxSource.d.vue.ts +12 -0
  12. package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue +364 -0
  13. package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue.d.ts +12 -0
  14. package/dist/runtime/components/Integrations/CredentialTypePicker.d.vue.ts +15 -0
  15. package/dist/runtime/components/Integrations/CredentialTypePicker.vue +71 -0
  16. package/dist/runtime/components/Integrations/CredentialTypePicker.vue.d.ts +15 -0
  17. package/dist/runtime/components/Integrations/DeadLettersPanel.d.vue.ts +5 -0
  18. package/dist/runtime/components/Integrations/DeadLettersPanel.vue +151 -0
  19. package/dist/runtime/components/Integrations/DeadLettersPanel.vue.d.ts +5 -0
  20. package/dist/runtime/components/Integrations/EditorTip.d.vue.ts +17 -0
  21. package/dist/runtime/components/Integrations/EditorTip.vue +46 -0
  22. package/dist/runtime/components/Integrations/EditorTip.vue.d.ts +17 -0
  23. package/dist/runtime/components/Integrations/EventTypeCombobox.d.vue.ts +9 -0
  24. package/dist/runtime/components/Integrations/EventTypeCombobox.vue +64 -0
  25. package/dist/runtime/components/Integrations/EventTypeCombobox.vue.d.ts +9 -0
  26. package/dist/runtime/components/Integrations/ExpressionInput.d.vue.ts +19 -0
  27. package/dist/runtime/components/Integrations/ExpressionInput.vue +236 -0
  28. package/dist/runtime/components/Integrations/ExpressionInput.vue.d.ts +19 -0
  29. package/dist/runtime/components/Integrations/GroupFrameView.d.vue.ts +28 -0
  30. package/dist/runtime/components/Integrations/GroupFrameView.vue +222 -0
  31. package/dist/runtime/components/Integrations/GroupFrameView.vue.d.ts +28 -0
  32. package/dist/runtime/components/Integrations/NodeConfigDynamicField.d.vue.ts +14 -0
  33. package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue +184 -0
  34. package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue.d.ts +14 -0
  35. package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.d.vue.ts +16 -0
  36. package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue +102 -0
  37. package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue.d.ts +16 -0
  38. package/dist/runtime/components/Integrations/NodeConfigField.d.vue.ts +23 -0
  39. package/dist/runtime/components/Integrations/NodeConfigField.vue +587 -0
  40. package/dist/runtime/components/Integrations/NodeConfigField.vue.d.ts +23 -0
  41. package/dist/runtime/components/Integrations/NodeDynamicOutputsResolver.d.vue.ts +14 -0
  42. package/dist/runtime/components/Integrations/NodeDynamicOutputsResolver.vue +80 -0
  43. package/dist/runtime/components/Integrations/NodeDynamicOutputsResolver.vue.d.ts +14 -0
  44. package/dist/runtime/components/Integrations/NodeInspector.d.vue.ts +22 -3
  45. package/dist/runtime/components/Integrations/NodeInspector.vue +1074 -294
  46. package/dist/runtime/components/Integrations/NodeInspector.vue.d.ts +22 -3
  47. package/dist/runtime/components/Integrations/NodeLogo.d.vue.ts +9 -0
  48. package/dist/runtime/components/Integrations/NodeLogo.vue +38 -0
  49. package/dist/runtime/components/Integrations/NodeLogo.vue.d.ts +9 -0
  50. package/dist/runtime/components/Integrations/NodePalette.d.vue.ts +22 -1
  51. package/dist/runtime/components/Integrations/NodePalette.vue +411 -90
  52. package/dist/runtime/components/Integrations/NodePalette.vue.d.ts +22 -1
  53. package/dist/runtime/components/Integrations/QueryBuilder.d.vue.ts +9 -0
  54. package/dist/runtime/components/Integrations/QueryBuilder.vue +239 -0
  55. package/dist/runtime/components/Integrations/QueryBuilder.vue.d.ts +9 -0
  56. package/dist/runtime/components/Integrations/{RunDetailDialog.d.vue.ts → RunActionsMenu.d.vue.ts} +3 -4
  57. package/dist/runtime/components/Integrations/RunActionsMenu.vue +86 -0
  58. package/dist/runtime/components/Integrations/{RunDetailDialog.vue.d.ts → RunActionsMenu.vue.d.ts} +3 -4
  59. package/dist/runtime/components/Integrations/RunDetailPanel.d.vue.ts +14 -0
  60. package/dist/runtime/components/Integrations/RunDetailPanel.vue +777 -0
  61. package/dist/runtime/components/Integrations/RunDetailPanel.vue.d.ts +14 -0
  62. package/dist/runtime/components/Integrations/RunPayloadInput.d.vue.ts +13 -0
  63. package/dist/runtime/components/Integrations/RunPayloadInput.vue +175 -0
  64. package/dist/runtime/components/Integrations/RunPayloadInput.vue.d.ts +13 -0
  65. package/dist/runtime/components/Integrations/RunResponseModal.d.vue.ts +27 -0
  66. package/dist/runtime/components/Integrations/RunResponseModal.vue +84 -0
  67. package/dist/runtime/components/Integrations/RunResponseModal.vue.d.ts +27 -0
  68. package/dist/runtime/components/Integrations/RunsTable.d.vue.ts +21 -0
  69. package/dist/runtime/components/Integrations/RunsTable.vue +114 -0
  70. package/dist/runtime/components/Integrations/RunsTable.vue.d.ts +21 -0
  71. package/dist/runtime/components/Integrations/SftpAccountImportDialog.d.vue.ts +11 -0
  72. package/dist/runtime/components/Integrations/SftpAccountImportDialog.vue +91 -0
  73. package/dist/runtime/components/Integrations/SftpAccountImportDialog.vue.d.ts +11 -0
  74. package/dist/runtime/components/Integrations/Sidebar.vue +1 -0
  75. package/dist/runtime/components/Integrations/StickyNoteView.d.vue.ts +28 -0
  76. package/dist/runtime/components/Integrations/StickyNoteView.vue +208 -0
  77. package/dist/runtime/components/Integrations/StickyNoteView.vue.d.ts +28 -0
  78. package/dist/runtime/components/Integrations/TemplateFlowPreview.d.vue.ts +29 -0
  79. package/dist/runtime/components/Integrations/TemplateFlowPreview.vue +201 -0
  80. package/dist/runtime/components/Integrations/TemplateFlowPreview.vue.d.ts +29 -0
  81. package/dist/runtime/components/Integrations/WorkflowContextMenu.d.vue.ts +32 -0
  82. package/dist/runtime/components/Integrations/WorkflowContextMenu.vue +138 -0
  83. package/dist/runtime/components/Integrations/WorkflowContextMenu.vue.d.ts +32 -0
  84. package/dist/runtime/components/Integrations/WorkflowCreateDialog.d.vue.ts +9 -0
  85. package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +322 -0
  86. package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue.d.ts +9 -0
  87. package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.d.vue.ts +18 -0
  88. package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue +63 -0
  89. package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue.d.ts +18 -0
  90. package/dist/runtime/components/Integrations/WorkflowEdgeView.d.vue.ts +12 -1
  91. package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +68 -6
  92. package/dist/runtime/components/Integrations/WorkflowEdgeView.vue.d.ts +12 -1
  93. package/dist/runtime/components/Integrations/WorkflowEditor.d.vue.ts +8 -1
  94. package/dist/runtime/components/Integrations/WorkflowEditor.vue +1424 -144
  95. package/dist/runtime/components/Integrations/WorkflowEditor.vue.d.ts +8 -1
  96. package/dist/runtime/components/Integrations/WorkflowHistoryPanel.d.vue.ts +12 -0
  97. package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +417 -0
  98. package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue.d.ts +12 -0
  99. package/dist/runtime/components/Integrations/WorkflowIconPicker.d.vue.ts +10 -0
  100. package/dist/runtime/components/Integrations/WorkflowIconPicker.vue +89 -0
  101. package/dist/runtime/components/Integrations/WorkflowIconPicker.vue.d.ts +10 -0
  102. package/dist/runtime/components/Integrations/WorkflowIconView.d.vue.ts +10 -0
  103. package/dist/runtime/components/Integrations/WorkflowIconView.vue +35 -0
  104. package/dist/runtime/components/Integrations/WorkflowIconView.vue.d.ts +10 -0
  105. package/dist/runtime/components/Integrations/WorkflowNodeView.d.vue.ts +5 -1
  106. package/dist/runtime/components/Integrations/WorkflowNodeView.vue +114 -84
  107. package/dist/runtime/components/Integrations/WorkflowNodeView.vue.d.ts +5 -1
  108. package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.d.vue.ts +12 -0
  109. package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +654 -0
  110. package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue.d.ts +12 -0
  111. package/dist/runtime/components/Integrations/WorkflowSettingsDialog.d.vue.ts +36 -0
  112. package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue +136 -0
  113. package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue.d.ts +36 -0
  114. package/dist/runtime/components/Integrations/WorkflowTagsInput.d.vue.ts +14 -0
  115. package/dist/runtime/components/Integrations/WorkflowTagsInput.vue +63 -0
  116. package/dist/runtime/components/Integrations/WorkflowTagsInput.vue.d.ts +14 -0
  117. package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +35 -15
  118. package/dist/runtime/components/Integrations/WorkflowVariablesDialog.d.vue.ts +13 -0
  119. package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +265 -0
  120. package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue.d.ts +13 -0
  121. package/dist/runtime/components/Integrations/WorkflowsTable.d.vue.ts +4 -1
  122. package/dist/runtime/components/Integrations/WorkflowsTable.vue +122 -39
  123. package/dist/runtime/components/Integrations/WorkflowsTable.vue.d.ts +4 -1
  124. package/dist/runtime/components/Integrations/workflowLayout.d.ts +5 -0
  125. package/dist/runtime/components/Integrations/workflowLayout.js +5 -3
  126. package/dist/runtime/composables/integrationsApi.d.ts +6 -2
  127. package/dist/runtime/composables/integrationsApi.js +5 -4
  128. package/dist/runtime/composables/useCanvasShortcuts.d.ts +37 -0
  129. package/dist/runtime/composables/useCanvasShortcuts.js +62 -0
  130. package/dist/runtime/composables/useIntegrationsDeadLetters.d.ts +22 -0
  131. package/dist/runtime/composables/useIntegrationsDeadLetters.js +50 -0
  132. package/dist/runtime/composables/useIntegrationsNodes.d.ts +4 -0
  133. package/dist/runtime/composables/useIntegrationsNodes.js +6 -3
  134. package/dist/runtime/composables/useIntegrationsRevisions.d.ts +40 -0
  135. package/dist/runtime/composables/useIntegrationsRevisions.js +51 -0
  136. package/dist/runtime/composables/useIntegrationsRuns.d.ts +21 -1
  137. package/dist/runtime/composables/useIntegrationsRuns.js +92 -4
  138. package/dist/runtime/composables/useIntegrationsTemplates.d.ts +1 -0
  139. package/dist/runtime/composables/useIntegrationsTemplates.js +32 -4
  140. package/dist/runtime/composables/useIntegrationsWorkflows.d.ts +1 -0
  141. package/dist/runtime/composables/useIntegrationsWorkflows.js +57 -1
  142. package/dist/runtime/composables/useMiniMapLayout.d.ts +34 -0
  143. package/dist/runtime/composables/useMiniMapLayout.js +119 -0
  144. package/dist/runtime/composables/useNodeConfigResolve.d.ts +43 -0
  145. package/dist/runtime/composables/useNodeConfigResolve.js +123 -0
  146. package/dist/runtime/composables/useNodeDrag.d.ts +22 -0
  147. package/dist/runtime/composables/useNodeDrag.js +10 -0
  148. package/dist/runtime/composables/usePaletteLayout.d.ts +41 -0
  149. package/dist/runtime/composables/usePaletteLayout.js +113 -0
  150. package/dist/runtime/data/expressionEditor.d.ts +22 -0
  151. package/dist/runtime/data/expressionEditor.js +40 -0
  152. package/dist/runtime/data/integrationNodeCatalog.js +8 -8
  153. package/dist/runtime/data/nodeInputData.d.ts +37 -0
  154. package/dist/runtime/data/nodeInputData.js +62 -0
  155. package/dist/runtime/data/nodeRunData.d.ts +56 -0
  156. package/dist/runtime/data/nodeRunData.js +91 -0
  157. package/dist/runtime/data/payloadForm.d.ts +44 -0
  158. package/dist/runtime/data/payloadForm.js +126 -0
  159. package/dist/runtime/data/workflowIconCatalog.d.ts +34 -0
  160. package/dist/runtime/data/workflowIconCatalog.js +116 -0
  161. package/dist/runtime/pages/integrations/audit.vue +25 -35
  162. package/dist/runtime/pages/integrations/credentials.vue +117 -122
  163. package/dist/runtime/pages/integrations/index.vue +133 -36
  164. package/dist/runtime/pages/integrations/runs.vue +125 -75
  165. package/dist/runtime/pages/integrations/schedules.vue +5 -16
  166. package/dist/runtime/pages/integrations/secrets.vue +1 -3
  167. package/dist/runtime/pages/integrations/workflows/[id].vue +599 -194
  168. package/dist/runtime/pages/integrations/workflows/index.vue +10 -8
  169. package/dist/runtime/types/integrations.d.ts +190 -4
  170. package/dist/runtime/utils/credentialState.d.ts +28 -0
  171. package/dist/runtime/utils/credentialState.js +29 -0
  172. package/dist/runtime/utils/localize.d.ts +3 -1
  173. package/dist/runtime/utils/localize.js +1 -2
  174. package/dist/runtime/utils/statusStyle.d.ts +6 -0
  175. package/dist/runtime/utils/statusStyle.js +15 -0
  176. package/package.json +12 -2
  177. package/dist/runtime/components/Integrations/RunDetailDialog.vue +0 -169
  178. package/dist/runtime/pages/integrations/workflows/new.d.vue.ts +0 -2
  179. package/dist/runtime/pages/integrations/workflows/new.vue +0 -153
  180. package/dist/runtime/pages/integrations/workflows/new.vue.d.ts +0 -2
@@ -1,140 +1,461 @@
1
1
  <script setup>
2
- import { computed, ref } from "vue";
2
+ import { computed, ref, watch } from "vue";
3
3
  import * as Lucide from "lucide-vue-next";
4
4
  import { INTEGRATION_NODE_CATALOG, NODE_CATEGORIES, TRIGGER_TEMPLATES } from "../../data/integrationNodeCatalog";
5
5
  const { load: loadNodes } = useIntegrationsNodes();
6
6
  onMounted(() => {
7
7
  void loadNodes();
8
8
  });
9
+ const emit = defineEmits(["insert"]);
9
10
  const search = ref("");
10
- function labelOf(value) {
11
- if (!value) return "";
12
- return typeof value === "string" ? value : Object.values(value)[0] ?? "";
13
- }
14
- function descOf(value) {
11
+ const searchInput = ref(null);
12
+ defineExpose({ focusSearch: () => searchInput.value?.focus() });
13
+ function loc(value) {
15
14
  if (!value) return "";
16
15
  return typeof value === "string" ? value : value.en ?? Object.values(value)[0] ?? "";
17
16
  }
18
- const grouped = computed(() => {
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
+ const CORE_GROUP_BY_CATEGORY = {
23
+ control: "Flow",
24
+ transform: "Data",
25
+ io: "Data",
26
+ action: "Actions",
27
+ trigger: "Triggers"
28
+ };
29
+ const CORE_LABEL_ALIAS = {
30
+ "Flow control": "Flow",
31
+ "Files": "Data",
32
+ "Files & I/O": "Data",
33
+ "Network": "Messaging"
34
+ };
35
+ const CORE_GROUP_META = {
36
+ "Data": { icon: "Database", color: "bg-slate-100 text-amber-700/80" },
37
+ "Flow": { icon: "Split", color: "bg-slate-100 text-rose-600/80" },
38
+ "Messaging": { icon: "MessageCircle", color: "bg-slate-100 text-violet-600/80" },
39
+ "Actions": { icon: "Wand2", color: "bg-slate-100 text-sky-600/80" }
40
+ };
41
+ function coreGroupIcon(label) {
42
+ const meta = CORE_GROUP_META[label];
43
+ return meta && Lucide[meta.icon] || Lucide.FolderOpen;
44
+ }
45
+ const VERB_PREFIX = /^(create|get|list|update|delete|search|send|sync|upsert|cancel)-(.+)$/;
46
+ function titleize(kebab) {
47
+ return kebab.split("-").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(" ");
48
+ }
49
+ function packageAppLabel(pkg) {
50
+ if (pkg.label) return pkg.label;
51
+ const tail = pkg.name.split("/").pop() ?? pkg.name;
52
+ return titleize(tail.replace(/^integrations-nodes-/, ""));
53
+ }
54
+ function isAppPackage(pkg) {
55
+ if (!pkg) return false;
56
+ const tail = pkg.name.split("/").pop() ?? pkg.name;
57
+ return tail.startsWith("integrations-nodes-") && tail !== "integrations-nodes-core";
58
+ }
59
+ function hasLogo(m) {
60
+ return (m.images ?? []).some((i) => i.category === "logo");
61
+ }
62
+ function groupPath(m) {
63
+ if (m.groups?.length) {
64
+ const path = m.groups.map((g) => loc(g)).filter(Boolean);
65
+ if (isIntegration(m) || !path.length) return path;
66
+ return [CORE_LABEL_ALIAS[path[0]] ?? path[0], ...path.slice(1)];
67
+ }
68
+ if (isAppPackage(m.__package)) {
69
+ const app = packageAppLabel(m.__package);
70
+ const local = m.slug.split(":").pop() ?? m.slug;
71
+ const pkgKey = (m.__package.name.split("/").pop() ?? "").replace(/^integrations-nodes-/, "");
72
+ const rest = local.startsWith(`${pkgKey}-`) ? local.slice(pkgKey.length + 1) : local;
73
+ const verb = VERB_PREFIX.exec(rest);
74
+ return verb ? [app, titleize(verb[2])] : [app];
75
+ }
76
+ return [CORE_GROUP_BY_CATEGORY[m.category] ?? "Other"];
77
+ }
78
+ function isIntegration(m) {
79
+ return isAppPackage(m.__package) || (m.groups?.length ?? 0) > 1 || hasLogo(m);
80
+ }
81
+ function makeGroup(label) {
82
+ return { label, children: /* @__PURE__ */ new Map(), leaves: [], count: 0 };
83
+ }
84
+ const latestManifests = computed(() => {
85
+ const seen = /* @__PURE__ */ new Set();
86
+ const out = [];
87
+ for (const m of INTEGRATION_NODE_CATALOG) {
88
+ if (seen.has(m.slug)) continue;
89
+ seen.add(m.slug);
90
+ out.push(m);
91
+ }
92
+ return out;
93
+ });
94
+ const tree = computed(() => {
95
+ const apps = makeGroup("Integrations");
96
+ const core = makeGroup("Core");
97
+ for (const m of latestManifests.value) {
98
+ if (m.category === "trigger") continue;
99
+ const path = groupPath(m);
100
+ const root = isIntegration(m) ? apps : core;
101
+ let cursor = root;
102
+ cursor.count++;
103
+ for (const label of path) {
104
+ if (!cursor.children.has(label)) cursor.children.set(label, makeGroup(label));
105
+ cursor = cursor.children.get(label);
106
+ cursor.count++;
107
+ }
108
+ cursor.leaves.push(m);
109
+ }
110
+ return { apps, core };
111
+ });
112
+ const appRows = computed(() => {
113
+ const rows = Array.from(tree.value.apps.children.entries());
114
+ const sftpIdx = rows.findIndex(([label]) => label === "SFTP");
115
+ const ftpIdx = rows.findIndex(([label]) => label === "FTP");
116
+ if (ftpIdx !== -1 && sftpIdx !== -1 && sftpIdx !== ftpIdx + 1) {
117
+ const [sftp] = rows.splice(sftpIdx, 1);
118
+ rows.splice(rows.findIndex(([label]) => label === "FTP") + 1, 0, sftp);
119
+ }
120
+ return rows;
121
+ });
122
+ function subtreeManifests(g) {
123
+ const out = [...g.leaves];
124
+ for (const child of g.children.values()) out.push(...subtreeManifests(child));
125
+ return out;
126
+ }
127
+ function groupLogoManifest(g) {
128
+ return subtreeManifests(g).find(hasLogo) ?? null;
129
+ }
130
+ const stack = ref([]);
131
+ const direction = ref("push");
132
+ const triggersLevel = computed(() => stack.value.length > 0 && stack.value[stack.value.length - 1][0] === "__triggers");
133
+ const currentGroup = computed(() => {
134
+ if (!stack.value.length || triggersLevel.value) return null;
135
+ const path = stack.value[stack.value.length - 1];
136
+ let cursor = path[0] === "__apps" ? tree.value.apps : tree.value.core;
137
+ for (const label of path.slice(1)) {
138
+ cursor = cursor?.children.get(label);
139
+ if (!cursor) return null;
140
+ }
141
+ return cursor ?? null;
142
+ });
143
+ const levelTitle = computed(() => {
144
+ if (triggersLevel.value) return "Triggers";
145
+ return currentGroup.value?.label ?? "Add node";
146
+ });
147
+ function pushPath(path) {
148
+ direction.value = "push";
149
+ stack.value = [...stack.value, path];
150
+ search.value = "";
151
+ }
152
+ function pushChild(label) {
153
+ const base = stack.value[stack.value.length - 1];
154
+ if (!base) return;
155
+ pushPath([...base, label]);
156
+ }
157
+ function popLevel() {
158
+ direction.value = "pop";
159
+ stack.value = stack.value.slice(0, -1);
160
+ search.value = "";
161
+ }
162
+ const levelKey = computed(() => stack.value.length);
163
+ const searchHits = computed(() => {
19
164
  const q = search.value.trim().toLowerCase();
20
- const matchesManifest = (m) => {
21
- if (!q) return true;
22
- return labelOf(m.name).toLowerCase().includes(q) || (m.slug ?? "").toLowerCase().includes(q) || descOf(m.description).toLowerCase().includes(q);
23
- };
24
- const matchesTrigger = (t) => {
25
- if (!q) return true;
26
- return labelOf(t.name).toLowerCase().includes(q) || t.type.includes(q) || t.id.includes(q);
27
- };
28
- const knownKeys = new Set(NODE_CATEGORIES.map((c) => String(c.key)));
29
- const groups = NODE_CATEGORIES.map((cat) => {
30
- const manifests = INTEGRATION_NODE_CATALOG.filter((m) => m.category === cat.key && matchesManifest(m));
31
- const triggers = cat.key === "trigger" ? TRIGGER_TEMPLATES.filter(matchesTrigger) : [];
32
- return { ...cat, manifests, triggers };
33
- });
34
- const other = INTEGRATION_NODE_CATALOG.filter((m) => !knownKeys.has(String(m.category ?? "")) && matchesManifest(m));
35
- if (other.length) {
36
- groups.push({ key: "other", label: "Nodes", color: "bg-slate-100 text-slate-500", manifests: other, triggers: [] });
165
+ if (!q || triggersLevel.value) return [];
166
+ const scope = currentGroup.value ? subtreeManifests(currentGroup.value) : latestManifests.value.filter((m) => m.category !== "trigger");
167
+ const hits = [];
168
+ for (const m of scope) {
169
+ const hay = `${loc(m.name)} ${m.slug} ${loc(m.description)}`.toLowerCase();
170
+ if (hay.includes(q)) hits.push({ manifest: m, path: groupPath(m) });
171
+ if (hits.length >= 50) break;
37
172
  }
38
- return groups.filter((g) => g.manifests.length || g.triggers.length);
173
+ return hits;
39
174
  });
40
- function iconFor(name) {
41
- if (!name) return Lucide.Box;
42
- const cleaned = (name.includes(":") ? name.split(":").pop() : name).split("-").map((s, i) => i === 0 ? s.charAt(0).toUpperCase() + s.slice(1) : s.charAt(0).toUpperCase() + s.slice(1)).join("");
43
- const fallback = Lucide[cleaned];
44
- if (fallback) return fallback;
45
- return Lucide.Box;
175
+ const matchedTriggers = computed(() => {
176
+ const q = search.value.trim().toLowerCase();
177
+ if (!q || currentGroup.value) return [];
178
+ const matches = (t) => loc(t.name).toLowerCase().includes(q) || t.type.includes(q) || loc(t.description).toLowerCase().includes(q);
179
+ return TRIGGER_TEMPLATES.filter(matches);
180
+ });
181
+ const nodeDrag = useNodeDrag();
182
+ function setCustomDragImage(ev, label, logoSrc, accent = "#10b981") {
183
+ if (!ev.dataTransfer) return;
184
+ const el = document.createElement("div");
185
+ el.style.cssText = "position:fixed;top:-1000px;left:-1000px;pointer-events:none;display:flex;align-items:center;gap:8px;padding:8px 14px 8px 10px;background:#fff;border:1px solid rgba(0,0,0,.1);border-radius:10px;box-shadow:0 8px 24px rgba(15,23,42,.18);font:600 13px ui-sans-serif,system-ui;color:#0f172a;max-width:240px;";
186
+ if (logoSrc) {
187
+ const img = document.createElement("img");
188
+ img.src = logoSrc;
189
+ img.style.cssText = "width:18px;height:18px;object-fit:contain;border-radius:3px;flex:none;";
190
+ el.appendChild(img);
191
+ } else {
192
+ const dot = document.createElement("span");
193
+ dot.style.cssText = `width:10px;height:10px;border-radius:9999px;background:${accent};flex:none;`;
194
+ el.appendChild(dot);
195
+ }
196
+ const text = document.createElement("span");
197
+ text.textContent = label;
198
+ text.style.cssText = "white-space:nowrap;overflow:hidden;text-overflow:ellipsis;";
199
+ el.appendChild(text);
200
+ document.body.appendChild(el);
201
+ ev.dataTransfer.setDragImage(el, 22, 20);
202
+ window.setTimeout(() => el.remove(), 0);
203
+ }
204
+ function logoSrcOf(m) {
205
+ const imgs = m.images ?? [];
206
+ return (imgs.find((i) => i.category === "logo") ?? imgs[0])?.src ?? null;
46
207
  }
47
- function onManifestDragStart(manifest, ev) {
208
+ function onManifestDragStart(m, ev) {
48
209
  if (!ev.dataTransfer) return;
49
- ev.dataTransfer.setData("application/integration-node", JSON.stringify({
50
- kind: "manifest",
51
- slug: manifest.slug,
52
- version: manifest.version
53
- }));
210
+ ev.dataTransfer.setData("application/integration-node", JSON.stringify({ kind: "manifest", slug: m.slug, version: m.version }));
54
211
  ev.dataTransfer.effectAllowed = "move";
212
+ setCustomDragImage(ev, loc(m.name), logoSrcOf(m), "#8b5cf6");
213
+ nodeDrag.start({ kind: "manifest", manifest: m });
55
214
  }
56
215
  function onTriggerDragStart(tpl, ev) {
57
216
  if (!ev.dataTransfer) return;
58
- ev.dataTransfer.setData("application/integration-node", JSON.stringify({
59
- kind: "trigger",
60
- type: tpl.type
61
- }));
217
+ ev.dataTransfer.setData("application/integration-node", JSON.stringify({ kind: "trigger", type: tpl.type }));
62
218
  ev.dataTransfer.effectAllowed = "move";
219
+ setCustomDragImage(ev, loc(tpl.name), null, "#10b981");
220
+ nodeDrag.start({ kind: "trigger", tpl });
221
+ }
222
+ function onDragEnd() {
223
+ nodeDrag.end();
224
+ }
225
+ function insertManifest(m) {
226
+ emit("insert", { kind: "manifest", slug: m.slug, version: m.version });
63
227
  }
228
+ function insertTrigger(tpl) {
229
+ emit("insert", { kind: "trigger", type: tpl.type });
230
+ }
231
+ function categoryColor(label) {
232
+ const meta = CORE_GROUP_META[label];
233
+ if (meta) return meta.color;
234
+ const cat = NODE_CATEGORIES.find((c) => c.label === label || CORE_GROUP_BY_CATEGORY[String(c.key)] === label);
235
+ return cat?.color ?? "bg-slate-100 text-slate-500";
236
+ }
237
+ watch(tree, () => {
238
+ if (stack.value.length && !triggersLevel.value && !currentGroup.value) stack.value = [];
239
+ });
240
+ const searching = computed(() => search.value.trim().length > 0);
64
241
  </script>
65
242
 
66
243
  <template>
67
- <aside class="flex h-full w-60 shrink-0 flex-col border-r border-black/[0.06] bg-white">
68
- <div class="p-3 border-b border-black/[0.06]">
69
- <div class="text-mono text-muted-foreground mb-2">
70
- Add node
244
+ <aside class="flex h-full w-full flex-col bg-white">
245
+ <!-- Header: search + (when inside a group) the back affordance. The root
246
+ title lives in the editor's palette shell header, not here. -->
247
+ <div class="border-b border-black/[0.06] p-3">
248
+ <div v-if="stack.length" class="mb-2 flex min-w-0 items-center gap-1.5">
249
+ <button
250
+ type="button"
251
+ class="flex size-6 shrink-0 items-center justify-center rounded-md text-muted-foreground transition hover:bg-muted/50 hover:text-foreground"
252
+ title="Back"
253
+ @click="popLevel"
254
+ >
255
+ <Lucide.ArrowLeft :size="14" />
256
+ </button>
257
+ <div class="min-w-0 flex-1 truncate text-mono text-muted-foreground">
258
+ {{ levelTitle }}
259
+ </div>
71
260
  </div>
72
261
  <div class="relative">
73
262
  <Lucide.Search :size="12" class="absolute left-2 top-1/2 -translate-y-1/2 text-muted-foreground" />
74
263
  <input
264
+ ref="searchInput"
75
265
  v-model="search"
76
266
  type="text"
77
- placeholder="Search…"
267
+ :placeholder="stack.length ? `Search in ${levelTitle}\u2026` : 'Search all nodes\u2026'"
78
268
  class="h-7 w-full rounded-md border border-black/[0.08] bg-white pl-7 pr-2 text-sm focus:outline-none focus:ring-2 focus:ring-emerald-500/40"
269
+ @keydown.esc="search ? search = '' : stack.length && popLevel()"
79
270
  >
80
271
  </div>
81
272
  </div>
82
273
 
83
- <div class="flex-1 overflow-y-auto p-2 space-y-3">
84
- <div v-for="cat in grouped" :key="cat.key">
85
- <div class="px-2 py-1 text-mono text-muted-foreground">
86
- {{ cat.label }}
87
- </div>
88
- <div class="space-y-0.5">
89
- <!-- Trigger templates (only in the "trigger" category) -->
90
- <div
91
- v-for="t in cat.triggers"
92
- :key="t.id"
93
- class="group flex items-center gap-2 rounded-md border border-transparent px-2 py-1.5 cursor-grab transition hover:border-black/[0.08] hover:bg-muted/40 active:cursor-grabbing"
94
- draggable="true"
95
- :title="labelOf(t.name)"
96
- @dragstart="onTriggerDragStart(t, $event)"
97
- >
98
- <span class="flex size-6 shrink-0 items-center justify-center rounded-md" :class="cat.color">
99
- <component :is="iconFor(t.icon)" :size="13" />
100
- </span>
101
- <div class="min-w-0">
102
- <div class="text-sm font-medium truncate">
103
- {{ labelOf(t.name) }}
274
+ <!-- Levels slide horizontally; search results replace the level flat.
275
+ The transition-active classes must NOT repeat tokens from the level's
276
+ static class (absolute/inset-0): Vue removes active-class tokens when
277
+ the transition ends and would strip them from the static class too,
278
+ leaving the level statically positioned and unscrollable. -->
279
+ <div class="relative min-h-0 flex-1 overflow-hidden">
280
+ <Transition
281
+ :enter-active-class="'transition-transform duration-200 ease-out'"
282
+ :enter-from-class="direction === 'push' ? 'translate-x-full' : '-translate-x-full'"
283
+ enter-to-class="translate-x-0"
284
+ :leave-active-class="'transition-transform duration-200 ease-in'"
285
+ leave-from-class="translate-x-0"
286
+ :leave-to-class="direction === 'push' ? '-translate-x-full' : 'translate-x-full'"
287
+ >
288
+ <!-- scrollbar-width/color opt out of macOS overlay scrollbars, so the
289
+ thin scrollbar stays visible whenever the level overflows. -->
290
+ <div :key="`${levelKey}-${searching}`" class="absolute inset-0 overflow-y-auto overscroll-contain p-2 [scrollbar-color:rgb(0_0_0/0.22)_transparent] [scrollbar-width:thin]">
291
+ <!-- ── Search results (flat, path as subtitle) ─────────────────── -->
292
+ <template v-if="searching">
293
+ <div v-if="matchedTriggers.length" class="mb-1">
294
+ <div class="px-2 py-1 text-mono text-muted-foreground">Triggers</div>
295
+ <div
296
+ v-for="t in matchedTriggers"
297
+ :key="t.id"
298
+ class="group flex cursor-grab items-center gap-2 rounded-md border border-transparent px-2 py-1.5 transition hover:border-black/[0.08] hover:bg-muted/40 active:cursor-grabbing"
299
+ draggable="true"
300
+ @dragstart="onTriggerDragStart(t, $event)"
301
+ @dragend="onDragEnd"
302
+ @click="insertTrigger(t)"
303
+ >
304
+ <span class="flex size-6 shrink-0 items-center justify-center rounded-md bg-emerald-100 text-emerald-600">
305
+ <component :is="iconFor(t.icon)" :size="13" />
306
+ </span>
307
+ <div class="min-w-0">
308
+ <div class="truncate text-sm font-medium">{{ loc(t.name) }}</div>
309
+ <div class="text-xs leading-snug text-muted-foreground">Trigger</div>
310
+ </div>
104
311
  </div>
105
- <div class="text-sm text-muted-foreground truncate">
106
- {{ t.type }}
312
+ </div>
313
+
314
+ <p v-if="!searchHits.length && !matchedTriggers.length" class="px-2 py-6 text-center text-sm text-muted-foreground">
315
+ Nothing matches “{{ search }}”.
316
+ </p>
317
+ <div
318
+ v-for="hit in searchHits"
319
+ :key="`${hit.manifest.slug}@${hit.manifest.version}`"
320
+ class="group flex cursor-grab items-start gap-2 rounded-md border border-transparent px-2 py-1.5 transition hover:border-black/[0.08] hover:bg-muted/40 active:cursor-grabbing"
321
+ draggable="true"
322
+ :title="loc(hit.manifest.name)"
323
+ @dragstart="onManifestDragStart(hit.manifest, $event)"
324
+ @dragend="onDragEnd"
325
+ @click="insertManifest(hit.manifest)"
326
+ >
327
+ <span class="flex size-6 shrink-0 items-center justify-center rounded-md" :class="categoryColor(hit.path[0] ?? '')">
328
+ <IntegrationsNodeLogo :manifest="hit.manifest" :size="13" />
329
+ </span>
330
+ <div class="min-w-0 flex-1">
331
+ <div class="truncate text-sm font-medium">{{ loc(hit.manifest.name) }}</div>
332
+ <div class="text-xs leading-snug text-muted-foreground">{{ loc(hit.manifest.description) }}</div>
333
+ <div class="truncate text-xs text-muted-foreground/70">{{ hit.path.join(" \u203A ") }}</div>
107
334
  </div>
108
335
  </div>
109
- </div>
336
+ </template>
110
337
 
111
- <!-- Real node manifests -->
112
- <div
113
- v-for="m in cat.manifests"
114
- :key="`${m.slug}@${m.version}`"
115
- class="group flex items-center gap-2 rounded-md border border-transparent px-2 py-1.5 cursor-grab transition hover:border-black/[0.08] hover:bg-muted/40 active:cursor-grabbing"
116
- draggable="true"
117
- :title="labelOf(m.name)"
118
- @dragstart="onManifestDragStart(m, $event)"
119
- >
120
- <span class="flex size-6 shrink-0 items-center justify-center rounded-md" :class="cat.color">
121
- <component :is="iconFor(m.icon)" :size="13" />
122
- </span>
123
- <div class="min-w-0">
124
- <div class="text-sm font-medium truncate">
125
- {{ labelOf(m.name) }}
338
+ <!-- ── Triggers level ──────────────────────────────────────────── -->
339
+ <template v-else-if="triggersLevel">
340
+ <div
341
+ v-for="t in TRIGGER_TEMPLATES"
342
+ :key="t.id"
343
+ class="group flex cursor-grab items-center gap-2 rounded-md border border-transparent px-2 py-1.5 transition hover:border-black/[0.08] hover:bg-muted/40 active:cursor-grabbing"
344
+ draggable="true"
345
+ :title="loc(t.name)"
346
+ @dragstart="onTriggerDragStart(t, $event)"
347
+ @dragend="onDragEnd"
348
+ @click="insertTrigger(t)"
349
+ >
350
+ <span class="flex size-6 shrink-0 items-center justify-center rounded-md bg-emerald-100 text-emerald-600">
351
+ <component :is="iconFor(t.icon)" :size="13" />
352
+ </span>
353
+ <div class="min-w-0">
354
+ <div class="truncate text-sm font-medium">{{ loc(t.name) }}</div>
355
+ <div class="text-xs leading-snug text-muted-foreground">{{ loc(t.description) }}</div>
126
356
  </div>
127
- <div class="text-sm text-muted-foreground truncate">
128
- {{ m.slug }}@{{ m.version }}
357
+ </div>
358
+ </template>
359
+
360
+ <!-- ── Root level ──────────────────────────────────────────────── -->
361
+ <template v-else-if="!currentGroup">
362
+ <!-- Triggers group on top -->
363
+ <div v-if="tree.core.children.size">
364
+ <div class="px-2 py-1 text-mono text-muted-foreground">Core</div>
365
+ <button
366
+ type="button"
367
+ class="flex w-full items-center gap-2 rounded-md border border-transparent px-2 py-1.5 text-left transition hover:border-black/[0.08] hover:bg-muted/40"
368
+ @click="pushPath(['__triggers'])"
369
+ >
370
+ <span class="flex size-6 shrink-0 items-center justify-center rounded-md bg-slate-100 text-emerald-600/80">
371
+ <Lucide.Zap :size="13" />
372
+ </span>
373
+ <span class="min-w-0 flex-1 truncate text-sm font-medium">Triggers</span>
374
+ <span class="text-sm tabular-nums text-muted-foreground">{{ TRIGGER_TEMPLATES.length }}</span>
375
+ <Lucide.ChevronRight :size="13" class="text-muted-foreground" />
376
+ </button>
377
+ <button
378
+ v-for="[label, group] in tree.core.children"
379
+ :key="`core-${label}`"
380
+ type="button"
381
+ class="flex w-full items-center gap-2 rounded-md border border-transparent px-2 py-1.5 text-left transition hover:border-black/[0.08] hover:bg-muted/40"
382
+ @click="pushPath(['__core', label])"
383
+ >
384
+ <span class="flex size-6 shrink-0 items-center justify-center rounded-md" :class="categoryColor(label)">
385
+ <component :is="coreGroupIcon(label)" :size="13" />
386
+ </span>
387
+ <span class="min-w-0 flex-1 truncate text-sm font-medium">{{ label }}</span>
388
+ <span class="text-sm tabular-nums text-muted-foreground">{{ group.count }}</span>
389
+ <Lucide.ChevronRight :size="13" class="text-muted-foreground" />
390
+ </button>
391
+ </div>
392
+
393
+ <div v-if="tree.apps.children.size" class="mt-2">
394
+ <div class="px-2 py-1 text-mono text-muted-foreground">Integrations</div>
395
+ <button
396
+ v-for="[label, group] in appRows"
397
+ :key="`app-${label}`"
398
+ type="button"
399
+ class="flex w-full items-center gap-2 rounded-md border border-transparent px-2 py-1.5 text-left transition hover:border-black/[0.08] hover:bg-muted/40"
400
+ @click="pushPath(['__apps', label])"
401
+ >
402
+ <span class="flex size-6 shrink-0 items-center justify-center rounded-md bg-white ring-1 ring-black/[0.06]">
403
+ <IntegrationsNodeLogo v-if="groupLogoManifest(group)" :manifest="groupLogoManifest(group)" :size="14" />
404
+ <Lucide.Package v-else :size="13" class="text-violet-600" />
405
+ </span>
406
+ <span class="min-w-0 flex-1 truncate text-sm font-medium">{{ label }}</span>
407
+ <span class="text-sm tabular-nums text-muted-foreground">{{ group.count }}</span>
408
+ <Lucide.ChevronRight :size="13" class="text-muted-foreground" />
409
+ </button>
410
+ </div>
411
+ </template>
412
+
413
+ <!-- ── Inside a group: subgroups first, then leaves ─────────────── -->
414
+ <template v-else>
415
+ <button
416
+ v-for="[label, group] in currentGroup.children"
417
+ :key="`sub-${label}`"
418
+ type="button"
419
+ class="flex w-full items-center gap-2 rounded-md border border-transparent px-2 py-1.5 text-left transition hover:border-black/[0.08] hover:bg-muted/40"
420
+ @click="pushChild(label)"
421
+ >
422
+ <span
423
+ class="flex size-6 shrink-0 items-center justify-center rounded-md"
424
+ :class="groupLogoManifest(group) ? 'bg-white ring-1 ring-black/[0.06]' : 'bg-slate-100 text-slate-500'"
425
+ >
426
+ <IntegrationsNodeLogo v-if="groupLogoManifest(group)" :manifest="groupLogoManifest(group)" :size="14" />
427
+ <Lucide.FolderOpen v-else :size="13" />
428
+ </span>
429
+ <span class="min-w-0 flex-1 truncate text-sm font-medium">{{ label }}</span>
430
+ <span class="text-sm tabular-nums text-muted-foreground">{{ group.count }}</span>
431
+ <Lucide.ChevronRight :size="13" class="text-muted-foreground" />
432
+ </button>
433
+
434
+ <div
435
+ v-for="m in currentGroup.leaves"
436
+ :key="`${m.slug}@${m.version}`"
437
+ class="group flex cursor-grab items-start gap-2 rounded-md border border-transparent px-2 py-1.5 transition hover:border-black/[0.08] hover:bg-muted/40 active:cursor-grabbing"
438
+ draggable="true"
439
+ :title="loc(m.name)"
440
+ @dragstart="onManifestDragStart(m, $event)"
441
+ @dragend="onDragEnd"
442
+ @click="insertManifest(m)"
443
+ >
444
+ <span class="flex size-6 shrink-0 items-center justify-center rounded-md" :class="hasLogo(m) ? 'bg-white ring-1 ring-black/[0.06]' : categoryColor(groupPath(m)[0] ?? '')">
445
+ <IntegrationsNodeLogo :manifest="m" :size="14" />
446
+ </span>
447
+ <div class="min-w-0 flex-1">
448
+ <div class="truncate text-sm font-medium">{{ loc(m.name) }}</div>
449
+ <div class="text-xs leading-snug text-muted-foreground">{{ loc(m.description) }}</div>
129
450
  </div>
130
451
  </div>
131
- </div>
452
+ </template>
132
453
  </div>
133
- </div>
454
+ </Transition>
134
455
  </div>
135
456
 
136
457
  <div class="border-t border-black/[0.06] px-3 py-2 text-sm text-muted-foreground">
137
- Drag a node onto the canvas.
458
+ Drag onto the canvas — or click to insert.
138
459
  </div>
139
460
  </aside>
140
461
  </template>
@@ -1,2 +1,23 @@
1
- declare const _default: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
1
+ import { type TriggerTemplate } from '../../data/integrationNodeCatalog.js';
2
+ declare const _default: import("vue").DefineComponent<{}, {
3
+ focusSearch: () => void | undefined;
4
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
5
+ insert: (payload: {
6
+ kind: "manifest";
7
+ slug: string;
8
+ version: string;
9
+ } | {
10
+ kind: "trigger";
11
+ type: TriggerTemplate["type"];
12
+ }) => any;
13
+ }, string, import("vue").PublicProps, Readonly<{}> & Readonly<{
14
+ onInsert?: ((payload: {
15
+ kind: "manifest";
16
+ slug: string;
17
+ version: string;
18
+ } | {
19
+ kind: "trigger";
20
+ type: TriggerTemplate["type"];
21
+ }) => any) | undefined;
22
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
23
  export default _default;
@@ -0,0 +1,9 @@
1
+ type __VLS_Props = {
2
+ modelValue: unknown;
3
+ };
4
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
5
+ "update:modelValue": (value: unknown) => any;
6
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
7
+ "onUpdate:modelValue"?: ((value: unknown) => any) | undefined;
8
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
9
+ export default _default;