mirror of
https://github.com/Findus23/RainbowRoad.git
synced 2024-09-19 16:03:52 +02:00
improve popups
This commit is contained in:
parent
04c2d7e785
commit
4e8aff95f4
12 changed files with 877 additions and 336 deletions
|
@ -1309,5 +1309,39 @@
|
|||
],
|
||||
"length": 26.42
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 38,
|
||||
"name": "Pilgramgasse/Margaretenstraße",
|
||||
"bezirk": 5,
|
||||
"type": "prideFlag",
|
||||
"sources": [
|
||||
{
|
||||
"type": "streetview",
|
||||
"date": "2020-11-01",
|
||||
"note": "Nicht in August 2019"
|
||||
}
|
||||
],
|
||||
"geosource": {
|
||||
"type": "OSMnodes",
|
||||
"nodes": [
|
||||
1877331771,
|
||||
292371940,
|
||||
2592738807
|
||||
]
|
||||
},
|
||||
"geo": {
|
||||
"coords": [
|
||||
[
|
||||
16.3581003,
|
||||
48.1914631
|
||||
],
|
||||
[
|
||||
16.3579901,
|
||||
48.1913387
|
||||
]
|
||||
],
|
||||
"length": 16.06
|
||||
}
|
||||
}
|
||||
]
|
|
@ -77,6 +77,9 @@
|
|||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>Rainbow Road</title>
|
||||
<meta name="description" content="Eine Karte aller momentan verfügbaren Fahrräder bei WienMobilRad">
|
||||
</head>
|
||||
|
|
791
package-lock.json
generated
791
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -11,13 +11,17 @@
|
|||
"validate": "ajv validate -s data/schema.json -d data/data.json"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/micromodal": "^0.3.3",
|
||||
"@types/node": "^18.6.3",
|
||||
"sass": "^1.54.4",
|
||||
"typescript": "^4.5.4",
|
||||
"vite": "^3.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"ajv-cli": "^5.0.0",
|
||||
"axios": "^0.27.2",
|
||||
"hint.css": "^2.7.0",
|
||||
"micromodal": "^0.4.10",
|
||||
"node-fetch": "^3.2.10",
|
||||
"ol": "^6.14.1",
|
||||
"ts-node": "^10.9.1"
|
||||
|
|
1
src/domutils.ts
Normal file
1
src/domutils.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export const createElement = document.createElement.bind(document)
|
85
src/main.ts
85
src/main.ts
|
@ -1,12 +1,11 @@
|
|||
import Map from "ol/Map";
|
||||
import {OSM, Vector as VectorSource} from "ol/source";
|
||||
import TileLayer from "ol/layer/Tile";
|
||||
import {Feature, Overlay, View} from "ol";
|
||||
import {Feature, View} from "ol";
|
||||
import {fromLonLat, transform, transformExtent} from "ol/proj";
|
||||
|
||||
import {LineString, Point} from "ol/geom";
|
||||
import {Vector as VectorLayer} from "ol/layer";
|
||||
import "./style.css"
|
||||
import {Circle, Fill, Icon, Stroke, Style} from "ol/style";
|
||||
import {Coordinate} from "ol/coordinate";
|
||||
import {State} from "ol/render";
|
||||
|
@ -19,7 +18,9 @@ import dataURL from "../data/data.json?url"
|
|||
import {Crossing} from "../interfaces";
|
||||
import prideFlag from "../assets/prideflag.svg"
|
||||
import transFlag from "../assets/transflag.svg"
|
||||
import {displaySources} from "./text";
|
||||
|
||||
import "./style.scss"
|
||||
import "hint.css/hint.base.css"
|
||||
|
||||
|
||||
function averageCoords(coords: number[][]): number[] {
|
||||
|
@ -38,12 +39,6 @@ const map = new Map({
|
|||
source: new OSM({url: "https://maps.lw1.at/tiles/1.0.0/osm/GLOBAL_MERCATOR/{z}/{x}/{y}.png"})
|
||||
}),
|
||||
],
|
||||
// view: new View({
|
||||
// center: fromLonLat([16.3787, 48.2089]),
|
||||
// zoom: 12,
|
||||
// extent: transformExtent([16.2988, 48.1353, 16.4986, 48.2974], 'EPSG:4326', 'EPSG:3857'),
|
||||
// constrainOnlyCenter: true
|
||||
// })
|
||||
view: new View({
|
||||
center: fromLonLat([16.3787, 48.2089]),
|
||||
zoom: 13,
|
||||
|
@ -52,10 +47,23 @@ const map = new Map({
|
|||
constrainOnlyCenter: true
|
||||
})
|
||||
});
|
||||
var vectorLine = new VectorSource({
|
||||
const vectorLine = new VectorSource({
|
||||
attributions: ["<a target='_blank' href='" + dataURL + "'>Rohdaten</a>"]
|
||||
});
|
||||
const metaData: { [id: number]: Crossing } = {}
|
||||
data.sort((a, b) => {
|
||||
/*
|
||||
put trans flag on top (so they are not covered,
|
||||
but apart from that keep the drawing order random
|
||||
*/
|
||||
if (a.type == "transFlag") {
|
||||
return 1
|
||||
}
|
||||
if (b.type == "transFlag") {
|
||||
return -1
|
||||
}
|
||||
return Math.random() - 0.5;
|
||||
})
|
||||
data.forEach(c => {
|
||||
if (typeof c.geo === "undefined") {
|
||||
return
|
||||
|
@ -124,7 +132,7 @@ const circleStyle = new Style({
|
|||
radius: 10,
|
||||
}),
|
||||
})
|
||||
var vectorLineLayer = new VectorLayer({
|
||||
const vectorLineLayer = new VectorLayer({
|
||||
source: vectorLine,
|
||||
style: function (feature, resolution) {
|
||||
const zoom = map.getView().getZoomForResolution(resolution);
|
||||
|
@ -150,55 +158,6 @@ var vectorLineLayer = new VectorLayer({
|
|||
map.addLayer(vectorLineLayer);
|
||||
|
||||
|
||||
// popups
|
||||
|
||||
const container = document.getElementById('popup')!;
|
||||
const content = document.getElementById('popup-content')!;
|
||||
const closer = document.getElementById('popup-closer')!;
|
||||
|
||||
var overlay = new Overlay({
|
||||
element: container,
|
||||
autoPan: true,
|
||||
autoPanAnimation: {
|
||||
duration: 250
|
||||
}
|
||||
});
|
||||
map.addOverlay(overlay);
|
||||
|
||||
closer.onclick = function () {
|
||||
overlay.setPosition(undefined);
|
||||
closer.blur();
|
||||
return false;
|
||||
};
|
||||
map.on('singleclick', function (event) {
|
||||
map.forEachFeatureAtPixel(event.pixel, feature => {
|
||||
var coordinate = event.coordinate;
|
||||
let id = Number(feature.getId())
|
||||
if (!id) {
|
||||
return
|
||||
}
|
||||
if (id > 10000) {
|
||||
id -= 10000
|
||||
}
|
||||
const crossing = metaData[id]
|
||||
|
||||
content.innerHTML = "";
|
||||
const p = document.createElement("p")
|
||||
p.innerText = crossing.name
|
||||
content.appendChild(p)
|
||||
if (crossing.comment) {
|
||||
const p = document.createElement("p")
|
||||
const small = document.createElement("small")
|
||||
small.innerText = crossing.comment
|
||||
p.appendChild(small)
|
||||
content.appendChild(p)
|
||||
}
|
||||
displaySources(crossing.sources, content)
|
||||
overlay.setPosition(coordinate);
|
||||
|
||||
}, {hitTolerance: 5})
|
||||
if (!map.hasFeatureAtPixel(event.pixel, {hitTolerance: 2})) {
|
||||
overlay.setPosition(undefined);
|
||||
closer.blur();
|
||||
}
|
||||
});
|
||||
import("./popups").then(popups => {
|
||||
popups.initPopups(map, metaData);
|
||||
})
|
||||
|
|
66
src/popups.ts
Normal file
66
src/popups.ts
Normal file
|
@ -0,0 +1,66 @@
|
|||
import {Overlay} from "ol";
|
||||
import {createDateWithDisclaimer, displaySources} from "./text";
|
||||
import type Map from "ol/Map";
|
||||
import {Crossing} from "../interfaces";
|
||||
import {createElement} from "./domutils";
|
||||
|
||||
export function initPopups(map: Map, metaData: { [id: number]: Crossing }) {
|
||||
const container = document.getElementById('popup')!;
|
||||
const content = document.getElementById('popup-content')!;
|
||||
const closer = document.getElementById('popup-closer')!;
|
||||
|
||||
const overlay = new Overlay({
|
||||
element: container,
|
||||
autoPan: {
|
||||
animation:{
|
||||
duration:250,
|
||||
}
|
||||
},
|
||||
});
|
||||
map.addOverlay(overlay);
|
||||
|
||||
closer.onclick = function () {
|
||||
overlay.setPosition(undefined);
|
||||
closer.blur();
|
||||
return false;
|
||||
};
|
||||
map.on('singleclick', function (event) {
|
||||
map.forEachFeatureAtPixel(event.pixel, feature => {
|
||||
const coordinate = event.coordinate;
|
||||
let id = Number(feature.getId())
|
||||
if (!id) {
|
||||
return
|
||||
}
|
||||
if (id > 10000) {
|
||||
id -= 10000
|
||||
}
|
||||
const crossing = metaData[id]
|
||||
|
||||
content.innerHTML = "";
|
||||
const p = createElement("p")
|
||||
p.innerText = crossing.name
|
||||
p.classList.add("name")
|
||||
content.appendChild(p)
|
||||
|
||||
const dateWithDisclaimer = createDateWithDisclaimer(crossing.sources)
|
||||
content.appendChild(dateWithDisclaimer)
|
||||
|
||||
if (crossing.comment) {
|
||||
const p = createElement("p")
|
||||
const small = createElement("small")
|
||||
small.innerText = crossing.comment
|
||||
p.appendChild(small)
|
||||
content.appendChild(p)
|
||||
}
|
||||
const sourcesBlock = displaySources(crossing.sources)
|
||||
content.appendChild(createElement("hr"))
|
||||
content.appendChild(sourcesBlock)
|
||||
overlay.setPosition(coordinate);
|
||||
|
||||
}, {hitTolerance: 5})
|
||||
if (!map.hasFeatureAtPixel(event.pixel, {hitTolerance: 5})) {
|
||||
overlay.setPosition(undefined);
|
||||
closer.blur();
|
||||
}
|
||||
});
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import "./style.css"
|
||||
import "./style.scss"
|
||||
import {Line, Vector2d} from "./vectorUtils";
|
||||
import {drawZebraCrossing, prideZebraPattern, transZebraPattern, zebraPattern} from "./zebraUtils";
|
||||
|
||||
|
|
|
@ -1,65 +0,0 @@
|
|||
@import "../node_modules/ol/ol.css";
|
||||
|
||||
html, body {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#map {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#canvas{
|
||||
background: #fcd6a4;
|
||||
}
|
||||
|
||||
.ol-popup {
|
||||
position: absolute;
|
||||
background-color: white;
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,0.2);
|
||||
padding: 15px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #cccccc;
|
||||
bottom: 12px;
|
||||
left: -50px;
|
||||
min-width: 280px;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
.ol-popup-closer {
|
||||
text-decoration: none;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 8px;
|
||||
}
|
||||
.ol-popup-closer:after {
|
||||
content: "✖";
|
||||
}
|
||||
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: .5rem;
|
||||
}
|
101
src/style.scss
Normal file
101
src/style.scss
Normal file
|
@ -0,0 +1,101 @@
|
|||
@import "../node_modules/ol/ol.css";
|
||||
|
||||
html, body {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
#map {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#canvas {
|
||||
background: #fcd6a4;
|
||||
}
|
||||
|
||||
.ol-popup {
|
||||
#popup-content > * {
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.name {
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.star {
|
||||
vertical-align: super;
|
||||
font-size: smaller;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
hr {
|
||||
padding: 0;
|
||||
border: none;
|
||||
height: 1px;
|
||||
background: lightgray;
|
||||
}
|
||||
}
|
||||
|
||||
.ol-popup {
|
||||
position: absolute;
|
||||
background-color: white;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
|
||||
//padding: 15px;
|
||||
padding: 15px 0;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #cccccc;
|
||||
bottom: 12px;
|
||||
left: -50px;
|
||||
min-width: 280px;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.ol-popup-closer {
|
||||
text-decoration: none;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 8px;
|
||||
}
|
||||
|
||||
.ol-popup-closer:after {
|
||||
content: "✖";
|
||||
}
|
||||
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: .5rem;
|
||||
}
|
57
src/text.ts
57
src/text.ts
|
@ -5,19 +5,30 @@ import kurierIcon from "../assets/favicons/kurier.at.png"
|
|||
import spoeIcon from "../assets/favicons/spoe.png"
|
||||
import neosIcon from "../assets/favicons/neos.png"
|
||||
import WienIcon from "../assets/favicons/wien.gv.at.png"
|
||||
import derStandardIcon from "../assets/favicons/derstandard.at.png"
|
||||
import {createElement} from "./domutils";
|
||||
|
||||
export function displaySources(sources: Source[], content: HTMLElement) {
|
||||
export function prettyDate(isoDate: string): string {
|
||||
return new Date(isoDate).toLocaleDateString()
|
||||
}
|
||||
|
||||
export function displaySources(sources: Source[]) {
|
||||
const sourcesBlock = createElement("div")
|
||||
sourcesBlock.classList.add("sources")
|
||||
const heading = createElement("p")
|
||||
heading.innerText = "Weitere Infomationen:"
|
||||
sourcesBlock.appendChild(heading)
|
||||
sources.forEach(s => {
|
||||
if (!s.url) {
|
||||
return
|
||||
}
|
||||
const img = document.createElement("img")
|
||||
const a = document.createElement("a")
|
||||
const img = createElement("img")
|
||||
const a = createElement("a")
|
||||
a.rel = "noopener"
|
||||
a.target = "_blank"
|
||||
a.title = s.date
|
||||
a.classList.add("hint--top")
|
||||
img.src = ""
|
||||
img.width = img.height = 32
|
||||
img.width = img.height = 24
|
||||
a.appendChild(img)
|
||||
a.href = s.url! // TODO: missing url
|
||||
switch (a.hostname) {
|
||||
|
@ -34,15 +45,47 @@ export function displaySources(sources: Source[], content: HTMLElement) {
|
|||
img.src = neosIcon
|
||||
break
|
||||
case "www.wien.gv.at":
|
||||
img.src=WienIcon
|
||||
img.src = WienIcon
|
||||
break
|
||||
case "www.derstandard.at":
|
||||
img.src=derStandardIcon
|
||||
break
|
||||
}
|
||||
|
||||
if (a.hostname.includes("spoe")) {
|
||||
img.src = spoeIcon
|
||||
}
|
||||
if (img.src === document.URL) {
|
||||
a.innerText = a.hostname
|
||||
}
|
||||
content.appendChild(a)
|
||||
a.title = prettyDate(s.date) + ": " + a.hostname
|
||||
a.setAttribute("aria-label", a.title)
|
||||
sourcesBlock.appendChild(a)
|
||||
})
|
||||
return sourcesBlock
|
||||
}
|
||||
|
||||
export function createDateWithDisclaimer(sources: Source[]): HTMLDivElement {
|
||||
const dateWithDisclaimer = createElement("div")
|
||||
const earliestDate = sources.map(s => s.date).sort()[0]
|
||||
const dateP = createElement("p")
|
||||
const disclaimerP = createElement("p")
|
||||
disclaimerP.innerText = "Das angezeigte Datum ist der Zeitpunkt der ersten Erwähnung in Medien " +
|
||||
"oder des ersten Bild auf Google Streetview."
|
||||
disclaimerP.style.display = "none"
|
||||
dateP.innerText = prettyDate(earliestDate)
|
||||
const starLink = createElement("a")
|
||||
starLink.href = "#"
|
||||
starLink.innerText = "*"
|
||||
starLink.classList.add("star")
|
||||
starLink.addEventListener("click", function (e) {
|
||||
disclaimerP.style.display = "block"
|
||||
e.preventDefault()
|
||||
return false
|
||||
})
|
||||
|
||||
dateP.appendChild(starLink)
|
||||
dateWithDisclaimer.appendChild(dateP)
|
||||
dateWithDisclaimer.appendChild(disclaimerP)
|
||||
return dateWithDisclaimer
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue