Skip to content

Commit e40a9b1

Browse files
Merge pull request #66 from Juliusolsson05/feat/show-all-map-labels
feat: add map show-all-labels toggle
2 parents f0f3489 + 4a90ae8 commit e40a9b1

8 files changed

Lines changed: 83 additions & 9 deletions

File tree

src/features/map/components/MapControls.tsx

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,52 @@ import type { MapViewState } from '@deck.gl/core';
55
import { Button } from '@/components/ui/button';
66

77
type Props = {
8+
showAllLabels: boolean;
89
viewState: MapViewState;
910
mapStyle: 'dark' | 'satellite';
1011
hasPanel: boolean;
1112
timelineVisible?: boolean;
1213
isMobile?: boolean;
14+
onShowAllLabelsChange: (show: boolean) => void;
1315
onStyleChange: (s: 'dark' | 'satellite') => void;
1416
};
1517

16-
export function MapControls({ viewState, mapStyle, hasPanel, timelineVisible = true, isMobile = false, onStyleChange }: Props) {
18+
export function MapControls({
19+
viewState,
20+
mapStyle,
21+
hasPanel,
22+
timelineVisible = true,
23+
isMobile = false,
24+
onShowAllLabelsChange,
25+
onStyleChange,
26+
showAllLabels,
27+
}: Props) {
1728
const right: number | string = isMobile ? 'max(12px, var(--safe-right))' : (hasPanel ? 332 : 12);
1829
const bottomOffset = timelineVisible ? 0 : -44;
1930
const coordBottom = isMobile ? 64 + bottomOffset : 56 + bottomOffset;
20-
const switcherBottom = isMobile ? 94 + bottomOffset : 86 + bottomOffset;
31+
const switcherBottom = isMobile ? 126 + bottomOffset : 118 + bottomOffset;
2132
const coordBottomStyle = isMobile ? `calc(${coordBottom}px + var(--safe-bottom))` : coordBottom;
2233
const switcherBottomStyle = isMobile ? `calc(${switcherBottom}px + var(--safe-bottom))` : switcherBottom;
2334

2435
return (
2536
<>
37+
<Button
38+
variant="ghost"
39+
size="xs"
40+
onClick={() => onShowAllLabelsChange(!showAllLabels)}
41+
className={`mono absolute z-10 h-auto rounded-sm px-2.5 py-1 font-bold ${isMobile ? 'text-[9px]' : 'text-[8px]'}`}
42+
style={{
43+
background: showAllLabels ? 'var(--blue-dim)' : 'rgba(28,33,39,0.92)',
44+
border: '1px solid var(--bd)',
45+
bottom: isMobile ? `calc(${switcherBottom + 32}px + var(--safe-bottom))` : switcherBottom + 30,
46+
color: showAllLabels ? 'var(--blue-l)' : 'var(--t3)',
47+
right,
48+
transition: 'right 0.22s cubic-bezier(0.4,0,0.2,1), bottom 0.22s cubic-bezier(0.4,0,0.2,1)',
49+
}}
50+
>
51+
LABELS {showAllLabels ? 'ALL' : 'SMART'}
52+
</Button>
53+
2654
{/* Map style switcher */}
2755
<div className="absolute flex overflow-hidden rounded-sm z-10"
2856
style={{ bottom: switcherBottomStyle, right, border: '1px solid var(--bd)', transition: 'right 0.22s cubic-bezier(0.4,0,0.2,1), bottom 0.22s cubic-bezier(0.4,0,0.2,1)' }}>

src/features/map/components/desktop/MapLayout.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ type Props = {
3030

3131
export function DesktopMapLayout({ ctx, embedded = false }: Props) {
3232
const {
33-
viewState, activeStory, selectedItem, sidebarOpen, mapStyle, stories,
33+
viewState, activeStory, selectedItem, showAllLabels, sidebarOpen, mapStyle, stories,
3434
overlayVisibility, toggleOverlay, f, tooltip, layers, handleMapClick, showTimeline,
35-
setViewState, activateStory, setActiveStory, setSelectedItem,
35+
setViewState, activateStory, setActiveStory, setSelectedItem, setShowAllLabels,
3636
toggleSidebar, setMapStyle,
3737
} = ctx;
3838

@@ -95,11 +95,13 @@ export function DesktopMapLayout({ ctx, embedded = false }: Props) {
9595
)}
9696

9797
<MapControls
98+
showAllLabels={showAllLabels}
9899
viewState={viewState}
99100
mapStyle={mapStyle}
100101
hasPanel={!!selectedItem}
101102
timelineVisible={showTimeline}
102103
isMobile={false}
104+
onShowAllLabelsChange={setShowAllLabels}
103105
onStyleChange={setMapStyle}
104106
/>
105107

src/features/map/components/landscape/MapCanvas.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ type Props = {
2828

2929
export function MapCanvas({ ctx, onOpenStories, onSelectFeature }: Props) {
3030
const {
31-
viewState, mapStyle, layers, tooltip, handleMapClick, showTimeline,
31+
viewState, mapStyle, layers, tooltip, handleMapClick, showAllLabels, showTimeline,
3232
overlayVisibility, toggleOverlay, f,
33-
setViewState, setMapStyle,
33+
setShowAllLabels, setViewState, setMapStyle,
3434
} = ctx;
3535

3636
const handleClick = (...args: Parameters<typeof handleMapClick>) => {
@@ -89,6 +89,20 @@ export function MapCanvas({ ctx, onOpenStories, onSelectFeature }: Props) {
8989
<MapIcon size={14} strokeWidth={2} />
9090
</Button>
9191

92+
<Button
93+
variant="ghost"
94+
size="icon-sm"
95+
onClick={() => setShowAllLabels(!showAllLabels)}
96+
className={`h-8 w-8 border transition-colors rounded-none ${
97+
showAllLabels
98+
? 'bg-[var(--blue-dim)] border-[var(--blue)] text-[var(--blue-l)]'
99+
: 'bg-[rgba(28,33,39,0.85)] border-[var(--bd)] text-[var(--t3)] hover:text-[var(--t1)]'
100+
}`}
101+
title={showAllLabels ? 'Use smart label filtering' : 'Show all labels'}
102+
>
103+
<span className="mono text-[8px] font-bold">LBL</span>
104+
</Button>
105+
92106
{/* Visibility menu */}
93107
<MapVisibilityMenu visibility={overlayVisibility} onToggle={toggleOverlay} direction="down" />
94108
</div>

src/features/map/components/mobile/MapLayout.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ type Props = {
2828

2929
export function MobileMapLayout({ ctx, embedded = false }: Props) {
3030
const {
31-
viewState, activeStory, selectedItem, sidebarOpen, mapStyle, stories,
31+
viewState, activeStory, selectedItem, showAllLabels, sidebarOpen, mapStyle, stories,
3232
overlayVisibility, toggleOverlay, f, tooltip, layers, handleMapClick, showTimeline,
33-
setViewState, activateStory, setActiveStory, setSelectedItem,
33+
setViewState, activateStory, setActiveStory, setSelectedItem, setShowAllLabels,
3434
toggleSidebar, setSidebarOpen, setMapStyle,
3535
} = ctx;
3636

@@ -117,11 +117,13 @@ export function MobileMapLayout({ ctx, embedded = false }: Props) {
117117
)}
118118

119119
<MapControls
120+
showAllLabels={showAllLabels}
120121
viewState={viewState}
121122
mapStyle={mapStyle}
122123
hasPanel={false}
123124
timelineVisible={showTimeline}
124125
isMobile
126+
onShowAllLabelsChange={setShowAllLabels}
125127
onStyleChange={setMapStyle}
126128
/>
127129

src/features/map/components/use-map-page.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
setActiveStory as setActiveStoryAction,
1818
setMapStyle as setMapStyleAction,
1919
setSelectedItem as setSelectedItemAction,
20+
setShowAllLabels as setShowAllLabelsAction,
2021
setSidebarOpen as setSidebarOpenAction,
2122
setViewState as setViewStateAction,
2223
toggleSidebar as toggleSidebarAction,
@@ -36,6 +37,7 @@ export function useMapPage({ isMobile }: { isMobile: boolean }) {
3637
const viewState = useAppSelector(s => s.map.viewState);
3738
const activeStory = useAppSelector(s => s.map.activeStory);
3839
const selectedItem = useAppSelector(s => s.map.selectedItem);
40+
const showAllLabels = useAppSelector(s => s.map.showAllLabels);
3941
const sidebarOpen = useAppSelector(s => s.map.sidebarOpen);
4042
const mapStyle = useAppSelector(s => s.map.mapStyle);
4143
const { data: stories = [], isLoading: storiesLoading } = useMapStories();
@@ -61,6 +63,7 @@ export function useMapPage({ isMobile }: { isMobile: boolean }) {
6163
viewState,
6264
isSatellite: mapStyle === 'satellite',
6365
isMobile,
66+
showAllLabels,
6467
});
6568

6669
const handleMapClick = useCallback(({ object, layer }: PickingInfo): SelectedItem | null => {
@@ -113,6 +116,7 @@ export function useMapPage({ isMobile }: { isMobile: boolean }) {
113116
viewState,
114117
activeStory,
115118
selectedItem,
119+
showAllLabels,
116120
sidebarOpen,
117121
mapStyle,
118122
stories,
@@ -136,6 +140,7 @@ export function useMapPage({ isMobile }: { isMobile: boolean }) {
136140
return dispatch(setActiveStoryAction(story));
137141
},
138142
setSelectedItem: (item: Parameters<typeof setSelectedItemAction>[0]) => dispatch(setSelectedItemAction(item)),
143+
setShowAllLabels: (show: boolean) => dispatch(setShowAllLabelsAction(show)),
139144
toggleSidebar: () => dispatch(toggleSidebarAction()),
140145
setSidebarOpen: (open: boolean) => dispatch(setSidebarOpenAction(open)),
141146
setMapStyle: (style: Parameters<typeof setMapStyleAction>[0]) => { track('map_style_changed', { style }); return dispatch(setMapStyleAction(style)); },

src/features/map/hooks/use-map-layers.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ type Props = {
3131
viewState: MapViewState;
3232
isSatellite: boolean;
3333
isMobile?: boolean;
34+
showAllLabels?: boolean;
3435
};
3536

3637
type RGBA = [number, number, number, number];
@@ -77,6 +78,7 @@ export function useMapLayers({
7778
viewState,
7879
isSatellite,
7980
isMobile = false,
81+
showAllLabels = false,
8082
}: Props): Layer[] {
8183
return useMemo(() => {
8284
const activeEventIds = activeStory
@@ -125,6 +127,7 @@ export function useMapLayers({
125127
viewState,
126128
selectedItem,
127129
mergedActiveStory,
130+
showAllLabels,
128131
);
129132

130133
// Heat map
@@ -297,7 +300,7 @@ export function useMapLayers({
297300
const layers = [heatLayer, zoneLayer, strikeLayer, missileLayer, targetLayer, assetLayer, targetLabels, assetLabels].filter(Boolean);
298301

299302
return layers as Layer[];
300-
}, [filtered, actorMeta, activeStory, selectedItem, viewState, isSatellite, isMobile]);
303+
}, [filtered, actorMeta, activeStory, selectedItem, viewState, isSatellite, isMobile, showAllLabels]);
301304
}
302305

303306
// Re-export so tooltip handler can share STATUS_META without another import

src/features/map/lib/label-visibility.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ export function selectVisibleLabels(
103103
viewState: MapViewState,
104104
selectedItem: SelectedItem | null,
105105
activeStory: MapStory | null,
106+
showAllLabels = false,
106107
): LabelSelection {
107108
const budget = labelBudget(viewState.zoom);
108109
const candidates: LabelCandidate[] = [
@@ -130,6 +131,17 @@ export function selectVisibleLabels(
130131
const cellCounts = new Map<string, number>();
131132
const selected: LabelCandidate[] = [];
132133

134+
if (showAllLabels) {
135+
return {
136+
targets: candidates
137+
.filter((item): item is LabelCandidate & { point: Target; kind: 'target' } => item.kind === 'target')
138+
.map((item) => item.point),
139+
assets: candidates
140+
.filter((item): item is LabelCandidate & { point: Asset; kind: 'asset' } => item.kind === 'asset')
141+
.map((item) => item.point),
142+
};
143+
}
144+
133145
for (const candidate of candidates) {
134146
const cellKey = toCellKey(candidate.position, viewState.zoom);
135147
const cellCount = cellCounts.get(cellKey) ?? 0;

src/features/map/state/map-slice.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export type MapState = {
3535
selectedItem: SelectedItem | null;
3636

3737
// UI chrome
38+
showAllLabels: boolean;
3839
sidebarOpen: boolean;
3940
mapStyle: 'dark' | 'satellite';
4041

@@ -45,6 +46,7 @@ export type MapState = {
4546
// localStorage persistence
4647

4748
type PersistedMapPrefs = {
49+
showAllLabels: boolean;
4850
sidebarOpen: boolean;
4951
mapStyle: 'dark' | 'satellite';
5052
};
@@ -64,6 +66,7 @@ export function persistMapPrefs(state: MapState): void {
6466
if (!hasPreferencesConsent()) return;
6567
try {
6668
const persisted: PersistedMapPrefs = {
69+
showAllLabels: state.showAllLabels,
6770
sidebarOpen: state.sidebarOpen,
6871
mapStyle: state.mapStyle,
6972
};
@@ -113,6 +116,7 @@ function buildInitialState(): MapState {
113116
viewExtent: [0, 0],
114117
activeStory: null,
115118
selectedItem: null,
119+
showAllLabels: persisted?.showAllLabels ?? false,
116120
sidebarOpen: persisted?.sidebarOpen ?? true,
117121
mapStyle: persisted?.mapStyle ?? 'dark',
118122
_filtersFingerprint: null,
@@ -252,6 +256,9 @@ const mapSlice = createSlice({
252256
setSidebarOpen(state, action: PayloadAction<boolean>) {
253257
state.sidebarOpen = action.payload;
254258
},
259+
setShowAllLabels(state, action: PayloadAction<boolean>) {
260+
state.showAllLabels = action.payload;
261+
},
255262
setMapStyle(state, action: PayloadAction<'dark' | 'satellite'>) {
256263
state.mapStyle = action.payload;
257264
},
@@ -275,6 +282,7 @@ export const {
275282
setSelectedItem,
276283
toggleSidebar,
277284
setSidebarOpen,
285+
setShowAllLabels,
278286
setMapStyle,
279287
} = mapSlice.actions;
280288

0 commit comments

Comments
 (0)