-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
krasch
committed
Dec 20, 2024
1 parent
6be7612
commit ec22122
Showing
9 changed files
with
492 additions
and
859 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,241 @@ | ||
function initHomeMarker(id, lngLat) { | ||
const element = createElementFromTemplate("template-city-marker-home", { | ||
$root$: { "data-city-id": id }, | ||
}); | ||
|
||
const marker = new maplibregl.Marker({ | ||
element: element, | ||
anchor: "bottom", | ||
}); | ||
marker.setLngLat(lngLat); | ||
return marker; | ||
} | ||
|
||
function initDestinationMarker(lngLat) { | ||
const marker = new maplibregl.Marker({ | ||
element: createElementFromTemplate("template-city-marker-destination"), | ||
anchor: "bottom", | ||
}); | ||
marker.setLngLat(lngLat); | ||
return marker; | ||
} | ||
|
||
function initCityMenu(id, name, lngLat) { | ||
const element = createElementFromTemplate("template-city-menu", { | ||
$root$: { "data-city-id": id, "data-city-name": name }, | ||
".title": { innerText: name }, | ||
}); | ||
|
||
const popup = new maplibregl.Popup({ | ||
anchor: "left", | ||
offset: [5, 0], | ||
closeButton: true, | ||
}); | ||
popup.setDOMContent(element).setLngLat(lngLat); | ||
|
||
const buttonShowRoutes = element.querySelector("button[value='showRoutes']"); | ||
const buttonMakeCut = element.querySelector("button[value='makeCut']"); | ||
|
||
popup.updateElement = (state) => { | ||
if (state.isDestination !== undefined) | ||
updateVisibility(buttonShowRoutes, state.isDestination); | ||
}; | ||
|
||
return popup; | ||
} | ||
|
||
function showStartAnimation(map, geo, initialState, animationDoneCallback) { | ||
const homeMarkers = []; | ||
const destinationMarkers = []; | ||
|
||
for (let id in initialState) { | ||
if (initialState[id].isHome) | ||
homeMarkers.push(initHomeMarker(id, geo[id].lngLat)); | ||
if (initialState[id].isDestination) | ||
destinationMarkers.push(initDestinationMarker(geo[id].lngLat)); | ||
} | ||
|
||
//this is the second animation we'll do (show destination markers dropping) | ||
const animateDestinations = () => | ||
animateDropWithBounce( | ||
map, | ||
destinationMarkers, | ||
200, | ||
3, | ||
() => animationDoneCallback(destinationMarkers), // when animation is done callback to main | ||
); | ||
|
||
// run the first animation (show home marker(s) dropping) | ||
animateDropWithBounce( | ||
map, | ||
homeMarkers, | ||
300, | ||
3, | ||
animateDestinations, // when that is done do the second animation | ||
); | ||
} | ||
|
||
ANIMATION = true; | ||
|
||
class Cities { | ||
#callbacks = { | ||
mouseOver: () => {}, | ||
mouseLeave: () => {}, | ||
click: () => {}, | ||
menuClick: () => {}, | ||
}; | ||
|
||
#source = "cities"; | ||
#layers = ["city-name", "city-circle-interact"]; | ||
|
||
#keys = { | ||
featureState: [ | ||
"hover", | ||
"isVisible", | ||
"isDestination", | ||
"isStop", | ||
"circleColor", | ||
], | ||
cityMenu: ["isDestination"], | ||
sourceData: ["rank", "isVisible"], // slow to update | ||
}; | ||
|
||
#map; | ||
#geo; // {id: {name: , lngLat: }} | ||
|
||
#state; | ||
#homeMarkers = {}; | ||
#menus = {}; | ||
|
||
#pulsars = null; | ||
|
||
constructor(map, geo, initialState) { | ||
this.#map = map; | ||
this.#geo = geo; | ||
|
||
this.#state = new StateDict(); | ||
const events = new MouseEventHelper(this.#map, this.#layers); | ||
|
||
events.on("mouseOver", (id, lngLat) => { | ||
this.#map.getCanvas().style.cursor = "pointer"; | ||
this.#stopAnimation(); | ||
this.setHover(id, true); | ||
this.#callbacks["mouseOver"](id); | ||
}); | ||
|
||
events.on("mouseLeave", (id, lngLat) => { | ||
this.#map.getCanvas().style.cursor = "default"; | ||
this.setHover(id, false); | ||
this.#callbacks["mouseLeave"](id); | ||
}); | ||
|
||
events.on("click", (id, lngLat) => { | ||
this.#showCityMenu(id); | ||
this.#callbacks["click"](id); | ||
}); | ||
|
||
this.#map._container.addEventListener("click", (e) => { | ||
// menu item was clicked | ||
if (e.target.tagName === "BUTTON") { | ||
const menu = e.target.parentElement.parentElement; | ||
const id = menu.dataset.cityId; | ||
const name = menu.dataset.cityName; | ||
this.#hideCityMenu(id); | ||
this.#callbacks["menuClick"](id, name, e.target.value); | ||
} | ||
// home marker was clicked | ||
else if (e.target.classList.contains("city-marker-home")) { | ||
const id = e.target.dataset.cityId; | ||
this.#showCityMenu(id); | ||
this.#callbacks["click"](id); | ||
} | ||
}); | ||
|
||
// initial drawing | ||
if (ANIMATION) { | ||
showStartAnimation(this.#map, geo, initialState, (pulsars) => { | ||
this.#pulsars = pulsars; | ||
this.update(initialState); | ||
}); | ||
} else { | ||
this.update(initialState); | ||
} | ||
} | ||
|
||
on(eventName, callback) { | ||
this.#callbacks[eventName] = callback; | ||
} | ||
|
||
update(updates) { | ||
// apply update to the state | ||
// changes contains the "true" changes, i.e. things that actually changed | ||
const changes = this.#state.update(updates); | ||
|
||
this.#updateFeatureState(changes); | ||
this.#updateSourceData(changes); | ||
|
||
for (let change of changes) { | ||
if (change.key === "isHome") | ||
this.#updateHomeMarker(change.id, change.value); | ||
} | ||
} | ||
|
||
setHover(id, state) { | ||
this.#state.set(id, "hover", state); | ||
this.#copyStateToFeatureState(id); | ||
} | ||
|
||
#updateFeatureState(changes) { | ||
const filtered = filterChanges(changes, this.#keys.featureState); | ||
const grouped = groupChangesById(filtered); | ||
|
||
for (let id in grouped) this.#copyStateToFeatureState(id); | ||
} | ||
|
||
#updateSourceData(changes) { | ||
const filtered = filterChanges(changes, this.#keys.sourceData); | ||
const grouped = groupChangesById(filtered); | ||
updateSourceData(this.#map, this.#source, grouped); | ||
} | ||
|
||
#updateHomeMarker(id, isHome) { | ||
if (isHome && !this.#homeMarkers[id]) { | ||
this.#homeMarkers[id] = initHomeMarker(id, this.#geo[id].lngLat); | ||
this.#homeMarkers[id].addTo(this.#map); | ||
} | ||
|
||
if (!isHome && this.#homeMarkers[id]) { | ||
this.#homeMarkers[id].remove(); | ||
delete this.#homeMarkers[id]; | ||
} | ||
} | ||
|
||
#copyStateToFeatureState(id) { | ||
const current = this.#state.getAll(id, this.#keys.featureState); | ||
this.#map.setFeatureState({ source: this.#source, id: id }, current); | ||
} | ||
|
||
#showCityMenu(id) { | ||
if (!this.#menus[id]) { | ||
this.#menus[id] = initCityMenu( | ||
id, | ||
this.#geo[id].name, | ||
this.#geo[id].lngLat, | ||
); | ||
} | ||
|
||
const current = this.#state.getAll(id, this.#keys.cityMenu); | ||
this.#menus[id].updateElement(current); | ||
this.#menus[id].addTo(this.#map); | ||
} | ||
|
||
#hideCityMenu(id) { | ||
this.#menus[id].remove(); | ||
} | ||
|
||
#stopAnimation() { | ||
if (this.#pulsars) { | ||
for (let p of this.#pulsars) p.remove(); | ||
} | ||
} | ||
} |
Oops, something went wrong.