@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.
- package/dist/apps/antenna-tools/components/AntennaSettingsModal.svelte +4 -174
- package/dist/apps/antenna-tools/components/DatabaseViewer.svelte +2 -2
- package/dist/apps/antenna-tools/components/MSIConverter.svelte +302 -43
- package/dist/apps/antenna-tools/db.js +4 -0
- package/dist/apps/antenna-tools/utils/db-utils.d.ts +19 -0
- package/dist/apps/antenna-tools/utils/db-utils.js +108 -0
- package/dist/core/Auth/LoginForm.svelte +397 -0
- package/dist/core/Auth/LoginForm.svelte.d.ts +16 -0
- package/dist/core/Auth/auth.svelte.d.ts +22 -0
- package/dist/core/Auth/auth.svelte.js +229 -0
- package/dist/core/Auth/config.d.ts +25 -0
- package/dist/core/Auth/config.js +256 -0
- package/dist/core/Auth/index.d.ts +4 -0
- package/dist/core/Auth/index.js +5 -0
- package/dist/core/Auth/types.d.ts +140 -0
- package/dist/core/Auth/types.js +2 -0
- package/dist/core/LandingPage/App.svelte +102 -0
- package/dist/core/LandingPage/App.svelte.d.ts +20 -0
- package/dist/core/LandingPage/LandingPage.svelte +480 -0
- package/dist/core/LandingPage/LandingPage.svelte.d.ts +21 -0
- package/dist/core/LandingPage/index.d.ts +2 -0
- package/dist/core/LandingPage/index.js +3 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +4 -0
- package/dist/map-v3/demo/DemoMap.svelte +18 -0
- package/dist/map-v3/demo/demo-custom-cells.d.ts +21 -0
- package/dist/map-v3/demo/demo-custom-cells.js +48 -0
- package/dist/map-v3/features/cells/custom/components/CustomCellFilterControl.svelte +220 -0
- package/dist/map-v3/features/cells/custom/components/CustomCellFilterControl.svelte.d.ts +15 -0
- package/dist/map-v3/features/cells/custom/components/CustomCellSetManager.svelte +306 -0
- package/dist/map-v3/features/cells/custom/components/CustomCellSetManager.svelte.d.ts +10 -0
- package/dist/map-v3/features/cells/custom/components/index.d.ts +5 -0
- package/dist/map-v3/features/cells/custom/components/index.js +5 -0
- package/dist/map-v3/features/cells/custom/index.d.ts +32 -0
- package/dist/map-v3/features/cells/custom/index.js +35 -0
- package/dist/map-v3/features/cells/custom/layers/CustomCellsLayer.svelte +262 -0
- package/dist/map-v3/features/cells/custom/layers/CustomCellsLayer.svelte.d.ts +10 -0
- package/dist/map-v3/features/cells/custom/layers/index.d.ts +4 -0
- package/dist/map-v3/features/cells/custom/layers/index.js +4 -0
- package/dist/map-v3/features/cells/custom/logic/csv-parser.d.ts +20 -0
- package/dist/map-v3/features/cells/custom/logic/csv-parser.js +164 -0
- package/dist/map-v3/features/cells/custom/logic/index.d.ts +5 -0
- package/dist/map-v3/features/cells/custom/logic/index.js +5 -0
- package/dist/map-v3/features/cells/custom/logic/tree-adapter.d.ts +24 -0
- package/dist/map-v3/features/cells/custom/logic/tree-adapter.js +67 -0
- package/dist/map-v3/features/cells/custom/stores/custom-cell-sets.svelte.d.ts +78 -0
- package/dist/map-v3/features/cells/custom/stores/custom-cell-sets.svelte.js +242 -0
- package/dist/map-v3/features/cells/custom/stores/index.d.ts +4 -0
- package/dist/map-v3/features/cells/custom/stores/index.js +4 -0
- package/dist/map-v3/features/cells/custom/types.d.ts +83 -0
- package/dist/map-v3/features/cells/custom/types.js +23 -0
- package/dist/map-v3/shared/controls/MapControl.svelte +27 -3
- 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,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,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;
|