Skip to content

Commit

Permalink
Merge pull request #110 from armada-ths/feat/map
Browse files Browse the repository at this point in the history
Feat/map
  • Loading branch information
AmiyaSX authored Nov 13, 2024
2 parents 6c783dd + 7ae6ba5 commit a32e2fd
Show file tree
Hide file tree
Showing 26 changed files with 3,966 additions and 875 deletions.
4 changes: 3 additions & 1 deletion src/app/student/map/_components/BoothMarker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ export function BoothMarker({ booth, scale }: { booth: Booth; scale: number }) {
height={300}></Image>
</div>
) : (
<span className="text-black">{booth.exhibitor.name}</span>
<div className="max-w-[100px] truncate text-neutral-200">
{booth.exhibitor.name}
</div>
)}
</div>
</Marker>
Expand Down
17 changes: 0 additions & 17 deletions src/app/student/map/_components/BoothPopup.tsx

This file was deleted.

64 changes: 64 additions & 0 deletions src/app/student/map/_components/FilterSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"use client"

import { Filter, FilterItem } from "@/app/student/lib/filters"
import { FilterSelectionItem } from "@/app/student/map/_components/FilterSelectionItem"
import { useState } from "react"

export default function FilterSection({
filter,
onChange
}: {
filter: Filter
onChange: (selected: FilterItem[]) => void
}) {
const { items, selected, label } = filter

const maxDisplayed = 7
const [numDisplayed, setNumDisplayed] = useState(maxDisplayed)

function isSelected(item: FilterItem) {
return selected.some(s => s.id === item.id)
}

function onSelectionChange(item: FilterItem) {
if (isSelected(item)) {
onChange(selected.filter(s => s.id !== item.id)) // remove item
} else {
onChange([...selected, item]) // add item
}
}

return (
<div className="flex flex-col justify-between">
<div>
<h2 className="m-2 text-left text-xl text-stone-200">{label}</h2>
<div className="m-1 mt-3 flex flex-wrap gap-4">
{items.slice(0, numDisplayed).map(item => (
<div className="" key={item.id}>
<FilterSelectionItem
name={item.name}
isSelected={filter.selected.includes(item)}
onClick={() => onSelectionChange(item)}
/>
</div>
))}
{maxDisplayed < items.length && (
<button
onClick={() =>
setNumDisplayed(
numDisplayed < items.length ? items.length : maxDisplayed
)
}
className="flex w-auto rounded-3xl border border-neutral-400 px-3 py-2 text-center text-xs text-neutral-400 xs:px-4 xs:py-2 xs:text-base">
{numDisplayed < items.length ? (
<span>Show all {items.length}</span>
) : (
<span>Show less</span>
)}
</button>
)}
</div>
</div>
</div>
)
}
21 changes: 21 additions & 0 deletions src/app/student/map/_components/FilterSelectionItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export function FilterSelectionItem({
name,
isSelected,
onClick
}: {
name: string
isSelected: boolean
onClick: () => void
}) {
return (
<div
className={`flex w-auto cursor-pointer text-ellipsis whitespace-nowrap rounded-3xl border px-3 py-2 text-center transition xs:px-4 xs:py-2 ${
isSelected
? "border-1 border-melon-700 text-melon-700 shadow-md shadow-melon-700/30"
: "border-emerald-700 text-emerald-700"
}`}
onClick={onClick}>
<span className="text-xs xs:text-base">{name}</span>
</div>
)
}
61 changes: 17 additions & 44 deletions src/app/student/map/_components/MainView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,12 @@

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 { Filter } from "lucide-react"
import { useRouter, useSearchParams } from "next/navigation"
import { useEffect, useState } from "react"
import { useSearchParams } from "next/navigation"
import { useMemo, useState } from "react"
import { Booth, BoothID, BoothMap } from "../lib/booths"
import {
defaultLocation,
Expand All @@ -35,16 +30,27 @@ export default function MainView({
// 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/2"
const [locationId, setLocationId] = useState<LocationId>(
validLocationId(floorUrlString) ? floorUrlString : defaultLocation.id
)
const [preLocationId, setPreLocationId] = useState<LocationId>(locationId)
const location = locations.find(loc => loc.id === locationId)!
const currentLocationBoothsById = boothsByLocation.get(locationId)!
const currentLocationBoothsById = useMemo(() => {
const boothsById =
locationId !== "library"
? new Map([
...Array.from(boothsByLocation.get("library")!.entries()),
...Array.from(boothsByLocation.get(locationId)!.entries()) // Merge library booths with default location booths
])
: new Map([
...Array.from(boothsByLocation.get(preLocationId)!.entries()),
...Array.from(boothsByLocation.get(locationId)!.entries()) // Merge library booths with default location booths
])
setPreLocationId(location.id)
return boothsById
}, [location.id])

const latitude =
parseFloat(searchParams.get("lat") ?? "") || location.center.latitude
Expand All @@ -60,27 +66,8 @@ 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 @@ -123,20 +110,6 @@ 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
90 changes: 63 additions & 27 deletions src/app/student/map/_components/MapComponent.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { BoothPopup } from "@/app/student/map/_components/BoothPopup"
import {
BoothID,
geoJsonBoothDataByLocation
} from "@/app/student/map/lib/booths"
import { Location } from "@/app/student/map/lib/locations"
import { Location, LocationId } 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"
Expand All @@ -20,6 +19,7 @@ import {
addMapIconAssets,
backgroundLayerStyle,
boothLayerStyle,
boothOutlineStyle,
buildingLayerStyle,
lineLayerStyle,
roomLayerStyle,
Expand Down Expand Up @@ -49,15 +49,21 @@ export function MapComponent({
}) {
const mapRef = useRef<MapRef>(null)

const [mapZoom, setMapZoom] = useState(initialView.zoom)

const [markerScale, setMarkerScale] = useState(1)

const [preLocationId, setPreLocationId] = useState<LocationId>(location.id)
// Fly to location center on change
useEffect(() => {
const { longitude, latitude, zoom } = location.center
mapRef.current?.flyTo({
center: [longitude, latitude],
zoom: zoom
})
const timeout = setTimeout(() => {
mapRef.current?.flyTo({
center: [longitude, latitude],
zoom
})
}, 300)
return () => clearTimeout(timeout)
setMarkerScale(0.6)
}, [location])

Expand All @@ -79,7 +85,7 @@ export function MapComponent({
if (!booth) return
mapRef.current?.flyTo({
center: booth.center as [number, number],
zoom: 21,
zoom: 20,
speed: 0.8
})
}, [activeBoothId, boothsById])
Expand All @@ -88,10 +94,28 @@ export function MapComponent({
useFeatureState(mapRef, hoveredBoothId ? [hoveredBoothId] : [], "hover")
useFeatureState(mapRef, filteredBoothIds, "filtered")

const activeBooth =
activeBoothId != null ? boothsById.get(activeBoothId) : null

const currentGeoJsonBoothData = geoJsonBoothDataByLocation.get(location.id)!
const currentGeoJsonBoothData = useMemo(() => {
const currentData = geoJsonBoothDataByLocation.get(
location.id === "library" ? preLocationId : location.id
) ?? {
type: "FeatureCollection",
features: []
}
const libraryFeatures = geoJsonBoothDataByLocation.get("library")!.features
// Merge library features with the current location's features
const mergedFeatures = [
...libraryFeatures,
...currentData.features.filter(
feature =>
!libraryFeatures.some(
libraryFeature =>
libraryFeature.properties.id === feature.properties.id
)
)
]
setPreLocationId(location.id)
return { ...currentData, features: mergedFeatures }
}, [location.id])

// Don't want to rerender markers on every map render
const markers = useMemo(
Expand Down Expand Up @@ -132,8 +156,9 @@ 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 - 20) * 0.5))
const scale = Math.max(0.2, Math.min(1, 1 + (zoom - 20) * 0.5))
setMarkerScale(scale)
setMapZoom(zoom)
}

return (
Expand All @@ -153,7 +178,7 @@ export function MapComponent({
[18.063, 59.345],
[18.079, 59.35]
]}
mapStyle="https://api.maptiler.com/maps/977e9770-60b4-4b8a-94e9-a9fa8db4c68d/style.json?key=57xj41WPFBbOEWiVSSwL">
mapStyle="https://api.maptiler.com/maps/376fa556-c405-4a91-8e9e-15be82eb3a58/style.json?key=mgMcr2yF2fWUHzf27ygv">
<Layer {...backgroundLayerStyle}></Layer>

{/** Order sensitive! */}
Expand Down Expand Up @@ -182,31 +207,42 @@ export function MapComponent({
</Source>

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

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

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

{mapZoom > 19 && (
<Source
id="nymble-plan-routes"
type="geojson"
promoteId={"id"}
data={geoJsonPlanRoutesData}>
<Layer {...routeLayerStyle}></Layer>
</Source>
)}

{mapZoom > 19 && (
<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 a32e2fd

Please sign in to comment.