@smartnet360/svelte-components 0.0.125 → 0.0.127
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/core/Auth/auth.svelte.js +47 -2
- package/dist/map-v3/demo/DemoMap.svelte +36 -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/features/sites/custom/components/CustomSiteFilterControl.svelte +203 -0
- package/dist/map-v3/features/sites/custom/components/CustomSiteFilterControl.svelte.d.ts +15 -0
- package/dist/map-v3/features/sites/custom/components/CustomSiteSetManager.svelte +261 -0
- package/dist/map-v3/features/sites/custom/components/CustomSiteSetManager.svelte.d.ts +10 -0
- package/dist/map-v3/features/sites/custom/index.d.ts +13 -0
- package/dist/map-v3/features/sites/custom/index.js +16 -0
- package/dist/map-v3/features/sites/custom/layers/CustomSitesLayer.svelte +201 -0
- package/dist/map-v3/features/sites/custom/layers/CustomSitesLayer.svelte.d.ts +8 -0
- package/dist/map-v3/features/sites/custom/logic/csv-parser.d.ts +12 -0
- package/dist/map-v3/features/sites/custom/logic/csv-parser.js +182 -0
- package/dist/map-v3/features/sites/custom/logic/tree-adapter.d.ts +16 -0
- package/dist/map-v3/features/sites/custom/logic/tree-adapter.js +59 -0
- package/dist/map-v3/features/sites/custom/stores/custom-site-sets.svelte.d.ts +78 -0
- package/dist/map-v3/features/sites/custom/stores/custom-site-sets.svelte.js +248 -0
- package/dist/map-v3/features/sites/custom/types.d.ts +74 -0
- package/dist/map-v3/features/sites/custom/types.js +8 -0
- package/dist/map-v3/index.d.ts +2 -0
- package/dist/map-v3/index.js +4 -0
- package/dist/map-v3/shared/controls/MapControl.svelte +27 -3
- package/package.json +1 -1
|
@@ -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;
|
|
@@ -0,0 +1,242 @@
|
|
|
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 { browser } from '$app/environment';
|
|
8
|
+
import { CUSTOM_CELL_PALETTE } from '../types';
|
|
9
|
+
import { parseCustomCellsCsv, buildCellLookup } from '../logic/csv-parser';
|
|
10
|
+
/**
|
|
11
|
+
* Generate a unique ID for a new set
|
|
12
|
+
*/
|
|
13
|
+
function generateSetId() {
|
|
14
|
+
return `set-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Store for managing custom cell sets
|
|
18
|
+
*/
|
|
19
|
+
export class CustomCellSetsStore {
|
|
20
|
+
/** All custom cell sets */
|
|
21
|
+
sets = $state([]);
|
|
22
|
+
/** Version counter for reactivity */
|
|
23
|
+
version = $state(0);
|
|
24
|
+
/** Reference to parent cell data store */
|
|
25
|
+
cellDataStore;
|
|
26
|
+
/** Storage key for persistence */
|
|
27
|
+
storageKey;
|
|
28
|
+
constructor(cellDataStore, namespace = 'default') {
|
|
29
|
+
this.cellDataStore = cellDataStore;
|
|
30
|
+
this.storageKey = `${namespace}:custom-cell-sets`;
|
|
31
|
+
if (browser) {
|
|
32
|
+
this.load();
|
|
33
|
+
// Auto-save on changes
|
|
34
|
+
$effect(() => {
|
|
35
|
+
// Read version to trigger on changes
|
|
36
|
+
const _v = this.version;
|
|
37
|
+
this.save();
|
|
38
|
+
});
|
|
39
|
+
// Re-resolve cells when main cell data changes
|
|
40
|
+
$effect(() => {
|
|
41
|
+
const cellCount = this.cellDataStore.rawCells.length;
|
|
42
|
+
if (cellCount > 0 && this.sets.length > 0) {
|
|
43
|
+
// Check if any cells need resolution
|
|
44
|
+
const needsResolution = this.sets.some(set => set.cells.some(c => !c.resolvedCell));
|
|
45
|
+
if (needsResolution) {
|
|
46
|
+
console.log('[CustomCellSetsStore] Re-resolving cells after main data loaded');
|
|
47
|
+
this.refreshResolutions();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Import a CSV file and create a new custom cell set
|
|
55
|
+
*/
|
|
56
|
+
importFromCsv(csvContent, fileName) {
|
|
57
|
+
// Build lookup from all cells (unfiltered)
|
|
58
|
+
const cellLookup = buildCellLookup(this.cellDataStore.rawCells);
|
|
59
|
+
// Parse CSV
|
|
60
|
+
const result = parseCustomCellsCsv(csvContent, cellLookup);
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Create a new set from import result
|
|
65
|
+
*/
|
|
66
|
+
createSet(name, importResult) {
|
|
67
|
+
const id = generateSetId();
|
|
68
|
+
// Assign default colors to groups
|
|
69
|
+
const groupColors = {};
|
|
70
|
+
importResult.groups.forEach((group, idx) => {
|
|
71
|
+
groupColors[group] = CUSTOM_CELL_PALETTE[idx % CUSTOM_CELL_PALETTE.length];
|
|
72
|
+
});
|
|
73
|
+
const newSet = {
|
|
74
|
+
id,
|
|
75
|
+
name,
|
|
76
|
+
cells: importResult.cells,
|
|
77
|
+
unmatchedTxIds: importResult.unmatchedTxIds,
|
|
78
|
+
groups: importResult.groups,
|
|
79
|
+
extraColumns: importResult.extraColumns,
|
|
80
|
+
baseSize: 50,
|
|
81
|
+
opacity: 0.7,
|
|
82
|
+
defaultColor: CUSTOM_CELL_PALETTE[0],
|
|
83
|
+
groupColors,
|
|
84
|
+
visible: true,
|
|
85
|
+
visibleGroups: new Set(importResult.groups)
|
|
86
|
+
};
|
|
87
|
+
this.sets.push(newSet);
|
|
88
|
+
this.version++;
|
|
89
|
+
return newSet;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Remove a set by ID
|
|
93
|
+
*/
|
|
94
|
+
removeSet(setId) {
|
|
95
|
+
const index = this.sets.findIndex(s => s.id === setId);
|
|
96
|
+
if (index !== -1) {
|
|
97
|
+
// Use filter to create a new array for proper reactivity
|
|
98
|
+
this.sets = this.sets.filter(s => s.id !== setId);
|
|
99
|
+
this.version++;
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Get a set by ID
|
|
106
|
+
*/
|
|
107
|
+
getSet(setId) {
|
|
108
|
+
return this.sets.find(s => s.id === setId);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Toggle set visibility
|
|
112
|
+
*/
|
|
113
|
+
toggleSetVisibility(setId) {
|
|
114
|
+
const set = this.getSet(setId);
|
|
115
|
+
if (set) {
|
|
116
|
+
set.visible = !set.visible;
|
|
117
|
+
this.version++;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Toggle group visibility within a set
|
|
122
|
+
*/
|
|
123
|
+
toggleGroupVisibility(setId, group) {
|
|
124
|
+
const set = this.getSet(setId);
|
|
125
|
+
if (set) {
|
|
126
|
+
if (set.visibleGroups.has(group)) {
|
|
127
|
+
set.visibleGroups.delete(group);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
set.visibleGroups.add(group);
|
|
131
|
+
}
|
|
132
|
+
this.version++;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Set group color
|
|
137
|
+
*/
|
|
138
|
+
setGroupColor(setId, group, color) {
|
|
139
|
+
const set = this.getSet(setId);
|
|
140
|
+
if (set) {
|
|
141
|
+
set.groupColors[group] = color;
|
|
142
|
+
this.version++;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Update set display settings
|
|
147
|
+
*/
|
|
148
|
+
updateSetSettings(setId, settings) {
|
|
149
|
+
const set = this.getSet(setId);
|
|
150
|
+
if (set) {
|
|
151
|
+
if (settings.baseSize !== undefined)
|
|
152
|
+
set.baseSize = settings.baseSize;
|
|
153
|
+
if (settings.opacity !== undefined)
|
|
154
|
+
set.opacity = settings.opacity;
|
|
155
|
+
if (settings.defaultColor !== undefined)
|
|
156
|
+
set.defaultColor = settings.defaultColor;
|
|
157
|
+
this.version++;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Rename a set
|
|
162
|
+
*/
|
|
163
|
+
renameSet(setId, newName) {
|
|
164
|
+
const set = this.getSet(setId);
|
|
165
|
+
if (set) {
|
|
166
|
+
set.name = newName;
|
|
167
|
+
this.version++;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Get visible cells for a set (filtered by visible groups)
|
|
172
|
+
*/
|
|
173
|
+
getVisibleCells(setId) {
|
|
174
|
+
const set = this.getSet(setId);
|
|
175
|
+
if (!set || !set.visible)
|
|
176
|
+
return [];
|
|
177
|
+
return set.cells.filter(c => c.resolvedCell && set.visibleGroups.has(c.customGroup));
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Re-resolve cells after main cell data changes
|
|
181
|
+
*/
|
|
182
|
+
refreshResolutions() {
|
|
183
|
+
const cellLookup = buildCellLookup(this.cellDataStore.rawCells);
|
|
184
|
+
for (const set of this.sets) {
|
|
185
|
+
for (const cell of set.cells) {
|
|
186
|
+
cell.resolvedCell = cellLookup.get(cell.txId);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
this.version++;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Load from localStorage
|
|
193
|
+
*/
|
|
194
|
+
load() {
|
|
195
|
+
try {
|
|
196
|
+
const stored = localStorage.getItem(this.storageKey);
|
|
197
|
+
if (stored) {
|
|
198
|
+
const data = JSON.parse(stored);
|
|
199
|
+
// Reconstruct sets with proper Set objects
|
|
200
|
+
this.sets = data.map((setData) => ({
|
|
201
|
+
...setData,
|
|
202
|
+
visibleGroups: new Set(setData.visibleGroups || []),
|
|
203
|
+
cells: setData.cells || []
|
|
204
|
+
}));
|
|
205
|
+
// Re-resolve cells
|
|
206
|
+
this.refreshResolutions();
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
catch (e) {
|
|
210
|
+
console.warn('[CustomCellSetsStore] Failed to load from storage', e);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Save to localStorage
|
|
215
|
+
*/
|
|
216
|
+
save() {
|
|
217
|
+
try {
|
|
218
|
+
// Convert Sets to arrays for JSON serialization
|
|
219
|
+
const data = this.sets.map(set => ({
|
|
220
|
+
...set,
|
|
221
|
+
visibleGroups: Array.from(set.visibleGroups),
|
|
222
|
+
// Don't persist resolved cells - will re-resolve on load
|
|
223
|
+
cells: set.cells.map(c => ({
|
|
224
|
+
txId: c.txId,
|
|
225
|
+
customGroup: c.customGroup,
|
|
226
|
+
sizeFactor: c.sizeFactor,
|
|
227
|
+
extraFields: c.extraFields
|
|
228
|
+
}))
|
|
229
|
+
}));
|
|
230
|
+
localStorage.setItem(this.storageKey, JSON.stringify(data));
|
|
231
|
+
}
|
|
232
|
+
catch (e) {
|
|
233
|
+
console.warn('[CustomCellSetsStore] Failed to save to storage', e);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Factory function to create a custom cell sets store
|
|
239
|
+
*/
|
|
240
|
+
export function createCustomCellSetsStore(cellDataStore, namespace = 'default') {
|
|
241
|
+
return new CustomCellSetsStore(cellDataStore, namespace);
|
|
242
|
+
}
|