1
0
Fork 0
mirror of https://github.com/Findus23/RainbowRoad.git synced 2024-09-19 16:03:52 +02:00

improve popups

This commit is contained in:
Lukas Winkler 2022-08-11 20:47:45 +02:00
parent 04c2d7e785
commit 4e8aff95f4
Signed by: lukas
GPG key ID: 54DE4D798D244853
12 changed files with 877 additions and 336 deletions

View file

@ -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
}
}
]

View file

@ -77,6 +77,9 @@
},
"url": {
"type": "string"
},
"note": {
"type": "string"
}
},
"required": [

View file

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

File diff suppressed because it is too large Load diff

View file

@ -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
View file

@ -0,0 +1 @@
export const createElement = document.createElement.bind(document)

View file

@ -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
View 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();
}
});
}

View file

@ -1,4 +1,4 @@
import "./style.css"
import "./style.scss"
import {Line, Vector2d} from "./vectorUtils";
import {drawZebraCrossing, prideZebraPattern, transZebraPattern, zebraPattern} from "./zebraUtils";

View file

@ -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
View 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;
}

View file

@ -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
}