@smartnet360/svelte-components 0.0.124 → 0.0.126

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 (53) hide show
  1. package/dist/apps/antenna-tools/components/AntennaSettingsModal.svelte +4 -174
  2. package/dist/apps/antenna-tools/components/DatabaseViewer.svelte +2 -2
  3. package/dist/apps/antenna-tools/components/MSIConverter.svelte +302 -43
  4. package/dist/apps/antenna-tools/db.js +4 -0
  5. package/dist/apps/antenna-tools/utils/db-utils.d.ts +19 -0
  6. package/dist/apps/antenna-tools/utils/db-utils.js +108 -0
  7. package/dist/core/Auth/LoginForm.svelte +397 -0
  8. package/dist/core/Auth/LoginForm.svelte.d.ts +16 -0
  9. package/dist/core/Auth/auth.svelte.d.ts +22 -0
  10. package/dist/core/Auth/auth.svelte.js +229 -0
  11. package/dist/core/Auth/config.d.ts +25 -0
  12. package/dist/core/Auth/config.js +256 -0
  13. package/dist/core/Auth/index.d.ts +4 -0
  14. package/dist/core/Auth/index.js +5 -0
  15. package/dist/core/Auth/types.d.ts +140 -0
  16. package/dist/core/Auth/types.js +2 -0
  17. package/dist/core/LandingPage/App.svelte +102 -0
  18. package/dist/core/LandingPage/App.svelte.d.ts +20 -0
  19. package/dist/core/LandingPage/LandingPage.svelte +480 -0
  20. package/dist/core/LandingPage/LandingPage.svelte.d.ts +21 -0
  21. package/dist/core/LandingPage/index.d.ts +2 -0
  22. package/dist/core/LandingPage/index.js +3 -0
  23. package/dist/core/index.d.ts +2 -0
  24. package/dist/core/index.js +4 -0
  25. package/dist/map-v3/demo/DemoMap.svelte +18 -0
  26. package/dist/map-v3/demo/demo-custom-cells.d.ts +21 -0
  27. package/dist/map-v3/demo/demo-custom-cells.js +48 -0
  28. package/dist/map-v3/features/cells/custom/components/CustomCellFilterControl.svelte +220 -0
  29. package/dist/map-v3/features/cells/custom/components/CustomCellFilterControl.svelte.d.ts +15 -0
  30. package/dist/map-v3/features/cells/custom/components/CustomCellSetManager.svelte +306 -0
  31. package/dist/map-v3/features/cells/custom/components/CustomCellSetManager.svelte.d.ts +10 -0
  32. package/dist/map-v3/features/cells/custom/components/index.d.ts +5 -0
  33. package/dist/map-v3/features/cells/custom/components/index.js +5 -0
  34. package/dist/map-v3/features/cells/custom/index.d.ts +32 -0
  35. package/dist/map-v3/features/cells/custom/index.js +35 -0
  36. package/dist/map-v3/features/cells/custom/layers/CustomCellsLayer.svelte +262 -0
  37. package/dist/map-v3/features/cells/custom/layers/CustomCellsLayer.svelte.d.ts +10 -0
  38. package/dist/map-v3/features/cells/custom/layers/index.d.ts +4 -0
  39. package/dist/map-v3/features/cells/custom/layers/index.js +4 -0
  40. package/dist/map-v3/features/cells/custom/logic/csv-parser.d.ts +20 -0
  41. package/dist/map-v3/features/cells/custom/logic/csv-parser.js +164 -0
  42. package/dist/map-v3/features/cells/custom/logic/index.d.ts +5 -0
  43. package/dist/map-v3/features/cells/custom/logic/index.js +5 -0
  44. package/dist/map-v3/features/cells/custom/logic/tree-adapter.d.ts +24 -0
  45. package/dist/map-v3/features/cells/custom/logic/tree-adapter.js +67 -0
  46. package/dist/map-v3/features/cells/custom/stores/custom-cell-sets.svelte.d.ts +78 -0
  47. package/dist/map-v3/features/cells/custom/stores/custom-cell-sets.svelte.js +242 -0
  48. package/dist/map-v3/features/cells/custom/stores/index.d.ts +4 -0
  49. package/dist/map-v3/features/cells/custom/stores/index.js +4 -0
  50. package/dist/map-v3/features/cells/custom/types.d.ts +83 -0
  51. package/dist/map-v3/features/cells/custom/types.js +23 -0
  52. package/dist/map-v3/shared/controls/MapControl.svelte +27 -3
  53. package/package.json +1 -1
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Custom Cells Feature - Main Barrel Export
3
+ *
4
+ * Ad-hoc cell layers loaded from CSV files.
5
+ * Reference existing cells by txId with custom grouping and sizing.
6
+ *
7
+ * @example
8
+ * ```svelte
9
+ * <script>
10
+ * import {
11
+ * CustomCellsLayer,
12
+ * CustomCellSetManager,
13
+ * CustomCellFilterControl,
14
+ * createCustomCellSetsStore
15
+ * } from './';
16
+ *
17
+ * const customSetsStore = createCustomCellSetsStore(cellDataStore, 'my-namespace');
18
+ * </script>
19
+ *
20
+ * <CustomCellSetManager setsStore={customSetsStore} position="top-left" />
21
+ * <CustomCellsLayer setsStore={customSetsStore} />
22
+ * {#each customSetsStore.sets as set (set.id)}
23
+ * <CustomCellFilterControl setsStore={customSetsStore} {set} position="top-left" />
24
+ * {/each}
25
+ * ```
26
+ */
27
+ export { CUSTOM_CELL_PALETTE } from './types';
28
+ // Stores
29
+ export { CustomCellSetsStore, createCustomCellSetsStore } from './stores';
30
+ // Components
31
+ export { CustomCellFilterControl, CustomCellSetManager } from './components';
32
+ // Layers
33
+ export { CustomCellsLayer } from './layers';
34
+ // Logic (for advanced usage)
35
+ export { parseCustomCellsCsv, buildCellLookup, buildCustomCellTree, getGroupCounts } from './logic';
@@ -0,0 +1,262 @@
1
+ <script lang="ts">
2
+ /**
3
+ * Custom Cells Layer
4
+ *
5
+ * Renders custom cell sets on the map with sizeFactor support.
6
+ * Each set is rendered as a separate layer for independent styling.
7
+ */
8
+ import { getContext, onMount, onDestroy } from 'svelte';
9
+ import type { MapStore } from '../../../../core/stores/map.store.svelte';
10
+ import type { CustomCellSetsStore } from '../stores/custom-cell-sets.svelte';
11
+ import type { CustomCellSet, CustomCell } from '../types';
12
+ import { generateCellArc, calculateRadiusInMeters } from '../../logic/geometry';
13
+ import type mapboxgl from 'mapbox-gl';
14
+
15
+ interface Props {
16
+ /** The custom cell sets store */
17
+ setsStore: CustomCellSetsStore;
18
+ /** Optional: specific set ID to render (if not provided, renders all) */
19
+ setId?: string;
20
+ }
21
+
22
+ let { setsStore, setId }: Props = $props();
23
+
24
+ const mapStore = getContext<MapStore>('MAP_CONTEXT');
25
+
26
+ // Track active layer/source IDs for cleanup
27
+ let activeSources = new Set<string>();
28
+ let activeLayers = new Set<string>();
29
+
30
+ // Debounce timer
31
+ let updateTimeout: ReturnType<typeof setTimeout>;
32
+
33
+ /**
34
+ * Get source ID for a set
35
+ */
36
+ function getSourceId(setId: string): string {
37
+ return `custom-cells-${setId}`;
38
+ }
39
+
40
+ /**
41
+ * Get layer IDs for a set
42
+ */
43
+ function getLayerIds(setId: string): { fill: string; line: string } {
44
+ return {
45
+ fill: `custom-cells-fill-${setId}`,
46
+ line: `custom-cells-line-${setId}`
47
+ };
48
+ }
49
+
50
+ /**
51
+ * Add source and layers for a set
52
+ */
53
+ function addSetLayers(map: mapboxgl.Map, set: CustomCellSet) {
54
+ const sourceId = getSourceId(set.id);
55
+ const { fill, line } = getLayerIds(set.id);
56
+
57
+ // Add source if not exists
58
+ if (!map.getSource(sourceId)) {
59
+ map.addSource(sourceId, {
60
+ type: 'geojson',
61
+ data: { type: 'FeatureCollection', features: [] }
62
+ });
63
+ activeSources.add(sourceId);
64
+ }
65
+
66
+ // Add fill layer
67
+ if (!map.getLayer(fill)) {
68
+ map.addLayer({
69
+ id: fill,
70
+ type: 'fill',
71
+ source: sourceId,
72
+ paint: {
73
+ 'fill-color': ['get', 'color'],
74
+ 'fill-opacity': set.opacity
75
+ }
76
+ });
77
+ activeLayers.add(fill);
78
+ }
79
+
80
+ // Add line layer
81
+ if (!map.getLayer(line)) {
82
+ map.addLayer({
83
+ id: line,
84
+ type: 'line',
85
+ source: sourceId,
86
+ paint: {
87
+ 'line-color': ['get', 'lineColor'],
88
+ 'line-width': ['get', 'lineWidth'],
89
+ 'line-opacity': ['get', 'lineOpacity']
90
+ }
91
+ });
92
+ activeLayers.add(line);
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Remove layers and source for a set
98
+ */
99
+ function removeSetLayers(map: mapboxgl.Map, setIdToRemove: string) {
100
+ const sourceId = getSourceId(setIdToRemove);
101
+ const { fill, line } = getLayerIds(setIdToRemove);
102
+
103
+ if (map.getLayer(line)) {
104
+ map.removeLayer(line);
105
+ activeLayers.delete(line);
106
+ }
107
+ if (map.getLayer(fill)) {
108
+ map.removeLayer(fill);
109
+ activeLayers.delete(fill);
110
+ }
111
+ if (map.getSource(sourceId)) {
112
+ map.removeSource(sourceId);
113
+ activeSources.delete(sourceId);
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Render cells for a specific set
119
+ */
120
+ function renderSet(map: mapboxgl.Map, set: CustomCellSet) {
121
+ const bounds = map.getBounds();
122
+ if (!bounds) return;
123
+
124
+ const zoom = map.getZoom();
125
+ const centerLat = map.getCenter().lat;
126
+
127
+ // Calculate base radius from pixel size
128
+ const baseRadiusMeters = calculateRadiusInMeters(centerLat, zoom, set.baseSize);
129
+
130
+ const features: GeoJSON.Feature[] = [];
131
+
132
+ for (const customCell of set.cells) {
133
+ const cell = customCell.resolvedCell;
134
+ if (!cell) continue;
135
+
136
+ // Check group visibility
137
+ if (!set.visibleGroups.has(customCell.customGroup)) continue;
138
+
139
+ // Viewport filter
140
+ if (!bounds.contains([cell.longitude, cell.latitude])) continue;
141
+
142
+ // Get color for this group
143
+ const color = set.groupColors[customCell.customGroup] || set.defaultColor;
144
+
145
+ // Apply size factor
146
+ const radiusMeters = baseRadiusMeters * customCell.sizeFactor;
147
+
148
+ // Generate arc feature
149
+ const feature = generateCellArc(cell, radiusMeters, 50, color);
150
+
151
+ // Add custom properties for tooltips
152
+ if (feature.properties) {
153
+ feature.properties.customGroup = customCell.customGroup;
154
+ feature.properties.sizeFactor = customCell.sizeFactor;
155
+ feature.properties.setName = set.name;
156
+
157
+ // Add extra fields
158
+ for (const [key, value] of Object.entries(customCell.extraFields)) {
159
+ feature.properties[`extra_${key}`] = value;
160
+ }
161
+ }
162
+
163
+ features.push(feature);
164
+ }
165
+
166
+ // Update source
167
+ const sourceId = getSourceId(set.id);
168
+ const source = map.getSource(sourceId) as mapboxgl.GeoJSONSource;
169
+ if (source) {
170
+ source.setData({
171
+ type: 'FeatureCollection',
172
+ features: features as any
173
+ });
174
+ }
175
+
176
+ // Update layer opacity
177
+ const { fill } = getLayerIds(set.id);
178
+ if (map.getLayer(fill)) {
179
+ map.setPaintProperty(fill, 'fill-opacity', set.opacity);
180
+ }
181
+
182
+ console.log(`[CustomCellsLayer] Rendered ${features.length} features for set "${set.name}"`);
183
+ }
184
+
185
+ /**
186
+ * Main update function
187
+ */
188
+ function updateLayers() {
189
+ const map = mapStore.map;
190
+ if (!map) return;
191
+
192
+ clearTimeout(updateTimeout);
193
+ updateTimeout = setTimeout(() => {
194
+ const setsToRender = setId
195
+ ? setsStore.sets.filter(s => s.id === setId)
196
+ : setsStore.sets;
197
+
198
+ // Track which sets we're rendering
199
+ const activeSetIds = new Set(setsToRender.map(s => s.id));
200
+
201
+ // Remove layers for sets that no longer exist
202
+ for (const sourceId of activeSources) {
203
+ const setIdFromSource = sourceId.replace('custom-cells-', '');
204
+ if (!activeSetIds.has(setIdFromSource)) {
205
+ removeSetLayers(map, setIdFromSource);
206
+ }
207
+ }
208
+
209
+ // Render each set
210
+ for (const set of setsToRender) {
211
+ if (set.visible) {
212
+ addSetLayers(map, set);
213
+ renderSet(map, set);
214
+ } else {
215
+ // Hide by clearing data
216
+ const sourceId = getSourceId(set.id);
217
+ const source = map.getSource(sourceId) as mapboxgl.GeoJSONSource;
218
+ if (source) {
219
+ source.setData({ type: 'FeatureCollection', features: [] });
220
+ }
221
+ }
222
+ }
223
+ }, 100);
224
+ }
225
+
226
+ // Setup and reactive updates
227
+ $effect(() => {
228
+ const map = mapStore.map;
229
+ if (!map) return;
230
+
231
+ const onMapEvent = () => updateLayers();
232
+
233
+ // Initial render
234
+ updateLayers();
235
+
236
+ // Listen to map events
237
+ map.on('moveend', onMapEvent);
238
+ map.on('zoomend', onMapEvent);
239
+ map.on('style.load', onMapEvent);
240
+
241
+ return () => {
242
+ map.off('moveend', onMapEvent);
243
+ map.off('zoomend', onMapEvent);
244
+ map.off('style.load', onMapEvent);
245
+
246
+ // Cleanup all layers
247
+ for (const sourceId of activeSources) {
248
+ const setIdFromSource = sourceId.replace('custom-cells-', '');
249
+ removeSetLayers(map, setIdFromSource);
250
+ }
251
+ };
252
+ });
253
+
254
+ // React to store changes
255
+ $effect(() => {
256
+ // Read version to trigger on changes
257
+ const _version = setsStore.version;
258
+ const _sets = setsStore.sets;
259
+
260
+ updateLayers();
261
+ });
262
+ </script>
@@ -0,0 +1,10 @@
1
+ import type { CustomCellSetsStore } from '../stores/custom-cell-sets.svelte';
2
+ interface Props {
3
+ /** The custom cell sets store */
4
+ setsStore: CustomCellSetsStore;
5
+ /** Optional: specific set ID to render (if not provided, renders all) */
6
+ setId?: string;
7
+ }
8
+ declare const CustomCellsLayer: import("svelte").Component<Props, {}, "">;
9
+ type CustomCellsLayer = ReturnType<typeof CustomCellsLayer>;
10
+ export default CustomCellsLayer;
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Custom Cells Layers - Barrel Export
3
+ */
4
+ export { default as CustomCellsLayer } from './CustomCellsLayer.svelte';
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Custom Cells Layers - Barrel Export
3
+ */
4
+ export { default as CustomCellsLayer } from './CustomCellsLayer.svelte';
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Custom Cells - CSV Parser
3
+ *
4
+ * Parses CSV files for custom cell sets.
5
+ * Required column: txId
6
+ * Optional columns: customGroup, sizeFactor, + any extras for tooltips
7
+ */
8
+ import type { CustomCellImportResult } from '../types';
9
+ import type { Cell } from '../../../../../shared/demo';
10
+ /**
11
+ * Parse a CSV string into custom cells
12
+ * @param csvContent Raw CSV content
13
+ * @param cellLookup Map of txId -> Cell for resolving cell data
14
+ * @returns Import result with cells, unmatched IDs, groups, and extra columns
15
+ */
16
+ export declare function parseCustomCellsCsv(csvContent: string, cellLookup: Map<string, Cell>): CustomCellImportResult;
17
+ /**
18
+ * Build a cell lookup map from an array of cells
19
+ */
20
+ export declare function buildCellLookup(cells: Cell[]): Map<string, Cell>;
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Custom Cells - CSV Parser
3
+ *
4
+ * Parses CSV files for custom cell sets.
5
+ * Required column: txId
6
+ * Optional columns: customGroup, sizeFactor, + any extras for tooltips
7
+ */
8
+ /**
9
+ * Known/reserved column names
10
+ */
11
+ const RESERVED_COLUMNS = ['txId', 'txid', 'customGroup', 'customgroup', 'sizeFactor', 'sizefactor'];
12
+ /**
13
+ * Normalize column name for matching
14
+ */
15
+ function normalizeColumnName(name) {
16
+ return name.trim().toLowerCase();
17
+ }
18
+ /**
19
+ * Parse a CSV string into custom cells
20
+ * @param csvContent Raw CSV content
21
+ * @param cellLookup Map of txId -> Cell for resolving cell data
22
+ * @returns Import result with cells, unmatched IDs, groups, and extra columns
23
+ */
24
+ export function parseCustomCellsCsv(csvContent, cellLookup) {
25
+ const lines = csvContent.trim().split('\n');
26
+ if (lines.length < 2) {
27
+ return {
28
+ cells: [],
29
+ unmatchedTxIds: [],
30
+ groups: [],
31
+ extraColumns: [],
32
+ totalRows: 0
33
+ };
34
+ }
35
+ // Parse header
36
+ const headerLine = lines[0];
37
+ const headers = parseCSVLine(headerLine);
38
+ const normalizedHeaders = headers.map(normalizeColumnName);
39
+ // Find required txId column
40
+ const txIdIndex = normalizedHeaders.findIndex(h => h === 'txid');
41
+ if (txIdIndex === -1) {
42
+ throw new Error('CSV must contain a "txId" column');
43
+ }
44
+ // Find optional columns
45
+ const groupIndex = normalizedHeaders.findIndex(h => h === 'customgroup');
46
+ const sizeFactorIndex = normalizedHeaders.findIndex(h => h === 'sizefactor');
47
+ // Find extra columns (not reserved)
48
+ const extraColumns = [];
49
+ const extraIndices = [];
50
+ headers.forEach((header, idx) => {
51
+ const normalized = normalizeColumnName(header);
52
+ if (!RESERVED_COLUMNS.includes(normalized)) {
53
+ extraColumns.push(header.trim());
54
+ extraIndices.push(idx);
55
+ }
56
+ });
57
+ // Parse data rows
58
+ const cells = [];
59
+ const unmatchedTxIds = [];
60
+ const groupsSet = new Set();
61
+ for (let i = 1; i < lines.length; i++) {
62
+ const line = lines[i].trim();
63
+ if (!line)
64
+ continue;
65
+ const values = parseCSVLine(line);
66
+ const txId = values[txIdIndex]?.trim();
67
+ if (!txId)
68
+ continue;
69
+ // Get custom group (default to 'default')
70
+ const customGroup = groupIndex !== -1
71
+ ? (values[groupIndex]?.trim() || 'default')
72
+ : 'default';
73
+ groupsSet.add(customGroup);
74
+ // Get size factor (default to 1)
75
+ let sizeFactor = 1;
76
+ if (sizeFactorIndex !== -1) {
77
+ const parsed = parseFloat(values[sizeFactorIndex]);
78
+ if (!isNaN(parsed) && parsed > 0) {
79
+ sizeFactor = parsed;
80
+ }
81
+ }
82
+ // Collect extra fields
83
+ const extraFields = {};
84
+ extraIndices.forEach((idx, i) => {
85
+ const value = values[idx]?.trim() || '';
86
+ // Try to parse as number
87
+ const numValue = parseFloat(value);
88
+ extraFields[extraColumns[i]] = isNaN(numValue) ? value : numValue;
89
+ });
90
+ // Resolve cell from lookup
91
+ const resolvedCell = cellLookup.get(txId);
92
+ if (resolvedCell) {
93
+ cells.push({
94
+ txId,
95
+ customGroup,
96
+ sizeFactor,
97
+ extraFields,
98
+ resolvedCell
99
+ });
100
+ }
101
+ else {
102
+ unmatchedTxIds.push(txId);
103
+ }
104
+ }
105
+ return {
106
+ cells,
107
+ unmatchedTxIds,
108
+ groups: Array.from(groupsSet).sort(),
109
+ extraColumns,
110
+ totalRows: lines.length - 1
111
+ };
112
+ }
113
+ /**
114
+ * Parse a single CSV line, handling quoted fields
115
+ */
116
+ function parseCSVLine(line) {
117
+ const result = [];
118
+ let current = '';
119
+ let inQuotes = false;
120
+ for (let i = 0; i < line.length; i++) {
121
+ const char = line[i];
122
+ const nextChar = line[i + 1];
123
+ if (inQuotes) {
124
+ if (char === '"' && nextChar === '"') {
125
+ // Escaped quote
126
+ current += '"';
127
+ i++;
128
+ }
129
+ else if (char === '"') {
130
+ // End of quoted field
131
+ inQuotes = false;
132
+ }
133
+ else {
134
+ current += char;
135
+ }
136
+ }
137
+ else {
138
+ if (char === '"') {
139
+ inQuotes = true;
140
+ }
141
+ else if (char === ',') {
142
+ result.push(current);
143
+ current = '';
144
+ }
145
+ else {
146
+ current += char;
147
+ }
148
+ }
149
+ }
150
+ result.push(current);
151
+ return result;
152
+ }
153
+ /**
154
+ * Build a cell lookup map from an array of cells
155
+ */
156
+ export function buildCellLookup(cells) {
157
+ const lookup = new Map();
158
+ for (const cell of cells) {
159
+ if (cell.txId) {
160
+ lookup.set(cell.txId, cell);
161
+ }
162
+ }
163
+ return lookup;
164
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Custom Cells Logic - Barrel Export
3
+ */
4
+ export { parseCustomCellsCsv, buildCellLookup } from './csv-parser';
5
+ export { buildCustomCellTree, getGroupCounts } from './tree-adapter';
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Custom Cells Logic - Barrel Export
3
+ */
4
+ export { parseCustomCellsCsv, buildCellLookup } from './csv-parser';
5
+ export { buildCustomCellTree, getGroupCounts } from './tree-adapter';
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Custom Cells - Tree Adapter
3
+ *
4
+ * Builds tree structure for TreeView component, grouped by customGroup
5
+ */
6
+ import type { TreeNode } from '../../../../../core/TreeView/tree.model';
7
+ import type { CustomCellSet } from '../types';
8
+ /**
9
+ * Metadata for tree nodes
10
+ */
11
+ export interface CustomCellTreeMetadata {
12
+ color: string;
13
+ count: number;
14
+ groupId: string;
15
+ setId: string;
16
+ }
17
+ /**
18
+ * Build tree nodes for a custom cell set, grouped by customGroup
19
+ */
20
+ export declare function buildCustomCellTree(set: CustomCellSet): TreeNode<CustomCellTreeMetadata>[];
21
+ /**
22
+ * Get cell counts per group
23
+ */
24
+ export declare function getGroupCounts(set: CustomCellSet): Map<string, number>;
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Custom Cells - Tree Adapter
3
+ *
4
+ * Builds tree structure for TreeView component, grouped by customGroup
5
+ */
6
+ /**
7
+ * Build tree nodes for a custom cell set, grouped by customGroup
8
+ */
9
+ export function buildCustomCellTree(set) {
10
+ // Group cells by customGroup
11
+ const groupMap = new Map();
12
+ for (const cell of set.cells) {
13
+ if (!cell.resolvedCell)
14
+ continue; // Skip unresolved cells
15
+ if (!groupMap.has(cell.customGroup)) {
16
+ groupMap.set(cell.customGroup, []);
17
+ }
18
+ groupMap.get(cell.customGroup).push(cell);
19
+ }
20
+ // Build leaf nodes for each group
21
+ const groupNodes = [];
22
+ for (const [group, cells] of groupMap) {
23
+ const color = set.groupColors[group] || set.defaultColor;
24
+ const isVisible = set.visibleGroups.has(group);
25
+ groupNodes.push({
26
+ id: `${set.id}__${group}`,
27
+ label: `${group} (${cells.length})`,
28
+ metadata: {
29
+ color,
30
+ count: cells.length,
31
+ groupId: group,
32
+ setId: set.id
33
+ },
34
+ defaultChecked: isVisible
35
+ });
36
+ }
37
+ // Sort groups alphabetically
38
+ groupNodes.sort((a, b) => a.label.localeCompare(b.label));
39
+ // Wrap in root node
40
+ const rootNode = {
41
+ id: `root-${set.id}`,
42
+ label: `${set.name} (${set.cells.length})`,
43
+ children: groupNodes,
44
+ defaultExpanded: true,
45
+ defaultChecked: set.visible,
46
+ metadata: {
47
+ color: set.defaultColor,
48
+ count: set.cells.length,
49
+ groupId: '__root__',
50
+ setId: set.id
51
+ }
52
+ };
53
+ return [rootNode];
54
+ }
55
+ /**
56
+ * Get cell counts per group
57
+ */
58
+ export function getGroupCounts(set) {
59
+ const counts = new Map();
60
+ for (const cell of set.cells) {
61
+ if (!cell.resolvedCell)
62
+ continue;
63
+ const current = counts.get(cell.customGroup) || 0;
64
+ counts.set(cell.customGroup, current + 1);
65
+ }
66
+ return counts;
67
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Custom Cell Sets Store
3
+ *
4
+ * Manages multiple custom cell sets, each loaded from a CSV file.
5
+ * Resolves cell data from the parent CellDataStore.
6
+ */
7
+ import type { CellDataStore } from '../../stores/cell.data.svelte';
8
+ import type { CustomCellSet, CustomCellImportResult } from '../types';
9
+ /**
10
+ * Store for managing custom cell sets
11
+ */
12
+ export declare class CustomCellSetsStore {
13
+ /** All custom cell sets */
14
+ sets: CustomCellSet[];
15
+ /** Version counter for reactivity */
16
+ version: number;
17
+ /** Reference to parent cell data store */
18
+ private cellDataStore;
19
+ /** Storage key for persistence */
20
+ private storageKey;
21
+ constructor(cellDataStore: CellDataStore, namespace?: string);
22
+ /**
23
+ * Import a CSV file and create a new custom cell set
24
+ */
25
+ importFromCsv(csvContent: string, fileName: string): CustomCellImportResult;
26
+ /**
27
+ * Create a new set from import result
28
+ */
29
+ createSet(name: string, importResult: CustomCellImportResult): CustomCellSet;
30
+ /**
31
+ * Remove a set by ID
32
+ */
33
+ removeSet(setId: string): boolean;
34
+ /**
35
+ * Get a set by ID
36
+ */
37
+ getSet(setId: string): CustomCellSet | undefined;
38
+ /**
39
+ * Toggle set visibility
40
+ */
41
+ toggleSetVisibility(setId: string): void;
42
+ /**
43
+ * Toggle group visibility within a set
44
+ */
45
+ toggleGroupVisibility(setId: string, group: string): void;
46
+ /**
47
+ * Set group color
48
+ */
49
+ setGroupColor(setId: string, group: string, color: string): void;
50
+ /**
51
+ * Update set display settings
52
+ */
53
+ updateSetSettings(setId: string, settings: Partial<Pick<CustomCellSet, 'baseSize' | 'opacity' | 'defaultColor'>>): void;
54
+ /**
55
+ * Rename a set
56
+ */
57
+ renameSet(setId: string, newName: string): void;
58
+ /**
59
+ * Get visible cells for a set (filtered by visible groups)
60
+ */
61
+ getVisibleCells(setId: string): import("..").CustomCell[];
62
+ /**
63
+ * Re-resolve cells after main cell data changes
64
+ */
65
+ refreshResolutions(): void;
66
+ /**
67
+ * Load from localStorage
68
+ */
69
+ private load;
70
+ /**
71
+ * Save to localStorage
72
+ */
73
+ private save;
74
+ }
75
+ /**
76
+ * Factory function to create a custom cell sets store
77
+ */
78
+ export declare function createCustomCellSetsStore(cellDataStore: CellDataStore, namespace?: string): CustomCellSetsStore;