@smartnet360/svelte-components 0.0.85 → 0.0.87
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-pattern/components/AntennaControls.svelte +1 -106
- package/dist/apps/antenna-pattern/components/AntennaDiagrams.svelte +0 -36
- package/dist/apps/antenna-pattern/components/AntennaSettingsModal.svelte +0 -2
- package/dist/apps/antenna-pattern/components/PlotlyRadarChart.svelte +0 -22
- package/dist/apps/antenna-pattern/components/chart-engines/PolarAreaChart.svelte +0 -2
- package/dist/apps/antenna-pattern/components/chart-engines/PolarBarChart.svelte +0 -2
- package/dist/apps/antenna-pattern/components/chart-engines/PolarLineChart.svelte +0 -2
- package/dist/apps/site-check/data-loader.js +0 -8
- package/dist/core/Charts/GlobalControls.svelte +0 -4
- package/dist/core/Desktop/Grid/ResizeHandle.svelte +0 -7
- package/dist/core/Desktop/Grid/resizeStore.js +0 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/map-v2/demo/DemoMap.svelte +0 -2
- package/dist/map-v2/demo/demo-cells.js +0 -1
- package/dist/map-v2/features/cells/layers/CellsLayer.svelte +7 -26
- package/dist/map-v2/features/cells/utils/cellTree.js +0 -29
- package/dist/map-v2/features/repeaters/layers/RepeaterLabelsLayer.svelte +3 -27
- package/dist/map-v2/features/repeaters/layers/RepeatersLayer.svelte +8 -25
- package/dist/map-v2/features/repeaters/utils/repeaterTree.js +0 -6
- package/dist/map-v2/features/sites/controls/SiteFilterControl.svelte +0 -8
- package/dist/map-v2/features/sites/utils/siteTreeUtils.js +0 -6
- package/dist/map-v3/core/components/Map.svelte +89 -0
- package/dist/map-v3/core/components/Map.svelte.d.ts +13 -0
- package/dist/map-v3/core/controls/FeatureSettingsControl.svelte +103 -0
- package/dist/map-v3/core/controls/FeatureSettingsControl.svelte.d.ts +15 -0
- package/dist/map-v3/core/controls/MapStyleControl.svelte +271 -0
- package/dist/map-v3/core/controls/MapStyleControl.svelte.d.ts +28 -0
- package/dist/map-v3/core/index.d.ts +6 -0
- package/dist/map-v3/core/index.js +5 -0
- package/dist/map-v3/core/stores/map.store.svelte.d.ts +8 -0
- package/dist/map-v3/core/stores/map.store.svelte.js +29 -0
- package/dist/map-v3/core/stores/viewport.store.svelte.d.ts +38 -0
- package/dist/map-v3/core/stores/viewport.store.svelte.js +107 -0
- package/dist/map-v3/demo/DemoMap.svelte +104 -0
- package/dist/map-v3/demo/DemoMap.svelte.d.ts +6 -0
- package/dist/map-v3/demo/demo-cells.d.ts +13 -0
- package/dist/map-v3/demo/demo-cells.js +130 -0
- package/dist/map-v3/demo/demo-data.d.ts +8 -0
- package/dist/map-v3/demo/demo-data.js +104 -0
- package/dist/map-v3/demo/demo-repeaters.d.ts +13 -0
- package/dist/map-v3/demo/demo-repeaters.js +73 -0
- package/dist/map-v3/features/cells/components/CellFilterControl.svelte +208 -0
- package/dist/map-v3/features/cells/components/CellFilterControl.svelte.d.ts +12 -0
- package/dist/map-v3/features/cells/components/CellSettingsPanel.svelte +229 -0
- package/dist/map-v3/features/cells/components/CellSettingsPanel.svelte.d.ts +7 -0
- package/dist/map-v3/features/cells/constants.d.ts +18 -0
- package/dist/map-v3/features/cells/constants.js +37 -0
- package/dist/map-v3/features/cells/layers/CellLabelsLayer.svelte +230 -0
- package/dist/map-v3/features/cells/layers/CellLabelsLayer.svelte.d.ts +11 -0
- package/dist/map-v3/features/cells/layers/CellsLayer.svelte +194 -0
- package/dist/map-v3/features/cells/layers/CellsLayer.svelte.d.ts +11 -0
- package/dist/map-v3/features/cells/layers/index.d.ts +2 -0
- package/dist/map-v3/features/cells/layers/index.js +2 -0
- package/dist/map-v3/features/cells/logic/geometry.d.ts +12 -0
- package/dist/map-v3/features/cells/logic/geometry.js +35 -0
- package/dist/map-v3/features/cells/logic/grouping.d.ts +18 -0
- package/dist/map-v3/features/cells/logic/grouping.js +30 -0
- package/dist/map-v3/features/cells/logic/tree-adapter.d.ts +11 -0
- package/dist/map-v3/features/cells/logic/tree-adapter.js +53 -0
- package/dist/map-v3/features/cells/stores/cell.data.svelte.d.ts +9 -0
- package/dist/map-v3/features/cells/stores/cell.data.svelte.js +16 -0
- package/dist/map-v3/features/cells/stores/cell.display.svelte.d.ts +25 -0
- package/dist/map-v3/features/cells/stores/cell.display.svelte.js +67 -0
- package/dist/map-v3/features/cells/stores/cell.registry.svelte.d.ts +23 -0
- package/dist/map-v3/features/cells/stores/cell.registry.svelte.js +68 -0
- package/dist/map-v3/features/cells/types.d.ts +62 -0
- package/dist/map-v3/features/cells/types.js +6 -0
- package/dist/map-v3/features/repeaters/components/RepeaterFilterControl.svelte +148 -0
- package/dist/map-v3/features/repeaters/components/RepeaterFilterControl.svelte.d.ts +12 -0
- package/dist/map-v3/features/repeaters/components/RepeaterSettingsPanel.svelte +209 -0
- package/dist/map-v3/features/repeaters/components/RepeaterSettingsPanel.svelte.d.ts +7 -0
- package/dist/map-v3/features/repeaters/layers/RepeaterLabelsLayer.svelte +177 -0
- package/dist/map-v3/features/repeaters/layers/RepeaterLabelsLayer.svelte.d.ts +11 -0
- package/dist/map-v3/features/repeaters/layers/RepeatersLayer.svelte +163 -0
- package/dist/map-v3/features/repeaters/layers/RepeatersLayer.svelte.d.ts +11 -0
- package/dist/map-v3/features/repeaters/logic/geometry.d.ts +3 -0
- package/dist/map-v3/features/repeaters/logic/geometry.js +23 -0
- package/dist/map-v3/features/repeaters/logic/grouping.d.ts +8 -0
- package/dist/map-v3/features/repeaters/logic/grouping.js +20 -0
- package/dist/map-v3/features/repeaters/logic/tree-adapter.d.ts +8 -0
- package/dist/map-v3/features/repeaters/logic/tree-adapter.js +43 -0
- package/dist/map-v3/features/repeaters/stores/repeater.data.svelte.d.ts +8 -0
- package/dist/map-v3/features/repeaters/stores/repeater.data.svelte.js +13 -0
- package/dist/map-v3/features/repeaters/stores/repeater.display.svelte.d.ts +21 -0
- package/dist/map-v3/features/repeaters/stores/repeater.display.svelte.js +64 -0
- package/dist/map-v3/features/repeaters/stores/repeater.registry.svelte.d.ts +23 -0
- package/dist/map-v3/features/repeaters/stores/repeater.registry.svelte.js +68 -0
- package/dist/map-v3/features/repeaters/types.d.ts +18 -0
- package/dist/map-v3/features/repeaters/types.js +1 -0
- package/dist/map-v3/features/sites/components/SiteFilterControl.svelte +119 -0
- package/dist/map-v3/features/sites/components/SiteFilterControl.svelte.d.ts +12 -0
- package/dist/map-v3/features/sites/components/SiteSettingsPanel.svelte +241 -0
- package/dist/map-v3/features/sites/components/SiteSettingsPanel.svelte.d.ts +7 -0
- package/dist/map-v3/features/sites/layers/SiteLabelsLayer.svelte +152 -0
- package/dist/map-v3/features/sites/layers/SiteLabelsLayer.svelte.d.ts +11 -0
- package/dist/map-v3/features/sites/layers/SitesLayer.svelte +132 -0
- package/dist/map-v3/features/sites/layers/SitesLayer.svelte.d.ts +11 -0
- package/dist/map-v3/features/sites/logic/tree-adapter.d.ts +9 -0
- package/dist/map-v3/features/sites/logic/tree-adapter.js +75 -0
- package/dist/map-v3/features/sites/stores/site.data.svelte.d.ts +8 -0
- package/dist/map-v3/features/sites/stores/site.data.svelte.js +40 -0
- package/dist/map-v3/features/sites/stores/site.display.svelte.d.ts +20 -0
- package/dist/map-v3/features/sites/stores/site.display.svelte.js +63 -0
- package/dist/map-v3/features/sites/stores/site.registry.svelte.d.ts +13 -0
- package/dist/map-v3/features/sites/stores/site.registry.svelte.js +83 -0
- package/dist/map-v3/features/sites/types.d.ts +12 -0
- package/dist/map-v3/features/sites/types.js +1 -0
- package/dist/map-v3/index.d.ts +30 -0
- package/dist/map-v3/index.js +36 -0
- package/dist/map-v3/shared/controls/MapControl.svelte +242 -0
- package/dist/map-v3/shared/controls/MapControl.svelte.d.ts +27 -0
- package/dist/map-v3/shared/index.d.ts +1 -0
- package/dist/map-v3/shared/index.js +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onDestroy, untrack } from 'svelte';
|
|
3
|
+
import { MapControl } from '../../../shared';
|
|
4
|
+
import { createTreeStore } from '../../../../core/TreeView/tree.store';
|
|
5
|
+
import TreeView from '../../../../core/TreeView/TreeView.svelte';
|
|
6
|
+
import type { RepeaterDataStore } from '../stores/repeater.data.svelte';
|
|
7
|
+
import type { RepeaterRegistry } from '../stores/repeater.registry.svelte';
|
|
8
|
+
import type { RepeaterDisplayStore } from '../stores/repeater.display.svelte';
|
|
9
|
+
import type { RepeaterGroupingField } from '../types';
|
|
10
|
+
import { buildRepeaterTree } from '../logic/tree-adapter';
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
dataStore: RepeaterDataStore;
|
|
14
|
+
registry: RepeaterRegistry;
|
|
15
|
+
displayStore: RepeaterDisplayStore;
|
|
16
|
+
position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let { dataStore, registry, displayStore, position = 'top-left' }: Props = $props();
|
|
20
|
+
|
|
21
|
+
const level1Options: RepeaterGroupingField[] = ['tech', 'provider', 'featureGroup'];
|
|
22
|
+
const level2Options: RepeaterGroupingField[] = ['fband', 'provider', 'featureGroup', 'none'];
|
|
23
|
+
|
|
24
|
+
let treeStore = $derived.by(() => {
|
|
25
|
+
const _repeaters = dataStore.filteredRepeaters;
|
|
26
|
+
const _l1 = displayStore.level1;
|
|
27
|
+
const _l2 = displayStore.level2;
|
|
28
|
+
|
|
29
|
+
return untrack(() => {
|
|
30
|
+
const nodes = buildRepeaterTree(_repeaters, registry, _l1, _l2);
|
|
31
|
+
return createTreeStore({
|
|
32
|
+
nodes,
|
|
33
|
+
namespace: `${registry.namespace}:repeater-tree:${_l1}:${_l2}`,
|
|
34
|
+
persistState: true,
|
|
35
|
+
defaultExpandAll: true
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
$effect(() => {
|
|
41
|
+
const unsubscribe = treeStore.subscribe((val) => {
|
|
42
|
+
let changes = 0;
|
|
43
|
+
val.state.nodes.forEach((nodeState) => {
|
|
44
|
+
if (nodeState.node.children && nodeState.node.children.length > 0) return;
|
|
45
|
+
|
|
46
|
+
const groupId = nodeState.node.id;
|
|
47
|
+
const isVisible = val.state.checkedPaths.has(nodeState.path);
|
|
48
|
+
|
|
49
|
+
const currentStyle = registry.getStyle(groupId, '#000');
|
|
50
|
+
if (currentStyle.visible !== isVisible) {
|
|
51
|
+
registry.toggleVisibility(groupId);
|
|
52
|
+
changes++;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
return () => {
|
|
58
|
+
unsubscribe();
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
function handleColorChange(groupId: string, event: Event) {
|
|
63
|
+
const input = event.target as HTMLInputElement;
|
|
64
|
+
registry.setColor(groupId, input.value);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function getFieldLabel(field: string): string {
|
|
68
|
+
const labels: Record<string, string> = {
|
|
69
|
+
tech: 'Technology',
|
|
70
|
+
fband: 'Tech + Band',
|
|
71
|
+
provider: 'Provider',
|
|
72
|
+
featureGroup: 'Feature Group',
|
|
73
|
+
none: 'None'
|
|
74
|
+
};
|
|
75
|
+
return labels[field] || field;
|
|
76
|
+
}
|
|
77
|
+
</script>
|
|
78
|
+
|
|
79
|
+
<MapControl {position} title="Repeaters" icon="router" controlWidth="320px">
|
|
80
|
+
<div class="card shadow-sm border-0">
|
|
81
|
+
<!-- <div class="card-header bg-white py-2 d-flex justify-content-between align-items-center">
|
|
82
|
+
<h6 class="mb-0 fw-bold text-primary">
|
|
83
|
+
<i class="bi bi-broadcast me-2"></i>Repeaters
|
|
84
|
+
</h6>
|
|
85
|
+
<span class="badge bg-light text-dark border">
|
|
86
|
+
{dataStore.filteredRepeaters.length}
|
|
87
|
+
</span>
|
|
88
|
+
</div> -->
|
|
89
|
+
|
|
90
|
+
<div class="card-body p-2 bg-light border-bottom">
|
|
91
|
+
<div class="row g-2">
|
|
92
|
+
<div class="col-6">
|
|
93
|
+
<label class="form-label x-small text-muted text-uppercase mb-1">Group By</label>
|
|
94
|
+
<select class="form-select form-select-sm" bind:value={displayStore.level1}>
|
|
95
|
+
{#each level1Options as opt}
|
|
96
|
+
<option value={opt}>{getFieldLabel(opt)}</option>
|
|
97
|
+
{/each}
|
|
98
|
+
</select>
|
|
99
|
+
</div>
|
|
100
|
+
<div class="col-6">
|
|
101
|
+
<label class="form-label x-small text-muted text-uppercase mb-1">Then By</label>
|
|
102
|
+
<select class="form-select form-select-sm" bind:value={displayStore.level2}>
|
|
103
|
+
{#each level2Options as opt}
|
|
104
|
+
<option value={opt}>{getFieldLabel(opt)}</option>
|
|
105
|
+
{/each}
|
|
106
|
+
</select>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<div class="card-body p-0 overflow-auto" style="max-height: 400px;">
|
|
112
|
+
<div class="p-2">
|
|
113
|
+
<TreeView showControls={false} store={$treeStore}>
|
|
114
|
+
{#snippet children({ node })}
|
|
115
|
+
<div class="d-flex align-items-center justify-content-between w-100">
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
{#if !node.children || node.children.length === 0}
|
|
119
|
+
<div
|
|
120
|
+
class="d-flex align-items-center"
|
|
121
|
+
role="group"
|
|
122
|
+
onclick={(e) => e.stopPropagation()}
|
|
123
|
+
onkeydown={(e) => e.stopPropagation()}
|
|
124
|
+
>
|
|
125
|
+
<input
|
|
126
|
+
type="color"
|
|
127
|
+
class="form-control form-control-color form-control-sm border-0 p-0"
|
|
128
|
+
style="width: 16px; height: 16px; min-height: 0;"
|
|
129
|
+
value={node.metadata?.color}
|
|
130
|
+
oninput={(e) => handleColorChange(node.id, e)}
|
|
131
|
+
title="Change color"
|
|
132
|
+
/>
|
|
133
|
+
</div>
|
|
134
|
+
{/if}
|
|
135
|
+
</div>
|
|
136
|
+
{/snippet}
|
|
137
|
+
</TreeView>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
</MapControl>
|
|
142
|
+
|
|
143
|
+
<style>
|
|
144
|
+
.x-small {
|
|
145
|
+
font-size: 0.7rem;
|
|
146
|
+
letter-spacing: 0.5px;
|
|
147
|
+
}
|
|
148
|
+
</style>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { RepeaterDataStore } from '../stores/repeater.data.svelte';
|
|
2
|
+
import type { RepeaterRegistry } from '../stores/repeater.registry.svelte';
|
|
3
|
+
import type { RepeaterDisplayStore } from '../stores/repeater.display.svelte';
|
|
4
|
+
interface Props {
|
|
5
|
+
dataStore: RepeaterDataStore;
|
|
6
|
+
registry: RepeaterRegistry;
|
|
7
|
+
displayStore: RepeaterDisplayStore;
|
|
8
|
+
position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
9
|
+
}
|
|
10
|
+
declare const RepeaterFilterControl: import("svelte").Component<Props, {}, "">;
|
|
11
|
+
type RepeaterFilterControl = ReturnType<typeof RepeaterFilterControl>;
|
|
12
|
+
export default RepeaterFilterControl;
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { RepeaterDisplayStore } from '../stores/repeater.display.svelte';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
displayStore: RepeaterDisplayStore;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
let { displayStore }: Props = $props();
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<div class="card border-0 shadow-sm rounded-2">
|
|
12
|
+
<div class="card-body bg-light p-3">
|
|
13
|
+
<!-- Target Pixel Size (Radius) -->
|
|
14
|
+
<div class="row align-items-center g-2 mb-3">
|
|
15
|
+
<div class="col-4 text-secondary fw-semibold small text-uppercase">Pixel Size</div>
|
|
16
|
+
<div class="col-3 text-end">
|
|
17
|
+
<span class="badge bg-white text-muted border">{displayStore.targetPixelSize}px</span>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="col-5">
|
|
20
|
+
<input
|
|
21
|
+
id="repeater-radius-slider"
|
|
22
|
+
type="range"
|
|
23
|
+
class="form-range w-100"
|
|
24
|
+
min="10"
|
|
25
|
+
max="200"
|
|
26
|
+
step="5"
|
|
27
|
+
bind:value={displayStore.targetPixelSize}
|
|
28
|
+
/>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<!-- Line Width -->
|
|
33
|
+
<div class="row align-items-center g-2 mb-3">
|
|
34
|
+
<div class="col-4 text-secondary fw-semibold small text-uppercase">Line Width</div>
|
|
35
|
+
<div class="col-3 text-end">
|
|
36
|
+
<span class="badge bg-white text-muted border">{displayStore.lineWidth}px</span>
|
|
37
|
+
</div>
|
|
38
|
+
<div class="col-5">
|
|
39
|
+
<input
|
|
40
|
+
id="repeater-line-width-slider"
|
|
41
|
+
type="range"
|
|
42
|
+
class="form-range w-100"
|
|
43
|
+
min="0.5"
|
|
44
|
+
max="5"
|
|
45
|
+
step="0.5"
|
|
46
|
+
bind:value={displayStore.lineWidth}
|
|
47
|
+
/>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<!-- Fill Opacity -->
|
|
52
|
+
<div class="row align-items-center g-2 mb-3">
|
|
53
|
+
<div class="col-4 text-secondary fw-semibold small text-uppercase">Fill Opacity</div>
|
|
54
|
+
<div class="col-3 text-end">
|
|
55
|
+
<span class="badge bg-white text-muted border">{Math.round(displayStore.fillOpacity * 100)}%</span>
|
|
56
|
+
</div>
|
|
57
|
+
<div class="col-5">
|
|
58
|
+
<input
|
|
59
|
+
id="repeater-opacity-slider"
|
|
60
|
+
type="range"
|
|
61
|
+
class="form-range w-100"
|
|
62
|
+
min="0"
|
|
63
|
+
max="1"
|
|
64
|
+
step="0.1"
|
|
65
|
+
bind:value={displayStore.fillOpacity}
|
|
66
|
+
/>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<div class="border-top my-3"></div>
|
|
71
|
+
|
|
72
|
+
<!-- Show Labels -->
|
|
73
|
+
<div class="row align-items-center g-2 mb-3">
|
|
74
|
+
<div class="col-4 text-secondary fw-semibold small text-uppercase">Show Labels</div>
|
|
75
|
+
<div class="col-3"></div>
|
|
76
|
+
<div class="col-5">
|
|
77
|
+
<div class="form-check form-switch m-0 d-flex align-items-center justify-content-end">
|
|
78
|
+
<input
|
|
79
|
+
id="repeater-labels-toggle"
|
|
80
|
+
type="checkbox"
|
|
81
|
+
class="form-check-input"
|
|
82
|
+
role="switch"
|
|
83
|
+
bind:checked={displayStore.showLabels}
|
|
84
|
+
/>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
{#if displayStore.showLabels}
|
|
90
|
+
<div class="ps-3 border-start border-2 mb-3">
|
|
91
|
+
<!-- Labels Config -->
|
|
92
|
+
<div class="mb-2">
|
|
93
|
+
<div class="text-secondary fw-semibold small text-uppercase mb-1">Label Fields</div>
|
|
94
|
+
<div class="row g-1">
|
|
95
|
+
<div class="col-6">
|
|
96
|
+
<select class="form-select form-select-sm" bind:value={displayStore.labels.primary}>
|
|
97
|
+
<option value="repeaterId">ID</option>
|
|
98
|
+
<option value="tech">Tech</option>
|
|
99
|
+
<option value="fband">Band</option>
|
|
100
|
+
<option value="provider">Provider</option>
|
|
101
|
+
</select>
|
|
102
|
+
</div>
|
|
103
|
+
<div class="col-6">
|
|
104
|
+
<select class="form-select form-select-sm" bind:value={displayStore.labels.secondary}>
|
|
105
|
+
<option value="none">None</option>
|
|
106
|
+
<option value="repeaterId">ID</option>
|
|
107
|
+
<option value="tech">Tech</option>
|
|
108
|
+
<option value="fband">Band</option>
|
|
109
|
+
<option value="provider">Provider</option>
|
|
110
|
+
</select>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
<!-- Label Settings -->
|
|
116
|
+
<div class="mt-3">
|
|
117
|
+
<!-- Label Distance -->
|
|
118
|
+
<div class="row align-items-center g-2 mb-2">
|
|
119
|
+
<div class="col-4 text-secondary small">Distance</div>
|
|
120
|
+
<div class="col-3 text-end">
|
|
121
|
+
<span class="badge bg-white text-muted border">{displayStore.labelPixelDistance}px</span>
|
|
122
|
+
</div>
|
|
123
|
+
<div class="col-5">
|
|
124
|
+
<input
|
|
125
|
+
type="range"
|
|
126
|
+
class="form-range w-100"
|
|
127
|
+
min="20"
|
|
128
|
+
max="150"
|
|
129
|
+
step="5"
|
|
130
|
+
bind:value={displayStore.labelPixelDistance}
|
|
131
|
+
/>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
|
|
135
|
+
<!-- Font Size -->
|
|
136
|
+
<div class="row align-items-center g-2 mb-3">
|
|
137
|
+
<div class="col-4 text-secondary small">Font Size</div>
|
|
138
|
+
<div class="col-3 text-end">
|
|
139
|
+
<span class="badge bg-white text-muted border">{displayStore.labelFontSize}px</span>
|
|
140
|
+
</div>
|
|
141
|
+
<div class="col-5">
|
|
142
|
+
<input
|
|
143
|
+
type="range"
|
|
144
|
+
class="form-range w-100"
|
|
145
|
+
min="8"
|
|
146
|
+
max="24"
|
|
147
|
+
step="1"
|
|
148
|
+
bind:value={displayStore.labelFontSize}
|
|
149
|
+
/>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
<!-- Label Colors -->
|
|
154
|
+
<div class="row align-items-center g-2 mb-2">
|
|
155
|
+
<div class="col-4 text-secondary small">Color</div>
|
|
156
|
+
<div class="col-8">
|
|
157
|
+
<div class="input-group input-group-sm">
|
|
158
|
+
<input
|
|
159
|
+
type="color"
|
|
160
|
+
class="form-control form-control-color"
|
|
161
|
+
bind:value={displayStore.labelColor}
|
|
162
|
+
title="Label Color"
|
|
163
|
+
/>
|
|
164
|
+
<span class="input-group-text bg-white text-muted small flex-grow-1 font-monospace">
|
|
165
|
+
{displayStore.labelColor}
|
|
166
|
+
</span>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
|
|
171
|
+
<!-- Halo Settings -->
|
|
172
|
+
<div class="row align-items-center g-2 mb-2">
|
|
173
|
+
<div class="col-4 text-secondary small">Halo</div>
|
|
174
|
+
<div class="col-8">
|
|
175
|
+
<div class="input-group input-group-sm">
|
|
176
|
+
<input
|
|
177
|
+
type="color"
|
|
178
|
+
class="form-control form-control-color"
|
|
179
|
+
bind:value={displayStore.labelHaloColor}
|
|
180
|
+
title="Halo Color"
|
|
181
|
+
/>
|
|
182
|
+
<span class="input-group-text bg-white text-muted small flex-grow-1 font-monospace">
|
|
183
|
+
{displayStore.labelHaloColor}
|
|
184
|
+
</span>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
|
|
189
|
+
<div class="row align-items-center g-2 mb-3">
|
|
190
|
+
<div class="col-4 text-secondary small">Halo Width</div>
|
|
191
|
+
<div class="col-3 text-end">
|
|
192
|
+
<span class="badge bg-white text-muted border">{displayStore.labelHaloWidth}px</span>
|
|
193
|
+
</div>
|
|
194
|
+
<div class="col-5">
|
|
195
|
+
<input
|
|
196
|
+
type="range"
|
|
197
|
+
class="form-range w-100"
|
|
198
|
+
min="0"
|
|
199
|
+
max="5"
|
|
200
|
+
step="0.5"
|
|
201
|
+
bind:value={displayStore.labelHaloWidth}
|
|
202
|
+
/>
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
207
|
+
{/if}
|
|
208
|
+
</div>
|
|
209
|
+
</div>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { RepeaterDisplayStore } from '../stores/repeater.display.svelte';
|
|
2
|
+
interface Props {
|
|
3
|
+
displayStore: RepeaterDisplayStore;
|
|
4
|
+
}
|
|
5
|
+
declare const RepeaterSettingsPanel: import("svelte").Component<Props, {}, "">;
|
|
6
|
+
type RepeaterSettingsPanel = ReturnType<typeof RepeaterSettingsPanel>;
|
|
7
|
+
export default RepeaterSettingsPanel;
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { getContext } from 'svelte';
|
|
3
|
+
import type { MapStore } from '../../../core/stores/map.store.svelte';
|
|
4
|
+
import type { RepeaterDataStore } from '../stores/repeater.data.svelte';
|
|
5
|
+
import type { RepeaterDisplayStore } from '../stores/repeater.display.svelte';
|
|
6
|
+
import type { RepeaterRegistry } from '../stores/repeater.registry.svelte';
|
|
7
|
+
import { groupRepeaters, getColorForGroup } from '../logic/grouping';
|
|
8
|
+
import type { Repeater } from '../types';
|
|
9
|
+
import type mapboxgl from 'mapbox-gl';
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
dataStore: RepeaterDataStore;
|
|
13
|
+
displayStore: RepeaterDisplayStore;
|
|
14
|
+
registry: RepeaterRegistry;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let { dataStore, displayStore, registry }: Props = $props();
|
|
18
|
+
|
|
19
|
+
const mapStore = getContext<MapStore>('MAP_CONTEXT');
|
|
20
|
+
let sourceId = 'repeater-labels-source';
|
|
21
|
+
let layerId = 'repeater-labels-layer';
|
|
22
|
+
let updateTimeout: any;
|
|
23
|
+
|
|
24
|
+
// Helper to get label text
|
|
25
|
+
function getLabelText(repeater: Repeater, config: { primary: string, secondary: string }): string {
|
|
26
|
+
const pVal = repeater[config.primary as keyof Repeater];
|
|
27
|
+
const sVal = config.secondary !== 'none' ? repeater[config.secondary as keyof Repeater] : null;
|
|
28
|
+
|
|
29
|
+
let text = pVal != null ? String(pVal) : '';
|
|
30
|
+
if (sVal != null) {
|
|
31
|
+
text += ` | ${sVal}`;
|
|
32
|
+
}
|
|
33
|
+
return text;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// React to changes
|
|
37
|
+
$effect(() => {
|
|
38
|
+
// Read dependencies
|
|
39
|
+
const _repeaters = dataStore.filteredRepeaters;
|
|
40
|
+
const _show = displayStore.showLabels;
|
|
41
|
+
const _dist = displayStore.labelPixelDistance;
|
|
42
|
+
const _size = displayStore.labelFontSize;
|
|
43
|
+
const _tol = displayStore.labelAzimuthTolerance;
|
|
44
|
+
// Track specific label properties for reactivity
|
|
45
|
+
const _lPrim = displayStore.labels.primary;
|
|
46
|
+
const _lSec = displayStore.labels.secondary;
|
|
47
|
+
const _color = displayStore.labelColor;
|
|
48
|
+
const _haloColor = displayStore.labelHaloColor;
|
|
49
|
+
const _haloWidth = displayStore.labelHaloWidth;
|
|
50
|
+
const _registryVersion = registry.version;
|
|
51
|
+
|
|
52
|
+
const map = mapStore.map;
|
|
53
|
+
if (map && map.getLayer(layerId)) {
|
|
54
|
+
map.setPaintProperty(layerId, 'text-color', displayStore.labelColor);
|
|
55
|
+
map.setPaintProperty(layerId, 'text-halo-color', displayStore.labelHaloColor);
|
|
56
|
+
map.setPaintProperty(layerId, 'text-halo-width', displayStore.labelHaloWidth);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
updateLayer();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
function updateLayer() {
|
|
63
|
+
const map = mapStore.map;
|
|
64
|
+
if (!map) return;
|
|
65
|
+
|
|
66
|
+
clearTimeout(updateTimeout);
|
|
67
|
+
updateTimeout = setTimeout(() => {
|
|
68
|
+
renderLabels(map);
|
|
69
|
+
}, 150);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function renderLabels(map: mapboxgl.Map) {
|
|
73
|
+
if (!displayStore.showLabels) {
|
|
74
|
+
if (map.getLayer(layerId)) map.setLayoutProperty(layerId, 'visibility', 'none');
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (map.getLayer(layerId)) map.setLayoutProperty(layerId, 'visibility', 'visible');
|
|
79
|
+
|
|
80
|
+
// Update text size if changed
|
|
81
|
+
if (map.getLayer(layerId)) {
|
|
82
|
+
map.setLayoutProperty(layerId, 'text-size', displayStore.labelFontSize);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const bounds = map.getBounds();
|
|
86
|
+
if (!bounds) return;
|
|
87
|
+
|
|
88
|
+
// 1. Filter visible repeaters (Bounds + Registry Visibility)
|
|
89
|
+
const visibleRepeaters: Repeater[] = [];
|
|
90
|
+
|
|
91
|
+
// Group repeaters using the same logic as RepeatersLayer to determine visibility
|
|
92
|
+
const repeaterGroups = groupRepeaters(dataStore.filteredRepeaters, displayStore.level1, displayStore.level2);
|
|
93
|
+
let groupIndex = 0;
|
|
94
|
+
|
|
95
|
+
for (const [groupId, repeaters] of repeaterGroups) {
|
|
96
|
+
const defaultColor = getColorForGroup(groupIndex++);
|
|
97
|
+
const style = registry.getStyle(groupId, defaultColor);
|
|
98
|
+
|
|
99
|
+
if (style.visible) {
|
|
100
|
+
// Check bounds
|
|
101
|
+
for (const r of repeaters) {
|
|
102
|
+
if (bounds.contains([r.longitude, r.latitude])) {
|
|
103
|
+
visibleRepeaters.push(r);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 2. Generate Label Features
|
|
110
|
+
const features: GeoJSON.Feature[] = [];
|
|
111
|
+
|
|
112
|
+
// Convert pixel distance to ems (approx 1em = 16px or labelFontSize)
|
|
113
|
+
const distEm = displayStore.labelPixelDistance / displayStore.labelFontSize;
|
|
114
|
+
|
|
115
|
+
for (const r of visibleRepeaters) {
|
|
116
|
+
const labelText = getLabelText(r, displayStore.labels);
|
|
117
|
+
if (!labelText) continue;
|
|
118
|
+
|
|
119
|
+
// Calculate offset based on azimuth
|
|
120
|
+
// Mapbox text-offset: [x, y]
|
|
121
|
+
// x = sin(az) * dist
|
|
122
|
+
// y = -cos(az) * dist (negative because y is down)
|
|
123
|
+
const az = r.azimuth ?? 0;
|
|
124
|
+
const azRad = (az * Math.PI) / 180;
|
|
125
|
+
const offsetX = Math.sin(azRad) * distEm;
|
|
126
|
+
const offsetY = -Math.cos(azRad) * distEm;
|
|
127
|
+
|
|
128
|
+
features.push({
|
|
129
|
+
type: 'Feature',
|
|
130
|
+
geometry: {
|
|
131
|
+
type: 'Point',
|
|
132
|
+
coordinates: [r.longitude, r.latitude]
|
|
133
|
+
},
|
|
134
|
+
properties: {
|
|
135
|
+
label: labelText,
|
|
136
|
+
offset: [offsetX, offsetY]
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const geojson: GeoJSON.FeatureCollection = {
|
|
142
|
+
type: 'FeatureCollection',
|
|
143
|
+
features
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const source = map.getSource(sourceId) as mapboxgl.GeoJSONSource;
|
|
147
|
+
if (source) {
|
|
148
|
+
source.setData(geojson);
|
|
149
|
+
} else {
|
|
150
|
+
map.addSource(sourceId, {
|
|
151
|
+
type: 'geojson',
|
|
152
|
+
data: geojson
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
map.addLayer({
|
|
156
|
+
id: layerId,
|
|
157
|
+
type: 'symbol',
|
|
158
|
+
source: sourceId,
|
|
159
|
+
layout: {
|
|
160
|
+
'text-field': ['get', 'label'],
|
|
161
|
+
'text-font': ['Open Sans Semibold', 'Arial Unicode MS Bold'],
|
|
162
|
+
'text-size': displayStore.labelFontSize,
|
|
163
|
+
'text-offset': ['get', 'offset'],
|
|
164
|
+
'text-anchor': 'center',
|
|
165
|
+
'text-justify': 'auto',
|
|
166
|
+
'text-allow-overlap': false,
|
|
167
|
+
'text-ignore-placement': false
|
|
168
|
+
},
|
|
169
|
+
paint: {
|
|
170
|
+
'text-color': displayStore.labelColor,
|
|
171
|
+
'text-halo-color': displayStore.labelHaloColor,
|
|
172
|
+
'text-halo-width': displayStore.labelHaloWidth
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
</script>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { RepeaterDataStore } from '../stores/repeater.data.svelte';
|
|
2
|
+
import type { RepeaterDisplayStore } from '../stores/repeater.display.svelte';
|
|
3
|
+
import type { RepeaterRegistry } from '../stores/repeater.registry.svelte';
|
|
4
|
+
interface Props {
|
|
5
|
+
dataStore: RepeaterDataStore;
|
|
6
|
+
displayStore: RepeaterDisplayStore;
|
|
7
|
+
registry: RepeaterRegistry;
|
|
8
|
+
}
|
|
9
|
+
declare const RepeaterLabelsLayer: import("svelte").Component<Props, {}, "">;
|
|
10
|
+
type RepeaterLabelsLayer = ReturnType<typeof RepeaterLabelsLayer>;
|
|
11
|
+
export default RepeaterLabelsLayer;
|