0
0
mirror of https://github.com/Maks1mS/free-ozon-dpr.git synced 2025-10-21 01:17:31 +03:00

chore: improve ux and dx (#1)

* move to src

* hide popup at startup

* add eslint
This commit is contained in:
2024-06-08 20:18:09 +03:00
committed by GitHub
parent ccdce54b73
commit deeed2e2ef
12 changed files with 930 additions and 11 deletions

34
src/features.js Normal file
View File

@@ -0,0 +1,34 @@
import Feature from "ol/Feature";
import Point from "ol/geom/Point";
import { fromLonLat } from "ol/proj";
import { Circle as CircleStyle, Fill, Stroke, Style } from "ol/style";
const iconStyle = new Style({
image: new CircleStyle({
radius: 8,
fill: new Fill({
color: "#c90036",
}),
stroke: new Stroke({
color: "#fff",
width: 2,
}),
}),
});
function createFeatures(places) {
return places.map((place) => {
const feature = new Feature({
geometry: new Point(fromLonLat(place.coordinates)),
name: `[${place.provider}] ${place.name}`,
address: place.address,
link: place.link,
source: place.source,
operationTime: place.operationTime,
});
feature.setStyle(iconStyle);
return feature;
});
}
export { createFeatures };

48
src/geolocation.js Normal file
View File

@@ -0,0 +1,48 @@
import map, { view } from "./map";
import Feature from "ol/Feature.js";
import Geolocation from "ol/Geolocation.js";
import Point from "ol/geom/Point.js";
import { Circle as CircleStyle, Fill, Stroke, Style } from "ol/style.js";
import { Vector as VectorSource } from "ol/source.js";
import { Vector as VectorLayer } from "ol/layer.js";
const geolocation = new Geolocation({
tracking: true,
trackingOptions: {
enableHighAccuracy: true,
},
projection: view.getProjection(),
});
const positionFeature = new Feature();
positionFeature.setStyle(
new Style({
image: new CircleStyle({
radius: 6,
fill: new Fill({
color: "#3399CC",
}),
stroke: new Stroke({
color: "#fff",
width: 2,
}),
}),
})
);
const accuracyFeature = new Feature();
geolocation.on("change:accuracyGeometry", function () {
accuracyFeature.setGeometry(geolocation.getAccuracyGeometry());
});
geolocation.on("change:position", function () {
const coordinates = geolocation.getPosition();
positionFeature.setGeometry(coordinates ? new Point(coordinates) : null);
});
new VectorLayer({
map: map,
source: new VectorSource({
features: [accuracyFeature, positionFeature],
}),
});

39
src/index.html Normal file
View File

@@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/x-icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link href="https://fonts.googleapis.com/css2?family=Roboto&display=swap" rel="stylesheet">
<title>БЕСПЛАТНЫЕ ПВЗ ОЗОН В ДНР</title>
<style>
#popup {
display: none;
}
.ol-overlay-container > #popup {
display: unset;
}
</style>
</head>
<body>
<div id="map" class="map"></div>
<div id="popup" class="ol-popup">
<a href="#" id="popup-closer" class="ol-popup-closer"></a>
<div id="popup-content">
<p id="popup-name"></p>
<a id="popup-address"></a>
<p>График работы: <span id="popup-operation-time">неизвестно</span></p>
<p>
Отсканируйте QR или нажмите на него
<a id="popup-link" href="">
<canvas id="popup-canvas"></canvas>
</a>
</p>
<p>
<a id="popup-source">Источник</a>
</p>
</div>
</div>
<script type="module" src="/main.js"></script>
</body>
</html>

19
src/main.js Normal file
View File

@@ -0,0 +1,19 @@
import "./style.css";
import map from "./map";
import "./popup";
import mergedData from "../merged-data.json";
import { createFeatures } from "./features";
import VectorSource from "ol/source/Vector";
import VectorLayer from "ol/layer/Vector";
const features = createFeatures(mergedData);
const vectorSource = new VectorSource({
features,
});
const vectorLayer = new VectorLayer({
source: vectorSource,
});
map.addLayer(vectorLayer);

38
src/map.js Normal file
View File

@@ -0,0 +1,38 @@
import 'ol/ol.css';
import Map from 'ol/Map';
import View from 'ol/View';
import TileLayer from 'ol/layer/Tile';
import { fromLonLat } from 'ol/proj';
import { XYZ } from 'ol/source';
// import { createXYZ } from 'ol/tilegrid';
const MAP_TARGET = 'map';
const MAP_CENTER = fromLonLat([37.57725139554275, 48.02287702854201]);
const MAP_ZOOM = 8.5;
const customTileSource = new TileLayer({
source: new XYZ({
// url: 'https://tile-server.ozon.ru/tile/default/{z}/{x}/{y}.png'
tileUrlFunction: ([z, x, y]) => {
const s = x % 4 + (y % 4) * 4
return `https://i${s}.wikimapia.org/?x=${x}&y=${y}&zoom=${z}&type=map&lng=1`
},
maxZoom: 18,
})
});
export const view = new View({
center: MAP_CENTER,
zoom: MAP_ZOOM,
})
const map = new Map({
target: MAP_TARGET,
layers: [
customTileSource
],
view,
});
export default map;

60
src/popup.js Normal file
View File

@@ -0,0 +1,60 @@
import map from "./map";
import { Overlay } from "ol";
import QRCode from "qrcode";
import { el } from "./utils";
import { toLonLat } from "ol/proj";
const popup = el("popup");
const closer = el("popup-closer");
const popupName = el("popup-name");
const popupAddress = el("popup-address");
const popupLink = el("popup-link");
const popupCanvas = el("popup-canvas");
const popupSource = el("popup-source");
const popupOperationTime = el("popup-operation-time");
const overlay = new Overlay({
element: popup,
autoPan: true,
autoPanAnimation: {
duration: 250,
},
});
map.addOverlay(overlay);
function close() {
overlay.setPosition(undefined);
closer.blur();
return false;
}
function onClick(event) {
const feature = map.forEachFeatureAtPixel(event.pixel, function (feature) {
return feature;
});
if (!feature) return close();
const coordinates = feature.getGeometry().getCoordinates();
const [lon, lat] = toLonLat(coordinates);
popupName.textContent = feature.get("name");
popupAddress.textContent = feature.get("address");
popupAddress.href = `https://yandex.ru/maps/?whatshere[point]=${lon},${lat}&whatshere[zoom]=18&l=map`
popupLink.href = feature.get("link");
popupSource.href = feature.get("source");
popupOperationTime.innerHTML = feature.get("operationTime") ?? "неизвестно";
QRCode.toCanvas(popupCanvas, feature.get("link"), function (error) {
if (error) console.error(error);
console.log("success!");
});
overlay.setPosition(coordinates);
}
closer.addEventListener("click", close);
map.on("singleclick", onClick);

73
src/style.css Normal file
View File

@@ -0,0 +1,73 @@
html,
body {
margin: 0;
height: 100%;
font-family: "Roboto", sans-serif;
font-weight: 400;
font-style: normal;
}
#map {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
height: 100vh;
}
.ol-popup {
position: absolute;
background-color: white;
padding: 15px;
border-radius: 10px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
border: 1px solid #cccccc;
bottom: 12px;
left: -50px;
width: 300px; /* Увеличенная ширина */
max-width: 300px; /* Максимальная ширина */
font-size: 14px; /* Размер шрифта */
color: #333; /* Цвет текста */
line-height: 1.5; /* Межстрочный интервал */
}
.ol-popup:after,
.ol-popup:before {
top: 100%;
border: solid transparent;
content: " ";
height: 0;
width: 0;
position: absolute;
pointer-events: none;
}
.ol-popup:after {
border-top-color: white;
border-width: 10px;
left: 48px;
margin-left: -10px;
}
.ol-popup:before {
border-top-color: #cccccc;
border-width: 11px;
left: 48px;
margin-left: -11px;
}
#popup-closer {
position: absolute;
top: 2px;
right: 8px;
font-size: 16px;
text-decoration: none;
color: #333;
background-color: transparent;
border: none;
cursor: pointer;
}
#popup-operation-time {
white-space: pre-line
}

52
src/utils.js Normal file
View File

@@ -0,0 +1,52 @@
import { toLonLat } from "ol/proj";
import { getDistance } from "ol/sphere";
export function el(id) {
return document.getElementById(id);
}
export function removeDuplicatesByRadius(features, radius) {
const uniqueFeatures = [];
const coordinatesSet = new Set();
features.forEach((feature) => {
const coordinates = feature.getGeometry().getCoordinates();
const lonLat = toLonLat(coordinates);
let isDuplicate = false;
coordinatesSet.forEach((setCoordinates) => {
if (getDistance(setCoordinates, lonLat) <= radius) {
isDuplicate = true;
}
});
if (!isDuplicate) {
uniqueFeatures.push(feature);
coordinatesSet.add(lonLat);
}
});
return uniqueFeatures;
}
export function removeDuplicatesByUrl(features) {
const uniqueFeatures = [];
const urlSet = new Set();
features.forEach((feature) => {
let isDuplicate = false;
const link = feature.get("link");
if (urlSet.has(link)) {
isDuplicate = true;
}
if (!isDuplicate) {
uniqueFeatures.push(feature);
urlSet.add(link);
}
});
return uniqueFeatures;
}