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;
|