Skip to content

Commit d251cc9

Browse files
authored
CODEX+JGARRETT: refactor POIController (#54)
* move POI overlay logic out of map * split POI controller into two * add docs * remove comment
1 parent 7848f6a commit d251cc9

8 files changed

Lines changed: 73 additions & 49 deletions

File tree

src/main.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import { initMap } from "./map/map";
33
import {
44
POIBoundsController,
55
POICollisionController,
6-
POIController,
6+
POIMarkerController,
7+
POIPopupController,
78
POIs,
89
POITracker,
910
} from "./points";
@@ -49,9 +50,14 @@ const locationController = new LocationController({
4950
});
5051

5152
/**
52-
* controls showing the popup and marking the circles
53+
* popup/audio controller for the currently active POI
5354
*/
54-
new POIController({ poiTracker });
55+
new POIPopupController({ poiTracker });
56+
57+
/**
58+
* on-map marker layer for POIs
59+
*/
60+
const poiMarkerController = new POIMarkerController({ poiTracker, POIs });
5561

5662
/**
5763
* controls collision between live user location and POI bounds
@@ -68,7 +74,6 @@ new POICollisionController({
6874
const boundsController = new POIBoundsController({ POIs });
6975

7076
const map = initMap({
71-
POIs,
7277
config: {
7378
initialLocation: [34.181922, -116.414579],
7479
initialZoom: 21,
@@ -86,7 +91,11 @@ const map = initMap({
8691
},
8792
},
8893
// TODO: baseclass for exposing a layer?
89-
additionalLayers: [locationController.layer, boundsController.layer],
94+
additionalLayers: [
95+
locationController.layer,
96+
boundsController.layer,
97+
poiMarkerController.layer,
98+
],
9099
providers: {
91100
poiTracker,
92101
locationController,

src/map/map.ts

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
import type { POI } from "../types";
21
import L, { Layer, TileLayer, type MapOptions } from "leaflet";
3-
import { poiMarker } from "./components/poi-marker";
42
import { POITracker } from "../points";
53
import { debug } from "../utils";
64
import type { LocationController } from "../location/location-controller";
@@ -26,7 +24,6 @@ type MapParameters = {
2624
locationController: LocationController;
2725
};
2826
additionalLayers?: Layer[];
29-
POIs: Array<POI>;
3027
};
3128

3229
export const initMap = (params: MapParameters) => {
@@ -53,19 +50,6 @@ export const initMap = (params: MapParameters) => {
5350
L.control.layers(config.tileLayers).addTo(map);
5451
}
5552

56-
// TODO: move this into a layer exposed on the POIControlller
57-
params.POIs.forEach((POI, index) => {
58-
const { latitude, longitude } = POI.location;
59-
60-
L.marker([latitude, longitude], {
61-
icon: poiMarker({ number: index + 1, POI }),
62-
})
63-
.addTo(map)
64-
.on("click", () => {
65-
poiTracker.select(POI);
66-
});
67-
});
68-
6953
// Safari (macOS/iOS) can change viewport when the location permission dialog
7054
// appears or closes, so Leaflet’s cached size becomes wrong. Recompute it.
7155
const recomputeMapSize = () => {
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import L from "leaflet";
2+
import type { POI } from "../types";
3+
import { getElementOrThrow } from "../utils";
4+
import type { POITracker } from "./POI-tracker";
5+
import { markerIdForPOI, poiMarker } from "./poi-marker";
6+
7+
/**
8+
* Owns the Leaflet marker layer for POIs.
9+
*
10+
* Responsibilities:
11+
* - create the on-map POI markers
12+
* - translate marker clicks into POITracker selections
13+
* - apply persistent selected-marker styling when POITracker changes
14+
*/
15+
export class POIMarkerController {
16+
constructor({ poiTracker, POIs }: { poiTracker: POITracker; POIs: POI[] }) {
17+
this.layer = L.layerGroup(
18+
POIs.map((POI, index) => {
19+
const { latitude, longitude } = POI.location;
20+
21+
return L.marker([latitude, longitude], {
22+
icon: poiMarker({ number: index + 1, POI }),
23+
}).on("click", () => {
24+
poiTracker.select(POI);
25+
});
26+
}),
27+
);
28+
29+
poiTracker.addListener((activePOI) => {
30+
if (activePOI) {
31+
const nextMarker = getElementOrThrow({
32+
id: markerIdForPOI(activePOI),
33+
});
34+
nextMarker.style.opacity = "0.7";
35+
}
36+
});
37+
}
38+
39+
public layer: L.LayerGroup;
40+
}
Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,27 @@
1-
import { markerIdForPOI } from "../map/components/poi-marker";
2-
import { POITracker } from "../points";
1+
import type { POITracker } from "./POI-tracker";
32
import type { POI } from "../types";
43
import { debug, getElementOrThrow, info } from "../utils";
54
import { AudioController } from "./audio-controller";
65

76
/**
8-
* Top-level coordinator for POI popup content.
7+
* Owns the popup UI for the active POI.
98
*
10-
* Audio stack breakdown:
11-
* - POIController shows and hides popup content for the active POI.
12-
* - AudioController owns the custom player UI inside the popup.
13-
* - AudioElement wraps the backing HTML audio node and handles media lifecycle.
9+
* Responsibilities:
10+
* - show and hide popup content for the active POI
11+
* - populate popup title and image state
12+
* - coordinate popup audio setup and teardown
1413
*
1514
* Provenance:
1615
* - the current custom POI audio player integration was AI-generated
1716
* - review behavior carefully when changing popup/audio interactions
1817
*/
19-
export class POIController {
20-
constructor(params: { poiTracker: POITracker }) {
18+
export class POIPopupController {
19+
constructor({ poiTracker }: { poiTracker: POITracker }) {
2120
getElementOrThrow({ id: "poi-popup-close" }).addEventListener(
2221
"click",
2322
() => {
2423
// when the user hits close, they're deselecting the active
25-
params.poiTracker.deselectActive();
24+
poiTracker.deselectActive();
2625
},
2726
);
2827

@@ -47,9 +46,9 @@ export class POIController {
4746
};
4847
this.audioController = new AudioController();
4948

50-
params.poiTracker.addListener((activePOI) => {
49+
poiTracker.addListener((activePOI) => {
5150
debug(
52-
`[POIController] listener for poiTrackerInstance fired: ${activePOI}`,
51+
`[POIPopupController] listener for poiTrackerInstance fired: ${activePOI}`,
5352
);
5453
if (activePOI) {
5554
this.display(activePOI);
@@ -59,29 +58,21 @@ export class POIController {
5958
});
6059
}
6160

62-
/**
63-
* called whenever POITracker sees a switch
64-
*/
61+
/** Called when POITracker selects or switches to a POI. */
6562
display(poi: POI) {
6663
if (this.active?.poi && poi.id !== this.active.poi?.id) {
6764
info(
68-
`[POIController] switching from ${this.active.poi?.id} to ${poi.id}`,
65+
`[POIPopupController] switching from ${this.active.poi?.id} to ${poi.id}`,
6966
);
7067

7168
this.audioController.teardown();
7269
}
7370

7471
this.active = { poi: poi };
7572

76-
// --------- configure marker
77-
78-
const poiMarker = getElementOrThrow({ id: markerIdForPOI(poi) });
79-
poiMarker.style.opacity = "0.7";
80-
81-
// --------- configure popup
82-
8373
this.elements.title.textContent = poi.title;
8474

75+
// TODO: non optional
8576
if (poi.imageName) {
8677
this.elements.image.classList.add("poi-popup-image-loading");
8778
this.elements.image.removeAttribute("src");
@@ -106,7 +97,6 @@ export class POIController {
10697
}
10798

10899
close() {
109-
// TODO: hidden check here?
110100
this.elements.container.classList.add("hidden");
111101
this.hidden = true;
112102

src/points/audio-controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ const formatRemainingTime = (currentTime: number, duration: number) => {
2222
* Owns the custom POI audio player UI.
2323
*
2424
* Audio stack breakdown:
25-
* - POIController decides when popup audio should be shown for an active POI.
25+
* - POIPopupController decides when popup audio should be shown for an active POI.
2626
* - AudioController owns the custom player UI inside the popup.
2727
* - AudioElement wraps the backing HTML audio node and handles media lifecycle.
2828
*

src/points/audio-element.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ const getTimestampKey = (poi: POI) => {
2222
* Wraps the backing HTMLAudioElement used by the POI popup.
2323
*
2424
* Audio stack breakdown:
25-
* - POIController decides when popup audio should be shown for an active POI.
25+
* - POIPopupController decides when popup audio should be shown for an active POI.
2626
* - AudioController owns the custom player UI inside the popup.
2727
* - AudioElement wraps the backing HTML audio node and handles media lifecycle.
2828
*

src/points/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export * from "./POIs";
22
export * from "./POI-bounds-controller";
33
export * from "./POI-collision-controller";
4-
export * from "./POI-controller";
4+
export * from "./POI-marker-controller";
5+
export * from "./POI-popup-controller";
56
export * from "./POI-tracker";
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { type DivIcon, divIcon } from "leaflet";
2-
import type { POI } from "../../types";
2+
import type { POI } from "../types";
33

44
type IconConfiguration = {
55
POI: Pick<POI, "id">;

0 commit comments

Comments
 (0)