Clustering (#75)

* Clustering

* Add package

* Shift markers based on the time control to keep they separated visually
This commit is contained in:
Timothy Armes
2023-07-06 18:11:10 +02:00
committed by GitHub
parent 8318265c87
commit 0a2dfa8743
7 changed files with 339 additions and 108 deletions
+27 -59
View File
@@ -1,77 +1,43 @@
"use client";
import { useEffect, useMemo, useRef } from "react";
import { Tournament } from "@/types";
import L, { LatLngLiteral, DomUtil } from "leaflet";
import { forwardRef, useMemo, useRef } from "react";
import { TimeControl, Tournament } from "@/types";
import L from "leaflet";
import { Marker, Popup, MarkerProps } from "react-leaflet";
import { useTranslations } from "next-intl";
import { useAtomValue, useSetAtom } from "jotai";
import "leaflet.smooth_marker_bouncing";
import { useSetAtom } from "jotai";
import {
debouncedHoveredListTournamentIdAtom,
debouncedHoveredMapTournamentIdAtom,
} from "@/app/atoms";
const coordinateRandomisation = (latLng: LatLngLiteral): LatLngLiteral => {
const randomisation = () => Math.random() * (-0.01 - 0.01) + 0.01;
return {
lat: latLng.lat + randomisation(),
lng: latLng.lng + randomisation(),
};
};
import { debouncedHoveredMapTournamentIdAtom } from "@/app/atoms";
type TournamentMarkerProps = {
tournament: Tournament;
colour: string;
} & Omit<MarkerProps, "position">;
export const TournamentMarker = ({
tournament,
colour,
...markerProps
}: TournamentMarkerProps) => {
export const TournamentMarker = forwardRef<
L.Marker<any> | null,
TournamentMarkerProps
>(({ tournament, colour, ...markerProps }, ref) => {
const t = useTranslations("Tournaments");
const markerRef = useRef<L.Marker<any> | null>(null);
const position = useRef(coordinateRandomisation(tournament.latLng));
const hoveredListTournamentId = useAtomValue(
debouncedHoveredListTournamentIdAtom
);
// We add shifts based on the time control, so that they don't hide each other
const position = useRef({
lat: tournament.latLng.lat,
lng:
tournament.latLng.lng +
(tournament.timeControl === TimeControl.Rapid
? -0.01
: tournament.timeControl === TimeControl.Blitz
? 0.01
: tournament.timeControl === TimeControl.KO
? 0.02
: 0),
});
const setHoveredMapTournamentId = useSetAtom(
debouncedHoveredMapTournamentIdAtom
);
useEffect(() => {
if (!markerRef.current) return;
if (hoveredListTournamentId === tournament._id) {
// @ts-ignore (the various bounce commands come from leaflet.smooth_marker_bouncing and aren't defined by the Typescript definitions)
markerRef.current.setBouncingOptions({ exclusive: true });
// @ts-ignore
markerRef.current.bounce();
} else {
// @ts-ignore
if (markerRef.current.isBouncing()) {
// @ts-ignore
markerRef.current.stopBouncing();
// The plugin keeps bouncing until the end of the animation. We want to stop it immediately
// An issue has been raised on the project (https://github.com/hosuaby/Leaflet.SmoothMarkerBouncing/issues/52), until then we have this hack.
// We remove the class and reset some internal state
// @ts-ignore
DomUtil.removeClass(markerRef.current._icon, "bouncing");
if (
// @ts-ignore
markerRef.current?._bouncingMotion?.bouncingAnimationPlaying === true
)
// @ts-ignore
markerRef.current._bouncingMotion.bouncingAnimationPlaying = false;
}
}
}, [hoveredListTournamentId, tournament._id]);
const iconOptions = useMemo(
() =>
new L.Icon({
@@ -87,7 +53,7 @@ export const TournamentMarker = ({
return (
<Marker
ref={markerRef}
ref={ref}
position={position.current}
icon={iconOptions}
eventHandlers={{
@@ -108,4 +74,6 @@ export const TournamentMarker = ({
</Popup>
</Marker>
);
};
});
TournamentMarker.displayName = "TournamentMarker";