@integry/sdk 4.6.17 → 4.6.18
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/.vscode/launch.json +2 -2
- package/dist/esm/index.csm.d.ts +1 -0
- package/dist/esm/index.csm.js +1 -1
- package/dist/umd/index.umd.d.ts +1 -0
- package/dist/umd/index.umd.js +1 -1
- package/package.json +1 -1
- package/src/components/MultipurposeField/TagMenu/index.ts +16 -5
- package/src/components/MultipurposeField/TagMenu/styles.module.scss +7 -0
- package/src/components/MultipurposeField/index.tsx +34 -8
- package/src/components/MultipurposeField/styles.module.scss +1 -1
- package/src/components/TagsMenu/index.ts +1123 -0
- package/src/components/TagsMenu/styles.module.scss +15 -6
- package/src/features/common/ActionForm/index.ts +11 -0
- package/src/index.ts +2 -0
- package/src/types/index.ts +1 -0
|
@@ -0,0 +1,1123 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { html } from 'htm/preact';
|
|
4
|
+
import { useState, useEffect } from 'preact/hooks';
|
|
5
|
+
import styles from './styles.module.scss';
|
|
6
|
+
|
|
7
|
+
// Update the TagNode interface to better handle various types
|
|
8
|
+
interface TagNode {
|
|
9
|
+
name?: string; // Step name or field name
|
|
10
|
+
machineName?: string; // Machine-readable name for the step
|
|
11
|
+
appIcon?: string; // URL for the step's app icon
|
|
12
|
+
output?: Record<string, unknown> | unknown[]; // JSON data for step output
|
|
13
|
+
tags?: Record<string, TagNode>; // Nested tags or fields
|
|
14
|
+
[key: string]: unknown; // Allow for additional properties
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Define the structure of the entire data object
|
|
18
|
+
interface TagData {
|
|
19
|
+
[key: string]: TagNode;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Define the props for the TagMenu component
|
|
23
|
+
interface TagMenuProps {
|
|
24
|
+
data: TagData;
|
|
25
|
+
onSelect: (tag: TagNode) => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface TagListProps {
|
|
29
|
+
tags: Record<string, unknown> | unknown[];
|
|
30
|
+
searchTerm: string;
|
|
31
|
+
level?: number;
|
|
32
|
+
path?: string;
|
|
33
|
+
globalSearchMode?: boolean;
|
|
34
|
+
onSelect?: (tag: Record<string, unknown>) => void;
|
|
35
|
+
activeTab?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Helper function to convert nested tags arrays to objects
|
|
39
|
+
function convertNestedTagsToObject(tags: TagNode[]): Record<string, TagNode> {
|
|
40
|
+
const result: Record<string, TagNode> = {};
|
|
41
|
+
|
|
42
|
+
tags.forEach((tag, index) => {
|
|
43
|
+
const tagKey = tag.machineName || `[${index}]`;
|
|
44
|
+
|
|
45
|
+
if (tag.tags && Array.isArray(tag.tags)) {
|
|
46
|
+
result[tagKey] = {
|
|
47
|
+
...tag,
|
|
48
|
+
tags: convertNestedTagsToObject(tag.tags),
|
|
49
|
+
};
|
|
50
|
+
} else {
|
|
51
|
+
result[tagKey] = tag;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Add a type guard function to check if a value is a TagNode
|
|
59
|
+
function isTagNode(value: unknown): value is TagNode {
|
|
60
|
+
return (
|
|
61
|
+
value !== null &&
|
|
62
|
+
typeof value === 'object' &&
|
|
63
|
+
!Array.isArray(value) &&
|
|
64
|
+
Object.prototype.hasOwnProperty.call(value, 'machineName')
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Update the arrayToObject function with proper typing
|
|
69
|
+
const arrayToObject = (arr: unknown[]): Record<string, unknown> =>
|
|
70
|
+
arr.reduce<Record<string, unknown>>((obj, item, index) => {
|
|
71
|
+
const newObj = { ...obj };
|
|
72
|
+
newObj[`[${index}]`] = item;
|
|
73
|
+
return newObj;
|
|
74
|
+
}, {});
|
|
75
|
+
// Check if output data matches the search term
|
|
76
|
+
const outputMatchesSearch = (output: unknown, term: string): boolean => {
|
|
77
|
+
if (!term || !output) return false;
|
|
78
|
+
const lowerTerm = term.toLowerCase();
|
|
79
|
+
|
|
80
|
+
if (typeof output === 'string') {
|
|
81
|
+
return output.toLowerCase().includes(lowerTerm);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (Array.isArray(output)) {
|
|
85
|
+
return output.some(
|
|
86
|
+
(item) =>
|
|
87
|
+
(typeof item === 'string' && item.toLowerCase().includes(lowerTerm)) ||
|
|
88
|
+
(typeof item === 'object' &&
|
|
89
|
+
item !== null &&
|
|
90
|
+
outputMatchesSearch(item, lowerTerm)),
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (typeof output === 'object' && output !== null) {
|
|
95
|
+
return Object.entries(output as Record<string, unknown>).some(
|
|
96
|
+
([key, val]) => {
|
|
97
|
+
if (key.toLowerCase().includes(lowerTerm)) return true;
|
|
98
|
+
|
|
99
|
+
if (typeof val === 'string') {
|
|
100
|
+
return val.toLowerCase().includes(lowerTerm);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (typeof val === 'object' && val !== null) {
|
|
104
|
+
return outputMatchesSearch(val, lowerTerm);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return false;
|
|
108
|
+
},
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return false;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// Function to count matches in a generic object
|
|
116
|
+
const countMatchesInObject = (obj: unknown, term: string): number => {
|
|
117
|
+
if (!obj || typeof obj !== 'object') return 0;
|
|
118
|
+
|
|
119
|
+
let count = 0;
|
|
120
|
+
|
|
121
|
+
// Handle arrays specifically
|
|
122
|
+
if (Array.isArray(obj)) {
|
|
123
|
+
obj.forEach((item) => {
|
|
124
|
+
if (typeof item === 'string' && item.toLowerCase().includes(term)) {
|
|
125
|
+
count += 1;
|
|
126
|
+
} else if (typeof item === 'object' && item !== null) {
|
|
127
|
+
count += countMatchesInObject(item, term);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
return count;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
Object.entries(obj as Record<string, unknown>).forEach(([key, value]) => {
|
|
134
|
+
if (key.toLowerCase().includes(term)) {
|
|
135
|
+
count += 1;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (typeof value === 'string' && value.toLowerCase().includes(term)) {
|
|
139
|
+
count += 1;
|
|
140
|
+
} else if (typeof value === 'object' && value !== null) {
|
|
141
|
+
count += countMatchesInObject(value, term);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
return count;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// Count matches in a nested structure
|
|
149
|
+
const countSearchMatches = (
|
|
150
|
+
tags: Record<string, TagNode>,
|
|
151
|
+
term: string,
|
|
152
|
+
): number => {
|
|
153
|
+
let count = 0;
|
|
154
|
+
|
|
155
|
+
Object.entries(tags).forEach(([key, value]) => {
|
|
156
|
+
// Check if the key, name, or machineName matches
|
|
157
|
+
if (
|
|
158
|
+
key.toLowerCase().includes(term) ||
|
|
159
|
+
(isTagNode(value) &&
|
|
160
|
+
value.name &&
|
|
161
|
+
value.name.toLowerCase().includes(term)) ||
|
|
162
|
+
(isTagNode(value) &&
|
|
163
|
+
value.machineName &&
|
|
164
|
+
value.machineName.toLowerCase().includes(term))
|
|
165
|
+
) {
|
|
166
|
+
count += 1;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Check output data for matches
|
|
170
|
+
if (value.output) {
|
|
171
|
+
count += countMatchesInObject(value.output, term);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Check nested tags
|
|
175
|
+
if (value.tags) {
|
|
176
|
+
count += countSearchMatches(value.tags, term);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
return count;
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
// Type icon component to render different icons based on data type
|
|
184
|
+
const TypeIcon = ({ value }: { value: unknown }) => {
|
|
185
|
+
// Determine the type of value
|
|
186
|
+
const getTypeIcon = (iconValue: unknown) => {
|
|
187
|
+
if (iconValue === null || iconValue === undefined) {
|
|
188
|
+
return html`
|
|
189
|
+
<svg
|
|
190
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
191
|
+
width="24"
|
|
192
|
+
height="24"
|
|
193
|
+
viewBox="0 0 24 24"
|
|
194
|
+
fill="none"
|
|
195
|
+
stroke="currentColor"
|
|
196
|
+
strokeWidth="2"
|
|
197
|
+
strokeLinecap="round"
|
|
198
|
+
strokeLinejoin="round"
|
|
199
|
+
class="lucide lucide-ban h-4 w-4"
|
|
200
|
+
>
|
|
201
|
+
<circle cx="12" cy="12" r="10"></circle>
|
|
202
|
+
<path d="m4.93 4.93 14.14 14.14"></path>
|
|
203
|
+
</svg>
|
|
204
|
+
`;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (typeof iconValue === 'string') {
|
|
208
|
+
return html`
|
|
209
|
+
<img
|
|
210
|
+
src="data:image/svg+xml,%3csvg%20width='23'%20height='15'%20viewBox='0%200%2023%2015'%20fill='none'%20xmlns='http://www.w3.org/2000/svg'%3e%3cpath%20fill-rule='evenodd'%20clip-rule='evenodd'%20d='M4.12408%2010.2498H9.09622L9.5856%2011.7004H9.08601C8.59375%2011.7004%208.24266%2011.8106%208.03275%2012.0311C7.82284%2012.2516%207.71835%2012.536%207.71835%2012.8844C7.71835%2013.24%207.82284%2013.5289%208.03275%2013.7493C8.24266%2013.9698%208.59375%2014.08%209.08601%2014.08H11.8424C12.3347%2014.08%2012.6839%2013.9698%2012.8902%2013.7493C13.0964%2013.5289%2013.2%2013.2444%2013.2%2012.896C13.2%2012.4782%2013.0469%2012.1413%2012.7408%2011.8862C12.5877%2011.7627%2012.2714%2011.7004%2011.792%2011.7004L7.71839%200.0115555L3.29537%200C2.80219%200%202.45111%200.110217%202.2412%200.330662C2.03221%200.551119%201.92679%200.840001%201.92679%201.19556C1.92679%201.55111%202.0322%201.84001%202.2412%202.06045C2.45111%202.28089%202.80219%202.39112%203.29537%202.39112H4.63279L1.41807%2011.7004C0.912084%2011.6853%200.549076%2011.7893%200.329075%2012.0142C0.110001%2012.2382%200%2012.5289%200%2012.8844C0%2013.24%200.104491%2013.5289%200.314408%2013.7493C0.524319%2013.9698%200.875405%2014.08%201.36767%2014.08H4.12408C4.61634%2014.08%204.96743%2013.9698%205.17734%2013.7493C5.38725%2013.5289%205.49175%2013.2444%205.49175%2012.896C5.49175%2012.5404%205.38725%2012.2515%205.17734%2012.0311C4.96743%2011.8107%204.61634%2011.7004%204.12408%2011.7004H3.62449L4.12408%2010.2498ZM6.60013%203.1573L8.24738%207.87022H4.94279L6.60013%203.1573Z'%20fill='%23999999'/%3e%3cpath%20fill-rule='evenodd'%20clip-rule='evenodd'%20d='M19.523%2013.1856V13.7089L21.6596%2013.7089C22.0995%2013.7089%2022.4124%2013.6051%2022.5991%2013.3983C22.7866%2013.1916%2022.88%2012.9248%2022.88%2012.597C22.88%2012.2633%2022.7866%2011.9922%2022.5991%2011.7855C22.4124%2011.5787%2022.0995%2011.4749%2021.6596%2011.4749H21.3485V6.87643C21.3485%205.83759%2021.0347%205.01831%2020.409%204.41862C19.7823%203.8198%2018.9025%203.51953%2017.7685%203.51953C17.3883%203.51953%2016.9639%203.565%2016.4946%203.65594C16.0262%203.74688%2015.5655%203.86871%2015.1143%204.02141C14.7764%204.13723%2014.5534%204.23761%2014.4471%204.32082C14.34%204.40404%2014.257%204.51471%2014.1974%204.65283C14.1377%204.79095%2014.1083%204.95481%2014.1083%205.14355C14.1083%205.47813%2014.193%205.75181%2014.3624%205.96628C14.531%206.18075%2014.7375%206.28799%2014.9813%206.28799C15.1593%206.28799%2015.4082%206.2408%2015.7289%206.14643C16.5897%205.88477%2017.2906%205.75351%2017.8308%205.75351C18.4894%205.75351%2018.9362%205.85903%2019.1713%206.07007C19.4055%206.28027%2019.523%206.55309%2019.523%206.88681V7.42128C18.7988%207.27544%2018.1601%207.20337%2017.6078%207.20337C16.231%207.20337%2015.1515%207.60489%2014.3711%208.40702C13.5906%209.21007%2013.2%2010.08%2013.2%2011.0177C13.2%2011.7872%2013.5059%2012.4907%2014.1169%2013.1264C14.7288%2013.7621%2015.524%2014.0795%2016.5041%2014.0795C16.9552%2014.0795%2017.4565%2014.0049%2018.0088%2013.8565C18.561%2013.7072%2019.0658%2013.4842%2019.523%2013.1856ZM17.5015%209.4477C18.1185%209.4477%2018.7927%209.53521%2019.523%209.70936V10.6685C19.0891%2011.0322%2018.5948%2011.3188%2018.0399%2011.5298C17.485%2011.74%2016.9872%2011.8455%2016.5482%2011.8455C16.0322%2011.8455%2015.6251%2011.7254%2015.3287%2011.4861C15.1679%2011.3548%2015.0875%2011.1841%2015.0875%2010.9739C15.0875%2010.6831%2015.2543%2010.3965%2015.5862%2010.1126C16.1212%209.66989%2016.759%209.4477%2017.5015%209.4477Z'%20fill='%23999999'/%3e%3c/svg%3e"
|
|
211
|
+
alt="alt-icon"
|
|
212
|
+
class="type-icon"
|
|
213
|
+
/>
|
|
214
|
+
`;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (typeof iconValue === 'number') {
|
|
218
|
+
return html`
|
|
219
|
+
<img
|
|
220
|
+
src="data:image/svg+xml,%3csvg%20width='15'%20height='8'%20viewBox='0%200%2015%208'%20fill='none'%20xmlns='http://www.w3.org/2000/svg'%3e%3cpath%20d='M3.37681%207.86147H1.83754V1.92808L0%202.49799V1.24633L3.2117%200.0958716H3.37681V7.86147Z'%20fill='%23999999'/%3e%3cpath%20d='M9.39897%207.86147H4.08342V6.80688L6.59206%204.13313C6.93649%203.75675%207.19037%203.4283%207.35371%203.14778C7.5206%202.86727%207.60404%202.60096%207.60404%202.34885C7.60404%202.00443%207.51704%201.73457%207.34305%201.53927C7.16907%201.34043%206.92051%201.241%206.59739%201.241C6.24941%201.241%205.97422%201.36173%205.77183%201.60319C5.57298%201.84109%205.47356%202.15534%205.47356%202.54592H3.92896C3.92896%202.07367%204.04081%201.64225%204.26451%201.25166C4.49176%200.861069%204.81133%200.5557%205.22323%200.335551C5.63512%200.11185%206.10205%200%206.62402%200C7.42295%200%208.04256%200.191743%208.48286%200.57523C8.92671%200.958716%209.14864%201.50021%209.14864%202.19972C9.14864%202.58321%209.04921%202.9738%208.85037%203.37148C8.65152%203.76917%208.31065%204.23255%207.82774%204.76162L6.06477%206.62047H9.39897V7.86147Z'%20fill='%23999999'/%3e%3cpath%20d='M11.3675%203.30224H12.1878C12.5784%203.30224%2012.8677%203.2046%2013.0559%203.0093C13.2441%202.81401%2013.3382%202.5548%2013.3382%202.23168C13.3382%201.91921%2013.2441%201.67598%2013.0559%201.50199C12.8713%201.328%2012.6156%201.241%2012.289%201.241C11.9942%201.241%2011.7475%201.32267%2011.5486%201.48601C11.3498%201.6458%2011.2504%201.85529%2011.2504%202.1145H9.71108C9.71108%201.70971%209.81938%201.34753%2010.036%201.02796C10.2561%200.704834%2010.5615%200.452727%2010.9521%200.271636C11.3462%200.0905454%2011.7794%200%2012.2517%200C13.0719%200%2013.7146%200.197069%2014.1798%200.591208C14.6449%200.981796%2014.8775%201.52152%2014.8775%202.21037C14.8775%202.56545%2014.7692%202.89213%2014.5526%203.19039C14.336%203.48866%2014.0519%203.71769%2013.7004%203.87747C14.1372%204.03371%2014.4621%204.26806%2014.6751%204.58053C14.8917%204.893%2015%205.26229%2015%205.68838C15%206.37724%2014.7479%206.92939%2014.2437%207.34483C13.743%207.76027%2013.079%207.968%2012.2517%207.968C11.4776%207.968%2010.8438%207.76382%2010.3502%207.35548C9.86022%206.94714%209.61521%206.40742%209.61521%205.73632H11.1545C11.1545%206.02748%2011.2628%206.26539%2011.4794%206.45003C11.6995%206.63467%2011.9694%206.72699%2012.289%206.72699C12.6547%206.72699%2012.9405%206.63112%2013.1465%206.43938C13.356%206.24408%2013.4607%205.98665%2013.4607%205.66708C13.4607%204.893%2013.0346%204.50597%2012.1824%204.50597H11.3675V3.30224Z'%20fill='%23999999'/%3e%3c/svg%3e"
|
|
221
|
+
alt="alt-icon"
|
|
222
|
+
class="type-icon"
|
|
223
|
+
/>
|
|
224
|
+
`;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (typeof iconValue === 'boolean') {
|
|
228
|
+
// Single icon for all boolean values
|
|
229
|
+
return html`
|
|
230
|
+
<svg
|
|
231
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
232
|
+
width="24"
|
|
233
|
+
height="24"
|
|
234
|
+
viewBox="0 0 24 24"
|
|
235
|
+
fill="none"
|
|
236
|
+
stroke="#71717a"
|
|
237
|
+
strokeWidth="2"
|
|
238
|
+
strokeLinecap="round"
|
|
239
|
+
strokeLinejoin="round"
|
|
240
|
+
class="lucide lucide-toggle-right h-4 w-4"
|
|
241
|
+
>
|
|
242
|
+
<rect width="20" height="12" x="2" y="6" rx="6" ry="6"></rect>
|
|
243
|
+
<circle cx="16" cy="12" r="2"></circle>
|
|
244
|
+
</svg>
|
|
245
|
+
`;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (Array.isArray(iconValue)) {
|
|
249
|
+
return html`
|
|
250
|
+
<svg
|
|
251
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
252
|
+
width="24"
|
|
253
|
+
height="24"
|
|
254
|
+
viewBox="0 0 24 24"
|
|
255
|
+
fill="none"
|
|
256
|
+
stroke="#71717a"
|
|
257
|
+
stroke-width="2"
|
|
258
|
+
stroke-linecap="round"
|
|
259
|
+
stroke-linejoin="round"
|
|
260
|
+
class="lucide lucide-layers"
|
|
261
|
+
>
|
|
262
|
+
<path
|
|
263
|
+
d="M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83z"
|
|
264
|
+
/>
|
|
265
|
+
<path
|
|
266
|
+
d="M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12"
|
|
267
|
+
/>
|
|
268
|
+
<path
|
|
269
|
+
d="M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17"
|
|
270
|
+
/>
|
|
271
|
+
</svg>
|
|
272
|
+
`;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (typeof iconValue === 'object') {
|
|
276
|
+
return html`
|
|
277
|
+
<img
|
|
278
|
+
src="data:image/svg+xml,%3csvg%20width='14'%20height='15'%20viewBox='0%200%2014%2015'%20fill='none'%20xmlns='http://www.w3.org/2000/svg'%3e%3cpath%20d='M12.8419%203.40522L7.78708%200.494922C7.3822%200.269922%206.88708%200.269922%206.49684%200.494922L1.44199%203.40522C1.03711%203.63022%200.796875%204.06498%200.796875%204.53022V10.3649C0.796875%2010.8302%201.05175%2011.2497%201.44199%2011.4899L6.49684%2014.3996C6.69197%2014.5197%206.91696%2014.5795%207.14196%2014.5795C7.36696%2014.5795%207.59196%2014.5197%207.78708%2014.3996L12.8419%2011.4746C13.2468%2011.2496%2013.4871%2010.8149%2013.4871%2010.3496V4.53007C13.4871%204.06483%2013.2322%203.63007%2012.8419%203.40507V3.40522ZM7.00723%201.38022C7.05235%201.35034%207.09747%201.35034%207.142%201.35034C7.18712%201.35034%207.23223%201.36557%207.27677%201.38022L11.9421%204.08022L7.14207%206.83992L2.34207%204.08022L7.00723%201.38022ZM1.96693%2010.5899C1.89193%2010.5448%201.83217%2010.4552%201.83217%2010.3649V4.96492L6.63217%207.73992V13.2746L1.96693%2010.5899ZM12.3169%2010.5899L7.65163%2013.2899V7.73992L12.4516%204.96492V10.3496C12.4516%2010.4551%2012.4071%2010.5301%2012.3169%2010.5899L12.3169%2010.5899Z'%20fill='%23999999'/%3e%3c/svg%3e"
|
|
279
|
+
alt="alt-icon"
|
|
280
|
+
class="type-icon"
|
|
281
|
+
/>
|
|
282
|
+
`;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Default tag icon for any other type
|
|
286
|
+
return html`
|
|
287
|
+
<svg
|
|
288
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
289
|
+
width="24"
|
|
290
|
+
height="24"
|
|
291
|
+
viewBox="0 0 24 24"
|
|
292
|
+
fill="none"
|
|
293
|
+
stroke="currentColor"
|
|
294
|
+
strokeWidth="2"
|
|
295
|
+
strokeLinecap="round"
|
|
296
|
+
strokeLinejoin="round"
|
|
297
|
+
class="lucide lucide-tag h-4 w-4"
|
|
298
|
+
>
|
|
299
|
+
<path
|
|
300
|
+
d="M12.586 2.586A2 2 0 0 0 11.172 2H4a2 2 0 0 0-2 2v7.172a2 2 0 0 0 .586 1.414l8.704 8.704a2.426 2.426 0 0 0 3.42 0l6.58-6.58a2.426 2.426 0 0 0 0-3.42z"
|
|
301
|
+
></path>
|
|
302
|
+
<circle cx="7.5" cy="7.5" r=".5" fill="currentColor"></circle>
|
|
303
|
+
</svg>
|
|
304
|
+
`;
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
return html`<span class="${styles.tagIcon}">${getTypeIcon(value)}</span>`;
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
const TagList = ({
|
|
311
|
+
tags,
|
|
312
|
+
searchTerm,
|
|
313
|
+
level = 0,
|
|
314
|
+
path = '',
|
|
315
|
+
globalSearchMode = false,
|
|
316
|
+
onSelect = (tag: Record<string, unknown>): void => {
|
|
317
|
+
/* Default empty implementation */
|
|
318
|
+
},
|
|
319
|
+
activeTab = '',
|
|
320
|
+
}: TagListProps) => {
|
|
321
|
+
// Track which nodes are expanded to show nested steps
|
|
322
|
+
const [expandedNodes, setExpandedNodes] = useState<Record<string, boolean>>(
|
|
323
|
+
{},
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
// Track which steps have their output expanded
|
|
327
|
+
const [expandedOutputs, setExpandedOutputs] = useState<
|
|
328
|
+
Record<string, boolean>
|
|
329
|
+
>({});
|
|
330
|
+
|
|
331
|
+
// Handle arrays by converting them to objects with indices as keys
|
|
332
|
+
const processedTags = Array.isArray(tags)
|
|
333
|
+
? arrayToObject(tags)
|
|
334
|
+
: (tags as Record<string, unknown>);
|
|
335
|
+
|
|
336
|
+
// Update the nodeMatchesSearch function with proper type checking
|
|
337
|
+
const nodeMatchesSearch = (
|
|
338
|
+
key: string,
|
|
339
|
+
node: unknown,
|
|
340
|
+
term: string,
|
|
341
|
+
): boolean => {
|
|
342
|
+
if (!term) return false;
|
|
343
|
+
const lowerTerm = term.toLowerCase();
|
|
344
|
+
|
|
345
|
+
return (
|
|
346
|
+
key?.toLowerCase().includes(lowerTerm) ||
|
|
347
|
+
(isTagNode(node) &&
|
|
348
|
+
node.name &&
|
|
349
|
+
node.name.toLowerCase().includes(lowerTerm)) ||
|
|
350
|
+
(isTagNode(node) &&
|
|
351
|
+
node.machineName &&
|
|
352
|
+
node.machineName.toLowerCase().includes(lowerTerm)) ||
|
|
353
|
+
false
|
|
354
|
+
);
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
// Update the childrenMatchSearch function with proper type checking
|
|
358
|
+
const childrenMatchSearch = (node: unknown, term: string): boolean => {
|
|
359
|
+
if (!term) return false;
|
|
360
|
+
|
|
361
|
+
// Check if node is an array
|
|
362
|
+
if (Array.isArray(node)) {
|
|
363
|
+
return node.some(
|
|
364
|
+
(item) =>
|
|
365
|
+
(typeof item === 'string' && item.toLowerCase().includes(term)) ||
|
|
366
|
+
(typeof item === 'object' &&
|
|
367
|
+
item !== null &&
|
|
368
|
+
outputMatchesSearch(item, term)),
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Check if node has tags
|
|
373
|
+
if (isTagNode(node) && node.tags) {
|
|
374
|
+
return Object.entries(node.tags).some(
|
|
375
|
+
([childKey, childNode]) =>
|
|
376
|
+
nodeMatchesSearch(childKey, childNode, term) ||
|
|
377
|
+
childrenMatchSearch(childNode, term) ||
|
|
378
|
+
(isTagNode(childNode) &&
|
|
379
|
+
childNode.output &&
|
|
380
|
+
outputMatchesSearch(childNode.output, term)),
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return false;
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
// Add the isExpandable function with proper type checking
|
|
388
|
+
const isExpandable = (value: unknown): boolean => {
|
|
389
|
+
if (value === null || value === undefined) return false;
|
|
390
|
+
|
|
391
|
+
// Arrays and objects with properties are expandable
|
|
392
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
393
|
+
if (typeof value === 'object') {
|
|
394
|
+
// Check if it's a TagNode with tags
|
|
395
|
+
if (isTagNode(value) && value.tags)
|
|
396
|
+
return Object.keys(value.tags).length > 0;
|
|
397
|
+
// Regular object with properties
|
|
398
|
+
return Object.keys(value as Record<string, unknown>).length > 0;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return false;
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
// Initialize expandedNodes for steps with nested steps and handle search expansion
|
|
405
|
+
useEffect(() => {
|
|
406
|
+
const initialExpanded: Record<string, boolean> = {};
|
|
407
|
+
const initialOutputs: Record<string, boolean> = {};
|
|
408
|
+
|
|
409
|
+
Object.entries(processedTags).forEach(([key, value]) => {
|
|
410
|
+
// Auto-expand steps that have nested steps
|
|
411
|
+
if (
|
|
412
|
+
isTagNode(value) &&
|
|
413
|
+
value.tags &&
|
|
414
|
+
Object.keys(value.tags).length > 0
|
|
415
|
+
) {
|
|
416
|
+
initialExpanded[key] = true;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// If in search mode, check if this node or its children match the search
|
|
420
|
+
if (
|
|
421
|
+
searchTerm &&
|
|
422
|
+
(nodeMatchesSearch(key, value, searchTerm) ||
|
|
423
|
+
childrenMatchSearch(value, searchTerm))
|
|
424
|
+
) {
|
|
425
|
+
initialExpanded[key] = true;
|
|
426
|
+
|
|
427
|
+
// If the node has output and the output matches the search, expand the output too
|
|
428
|
+
if (
|
|
429
|
+
isTagNode(value) &&
|
|
430
|
+
value.output &&
|
|
431
|
+
outputMatchesSearch(value.output, searchTerm)
|
|
432
|
+
) {
|
|
433
|
+
initialOutputs[key] = true;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
setExpandedNodes((prev) => ({ ...prev, ...initialExpanded }));
|
|
439
|
+
setExpandedOutputs((prev) => ({ ...prev, ...initialOutputs }));
|
|
440
|
+
}, [processedTags, searchTerm]);
|
|
441
|
+
|
|
442
|
+
// Update the toggleExpand function with proper type checking
|
|
443
|
+
const toggleExpand = (key: string) => {
|
|
444
|
+
// For steps with nested steps, we don't allow collapsing
|
|
445
|
+
const node = processedTags[key];
|
|
446
|
+
if (isTagNode(node) && node.tags && Object.keys(node.tags).length > 0) {
|
|
447
|
+
// Instead, toggle the output expansion
|
|
448
|
+
setExpandedOutputs((prev) => ({
|
|
449
|
+
...prev,
|
|
450
|
+
[key]: !prev[key],
|
|
451
|
+
}));
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// For regular nodes or steps without nested steps, toggle normal expansion
|
|
456
|
+
setExpandedNodes((prev) => ({
|
|
457
|
+
...prev,
|
|
458
|
+
[key]: !prev[key],
|
|
459
|
+
}));
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
const toggleOutputExpand = (key: string, e: Event) => {
|
|
463
|
+
e.stopPropagation(); // Prevent the click from affecting the parent
|
|
464
|
+
setExpandedOutputs((prev) => ({
|
|
465
|
+
...prev,
|
|
466
|
+
[key]: !prev[key],
|
|
467
|
+
}));
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
const highlightSearchTerm = (text: string, term: string) => {
|
|
471
|
+
if (!term) return text;
|
|
472
|
+
const regex = new RegExp(`(${term})`, 'gi');
|
|
473
|
+
return text.replace(regex, '<mark>$1</mark>');
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
// Update the filterTags function with proper type checking
|
|
477
|
+
const filterTags = (
|
|
478
|
+
tagsToFilter: Record<string, unknown> | unknown[],
|
|
479
|
+
termToSearch: string,
|
|
480
|
+
pathPrefix: string,
|
|
481
|
+
): Record<string, unknown> | unknown[] => {
|
|
482
|
+
if (!termToSearch) return tagsToFilter;
|
|
483
|
+
|
|
484
|
+
// Handle arrays
|
|
485
|
+
if (Array.isArray(tagsToFilter)) {
|
|
486
|
+
const filteredArray = tagsToFilter.filter((item, index) => {
|
|
487
|
+
const currentPath = pathPrefix
|
|
488
|
+
? `${pathPrefix}[${index}]`
|
|
489
|
+
: `[${index}]`;
|
|
490
|
+
|
|
491
|
+
// Check if this item matches the search
|
|
492
|
+
if (
|
|
493
|
+
typeof item === 'string' &&
|
|
494
|
+
item.toLowerCase().includes(termToSearch.toLowerCase())
|
|
495
|
+
) {
|
|
496
|
+
return true;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Check if this is an object that matches
|
|
500
|
+
if (typeof item === 'object' && item !== null) {
|
|
501
|
+
const filteredItem = filterTags(
|
|
502
|
+
item as Record<string, unknown>,
|
|
503
|
+
termToSearch,
|
|
504
|
+
currentPath,
|
|
505
|
+
);
|
|
506
|
+
return (
|
|
507
|
+
(typeof filteredItem === 'object' &&
|
|
508
|
+
Object.keys(filteredItem as Record<string, unknown>).length >
|
|
509
|
+
0) ||
|
|
510
|
+
outputMatchesSearch(item, termToSearch)
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
return false;
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
return filteredArray.length > 0 ? arrayToObject(filteredArray) : {};
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const filteredTags: Record<string, unknown> = {};
|
|
521
|
+
const lowerTerm = termToSearch.toLowerCase();
|
|
522
|
+
|
|
523
|
+
Object.entries(tagsToFilter).forEach(([key, value]) => {
|
|
524
|
+
const currentPath = pathPrefix ? `${pathPrefix}.${key}` : key;
|
|
525
|
+
|
|
526
|
+
// Check if this node matches the search
|
|
527
|
+
const nodeMatches = nodeMatchesSearch(key, value, lowerTerm);
|
|
528
|
+
|
|
529
|
+
// Check if output matches the search
|
|
530
|
+
const outputMatches =
|
|
531
|
+
isTagNode(value) &&
|
|
532
|
+
value.output &&
|
|
533
|
+
outputMatchesSearch(value.output, lowerTerm);
|
|
534
|
+
|
|
535
|
+
// Check if children match the search
|
|
536
|
+
let filteredChildren: Record<string, unknown> = {};
|
|
537
|
+
|
|
538
|
+
// Handle array in output
|
|
539
|
+
if (isTagNode(value) && value.output && Array.isArray(value.output)) {
|
|
540
|
+
const filteredOutput = filterTags(
|
|
541
|
+
value.output,
|
|
542
|
+
termToSearch,
|
|
543
|
+
`${currentPath}.output`,
|
|
544
|
+
);
|
|
545
|
+
const outputMatch =
|
|
546
|
+
typeof filteredOutput === 'object' &&
|
|
547
|
+
Object.keys(filteredOutput as Record<string, unknown>).length > 0;
|
|
548
|
+
if (outputMatch) {
|
|
549
|
+
const clonedNode = { ...value };
|
|
550
|
+
clonedNode.output = value.output; // Keep original array for proper rendering
|
|
551
|
+
filteredTags[key] = clonedNode;
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Handle regular tags
|
|
557
|
+
if (isTagNode(value) && value.tags) {
|
|
558
|
+
filteredChildren = filterTags(
|
|
559
|
+
value.tags,
|
|
560
|
+
termToSearch,
|
|
561
|
+
currentPath,
|
|
562
|
+
) as Record<string, unknown>;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
const childrenMatch =
|
|
566
|
+
typeof filteredChildren === 'object' &&
|
|
567
|
+
Object.keys(filteredChildren).length > 0;
|
|
568
|
+
|
|
569
|
+
// Include this node if it matches or has matching children
|
|
570
|
+
if (nodeMatches || outputMatches || childrenMatch) {
|
|
571
|
+
// Clone the node to avoid modifying the original
|
|
572
|
+
const clonedNode = isTagNode(value) ? { ...value } : value;
|
|
573
|
+
|
|
574
|
+
// If children match, include the filtered children
|
|
575
|
+
if (childrenMatch && isTagNode(clonedNode)) {
|
|
576
|
+
clonedNode.tags = filteredChildren as Record<string, TagNode>;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
filteredTags[key] = clonedNode;
|
|
580
|
+
}
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
return filteredTags;
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
// Only filter if in global search mode, otherwise show all
|
|
587
|
+
const filteredTags = globalSearchMode
|
|
588
|
+
? filterTags(processedTags, searchTerm, path)
|
|
589
|
+
: processedTags;
|
|
590
|
+
|
|
591
|
+
const extractStepsFromOutputPath = (pathToExtract: string) => {
|
|
592
|
+
const parts = pathToExtract.split('.'); // Split the string by dots
|
|
593
|
+
const outputIndex = parts.lastIndexOf('output'); // Find the last occurrence of 'output'
|
|
594
|
+
|
|
595
|
+
if (outputIndex === -1 || outputIndex === 0) return ''; // Return empty if 'output' is not found or it's at the start
|
|
596
|
+
|
|
597
|
+
// Extract everything from the last step before 'output' to the end
|
|
598
|
+
return parts
|
|
599
|
+
.slice(outputIndex - 1)
|
|
600
|
+
.filter((part) => part !== 'output')
|
|
601
|
+
.join('.');
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
// Simplified rendering to fix syntax issues
|
|
605
|
+
return html`
|
|
606
|
+
<ul style="padding-left: ${1 * 20}px;">
|
|
607
|
+
${Object.entries(filteredTags as Record<string, unknown>).map(
|
|
608
|
+
([key, value]) => {
|
|
609
|
+
const isStep = isTagNode(value);
|
|
610
|
+
// We don't use isOutput but keep it for future reference
|
|
611
|
+
const isArrayItem = key.startsWith('[') && key.endsWith(']');
|
|
612
|
+
const hasNestedSteps =
|
|
613
|
+
isStep &&
|
|
614
|
+
isTagNode(value) &&
|
|
615
|
+
value.tags &&
|
|
616
|
+
Object.keys(value.tags).length > 0;
|
|
617
|
+
const hasOutput =
|
|
618
|
+
isTagNode(value) &&
|
|
619
|
+
value.output &&
|
|
620
|
+
typeof value.output === 'object';
|
|
621
|
+
const hasChildren =
|
|
622
|
+
hasNestedSteps || hasOutput || isExpandable(value);
|
|
623
|
+
const isExpanded = expandedNodes[key];
|
|
624
|
+
const isOutputExpanded = expandedOutputs[key];
|
|
625
|
+
const currentPath = path ? `${path}.${key}` : key;
|
|
626
|
+
const nodeMatches =
|
|
627
|
+
searchTerm && nodeMatchesSearch(key, value, searchTerm);
|
|
628
|
+
let text: string;
|
|
629
|
+
|
|
630
|
+
if (isArrayItem) {
|
|
631
|
+
text = key;
|
|
632
|
+
} else if (isTagNode(value) && value.name) {
|
|
633
|
+
text = value.name;
|
|
634
|
+
} else {
|
|
635
|
+
text = key;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
let tagsParam: Record<string, unknown> | unknown[] = {};
|
|
639
|
+
|
|
640
|
+
if (isTagNode(value)) {
|
|
641
|
+
if (Array.isArray(value.output)) {
|
|
642
|
+
tagsParam = value.output;
|
|
643
|
+
} else {
|
|
644
|
+
tagsParam = value.output as Record<string, unknown>;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
let tagsParamForOutput: unknown;
|
|
649
|
+
|
|
650
|
+
if (Array.isArray(value)) {
|
|
651
|
+
tagsParamForOutput = value;
|
|
652
|
+
} else if (isTagNode(value) && value.tags) {
|
|
653
|
+
tagsParamForOutput = value.tags;
|
|
654
|
+
} else {
|
|
655
|
+
tagsParamForOutput = value as Record<string, unknown>;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
return html`
|
|
659
|
+
<li key=${key} class="${nodeMatches ? styles.searchMatch : ''}">
|
|
660
|
+
<div
|
|
661
|
+
onClick=${() => {
|
|
662
|
+
if (isStep) {
|
|
663
|
+
setExpandedOutputs((prev) => ({
|
|
664
|
+
...prev,
|
|
665
|
+
[key]: !prev[key],
|
|
666
|
+
}));
|
|
667
|
+
} else if (hasChildren) {
|
|
668
|
+
toggleExpand(key);
|
|
669
|
+
} else {
|
|
670
|
+
const tagPath = `${activeTab}.${extractStepsFromOutputPath(
|
|
671
|
+
currentPath,
|
|
672
|
+
)}`;
|
|
673
|
+
onSelect({ currentPath: tagPath, key, value });
|
|
674
|
+
}
|
|
675
|
+
}}
|
|
676
|
+
class="${styles.listItemContent}"
|
|
677
|
+
>
|
|
678
|
+
${!isStep ? html`<${TypeIcon} value=${value} />` : ''}
|
|
679
|
+
${isTagNode(value) && value.appIcon
|
|
680
|
+
? html`<img src=${value.appIcon} alt="App Icon" />`
|
|
681
|
+
: ''}
|
|
682
|
+
|
|
683
|
+
<span
|
|
684
|
+
class="${styles.key}"
|
|
685
|
+
dangerouslySetInnerHTML=${{
|
|
686
|
+
__html: highlightSearchTerm(text, searchTerm),
|
|
687
|
+
}}
|
|
688
|
+
></span>
|
|
689
|
+
|
|
690
|
+
${!hasChildren &&
|
|
691
|
+
value !== undefined &&
|
|
692
|
+
(typeof value === 'string' ||
|
|
693
|
+
typeof value === 'number' ||
|
|
694
|
+
typeof value === 'boolean')
|
|
695
|
+
? html`<span class="${styles.value}">
|
|
696
|
+
${value ? ' ' : ''}
|
|
697
|
+
<span
|
|
698
|
+
dangerouslySetInnerHTML=${{
|
|
699
|
+
__html: highlightSearchTerm(
|
|
700
|
+
String(value),
|
|
701
|
+
searchTerm,
|
|
702
|
+
),
|
|
703
|
+
}}
|
|
704
|
+
></span>
|
|
705
|
+
</span>`
|
|
706
|
+
: ''}
|
|
707
|
+
${isStep &&
|
|
708
|
+
value !== undefined &&
|
|
709
|
+
isTagNode(value) &&
|
|
710
|
+
value.machineName
|
|
711
|
+
? html`<span class="${styles.value}">
|
|
712
|
+
${' '}
|
|
713
|
+
<span
|
|
714
|
+
dangerouslySetInnerHTML=${{
|
|
715
|
+
__html: highlightSearchTerm(
|
|
716
|
+
value.machineName,
|
|
717
|
+
searchTerm,
|
|
718
|
+
),
|
|
719
|
+
}}
|
|
720
|
+
></span>
|
|
721
|
+
</span>`
|
|
722
|
+
: ''}
|
|
723
|
+
${isArrayItem && typeof value === 'object' && !isStep
|
|
724
|
+
? html`<span class="${styles.value}">
|
|
725
|
+
${' '}
|
|
726
|
+
<span>
|
|
727
|
+
${Array.isArray(value)
|
|
728
|
+
? `Array(${value.length})`
|
|
729
|
+
: `Object${
|
|
730
|
+
value && typeof value === 'object'
|
|
731
|
+
? ` (${
|
|
732
|
+
Object.keys(
|
|
733
|
+
value as Record<string, unknown>,
|
|
734
|
+
).length
|
|
735
|
+
} keys)`
|
|
736
|
+
: ''
|
|
737
|
+
}`}
|
|
738
|
+
</span>
|
|
739
|
+
</span>`
|
|
740
|
+
: ''}
|
|
741
|
+
${isStep && hasOutput
|
|
742
|
+
? html`<span
|
|
743
|
+
class="${styles.outputIndicator}"
|
|
744
|
+
onClick=${(e: Event) => toggleOutputExpand(key, e)}
|
|
745
|
+
title="Toggle output data"
|
|
746
|
+
>
|
|
747
|
+
<svg
|
|
748
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
749
|
+
width="24"
|
|
750
|
+
height="24"
|
|
751
|
+
viewBox="0 0 24 24"
|
|
752
|
+
fill="none"
|
|
753
|
+
stroke="currentColor"
|
|
754
|
+
strokeWidth="2"
|
|
755
|
+
strokeLinecap="round"
|
|
756
|
+
strokeLinejoin="round"
|
|
757
|
+
style="transform: ${isOutputExpanded
|
|
758
|
+
? 'rotate(180deg)'
|
|
759
|
+
: 'rotate(0deg)'};
|
|
760
|
+
transition: transform 0.2s;"
|
|
761
|
+
>
|
|
762
|
+
<path d="m6 9 6 6 6-6"></path>
|
|
763
|
+
</svg>
|
|
764
|
+
</span>`
|
|
765
|
+
: ''}
|
|
766
|
+
</div>
|
|
767
|
+
|
|
768
|
+
${isStep && hasOutput && isOutputExpanded
|
|
769
|
+
? html`<div class="${styles.outputSection}">
|
|
770
|
+
<${TagList}
|
|
771
|
+
tags=${tagsParam}
|
|
772
|
+
searchTerm=${searchTerm}
|
|
773
|
+
level=${level + 1}
|
|
774
|
+
path=${`${currentPath}.output`}
|
|
775
|
+
globalSearchMode=${globalSearchMode}
|
|
776
|
+
onSelect=${onSelect}
|
|
777
|
+
activeTab=${activeTab}
|
|
778
|
+
/>
|
|
779
|
+
</div>`
|
|
780
|
+
: ''}
|
|
781
|
+
${hasNestedSteps && isExpanded
|
|
782
|
+
? html`<${TagList}
|
|
783
|
+
tags=${isTagNode(value) ? value.tags || {} : {}}
|
|
784
|
+
searchTerm=${searchTerm}
|
|
785
|
+
level=${level + 1}
|
|
786
|
+
path=${currentPath}
|
|
787
|
+
globalSearchMode=${globalSearchMode}
|
|
788
|
+
onSelect=${onSelect}
|
|
789
|
+
activeTab=${activeTab}
|
|
790
|
+
/>`
|
|
791
|
+
: ''}
|
|
792
|
+
${!isStep && hasChildren && isExpanded
|
|
793
|
+
? html`<${TagList}
|
|
794
|
+
tags=${tagsParamForOutput}
|
|
795
|
+
searchTerm=${searchTerm}
|
|
796
|
+
level=${level + 1}
|
|
797
|
+
path=${currentPath}
|
|
798
|
+
globalSearchMode=${globalSearchMode}
|
|
799
|
+
onSelect=${onSelect}
|
|
800
|
+
activeTab=${activeTab}
|
|
801
|
+
/>`
|
|
802
|
+
: ''}
|
|
803
|
+
</li>
|
|
804
|
+
`;
|
|
805
|
+
},
|
|
806
|
+
)}
|
|
807
|
+
</ul>
|
|
808
|
+
`;
|
|
809
|
+
};
|
|
810
|
+
|
|
811
|
+
// Update the TabMenu component to search across all tabs
|
|
812
|
+
const TabMenu = ({ data, onSelect }: TagMenuProps) => {
|
|
813
|
+
const [activeTab, setActiveTab] = useState<string>(Object.keys(data)[0]);
|
|
814
|
+
const [searchTerm, setSearchTerm] = useState<string>('');
|
|
815
|
+
const [searchResults, setSearchResults] = useState<Record<string, number>>(
|
|
816
|
+
{},
|
|
817
|
+
);
|
|
818
|
+
const [globalSearchMode, setGlobalSearchMode] = useState<boolean>(false);
|
|
819
|
+
|
|
820
|
+
// Search across all tabs and count matches
|
|
821
|
+
useEffect(() => {
|
|
822
|
+
if (!searchTerm) {
|
|
823
|
+
setSearchResults({});
|
|
824
|
+
setGlobalSearchMode(false);
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
const results: Record<string, number> = {};
|
|
829
|
+
|
|
830
|
+
Object.keys(data).forEach((tabKey) => {
|
|
831
|
+
const matches = countSearchMatches(
|
|
832
|
+
data[tabKey].tags || {},
|
|
833
|
+
searchTerm.toLowerCase(),
|
|
834
|
+
);
|
|
835
|
+
results[tabKey] = matches;
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
setSearchResults(results);
|
|
839
|
+
setGlobalSearchMode(searchTerm.length > 0);
|
|
840
|
+
}, [searchTerm, data]);
|
|
841
|
+
|
|
842
|
+
const getTabIcon = (tabName: string) => {
|
|
843
|
+
const name = tabName.toLowerCase();
|
|
844
|
+
|
|
845
|
+
if (name.includes('trigger')) {
|
|
846
|
+
return html`
|
|
847
|
+
<svg
|
|
848
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
849
|
+
width="16"
|
|
850
|
+
height="16"
|
|
851
|
+
viewBox="0 0 24 24"
|
|
852
|
+
fill="none"
|
|
853
|
+
stroke="currentColor"
|
|
854
|
+
strokeWidth="2"
|
|
855
|
+
strokeLinecap="round"
|
|
856
|
+
strokeLinejoin="round"
|
|
857
|
+
class="mr-1"
|
|
858
|
+
>
|
|
859
|
+
<path d="M13 2 3 14h9l-1 8 10-12h-9l1-8z"></path>
|
|
860
|
+
</svg>
|
|
861
|
+
`;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
if (name.includes('storage') || name.includes('variable')) {
|
|
865
|
+
return html`
|
|
866
|
+
<svg
|
|
867
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
868
|
+
width="16"
|
|
869
|
+
height="16"
|
|
870
|
+
viewBox="0 0 24 24"
|
|
871
|
+
fill="none"
|
|
872
|
+
stroke="currentColor"
|
|
873
|
+
strokeWidth="2"
|
|
874
|
+
strokeLinecap="round"
|
|
875
|
+
strokeLinejoin="round"
|
|
876
|
+
class="mr-1"
|
|
877
|
+
>
|
|
878
|
+
<ellipse cx="12" cy="5" rx="9" ry="3"></ellipse>
|
|
879
|
+
<path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"></path>
|
|
880
|
+
<path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"></path>
|
|
881
|
+
</svg>
|
|
882
|
+
`;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
if (name.includes('form') || name.includes('setup')) {
|
|
886
|
+
// Keep the existing form icon
|
|
887
|
+
return html`
|
|
888
|
+
<svg
|
|
889
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
890
|
+
width="16"
|
|
891
|
+
height="16"
|
|
892
|
+
viewBox="0 0 24 24"
|
|
893
|
+
fill="none"
|
|
894
|
+
stroke="currentColor"
|
|
895
|
+
strokeWidth="2"
|
|
896
|
+
strokeLinecap="round"
|
|
897
|
+
strokeLinejoin="round"
|
|
898
|
+
class="mr-1"
|
|
899
|
+
>
|
|
900
|
+
<rect width="18" height="18" x="3" y="3" rx="2"></rect>
|
|
901
|
+
<path d="M9 9h6"></path>
|
|
902
|
+
<path d="M9 13h6"></path>
|
|
903
|
+
<path d="M9 17h6"></path>
|
|
904
|
+
</svg>
|
|
905
|
+
`;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
if (name.includes('auth') || name.includes('authorization')) {
|
|
909
|
+
return html`
|
|
910
|
+
<svg
|
|
911
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
912
|
+
width="16"
|
|
913
|
+
height="16"
|
|
914
|
+
viewBox="0 0 24 24"
|
|
915
|
+
fill="none"
|
|
916
|
+
stroke="currentColor"
|
|
917
|
+
strokeWidth="2"
|
|
918
|
+
strokeLinecap="round"
|
|
919
|
+
strokeLinejoin="round"
|
|
920
|
+
class="mr-1"
|
|
921
|
+
>
|
|
922
|
+
<rect width="18" height="11" x="3" y="11" rx="2" ry="2"></rect>
|
|
923
|
+
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
|
|
924
|
+
<path d="M12 16v3"></path>
|
|
925
|
+
</svg>
|
|
926
|
+
`;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
if (name.includes('parameter')) {
|
|
930
|
+
return html`
|
|
931
|
+
<svg
|
|
932
|
+
width="18"
|
|
933
|
+
height="18"
|
|
934
|
+
viewBox="0 0 24 24"
|
|
935
|
+
fill="none"
|
|
936
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
937
|
+
>
|
|
938
|
+
<path
|
|
939
|
+
d="M4 6H14"
|
|
940
|
+
stroke="currentColor"
|
|
941
|
+
stroke-width="2"
|
|
942
|
+
stroke-linecap="round"
|
|
943
|
+
/>
|
|
944
|
+
<circle cx="18" cy="6" r="2" stroke="currentColor" stroke-width="2" />
|
|
945
|
+
<path
|
|
946
|
+
d="M4 12H10"
|
|
947
|
+
stroke="currentColor"
|
|
948
|
+
stroke-width="2"
|
|
949
|
+
stroke-linecap="round"
|
|
950
|
+
/>
|
|
951
|
+
<circle
|
|
952
|
+
cx="14"
|
|
953
|
+
cy="12"
|
|
954
|
+
r="2"
|
|
955
|
+
stroke="currentColor"
|
|
956
|
+
stroke-width="2"
|
|
957
|
+
/>
|
|
958
|
+
<path
|
|
959
|
+
d="M4 18H12"
|
|
960
|
+
stroke="currentColor"
|
|
961
|
+
stroke-width="2"
|
|
962
|
+
stroke-linecap="round"
|
|
963
|
+
/>
|
|
964
|
+
<circle
|
|
965
|
+
cx="16"
|
|
966
|
+
cy="18"
|
|
967
|
+
r="2"
|
|
968
|
+
stroke="currentColor"
|
|
969
|
+
stroke-width="2"
|
|
970
|
+
/>
|
|
971
|
+
</svg>
|
|
972
|
+
`;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
// Default icon
|
|
976
|
+
return html`
|
|
977
|
+
<svg
|
|
978
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
979
|
+
width="16"
|
|
980
|
+
height="16"
|
|
981
|
+
viewBox="0 0 24 24"
|
|
982
|
+
fill="none"
|
|
983
|
+
stroke="currentColor"
|
|
984
|
+
strokeWidth="2"
|
|
985
|
+
strokeLinecap="round"
|
|
986
|
+
strokeLinejoin="round"
|
|
987
|
+
class="mr-1"
|
|
988
|
+
>
|
|
989
|
+
<path
|
|
990
|
+
d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"
|
|
991
|
+
></path>
|
|
992
|
+
</svg>
|
|
993
|
+
`;
|
|
994
|
+
};
|
|
995
|
+
|
|
996
|
+
const renderTabs = () =>
|
|
997
|
+
html`
|
|
998
|
+
<div class="${styles.tabsWrapper}">
|
|
999
|
+
${Object.keys(data).map(
|
|
1000
|
+
(key) => html`
|
|
1001
|
+
<div
|
|
1002
|
+
class="${styles.tab} ${activeTab === key
|
|
1003
|
+
? styles.activeTab
|
|
1004
|
+
: ''} ${searchResults[key] > 0 ? styles.tabWithResults : ''}"
|
|
1005
|
+
onClick=${() => setActiveTab(key)}
|
|
1006
|
+
>
|
|
1007
|
+
${getTabIcon(data[key].name || key)} ${data[key].name || key}
|
|
1008
|
+
${searchResults[key] > 0
|
|
1009
|
+
? html`<span class="${styles.resultBadge}"
|
|
1010
|
+
>${searchResults[key]}</span
|
|
1011
|
+
>`
|
|
1012
|
+
: ''}
|
|
1013
|
+
</div>
|
|
1014
|
+
`,
|
|
1015
|
+
)}
|
|
1016
|
+
</div>
|
|
1017
|
+
`;
|
|
1018
|
+
const renderContent = () => {
|
|
1019
|
+
const activeData = data[activeTab];
|
|
1020
|
+
return html`
|
|
1021
|
+
<div class="${styles.tabContent}">
|
|
1022
|
+
<div class="${styles.searchWrapper}">
|
|
1023
|
+
<input
|
|
1024
|
+
type="text"
|
|
1025
|
+
placeholder="Search across all steps and fields..."
|
|
1026
|
+
value=${searchTerm}
|
|
1027
|
+
onInput=${(e: Event) =>
|
|
1028
|
+
setSearchTerm((e.target as HTMLInputElement).value)}
|
|
1029
|
+
/>
|
|
1030
|
+
${searchTerm
|
|
1031
|
+
? html`
|
|
1032
|
+
<button
|
|
1033
|
+
class="${styles.clearSearch}"
|
|
1034
|
+
onClick=${() => setSearchTerm('')}
|
|
1035
|
+
title="Clear search"
|
|
1036
|
+
>
|
|
1037
|
+
<svg
|
|
1038
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
1039
|
+
width="16"
|
|
1040
|
+
height="16"
|
|
1041
|
+
viewBox="0 0 24 24"
|
|
1042
|
+
fill="none"
|
|
1043
|
+
stroke="currentColor"
|
|
1044
|
+
strokeWidth="2"
|
|
1045
|
+
strokeLinecap="round"
|
|
1046
|
+
strokeLinejoin="round"
|
|
1047
|
+
>
|
|
1048
|
+
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
1049
|
+
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
1050
|
+
</svg>
|
|
1051
|
+
</button>
|
|
1052
|
+
`
|
|
1053
|
+
: ''}
|
|
1054
|
+
</div>
|
|
1055
|
+
<div class="${styles.tagsListWrapper}">
|
|
1056
|
+
<${TagList}
|
|
1057
|
+
tags=${activeData.tags}
|
|
1058
|
+
searchTerm=${searchTerm}
|
|
1059
|
+
globalSearchMode=${globalSearchMode}
|
|
1060
|
+
onSelect=${onSelect}
|
|
1061
|
+
activeTab=${data[activeTab].machineName || activeTab}
|
|
1062
|
+
/>
|
|
1063
|
+
</div>
|
|
1064
|
+
</div>
|
|
1065
|
+
`;
|
|
1066
|
+
};
|
|
1067
|
+
|
|
1068
|
+
return html`<div>${renderTabs()} ${renderContent()}</div>`;
|
|
1069
|
+
};
|
|
1070
|
+
|
|
1071
|
+
// Update the TagList component parameter types
|
|
1072
|
+
|
|
1073
|
+
// Add this function to convert array-based tags back to object-based tags
|
|
1074
|
+
export function convertTagsToObject(tagData: Record<string, unknown>): TagData {
|
|
1075
|
+
const result: TagData = {};
|
|
1076
|
+
|
|
1077
|
+
Object.keys(tagData).forEach((key) => {
|
|
1078
|
+
const section = tagData[key] as TagNode;
|
|
1079
|
+
const newSection: TagNode = {
|
|
1080
|
+
...section,
|
|
1081
|
+
};
|
|
1082
|
+
|
|
1083
|
+
// Convert tags array to object
|
|
1084
|
+
if (section.tags && Array.isArray(section.tags)) {
|
|
1085
|
+
const tagsObject: Record<string, TagNode> = {};
|
|
1086
|
+
|
|
1087
|
+
section.tags.forEach((tag: TagNode, index: number) => {
|
|
1088
|
+
// Use machineName as key if available, otherwise use index
|
|
1089
|
+
const tagKey = tag.machineName || `[${index}]`;
|
|
1090
|
+
|
|
1091
|
+
// Recursively convert nested tags
|
|
1092
|
+
if (tag.tags && Array.isArray(tag.tags)) {
|
|
1093
|
+
tagsObject[tagKey] = {
|
|
1094
|
+
...tag,
|
|
1095
|
+
tags: convertNestedTagsToObject(tag.tags),
|
|
1096
|
+
};
|
|
1097
|
+
} else {
|
|
1098
|
+
tagsObject[tagKey] = tag;
|
|
1099
|
+
}
|
|
1100
|
+
});
|
|
1101
|
+
|
|
1102
|
+
newSection.tags = tagsObject;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
result[key] = newSection;
|
|
1106
|
+
});
|
|
1107
|
+
|
|
1108
|
+
return result;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
interface TagsMenuProps {
|
|
1112
|
+
tagsTree: Record<string, unknown>;
|
|
1113
|
+
onSelect: (tag: Record<string, unknown>) => void;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
export function TagsMenu({ tagsTree, onSelect }: TagsMenuProps) {
|
|
1117
|
+
const modifiedData = convertTagsToObject(tagsTree);
|
|
1118
|
+
return html`
|
|
1119
|
+
<div class="${styles.tagsMenuWrapper}">
|
|
1120
|
+
<${TabMenu} data=${modifiedData} onSelect=${onSelect} />
|
|
1121
|
+
</div>
|
|
1122
|
+
`;
|
|
1123
|
+
}
|