initial commit
13
README.md
|
|
@ -1,2 +1,13 @@
|
|||
# map.meshcore.dev
|
||||
Official MeshCore Map
|
||||
Official MeshCore Map (frontend)
|
||||
|
||||
## Installation
|
||||
This is fully static and build-free site, cloning it to web location that can serve static content should be enough.
|
||||
It uses backend api deployed on https://meshcore.dev/api/v1/nodes, so you will need to edit `apiUrl` in `src/map.js`.
|
||||
|
||||
## Libraries used
|
||||
* [Vue3](https://github.com/vuejs/core)
|
||||
* [Beer.css](https://github.com/beercss/beercss)
|
||||
* [Leaflet](https://github.com/Leaflet/Leaflet)
|
||||
* [Leaflet.markercluster](https://github.com/Leaflet/Leaflet.markercluster)
|
||||
* [Material icons](https://fonts.google.com/icons)
|
||||
203
css/style.css
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
@font-face {
|
||||
font-family: Material Symbols Outlined;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: block;
|
||||
src: url(https://cdn.jsdelivr.net/npm/beercss@3.9.4/dist/cdn/material-symbols-outlined.woff2) format("woff2")
|
||||
}
|
||||
|
||||
[v-cloak] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
body { margin: 0; }
|
||||
body:has(dialog[open])::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
z-index: 1049;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
dialog[open] {
|
||||
z-index: 1050 !important;
|
||||
}
|
||||
|
||||
#map { height: 100vh; }
|
||||
table.node-info {
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
}
|
||||
table.node-info td {
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
table.node-info tr td:first-child {
|
||||
width: 150px;
|
||||
}
|
||||
table.node-info tr td:last-child {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.search-results {
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.search-results li {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.search-pkey {
|
||||
max-width: 400px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
#app {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.add-dialog {
|
||||
z-index: 1050;
|
||||
padding: 15px;
|
||||
max-height: 80vh;
|
||||
}
|
||||
|
||||
.add-dialog .page {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.add-dialog .page img {
|
||||
max-width: 50%;
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
@media (max-width: 800px) {
|
||||
.add-dialog {
|
||||
width: 95%;
|
||||
max-height: 100vh;
|
||||
}
|
||||
}
|
||||
|
||||
.pointer-help {
|
||||
cursor: help
|
||||
}
|
||||
|
||||
.stats {
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-self: center;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
column-gap: 15px;
|
||||
row-gap: 32px;
|
||||
height: 30px;
|
||||
width: 100%;
|
||||
min-width: 700px;
|
||||
overflow-y: hidden;
|
||||
overflow-x: auto;
|
||||
color: #000;
|
||||
background-color: #ffffff55;
|
||||
backdrop-filter: blur(10px);
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
.stats > span {
|
||||
line-height: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stats i.node-type {
|
||||
background-color: #667a8c;
|
||||
border-radius: 50%;
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
margin-left: 7px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.stats a img {
|
||||
width: 16px;
|
||||
min-inline-size: 0px !important;
|
||||
}
|
||||
.search {
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 10px;
|
||||
top: 30px;
|
||||
max-width: 600px;
|
||||
flex-grow: 1;
|
||||
width: calc(100% - 20px);
|
||||
align-self: center;
|
||||
justify-content: center;
|
||||
gap: 5px;
|
||||
}
|
||||
.search .field {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.search .submit {
|
||||
width: 12px;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
.search .filter {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.search-text b {
|
||||
background-color: #ee0;
|
||||
}
|
||||
button.manual-add {
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
display: block;
|
||||
bottom: 55px;
|
||||
right: 10px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
button.manual-add::after {
|
||||
font-size: 40px;
|
||||
content: '+';
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.leaflet-bottom {
|
||||
position: fixed !important;
|
||||
}
|
||||
.leaflet-control-layers-list {
|
||||
margin-block-start: 0 !important;
|
||||
}
|
||||
.leaflet-bottom .leaflet-control-layers {
|
||||
margin-bottom: 55px !important;
|
||||
}
|
||||
.marker-cluster span {
|
||||
color: #000;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.leaflet-popup-close-button span {
|
||||
font-size: 30px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.leaflet-container a.leaflet-popup-close-button {
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
}
|
||||
BIN
img/button_contact.jpg
Normal file
|
After Width: | Height: | Size: 139 KiB |
BIN
img/button_self.jpg
Normal file
|
After Width: | Height: | Size: 139 KiB |
1
img/node_types/1.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512"><style>.a{fill:#667b89}.b{fill:#fff}</style><path fill-rule="evenodd" class="a" d="m256 512c-141.6 0-256-114.4-256-256 0-141.6 114.4-256 256-256 141.6 0 256 114.4 256 256 0 141.6-114.4 256-256 256z"/><path class="b" d="m256.1 256.1c34.5 0 62.4-27.9 62.4-62.4 0-34.5-27.9-62.4-62.4-62.4-34.5 0-62.4 27.9-62.4 62.4 0 34.5 27.9 62.4 62.4 62.4zm0 31.2c-41.6 0-124.8 20.9-124.8 62.4v31.1h249.5v-31.1c0-41.5-83.1-62.4-124.7-62.4z"/></svg>
|
||||
|
After Width: | Height: | Size: 533 B |
1
img/node_types/2.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512"><style>.a{fill:#667b89}.b{fill:#fff}</style><path fill-rule="evenodd" class="a" d="m256 512c-141.6 0-256-114.4-256-256 0-141.6 114.4-256 256-256 141.6 0 256 114.4 256 256 0 141.6-114.4 256-256 256z"/><path fill-rule="evenodd" class="b" d="m196.7 284l15-15c-12.5-12.5-18.7-28.7-18.7-43.6 0-16.2 6.2-32.4 18.7-43.7l-15-14.9c-16.2 16.2-24.9 37.4-24.9 58.6 0 21.2 8.7 42.4 24.9 58.6zm147.1-147.1l-15 14.9c19.9 20 29.9 47.4 29.9 73.6 0 26.2-10 53.6-29.9 73.5l15 15c24.9-24.9 36.1-56.1 36.1-88.5 0-32.4-12.5-63.6-36.1-88.5zm-162 14.9l-15-14.9c-23.7 24.9-36.1 56.1-36.1 88.5 0 32.4 12.4 63.6 36.1 88.5l15-15c-20-19.9-29.9-47.3-29.9-73.5 0-26.2 9.9-53.6 29.9-73.6zm132 132.2c16.2-16.2 25-37.4 25-58.6-1.3-21.2-8.8-42.4-25-58.6l-14.9 14.9c12.5 12.5 18.7 28.7 18.7 43.7 0 16.2-6.2 32.4-18.7 43.6zm-27.4-58.6c0-17.2-14-31.1-31.2-31.1-17.2 0-31.1 13.9-31.1 31.1 0 9.5 4.2 17.7 10.8 23.5l-42 126.1h24.9l8.4-24.9h58.2l8.2 24.9h24.9l-42-126.1c6.6-5.8 10.9-14 10.9-23.5zm-52 99.7l20.8-62.3 20.8 62.3z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
1
img/node_types/3.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512"><style>.a{fill:#667b89}.b{fill:#fff}</style><path fill-rule="evenodd" class="a" d="m256 512c-141.6 0-256-114.4-256-256 0-141.6 114.4-256 256-256 141.6 0 256 114.4 256 256 0 141.6-114.4 256-256 256z"/><path class="b" d="m256 265.4c20.4 0 38.4 4.9 53 11.2 13.5 6 22 19.5 22 34.2v20.2h-150v-20.1c0-14.8 8.5-28.3 22-34.1 14.6-6.5 32.6-11.4 53-11.4zm-100 3.1c13.8 0 25-11.2 25-25 0-13.7-11.2-25-25-25-13.7 0-25 11.3-25 25 0 13.8 11.3 25 25 25zm14.1 13.8c-4.6-0.8-9.2-1.3-14.1-1.3-12.4 0-24.1 2.6-34.7 7.3-9.3 4-15.3 13-15.3 23.1v19.6h56.3v-20.1c0-10.4 2.8-20.1 7.8-28.6zm185.9-13.8c13.8 0 25-11.2 25-25 0-13.8-11.2-25-25-25-13.8 0-25 11.2-25 25 0 13.8 11.2 25 25 25zm50 42.9c0-10.1-6-19.1-15.2-23.1-10.7-4.7-22.4-7.3-34.8-7.3-4.9 0-9.5 0.5-14.1 1.3 5 8.5 7.8 18.2 7.8 28.6v20.1h56.3zm-150-130.4c20.8 0 37.5 16.8 37.5 37.5 0 20.8-16.8 37.5-37.5 37.5-20.7 0-37.5-16.7-37.5-37.5 0-20.7 16.8-37.5 37.5-37.5z"/></svg>
|
||||
|
After Width: | Height: | Size: 1,008 B |
1
img/node_types/4.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512"><style>.a{fill:#667b89}.b{fill:#fff}</style><path fill-rule="evenodd" class="a" d="m256 512c-141.6 0-256-114.4-256-256 0-141.6 114.4-256 256-256 141.6 0 256 114.4 256 256 0 141.6-114.4 256-256 256z"/><path class="b" d="m381 242.1v-27.8h-27.8v-27.7c0-15.3-12.5-27.8-27.8-27.8h-27.7v-27.8h-27.8v27.8h-27.8v-27.8h-27.8v27.8h-27.7c-15.3 0-27.8 12.5-27.8 27.8v27.7h-27.8v27.8h27.8v27.8h-27.8v27.8h27.8v27.7c0 15.3 12.5 27.8 27.8 27.8h27.7v27.8h27.8v-27.8h27.8v27.8h27.8v-27.8h27.7c15.3 0 27.8-12.5 27.8-27.8v-27.7h27.8v-27.8h-27.8v-27.8zm-55.5 83.3h-138.9v-138.8h138.9zm-27.8-111.2h-83.3v83.4h83.3zm-27.8 55.5h-27.8v-27.7h27.8z"/></svg>
|
||||
|
After Width: | Height: | Size: 732 B |
BIN
img/share_contact.jpg
Normal file
|
After Width: | Height: | Size: 169 KiB |
BIN
img/share_self.jpg
Normal file
|
After Width: | Height: | Size: 158 KiB |
99
index.html
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="./css/style.css" />
|
||||
<link rel="stylesheet" href="./lib/css/beer.css" />
|
||||
<link rel="stylesheet" href="./lib/css/leaflet.css" />
|
||||
<link rel="stylesheet" href="./lib/css/MarkerCluster.css" />
|
||||
<link rel="stylesheet" href="./lib/css/MarkerCluster.Default.css" />
|
||||
<script src="./lib/beer.min.js" type="module"></script>
|
||||
<script src="./lib/leaflet.js"></script>
|
||||
<script src="./lib/leaflet.markercluster.js"></script>
|
||||
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
|
||||
<title>MeshCore Node Map</title>
|
||||
</head>
|
||||
<body class="light">
|
||||
<div class="container">
|
||||
<div id="map"></div>
|
||||
<div id="app" v-if="app.nodes" v-cloak>
|
||||
<dialog class="add-dialog background no-round" ref="dialogAddNode">
|
||||
<h6>Add node / Replace node</h6>
|
||||
<div>
|
||||
<div class="tabs">
|
||||
<a class="active" data-ui="#add-self" tabindex="0">Your Node</a>
|
||||
<a data-ui="#add-contact" tabindex="0">From Contacts</a>
|
||||
</div>
|
||||
<div class="page active" id="add-self">
|
||||
<img src="./img/button_self.jpg">
|
||||
<img src="./img/share_self.jpg">
|
||||
</div>
|
||||
<div class="page" id="add-contact">
|
||||
<img src="./img/button_contact.jpg">
|
||||
<img src="./img/share_contact.jpg">
|
||||
</div>
|
||||
</div>
|
||||
<div class="small-space"></div>
|
||||
<div>
|
||||
Please paste your meshcore link.<br>
|
||||
If you use link with same public key as node already on the map, it will get replaced.
|
||||
</div>
|
||||
<div class="field border"><input placeholder="meshcore:// link" v-model="app.link"></div>
|
||||
<nav class="right-align">
|
||||
<button class="transparent link" @click="dialogAddNode.close()">Cancel</button>
|
||||
<button class="transparent link" @click="addNode">Submit</button>
|
||||
</nav>
|
||||
</dialog>
|
||||
<button class="manual-add square round light-blue extra" title="add node from meshcore:// link" @click="dialogAddNode.showModal()"><i class="extra">add</i></button>
|
||||
<div class="stats">
|
||||
<span title="Stats">
|
||||
<i>monitoring</i>
|
||||
</span>
|
||||
<span v-for="stat in stats" v-html="stat"></span>
|
||||
</a>
|
||||
</div>
|
||||
<form class="search no-margin" action="javascript:;">
|
||||
<div class="field border no-margin">
|
||||
<input type="text" class="background" list="nodes" v-model="app.search" placeholder="Search Nodes">
|
||||
<!--button class="filter circle transparent" data-ui="#node-filter">
|
||||
<i>filter_alt</i>
|
||||
</button>
|
||||
<menu class="left no-wrap" id="node-filter" data-ui="#node-filter">
|
||||
<li>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" value="1" v-model="app.nodeFilter"><span>Clients</span>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" value="2" v-model="app.nodeFilter"><span>Repeaters</span>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" value="3" v-model="app.nodeFilter"><span>Room Servers</span>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" value="3" v-model="app.nodeFilter"><span>Sensors</span>
|
||||
</label>
|
||||
</li>
|
||||
</menu-->
|
||||
</div>
|
||||
<article class="search-results no-margin no-padding" v-if="searchResults?.length > 0">
|
||||
<ul class="list no-space border">
|
||||
<li v-for="node in searchResults" tabindex="0" @click="showNode(node)" @keyup.enter="showNode(node)">
|
||||
<img :src="`./img/node_types/${node.type}.svg`" width="32">
|
||||
<div class="max search-text">
|
||||
<h6 class="small" v-html="highlightString(node.adv_name, app.search)"></h6>
|
||||
<div class="search-pkey" v-html="highlightString(node.public_key, app.search)"></div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</article>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
<script type="module" src="./src/map.js"></script>
|
||||
</html>
|
||||
54
lib/Helpers.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
const DEC2HEX = (() => {
|
||||
const alphabet = '0123456789abcdef';
|
||||
const dec2hex16 = [...alphabet];
|
||||
const dec2hex256 = new Array<string> (256);
|
||||
|
||||
for (let i = 0; i < 256; i++) {
|
||||
dec2hex256[i] = `${dec2hex16[(i >>> 4) & 0xF]}${dec2hex16[i & 0xF]}`;
|
||||
}
|
||||
|
||||
return dec2hex256;
|
||||
})();
|
||||
|
||||
const HEX2DEC = (() => {
|
||||
const hex2dec: Record<string, number> = {};
|
||||
|
||||
for ( let i = 0; i < 256; i++ ) {
|
||||
const hex = DEC2HEX[i];
|
||||
const firstLower = hex[0];
|
||||
const firstUpper = firstLower.toUpperCase();
|
||||
const lastLower = hex[1];
|
||||
const lastUpper = lastLower.toUpperCase();
|
||||
|
||||
hex2dec[hex] = i;
|
||||
hex2dec[`${firstLower}${lastUpper}`] = i;
|
||||
hex2dec[`${firstUpper}${lastLower}`] = i;
|
||||
hex2dec[`${firstUpper}${lastUpper}`] = i;
|
||||
}
|
||||
|
||||
return hex2dec;
|
||||
})();
|
||||
|
||||
export function uint8ArrayConcat(arrays: Uint8Array[]) {
|
||||
const totalLength = arrays.reduce((acc, arr) => acc + arr.length, 0);
|
||||
const result = new Uint8Array(totalLength);
|
||||
|
||||
let offset = 0;
|
||||
for(const array of arrays) {
|
||||
result.set(array, offset);
|
||||
offset += array.length;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function hexToUint8Array(hexString: string, maxLength?: number): Uint8Array {
|
||||
const length = maxLength ?? hexString.length / 2;
|
||||
const result = new Uint8Array(length);
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
result[i] = HEX2DEC[hexString.slice (i * 2, (i * 2) + 2)];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
1
lib/beer.min.js
vendored
Normal file
60
lib/css/MarkerCluster.Default.css
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
.marker-cluster-small {
|
||||
background-color: rgba(181, 226, 140, 0.6);
|
||||
}
|
||||
.marker-cluster-small div {
|
||||
background-color: rgba(110, 204, 57, 0.6);
|
||||
}
|
||||
|
||||
.marker-cluster-medium {
|
||||
background-color: rgba(241, 211, 87, 0.6);
|
||||
}
|
||||
.marker-cluster-medium div {
|
||||
background-color: rgba(240, 194, 12, 0.6);
|
||||
}
|
||||
|
||||
.marker-cluster-large {
|
||||
background-color: rgba(253, 156, 115, 0.6);
|
||||
}
|
||||
.marker-cluster-large div {
|
||||
background-color: rgba(241, 128, 23, 0.6);
|
||||
}
|
||||
|
||||
/* IE 6-8 fallback colors */
|
||||
.leaflet-oldie .marker-cluster-small {
|
||||
background-color: rgb(181, 226, 140);
|
||||
}
|
||||
.leaflet-oldie .marker-cluster-small div {
|
||||
background-color: rgb(110, 204, 57);
|
||||
}
|
||||
|
||||
.leaflet-oldie .marker-cluster-medium {
|
||||
background-color: rgb(241, 211, 87);
|
||||
}
|
||||
.leaflet-oldie .marker-cluster-medium div {
|
||||
background-color: rgb(240, 194, 12);
|
||||
}
|
||||
|
||||
.leaflet-oldie .marker-cluster-large {
|
||||
background-color: rgb(253, 156, 115);
|
||||
}
|
||||
.leaflet-oldie .marker-cluster-large div {
|
||||
background-color: rgb(241, 128, 23);
|
||||
}
|
||||
|
||||
.marker-cluster {
|
||||
background-clip: padding-box;
|
||||
border-radius: 20px;
|
||||
}
|
||||
.marker-cluster div {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin-left: 5px;
|
||||
margin-top: 5px;
|
||||
|
||||
text-align: center;
|
||||
border-radius: 15px;
|
||||
font: 12px "Helvetica Neue", Arial, Helvetica, sans-serif;
|
||||
}
|
||||
.marker-cluster span {
|
||||
line-height: 30px;
|
||||
}
|
||||
14
lib/css/MarkerCluster.css
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
.leaflet-cluster-anim .leaflet-marker-icon, .leaflet-cluster-anim .leaflet-marker-shadow {
|
||||
-webkit-transition: -webkit-transform 0.3s ease-out, opacity 0.3s ease-in;
|
||||
-moz-transition: -moz-transform 0.3s ease-out, opacity 0.3s ease-in;
|
||||
-o-transition: -o-transform 0.3s ease-out, opacity 0.3s ease-in;
|
||||
transition: transform 0.3s ease-out, opacity 0.3s ease-in;
|
||||
}
|
||||
|
||||
.leaflet-cluster-spider-leg {
|
||||
/* stroke-dashoffset (duration and function) should match with leaflet-marker-icon transform in order to track it exactly */
|
||||
-webkit-transition: -webkit-stroke-dashoffset 0.3s ease-out, -webkit-stroke-opacity 0.3s ease-in;
|
||||
-moz-transition: -moz-stroke-dashoffset 0.3s ease-out, -moz-stroke-opacity 0.3s ease-in;
|
||||
-o-transition: -o-stroke-dashoffset 0.3s ease-out, -o-stroke-opacity 0.3s ease-in;
|
||||
transition: stroke-dashoffset 0.3s ease-out, stroke-opacity 0.3s ease-in;
|
||||
}
|
||||
1
lib/css/beer.css
Normal file
BIN
lib/css/images/layers-2x.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
661
lib/css/leaflet.css
Normal file
|
|
@ -0,0 +1,661 @@
|
|||
/* required styles */
|
||||
|
||||
.leaflet-pane,
|
||||
.leaflet-tile,
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow,
|
||||
.leaflet-tile-container,
|
||||
.leaflet-pane > svg,
|
||||
.leaflet-pane > canvas,
|
||||
.leaflet-zoom-box,
|
||||
.leaflet-image-layer,
|
||||
.leaflet-layer {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
.leaflet-container {
|
||||
overflow: hidden;
|
||||
}
|
||||
.leaflet-tile,
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
}
|
||||
/* Prevents IE11 from highlighting tiles in blue */
|
||||
.leaflet-tile::selection {
|
||||
background: transparent;
|
||||
}
|
||||
/* Safari renders non-retina tile on retina better with this, but Chrome is worse */
|
||||
.leaflet-safari .leaflet-tile {
|
||||
image-rendering: -webkit-optimize-contrast;
|
||||
}
|
||||
/* hack that prevents hw layers "stretching" when loading new tiles */
|
||||
.leaflet-safari .leaflet-tile-container {
|
||||
width: 1600px;
|
||||
height: 1600px;
|
||||
-webkit-transform-origin: 0 0;
|
||||
}
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow {
|
||||
display: block;
|
||||
}
|
||||
/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
|
||||
/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
|
||||
.leaflet-container .leaflet-overlay-pane svg {
|
||||
max-width: none !important;
|
||||
max-height: none !important;
|
||||
}
|
||||
.leaflet-container .leaflet-marker-pane img,
|
||||
.leaflet-container .leaflet-shadow-pane img,
|
||||
.leaflet-container .leaflet-tile-pane img,
|
||||
.leaflet-container img.leaflet-image-layer,
|
||||
.leaflet-container .leaflet-tile {
|
||||
max-width: none !important;
|
||||
max-height: none !important;
|
||||
width: auto;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.leaflet-container img.leaflet-tile {
|
||||
/* See: https://bugs.chromium.org/p/chromium/issues/detail?id=600120 */
|
||||
mix-blend-mode: plus-lighter;
|
||||
}
|
||||
|
||||
.leaflet-container.leaflet-touch-zoom {
|
||||
-ms-touch-action: pan-x pan-y;
|
||||
touch-action: pan-x pan-y;
|
||||
}
|
||||
.leaflet-container.leaflet-touch-drag {
|
||||
-ms-touch-action: pinch-zoom;
|
||||
/* Fallback for FF which doesn't support pinch-zoom */
|
||||
touch-action: none;
|
||||
touch-action: pinch-zoom;
|
||||
}
|
||||
.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {
|
||||
-ms-touch-action: none;
|
||||
touch-action: none;
|
||||
}
|
||||
.leaflet-container {
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
.leaflet-container a {
|
||||
-webkit-tap-highlight-color: rgba(51, 181, 229, 0.4);
|
||||
}
|
||||
.leaflet-tile {
|
||||
filter: inherit;
|
||||
visibility: hidden;
|
||||
}
|
||||
.leaflet-tile-loaded {
|
||||
visibility: inherit;
|
||||
}
|
||||
.leaflet-zoom-box {
|
||||
width: 0;
|
||||
height: 0;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
z-index: 800;
|
||||
}
|
||||
/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
|
||||
.leaflet-overlay-pane svg {
|
||||
-moz-user-select: none;
|
||||
}
|
||||
|
||||
.leaflet-pane { z-index: 400; }
|
||||
|
||||
.leaflet-tile-pane { z-index: 200; }
|
||||
.leaflet-overlay-pane { z-index: 400; }
|
||||
.leaflet-shadow-pane { z-index: 500; }
|
||||
.leaflet-marker-pane { z-index: 600; }
|
||||
.leaflet-tooltip-pane { z-index: 650; }
|
||||
.leaflet-popup-pane { z-index: 700; }
|
||||
|
||||
.leaflet-map-pane canvas { z-index: 100; }
|
||||
.leaflet-map-pane svg { z-index: 200; }
|
||||
|
||||
.leaflet-vml-shape {
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
}
|
||||
.lvml {
|
||||
behavior: url(#default#VML);
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
|
||||
/* control positioning */
|
||||
|
||||
.leaflet-control {
|
||||
position: relative;
|
||||
z-index: 800;
|
||||
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
|
||||
pointer-events: auto;
|
||||
}
|
||||
.leaflet-top,
|
||||
.leaflet-bottom {
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
pointer-events: none;
|
||||
}
|
||||
.leaflet-top {
|
||||
top: 0;
|
||||
}
|
||||
.leaflet-right {
|
||||
right: 0;
|
||||
}
|
||||
.leaflet-bottom {
|
||||
bottom: 0;
|
||||
}
|
||||
.leaflet-left {
|
||||
left: 0;
|
||||
}
|
||||
.leaflet-control {
|
||||
float: left;
|
||||
clear: both;
|
||||
}
|
||||
.leaflet-right .leaflet-control {
|
||||
float: right;
|
||||
}
|
||||
.leaflet-top .leaflet-control {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.leaflet-bottom .leaflet-control {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.leaflet-left .leaflet-control {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.leaflet-right .leaflet-control {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
|
||||
/* zoom and fade animations */
|
||||
|
||||
.leaflet-fade-anim .leaflet-popup {
|
||||
opacity: 0;
|
||||
-webkit-transition: opacity 0.2s linear;
|
||||
-moz-transition: opacity 0.2s linear;
|
||||
transition: opacity 0.2s linear;
|
||||
}
|
||||
.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
|
||||
opacity: 1;
|
||||
}
|
||||
.leaflet-zoom-animated {
|
||||
-webkit-transform-origin: 0 0;
|
||||
-ms-transform-origin: 0 0;
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
svg.leaflet-zoom-animated {
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.leaflet-zoom-anim .leaflet-zoom-animated {
|
||||
-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||
-moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||
transition: transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||
}
|
||||
.leaflet-zoom-anim .leaflet-tile,
|
||||
.leaflet-pan-anim .leaflet-tile {
|
||||
-webkit-transition: none;
|
||||
-moz-transition: none;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.leaflet-zoom-anim .leaflet-zoom-hide {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
|
||||
/* cursors */
|
||||
|
||||
.leaflet-interactive {
|
||||
cursor: pointer;
|
||||
}
|
||||
.leaflet-grab {
|
||||
cursor: -webkit-grab;
|
||||
cursor: -moz-grab;
|
||||
cursor: grab;
|
||||
}
|
||||
.leaflet-crosshair,
|
||||
.leaflet-crosshair .leaflet-interactive {
|
||||
cursor: crosshair;
|
||||
}
|
||||
.leaflet-popup-pane,
|
||||
.leaflet-control {
|
||||
cursor: auto;
|
||||
}
|
||||
.leaflet-dragging .leaflet-grab,
|
||||
.leaflet-dragging .leaflet-grab .leaflet-interactive,
|
||||
.leaflet-dragging .leaflet-marker-draggable {
|
||||
cursor: move;
|
||||
cursor: -webkit-grabbing;
|
||||
cursor: -moz-grabbing;
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
/* marker & overlays interactivity */
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow,
|
||||
.leaflet-image-layer,
|
||||
.leaflet-pane > svg path,
|
||||
.leaflet-tile-container {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.leaflet-marker-icon.leaflet-interactive,
|
||||
.leaflet-image-layer.leaflet-interactive,
|
||||
.leaflet-pane > svg path.leaflet-interactive,
|
||||
svg.leaflet-image-layer.leaflet-interactive path {
|
||||
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
/* visual tweaks */
|
||||
|
||||
.leaflet-container {
|
||||
background: #ddd;
|
||||
outline-offset: 1px;
|
||||
}
|
||||
.leaflet-container a {
|
||||
color: #0078A8;
|
||||
}
|
||||
.leaflet-zoom-box {
|
||||
border: 2px dotted #38f;
|
||||
background: rgba(255,255,255,0.5);
|
||||
}
|
||||
|
||||
|
||||
/* general typography */
|
||||
.leaflet-container {
|
||||
font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
|
||||
font-size: 12px;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
|
||||
/* general toolbar styles */
|
||||
|
||||
.leaflet-bar {
|
||||
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.leaflet-bar a {
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #ccc;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
line-height: 26px;
|
||||
display: block;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
}
|
||||
.leaflet-bar a,
|
||||
.leaflet-control-layers-toggle {
|
||||
background-position: 50% 50%;
|
||||
background-repeat: no-repeat;
|
||||
display: block;
|
||||
}
|
||||
.leaflet-bar a:hover,
|
||||
.leaflet-bar a:focus {
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
.leaflet-bar a:first-child {
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
}
|
||||
.leaflet-bar a:last-child {
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
border-bottom: none;
|
||||
}
|
||||
.leaflet-bar a.leaflet-disabled {
|
||||
cursor: default;
|
||||
background-color: #f4f4f4;
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.leaflet-touch .leaflet-bar a {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
}
|
||||
.leaflet-touch .leaflet-bar a:first-child {
|
||||
border-top-left-radius: 2px;
|
||||
border-top-right-radius: 2px;
|
||||
}
|
||||
.leaflet-touch .leaflet-bar a:last-child {
|
||||
border-bottom-left-radius: 2px;
|
||||
border-bottom-right-radius: 2px;
|
||||
}
|
||||
|
||||
/* zoom control */
|
||||
|
||||
.leaflet-control-zoom-in,
|
||||
.leaflet-control-zoom-out {
|
||||
font: bold 18px 'Lucida Console', Monaco, monospace;
|
||||
text-indent: 1px;
|
||||
}
|
||||
|
||||
.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
|
||||
/* layers control */
|
||||
|
||||
.leaflet-control-layers {
|
||||
box-shadow: 0 1px 5px rgba(0,0,0,0.4);
|
||||
background: #fff;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.leaflet-control-layers-toggle {
|
||||
background-image: url(images/layers.png);
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
.leaflet-retina .leaflet-control-layers-toggle {
|
||||
background-image: url(images/layers-2x.png);
|
||||
background-size: 26px 26px;
|
||||
}
|
||||
.leaflet-touch .leaflet-control-layers-toggle {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
}
|
||||
.leaflet-control-layers .leaflet-control-layers-list,
|
||||
.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
|
||||
display: none;
|
||||
}
|
||||
.leaflet-control-layers-expanded .leaflet-control-layers-list {
|
||||
display: block;
|
||||
position: relative;
|
||||
}
|
||||
.leaflet-control-layers-expanded {
|
||||
padding: 6px 10px 6px 6px;
|
||||
color: #333;
|
||||
background: #fff;
|
||||
}
|
||||
.leaflet-control-layers-scrollbar {
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
padding-right: 5px;
|
||||
}
|
||||
.leaflet-control-layers-selector {
|
||||
margin-top: 2px;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
.leaflet-control-layers label {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
font-size: 1.08333em;
|
||||
}
|
||||
.leaflet-control-layers-separator {
|
||||
height: 0;
|
||||
border-top: 1px solid #ddd;
|
||||
margin: 5px -10px 5px -6px;
|
||||
}
|
||||
|
||||
/* Default icon URLs */
|
||||
.leaflet-default-icon-path { /* used only in path-guessing heuristic, see L.Icon.Default */
|
||||
background-image: url(images/marker-icon.png);
|
||||
}
|
||||
|
||||
|
||||
/* attribution and scale controls */
|
||||
|
||||
.leaflet-container .leaflet-control-attribution {
|
||||
background: #fff;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
margin: 0;
|
||||
}
|
||||
.leaflet-control-attribution,
|
||||
.leaflet-control-scale-line {
|
||||
padding: 0 5px;
|
||||
color: #333;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.leaflet-control-attribution a {
|
||||
text-decoration: none;
|
||||
}
|
||||
.leaflet-control-attribution a:hover,
|
||||
.leaflet-control-attribution a:focus {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.leaflet-attribution-flag {
|
||||
display: inline !important;
|
||||
vertical-align: baseline !important;
|
||||
width: 1em;
|
||||
height: 0.6669em;
|
||||
}
|
||||
.leaflet-left .leaflet-control-scale {
|
||||
margin-left: 5px;
|
||||
}
|
||||
.leaflet-bottom .leaflet-control-scale {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.leaflet-control-scale-line {
|
||||
border: 2px solid #777;
|
||||
border-top: none;
|
||||
line-height: 1.1;
|
||||
padding: 2px 5px 1px;
|
||||
white-space: nowrap;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
text-shadow: 1px 1px #fff;
|
||||
}
|
||||
.leaflet-control-scale-line:not(:first-child) {
|
||||
border-top: 2px solid #777;
|
||||
border-bottom: none;
|
||||
margin-top: -2px;
|
||||
}
|
||||
.leaflet-control-scale-line:not(:first-child):not(:last-child) {
|
||||
border-bottom: 2px solid #777;
|
||||
}
|
||||
|
||||
.leaflet-touch .leaflet-control-attribution,
|
||||
.leaflet-touch .leaflet-control-layers,
|
||||
.leaflet-touch .leaflet-bar {
|
||||
box-shadow: none;
|
||||
}
|
||||
.leaflet-touch .leaflet-control-layers,
|
||||
.leaflet-touch .leaflet-bar {
|
||||
border: 2px solid rgba(0,0,0,0.2);
|
||||
background-clip: padding-box;
|
||||
}
|
||||
|
||||
|
||||
/* popup */
|
||||
|
||||
.leaflet-popup {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.leaflet-popup-content-wrapper {
|
||||
padding: 1px;
|
||||
text-align: left;
|
||||
border-radius: 12px;
|
||||
}
|
||||
.leaflet-popup-content {
|
||||
margin: 13px 24px 13px 20px;
|
||||
line-height: 1.3;
|
||||
font-size: 13px;
|
||||
font-size: 1.08333em;
|
||||
min-height: 1px;
|
||||
}
|
||||
.leaflet-popup-content p {
|
||||
margin: 17px 0;
|
||||
margin: 1.3em 0;
|
||||
}
|
||||
.leaflet-popup-tip-container {
|
||||
width: 40px;
|
||||
height: 20px;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
margin-top: -1px;
|
||||
margin-left: -20px;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
.leaflet-popup-tip {
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
padding: 1px;
|
||||
|
||||
margin: -10px auto 0;
|
||||
pointer-events: auto;
|
||||
|
||||
-webkit-transform: rotate(45deg);
|
||||
-moz-transform: rotate(45deg);
|
||||
-ms-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
.leaflet-popup-content-wrapper,
|
||||
.leaflet-popup-tip {
|
||||
background: white;
|
||||
color: #333;
|
||||
box-shadow: 0 3px 14px rgba(0,0,0,0.4);
|
||||
}
|
||||
.leaflet-container a.leaflet-popup-close-button {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
border: none;
|
||||
text-align: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
font: 16px/24px Tahoma, Verdana, sans-serif;
|
||||
color: #757575;
|
||||
text-decoration: none;
|
||||
background: transparent;
|
||||
}
|
||||
.leaflet-container a.leaflet-popup-close-button:hover,
|
||||
.leaflet-container a.leaflet-popup-close-button:focus {
|
||||
color: #585858;
|
||||
}
|
||||
.leaflet-popup-scrolled {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.leaflet-oldie .leaflet-popup-content-wrapper {
|
||||
-ms-zoom: 1;
|
||||
}
|
||||
.leaflet-oldie .leaflet-popup-tip {
|
||||
width: 24px;
|
||||
margin: 0 auto;
|
||||
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
|
||||
filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
|
||||
}
|
||||
|
||||
.leaflet-oldie .leaflet-control-zoom,
|
||||
.leaflet-oldie .leaflet-control-layers,
|
||||
.leaflet-oldie .leaflet-popup-content-wrapper,
|
||||
.leaflet-oldie .leaflet-popup-tip {
|
||||
border: 1px solid #999;
|
||||
}
|
||||
|
||||
|
||||
/* div icon */
|
||||
|
||||
.leaflet-div-icon {
|
||||
background: #fff;
|
||||
border: 1px solid #666;
|
||||
}
|
||||
|
||||
|
||||
/* Tooltip */
|
||||
/* Base styles for the element that has a tooltip */
|
||||
.leaflet-tooltip {
|
||||
position: absolute;
|
||||
padding: 6px;
|
||||
background-color: #fff;
|
||||
border: 1px solid #fff;
|
||||
border-radius: 3px;
|
||||
color: #222;
|
||||
white-space: nowrap;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.4);
|
||||
}
|
||||
.leaflet-tooltip.leaflet-interactive {
|
||||
cursor: pointer;
|
||||
pointer-events: auto;
|
||||
}
|
||||
.leaflet-tooltip-top:before,
|
||||
.leaflet-tooltip-bottom:before,
|
||||
.leaflet-tooltip-left:before,
|
||||
.leaflet-tooltip-right:before {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
border: 6px solid transparent;
|
||||
background: transparent;
|
||||
content: "";
|
||||
}
|
||||
|
||||
/* Directions */
|
||||
|
||||
.leaflet-tooltip-bottom {
|
||||
margin-top: 6px;
|
||||
}
|
||||
.leaflet-tooltip-top {
|
||||
margin-top: -6px;
|
||||
}
|
||||
.leaflet-tooltip-bottom:before,
|
||||
.leaflet-tooltip-top:before {
|
||||
left: 50%;
|
||||
margin-left: -6px;
|
||||
}
|
||||
.leaflet-tooltip-top:before {
|
||||
bottom: 0;
|
||||
margin-bottom: -12px;
|
||||
border-top-color: #fff;
|
||||
}
|
||||
.leaflet-tooltip-bottom:before {
|
||||
top: 0;
|
||||
margin-top: -12px;
|
||||
margin-left: -6px;
|
||||
border-bottom-color: #fff;
|
||||
}
|
||||
.leaflet-tooltip-left {
|
||||
margin-left: -6px;
|
||||
}
|
||||
.leaflet-tooltip-right {
|
||||
margin-left: 6px;
|
||||
}
|
||||
.leaflet-tooltip-left:before,
|
||||
.leaflet-tooltip-right:before {
|
||||
top: 50%;
|
||||
margin-top: -6px;
|
||||
}
|
||||
.leaflet-tooltip-left:before {
|
||||
right: 0;
|
||||
margin-right: -12px;
|
||||
border-left-color: #fff;
|
||||
}
|
||||
.leaflet-tooltip-right:before {
|
||||
left: 0;
|
||||
margin-left: -12px;
|
||||
border-right-color: #fff;
|
||||
}
|
||||
|
||||
/* Printing */
|
||||
|
||||
@media print {
|
||||
/* Prevent printers from removing background-images of controls. */
|
||||
.leaflet-control {
|
||||
-webkit-print-color-adjust: exact;
|
||||
print-color-adjust: exact;
|
||||
}
|
||||
}
|
||||
BIN
lib/images/layers-2x.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
661
lib/leaflet.css
Normal file
|
|
@ -0,0 +1,661 @@
|
|||
/* required styles */
|
||||
|
||||
.leaflet-pane,
|
||||
.leaflet-tile,
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow,
|
||||
.leaflet-tile-container,
|
||||
.leaflet-pane > svg,
|
||||
.leaflet-pane > canvas,
|
||||
.leaflet-zoom-box,
|
||||
.leaflet-image-layer,
|
||||
.leaflet-layer {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
.leaflet-container {
|
||||
overflow: hidden;
|
||||
}
|
||||
.leaflet-tile,
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
}
|
||||
/* Prevents IE11 from highlighting tiles in blue */
|
||||
.leaflet-tile::selection {
|
||||
background: transparent;
|
||||
}
|
||||
/* Safari renders non-retina tile on retina better with this, but Chrome is worse */
|
||||
.leaflet-safari .leaflet-tile {
|
||||
image-rendering: -webkit-optimize-contrast;
|
||||
}
|
||||
/* hack that prevents hw layers "stretching" when loading new tiles */
|
||||
.leaflet-safari .leaflet-tile-container {
|
||||
width: 1600px;
|
||||
height: 1600px;
|
||||
-webkit-transform-origin: 0 0;
|
||||
}
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow {
|
||||
display: block;
|
||||
}
|
||||
/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
|
||||
/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
|
||||
.leaflet-container .leaflet-overlay-pane svg {
|
||||
max-width: none !important;
|
||||
max-height: none !important;
|
||||
}
|
||||
.leaflet-container .leaflet-marker-pane img,
|
||||
.leaflet-container .leaflet-shadow-pane img,
|
||||
.leaflet-container .leaflet-tile-pane img,
|
||||
.leaflet-container img.leaflet-image-layer,
|
||||
.leaflet-container .leaflet-tile {
|
||||
max-width: none !important;
|
||||
max-height: none !important;
|
||||
width: auto;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.leaflet-container img.leaflet-tile {
|
||||
/* See: https://bugs.chromium.org/p/chromium/issues/detail?id=600120 */
|
||||
mix-blend-mode: plus-lighter;
|
||||
}
|
||||
|
||||
.leaflet-container.leaflet-touch-zoom {
|
||||
-ms-touch-action: pan-x pan-y;
|
||||
touch-action: pan-x pan-y;
|
||||
}
|
||||
.leaflet-container.leaflet-touch-drag {
|
||||
-ms-touch-action: pinch-zoom;
|
||||
/* Fallback for FF which doesn't support pinch-zoom */
|
||||
touch-action: none;
|
||||
touch-action: pinch-zoom;
|
||||
}
|
||||
.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {
|
||||
-ms-touch-action: none;
|
||||
touch-action: none;
|
||||
}
|
||||
.leaflet-container {
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
.leaflet-container a {
|
||||
-webkit-tap-highlight-color: rgba(51, 181, 229, 0.4);
|
||||
}
|
||||
.leaflet-tile {
|
||||
filter: inherit;
|
||||
visibility: hidden;
|
||||
}
|
||||
.leaflet-tile-loaded {
|
||||
visibility: inherit;
|
||||
}
|
||||
.leaflet-zoom-box {
|
||||
width: 0;
|
||||
height: 0;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
z-index: 800;
|
||||
}
|
||||
/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
|
||||
.leaflet-overlay-pane svg {
|
||||
-moz-user-select: none;
|
||||
}
|
||||
|
||||
.leaflet-pane { z-index: 400; }
|
||||
|
||||
.leaflet-tile-pane { z-index: 200; }
|
||||
.leaflet-overlay-pane { z-index: 400; }
|
||||
.leaflet-shadow-pane { z-index: 500; }
|
||||
.leaflet-marker-pane { z-index: 600; }
|
||||
.leaflet-tooltip-pane { z-index: 650; }
|
||||
.leaflet-popup-pane { z-index: 700; }
|
||||
|
||||
.leaflet-map-pane canvas { z-index: 100; }
|
||||
.leaflet-map-pane svg { z-index: 200; }
|
||||
|
||||
.leaflet-vml-shape {
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
}
|
||||
.lvml {
|
||||
behavior: url(#default#VML);
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
|
||||
/* control positioning */
|
||||
|
||||
.leaflet-control {
|
||||
position: relative;
|
||||
z-index: 800;
|
||||
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
|
||||
pointer-events: auto;
|
||||
}
|
||||
.leaflet-top,
|
||||
.leaflet-bottom {
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
pointer-events: none;
|
||||
}
|
||||
.leaflet-top {
|
||||
top: 0;
|
||||
}
|
||||
.leaflet-right {
|
||||
right: 0;
|
||||
}
|
||||
.leaflet-bottom {
|
||||
bottom: 0;
|
||||
}
|
||||
.leaflet-left {
|
||||
left: 0;
|
||||
}
|
||||
.leaflet-control {
|
||||
float: left;
|
||||
clear: both;
|
||||
}
|
||||
.leaflet-right .leaflet-control {
|
||||
float: right;
|
||||
}
|
||||
.leaflet-top .leaflet-control {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.leaflet-bottom .leaflet-control {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.leaflet-left .leaflet-control {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.leaflet-right .leaflet-control {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
|
||||
/* zoom and fade animations */
|
||||
|
||||
.leaflet-fade-anim .leaflet-popup {
|
||||
opacity: 0;
|
||||
-webkit-transition: opacity 0.2s linear;
|
||||
-moz-transition: opacity 0.2s linear;
|
||||
transition: opacity 0.2s linear;
|
||||
}
|
||||
.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
|
||||
opacity: 1;
|
||||
}
|
||||
.leaflet-zoom-animated {
|
||||
-webkit-transform-origin: 0 0;
|
||||
-ms-transform-origin: 0 0;
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
svg.leaflet-zoom-animated {
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.leaflet-zoom-anim .leaflet-zoom-animated {
|
||||
-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||
-moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||
transition: transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||
}
|
||||
.leaflet-zoom-anim .leaflet-tile,
|
||||
.leaflet-pan-anim .leaflet-tile {
|
||||
-webkit-transition: none;
|
||||
-moz-transition: none;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.leaflet-zoom-anim .leaflet-zoom-hide {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
|
||||
/* cursors */
|
||||
|
||||
.leaflet-interactive {
|
||||
cursor: pointer;
|
||||
}
|
||||
.leaflet-grab {
|
||||
cursor: -webkit-grab;
|
||||
cursor: -moz-grab;
|
||||
cursor: grab;
|
||||
}
|
||||
.leaflet-crosshair,
|
||||
.leaflet-crosshair .leaflet-interactive {
|
||||
cursor: crosshair;
|
||||
}
|
||||
.leaflet-popup-pane,
|
||||
.leaflet-control {
|
||||
cursor: auto;
|
||||
}
|
||||
.leaflet-dragging .leaflet-grab,
|
||||
.leaflet-dragging .leaflet-grab .leaflet-interactive,
|
||||
.leaflet-dragging .leaflet-marker-draggable {
|
||||
cursor: move;
|
||||
cursor: -webkit-grabbing;
|
||||
cursor: -moz-grabbing;
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
/* marker & overlays interactivity */
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow,
|
||||
.leaflet-image-layer,
|
||||
.leaflet-pane > svg path,
|
||||
.leaflet-tile-container {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.leaflet-marker-icon.leaflet-interactive,
|
||||
.leaflet-image-layer.leaflet-interactive,
|
||||
.leaflet-pane > svg path.leaflet-interactive,
|
||||
svg.leaflet-image-layer.leaflet-interactive path {
|
||||
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
/* visual tweaks */
|
||||
|
||||
.leaflet-container {
|
||||
background: #ddd;
|
||||
outline-offset: 1px;
|
||||
}
|
||||
.leaflet-container a {
|
||||
color: #0078A8;
|
||||
}
|
||||
.leaflet-zoom-box {
|
||||
border: 2px dotted #38f;
|
||||
background: rgba(255,255,255,0.5);
|
||||
}
|
||||
|
||||
|
||||
/* general typography */
|
||||
.leaflet-container {
|
||||
font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
|
||||
font-size: 12px;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
|
||||
/* general toolbar styles */
|
||||
|
||||
.leaflet-bar {
|
||||
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.leaflet-bar a {
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #ccc;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
line-height: 26px;
|
||||
display: block;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
}
|
||||
.leaflet-bar a,
|
||||
.leaflet-control-layers-toggle {
|
||||
background-position: 50% 50%;
|
||||
background-repeat: no-repeat;
|
||||
display: block;
|
||||
}
|
||||
.leaflet-bar a:hover,
|
||||
.leaflet-bar a:focus {
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
.leaflet-bar a:first-child {
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
}
|
||||
.leaflet-bar a:last-child {
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
border-bottom: none;
|
||||
}
|
||||
.leaflet-bar a.leaflet-disabled {
|
||||
cursor: default;
|
||||
background-color: #f4f4f4;
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.leaflet-touch .leaflet-bar a {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
}
|
||||
.leaflet-touch .leaflet-bar a:first-child {
|
||||
border-top-left-radius: 2px;
|
||||
border-top-right-radius: 2px;
|
||||
}
|
||||
.leaflet-touch .leaflet-bar a:last-child {
|
||||
border-bottom-left-radius: 2px;
|
||||
border-bottom-right-radius: 2px;
|
||||
}
|
||||
|
||||
/* zoom control */
|
||||
|
||||
.leaflet-control-zoom-in,
|
||||
.leaflet-control-zoom-out {
|
||||
font: bold 18px 'Lucida Console', Monaco, monospace;
|
||||
text-indent: 1px;
|
||||
}
|
||||
|
||||
.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
|
||||
/* layers control */
|
||||
|
||||
.leaflet-control-layers {
|
||||
box-shadow: 0 1px 5px rgba(0,0,0,0.4);
|
||||
background: #fff;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.leaflet-control-layers-toggle {
|
||||
background-image: url(images/layers.png);
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
.leaflet-retina .leaflet-control-layers-toggle {
|
||||
background-image: url(images/layers-2x.png);
|
||||
background-size: 26px 26px;
|
||||
}
|
||||
.leaflet-touch .leaflet-control-layers-toggle {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
}
|
||||
.leaflet-control-layers .leaflet-control-layers-list,
|
||||
.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
|
||||
display: none;
|
||||
}
|
||||
.leaflet-control-layers-expanded .leaflet-control-layers-list {
|
||||
display: block;
|
||||
position: relative;
|
||||
}
|
||||
.leaflet-control-layers-expanded {
|
||||
padding: 6px 10px 6px 6px;
|
||||
color: #333;
|
||||
background: #fff;
|
||||
}
|
||||
.leaflet-control-layers-scrollbar {
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
padding-right: 5px;
|
||||
}
|
||||
.leaflet-control-layers-selector {
|
||||
margin-top: 2px;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
.leaflet-control-layers label {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
font-size: 1.08333em;
|
||||
}
|
||||
.leaflet-control-layers-separator {
|
||||
height: 0;
|
||||
border-top: 1px solid #ddd;
|
||||
margin: 5px -10px 5px -6px;
|
||||
}
|
||||
|
||||
/* Default icon URLs */
|
||||
.leaflet-default-icon-path { /* used only in path-guessing heuristic, see L.Icon.Default */
|
||||
background-image: url(images/marker-icon.png);
|
||||
}
|
||||
|
||||
|
||||
/* attribution and scale controls */
|
||||
|
||||
.leaflet-container .leaflet-control-attribution {
|
||||
background: #fff;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
margin: 0;
|
||||
}
|
||||
.leaflet-control-attribution,
|
||||
.leaflet-control-scale-line {
|
||||
padding: 0 5px;
|
||||
color: #333;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.leaflet-control-attribution a {
|
||||
text-decoration: none;
|
||||
}
|
||||
.leaflet-control-attribution a:hover,
|
||||
.leaflet-control-attribution a:focus {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.leaflet-attribution-flag {
|
||||
display: inline !important;
|
||||
vertical-align: baseline !important;
|
||||
width: 1em;
|
||||
height: 0.6669em;
|
||||
}
|
||||
.leaflet-left .leaflet-control-scale {
|
||||
margin-left: 5px;
|
||||
}
|
||||
.leaflet-bottom .leaflet-control-scale {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.leaflet-control-scale-line {
|
||||
border: 2px solid #777;
|
||||
border-top: none;
|
||||
line-height: 1.1;
|
||||
padding: 2px 5px 1px;
|
||||
white-space: nowrap;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
text-shadow: 1px 1px #fff;
|
||||
}
|
||||
.leaflet-control-scale-line:not(:first-child) {
|
||||
border-top: 2px solid #777;
|
||||
border-bottom: none;
|
||||
margin-top: -2px;
|
||||
}
|
||||
.leaflet-control-scale-line:not(:first-child):not(:last-child) {
|
||||
border-bottom: 2px solid #777;
|
||||
}
|
||||
|
||||
.leaflet-touch .leaflet-control-attribution,
|
||||
.leaflet-touch .leaflet-control-layers,
|
||||
.leaflet-touch .leaflet-bar {
|
||||
box-shadow: none;
|
||||
}
|
||||
.leaflet-touch .leaflet-control-layers,
|
||||
.leaflet-touch .leaflet-bar {
|
||||
border: 2px solid rgba(0,0,0,0.2);
|
||||
background-clip: padding-box;
|
||||
}
|
||||
|
||||
|
||||
/* popup */
|
||||
|
||||
.leaflet-popup {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.leaflet-popup-content-wrapper {
|
||||
padding: 1px;
|
||||
text-align: left;
|
||||
border-radius: 12px;
|
||||
}
|
||||
.leaflet-popup-content {
|
||||
margin: 13px 24px 13px 20px;
|
||||
line-height: 1.3;
|
||||
font-size: 13px;
|
||||
font-size: 1.08333em;
|
||||
min-height: 1px;
|
||||
}
|
||||
.leaflet-popup-content p {
|
||||
margin: 17px 0;
|
||||
margin: 1.3em 0;
|
||||
}
|
||||
.leaflet-popup-tip-container {
|
||||
width: 40px;
|
||||
height: 20px;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
margin-top: -1px;
|
||||
margin-left: -20px;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
.leaflet-popup-tip {
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
padding: 1px;
|
||||
|
||||
margin: -10px auto 0;
|
||||
pointer-events: auto;
|
||||
|
||||
-webkit-transform: rotate(45deg);
|
||||
-moz-transform: rotate(45deg);
|
||||
-ms-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
.leaflet-popup-content-wrapper,
|
||||
.leaflet-popup-tip {
|
||||
background: white;
|
||||
color: #333;
|
||||
box-shadow: 0 3px 14px rgba(0,0,0,0.4);
|
||||
}
|
||||
.leaflet-container a.leaflet-popup-close-button {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
border: none;
|
||||
text-align: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
font: 16px/24px Tahoma, Verdana, sans-serif;
|
||||
color: #757575;
|
||||
text-decoration: none;
|
||||
background: transparent;
|
||||
}
|
||||
.leaflet-container a.leaflet-popup-close-button:hover,
|
||||
.leaflet-container a.leaflet-popup-close-button:focus {
|
||||
color: #585858;
|
||||
}
|
||||
.leaflet-popup-scrolled {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.leaflet-oldie .leaflet-popup-content-wrapper {
|
||||
-ms-zoom: 1;
|
||||
}
|
||||
.leaflet-oldie .leaflet-popup-tip {
|
||||
width: 24px;
|
||||
margin: 0 auto;
|
||||
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
|
||||
filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
|
||||
}
|
||||
|
||||
.leaflet-oldie .leaflet-control-zoom,
|
||||
.leaflet-oldie .leaflet-control-layers,
|
||||
.leaflet-oldie .leaflet-popup-content-wrapper,
|
||||
.leaflet-oldie .leaflet-popup-tip {
|
||||
border: 1px solid #999;
|
||||
}
|
||||
|
||||
|
||||
/* div icon */
|
||||
|
||||
.leaflet-div-icon {
|
||||
background: #fff;
|
||||
border: 1px solid #666;
|
||||
}
|
||||
|
||||
|
||||
/* Tooltip */
|
||||
/* Base styles for the element that has a tooltip */
|
||||
.leaflet-tooltip {
|
||||
position: absolute;
|
||||
padding: 6px;
|
||||
background-color: #fff;
|
||||
border: 1px solid #fff;
|
||||
border-radius: 3px;
|
||||
color: #222;
|
||||
white-space: nowrap;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.4);
|
||||
}
|
||||
.leaflet-tooltip.leaflet-interactive {
|
||||
cursor: pointer;
|
||||
pointer-events: auto;
|
||||
}
|
||||
.leaflet-tooltip-top:before,
|
||||
.leaflet-tooltip-bottom:before,
|
||||
.leaflet-tooltip-left:before,
|
||||
.leaflet-tooltip-right:before {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
border: 6px solid transparent;
|
||||
background: transparent;
|
||||
content: "";
|
||||
}
|
||||
|
||||
/* Directions */
|
||||
|
||||
.leaflet-tooltip-bottom {
|
||||
margin-top: 6px;
|
||||
}
|
||||
.leaflet-tooltip-top {
|
||||
margin-top: -6px;
|
||||
}
|
||||
.leaflet-tooltip-bottom:before,
|
||||
.leaflet-tooltip-top:before {
|
||||
left: 50%;
|
||||
margin-left: -6px;
|
||||
}
|
||||
.leaflet-tooltip-top:before {
|
||||
bottom: 0;
|
||||
margin-bottom: -12px;
|
||||
border-top-color: #fff;
|
||||
}
|
||||
.leaflet-tooltip-bottom:before {
|
||||
top: 0;
|
||||
margin-top: -12px;
|
||||
margin-left: -6px;
|
||||
border-bottom-color: #fff;
|
||||
}
|
||||
.leaflet-tooltip-left {
|
||||
margin-left: -6px;
|
||||
}
|
||||
.leaflet-tooltip-right {
|
||||
margin-left: 6px;
|
||||
}
|
||||
.leaflet-tooltip-left:before,
|
||||
.leaflet-tooltip-right:before {
|
||||
top: 50%;
|
||||
margin-top: -6px;
|
||||
}
|
||||
.leaflet-tooltip-left:before {
|
||||
right: 0;
|
||||
margin-right: -12px;
|
||||
border-left-color: #fff;
|
||||
}
|
||||
.leaflet-tooltip-right:before {
|
||||
left: 0;
|
||||
margin-left: -12px;
|
||||
border-right-color: #fff;
|
||||
}
|
||||
|
||||
/* Printing */
|
||||
|
||||
@media print {
|
||||
/* Prevent printers from removing background-images of controls. */
|
||||
.leaflet-control {
|
||||
-webkit-print-color-adjust: exact;
|
||||
print-color-adjust: exact;
|
||||
}
|
||||
}
|
||||
6
lib/leaflet.js
Normal file
3
lib/leaflet.markercluster.js
Normal file
18216
lib/vue.esm-browser.js
Normal file
0
lib/vue.esm-browser.min.js
vendored
Normal file
228
src/map.js
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
import { createApp, reactive, ref, computed, onMounted } from '../lib/vue.esm-browser.js';
|
||||
|
||||
const apiUrl = '/api/v1/nodes';
|
||||
const keyOrder = ['adv_name', 'type', 'link', 'last_advert', 'public_key', 'coords', 'params' ]
|
||||
const humanLabel = {
|
||||
coords: 'Coordinates',
|
||||
adv_name: 'Name',
|
||||
last_advert: 'Last heard',
|
||||
public_key: 'Public key',
|
||||
type: 'Node type',
|
||||
params: 'Radio params',
|
||||
link: 'Meshcore link',
|
||||
};
|
||||
|
||||
const types = {
|
||||
'1': 'Client',
|
||||
'2': 'Repeater',
|
||||
'3': 'Room Server',
|
||||
'4': 'Sensor'
|
||||
};
|
||||
|
||||
const humanValue = {
|
||||
last_advert(val) {
|
||||
return new Date(val).toLocaleString();
|
||||
},
|
||||
coords(val) {
|
||||
return `<a target="_blank" href="https://google.com/maps/place/${val.replace(' ', '')}">${val}</a>`;
|
||||
},
|
||||
type(val) {
|
||||
return types[val];
|
||||
},
|
||||
link(val) {
|
||||
return `<a href="javascript:navigator.clipboard.writeText('${val}')">Copy to clipboard</a>`
|
||||
},
|
||||
params(val) {
|
||||
return Object.entries(val).map(([key, val]) => `${key}=${val}`).join(', ')
|
||||
}
|
||||
}
|
||||
|
||||
function clearLocationHash () {
|
||||
history.pushState('', document.title, location.pathname + location.search);
|
||||
}
|
||||
|
||||
function getTable(node) {
|
||||
return '<table class="node-info"><tbody>'+
|
||||
'<tr>' + keyOrder.flatMap(key => node[key] ? [`<td><b>${humanLabel[key]}</b></td><td>${ humanValue[key] ? humanValue[key](node[key]) : node[key] }</td>`] : [] ).join('</tr><tr>') + '</tr>'+
|
||||
'</tbody></table>';
|
||||
}
|
||||
|
||||
window.isNewerThan = (date, days) => {
|
||||
const daysMs = 1000 * 3600 * 24 * days;
|
||||
const dateMs = new Date(date).getTime();
|
||||
|
||||
return dateMs > Date.now() - daysMs;
|
||||
}
|
||||
|
||||
const deletionMailUrl = new URL('mailto:recrof@gmail.com');
|
||||
deletionMailUrl.searchParams.append('subject', 'MeshCore Map node deletion request');
|
||||
deletionMailUrl.searchParams.append('body',
|
||||
'Please delete my node from MeshCore Map database\n'+
|
||||
'MeshCore link: <please insert meshcore:// link here>\n'
|
||||
);
|
||||
|
||||
const appAttribution = `
|
||||
App: recrof, <a target="_blank" href="https://www.paypal.com/donate/?business=DREHF5HM265ES&no_recurring=0&item_name=If+you+enjoy+my+work%2C+you+can+support+me+here%3A¤cy_code=EUR">
|
||||
<strong>support my work</strong></a> |
|
||||
<a target="_blank" href="${deletionMailUrl.toString().replaceAll('+', '%20')}"><strong>Node deletion request</strong></a>
|
||||
`;
|
||||
|
||||
const baseMapSelected = localStorage.getItem('baseMapSelected') || 'OpenStreetMap';
|
||||
const baseMaps = {
|
||||
'OpenStreetMap': L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
maxZoom: 19,
|
||||
attribution: `Tiles: © <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> | ${appAttribution}`
|
||||
}),
|
||||
'Esri Satellite': L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
|
||||
maxZoom: 18,
|
||||
attribution: `Tiles: © Esri | Sources: Esri, DigitalGlobe, GeoEye, i-cubed, USDA FSA, USGS, AEX, Getmapping, Aerogrid, IGN, IGP, swisstopo, GIS Users | ${appAttribution}`,
|
||||
}),
|
||||
};
|
||||
|
||||
let initCoords = { lat: 7, lon: 25, zoom: 3 };
|
||||
|
||||
const urlParams = Object.fromEntries(new URLSearchParams(location.search));
|
||||
if(!(isNaN(urlParams.lat) || isNaN(urlParams.lon) || isNaN(urlParams.zoom))) {
|
||||
initCoords = urlParams
|
||||
}
|
||||
|
||||
const map = window.leafletMap = leaflet.map('map', {
|
||||
minZoom: 2,
|
||||
maxBounds: [
|
||||
[-90, -180], // top left
|
||||
[90, 200], // bottom right
|
||||
],
|
||||
layers: baseMaps[baseMapSelected],
|
||||
zoomControl: false
|
||||
}).setView([initCoords.lat, initCoords.lon], initCoords.zoom);
|
||||
|
||||
map.on('baselayerchange', function(ev) {
|
||||
localStorage.setItem('baseMapSelected', ev.name);
|
||||
});
|
||||
|
||||
L.control.layers(baseMaps, null, { position: 'bottomleft' }).addTo(map);
|
||||
|
||||
// map.zoomControl.setPosition('bottomleft');
|
||||
const icons = Object.fromEntries([1, 2, 3, 4].map(id => [id, L.icon({
|
||||
iconUrl: `img/node_types/${id}.svg`,
|
||||
iconSize: [32, 32],
|
||||
iconAnchor: [17, 17],
|
||||
popupAnchor: [0, -16],
|
||||
})]));
|
||||
|
||||
createApp({
|
||||
setup() {
|
||||
const dialogAddNode = ref();
|
||||
const app = window.app = reactive({
|
||||
nodes: null,
|
||||
search: '',
|
||||
link: '',
|
||||
nodeFilter: [1, 2, 3, 4]
|
||||
});
|
||||
|
||||
const stats = computed(() => {
|
||||
const nodes = app.nodes;
|
||||
|
||||
if(!nodes) return [];
|
||||
|
||||
const result = [];
|
||||
result.push(`
|
||||
<span>total: <b>${nodes.length}</b></span> |
|
||||
<i class="node-type pointer-help" title="Total client nodes">person</i><b>${nodes.filter(n => n.type === 1).length}</b> |
|
||||
<i class="node-type pointer-help" title="Total repeater nodes">cell_tower</i><b>${nodes.filter(n => n.type === 2).length}</b> |
|
||||
<i class="node-type pointer-help" title="Total room server nodes">forum</i><b>${nodes.filter(n => n.type === 3).length}</b>
|
||||
`);
|
||||
result.push(`<span class="pointer-help" title="Nodes added in last 24 hours">24h: <b>${app.nodes.filter(n => isNewerThan(n.inserted_date, 1)).length}</b></span>`);
|
||||
result.push(`<span class="pointer-help" title="Nodes added in last 7 days">7d: <b>${app.nodes.filter(n => isNewerThan(n.inserted_date, 7)).length}</b></span>`);
|
||||
result.push(`<span class="pointer-help" title="Nodes added in last 30 days">30d: <b>${app.nodes.filter(n => isNewerThan(n.inserted_date, 30)).length}</b></span>`);
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
const searchResults = computed(() => {
|
||||
if(!app.search) { return [] }
|
||||
|
||||
return app.nodes.filter(
|
||||
node => node.adv_name.toLowerCase().includes(app.search.toLowerCase()) || node.public_key.startsWith(app.search)
|
||||
).toSorted(
|
||||
(a, b) => a.adv_name.localeCompare(b.adv_name)
|
||||
).slice(0, 20);
|
||||
})
|
||||
|
||||
async function refreshMap(refresh, noDownload) {
|
||||
if(!noDownload) {
|
||||
const nodesReq = await fetch(apiUrl);
|
||||
app.nodes = await nodesReq.json();
|
||||
}
|
||||
const markers = L.markerClusterGroup();
|
||||
for(const node of app.nodes) {
|
||||
const marker = L.marker([node.adv_lat, node.adv_lon], { icon: icons[node.type.toString()], title: node.adv_name });
|
||||
node.marker = marker;
|
||||
node.coords = `${node.adv_lat.toFixed(4)}, ${node.adv_lon.toFixed(4)}`;
|
||||
node.lastAdvertDate = new Date(node.last_advert);
|
||||
const popup = L.popup({ minWidth: 350, maxWidth: 350, content: getTable(node) });
|
||||
marker.bindPopup(popup);
|
||||
markers.addLayer(marker);
|
||||
}
|
||||
if(refresh) {
|
||||
map.eachLayer(layer => layer.clearLayers());
|
||||
}
|
||||
map.addLayer(markers);
|
||||
}
|
||||
|
||||
async function addNode() {
|
||||
if(!(app.link && app.link.startsWith('meshcore://'))) {
|
||||
alert('Please paste valid meshcore link.');
|
||||
return;
|
||||
};
|
||||
|
||||
const res = await fetch(apiUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
links: [ app.link ],
|
||||
radio: {}
|
||||
})
|
||||
});
|
||||
const reply = await res.json();
|
||||
alert(reply.message || reply.error);
|
||||
clearLocationHash();
|
||||
location.reload();
|
||||
}
|
||||
|
||||
function showNode(node) {
|
||||
node.marker.openPopup();
|
||||
map.flyTo(node.marker.getLatLng(), 19);
|
||||
app.search = '';
|
||||
}
|
||||
|
||||
function highlightString(source, toHighlight) {
|
||||
const escapedSource = source.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>');
|
||||
const matchIndex = source.toLowerCase().indexOf(toHighlight.toLowerCase());
|
||||
const highlightString = matchIndex >= 0 ? source.substring(matchIndex, matchIndex + toHighlight.length) : toHighlight;
|
||||
return escapedSource.replace(highlightString, `<b>${highlightString}</b>`);
|
||||
}
|
||||
|
||||
refreshMap();
|
||||
|
||||
map.on('moveend', function(e) {
|
||||
const pos = map.getCenter();
|
||||
const zoom = map.getZoom();
|
||||
history.replaceState({}, '', `/?lat=${pos.lat.toFixed(4)}&lon=${pos.lng.toFixed(4)}&zoom=${zoom}`);
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
if(location.hash === '#add-new-node') {
|
||||
dialogAddNode.value.showModal();
|
||||
dialogAddNode.value.addEventListener("close", () => clearLocationHash());
|
||||
}
|
||||
})
|
||||
|
||||
window.refreshMap = refreshMap;
|
||||
return {
|
||||
app, refreshMap, addNode,
|
||||
stats, searchResults,
|
||||
showNode, dialogAddNode, highlightString
|
||||
}
|
||||
},
|
||||
}).mount('#app')
|
||||