Skip to content

Commit

Permalink
Merge pull request #103 from armada-ths/feat/map
Browse files Browse the repository at this point in the history
Feat/map: Filter Page and Layout Update(booths, routes and symbol points)
  • Loading branch information
AmiyaSX authored Nov 7, 2024
2 parents 097160c + e56c8d0 commit 861ef00
Show file tree
Hide file tree
Showing 30 changed files with 4,628 additions and 200 deletions.
Binary file added public/map_icons/disability.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/map_icons/door.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/map_icons/exit.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/map_icons/stair.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/map_icons/wc.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/app/student/map/_components/LocationSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export default function SelectLocation({
setActiveBoothId: (id: number | null) => void
}) {
return (
<div className="absolute top-2 justify-self-center rounded-full sm:right-2">
<div className="absolute right-2 top-2 justify-self-center rounded-full">
<Select
value={locationId}
onValueChange={(id: LocationId) => {
Expand Down
53 changes: 47 additions & 6 deletions src/app/student/map/_components/MainView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@

import LocationSelect from "@/app/student/map/_components/LocationSelect"
import { MapComponent } from "@/app/student/map/_components/MapComponent"
import { QuestionnaireForm } from "@/app/student/map/_components/QuestionnaireForm"
import Sidebar from "@/app/student/map/_components/Sidebar"
import EditorMapComponent from "@/app/student/map/editor/EditorMapComponent"
import { Exhibitor } from "@/components/shared/hooks/api/useExhibitors"
import { useScreenSize } from "@/components/shared/hooks/useScreenSize"
import { useSurveyData } from "@/components/shared/hooks/useSurveyData"
import Modal from "@/components/shared/Modal"
import { Button } from "@/components/ui/button"
import { useSearchParams } from "next/navigation"
import { useState } from "react"
import { Filter } from "lucide-react"
import { useRouter, useSearchParams } from "next/navigation"
import { useEffect, useState } from "react"
import { Booth, BoothID, BoothMap } from "../lib/booths"
import {
defaultLocation,
Expand All @@ -25,12 +30,16 @@ export default function MainView({
boothsById: BoothMap
exhibitorsById: Map<number, Exhibitor>
}) {
// url: /student/map?floor=[nymble/1|nymble/2|nymble/3|library]&lat=[number]&lng=[number]&zoom=[number]
// if floor is not provided or is invalid, default to nymble/1
// url: /student/map?floor=[nymble/2|nymble/3|library]&lat=[number]&lng=[number]&zoom=[number]
// if floor is not provided or is invalid, default to nymble/2
// if lat, lng or zoom is not provided, default to location center

const searchParams = useSearchParams()
const { width } = useScreenSize()
const router = useRouter()
const { surveyData, isSurveyDataLoaded } = useSurveyData()

const floorUrlString = searchParams.get("floor") ?? "nymble/1"
const floorUrlString = searchParams.get("floor") ?? "nymble/2"
const [locationId, setLocationId] = useState<LocationId>(
validLocationId(floorUrlString) ? floorUrlString : defaultLocation.id
)
Expand All @@ -51,9 +60,27 @@ export default function MainView({
const [filteredBooths, setFilteredBooths] = useState<Booth[]>(
Array.from(boothsById.values())
)

const [openSurvey, setOpenSurvey] = useState(false)
const [editorMode, setEditorMode] = useState(false)

useEffect(() => {
if (isSurveyDataLoaded) setOpenSurvey(!surveyData)
}, [surveyData, isSurveyDataLoaded])

useEffect(() => {
// A new survey page for filter when using mobile
if (openSurvey && width && width < 768) {
router.push("/student/map/survey")
}
}, [width])

const handleClickFilter = () => {
setOpenSurvey(prev => !prev)
if (width && width < 768) {
router.push("/student/map/survey")
}
}

return (
<div className="relative flex h-full w-full">
{!editorMode ? (
Expand Down Expand Up @@ -96,6 +123,20 @@ export default function MainView({
</Button>
)}

{/* Questions Modal for filter when using PC*/}
<Modal
open={openSurvey}
setOpen={setOpenSurvey}
className="max-w-[780px] bg-gradient-to-br from-emerald-950 via-stone-900 to-stone-900 p-0">
<QuestionnaireForm onClose={() => setOpenSurvey(false)} />
</Modal>

<Button
className="absolute top-2 ml-2 justify-self-center rounded-full sm:right-2 sm:top-20"
onClick={handleClickFilter}>
<Filter />
</Button>

<LocationSelect
locationId={locationId}
setLocationId={setLocationId}
Expand Down
116 changes: 70 additions & 46 deletions src/app/student/map/_components/MapComponent.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { BoothPopup } from "@/app/student/map/_components/BoothPopup"
import {
BoothID,
geoJsonBoothDataByLocation,
geoJsonBuildingData
geoJsonBoothDataByLocation
} from "@/app/student/map/lib/booths"
import { Location } from "@/app/student/map/lib/locations"
import { useFeatureState } from "@/components/shared/hooks/useFeatureState"
import { useGeoJsonPlanData } from "@/components/shared/hooks/useGeoJsonPlanData"
import "maplibre-gl/dist/maplibre-gl.css"
import { MutableRefObject, useEffect, useMemo, useRef, useState } from "react"
import { useEffect, useMemo, useRef, useState } from "react"
import {
Layer,
MapLayerMouseEvent,
Expand All @@ -16,41 +17,17 @@ import {
} from "react-map-gl/maplibre"
import { BoothMap, GeoJsonBooth } from "../lib/booths"
import {
addMapIconAssets,
backgroundLayerStyle,
boothLayerStyle,
buildingLayerStyle
buildingLayerStyle,
lineLayerStyle,
roomLayerStyle,
routeLayerStyle,
symbolLayerStyle
} from "../lib/config"
import { BoothMarker } from "./BoothMarker"

// Keep mapbox feature state in sync with component state
// to allow for styling of the features
function useFeatureState(
mapRef: MutableRefObject<MapRef | null>,
boothIds: BoothID[],
stateKey: "active" | "hover" | "filtered"
) {
useEffect(() => {
const map = mapRef.current
if (map == null || boothIds.length === 0) return

for (const boothId of boothIds) {
map.setFeatureState(
{ source: "booths", id: boothId },
{ [stateKey]: true }
)
}

return () => {
for (const boothId of boothIds) {
map.setFeatureState(
{ source: "booths", id: boothId },
{ [stateKey]: false }
)
}
}
}, [boothIds, stateKey])
}

export function MapComponent({
boothsById,
location,
Expand Down Expand Up @@ -81,17 +58,28 @@ export function MapComponent({
center: [longitude, latitude],
zoom: zoom
})
})
setMarkerScale(0.6)
}, [location])

useEffect(() => {
// Load icon assets for points location
if (mapRef && !mapRef.current?.hasImage("exit-icon")) {
addMapIconAssets(mapRef)
}
}, [mapRef.current])

//Change layer style data source based on selected location
const [geoJsonPlanData, geoJsonPlanRoutesData, geoJsonPlanRoomsData] =
useGeoJsonPlanData(location)

// Fly to selected booth on change
useEffect(() => {
if (activeBoothId == null) return
const booth = boothsById.get(activeBoothId)
if (!booth) return

mapRef.current?.flyTo({
center: booth.center as [number, number],
zoom: 18.5,
zoom: 21,
speed: 0.8
})
}, [activeBoothId, boothsById])
Expand Down Expand Up @@ -123,10 +111,14 @@ export function MapComponent({
}
}

function onBoothMouseEnter(e: MapLayerMouseEvent) {
// Avoid delays in booth switching
function onBoothMouseMove(e: MapLayerMouseEvent) {
const feature = e.features?.[0] as GeoJsonBooth | undefined
if (feature) {
setHoveredBoothId(feature.properties.id)
const boothId = feature.properties.id
if (boothId !== hoveredBoothId) {
setHoveredBoothId(boothId)
}
}
}

Expand All @@ -140,7 +132,7 @@ export function MapComponent({
function onZoomChange() {
const zoom = mapRef.current?.getZoom()
if (zoom === undefined) return
const scale = Math.max(0.3, Math.min(2, 1 + (zoom - 18) * 0.3))
const scale = Math.max(0.3, Math.min(2, 1 + (zoom - 20) * 0.5))
setMarkerScale(scale)
}

Expand All @@ -149,21 +141,38 @@ export function MapComponent({
<MapboxMap
ref={mapRef}
onClick={onMapClick}
onMouseEnter={onBoothMouseEnter}
onMouseMove={onBoothMouseMove}
onMouseLeave={onBoothMouseLeave}
onZoom={onZoomChange}
interactiveLayerIds={["booths"]}
initialViewState={initialView}
cursor={"auto"}
minZoom={16}
maxZoom={20}
minZoom={17}
maxZoom={22}
maxBounds={[
[18.063, 59.345],
[18.079, 59.35]
]}
mapStyle="https://api.maptiler.com/maps/977e9770-60b4-4b8a-94e9-a9fa8db4c68d/style.json?key=57xj41WPFBbOEWiVSSwL">
<Layer {...backgroundLayerStyle}></Layer>

{/** Order sensitive! */}
<Source
id="buildings"
type="geojson"
promoteId={"id"}
data={geoJsonPlanData}>
<Layer {...buildingLayerStyle}></Layer>
</Source>

<Source
id="rooms"
type="geojson"
promoteId={"id"}
data={geoJsonPlanRoomsData}>
<Layer {...roomLayerStyle}></Layer>
</Source>

<Source
id="booths"
type="geojson"
Expand All @@ -173,15 +182,30 @@ export function MapComponent({
</Source>

<Source
id="buildings"
id="nymble-plan-style"
type="geojson"
promoteId={"id"}
data={geoJsonBuildingData}>
<Layer {...buildingLayerStyle}></Layer>
data={geoJsonPlanData}>
<Layer {...lineLayerStyle}></Layer>
</Source>

{markers}
<Source
id="nymble-plan-routes"
type="geojson"
promoteId={"id"}
data={geoJsonPlanRoutesData}>
<Layer {...routeLayerStyle}></Layer>
</Source>

<Source
id="nymble-plan-points"
type="geojson"
promoteId={"id"}
data={geoJsonPlanData}>
<Layer {...symbolLayerStyle}></Layer>
</Source>

{markers}
{activeBooth && <BoothPopup key={activeBooth.id} booth={activeBooth} />}
</MapboxMap>
</div>
Expand Down
Loading

0 comments on commit 861ef00

Please sign in to comment.