diff --git a/app/client/package.json b/app/client/package.json
index 06c4555eb6..77e17e12f6 100644
--- a/app/client/package.json
+++ b/app/client/package.json
@@ -142,6 +142,7 @@
"js-sha256": "^0.9.0",
"jshint": "^2.13.4",
"klona": "^2.0.5",
+ "leaflet": "^1.9.4",
"libphonenumber-js": "^1.9.44",
"linkedom": "^0.14.20",
"localforage": "^1.7.3",
@@ -185,6 +186,7 @@
"react-helmet": "^5.2.1",
"react-hook-form": "^7.28.0",
"react-json-view": "^1.21.3",
+ "react-leaflet": "3.2.5",
"react-media-recorder": "^1.6.1",
"react-modal": "^3.15.1",
"react-page-visibility": "^7.0.0",
diff --git a/app/client/src/widgets/OpenStreetMapWidget/component/index.tsx b/app/client/src/widgets/OpenStreetMapWidget/component/index.tsx
new file mode 100644
index 0000000000..560e714f03
--- /dev/null
+++ b/app/client/src/widgets/OpenStreetMapWidget/component/index.tsx
@@ -0,0 +1,158 @@
+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 (
+
+
+
+
+
+
+ {markerText}
+
+
+
+
+ {/* Кнопка для переключения полноэкранного режима */}
+
+
+
+
+ );
+}
+
+export interface OpenStreetMapComponentProps extends WidgetProps {
+ centerLat?: number;
+ centerLng?: number;
+ zoom?: number;
+ markerLat?: number;
+ markerLng?: number;
+ markerText?: string;
+}
+
+export default OpenStreetMapComponent;
diff --git a/app/client/src/widgets/OpenStreetMapWidget/constants.ts b/app/client/src/widgets/OpenStreetMapWidget/constants.ts
new file mode 100644
index 0000000000..6dec3a3f08
--- /dev/null
+++ b/app/client/src/widgets/OpenStreetMapWidget/constants.ts
@@ -0,0 +1,9 @@
+// This file contains common constants which can be used across the widget configuration file (index.ts), widget and component folders.
+export const OPENSTREETMAP_WIDGET_CONSTANT = "";
+
+export enum OverflowTypes {
+ SCROLL = "SCROLL",
+ TRUNCATE = "TRUNCATE",
+ NONE = "NONE",
+ }
+
\ No newline at end of file
diff --git a/app/client/src/widgets/OpenStreetMapWidget/icon.svg b/app/client/src/widgets/OpenStreetMapWidget/icon.svg
new file mode 100644
index 0000000000..19a4df7cd7
--- /dev/null
+++ b/app/client/src/widgets/OpenStreetMapWidget/icon.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/app/client/src/widgets/OpenStreetMapWidget/index.ts b/app/client/src/widgets/OpenStreetMapWidget/index.ts
new file mode 100644
index 0000000000..b668e51d06
--- /dev/null
+++ b/app/client/src/widgets/OpenStreetMapWidget/index.ts
@@ -0,0 +1,3 @@
+import Widget from "./widget";
+
+export default Widget;
diff --git a/app/client/src/widgets/OpenStreetMapWidget/widget/index.tsx b/app/client/src/widgets/OpenStreetMapWidget/widget/index.tsx
new file mode 100644
index 0000000000..dc0905a6ba
--- /dev/null
+++ b/app/client/src/widgets/OpenStreetMapWidget/widget/index.tsx
@@ -0,0 +1,203 @@
+import React from "react";
+
+import type { DerivedPropertiesMap } from "WidgetProvider/factory/types";
+
+import BaseWidget from "widgets/BaseWidget";
+import type { WidgetProps, WidgetState } from "widgets/BaseWidget";
+
+import OpenStreetMapComponent from "../component";
+import { ValidationTypes } from "constants/WidgetValidation";
+
+import IconSVG from "../icon.svg";
+import { WIDGET_TAGS } from "constants/WidgetConstants";
+
+class OpenStreetMapWidget extends BaseWidget {
+ static type = "OPENSTREETMAP_WIDGET";
+
+ //Метаданные
+ static getConfig() {
+ return {
+ name: "OpenStreetMap",
+ iconSVG: IconSVG,
+ needsMeta: false,
+ isCanvas: false,
+ tags: [WIDGET_TAGS.CONTENT],
+ searchTags: ["map", "openstreet", "open"],
+ };
+ }
+
+ static getFeatures() {
+ return {
+ dynamicHeight: {
+ sectionIndex: 0,
+ active: false,
+ },
+ };
+ }
+ //Значения по умолчанию
+ static getDefaults() {
+ return {
+ widgetName: "OpenStreetMap",
+ rows: 10,
+ columns: 10,
+ version: 1,
+ // Значения по умолчанию для карты
+ centerLat: 55.751244,
+ centerLng: 37.618423,
+ zoom: 7,
+ markerLat: 55.751244,
+ markerLng: 37.618423,
+ markerText: "Привет! Это Москва 🏙️",
+ };
+ }
+
+ static getPropertyPaneContentConfig() {
+ return [
+ {
+ sectionName: "General",
+ children: [
+ {
+ propertyName: "centerLat",
+ helpText: "Широта центра карты",
+ label: "Широта центра",
+ controlType: "INPUT_TEXT",
+ placeholderText: "55.751244",
+ defaultValue: 55.751244,
+ isBindProperty: true,
+ isTriggerProperty: false,
+ validation: {
+ type: ValidationTypes.NUMBER,
+ params: {
+ min: -90,
+ max: 90,
+ },
+ },
+ },
+ {
+ propertyName: "centerLng",
+ helpText: "Долгота центра карты",
+ label: "Долгота центра",
+ controlType: "INPUT_TEXT",
+ placeholderText: "37.618423",
+ defaultValue: 37.618423,
+ isBindProperty: true,
+ isTriggerProperty: false,
+ validation: {
+ type: ValidationTypes.NUMBER,
+ params: {
+ min: -180,
+ max: 180,
+ },
+ },
+ },
+ {
+ propertyName: "zoom",
+ helpText: "Уровень масштабирования карты (1-18)",
+ label: "Масштаб",
+ controlType: "INPUT_TEXT",
+ placeholderText: "7",
+ defaultValue: 7,
+ isBindProperty: true,
+ isTriggerProperty: false,
+ validation: {
+ type: ValidationTypes.NUMBER,
+ params: {
+ min: 1,
+ max: 18,
+ },
+ },
+ },
+ {
+ propertyName: "markerLat",
+ helpText: "Широта маркера",
+ label: "Широта маркера",
+ controlType: "INPUT_TEXT",
+ placeholderText: "55.751244",
+ defaultValue: 55.751244,
+ isBindProperty: true,
+ isTriggerProperty: false,
+ validation: {
+ type: ValidationTypes.NUMBER,
+ params: {
+ min: -90,
+ max: 90,
+ },
+ },
+ },
+ {
+ propertyName: "markerLng",
+ helpText: "Долгота маркера",
+ label: "Долгота маркера",
+ controlType: "INPUT_TEXT",
+ placeholderText: "37.618423",
+ defaultValue: 37.618423,
+ isBindProperty: true,
+ isTriggerProperty: false,
+ validation: {
+ type: ValidationTypes.NUMBER,
+ params: {
+ min: -180,
+ max: 180,
+ },
+ },
+ },
+ {
+ propertyName: "markerText",
+ helpText: "Текст во всплывающем окне маркера",
+ label: "Текст маркера",
+ controlType: "INPUT_TEXT",
+ placeholderText: "Привет! Это Москва 🏙️",
+ defaultValue: "Привет! Это Москва 🏙️",
+ isBindProperty: true,
+ isTriggerProperty: false,
+ validation: {
+ type: ValidationTypes.TEXT,
+ },
+ },
+ {
+ propertyName: "isVisible",
+ helpText: "Управляет видимостью виджета",
+ label: "Видимый",
+ controlType: "SWITCH",
+ isJSConvertible: true,
+ isBindProperty: true,
+ isTriggerProperty: false,
+ validation: { type: ValidationTypes.BOOLEAN },
+ },
+ ],
+ },
+ ];
+ }
+
+ static getPropertyPaneStyleConfig() {
+ return [];
+ }
+
+ static getDerivedPropertiesMap(): DerivedPropertiesMap {
+ return {};
+ }
+
+ static getDefaultPropertiesMap(): Record {
+ return {};
+ }
+
+ static getMetaPropertiesMap(): Record {
+ return {};
+ }
+
+ //Рендерит компонент
+ getWidgetView() {
+ return ;
+ }
+}
+
+export interface OpenStreetMapWidgetProps extends WidgetProps {
+ centerLat?: number;
+ centerLng?: number;
+ zoom?: number;
+ markerLat?: number;
+ markerLng?: number;
+ markerText?: string;
+}
+
+export default OpenStreetMapWidget;
\ No newline at end of file
diff --git a/app/client/src/widgets/index.ts b/app/client/src/widgets/index.ts
index fc83e4e27a..274d75af0f 100644
--- a/app/client/src/widgets/index.ts
+++ b/app/client/src/widgets/index.ts
@@ -368,6 +368,10 @@ const WidgetLoaders = new Map Promise>([
"EXTERNAL_WIDGET",
async () => import("./ExternalWidget").then((m) => m.default),
],
+ [
+ "OPENSTREETMAP_WIDGET",
+ async () => import("./OpenStreetMapWidget").then((m) => m.default),
+ ],
// Deprecated Widgets
[
diff --git a/app/client/yarn.lock b/app/client/yarn.lock
index 04dcebce9e..d79cee12ac 100644
--- a/app/client/yarn.lock
+++ b/app/client/yarn.lock
@@ -7436,6 +7436,17 @@ __metadata:
languageName: node
linkType: hard
+"@react-leaflet/core@npm:^1.1.1":
+ version: 1.1.1
+ resolution: "@react-leaflet/core@npm:1.1.1"
+ peerDependencies:
+ leaflet: ^1.7.1
+ react: ^17.0.1
+ react-dom: ^17.0.1
+ checksum: 2fc4a80e5524f9437ac6cef0f95e63388f2df6ecc5107fef85fd097eb2455436e796d41a4c43cdcbb983a4132403646823ba1dc0d953e866eb80808cbfaf0232
+ languageName: node
+ linkType: hard
+
"@react-spectrum/utils@npm:^3.11.10, @react-spectrum/utils@npm:^3.9.0":
version: 3.11.10
resolution: "@react-spectrum/utils@npm:3.11.10"
@@ -13837,6 +13848,7 @@ __metadata:
json5: ^2.2.3
klona: ^2.0.5
knip: ^5.30.2
+ leaflet: ^1.9.4
libphonenumber-js: ^1.9.44
linkedom: ^0.14.20
lint-staged: ^14.0.1
@@ -13900,6 +13912,7 @@ __metadata:
react-hook-form: ^7.28.0
react-is: ^16.12.0
react-json-view: ^1.21.3
+ react-leaflet: 3.2.5
react-media-recorder: ^1.6.1
react-modal: ^3.15.1
react-page-visibility: ^7.0.0
@@ -24138,6 +24151,13 @@ __metadata:
languageName: node
linkType: hard
+"leaflet@npm:^1.9.4":
+ version: 1.9.4
+ resolution: "leaflet@npm:1.9.4"
+ checksum: bfc79f17a247b37b92d84b3c78702501603392d6589fde606de4a825d11f1609d90225388834f2e0709dac327e52dcd4b4b9cc9fd3d590060c5b1e53b84fa6c6
+ languageName: node
+ linkType: hard
+
"leven@npm:^3.1.0":
version: 3.1.0
resolution: "leven@npm:3.1.0"
@@ -29956,6 +29976,19 @@ __metadata:
languageName: node
linkType: hard
+"react-leaflet@npm:3.2.5":
+ version: 3.2.5
+ resolution: "react-leaflet@npm:3.2.5"
+ dependencies:
+ "@react-leaflet/core": ^1.1.1
+ peerDependencies:
+ leaflet: ^1.7.1
+ react: ^17.0.1
+ react-dom: ^17.0.1
+ checksum: 503b7cee8acc12e0e2c5e7675432e7ef5742463e3e5420282ce60a9efd306430caefae3cb282c976e3df7665f19aef5b49cdce44a034979b4ea3ee968c2621d2
+ languageName: node
+ linkType: hard
+
"react-lifecycles-compat@npm:^3.0.0, react-lifecycles-compat@npm:^3.0.4":
version: 3.0.4
resolution: "react-lifecycles-compat@npm:3.0.4"