You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

324 lines
9.0 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import { Paper, Flex, ScrollArea, Autocomplete, Title } from "@mantine/core";
import { ArticleCardVertical } from "./ArticleCard";
import Map, {
Source,
Layer,
ScaleControl,
GeolocateControl,
} from "react-map-gl/maplibre";
import 'maplibre-gl/dist/maplibre-gl.css';
import { useState, useEffect, useRef } from "react";
import Fuse from "fuse.js";
// import mapstyle from './assets/basemap.json';
import mapstyle from "./assets/basemap-mapbox.json";
import plus from "./assets/plus.png";
import minus from "./assets/minus.png";
import pin from "./assets/pin.png";
import prishvin from "./assets/prishvin.png";
import ostrovskiy from "./assets/ostrovskiy.png";
import brusov from "./assets/brusov.png";
export function KartaPage() {
const mapRef = useRef(null);
const [initial, setInitial] = useState(null);
const [articles, setArticles] = useState(null);
const [search, setSearch] = useState("");
const [selected, setSelected] = useState(-1);
const [cursor, setCursor] = useState("grab");
const host = "https://strapi.litmusmap.ru";
// Load icons
const handleMapLoad = (e) => {
const icons = ["pin", "prishvin", "ostrovskiy", "brusov"];
const pinImage = new Image();
pinImage.src = pin;
pinImage.onload = () => mapRef.current.addImage("pin-marker", pinImage);
const prishvinImage = new Image();
prishvinImage.src = prishvin;
prishvinImage.onload = () =>
mapRef.current.addImage("prishvin-marker", prishvinImage);
const ostrovskiyImage = new Image();
ostrovskiyImage.src = ostrovskiy;
ostrovskiyImage.onload = () =>
mapRef.current.addImage("ostrovskiy-marker", ostrovskiyImage);
const brusovImage = new Image();
brusovImage.src = brusov;
brusovImage.onload = () =>
mapRef.current.addImage("brusov-marker", brusovImage);
};
// Load articles
useEffect(() => {
fetch(`${host}/api/articles?populate=*`)
.then((r) => r.json())
.then((d) => {
setInitial(d.data);
setArticles(d.data);
});
}, []);
// Search
const fuse =
initial !== null &&
new Fuse(initial, {
keys: ["attributes.tags.value"],
});
useEffect(() => {
const foundArticles = fuse && fuse.search(search).map((e) => e.item);
const updatedArticles = search.length > 0 ? foundArticles : initial;
setArticles(updatedArticles);
}, [search]);
// Select article interaction
useEffect(() => {
if (selected > 0) {
const selectedArticleIndex = articles.findIndex((a) => a.id == selected);
const selectedArticle = articles[selectedArticleIndex];
const reorderedArticles = [
selectedArticle,
...articles.filter((a) => a.id !== selected),
];
setArticles(reorderedArticles);
mapRef.current?.flyTo({
center: [
selectedArticle.attributes.longitude,
selectedArticle.attributes.latitude,
],
zoom: 10,
});
} else {
mapRef.current?.flyTo({
center: [35, 57],
zoom: 5,
});
}
}, [selected]);
const handleAddressClick = (id) => {
setSelected(id);
};
// Zoom
const ZoomControl = (props) => {
const zoomIn = () => {
mapRef.current.zoomTo(mapRef.current.getZoom() + 1);
};
const zoomOut = () => {
mapRef.current.zoomTo(mapRef.current.getZoom() - 1);
};
return (
<Flex
style={{
position: "absolute",
bottom: props.bottom,
right: props.right,
}}
>
<Paper
onClick={zoomOut}
radius="xl"
bg={"none"}
sx={{ "&:hover": { backgroundColor: "white", cursor: "pointer" } }}
maw={48}
mah={48}
>
<img src={minus} style={{ width: "48px", height: "48px" }}></img>
</Paper>
<Paper
onClick={zoomIn}
radius="xl"
bg={"none"}
sx={{ "&:hover": { backgroundColor: "white", cursor: "pointer" } }}
maw={48}
mah={48}
>
<img src={plus} style={{ width: "48px", height: "48px" }}></img>
</Paper>
</Flex>
);
};
return (
<div style={{ position: "relative" }}>
<Map
ref={mapRef}
initialViewState={{
longitude: 30,
latitude: 60,
zoom: 4,
}}
minZoom={3.5}
maxZoom={16}
maxBounds={[
[-20, 35],
[200, 80],
]}
style={{
height: "90vh",
}}
cursor={cursor}
mapStyle={mapstyle}
interactiveLayerIds={["points-layer"]}
onClick={(e) => {
// console.log(e.features);
e.features[0]
? (setSelected(e.features[0].properties.id),
mapRef.current.flyTo({
center: e.features[0].geometry.coordinates,
zoom: mapRef.current.getZoom() + 6,
}))
: setSelected(-1);
}}
onMouseEnter={() => setCursor("pointer")}
onMouseLeave={() => setCursor("grab")}
onLoad={handleMapLoad}
>
<Source
id="points"
type="vector"
tiles={[`${host}/tiles/{z}/{x}/{y}.pbf`]}
maxzoom={10}
>
<Layer
id="points-layer"
type="circle"
source-layer="layer"
filter={
articles === null || articles.length == 0
? false
: ["in", "id", ...articles.map((a) => a.id)]
}
paint={{
"circle-color": [
"match",
["get", "id"],
selected,
"#e66a5a",
"#F2994A",
],
"circle-radius": 8,
"circle-opacity": ["step", ["zoom"], 0.8, 7, 0],
}}
/>
<Layer
id="icons-layer"
type="symbol"
source-layer="layer"
filter={
articles === null || articles.length == 0
? false
: ["in", "id", ...articles.map((a) => a.id)]
}
layout={{
"icon-image": "pin-marker",
"icon-size": ['interpolate', ['linear'], ['zoom'], 7, 0.5, 12, 1],
}}
minzoom={7}
maxzoom={12}
/>
<Layer
id="portret-layer"
type="symbol"
source-layer="layer"
filter={
articles === null || articles.length == 0
? false
: ["in", "id", ...articles.map((a) => a.id)]
}
layout={{
"icon-image": [
"match",
["get", "id"],
6,
"ostrovskiy-marker",
7,
"brusov-marker",
1,
"prishvin-marker",
"pin-marker",
],
"icon-size": ['interpolate', ['linear'], ['zoom'], 12, 0.05, 14, 0.1],
}}
minzoom={12}
/>
</Source>
<ScaleControl position="bottom-right" />
<ZoomControl bottom="35px" right="4px" />
<GeolocateControl
showAccuracyCircle={false}
position="bottom-right"
style={{
marginBottom: "55px",
marginRight: "14px",
background: "none",
boxShadow: "none",
}}
/>
</Map>
<Paper
shadow={"md"}
withBorder
style={{
position: "absolute",
top: "1rem",
left: "1rem",
width: "36rem",
height: "83vh",
opacity: "95%",
padding: "1rem 0 1rem 1rem",
}}
>
<Title order={3} mb={10}>
Статьи
</Title>
<Autocomplete
mr={20}
mb={30}
placeholder="Поиск"
limit={2}
value={search}
onChange={setSearch}
data={[]}
/>
<ScrollArea h={"67vh"} type="auto">
<Flex
mih={50}
gap="md"
justify="flex-start"
align="start"
direction="column"
wrap="wrap"
mr={20}
>
{articles !== null &&
articles.length > 0 &&
articles.map((article) => (
<ArticleCardVertical
key={article.id}
id={article.id}
image={
article.attributes.cover.data !== null
? host + article.attributes.cover.data.attributes.url
: ""
}
category="музеи"
title={article.attributes.title}
address={article.attributes.address}
longitude={article.attributes.longitude}
latitude={article.attributes.latitude}
selected={selected}
clickAddressAction={handleAddressClick}
/>
))}
</Flex>
</ScrollArea>
</Paper>
</div>
);
}