@industream/flowmaker-flowbox-ui-components 0.0.5 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +229 -3
- package/dist/DCCatalogEntry.svelte +211 -0
- package/dist/DCCatalogEntry.svelte.d.ts +19 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/package.json +6 -1
- package/src/DCCatalogEntry.svelte +211 -0
- package/src/index.ts +3 -0
package/README.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
Reusable Svelte 5 components for FlowMaker FlowBox configuration UIs.
|
|
4
4
|
|
|
5
|
+
**Components:**
|
|
6
|
+
- [DCSourceConnection](#dcsourceconnection) - Source connection selector
|
|
7
|
+
- [DCCatalogEntry](#dccatalogentry) - Catalog entry selector
|
|
8
|
+
|
|
5
9
|
## Installation
|
|
6
10
|
|
|
7
11
|
```bash
|
|
@@ -28,13 +32,13 @@ A dropdown component that fetches and displays source connections from the DataC
|
|
|
28
32
|
<script>
|
|
29
33
|
let conn = $state('');
|
|
30
34
|
</script>
|
|
31
|
-
|
|
32
|
-
Selection: {
|
|
35
|
+
|
|
36
|
+
Selection: { conn ?? '-'}
|
|
33
37
|
|
|
34
38
|
<DCSourceConnection
|
|
35
39
|
dcapiurl="http://localhost:8002"
|
|
36
40
|
sourcetypefilter="DataBridge"
|
|
37
|
-
onsourceselect={(newValue) => conn =
|
|
41
|
+
onsourceselect={(newValue) => conn = (newValue?.id ?? '')}
|
|
38
42
|
/>
|
|
39
43
|
```
|
|
40
44
|
|
|
@@ -175,3 +179,225 @@ interface SourceConnection {
|
|
|
175
179
|
[key: string]: any;
|
|
176
180
|
}
|
|
177
181
|
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
### DCCatalogEntry
|
|
186
|
+
|
|
187
|
+
A dropdown component that fetches and displays catalog entries from the DataCatalog API. Catalog entries represent data sources with their connection parameters and entry-specific parameters.
|
|
188
|
+
|
|
189
|
+
#### Quick Start
|
|
190
|
+
|
|
191
|
+
```svelte
|
|
192
|
+
<script>
|
|
193
|
+
import { DCCatalogEntry } from '@industream/flowmaker-flowbox-ui-components';
|
|
194
|
+
|
|
195
|
+
let selectedEntry = $state(null);
|
|
196
|
+
</script>
|
|
197
|
+
|
|
198
|
+
<DCCatalogEntry
|
|
199
|
+
dcapiurl="http://localhost:8002"
|
|
200
|
+
onentryselect={(entry) => selectedEntry = entry}
|
|
201
|
+
/>
|
|
202
|
+
|
|
203
|
+
{#if selectedEntry}
|
|
204
|
+
<p>Selected: {selectedEntry.name} ({selectedEntry.dataType})</p>
|
|
205
|
+
{/if}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
#### Environment Configuration with `jsonEnv`
|
|
209
|
+
|
|
210
|
+
```svelte
|
|
211
|
+
<script>
|
|
212
|
+
import { flowMakerBridge } from '@industream/flowmaker-flowbox-ui';
|
|
213
|
+
import { DCCatalogEntry } from '@industream/flowmaker-flowbox-ui-components';
|
|
214
|
+
|
|
215
|
+
const props = $props();
|
|
216
|
+
const { values, jsonEnv } = flowMakerBridge(props);
|
|
217
|
+
|
|
218
|
+
// Get DataCatalog config from environment variables (reactive derived store)
|
|
219
|
+
const dcConfig = jsonEnv('datacatalog');
|
|
220
|
+
|
|
221
|
+
// Optional: data type filter
|
|
222
|
+
const dataTypeFilter = "string";
|
|
223
|
+
</script>
|
|
224
|
+
|
|
225
|
+
<div class="dc-selector-container">
|
|
226
|
+
<div>Data Catalog Entry</div>
|
|
227
|
+
|
|
228
|
+
<DCCatalogEntry
|
|
229
|
+
dcapiurl={$dcConfig?.url}
|
|
230
|
+
datatypefilter={dataTypeFilter}
|
|
231
|
+
initialselection={$values?.catalogEntryId ?? ''}
|
|
232
|
+
onentryselect={(entry) => $values.catalogEntryId = entry.id}
|
|
233
|
+
/>
|
|
234
|
+
|
|
235
|
+
{#if $values.catalogEntryId}
|
|
236
|
+
<div class="selection-info">
|
|
237
|
+
<h5>Selected Entry</h5>
|
|
238
|
+
<p><strong>ID:</strong> {$values.catalogEntryId}</p>
|
|
239
|
+
</div>
|
|
240
|
+
{/if}
|
|
241
|
+
</div>
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
#### Filtering
|
|
245
|
+
|
|
246
|
+
```svelte
|
|
247
|
+
<!-- Filter by source type -->
|
|
248
|
+
<DCCatalogEntry
|
|
249
|
+
dcapiurl={dcApiUrl}
|
|
250
|
+
sourcetypefilter="InfluxDB2"
|
|
251
|
+
onentryselect={handleSelect}
|
|
252
|
+
/>
|
|
253
|
+
|
|
254
|
+
<!-- Filter by data type -->
|
|
255
|
+
<DCCatalogEntry
|
|
256
|
+
dcapiurl={dcApiUrl}
|
|
257
|
+
datatypefilter="string"
|
|
258
|
+
onentryselect={handleSelect}
|
|
259
|
+
/>
|
|
260
|
+
|
|
261
|
+
<!-- Filter by name(s) - sent to API -->
|
|
262
|
+
<DCCatalogEntry
|
|
263
|
+
dcapiurl={dcApiUrl}
|
|
264
|
+
namefilter={["Entry1", "Entry2"]}
|
|
265
|
+
onentryselect={handleSelect}
|
|
266
|
+
/>
|
|
267
|
+
|
|
268
|
+
<!-- Multiple filters combined -->
|
|
269
|
+
<DCCatalogEntry
|
|
270
|
+
dcapiurl={dcApiUrl}
|
|
271
|
+
sourcetypefilter={["InfluxDB2", "MQTT"]}
|
|
272
|
+
datatypefilter={["string", "number"]}
|
|
273
|
+
onentryselect={handleSelect}
|
|
274
|
+
/>
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
#### Programmatic Methods
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
// Select by ID
|
|
281
|
+
dcCatalogEntry.select('xxxx-guid-1234-4567');
|
|
282
|
+
|
|
283
|
+
// Select by entry object
|
|
284
|
+
dcCatalogEntry.select(entryObject);
|
|
285
|
+
|
|
286
|
+
// Get current selection
|
|
287
|
+
const selected = dcCatalogEntry.getSelection();
|
|
288
|
+
|
|
289
|
+
// Get all loaded entries (filtered)
|
|
290
|
+
const filtered = dcCatalogEntry.getEntries();
|
|
291
|
+
|
|
292
|
+
// Get all loaded entries (unfiltered)
|
|
293
|
+
const all = dcCatalogEntry.getAllEntries();
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## API Reference
|
|
299
|
+
|
|
300
|
+
### DCCatalogEntry
|
|
301
|
+
|
|
302
|
+
##### Import
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
import { DCCatalogEntry, type CatalogEntry, type DataType, type SourceType } from '@industream/flowmaker-flowbox-ui-components';
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
##### Props
|
|
309
|
+
|
|
310
|
+
| Prop | Type | Default | Description |
|
|
311
|
+
|------|------|---------|-------------|
|
|
312
|
+
| `id` | `string` | `undefined` | HTML id attribute for the dropdown |
|
|
313
|
+
| `dcapiurl` | `string` | `''` | Base URL of the DataCatalog API |
|
|
314
|
+
| `sourcetypefilter` | `SourceType \| SourceType[] \| null` | `null` | Filter entries by source type(s) (client-side) |
|
|
315
|
+
| `datatypefilter` | `DataType \| DataType[] \| null` | `null` | Filter entries by data type(s) (client-side) |
|
|
316
|
+
| `namefilter` | `string \| string[] \| null` | `null` | Filter entries by name(s) (API query) |
|
|
317
|
+
| `initialselection` | `string \| null` | `null` | Entry ID to auto-select when items load |
|
|
318
|
+
| `onentryselect` | `(entry: CatalogEntry) => void` | `null` | Callback when an entry is selected |
|
|
319
|
+
| `onitemsloaded` | `(entries: CatalogEntry[]) => void` | `null` | Callback when entries are loaded |
|
|
320
|
+
|
|
321
|
+
##### Exported Methods
|
|
322
|
+
|
|
323
|
+
| Method | Signature | Description |
|
|
324
|
+
|--------|-----------|-------------|
|
|
325
|
+
| `select` | `(idOrEntry: string \| CatalogEntry) => void` | Programmatically select an entry |
|
|
326
|
+
| `getSelection` | `() => CatalogEntry \| null` | Get the currently selected entry |
|
|
327
|
+
| `getEntries` | `() => CatalogEntry[]` | Get all loaded entries (filtered) |
|
|
328
|
+
| `getAllEntries` | `() => CatalogEntry[]` | Get all loaded entries (unfiltered) |
|
|
329
|
+
|
|
330
|
+
##### CatalogEntry Type
|
|
331
|
+
|
|
332
|
+
The `CatalogEntry` object returned on selection contains the full entry data including:
|
|
333
|
+
|
|
334
|
+
- **sourceConnection**: The parent source connection with all connection parameters (hostname, credentials, etc.)
|
|
335
|
+
- **sourceParams**: Entry-specific parameters that override or extend the connection settings
|
|
336
|
+
- **labels**: User-defined labels for categorization
|
|
337
|
+
- **metadata**: Additional metadata key-value pairs
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
interface CatalogEntry {
|
|
341
|
+
id: string;
|
|
342
|
+
name: string;
|
|
343
|
+
dataType: string; // e.g., "string", "number", "boolean"
|
|
344
|
+
sourceConnection: {
|
|
345
|
+
id: string;
|
|
346
|
+
name: string;
|
|
347
|
+
sourceType: {
|
|
348
|
+
id: string;
|
|
349
|
+
name: string; // e.g., "InfluxDB2", "MQTT", "DataBridge"
|
|
350
|
+
isProtected: boolean;
|
|
351
|
+
};
|
|
352
|
+
// Connection-level parameters (varies by source type)
|
|
353
|
+
hostname?: string;
|
|
354
|
+
token?: string;
|
|
355
|
+
bucket?: string;
|
|
356
|
+
organization?: string;
|
|
357
|
+
// ... other connection params
|
|
358
|
+
};
|
|
359
|
+
sourceParams: { // Entry-specific parameters
|
|
360
|
+
bucket?: string; // e.g., override bucket for this entry
|
|
361
|
+
// ... other entry-specific params
|
|
362
|
+
};
|
|
363
|
+
labels: Array<{
|
|
364
|
+
id: string;
|
|
365
|
+
name: string;
|
|
366
|
+
}>;
|
|
367
|
+
metadata: Record<string, any>;
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
**Example CatalogEntry:**
|
|
372
|
+
|
|
373
|
+
```json
|
|
374
|
+
{
|
|
375
|
+
"id": "57c14290-4dc7-408f-84fa-8fbe568bd506",
|
|
376
|
+
"name": "Influx with myBucket",
|
|
377
|
+
"dataType": "string",
|
|
378
|
+
"sourceConnection": {
|
|
379
|
+
"id": "6e75699f-1e97-493d-9718-930a809b93a4",
|
|
380
|
+
"name": "Influx Localhost",
|
|
381
|
+
"sourceType": {
|
|
382
|
+
"id": "InfluxDB2",
|
|
383
|
+
"name": "InfluxDB2",
|
|
384
|
+
"isProtected": true
|
|
385
|
+
},
|
|
386
|
+
"hostname": "http://influxdb:8086",
|
|
387
|
+
"token": "DQxcrQBORed9pSzH11Ngvrn...",
|
|
388
|
+
"bucket": "test",
|
|
389
|
+
"organization": "industream",
|
|
390
|
+
"ingressHostname": "http://localhost:8086"
|
|
391
|
+
},
|
|
392
|
+
"sourceParams": {
|
|
393
|
+
"bucket": "myBucket"
|
|
394
|
+
},
|
|
395
|
+
"labels": [
|
|
396
|
+
{ "id": "1f405a8c-...", "name": "zerzer" },
|
|
397
|
+
{ "id": "76432802-...", "name": "Toto" }
|
|
398
|
+
],
|
|
399
|
+
"metadata": {}
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
In this example, the `sourceConnection` defines the InfluxDB connection to `test` bucket, but `sourceParams.bucket` overrides it to `myBucket` for this specific catalog entry.
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { DataCatalogClient } from '@industream/datacatalog-client';
|
|
3
|
+
import type { CatalogEntry, DataType, SourceType } from '@industream/datacatalog-client/dto';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
id?: string;
|
|
7
|
+
dcapiurl?: string;
|
|
8
|
+
sourcetypefilter?: SourceType | SourceType[] | null;
|
|
9
|
+
datatypefilter?: DataType | DataType[] | null;
|
|
10
|
+
namefilter?: string | string[] | null;
|
|
11
|
+
initialselection?: string | null;
|
|
12
|
+
onentryselect?: (entry: CatalogEntry) => void;
|
|
13
|
+
onitemsloaded?: (entries: CatalogEntry[]) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let {
|
|
17
|
+
id = undefined,
|
|
18
|
+
dcapiurl = '',
|
|
19
|
+
sourcetypefilter = null,
|
|
20
|
+
datatypefilter = null,
|
|
21
|
+
namefilter = null,
|
|
22
|
+
initialselection = null,
|
|
23
|
+
onentryselect = null,
|
|
24
|
+
onitemsloaded = null
|
|
25
|
+
}: Props = $props();
|
|
26
|
+
|
|
27
|
+
let catalogEntries = $state<CatalogEntry[]>([]);
|
|
28
|
+
let filteredEntries = $state<CatalogEntry[]>([]);
|
|
29
|
+
let selectedValue = $state('');
|
|
30
|
+
let loading = $state(true);
|
|
31
|
+
let error = $state<string | null>(null);
|
|
32
|
+
let dropdownRef = $state<HTMLElement | null>(null);
|
|
33
|
+
|
|
34
|
+
// Load catalog entries when component mounts or dcapiurl changes
|
|
35
|
+
$effect(() => {
|
|
36
|
+
if (dcapiurl) {
|
|
37
|
+
loadCatalogEntries();
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Apply client-side filters when entries or filters change
|
|
42
|
+
$effect(() => {
|
|
43
|
+
filteredEntries = applyFilters(catalogEntries);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Inject styles into shadow DOM to fix trigger-label width
|
|
47
|
+
$effect(() => {
|
|
48
|
+
if (dropdownRef) {
|
|
49
|
+
requestAnimationFrame(() => {
|
|
50
|
+
const sr = (dropdownRef as any)?.shadowRoot;
|
|
51
|
+
if (sr && !sr.querySelector('#dc-entry-fix')) {
|
|
52
|
+
const style = document.createElement('style');
|
|
53
|
+
style.id = 'dc-entry-fix';
|
|
54
|
+
style.textContent = `.cds--list-box__field { display: flex; width: 100%; } .cds--list-box__label { flex: 1; width: 100%; }`;
|
|
55
|
+
sr.appendChild(style);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
function applyFilters(entries: CatalogEntry[]): CatalogEntry[] {
|
|
62
|
+
let result = entries;
|
|
63
|
+
|
|
64
|
+
// Filter by source type (from sourceConnection)
|
|
65
|
+
if (sourcetypefilter && sourcetypefilter.length > 0) {
|
|
66
|
+
const sourceTypes = Array.isArray(sourcetypefilter) ? sourcetypefilter : [sourcetypefilter];
|
|
67
|
+
result = result.filter(entry =>
|
|
68
|
+
entry.sourceConnection?.sourceType &&
|
|
69
|
+
sourceTypes.includes(entry.sourceConnection.sourceType)
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Filter by data type
|
|
74
|
+
if (datatypefilter && datatypefilter.length > 0) {
|
|
75
|
+
const dataTypes = Array.isArray(datatypefilter) ? datatypefilter : [datatypefilter];
|
|
76
|
+
result = result.filter(entry =>
|
|
77
|
+
entry.dataType && dataTypes.includes(entry.dataType)
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function loadCatalogEntries() {
|
|
85
|
+
loading = true;
|
|
86
|
+
error = null;
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
const client = new DataCatalogClient({ baseUrl: dcapiurl });
|
|
90
|
+
const filters: { names?: string[] } = {};
|
|
91
|
+
|
|
92
|
+
// Use name filter for API query if provided
|
|
93
|
+
if (namefilter && namefilter.length > 0) {
|
|
94
|
+
filters.names = Array.isArray(namefilter) ? namefilter : [namefilter];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const result = await client.catalogEntries.get(filters);
|
|
98
|
+
catalogEntries = result.items || [];
|
|
99
|
+
// Apply filters synchronously so initialselection works
|
|
100
|
+
filteredEntries = applyFilters(catalogEntries);
|
|
101
|
+
console.log('Loaded catalog entries:', catalogEntries);
|
|
102
|
+
|
|
103
|
+
// Auto-select initial selection if provided
|
|
104
|
+
if (initialselection) {
|
|
105
|
+
selectedValue = initialselection;
|
|
106
|
+
const entry = filteredEntries.find(ce => ce.id === initialselection);
|
|
107
|
+
if (entry && onentryselect) {
|
|
108
|
+
onentryselect(entry);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
onitemsloaded?.(filteredEntries);
|
|
113
|
+
} catch (e) {
|
|
114
|
+
console.error('Failed to load catalog entries:', e);
|
|
115
|
+
error = e.message || 'Failed to load catalog entries';
|
|
116
|
+
catalogEntries = [];
|
|
117
|
+
} finally {
|
|
118
|
+
loading = false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function handleSelect(e: CustomEvent) {
|
|
123
|
+
const selectedId = e.detail?.item?.value || e.target?.value;
|
|
124
|
+
console.log('Dropdown selected event:', e, 'selectedId:', selectedId);
|
|
125
|
+
selectedValue = selectedId;
|
|
126
|
+
|
|
127
|
+
if (selectedId && onentryselect) {
|
|
128
|
+
const selected = filteredEntries.find(ce => ce.id === selectedId);
|
|
129
|
+
console.log('Found selected:', selected);
|
|
130
|
+
if (selected) {
|
|
131
|
+
onentryselect(selected);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Exposed method to programmatically select a catalog entry
|
|
138
|
+
export function select(idOrEntry: string | CatalogEntry) {
|
|
139
|
+
const id = typeof idOrEntry === 'string' ? idOrEntry : idOrEntry?.id;
|
|
140
|
+
if (!id) return;
|
|
141
|
+
|
|
142
|
+
const entry = filteredEntries.find(ce => ce.id === id);
|
|
143
|
+
if (entry) {
|
|
144
|
+
selectedValue = id;
|
|
145
|
+
if (dropdownRef) {
|
|
146
|
+
dropdownRef.value = id;
|
|
147
|
+
}
|
|
148
|
+
if (onentryselect) {
|
|
149
|
+
onentryselect(entry);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Exposed method to get current selection
|
|
155
|
+
export function getSelection(): CatalogEntry | null {
|
|
156
|
+
return filteredEntries.find(ce => ce.id === selectedValue) || null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Exposed method to get all loaded entries (filtered)
|
|
160
|
+
export function getEntries(): CatalogEntry[] {
|
|
161
|
+
return filteredEntries;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Exposed method to get all loaded entries (unfiltered)
|
|
165
|
+
export function getAllEntries(): CatalogEntry[] {
|
|
166
|
+
return catalogEntries;
|
|
167
|
+
}
|
|
168
|
+
</script>
|
|
169
|
+
|
|
170
|
+
{#if loading}
|
|
171
|
+
<cds-dropdown {id} label="Catalog Entry" disabled>
|
|
172
|
+
<cds-dropdown-item value="">Loading...</cds-dropdown-item>
|
|
173
|
+
</cds-dropdown>
|
|
174
|
+
{:else if error}
|
|
175
|
+
<cds-dropdown {id} label="Catalog Entry" invalid invalid-text={error}>
|
|
176
|
+
<cds-dropdown-item value="">Error loading entries</cds-dropdown-item>
|
|
177
|
+
</cds-dropdown>
|
|
178
|
+
{:else}
|
|
179
|
+
<cds-dropdown
|
|
180
|
+
{id}
|
|
181
|
+
bind:this={dropdownRef}
|
|
182
|
+
label="Select a catalog entry"
|
|
183
|
+
value={selectedValue}
|
|
184
|
+
oncds-dropdown-selected={handleSelect}
|
|
185
|
+
>
|
|
186
|
+
{#each filteredEntries as entry}
|
|
187
|
+
<cds-dropdown-item value={entry.id}>
|
|
188
|
+
<span style="display: flex; justify-content: space-between; align-items: center; width: 100%;">
|
|
189
|
+
<span style="display: flex; align-items: center; gap: 0.5rem;">
|
|
190
|
+
<span>{entry.name}</span>
|
|
191
|
+
<cds-tag size="sm" type="green">{entry.dataType}</cds-tag>
|
|
192
|
+
</span>
|
|
193
|
+
<span style="display: flex; align-items: center; gap: 0.5rem;">
|
|
194
|
+
<span style="color: var(--cds-text-secondary, #525252); font-size: 0.875rem;">Connection: {entry.sourceConnection?.name || 'Unknown'}</span>
|
|
195
|
+
{#if entry.labels?.length}
|
|
196
|
+
<span style="display: flex; gap: 0.25rem;">
|
|
197
|
+
{#each entry.labels as label}
|
|
198
|
+
<cds-tag size="sm" type="purple">{label.name}</cds-tag>
|
|
199
|
+
{/each}
|
|
200
|
+
</span>
|
|
201
|
+
{/if}
|
|
202
|
+
</span>
|
|
203
|
+
</span>
|
|
204
|
+
</cds-dropdown-item>
|
|
205
|
+
{/each}
|
|
206
|
+
</cds-dropdown>
|
|
207
|
+
{/if}
|
|
208
|
+
|
|
209
|
+
<style>
|
|
210
|
+
/* Styles moved to inline for shadow DOM compatibility */
|
|
211
|
+
</style>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { CatalogEntry, DataType, SourceType } from '@industream/datacatalog-client/dto';
|
|
2
|
+
interface Props {
|
|
3
|
+
id?: string;
|
|
4
|
+
dcapiurl?: string;
|
|
5
|
+
sourcetypefilter?: SourceType | SourceType[] | null;
|
|
6
|
+
datatypefilter?: DataType | DataType[] | null;
|
|
7
|
+
namefilter?: string | string[] | null;
|
|
8
|
+
initialselection?: string | null;
|
|
9
|
+
onentryselect?: (entry: CatalogEntry) => void;
|
|
10
|
+
onitemsloaded?: (entries: CatalogEntry[]) => void;
|
|
11
|
+
}
|
|
12
|
+
declare const DCCatalogEntry: import("svelte").Component<Props, {
|
|
13
|
+
select: (idOrEntry: string | CatalogEntry) => void;
|
|
14
|
+
getSelection: () => CatalogEntry | null;
|
|
15
|
+
getEntries: () => CatalogEntry[];
|
|
16
|
+
getAllEntries: () => CatalogEntry[];
|
|
17
|
+
}, "">;
|
|
18
|
+
type DCCatalogEntry = ReturnType<typeof DCCatalogEntry>;
|
|
19
|
+
export default DCCatalogEntry;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
export { default as DCSourceConnection } from './DCSourceConnection.svelte';
|
|
2
2
|
export type { SourceConnection } from './DCSourceConnection.svelte';
|
|
3
|
+
export { default as DCCatalogEntry } from './DCCatalogEntry.svelte';
|
|
4
|
+
export type { CatalogEntry, DataType, SourceType } from '@industream/datacatalog-client/dto';
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@industream/flowmaker-flowbox-ui-components",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"description": "Reusable Svelte components for FlowMaker FlowBox UI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"svelte": "./dist/index.js",
|
|
@@ -17,6 +17,11 @@
|
|
|
17
17
|
"types": "./dist/DCSourceConnection.svelte.d.ts",
|
|
18
18
|
"svelte": "./dist/DCSourceConnection.svelte",
|
|
19
19
|
"import": "./dist/DCSourceConnection.svelte"
|
|
20
|
+
},
|
|
21
|
+
"./DCCatalogEntry.svelte": {
|
|
22
|
+
"types": "./dist/DCCatalogEntry.svelte.d.ts",
|
|
23
|
+
"svelte": "./dist/DCCatalogEntry.svelte",
|
|
24
|
+
"import": "./dist/DCCatalogEntry.svelte"
|
|
20
25
|
}
|
|
21
26
|
},
|
|
22
27
|
"files": [
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { DataCatalogClient } from '@industream/datacatalog-client';
|
|
3
|
+
import type { CatalogEntry, DataType, SourceType } from '@industream/datacatalog-client/dto';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
id?: string;
|
|
7
|
+
dcapiurl?: string;
|
|
8
|
+
sourcetypefilter?: SourceType | SourceType[] | null;
|
|
9
|
+
datatypefilter?: DataType | DataType[] | null;
|
|
10
|
+
namefilter?: string | string[] | null;
|
|
11
|
+
initialselection?: string | null;
|
|
12
|
+
onentryselect?: (entry: CatalogEntry) => void;
|
|
13
|
+
onitemsloaded?: (entries: CatalogEntry[]) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let {
|
|
17
|
+
id = undefined,
|
|
18
|
+
dcapiurl = '',
|
|
19
|
+
sourcetypefilter = null,
|
|
20
|
+
datatypefilter = null,
|
|
21
|
+
namefilter = null,
|
|
22
|
+
initialselection = null,
|
|
23
|
+
onentryselect = null,
|
|
24
|
+
onitemsloaded = null
|
|
25
|
+
}: Props = $props();
|
|
26
|
+
|
|
27
|
+
let catalogEntries = $state<CatalogEntry[]>([]);
|
|
28
|
+
let filteredEntries = $state<CatalogEntry[]>([]);
|
|
29
|
+
let selectedValue = $state('');
|
|
30
|
+
let loading = $state(true);
|
|
31
|
+
let error = $state<string | null>(null);
|
|
32
|
+
let dropdownRef = $state<HTMLElement | null>(null);
|
|
33
|
+
|
|
34
|
+
// Load catalog entries when component mounts or dcapiurl changes
|
|
35
|
+
$effect(() => {
|
|
36
|
+
if (dcapiurl) {
|
|
37
|
+
loadCatalogEntries();
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Apply client-side filters when entries or filters change
|
|
42
|
+
$effect(() => {
|
|
43
|
+
filteredEntries = applyFilters(catalogEntries);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Inject styles into shadow DOM to fix trigger-label width
|
|
47
|
+
$effect(() => {
|
|
48
|
+
if (dropdownRef) {
|
|
49
|
+
requestAnimationFrame(() => {
|
|
50
|
+
const sr = (dropdownRef as any)?.shadowRoot;
|
|
51
|
+
if (sr && !sr.querySelector('#dc-entry-fix')) {
|
|
52
|
+
const style = document.createElement('style');
|
|
53
|
+
style.id = 'dc-entry-fix';
|
|
54
|
+
style.textContent = `.cds--list-box__field { display: flex; width: 100%; } .cds--list-box__label { flex: 1; width: 100%; }`;
|
|
55
|
+
sr.appendChild(style);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
function applyFilters(entries: CatalogEntry[]): CatalogEntry[] {
|
|
62
|
+
let result = entries;
|
|
63
|
+
|
|
64
|
+
// Filter by source type (from sourceConnection)
|
|
65
|
+
if (sourcetypefilter && sourcetypefilter.length > 0) {
|
|
66
|
+
const sourceTypes = Array.isArray(sourcetypefilter) ? sourcetypefilter : [sourcetypefilter];
|
|
67
|
+
result = result.filter(entry =>
|
|
68
|
+
entry.sourceConnection?.sourceType &&
|
|
69
|
+
sourceTypes.includes(entry.sourceConnection.sourceType)
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Filter by data type
|
|
74
|
+
if (datatypefilter && datatypefilter.length > 0) {
|
|
75
|
+
const dataTypes = Array.isArray(datatypefilter) ? datatypefilter : [datatypefilter];
|
|
76
|
+
result = result.filter(entry =>
|
|
77
|
+
entry.dataType && dataTypes.includes(entry.dataType)
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function loadCatalogEntries() {
|
|
85
|
+
loading = true;
|
|
86
|
+
error = null;
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
const client = new DataCatalogClient({ baseUrl: dcapiurl });
|
|
90
|
+
const filters: { names?: string[] } = {};
|
|
91
|
+
|
|
92
|
+
// Use name filter for API query if provided
|
|
93
|
+
if (namefilter && namefilter.length > 0) {
|
|
94
|
+
filters.names = Array.isArray(namefilter) ? namefilter : [namefilter];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const result = await client.catalogEntries.get(filters);
|
|
98
|
+
catalogEntries = result.items || [];
|
|
99
|
+
// Apply filters synchronously so initialselection works
|
|
100
|
+
filteredEntries = applyFilters(catalogEntries);
|
|
101
|
+
console.log('Loaded catalog entries:', catalogEntries);
|
|
102
|
+
|
|
103
|
+
// Auto-select initial selection if provided
|
|
104
|
+
if (initialselection) {
|
|
105
|
+
selectedValue = initialselection;
|
|
106
|
+
const entry = filteredEntries.find(ce => ce.id === initialselection);
|
|
107
|
+
if (entry && onentryselect) {
|
|
108
|
+
onentryselect(entry);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
onitemsloaded?.(filteredEntries);
|
|
113
|
+
} catch (e) {
|
|
114
|
+
console.error('Failed to load catalog entries:', e);
|
|
115
|
+
error = e.message || 'Failed to load catalog entries';
|
|
116
|
+
catalogEntries = [];
|
|
117
|
+
} finally {
|
|
118
|
+
loading = false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function handleSelect(e: CustomEvent) {
|
|
123
|
+
const selectedId = e.detail?.item?.value || e.target?.value;
|
|
124
|
+
console.log('Dropdown selected event:', e, 'selectedId:', selectedId);
|
|
125
|
+
selectedValue = selectedId;
|
|
126
|
+
|
|
127
|
+
if (selectedId && onentryselect) {
|
|
128
|
+
const selected = filteredEntries.find(ce => ce.id === selectedId);
|
|
129
|
+
console.log('Found selected:', selected);
|
|
130
|
+
if (selected) {
|
|
131
|
+
onentryselect(selected);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Exposed method to programmatically select a catalog entry
|
|
138
|
+
export function select(idOrEntry: string | CatalogEntry) {
|
|
139
|
+
const id = typeof idOrEntry === 'string' ? idOrEntry : idOrEntry?.id;
|
|
140
|
+
if (!id) return;
|
|
141
|
+
|
|
142
|
+
const entry = filteredEntries.find(ce => ce.id === id);
|
|
143
|
+
if (entry) {
|
|
144
|
+
selectedValue = id;
|
|
145
|
+
if (dropdownRef) {
|
|
146
|
+
dropdownRef.value = id;
|
|
147
|
+
}
|
|
148
|
+
if (onentryselect) {
|
|
149
|
+
onentryselect(entry);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Exposed method to get current selection
|
|
155
|
+
export function getSelection(): CatalogEntry | null {
|
|
156
|
+
return filteredEntries.find(ce => ce.id === selectedValue) || null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Exposed method to get all loaded entries (filtered)
|
|
160
|
+
export function getEntries(): CatalogEntry[] {
|
|
161
|
+
return filteredEntries;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Exposed method to get all loaded entries (unfiltered)
|
|
165
|
+
export function getAllEntries(): CatalogEntry[] {
|
|
166
|
+
return catalogEntries;
|
|
167
|
+
}
|
|
168
|
+
</script>
|
|
169
|
+
|
|
170
|
+
{#if loading}
|
|
171
|
+
<cds-dropdown {id} label="Catalog Entry" disabled>
|
|
172
|
+
<cds-dropdown-item value="">Loading...</cds-dropdown-item>
|
|
173
|
+
</cds-dropdown>
|
|
174
|
+
{:else if error}
|
|
175
|
+
<cds-dropdown {id} label="Catalog Entry" invalid invalid-text={error}>
|
|
176
|
+
<cds-dropdown-item value="">Error loading entries</cds-dropdown-item>
|
|
177
|
+
</cds-dropdown>
|
|
178
|
+
{:else}
|
|
179
|
+
<cds-dropdown
|
|
180
|
+
{id}
|
|
181
|
+
bind:this={dropdownRef}
|
|
182
|
+
label="Select a catalog entry"
|
|
183
|
+
value={selectedValue}
|
|
184
|
+
oncds-dropdown-selected={handleSelect}
|
|
185
|
+
>
|
|
186
|
+
{#each filteredEntries as entry}
|
|
187
|
+
<cds-dropdown-item value={entry.id}>
|
|
188
|
+
<span style="display: flex; justify-content: space-between; align-items: center; width: 100%;">
|
|
189
|
+
<span style="display: flex; align-items: center; gap: 0.5rem;">
|
|
190
|
+
<span>{entry.name}</span>
|
|
191
|
+
<cds-tag size="sm" type="green">{entry.dataType}</cds-tag>
|
|
192
|
+
</span>
|
|
193
|
+
<span style="display: flex; align-items: center; gap: 0.5rem;">
|
|
194
|
+
<span style="color: var(--cds-text-secondary, #525252); font-size: 0.875rem;">Connection: {entry.sourceConnection?.name || 'Unknown'}</span>
|
|
195
|
+
{#if entry.labels?.length}
|
|
196
|
+
<span style="display: flex; gap: 0.25rem;">
|
|
197
|
+
{#each entry.labels as label}
|
|
198
|
+
<cds-tag size="sm" type="purple">{label.name}</cds-tag>
|
|
199
|
+
{/each}
|
|
200
|
+
</span>
|
|
201
|
+
{/if}
|
|
202
|
+
</span>
|
|
203
|
+
</span>
|
|
204
|
+
</cds-dropdown-item>
|
|
205
|
+
{/each}
|
|
206
|
+
</cds-dropdown>
|
|
207
|
+
{/if}
|
|
208
|
+
|
|
209
|
+
<style>
|
|
210
|
+
/* Styles moved to inline for shadow DOM compatibility */
|
|
211
|
+
</style>
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export { default as DCSourceConnection } from './DCSourceConnection.svelte';
|
|
2
2
|
export type { SourceConnection } from './DCSourceConnection.svelte';
|
|
3
3
|
|
|
4
|
+
export { default as DCCatalogEntry } from './DCCatalogEntry.svelte';
|
|
5
|
+
export type { CatalogEntry, DataType, SourceType } from '@industream/datacatalog-client/dto';
|
|
6
|
+
|
|
4
7
|
|
|
5
8
|
|