@smartnet360/svelte-components 0.0.142 → 0.0.144

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 (37) hide show
  1. package/dist/apps/antenna-tools/components/AntennaControls.svelte +14 -6
  2. package/dist/apps/antenna-tools/components/AntennaTools.svelte +6 -5
  3. package/dist/core/Auth/auth.svelte.js +72 -39
  4. package/dist/core/Auth/config.d.ts +1 -0
  5. package/dist/core/Auth/config.js +3 -4
  6. package/dist/map-v3/demo/DemoMap.svelte +3 -1
  7. package/dist/map-v3/features/custom/components/CustomCellSetManager.svelte +205 -14
  8. package/dist/map-v3/features/custom/components/CustomCellSetManager.svelte.d.ts +3 -0
  9. package/dist/map-v3/features/custom/components/ServerSetBrowser.svelte +398 -0
  10. package/dist/map-v3/features/custom/components/ServerSetBrowser.svelte.d.ts +22 -0
  11. package/dist/map-v3/features/custom/components/index.d.ts +1 -0
  12. package/dist/map-v3/features/custom/components/index.js +1 -0
  13. package/dist/map-v3/features/custom/db/custom-sets-api.d.ts +65 -0
  14. package/dist/map-v3/features/custom/db/custom-sets-api.js +220 -0
  15. package/dist/map-v3/features/custom/db/custom-sets-repository.d.ts +77 -0
  16. package/dist/map-v3/features/custom/db/custom-sets-repository.js +195 -0
  17. package/dist/map-v3/features/custom/db/index.d.ts +10 -0
  18. package/dist/map-v3/features/custom/db/index.js +9 -0
  19. package/dist/map-v3/features/custom/db/schema.sql +102 -0
  20. package/dist/map-v3/features/custom/db/types.d.ts +95 -0
  21. package/dist/map-v3/features/custom/db/types.js +95 -0
  22. package/dist/map-v3/features/custom/index.d.ts +2 -0
  23. package/dist/map-v3/features/custom/index.js +2 -0
  24. package/dist/map-v3/features/custom/logic/csv-parser.d.ts +12 -1
  25. package/dist/map-v3/features/custom/logic/csv-parser.js +54 -16
  26. package/dist/map-v3/features/custom/logic/tree-adapter.js +5 -3
  27. package/dist/map-v3/features/custom/stores/custom-cell-sets.svelte.d.ts +5 -1
  28. package/dist/map-v3/features/custom/stores/custom-cell-sets.svelte.js +6 -3
  29. package/dist/shared/csv-import/ColumnMapper.svelte +194 -0
  30. package/dist/shared/csv-import/ColumnMapper.svelte.d.ts +22 -0
  31. package/dist/shared/csv-import/column-detector.d.ts +58 -0
  32. package/dist/shared/csv-import/column-detector.js +228 -0
  33. package/dist/shared/csv-import/index.d.ts +10 -0
  34. package/dist/shared/csv-import/index.js +12 -0
  35. package/dist/shared/csv-import/types.d.ts +67 -0
  36. package/dist/shared/csv-import/types.js +70 -0
  37. package/package.json +1 -1
@@ -0,0 +1,398 @@
1
+ <script lang="ts">
2
+ /**
3
+ * Server Set Browser
4
+ *
5
+ * Modal component for browsing, loading, and deleting shared custom sets from the server.
6
+ * Shows a list of available sets with load/delete actions.
7
+ * Prompts user for overwrite/skip when loading a set with conflicting name.
8
+ */
9
+ import type { CustomSetsApiClient } from '../db/custom-sets-api';
10
+ import type { CustomSetListItem } from '../db/types';
11
+ import type { CustomCellSet } from '../types';
12
+ import type { CustomCellSetsStore } from '../stores/custom-cell-sets.svelte';
13
+
14
+ interface Props {
15
+ /** API client for server operations */
16
+ apiClient: CustomSetsApiClient;
17
+ /** Local sets store for importing */
18
+ setsStore: CustomCellSetsStore;
19
+ /** Whether the modal is visible */
20
+ show: boolean;
21
+ /** Called when modal should close */
22
+ onclose: () => void;
23
+ }
24
+
25
+ let {
26
+ apiClient,
27
+ setsStore,
28
+ show,
29
+ onclose
30
+ }: Props = $props();
31
+
32
+ // State
33
+ let loading = $state(true);
34
+ let serverSets = $state<CustomSetListItem[]>([]);
35
+ let error = $state('');
36
+ let searchQuery = $state('');
37
+
38
+ // Loading/action state
39
+ let loadingSetId = $state<string | null>(null);
40
+ let deletingSetId = $state<string | null>(null);
41
+
42
+ // Conflict resolution modal
43
+ let conflictSet = $state<CustomSetListItem | null>(null);
44
+ let conflictLocalSet = $state<CustomCellSet | null>(null);
45
+ let loadedSetData = $state<CustomCellSet | null>(null);
46
+
47
+ // Filtered sets based on search
48
+ let filteredSets = $derived.by(() => {
49
+ if (!searchQuery.trim()) return serverSets;
50
+ const query = searchQuery.toLowerCase();
51
+ return serverSets.filter(s =>
52
+ s.name.toLowerCase().includes(query) ||
53
+ s.created_by.toLowerCase().includes(query)
54
+ );
55
+ });
56
+
57
+ // Load sets when modal opens
58
+ $effect(() => {
59
+ if (show) {
60
+ loadServerSets();
61
+ }
62
+ });
63
+
64
+ async function loadServerSets() {
65
+ loading = true;
66
+ error = '';
67
+
68
+ const result = await apiClient.listSets();
69
+
70
+ if (result.success && result.data) {
71
+ serverSets = result.data;
72
+ } else {
73
+ error = result.error || 'Failed to load sets from server';
74
+ serverSets = [];
75
+ }
76
+
77
+ loading = false;
78
+ }
79
+
80
+ async function handleLoad(set: CustomSetListItem) {
81
+ loadingSetId = set.id;
82
+ error = '';
83
+
84
+ // Fetch full set data
85
+ const result = await apiClient.getSet(set.id);
86
+
87
+ if (!result.success || !result.data) {
88
+ error = result.error || 'Failed to load set';
89
+ loadingSetId = null;
90
+ return;
91
+ }
92
+
93
+ // Check for name conflict with local sets
94
+ const existingLocal = setsStore.sets.find(s => s.name === result.data!.name);
95
+
96
+ if (existingLocal) {
97
+ // Show conflict resolution modal
98
+ conflictSet = set;
99
+ conflictLocalSet = existingLocal;
100
+ loadedSetData = result.data;
101
+ loadingSetId = null;
102
+ return;
103
+ }
104
+
105
+ // No conflict - import directly
106
+ importSetToStore(result.data);
107
+ loadingSetId = null;
108
+ }
109
+
110
+ function handleConflictOverwrite() {
111
+ if (!loadedSetData || !conflictLocalSet) return;
112
+
113
+ // Remove existing local set
114
+ setsStore.removeSet(conflictLocalSet.id);
115
+
116
+ // Import the new one
117
+ importSetToStore(loadedSetData);
118
+
119
+ // Clear conflict state
120
+ conflictSet = null;
121
+ conflictLocalSet = null;
122
+ loadedSetData = null;
123
+ }
124
+
125
+ function handleConflictSkip() {
126
+ // Just clear the conflict state
127
+ conflictSet = null;
128
+ conflictLocalSet = null;
129
+ loadedSetData = null;
130
+ }
131
+
132
+ function importSetToStore(set: CustomCellSet) {
133
+ // Create import result format from set
134
+ const importResult = {
135
+ cells: set.cells,
136
+ unmatchedTxIds: set.unmatchedTxIds,
137
+ groups: set.groups,
138
+ extraColumns: set.extraColumns,
139
+ totalRows: set.cells.length,
140
+ cellCount: set.cells.filter(c => c.geometry === 'cell').length,
141
+ pointCount: set.cells.filter(c => c.geometry === 'point').length
142
+ };
143
+
144
+ // Create the set in local store
145
+ const newSet = setsStore.createSet(set.name, importResult);
146
+
147
+ // Apply styling from loaded set
148
+ setsStore.updateSetSettings(newSet.id, {
149
+ baseSize: set.baseSize,
150
+ pointSize: set.pointSize,
151
+ opacity: set.opacity,
152
+ defaultColor: set.defaultColor
153
+ });
154
+
155
+ // Apply group colors
156
+ for (const [group, color] of Object.entries(set.groupColors)) {
157
+ setsStore.setGroupColor(newSet.id, group, color);
158
+ }
159
+ }
160
+
161
+ async function handleDelete(set: CustomSetListItem) {
162
+ if (!confirm(`Delete "${set.name}" from server? This cannot be undone.`)) {
163
+ return;
164
+ }
165
+
166
+ deletingSetId = set.id;
167
+ error = '';
168
+
169
+ const result = await apiClient.deleteSet(set.id);
170
+
171
+ if (result.success) {
172
+ // Remove from local list
173
+ serverSets = serverSets.filter(s => s.id !== set.id);
174
+ } else {
175
+ error = result.error || 'Failed to delete set';
176
+ }
177
+
178
+ deletingSetId = null;
179
+ }
180
+
181
+ function formatDate(dateStr: string): string {
182
+ try {
183
+ const date = new Date(dateStr);
184
+ return date.toLocaleDateString(undefined, {
185
+ year: 'numeric',
186
+ month: 'short',
187
+ day: 'numeric'
188
+ });
189
+ } catch {
190
+ return dateStr;
191
+ }
192
+ }
193
+
194
+ function getItemSummary(set: CustomSetListItem): string {
195
+ const parts: string[] = [];
196
+ if (set.cellCount && set.cellCount > 0) {
197
+ parts.push(`${set.cellCount} cells`);
198
+ }
199
+ if (set.pointCount && set.pointCount > 0) {
200
+ parts.push(`${set.pointCount} points`);
201
+ }
202
+ return parts.join(', ') || 'No items';
203
+ }
204
+ </script>
205
+
206
+ {#if show}
207
+ <div class="modal fade show d-block" tabindex="-1" style="background: rgba(0,0,0,0.5);">
208
+ <div class="modal-dialog modal-dialog-centered modal-lg">
209
+ <div class="modal-content">
210
+ <div class="modal-header py-2">
211
+ <h6 class="modal-title">
212
+ <i class="bi bi-cloud-download me-2"></i>
213
+ Load from Server
214
+ </h6>
215
+ <button type="button" class="btn-close" onclick={onclose} aria-label="Close"></button>
216
+ </div>
217
+
218
+ <div class="modal-body p-0">
219
+ <!-- Search bar -->
220
+ <div class="p-2 border-bottom">
221
+ <div class="input-group input-group-sm">
222
+ <span class="input-group-text">
223
+ <i class="bi bi-search"></i>
224
+ </span>
225
+ <input
226
+ type="text"
227
+ class="form-control"
228
+ placeholder="Search sets..."
229
+ bind:value={searchQuery}
230
+ />
231
+ {#if searchQuery}
232
+ <button
233
+ class="btn btn-outline-secondary"
234
+ type="button"
235
+ onclick={() => searchQuery = ''}
236
+ aria-label="Clear search"
237
+ >
238
+ <i class="bi bi-x"></i>
239
+ </button>
240
+ {/if}
241
+ <button
242
+ class="btn btn-outline-secondary"
243
+ type="button"
244
+ onclick={loadServerSets}
245
+ disabled={loading}
246
+ title="Refresh"
247
+ aria-label="Refresh list"
248
+ >
249
+ <i class="bi bi-arrow-clockwise" class:spin={loading}></i>
250
+ </button>
251
+ </div>
252
+ </div>
253
+
254
+ <!-- Error message -->
255
+ {#if error}
256
+ <div class="alert alert-danger m-2 py-2 small">
257
+ <i class="bi bi-exclamation-triangle me-1"></i>
258
+ {error}
259
+ </div>
260
+ {/if}
261
+
262
+ <!-- Loading state -->
263
+ {#if loading}
264
+ <div class="text-center p-4">
265
+ <div class="spinner-border spinner-border-sm text-primary" role="status">
266
+ <span class="visually-hidden">Loading...</span>
267
+ </div>
268
+ <p class="text-muted small mt-2 mb-0">Loading sets from server...</p>
269
+ </div>
270
+ {:else if filteredSets.length === 0}
271
+ <div class="text-center p-4">
272
+ <i class="bi bi-inbox fs-1 text-muted"></i>
273
+ <p class="text-muted small mt-2 mb-0">
274
+ {searchQuery ? 'No sets match your search' : 'No shared sets available'}
275
+ </p>
276
+ </div>
277
+ {:else}
278
+ <!-- Sets list -->
279
+ <div class="sets-list" style="max-height: 400px; overflow-y: auto;">
280
+ {#each filteredSets as set (set.id)}
281
+ <div class="set-row d-flex align-items-center px-3 py-2 border-bottom">
282
+ <div class="flex-grow-1" style="min-width: 0;">
283
+ <div class="fw-medium text-truncate">{set.name}</div>
284
+ <div class="small text-muted">
285
+ {getItemSummary(set)}
286
+ {#if set.groups && set.groups.length > 0}
287
+ <span class="mx-1">·</span>
288
+ {set.groups.length} groups
289
+ {/if}
290
+ </div>
291
+ <div class="small text-muted">
292
+ <i class="bi bi-person me-1"></i>{set.created_by}
293
+ <span class="mx-1">·</span>
294
+ <i class="bi bi-calendar me-1"></i>{formatDate(set.updated_at)}
295
+ </div>
296
+ </div>
297
+ <div class="d-flex gap-1 ms-2">
298
+ <button
299
+ class="btn btn-sm btn-outline-primary"
300
+ onclick={() => handleLoad(set)}
301
+ disabled={loadingSetId === set.id}
302
+ title="Load this set"
303
+ aria-label="Load this set"
304
+ >
305
+ {#if loadingSetId === set.id}
306
+ <span class="spinner-border spinner-border-sm" role="status"></span>
307
+ {:else}
308
+ <i class="bi bi-download"></i>
309
+ {/if}
310
+ </button>
311
+ <button
312
+ class="btn btn-sm btn-outline-danger"
313
+ onclick={() => handleDelete(set)}
314
+ disabled={deletingSetId === set.id}
315
+ title="Delete from server"
316
+ aria-label="Delete from server"
317
+ >
318
+ {#if deletingSetId === set.id}
319
+ <span class="spinner-border spinner-border-sm" role="status"></span>
320
+ {:else}
321
+ <i class="bi bi-trash"></i>
322
+ {/if}
323
+ </button>
324
+ </div>
325
+ </div>
326
+ {/each}
327
+ </div>
328
+ {/if}
329
+ </div>
330
+
331
+ <div class="modal-footer py-2">
332
+ <small class="text-muted me-auto">
333
+ {filteredSets.length} set{filteredSets.length !== 1 ? 's' : ''} available
334
+ </small>
335
+ <button type="button" class="btn btn-secondary btn-sm" onclick={onclose}>
336
+ Close
337
+ </button>
338
+ </div>
339
+ </div>
340
+ </div>
341
+ </div>
342
+ {/if}
343
+
344
+ <!-- Conflict Resolution Modal -->
345
+ {#if conflictSet && conflictLocalSet}
346
+ <div class="modal fade show d-block" tabindex="-1" style="background: rgba(0,0,0,0.6); z-index: 2001;">
347
+ <div class="modal-dialog modal-dialog-centered">
348
+ <div class="modal-content">
349
+ <div class="modal-header py-2 bg-warning-subtle">
350
+ <h6 class="modal-title">
351
+ <i class="bi bi-exclamation-triangle me-2"></i>
352
+ Name Conflict
353
+ </h6>
354
+ </div>
355
+ <div class="modal-body">
356
+ <p>
357
+ A set named <strong>"{conflictSet.name}"</strong> already exists locally.
358
+ </p>
359
+ <p class="text-muted small mb-0">
360
+ What would you like to do?
361
+ </p>
362
+ </div>
363
+ <div class="modal-footer py-2">
364
+ <button type="button" class="btn btn-secondary btn-sm" onclick={handleConflictSkip}>
365
+ Skip
366
+ </button>
367
+ <button type="button" class="btn btn-warning btn-sm" onclick={handleConflictOverwrite}>
368
+ <i class="bi bi-arrow-repeat me-1"></i>
369
+ Overwrite Local
370
+ </button>
371
+ </div>
372
+ </div>
373
+ </div>
374
+ </div>
375
+ {/if}
376
+
377
+ <style>
378
+ .modal {
379
+ z-index: 2000;
380
+ }
381
+
382
+ .set-row {
383
+ transition: background-color 0.15s ease;
384
+ }
385
+
386
+ .set-row:hover {
387
+ background-color: rgba(0, 0, 0, 0.02);
388
+ }
389
+
390
+ .spin {
391
+ animation: spin 1s linear infinite;
392
+ }
393
+
394
+ @keyframes spin {
395
+ from { transform: rotate(0deg); }
396
+ to { transform: rotate(360deg); }
397
+ }
398
+ </style>
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Server Set Browser
3
+ *
4
+ * Modal component for browsing, loading, and deleting shared custom sets from the server.
5
+ * Shows a list of available sets with load/delete actions.
6
+ * Prompts user for overwrite/skip when loading a set with conflicting name.
7
+ */
8
+ import type { CustomSetsApiClient } from '../db/custom-sets-api';
9
+ import type { CustomCellSetsStore } from '../stores/custom-cell-sets.svelte';
10
+ interface Props {
11
+ /** API client for server operations */
12
+ apiClient: CustomSetsApiClient;
13
+ /** Local sets store for importing */
14
+ setsStore: CustomCellSetsStore;
15
+ /** Whether the modal is visible */
16
+ show: boolean;
17
+ /** Called when modal should close */
18
+ onclose: () => void;
19
+ }
20
+ declare const ServerSetBrowser: import("svelte").Component<Props, {}, "">;
21
+ type ServerSetBrowser = ReturnType<typeof ServerSetBrowser>;
22
+ export default ServerSetBrowser;
@@ -3,3 +3,4 @@
3
3
  */
4
4
  export { default as CustomCellFilterControl } from './CustomCellFilterControl.svelte';
5
5
  export { default as CustomCellSetManager } from './CustomCellSetManager.svelte';
6
+ export { default as ServerSetBrowser } from './ServerSetBrowser.svelte';
@@ -3,3 +3,4 @@
3
3
  */
4
4
  export { default as CustomCellFilterControl } from './CustomCellFilterControl.svelte';
5
5
  export { default as CustomCellSetManager } from './CustomCellSetManager.svelte';
6
+ export { default as ServerSetBrowser } from './ServerSetBrowser.svelte';
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Custom Feature - API Client
3
+ *
4
+ * Client-side service for interacting with custom cell sets API.
5
+ * Designed to work with any backend that implements the expected endpoints.
6
+ *
7
+ * Usage in consuming app:
8
+ * ```typescript
9
+ * const api = new CustomSetsApiClient('/api/custom-sets');
10
+ * const sets = await api.listSets();
11
+ * ```
12
+ */
13
+ import type { CustomSetListItem, ApiResponse } from './types';
14
+ import type { CustomCellSet } from '../types';
15
+ /**
16
+ * API client for custom cell sets
17
+ * Provides CRUD operations against a REST API endpoint
18
+ */
19
+ export declare class CustomSetsApiClient {
20
+ private baseUrl;
21
+ private headers;
22
+ /**
23
+ * Create a new API client
24
+ * @param baseUrl Base URL for the API (e.g., '/api/custom-sets')
25
+ * @param headers Optional additional headers (e.g., auth tokens)
26
+ */
27
+ constructor(baseUrl: string, headers?: Record<string, string>);
28
+ /**
29
+ * Update headers (e.g., after auth token refresh)
30
+ */
31
+ setHeaders(headers: Record<string, string>): void;
32
+ /**
33
+ * List all available sets (lightweight, no cell data)
34
+ */
35
+ listSets(): Promise<ApiResponse<CustomSetListItem[]>>;
36
+ /**
37
+ * Get a single set by ID (full data including cells)
38
+ */
39
+ getSet(id: string): Promise<ApiResponse<CustomCellSet>>;
40
+ /**
41
+ * Save a set to the server (create or update)
42
+ * @param set The CustomCellSet to save
43
+ * @param id Optional ID for update (omit for create)
44
+ */
45
+ saveSet(set: CustomCellSet, id?: string): Promise<ApiResponse<{
46
+ id: string;
47
+ }>>;
48
+ /**
49
+ * Delete a set from the server
50
+ */
51
+ deleteSet(id: string): Promise<ApiResponse<void>>;
52
+ /**
53
+ * Check if a set with the given name exists
54
+ */
55
+ checkNameExists(name: string): Promise<ApiResponse<{
56
+ exists: boolean;
57
+ id?: string;
58
+ }>>;
59
+ }
60
+ /**
61
+ * Factory function to create API client
62
+ * @param baseUrl API endpoint base URL
63
+ * @param headers Optional headers (e.g., auth tokens)
64
+ */
65
+ export declare function createCustomSetsApi(baseUrl: string, headers?: Record<string, string>): CustomSetsApiClient;