From 912d326442a69bef320d24d8356752a93f88cb7a Mon Sep 17 00:00:00 2001 From: "Jihun (James) Doh" Date: Sat, 5 Oct 2024 00:42:22 -0400 Subject: [PATCH] basic map functionality implementation using google maps api, hardcoded geocoordinates --- frontend/plan/components/modals/MapModal.tsx | 96 +++++++++++++++++++ .../modals/generic_modal_container.tsx | 1 - .../modals/model_content_generator.js | 7 ++ frontend/plan/components/selector/Section.tsx | 20 ++-- .../plan/components/selector/SectionList.tsx | 55 +++++++++-- frontend/plan/package.json | 1 + frontend/plan/types.ts | 5 + 7 files changed, 165 insertions(+), 20 deletions(-) create mode 100644 frontend/plan/components/modals/MapModal.tsx diff --git a/frontend/plan/components/modals/MapModal.tsx b/frontend/plan/components/modals/MapModal.tsx new file mode 100644 index 000000000..f92b83cd6 --- /dev/null +++ b/frontend/plan/components/modals/MapModal.tsx @@ -0,0 +1,96 @@ +import React, { useState, useEffect } from "react"; +import { GeoLocation } from "../../types"; +import { + APIProvider, + Map, + Marker, + useMap, + useMapsLibrary, +} from "@vis.gl/react-google-maps"; + +interface MapModalProps { + src: GeoLocation; + tgt?: GeoLocation; +} + +interface DirectionsProps { + src: GeoLocation; + tgt: GeoLocation; +} + +const MapModal = ({ src, tgt }: MapModalProps) => { + const { latitude: centerLat, longitude: centerLng } = tgt + ? { + latitude: (src.latitude + tgt.latitude) / 2, + longitude: (src.longitude + tgt.longitude) / 2, + } + : src; + + return ( + + + {tgt ? ( + + ) : ( + + )} + + + ); +}; + +function Directions({ src, tgt }: DirectionsProps) { + const map = useMap(); + const routesLibrary = useMapsLibrary("routes"); + const [directionsService, setDirectionsService] = useState< + google.maps.DirectionsService + >(); + const [directionsRenderer, setDirectionsRenderer] = useState< + google.maps.DirectionsRenderer + >(); + const [routes, setRoutes] = useState([]); + + const leg = routes[0]?.legs[0]; + + useEffect(() => { + if (!routesLibrary || !map) return; + setDirectionsService(new routesLibrary.DirectionsService()); + setDirectionsRenderer(new routesLibrary.DirectionsRenderer({ map })); + }, [routesLibrary, map]); + + useEffect(() => { + if (!directionsService || !directionsRenderer) return; + + directionsService + .route({ + origin: `${src.latitude}, ${src.longitude}`, + destination: `${tgt.latitude}, ${tgt.longitude}`, + travelMode: google.maps.TravelMode.WALKING, + provideRouteAlternatives: false, + }) + .then((response) => { + directionsRenderer.setDirections(response); + setRoutes(response.routes); + }); + }, [directionsService, directionsRenderer]); + + if (!leg) return null; + + return ( +
+

{leg.duration?.text}

+
+ ); +} + +export default MapModal; diff --git a/frontend/plan/components/modals/generic_modal_container.tsx b/frontend/plan/components/modals/generic_modal_container.tsx index b943bab3f..bccb90bfa 100644 --- a/frontend/plan/components/modals/generic_modal_container.tsx +++ b/frontend/plan/components/modals/generic_modal_container.tsx @@ -124,7 +124,6 @@ const ModalCardBody = styled.section` flex-grow: 1; flex-shrink: 1; overflow: auto; - padding: 20px; padding-left: 2rem; padding-right: 2rem; padding-bottom: 1.5rem; diff --git a/frontend/plan/components/modals/model_content_generator.js b/frontend/plan/components/modals/model_content_generator.js index 16da29e92..7707db56a 100644 --- a/frontend/plan/components/modals/model_content_generator.js +++ b/frontend/plan/components/modals/model_content_generator.js @@ -1,4 +1,5 @@ import React from "react"; +import dynamic from "next/dynamic"; import { renameSchedule, downloadSchedule, @@ -28,6 +29,10 @@ import AlertFormModal from "./AlertFormModal"; * @returns A component */ export const generateModalInterior = (reduxState) => { + const Map = dynamic(() => import("./MapModal"), { + ssr: false, + }); + switch (reduxState.modals.modalKey) { case "SEMESTER_FETCH_ERROR": return ( @@ -102,6 +107,8 @@ export const generateModalInterior = (reduxState) => { return ( ); + case "MAP": + return ; default: return null; } diff --git a/frontend/plan/components/selector/Section.tsx b/frontend/plan/components/selector/Section.tsx index 847883c31..5b0e5dc2c 100644 --- a/frontend/plan/components/selector/Section.tsx +++ b/frontend/plan/components/selector/Section.tsx @@ -15,6 +15,9 @@ interface SectionProps { add: () => void; remove: () => void; }; + toggleMap: { + open: () => void; + }; inCart: boolean; alerts: { add: () => void; @@ -130,7 +133,7 @@ const HoverSwitch = styled.div` } `; -export default function Section({ section, cart, inCart, alerts, inAlerts }: SectionProps) { +export default function Section({ section, cart, inCart, toggleMap, alerts, inAlerts }: SectionProps) { const { instructors, meetings, status } = section; const { schedules, scheduleSelected } = useSelector( @@ -225,7 +228,9 @@ export default function Section({ section, cart, inCart, alerts, inAlerts }: Sec style={{ color: "#c6c6c6" }} />   - {cleanedRooms.join(", ")} + + {cleanedRooms.join(", ")} + ) : null} @@ -246,17 +251,14 @@ export default function Section({ section, cart, inCart, alerts, inAlerts }: Sec {status === "C" ? (
- - - {inAlerts || + + + {inAlerts || ( {" "} Course is closed. Sign up for an alert!{" "} - } + )}
) : (
diff --git a/frontend/plan/components/selector/SectionList.tsx b/frontend/plan/components/selector/SectionList.tsx index fbaa143cc..9c8e512cf 100644 --- a/frontend/plan/components/selector/SectionList.tsx +++ b/frontend/plan/components/selector/SectionList.tsx @@ -3,7 +3,13 @@ import styled from "styled-components"; import { connect } from "react-redux"; import Section from "./Section"; import { Section as SectionType, Alert as AlertType } from "../../types"; -import { addCartItem, openModal, deleteAlertItem, removeCartItem, deactivateAlertItem } from "../../actions"; +import { + addCartItem, + openModal, + deleteAlertItem, + removeCartItem, + deactivateAlertItem, +} from "../../actions"; interface SectionListProps { sections: SectionType[]; @@ -22,8 +28,9 @@ interface SectionListDispatchProps { ) => { add: () => void; remove: () => void }; manageAlerts: ( section: SectionType, - alerts: AlertType[], + alerts: AlertType[] ) => { add: () => void; remove: () => void }; + toggleMap: (section: SectionType) => { open: () => void }; onContactInfoChange: (email: string, phone: string) => void; } @@ -37,15 +44,18 @@ function SectionList({ sections, cartSections, manageCart, + toggleMap, alerts, manageAlerts, view, }: SectionListProps & SectionListStateProps & SectionListDispatchProps) { const isInCart = ({ id }: SectionType) => cartSections.indexOf(id) !== -1; - const isInAlerts = ({ id }: SectionType) => alerts - .filter((alert: AlertType) => alert.cancelled === false) - .map((alert: AlertType) => alert.section) - .indexOf(id) !== -1; + const isInAlerts = ({ id }: SectionType) => + alerts + .filter((alert: AlertType) => alert.cancelled === false) + .map((alert: AlertType) => alert.section) + .indexOf(id) !== -1; + console.log(sections); return (
    @@ -55,6 +65,7 @@ function SectionList({ section={s} cart={manageCart(s)} inCart={isInCart(s)} + toggleMap={toggleMap(s)} alerts={manageAlerts(s, alerts)} inAlerts={isInAlerts(s)} /> @@ -77,13 +88,37 @@ const mapDispatchToProps = (dispatch: (payload: any) => void) => ({ }), manageAlerts: (section: SectionType, alerts: AlertType[]) => ({ add: () => { - const alertId = alerts.find((a: AlertType) => a.section === section.id)?.id; - dispatch(openModal("ALERT_FORM", { sectionId: section.id, alertId: alertId }, "Sign up for Alerts")) + const alertId = alerts.find( + (a: AlertType) => a.section === section.id + )?.id; + dispatch( + openModal( + "ALERT_FORM", + { sectionId: section.id, alertId: alertId }, + "Sign up for Alerts" + ) + ); }, remove: () => { - const alertId = alerts.find((a: AlertType) => a.section === section.id)?.id; + const alertId = alerts.find( + (a: AlertType) => a.section === section.id + )?.id; dispatch(deactivateAlertItem(section.id, alertId)); - } + }, + }), + toggleMap: (section: SectionType) => ({ + open: () => { + dispatch( + openModal( + "MAP", + { + src: { latitude: 39.95302, longitude: -75.19815 }, + // tgt: { latitude: 39.95184, longitude: -75.19092 }, + }, // hard-coded for now + `LOCATION FOR ${section.id}` + ) + ); + }, }), }); diff --git a/frontend/plan/package.json b/frontend/plan/package.json index 13705ea0d..dd3c53fa3 100644 --- a/frontend/plan/package.json +++ b/frontend/plan/package.json @@ -19,6 +19,7 @@ "@types/react-dom": "^18.2.0", "@types/react-swipeable-views": "^0.13.0", "@typescript-eslint/parser": "^4.7.0", + "@vis.gl/react-google-maps": "^1.3.0", "awesome-debounce-promise": "^2.1.0", "bulma": "^0.7.5", "bulma-extensions": "^6.2.7", diff --git a/frontend/plan/types.ts b/frontend/plan/types.ts index abf2c0ffc..52dd28607 100644 --- a/frontend/plan/types.ts +++ b/frontend/plan/types.ts @@ -227,4 +227,9 @@ export type FilterType = export interface ColorsMap { [key: string]: Color + } + + export type GeoLocation = { + latitude: number; + longitude: number; } \ No newline at end of file