Skip to content

Commit

Permalink
Add compass
Browse files Browse the repository at this point in the history
  • Loading branch information
chunlaw committed Nov 14, 2023
1 parent a7c1c09 commit 23298f6
Show file tree
Hide file tree
Showing 9 changed files with 2,216 additions and 1,363 deletions.
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"i18next": "^21.8.10",
"immer": "^9.0.15",
"leaflet": "^1.7.1",
"leaflet-rotatedmarker": "^0.2.0",
"lodash.debounce": "^4.0.8",
"lodash.throttle": "^4.1.1",
"lzutf8-light": "^0.6.2",
Expand All @@ -32,12 +33,12 @@
"react-leaflet": "4.0.0",
"react-markdown": "^8.0.7",
"react-router-dom": "^6.3.0",
"react-scripts": "5.0.1",
"react-select": "^5.3.2",
"react-select-search": "^3.0.5",
"react-swipeable-views": "^0.14.0",
"react-virtualized-auto-sizer": "^1.0.5",
"react-window": "^1.8.6",
"react-world-compass": "^1.1.1",
"remark-gfm": "^3.0.1",
"string-similarity": "^4.0.4",
"web-vitals": "^2.1.4",
Expand All @@ -63,7 +64,7 @@
"render": "node scripts/pre-rendering.js",
"test": "react-scripts test",
"eject": "react-scripts eject",
"predeploy": "yarn build && node scripts/sitemap-generator.js && node scripts/pre-rendering.js",

"deploy": "gh-pages -d build -t",
"alpha-deploy": "yarn build && gh-pages -d build --remote alpha -t -f",
"docker": "cd docker && docker-compose up -d --build",
Expand Down Expand Up @@ -106,6 +107,7 @@
"express": "^4.18.1",
"prettier": "2.7.1",
"puppeteer": "^14.4.1",
"react-scripts": "^5.0.1",
"source-map-explorer": "^2.5.2",
"typescript": "^4.7.4"
},
Expand Down
1 change: 1 addition & 0 deletions public/self.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 29 additions & 0 deletions src/AppContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
Language,
NumPadOrder,
} from "./data";
import { DeviceOrientationPermission } from "react-world-compass";

type GeoPermission = "opening" | "granted" | "denied" | "closed" | null;

Expand All @@ -31,6 +32,7 @@ export interface AppState {
selectedRoute: string;
geoPermission: GeoPermission;
geolocation: GeoLocation;
compassPermission: DeviceOrientationPermission;
/**
* route search history
*/
Expand Down Expand Up @@ -95,6 +97,7 @@ interface AppContextValue
updateSearchRouteByButton: (buttonValue: string) => void;
updateSelectedRoute: (route: string, seq?: string) => void;
// UX
setCompassPermission: (permission: DeviceOrientationPermission) => void;
updateGeolocation: (geoLocation: GeoLocation) => void;
addSearchHistory: (routeSearchHistory: string) => void;
removeSearchHistoryByRouteId: (routeSearchHistoryId: string) => void;
Expand Down Expand Up @@ -146,6 +149,12 @@ const isGeoLocation = (input: unknown): input is GeoLocation => {
return false;
};

const isCompassPermission = (
input: unknown
): input is DeviceOrientationPermission => {
return input === "granted" || input === "denied" || input === "default";
};

export const isBusSortOrder = (input: unknown): input is BusSortOrder => {
return input === "KMB first" || input === "CTB first";
};
Expand Down Expand Up @@ -178,6 +187,8 @@ export const AppContextProvider = ({
const geoLocation: unknown = JSON.parse(
localStorage.getItem("geolocation")
);
const compassPermission: unknown =
localStorage.getItem("compassPermission");
const busSortOrder: unknown = localStorage.getItem("busSortOrder");
const numPadOrder: unknown = localStorage.getItem("numPadOrder");
const etaFormat: unknown = localStorage.getItem("etaFormat");
Expand All @@ -192,6 +203,9 @@ export const AppContextProvider = ({
geolocation: isGeoLocation(geoLocation)
? geoLocation
: defaultGeolocation,
compassPermission: isCompassPermission(compassPermission)
? compassPermission
: "default",
isRouteFilter:
!!JSON.parse(localStorage.getItem("isRouteFilter")) || false,
busSortOrder: isBusSortOrder(busSortOrder) ? busSortOrder : "KMB first",
Expand Down Expand Up @@ -313,6 +327,15 @@ export const AppContextProvider = ({
[setGeoPermission, updateGeolocation]
);

const setCompassPermission = useCallback(
(compassPermission: AppState["compassPermission"]) => {
setState((state) => {
state.compassPermission = compassPermission;
});
},
[setState]
);

const toggleRouteFilter = useCallback(() => {
setStateRaw(
produce((state: State) => {
Expand Down Expand Up @@ -542,6 +565,10 @@ export const AppContextProvider = ({
localStorage.setItem("geoPermission", state.geoPermission);
}, [state.geoPermission]);

useEffect(() => {
localStorage.setItem("compassPermission", state.compassPermission);
}, [state.compassPermission]);

useEffect(() => {
localStorage.setItem("isRouteFilter", JSON.stringify(state.isRouteFilter));
}, [state.isRouteFilter]);
Expand Down Expand Up @@ -622,6 +649,7 @@ export const AppContextProvider = ({
setSearchRoute,
updateSearchRouteByButton,
updateSelectedRoute,
setCompassPermission,
updateGeolocation,
addSearchHistory,
removeSearchHistoryByRouteId,
Expand Down Expand Up @@ -651,6 +679,7 @@ export const AppContextProvider = ({
setSearchRoute,
updateSearchRouteByButton,
updateSelectedRoute,
setCompassPermission,
updateGeolocation,
addSearchHistory,
removeSearchHistoryByRouteId,
Expand Down
44 changes: 44 additions & 0 deletions src/components/map/CompassControl.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Box, SxProps, Theme } from "@mui/material";
import { Explore as ExploreIcon } from "@mui/icons-material";
import { isSafari, requestPermission } from "react-world-compass";
import { useCallback, useContext } from "react";
import AppContext from "../../AppContext";

const CompassControl = () => {
const { compassPermission, setCompassPermission } = useContext(AppContext);
const handleClick = useCallback(() => {
requestPermission().then((r) => {
// @ts-ignore
setCompassPermission(r);
});
}, [setCompassPermission]);

if (!isSafari || compassPermission === "granted") {
return <></>;
}

return (
<div className="leaflet-bottom leaflet-right">
<Box
sx={compassControlSx}
className="leaflet-control leaflet-bar"
onClick={handleClick}
>
<ExploreIcon sx={{ p: "3px", color: "black" }} />
</Box>
</div>
);
};

export default CompassControl;

const compassControlSx: SxProps<Theme> = {
background: "white",
width: 32,
height: 32,
marginBottom: "57px !important",
marginRight: "5px !important",
display: "flex",
justifyContent: "center",
alignItems: "center",
};
105 changes: 105 additions & 0 deletions src/components/map/SelfCircle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import {
forwardRef,
useContext,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import AppContext from "../../AppContext";
import { checkPosition } from "../../utils";
import { Circle, Marker } from "react-leaflet";
import L from "leaflet";
import useCompass, { OrientationState } from "react-world-compass";
import "leaflet-rotatedmarker";

interface RotatedMarkerType<T> extends L.Marker<T> {
setRotationAngle: (angle: number) => void;
setRotationOrigin: (origin: string) => void;
}

const RotatedMarker = forwardRef<RotatedMarkerType<any>, any>(
({ children, ...props }, forwardRef) => {
const markerRef = useRef<RotatedMarkerType<any>>(null);

const _compass = useCompass(100);
const [compass, setCompass] = useState<OrientationState | null>(null);
useEffect(() => {
const elf = (nativeEvent: any) => {
try {
const data = JSON.parse(nativeEvent.data);
if (data?.type === "compass") {
setCompass(data.compass);
}
} catch (e) {
console.log(e);
}
};
window.addEventListener("message", elf);
return () => {
window.removeEventListener("message", elf);
};
}, []);

useEffect(() => {
setCompass(_compass);
}, [_compass]);

useEffect(() => {
const marker = markerRef.current;
if (marker && compass) {
marker.setRotationAngle(360 - compass.degree);
marker.setRotationOrigin("center");
}
}, [compass]);

if (compass === null) {
return <></>;
}

return (
<Marker
ref={(ref) => {
markerRef.current = ref as RotatedMarkerType<any>;
if (forwardRef) {
// @ts-ignore
forwardRef.current = ref as RotatedMarkerType<any>;
}
}}
{...props}
>
{children}
</Marker>
);
}
);

const SelfCircle = () => {
const { geolocation, geoPermission } = useContext(AppContext);
const icon = useMemo(() => myIcon(), []);

if (geoPermission !== "granted") {
return null;
}

return (
<>
<Circle center={checkPosition(geolocation)} radius={25} />
<RotatedMarker
rotationOrigin="center"
icon={icon}
position={checkPosition(geolocation)}
/>
</>
);
};

export default SelfCircle;

const myIcon = () => {
return L.divIcon({
iconSize: [20, 20],
iconAnchor: [10, 10],
className: "self-center",
});
};
29 changes: 13 additions & 16 deletions src/components/route-eta/RouteMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,19 @@ import {
useMemo,
useState,
} from "react";
import {
MapContainer,
Marker,
TileLayer,
Polyline,
Circle,
} from "react-leaflet";
import { MapContainer, Marker, TileLayer, Polyline } from "react-leaflet";
import Leaflet from "leaflet";
import markerIcon2X from "leaflet/dist/images/marker-icon-2x.png";
import { Box, SxProps, Theme } from "@mui/material";
import { useTranslation } from "react-i18next";
import AppContext from "../../AppContext";
import type { StopListEntry } from "hk-bus-eta";
import MyLocationIcon from "@mui/icons-material/MyLocation";
import { MyLocation as MyLocationIcon } from "@mui/icons-material";
import { checkPosition, locationEqual } from "../../utils";
import type { Map as LeafletMap } from "leaflet";
import type { Location as GeoLocation } from "hk-bus-eta";

const SelfCircle = () => {
const { geolocation, geoPermission } = useContext(AppContext);
if (geoPermission !== "granted") {
return null;
}
return <Circle center={checkPosition(geolocation)} radius={25} />;
};
import SelfCircle from "../map/SelfCircle";
import CompassControl from "../map/CompassControl";

const CenterControl = ({ onClick }) => {
return (
Expand Down Expand Up @@ -234,6 +222,7 @@ const RouteMap = ({ stops, stopIdx, onMarkerClick }: RouteMapProps) => {
{lines}
<SelfCircle />
<CenterControl onClick={onClickJumpToMyLocation} />
<CompassControl />
</MapContainer>
</Box>
);
Expand Down Expand Up @@ -286,6 +275,14 @@ const rootSx: SxProps<Theme> = {
[`& .${classes.passed}`]: {
filter: "grayscale(100%)",
},
[`& .self-center`]: {
backgroundImage: "url(/self.svg)",
backgroundSize: "contain",
backgroundRepeat: "no-repeat",
backgroundPosition: "center",
transition: "transform 0.1s ease-out",
transformOrigin: "center",
},
};

const centerControlSx: SxProps<Theme> = {
Expand Down
Loading

0 comments on commit 23298f6

Please sign in to comment.