@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
@@ -15,7 +15,12 @@ function toCockpit(api) {
15
15
  name: apiBlob.name ?? api.name,
16
16
  nodes: apiBlob.nodes ?? [],
17
17
  edges: apiBlob.edges ?? [],
18
+ variables: apiBlob.variables ?? void 0,
18
19
  triggerPositions: apiBlob.triggerPositions ?? {},
20
+ stickies: apiBlob.stickies ?? [],
21
+ groups: apiBlob.groups ?? [],
22
+ tags: apiBlob.tags ?? void 0,
23
+ icon: apiBlob.icon ?? void 0,
19
24
  triggers
20
25
  };
21
26
  return {
@@ -27,6 +32,8 @@ function toCockpit(api) {
27
32
  active: api.active ?? false,
28
33
  revision: api.revision ?? 0,
29
34
  execution_mode: api.execution_mode,
35
+ buildStatus: api.build_status ?? null,
36
+ buildError: api.build_error ?? null,
30
37
  created_at: api.created_at ?? "",
31
38
  updated_at: api.updated_at ?? ""
32
39
  };
@@ -42,7 +49,15 @@ function toPayload(meta) {
42
49
  name: typeof b.name === "string" ? b.name : meta.name,
43
50
  nodes: b.nodes ?? [],
44
51
  edges: b.edges ?? [],
45
- triggerPositions: b.triggerPositions ?? {}
52
+ triggerPositions: b.triggerPositions ?? {},
53
+ // Only send `stickies` when non-empty: backends without the stickies
54
+ // schema PR reject unknown blob keys (additionalProperties:false).
55
+ ...b.stickies?.length ? { stickies: b.stickies } : {},
56
+ ...b.groups?.length ? { groups: b.groups } : {},
57
+ // variables were silently dropped before — the schema always allowed them.
58
+ ...b.variables && Object.keys(b.variables).length ? { variables: b.variables } : {},
59
+ ...b.tags?.length ? { tags: b.tags } : {},
60
+ ...b.icon ? { icon: b.icon } : {}
46
61
  },
47
62
  active: meta.active,
48
63
  execution_mode: meta.executionMode ?? "async_only",
@@ -112,6 +127,46 @@ export function useIntegrationsWorkflows() {
112
127
  upsertCache(wf);
113
128
  return wf;
114
129
  }
130
+ async function duplicate(id, name) {
131
+ const source = await get(id);
132
+ if (!source) throw new Error("Workflow not found.");
133
+ const blob = JSON.parse(JSON.stringify(source.blob));
134
+ const handleMap = /* @__PURE__ */ new Map();
135
+ for (const trigger of blob.triggers ?? []) {
136
+ const fresh = crypto.randomUUID();
137
+ handleMap.set(trigger.id, fresh);
138
+ trigger.id = fresh;
139
+ trigger.active = false;
140
+ }
141
+ for (const edge of blob.edges ?? []) {
142
+ const mapped = handleMap.get(edge.from.nodeId);
143
+ if (mapped) edge.from.nodeId = mapped;
144
+ }
145
+ if (blob.triggerPositions) {
146
+ const rekeyed = {};
147
+ for (const [handle, pos] of Object.entries(blob.triggerPositions)) {
148
+ rekeyed[handleMap.get(handle) ?? handle] = pos;
149
+ }
150
+ blob.triggerPositions = rekeyed;
151
+ }
152
+ const base = {
153
+ description: source.description,
154
+ blob,
155
+ active: false,
156
+ executionMode: source.execution_mode ?? void 0
157
+ };
158
+ if (name?.trim()) return await create({ ...base, name: name.trim() });
159
+ const attempts = [`${source.name} (Copy)`, ...Array.from({ length: 8 }, (_, i) => `${source.name} (Copy ${i + 2})`)];
160
+ let lastError = null;
161
+ for (const attempt of attempts) {
162
+ try {
163
+ return await create({ ...base, name: attempt });
164
+ } catch (err) {
165
+ lastError = err;
166
+ }
167
+ }
168
+ throw lastError instanceof Error ? lastError : new Error("Could not duplicate the workflow.");
169
+ }
115
170
  async function update(id, patch) {
116
171
  const wf = toCockpit(unwrap(await api.put(`/workflows/${id}`, toPayload({
117
172
  name: patch.name,
@@ -146,6 +201,7 @@ export function useIntegrationsWorkflows() {
146
201
  get,
147
202
  create,
148
203
  update,
204
+ duplicate,
149
205
  remove,
150
206
  toggleActive
151
207
  };
@@ -0,0 +1,34 @@
1
+ import { type Ref } from 'vue';
2
+ export type MiniMapCorner = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
3
+ export type MiniMapMode = MiniMapCorner | 'floating';
4
+ /**
5
+ * Layout state for the workflow editor's minimap: pinned to one of the four
6
+ * canvas corners, free-floating, or collapsed into a pill — the same
7
+ * interaction grammar as usePaletteLayout, with corner snapping instead of
8
+ * full-height docks. Drags by its header via pointer events; releasing within
9
+ * SNAP_ZONE of two edges snaps into that corner. Persists to localStorage.
10
+ */
11
+ export declare function useMiniMapLayout(canvasRef: Ref<HTMLElement | null>): {
12
+ mode: Ref<MiniMapMode, MiniMapMode>;
13
+ collapsed: Ref<boolean, boolean>;
14
+ floating: Ref<{
15
+ x: number;
16
+ y: number;
17
+ }, {
18
+ x: number;
19
+ y: number;
20
+ } | {
21
+ x: number;
22
+ y: number;
23
+ }>;
24
+ floatingStyle: import("vue").ComputedRef<{
25
+ left: string;
26
+ top: string;
27
+ }>;
28
+ dragging: Ref<boolean, boolean>;
29
+ dockPreview: Ref<MiniMapCorner | null, MiniMapCorner | null>;
30
+ startDrag: (ev: PointerEvent, shellEl: HTMLElement | null) => void;
31
+ dock: (corner: MiniMapCorner) => void;
32
+ floatHere: (shellEl: HTMLElement | null) => void;
33
+ setCollapsed: (v: boolean) => void;
34
+ };
@@ -0,0 +1,119 @@
1
+ import { computed, onBeforeUnmount, onMounted, ref } from "vue";
2
+ const STORAGE_KEY = "integrations.minimap.layout";
3
+ const CORNERS = ["top-left", "top-right", "bottom-left", "bottom-right"];
4
+ const MARGIN = 12;
5
+ const BOTTOM_CLEARANCE = 64;
6
+ const SNAP_ZONE = 56;
7
+ function clampNum(v, lo, hi) {
8
+ return Math.min(hi, Math.max(lo, v));
9
+ }
10
+ export function useMiniMapLayout(canvasRef) {
11
+ const mode = ref("bottom-right");
12
+ const collapsed = ref(false);
13
+ const floating = ref({ x: MARGIN, y: MARGIN });
14
+ const dragging = ref(false);
15
+ const dockPreview = ref(null);
16
+ function persist() {
17
+ try {
18
+ const payload = { mode: mode.value, floating: floating.value, collapsed: collapsed.value };
19
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(payload));
20
+ } catch {
21
+ }
22
+ }
23
+ function restore() {
24
+ try {
25
+ const raw = localStorage.getItem(STORAGE_KEY);
26
+ if (!raw) return;
27
+ const saved = JSON.parse(raw);
28
+ if (saved.mode === "floating" || CORNERS.includes(saved.mode)) {
29
+ mode.value = saved.mode;
30
+ }
31
+ if (typeof saved.floating?.x === "number" && typeof saved.floating?.y === "number") {
32
+ floating.value = { x: saved.floating.x, y: saved.floating.y };
33
+ }
34
+ collapsed.value = saved.collapsed === true;
35
+ clampFloating();
36
+ } catch {
37
+ }
38
+ }
39
+ function clampFloating() {
40
+ const rect = canvasRef.value?.getBoundingClientRect();
41
+ if (!rect || rect.width === 0) return;
42
+ floating.value = {
43
+ x: clampNum(floating.value.x, MARGIN, Math.max(MARGIN, rect.width - panelW - MARGIN)),
44
+ y: clampNum(floating.value.y, MARGIN, Math.max(MARGIN, rect.height - BOTTOM_CLEARANCE - panelH))
45
+ };
46
+ }
47
+ onMounted(restore);
48
+ let grabDX = 0;
49
+ let grabDY = 0;
50
+ let panelW = 200;
51
+ let panelH = 190;
52
+ function startDrag(ev, shellEl) {
53
+ if (ev.button !== 0 || dragging.value) return;
54
+ if (ev.target?.closest("button")) return;
55
+ const canvas = canvasRef.value;
56
+ if (!canvas || !shellEl) return;
57
+ const cRect = canvas.getBoundingClientRect();
58
+ const pRect = shellEl.getBoundingClientRect();
59
+ floating.value = { x: pRect.left - cRect.left, y: pRect.top - cRect.top };
60
+ mode.value = "floating";
61
+ grabDX = ev.clientX - pRect.left;
62
+ grabDY = ev.clientY - pRect.top;
63
+ panelW = pRect.width;
64
+ panelH = pRect.height;
65
+ dragging.value = true;
66
+ ev.preventDefault();
67
+ window.addEventListener("pointermove", onDragMove);
68
+ window.addEventListener("pointerup", onDragEnd);
69
+ window.addEventListener("pointercancel", onDragEnd);
70
+ }
71
+ function onDragMove(ev) {
72
+ const rect = canvasRef.value?.getBoundingClientRect();
73
+ if (!rect) return;
74
+ const x = clampNum(ev.clientX - rect.left - grabDX, MARGIN, Math.max(MARGIN, rect.width - panelW - MARGIN));
75
+ const y = clampNum(ev.clientY - rect.top - grabDY, MARGIN, Math.max(MARGIN, rect.height - BOTTOM_CLEARANCE - panelH));
76
+ floating.value = { x, y };
77
+ const nearLeft = x <= SNAP_ZONE;
78
+ const nearRight = x + panelW >= rect.width - SNAP_ZONE;
79
+ const nearTop = y <= SNAP_ZONE;
80
+ const nearBottom = y + panelH >= rect.height - BOTTOM_CLEARANCE - SNAP_ZONE;
81
+ dockPreview.value = nearTop && nearLeft ? "top-left" : nearTop && nearRight ? "top-right" : nearBottom && nearLeft ? "bottom-left" : nearBottom && nearRight ? "bottom-right" : null;
82
+ }
83
+ function stopListeners() {
84
+ window.removeEventListener("pointermove", onDragMove);
85
+ window.removeEventListener("pointerup", onDragEnd);
86
+ window.removeEventListener("pointercancel", onDragEnd);
87
+ }
88
+ function onDragEnd() {
89
+ if (dockPreview.value) mode.value = dockPreview.value;
90
+ dockPreview.value = null;
91
+ dragging.value = false;
92
+ stopListeners();
93
+ persist();
94
+ }
95
+ onBeforeUnmount(stopListeners);
96
+ function dock(corner) {
97
+ mode.value = corner;
98
+ persist();
99
+ }
100
+ function floatHere(shellEl) {
101
+ if (mode.value !== "floating") {
102
+ const cRect = canvasRef.value?.getBoundingClientRect();
103
+ const pRect = shellEl?.getBoundingClientRect();
104
+ if (cRect && pRect) floating.value = { x: pRect.left - cRect.left, y: pRect.top - cRect.top };
105
+ mode.value = "floating";
106
+ clampFloating();
107
+ }
108
+ persist();
109
+ }
110
+ function setCollapsed(v) {
111
+ collapsed.value = v;
112
+ persist();
113
+ }
114
+ const floatingStyle = computed(() => ({
115
+ left: `${floating.value.x}px`,
116
+ top: `${floating.value.y}px`
117
+ }));
118
+ return { mode, collapsed, floating, floatingStyle, dragging, dockPreview, startDrag, dock, floatHere, setCollapsed };
119
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Author-time resolution of a node's dynamic options / fields / outputs
3
+ * (PO-143, Phase 1). Ports the colleagues' React-Query hook
4
+ * (`integrations-ui/src/lib/useNodeConfigResolve.ts`) to a plain Vue composable
5
+ * — no query library, hand-rolled debounce + abort + keep-previous-data.
6
+ *
7
+ * Backend: `POST {integrationsApi}/nodes/{slug}/{version}/config:resolve`
8
+ * body `{ target, config, locale? }`. `target` is a config field key
9
+ * (→ `{ options }`), `'*'` (→ `{ fields }`) or `'outputs'` (→ `{ outputs }`).
10
+ *
11
+ * `dependsOn` given (options / dynamic-schema): those keys both gate the query
12
+ * (must be set as literals) and form the cache-key subset sent as `config`.
13
+ * `dependsOn` omitted (outputs): no gate, and the whole `config` keys the query
14
+ * and is sent — so any config edit re-resolves.
15
+ */
16
+ import type { MaybeRefOrGetter, Ref } from 'vue';
17
+ import type { NodeConfigResolveError, NodeConfigResolveResult } from '../types/integrations.js';
18
+ /**
19
+ * A config value counts as "set" for gating a dynamic resolve when it is a
20
+ * non-empty literal. Expression-looking strings (`${{ … }}` / leading `=`) are
21
+ * treated as unset: the backend resolves against literal values only, so a
22
+ * dependency still holding an expression can't drive resolution yet.
23
+ */
24
+ export declare function isResolvableValue(v: unknown): boolean;
25
+ export declare function useNodeConfigResolve(opts: {
26
+ slug: MaybeRefOrGetter<string>;
27
+ version: MaybeRefOrGetter<string>;
28
+ target: MaybeRefOrGetter<string>;
29
+ config: MaybeRefOrGetter<Record<string, unknown>>;
30
+ dependsOn?: MaybeRefOrGetter<string[] | undefined>;
31
+ enabled?: MaybeRefOrGetter<boolean>;
32
+ /** Type-to-search term, forwarded to loadOptions (PE-334). Keys the query. */
33
+ search?: MaybeRefOrGetter<string | undefined>;
34
+ }): {
35
+ data: Ref<NodeConfigResolveResult | null>;
36
+ error: Ref<NodeConfigResolveError | null>;
37
+ isFetching: Ref<boolean>;
38
+ hasData: Ref<boolean>;
39
+ gated: Ref<boolean>;
40
+ unsatisfied: Ref<string[]>;
41
+ isEnabled: Ref<boolean>;
42
+ refetch: () => void;
43
+ };
@@ -0,0 +1,123 @@
1
+ import { computed, onScopeDispose, ref, toValue, watch } from "vue";
2
+ import { IntegrationsApiError, useIntegrationsApi } from "./integrationsApi.js";
3
+ export function isResolvableValue(v) {
4
+ if (v === void 0 || v === null) return false;
5
+ if (typeof v === "string") {
6
+ const t = v.trim();
7
+ if (t === "") return false;
8
+ if (t.startsWith("=")) return false;
9
+ if (/\$?\{\{[\s\S]*?\}\}/.test(t)) return false;
10
+ return true;
11
+ }
12
+ if (Array.isArray(v)) return v.length > 0;
13
+ return true;
14
+ }
15
+ function classifyError(err) {
16
+ const status = err instanceof IntegrationsApiError ? err.status : void 0;
17
+ const message = err instanceof IntegrationsApiError ? err.message : err?.message ?? "Resolve failed";
18
+ let kind;
19
+ switch (status) {
20
+ case 409:
21
+ kind = "building";
22
+ break;
23
+ case 502:
24
+ kind = "runtime";
25
+ break;
26
+ case 403:
27
+ kind = "forbidden";
28
+ break;
29
+ case 404:
30
+ kind = "notfound";
31
+ break;
32
+ default:
33
+ kind = "generic";
34
+ }
35
+ return { kind, message };
36
+ }
37
+ function isAbortError(err) {
38
+ return err instanceof DOMException ? err.name === "AbortError" : err?.name === "AbortError";
39
+ }
40
+ const DEBOUNCE_MS = 300;
41
+ const STALE_MS = 3e4;
42
+ export function useNodeConfigResolve(opts) {
43
+ const api = useIntegrationsApi();
44
+ const data = ref(null);
45
+ const error = ref(null);
46
+ const isFetching = ref(false);
47
+ const hasData = ref(false);
48
+ const unsatisfied = computed(() => {
49
+ const cfg = toValue(opts.config);
50
+ const gateKeys = toValue(opts.dependsOn) ?? [];
51
+ return gateKeys.filter((k) => !isResolvableValue(cfg[k]));
52
+ });
53
+ const gated = computed(() => unsatisfied.value.length > 0);
54
+ const isEnabled = computed(
55
+ () => (toValue(opts.enabled) ?? true) && !gated.value && !!toValue(opts.slug) && !!toValue(opts.version)
56
+ );
57
+ const subsetKey = computed(() => {
58
+ const cfg = toValue(opts.config);
59
+ const dep = toValue(opts.dependsOn);
60
+ const keyKeys = (dep ?? Object.keys(cfg)).slice().sort();
61
+ const subset = {};
62
+ for (const k of keyKeys) subset[k] = cfg[k];
63
+ const rawSearch = toValue(opts.search);
64
+ const search = typeof rawSearch === "string" ? rawSearch.trim().slice(0, 200) : "";
65
+ return JSON.stringify({ config: subset, search: search || void 0 });
66
+ });
67
+ const debouncedKey = ref(subsetKey.value);
68
+ let debounceTimer = null;
69
+ watch(subsetKey, (next) => {
70
+ if (debounceTimer) clearTimeout(debounceTimer);
71
+ debounceTimer = setTimeout(() => {
72
+ debouncedKey.value = next;
73
+ debounceTimer = null;
74
+ }, DEBOUNCE_MS);
75
+ });
76
+ let controller = null;
77
+ let lastFetchedKey = null;
78
+ let lastFetchedAt = 0;
79
+ async function run(key) {
80
+ if (controller) controller.abort();
81
+ controller = new AbortController();
82
+ const signal = controller.signal;
83
+ const slug = toValue(opts.slug);
84
+ const version = toValue(opts.version);
85
+ const target = toValue(opts.target);
86
+ const { config, search } = JSON.parse(key);
87
+ isFetching.value = true;
88
+ try {
89
+ const result = await api.post(
90
+ `/nodes/${encodeURIComponent(slug)}/${encodeURIComponent(version)}/config:resolve`,
91
+ { target, config, ...search ? { search } : {} },
92
+ { signal }
93
+ );
94
+ data.value = result;
95
+ hasData.value = true;
96
+ error.value = null;
97
+ lastFetchedKey = key;
98
+ lastFetchedAt = Date.now();
99
+ } catch (err) {
100
+ if (isAbortError(err) || signal.aborted) return;
101
+ error.value = classifyError(err);
102
+ } finally {
103
+ if (!signal.aborted) isFetching.value = false;
104
+ }
105
+ }
106
+ function maybeFetch(force) {
107
+ if (!isEnabled.value) return;
108
+ const key = debouncedKey.value;
109
+ if (!force && lastFetchedKey === key && Date.now() - lastFetchedAt < STALE_MS && !error.value) {
110
+ return;
111
+ }
112
+ void run(key);
113
+ }
114
+ watch([debouncedKey, isEnabled], () => maybeFetch(false), { immediate: true });
115
+ function refetch() {
116
+ maybeFetch(true);
117
+ }
118
+ onScopeDispose(() => {
119
+ if (debounceTimer) clearTimeout(debounceTimer);
120
+ if (controller) controller.abort();
121
+ });
122
+ return { data, error, isFetching, hasData, gated, unsatisfied, isEnabled, refetch };
123
+ }
@@ -0,0 +1,22 @@
1
+ import type { NodeManifest } from '../types/integrations.js';
2
+ import type { TriggerTemplate } from '../data/integrationNodeCatalog.js';
3
+ /**
4
+ * Shared state of an in-flight palette drag. The HTML5 drag payload
5
+ * (`dataTransfer`) is unreadable during `dragover`, so the palette mirrors
6
+ * WHAT is being dragged here and the editor reads it to render the
7
+ * grid-snapped placement ghost on the canvas.
8
+ */
9
+ export type NodeDragInfo = {
10
+ kind: 'manifest';
11
+ manifest: NodeManifest;
12
+ } | {
13
+ kind: 'trigger';
14
+ tpl: TriggerTemplate;
15
+ } | {
16
+ kind: 'group';
17
+ };
18
+ export declare function useNodeDrag(): {
19
+ dragging: any;
20
+ start: (info: NodeDragInfo) => void;
21
+ end: () => void;
22
+ };
@@ -0,0 +1,10 @@
1
+ export function useNodeDrag() {
2
+ const dragging = useState("integrations.nodeDrag", () => null);
3
+ function start(info) {
4
+ dragging.value = info;
5
+ }
6
+ function end() {
7
+ dragging.value = null;
8
+ }
9
+ return { dragging, start, end };
10
+ }
@@ -0,0 +1,41 @@
1
+ import { type Ref } from 'vue';
2
+ export type PaletteDock = 'left' | 'right';
3
+ export type PaletteMode = 'docked-left' | 'docked-right' | 'floating';
4
+ /**
5
+ * Layout state for the workflow editor's node palette: docked left/right
6
+ * (full-height inlay), free-floating at a canvas position, or collapsed into
7
+ * a pill. `mode` survives collapse, so collapse/expand round-trips to the
8
+ * same placement.
9
+ *
10
+ * Moving uses plain pointer events — HTML5 drag stays reserved for the
11
+ * palette's node rows and the canvas drop. While dragging, coming within
12
+ * DOCK_ZONE of the left/right canvas edge arms a dock preview; release then
13
+ * snaps into the full-height dock, anywhere else keeps the panel floating.
14
+ *
15
+ * Persists to localStorage (guarded — private mode/quota must not throw) and
16
+ * restores clamped to the canvas on mount.
17
+ */
18
+ export declare function usePaletteLayout(canvasRef: Ref<HTMLElement | null>): {
19
+ mode: Ref<PaletteMode, PaletteMode>;
20
+ collapsed: Ref<boolean, boolean>;
21
+ floating: Ref<{
22
+ x: number;
23
+ y: number;
24
+ }, {
25
+ x: number;
26
+ y: number;
27
+ } | {
28
+ x: number;
29
+ y: number;
30
+ }>;
31
+ floatingStyle: import("vue").ComputedRef<{
32
+ left: string;
33
+ top: string;
34
+ }>;
35
+ dragging: Ref<boolean, boolean>;
36
+ dockPreview: Ref<PaletteDock | null, PaletteDock | null>;
37
+ startDrag: (ev: PointerEvent, shellEl: HTMLElement | null) => void;
38
+ dock: (side: PaletteDock) => void;
39
+ floatHere: (shellEl: HTMLElement | null) => void;
40
+ setCollapsed: (v: boolean) => void;
41
+ };
@@ -0,0 +1,113 @@
1
+ import { computed, onBeforeUnmount, onMounted, ref } from "vue";
2
+ const STORAGE_KEY = "integrations.palette.layout";
3
+ const MARGIN = 12;
4
+ const BOTTOM_CLEARANCE = 64;
5
+ const PANEL_WIDTH = 320;
6
+ const DOCK_ZONE = 48;
7
+ function clampNum(v, lo, hi) {
8
+ return Math.min(hi, Math.max(lo, v));
9
+ }
10
+ export function usePaletteLayout(canvasRef) {
11
+ const mode = ref("docked-left");
12
+ const collapsed = ref(false);
13
+ const floating = ref({ x: MARGIN, y: MARGIN });
14
+ const dragging = ref(false);
15
+ const dockPreview = ref(null);
16
+ function persist() {
17
+ try {
18
+ const payload = { mode: mode.value, floating: floating.value, collapsed: collapsed.value };
19
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(payload));
20
+ } catch {
21
+ }
22
+ }
23
+ function restore() {
24
+ try {
25
+ const raw = localStorage.getItem(STORAGE_KEY);
26
+ if (!raw) return;
27
+ const saved = JSON.parse(raw);
28
+ if (saved.mode === "docked-left" || saved.mode === "docked-right" || saved.mode === "floating") {
29
+ mode.value = saved.mode;
30
+ }
31
+ if (typeof saved.floating?.x === "number" && typeof saved.floating?.y === "number") {
32
+ floating.value = { x: saved.floating.x, y: saved.floating.y };
33
+ }
34
+ collapsed.value = saved.collapsed === true;
35
+ clampFloating();
36
+ } catch {
37
+ }
38
+ }
39
+ function clampFloating() {
40
+ const rect = canvasRef.value?.getBoundingClientRect();
41
+ if (!rect || rect.width === 0) return;
42
+ floating.value = {
43
+ x: clampNum(floating.value.x, MARGIN, Math.max(MARGIN, rect.width - PANEL_WIDTH - MARGIN)),
44
+ y: clampNum(floating.value.y, MARGIN, Math.max(MARGIN, rect.height - BOTTOM_CLEARANCE - 160))
45
+ };
46
+ }
47
+ onMounted(restore);
48
+ let grabDX = 0;
49
+ let grabDY = 0;
50
+ let panelHeight = 0;
51
+ function startDrag(ev, shellEl) {
52
+ if (ev.button !== 0 || dragging.value) return;
53
+ if (ev.target?.closest("button")) return;
54
+ const canvas = canvasRef.value;
55
+ if (!canvas || !shellEl) return;
56
+ const cRect = canvas.getBoundingClientRect();
57
+ const pRect = shellEl.getBoundingClientRect();
58
+ floating.value = { x: pRect.left - cRect.left, y: pRect.top - cRect.top };
59
+ mode.value = "floating";
60
+ grabDX = ev.clientX - pRect.left;
61
+ grabDY = ev.clientY - pRect.top;
62
+ panelHeight = Math.min(pRect.height, window.innerHeight * 0.7);
63
+ dragging.value = true;
64
+ ev.preventDefault();
65
+ window.addEventListener("pointermove", onDragMove);
66
+ window.addEventListener("pointerup", onDragEnd);
67
+ window.addEventListener("pointercancel", onDragEnd);
68
+ }
69
+ function onDragMove(ev) {
70
+ const rect = canvasRef.value?.getBoundingClientRect();
71
+ if (!rect) return;
72
+ const x = clampNum(ev.clientX - rect.left - grabDX, MARGIN, Math.max(MARGIN, rect.width - PANEL_WIDTH - MARGIN));
73
+ const y = clampNum(ev.clientY - rect.top - grabDY, MARGIN, Math.max(MARGIN, rect.height - BOTTOM_CLEARANCE - panelHeight));
74
+ floating.value = { x, y };
75
+ dockPreview.value = x <= DOCK_ZONE ? "left" : x + PANEL_WIDTH >= rect.width - DOCK_ZONE ? "right" : null;
76
+ }
77
+ function stopListeners() {
78
+ window.removeEventListener("pointermove", onDragMove);
79
+ window.removeEventListener("pointerup", onDragEnd);
80
+ window.removeEventListener("pointercancel", onDragEnd);
81
+ }
82
+ function onDragEnd() {
83
+ if (dockPreview.value) mode.value = dockPreview.value === "left" ? "docked-left" : "docked-right";
84
+ dockPreview.value = null;
85
+ dragging.value = false;
86
+ stopListeners();
87
+ persist();
88
+ }
89
+ onBeforeUnmount(stopListeners);
90
+ function dock(side) {
91
+ mode.value = side === "left" ? "docked-left" : "docked-right";
92
+ persist();
93
+ }
94
+ function floatHere(shellEl) {
95
+ if (mode.value !== "floating") {
96
+ const cRect = canvasRef.value?.getBoundingClientRect();
97
+ const pRect = shellEl?.getBoundingClientRect();
98
+ if (cRect && pRect) floating.value = { x: pRect.left - cRect.left, y: pRect.top - cRect.top };
99
+ mode.value = "floating";
100
+ clampFloating();
101
+ }
102
+ persist();
103
+ }
104
+ function setCollapsed(v) {
105
+ collapsed.value = v;
106
+ persist();
107
+ }
108
+ const floatingStyle = computed(() => ({
109
+ left: `${floating.value.x}px`,
110
+ top: `${floating.value.y}px`
111
+ }));
112
+ return { mode, collapsed, floating, floatingStyle, dragging, dockPreview, startDrag, dock, floatHere, setCollapsed };
113
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Round-trip between the raw expression text (the single source of truth
3
+ * stored in the workflow blob) and the TipTap document the inline pill editor
4
+ * renders. Pure JSON transforms — no editor dependency, fully testable.
5
+ */
6
+ export interface InlineNodeJSON {
7
+ type: string;
8
+ text?: string;
9
+ attrs?: Record<string, unknown>;
10
+ content?: InlineNodeJSON[];
11
+ }
12
+ export declare const EXPR_TOKEN_NODE = "exprToken";
13
+ /** Raw text → inline content: text nodes with one atom pill per token. */
14
+ export declare function textToInlineContent(text: string): InlineNodeJSON[];
15
+ /** Raw text → a full single-paragraph TipTap doc. */
16
+ export declare function textToDoc(text: string): InlineNodeJSON;
17
+ /** TipTap doc JSON → raw text (pills serialize back to their `${{ … }}` token).
18
+ * Multiple paragraphs (pasted newlines) collapse to single spaces — the
19
+ * fields are single-line. */
20
+ export declare function docToText(doc: InlineNodeJSON | null | undefined): string;
21
+ /** True when any TEXT node still carries a complete raw token (needs re-pilling). */
22
+ export declare function hasRawTokenText(doc: InlineNodeJSON | null | undefined): boolean;
@@ -0,0 +1,40 @@
1
+ import { listExpressionTokens } from "./nodeRunData.js";
2
+ export const EXPR_TOKEN_NODE = "exprToken";
3
+ export function textToInlineContent(text) {
4
+ const out = [];
5
+ let cursor = 0;
6
+ for (const t of listExpressionTokens(text)) {
7
+ if (t.start > cursor) out.push({ type: "text", text: text.slice(cursor, t.start) });
8
+ out.push({ type: EXPR_TOKEN_NODE, attrs: { token: t.token } });
9
+ cursor = t.end;
10
+ }
11
+ if (cursor < text.length) out.push({ type: "text", text: text.slice(cursor) });
12
+ return out;
13
+ }
14
+ export function textToDoc(text) {
15
+ const content = textToInlineContent(text);
16
+ return { type: "doc", content: [content.length ? { type: "paragraph", content } : { type: "paragraph" }] };
17
+ }
18
+ export function docToText(doc) {
19
+ if (!doc) return "";
20
+ const inlineText = (p) => {
21
+ let s = "";
22
+ for (const child of p.content ?? []) {
23
+ if (child.type === "text") s += child.text ?? "";
24
+ else if (child.type === EXPR_TOKEN_NODE) s += String(child.attrs?.token ?? "");
25
+ else s += inlineText(child);
26
+ }
27
+ return s;
28
+ };
29
+ return (doc.content ?? []).filter((n) => n.type === "paragraph").map(inlineText).join(" ");
30
+ }
31
+ export function hasRawTokenText(doc) {
32
+ if (!doc) return false;
33
+ const stack = [doc];
34
+ while (stack.length) {
35
+ const node = stack.pop();
36
+ if (node.type === "text" && listExpressionTokens(node.text ?? "").length > 0) return true;
37
+ for (const child of node.content ?? []) stack.push(child);
38
+ }
39
+ return false;
40
+ }