159 lines
5.3 KiB
TypeScript
159 lines
5.3 KiB
TypeScript
|
|
import React, { useEffect } from "react";
|
|||
|
|
import type { WidgetProps } from "widgets/BaseWidget";
|
|||
|
|
import styled from "styled-components";
|
|||
|
|
|
|||
|
|
import { MapContainer, TileLayer, Marker, Popup, useMap } from "react-leaflet";
|
|||
|
|
import "leaflet/dist/leaflet.css";
|
|||
|
|
import L from "leaflet";
|
|||
|
|
import { FullScreen, useFullScreenHandle } from "react-full-screen";
|
|||
|
|
import { Icon } from "@blueprintjs/core";
|
|||
|
|
|
|||
|
|
// Контейнер для карты с относительным позиционированием
|
|||
|
|
const MapWrapper = styled.div`
|
|||
|
|
position: relative;
|
|||
|
|
width: 100%;
|
|||
|
|
height: 100%;
|
|||
|
|
`;
|
|||
|
|
|
|||
|
|
// Кнопка для переключения полноэкранного режима
|
|||
|
|
const FullscreenButton = styled.button`
|
|||
|
|
position: absolute;
|
|||
|
|
top: 10px;
|
|||
|
|
right: 10px;
|
|||
|
|
z-index: 1000;
|
|||
|
|
background: white;
|
|||
|
|
border: 1px solid #ccc;
|
|||
|
|
border-radius: 4px;
|
|||
|
|
padding: 8px;
|
|||
|
|
cursor: pointer;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
|||
|
|
transition: background-color 0.2s;
|
|||
|
|
|
|||
|
|
&:hover {
|
|||
|
|
background-color: #f0f0f0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
&:active {
|
|||
|
|
background-color: #e0e0e0;
|
|||
|
|
}
|
|||
|
|
`;
|
|||
|
|
|
|||
|
|
// 🧩 фикс, чтобы маркеры отображались (иначе пустые иконки)
|
|||
|
|
delete (L.Icon.Default.prototype as any)._getIconUrl;
|
|||
|
|
|
|||
|
|
L.Icon.Default.mergeOptions({
|
|||
|
|
iconRetinaUrl: require("leaflet/dist/images/marker-icon-2x.png"),
|
|||
|
|
iconUrl: require("leaflet/dist/images/marker-icon.png"),
|
|||
|
|
shadowUrl: require("leaflet/dist/images/marker-shadow.png"),
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Компонент для обновления размера карты при изменении размера контейнера
|
|||
|
|
function MapResizer() {
|
|||
|
|
const map = useMap();
|
|||
|
|
|
|||
|
|
useEffect(() => {
|
|||
|
|
// Обновляем размер карты при монтировании
|
|||
|
|
const timer = setTimeout(() => {
|
|||
|
|
map.invalidateSize();
|
|||
|
|
}, 100);
|
|||
|
|
|
|||
|
|
return () => clearTimeout(timer);
|
|||
|
|
}, [map]);
|
|||
|
|
|
|||
|
|
// Обновляем размер при изменении размера окна
|
|||
|
|
useEffect(() => {
|
|||
|
|
const handleResize = () => {
|
|||
|
|
map.invalidateSize();
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
window.addEventListener("resize", handleResize);
|
|||
|
|
return () => window.removeEventListener("resize", handleResize);
|
|||
|
|
}, [map]);
|
|||
|
|
|
|||
|
|
// Обновляем размер при переключении полноэкранного режима
|
|||
|
|
useEffect(() => {
|
|||
|
|
const handleFullscreenChange = () => {
|
|||
|
|
// Небольшая задержка, чтобы контейнер успел изменить размер
|
|||
|
|
setTimeout(() => {
|
|||
|
|
map.invalidateSize();
|
|||
|
|
}, 200);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
document.addEventListener("fullscreenchange", handleFullscreenChange);
|
|||
|
|
document.addEventListener("webkitfullscreenchange", handleFullscreenChange);
|
|||
|
|
document.addEventListener("mozfullscreenchange", handleFullscreenChange);
|
|||
|
|
document.addEventListener("MSFullscreenChange", handleFullscreenChange);
|
|||
|
|
|
|||
|
|
return () => {
|
|||
|
|
document.removeEventListener("fullscreenchange", handleFullscreenChange);
|
|||
|
|
document.removeEventListener("webkitfullscreenchange", handleFullscreenChange);
|
|||
|
|
document.removeEventListener("mozfullscreenchange", handleFullscreenChange);
|
|||
|
|
document.removeEventListener("MSFullscreenChange", handleFullscreenChange);
|
|||
|
|
};
|
|||
|
|
}, [map]);
|
|||
|
|
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function OpenStreetMapComponent(props: OpenStreetMapComponentProps) {
|
|||
|
|
// Получаем значения из props или используем значения по умолчанию
|
|||
|
|
const centerLat = props.centerLat ?? 55.751244;
|
|||
|
|
const centerLng = props.centerLng ?? 37.618423;
|
|||
|
|
const zoom = props.zoom ?? 7;
|
|||
|
|
const markerLat = props.markerLat ?? centerLat;
|
|||
|
|
const markerLng = props.markerLng ?? centerLng;
|
|||
|
|
const markerText = props.markerText ?? "Привет! Это Москва 🏙️";
|
|||
|
|
|
|||
|
|
const center: [number, number] = [centerLat, centerLng];
|
|||
|
|
const markerPosition: [number, number] = [markerLat, markerLng];
|
|||
|
|
|
|||
|
|
// Хук для управления полноэкранным режимом
|
|||
|
|
const fullScreenHandle = useFullScreenHandle();
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<MapWrapper>
|
|||
|
|
<FullScreen handle={fullScreenHandle}>
|
|||
|
|
<MapContainer
|
|||
|
|
center={center}
|
|||
|
|
zoom={zoom}
|
|||
|
|
style={{ height: "500px", width: "500px" }}
|
|||
|
|
>
|
|||
|
|
<MapResizer />
|
|||
|
|
<TileLayer
|
|||
|
|
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
|||
|
|
attribution='© <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors'
|
|||
|
|
/>
|
|||
|
|
<Marker position={markerPosition}>
|
|||
|
|
<Popup>{markerText}</Popup>
|
|||
|
|
</Marker>
|
|||
|
|
</MapContainer>
|
|||
|
|
</FullScreen>
|
|||
|
|
|
|||
|
|
{/* Кнопка для переключения полноэкранного режима */}
|
|||
|
|
<FullscreenButton
|
|||
|
|
onClick={fullScreenHandle.active ? fullScreenHandle.exit : fullScreenHandle.enter}
|
|||
|
|
title={fullScreenHandle.active ? "Выйти из полноэкранного режима" : "Развернуть на весь экран"}
|
|||
|
|
>
|
|||
|
|
<Icon
|
|||
|
|
icon={fullScreenHandle.active ? "minimize" : "maximize"}
|
|||
|
|
iconSize={16}
|
|||
|
|
/>
|
|||
|
|
</FullscreenButton>
|
|||
|
|
</MapWrapper>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export interface OpenStreetMapComponentProps extends WidgetProps {
|
|||
|
|
centerLat?: number;
|
|||
|
|
centerLng?: number;
|
|||
|
|
zoom?: number;
|
|||
|
|
markerLat?: number;
|
|||
|
|
markerLng?: number;
|
|||
|
|
markerText?: string;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export default OpenStreetMapComponent;
|