parent
efef3fbf8e
commit
81b434b6a2
@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
@ -1,93 +1,8 @@
|
||||
import "./App.css";
|
||||
import Map, { Layer, Popup, Source } from "react-map-gl";
|
||||
import maplibregl from "maplibre-gl";
|
||||
|
||||
import "maplibre-gl/dist/maplibre-gl.css";
|
||||
import { useRef, useState } from "react";
|
||||
import {
|
||||
clusterCountLayer,
|
||||
clusterLayer,
|
||||
unclusteredPointLayer,
|
||||
} from "./clusters";
|
||||
import { countoursLayer } from "./vector-tile";
|
||||
import { NavigateButton } from "./NavigateButton";
|
||||
|
||||
const MAP_TILER_KEY = "hE7PBueqYiS7hKSYUXP9";
|
||||
import { MapComponent } from "./Map/MapComponent";
|
||||
|
||||
function App() {
|
||||
const mapRef = useRef(null);
|
||||
const [showPopup, setShowPopup] = useState(true);
|
||||
|
||||
const onClick = (event) => {
|
||||
try {
|
||||
console.log(event.features);
|
||||
const feature = event.features[0];
|
||||
const clusterId = feature.properties.cluster_id;
|
||||
|
||||
const mapboxSource = mapRef.current.getSource("earthquakes");
|
||||
|
||||
mapboxSource.getClusterExpansionZoom(clusterId, (err, zoom) => {
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
|
||||
mapRef.current.easeTo({
|
||||
center: feature.geometry.coordinates,
|
||||
zoom,
|
||||
duration: 500,
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Map
|
||||
mapLib={maplibregl}
|
||||
mapStyle={`https://api.maptiler.com/maps/voyager/style.json?key=${MAP_TILER_KEY}`}
|
||||
style={{ width: "100vw", height: "100vh" }}
|
||||
initialViewState={{
|
||||
latitude: 40.67,
|
||||
longitude: -103.59,
|
||||
zoom: 3,
|
||||
}}
|
||||
ref={mapRef}
|
||||
interactiveLayerIds={[clusterLayer.id]}
|
||||
onClick={onClick}
|
||||
>
|
||||
{showPopup && (
|
||||
<Popup
|
||||
longitude={-100}
|
||||
latitude={40}
|
||||
anchor="bottom"
|
||||
onClose={() => setShowPopup(false)}
|
||||
>
|
||||
<div style={{ color: "red", fontWeight: "bold" }}>You are here</div>
|
||||
</Popup>
|
||||
)}
|
||||
<Source
|
||||
id="earthquakes"
|
||||
type="geojson"
|
||||
data="https://docs.mapbox.com/mapbox-gl-js/assets/earthquakes.geojson"
|
||||
cluster={true}
|
||||
clusterMaxZoom={14}
|
||||
clusterRadius={50}
|
||||
>
|
||||
<Layer {...clusterLayer} />
|
||||
<Layer {...clusterCountLayer} />
|
||||
<Layer {...unclusteredPointLayer} />
|
||||
</Source>
|
||||
<Source
|
||||
id="countours"
|
||||
type="vector"
|
||||
url="https://api.maptiler.com/tiles/contours/tiles.json?key=hE7PBueqYiS7hKSYUXP9"
|
||||
>
|
||||
<Layer {...countoursLayer} />
|
||||
</Source>
|
||||
<NavigateButton />
|
||||
</Map>
|
||||
);
|
||||
return <MapComponent />;
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
||||
@ -0,0 +1,91 @@
|
||||
import maplibregl from "maplibre-gl";
|
||||
import Map, { Layer, Popup, Source } from "react-map-gl";
|
||||
import { useRef, useState } from "react";
|
||||
import {
|
||||
clusterCountLayer,
|
||||
clusterLayer,
|
||||
unclusteredPointLayer,
|
||||
} from "../clusters";
|
||||
import { countoursLayer } from "../vector-tile";
|
||||
import { NavigateButton } from "../NavigateButton";
|
||||
import { Sidebar } from "../modules/Sidebar/Sidebar";
|
||||
|
||||
const MAP_TILER_KEY = "hE7PBueqYiS7hKSYUXP9";
|
||||
|
||||
export const MapComponent = () => {
|
||||
const mapRef = useRef(null);
|
||||
const [showPopup, setShowPopup] = useState(true);
|
||||
|
||||
const onClick = (event) => {
|
||||
try {
|
||||
const feature = event.features[0];
|
||||
if (!feature) return;
|
||||
|
||||
const clusterId = feature.properties.cluster_id;
|
||||
|
||||
const mapboxSource = mapRef.current.getSource("earthquakes");
|
||||
|
||||
mapboxSource.getClusterExpansionZoom(clusterId, (err, zoom) => {
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
|
||||
mapRef.current.easeTo({
|
||||
center: feature.geometry.coordinates,
|
||||
zoom,
|
||||
duration: 500,
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Map
|
||||
mapLib={maplibregl}
|
||||
mapStyle={`https://api.maptiler.com/maps/voyager/style.json?key=${MAP_TILER_KEY}`}
|
||||
style={{ width: "100vw", height: "100vh" }}
|
||||
initialViewState={{
|
||||
latitude: 55.7558,
|
||||
longitude: 37.6173,
|
||||
zoom: 10,
|
||||
}}
|
||||
ref={mapRef}
|
||||
interactiveLayerIds={[clusterLayer.id]}
|
||||
onClick={onClick}
|
||||
>
|
||||
{showPopup && (
|
||||
<Popup
|
||||
longitude={-100}
|
||||
latitude={40}
|
||||
anchor="bottom"
|
||||
onClose={() => setShowPopup(false)}
|
||||
>
|
||||
<div style={{ color: "red", fontWeight: "bold" }}>You are here</div>
|
||||
</Popup>
|
||||
)}
|
||||
<Source
|
||||
id="earthquakes"
|
||||
type="geojson"
|
||||
data="https://docs.mapbox.com/mapbox-gl-js/assets/earthquakes.geojson"
|
||||
cluster={true}
|
||||
clusterMaxZoom={14}
|
||||
clusterRadius={50}
|
||||
>
|
||||
<Layer {...clusterLayer} />
|
||||
<Layer {...clusterCountLayer} />
|
||||
<Layer {...unclusteredPointLayer} />
|
||||
</Source>
|
||||
<Source
|
||||
id="countours"
|
||||
type="vector"
|
||||
url="https://api.maptiler.com/tiles/contours/tiles.json?key=hE7PBueqYiS7hKSYUXP9"
|
||||
>
|
||||
<Layer {...countoursLayer} />
|
||||
</Source>
|
||||
<NavigateButton />
|
||||
<Sidebar />
|
||||
</Map>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,14 @@
|
||||
import { Typography } from "antd";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
export const Title = ({ text, className }) => {
|
||||
return (
|
||||
<div className={className}>
|
||||
<Text type="secondary" className={twMerge("uppercase")}>
|
||||
{text}
|
||||
</Text>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -1,70 +1,3 @@
|
||||
:root {
|
||||
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
background-color: #1a1a1a;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
button:hover {
|
||||
border-color: #646cff;
|
||||
}
|
||||
button:focus,
|
||||
button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: #213547;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
a:hover {
|
||||
color: #747bff;
|
||||
}
|
||||
button {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
}
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import App from './App'
|
||||
import './index.css'
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import App from "./App";
|
||||
import "maplibre-gl/dist/maplibre-gl.css";
|
||||
import "antd/dist/antd.css";
|
||||
import "./index.css";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||
ReactDOM.createRoot(document.getElementById("root")).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
)
|
||||
);
|
||||
|
||||
@ -0,0 +1,67 @@
|
||||
import { Button } from "antd";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { useState } from "react";
|
||||
import { Title } from "../../components/Title";
|
||||
|
||||
const types = [
|
||||
{ id: "kiosk", name: "Городские киоски" },
|
||||
{ id: "mfc", name: "МФЦ" },
|
||||
{ id: "library", name: "Библиотеки" },
|
||||
];
|
||||
|
||||
const SelectItem = ({ name, isActive, onClick }) => {
|
||||
return (
|
||||
<Button
|
||||
block
|
||||
type="text"
|
||||
className={twMerge(
|
||||
"text-left",
|
||||
isActive && "bg-blue hover:bg-blue active:bg-blue focus:bg-blue"
|
||||
)}
|
||||
onClick={onClick}
|
||||
>
|
||||
{name}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export const ObjectTypesSelect = () => {
|
||||
const [activeTypes, setActiveTypes] = useState([]);
|
||||
|
||||
const handleClick = (typeId) =>
|
||||
setActiveTypes((activeTypes) =>
|
||||
activeTypes.includes(typeId)
|
||||
? activeTypes.filter((id) => id !== typeId)
|
||||
: [...activeTypes, typeId]
|
||||
);
|
||||
|
||||
const clear = () => setActiveTypes([]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex justify-between items-center mb-1">
|
||||
<Title text={"Тип объекта размещения"} />
|
||||
{activeTypes.length !== 0 && (
|
||||
<Button
|
||||
type="text"
|
||||
className="text-grey text-xs p-0 hover:bg-transparent active:bg-transparent focus:bg-transparent h-fit"
|
||||
onClick={clear}
|
||||
>
|
||||
сбросить
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
{types.map((type) => (
|
||||
<SelectItem
|
||||
key={type.id}
|
||||
name={type.name}
|
||||
isActive={activeTypes.includes(type.id)}
|
||||
onClick={() => handleClick(type.id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,14 @@
|
||||
import { Slider } from "antd";
|
||||
import { Title } from "../../components/Title";
|
||||
|
||||
export const RatingSlider = () => {
|
||||
return (
|
||||
<div>
|
||||
<Title
|
||||
text={"Востребованность постамата, усл. ед."}
|
||||
className="flex justify-center"
|
||||
/>
|
||||
<Slider range defaultValue={[20, 50]} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,29 @@
|
||||
import { RegionSelect } from "./RegionSelect";
|
||||
import { Button, Checkbox } from "antd";
|
||||
import { BsChevronLeft } from "react-icons/bs";
|
||||
import { ObjectTypesSelect } from "./ObjectTypesSelect";
|
||||
import { RatingSlider } from "./RatingSlider";
|
||||
|
||||
export const Sidebar = () => {
|
||||
return (
|
||||
<div className="absolute top-10 right-10 bg-white-background w-[300px] rounded-xl p-3">
|
||||
<Button
|
||||
type="text"
|
||||
icon={<BsChevronLeft className="mr-3" />}
|
||||
className="flex items-center p-0 pr-1 text-grey mb-2"
|
||||
>
|
||||
Настройки
|
||||
</Button>
|
||||
<div className="space-y-5">
|
||||
<RegionSelect />
|
||||
<ObjectTypesSelect />
|
||||
<Checkbox>Тепловая карта</Checkbox>
|
||||
<RatingSlider />
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button type="primary">Экспорт данных</Button>
|
||||
<Button type="primary">Экспорт карты</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,17 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: ["./src/**/*.{js,jsx}"],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
'white-background': 'rgba(255, 255, 255, 0.8)',
|
||||
'blue': '#8fc6f9',
|
||||
'grey': 'rgba(0,0, 0, 0.5)'
|
||||
},
|
||||
}
|
||||
},
|
||||
plugins: [],
|
||||
corePlugins: {
|
||||
preflight: false // <== disable this!
|
||||
},
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()]
|
||||
})
|
||||
plugins: [react()],
|
||||
});
|
||||
|
||||
Loading…
Reference in new issue