Port bird-hides to new JSON-format, various improvements

This commit is contained in:
Pieter Vander Vennet 2020-09-03 19:05:18 +02:00
parent 00a6611e1f
commit 9e4035befc
22 changed files with 460 additions and 378 deletions

View file

@ -15,6 +15,8 @@ import * as drinkingWater from "../../assets/layers/drinking_water/drinking_wate
import * as ghostbikes from "../../assets/layers/ghost_bike/ghost_bike.json"
import * as viewpoint from "../../assets/layers/viewpoint/viewpoint.json"
import * as bike_parking from "../../assets/layers/bike_parking/bike_parking.json"
import * as birdhides from "../../assets/layers/bird_hide/birdhides.json"
import {Utils} from "../../Utils";
export class FromJSON {
@ -29,6 +31,7 @@ export class FromJSON {
FromJSON.Layer(ghostbikes),
FromJSON.Layer(viewpoint),
FromJSON.Layer(bike_parking),
FromJSON.Layer(birdhides),
];
for (const layer of sharedLayersList) {

View file

@ -1,164 +0,0 @@
import {LayerDefinition} from "../LayerDefinition";
import {And, Or, Tag} from "../../Logic/Tags";
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
import {TagRenderingOptions} from "../TagRenderingOptions";
export class Birdhide extends LayerDefinition {
private static readonly birdhide = new Tag("leisure", "bird_hide");
constructor() {
super("birdhide",{
name: "vogelkijkplaats",
description: "Een plaats om vogels te kijken, zoals een vogelkijkhut of kijkwand",
overpassFilter: Birdhide.birdhide,
elementsToShow: [],
icon: "assets/nature/birdhide.svg",
minzoom: 12,
wayHandling: LayerDefinition.WAYHANDLING_CENTER_AND_WAY,
presets: [
{
title: "Vogelkijkplaats",
tags: [Birdhide.birdhide]
}
],
style(): { color: string; icon: any } {
return {color: "", icon: undefined};
},
});
function rmStart(toRemove: string, title: string): string {
if (title.toLowerCase().indexOf(toRemove.toLowerCase()) == 0) {
return title.substr(toRemove.length).trim();
}
return title;
}
function rmStarts(toRemove: string[], title: string) {
for (const toRm of toRemove) {
title = rmStart(toRm, title);
}
return title;
}
this.title = new TagRenderingOptions({
tagsPreprocessor: (tags) => {
if (tags.name) {
const nm =
rmStarts(
["Vogelkijkhut", "Vogelkijkwand", "Kijkwand", "Kijkhut"],
tags.name);
tags.name = " '" + nm + "'";
} else {
tags.name = "";
}
},
mappings: [
{
k: new And([new Tag("shelter", "no"), new Tag("building", "")]),
txt: "Vogelkijkwand{name}"
},
{
k: new And([new Tag("amenity", "shelter"), new Tag("building", "yes")]),
txt: "Vogelijkhut{name}"
},
{
k: new Tag("amenity", "shelter"),
txt: "Vogelijkhut{name}"
},
{
k: new Tag("shelter", "yes"),
txt: "Vogelijkhut{name}"
},
{
k: new Tag("amenity", "shelter"),
txt: "Vogelijkhut{name}"
},
{
k: new Tag("building", "yes"),
txt: "Vogelijkhut{name}"
},
{k: null, txt: "Vogelkijkplaats{name}"}
]
});
this.style = (properties) => {
let icon = "assets/nature/birdhide.svg";
if (new Or([new Tag("amenity", "shelter"), new Tag("building", "yes"), new Tag("shelter", "yes")]).matchesProperties(properties)) {
icon = "assets/nature/birdshelter.svg";
}
return {
color: "#0000bb",
icon: {
iconUrl: icon,
iconSize: [40,40],
iconAnchor: [20,20]
}
}
}
this.elementsToShow = [
new ImageCarouselWithUploadConstructor(),
new TagRenderingOptions({
question: "Is dit een kijkwand of kijkhut?",
mappings: [
{
k: new And([new Tag("shelter", "no"), new Tag("building", ""), new Tag("amenity", "")]),
txt: "Vogelkijkwand"
},
{
k: new And([new Tag("amenity", "shelter"), new Tag("building", "yes"), new Tag("shelter", "yes")]),
txt: "Vogelijkhut"
},
{
k: new Or([new Tag("amenity", "shelter"), new Tag("building", "yes"), new Tag("shelter", "yes")]),
txt: "Vogelijkhut"
},
]
}),
new TagRenderingOptions({
question: "Is ze rolstoeltoegankelijk?",
mappings: [
{
k: new Tag("wheelchair", "no"),
txt: "Niet rolstoeltoegankelijk"
},
{
k: new Tag("wheelchair", "limited"),
txt: "Een rolstoel raakt er, maar het is niet makkelijk"
},
{
k: new Tag("wheelchair", "yes"),
txt: "Een rolstoel raakt er gemakkelijk"
}
]
}),
new TagRenderingOptions({
question: "Wie beheert deze?",
freeform: {
key: "operator",
template: "Beheer door $$$",
renderTemplate: "Beheer door {operator}",
placeholder: "organisatie"
},
mappings: [
{k: new Tag("operator", "Natuurpunt"), txt: "Natuurpunt"},
{k: new Tag("operator", "Agentschap Natuur en Bos"), txt: "het Agentschap Natuur en Bos (ANB)"},
]
})
];
}
}

View file

@ -1,5 +1,4 @@
import {Layout} from "../Layout";
import {Birdhide} from "../Layers/Birdhide";
import {InformationBoard} from "../Layers/InformationBoard";
import {NatureReserves} from "../Layers/NatureReserves";
@ -9,7 +8,7 @@ export class Natuurpunt extends Layout{
"natuurpunt",
["nl"],
"De natuur in",
[new Birdhide(), new InformationBoard(), new NatureReserves(true), "drinking_water"],
["birdhides", new InformationBoard(), new NatureReserves(true), "drinking_water"],
12,
51.20875,
3.22435,
@ -17,6 +16,6 @@ export class Natuurpunt extends Layout{
"",
""
);
this.icon = "./assets/nature/birdhide.svg"
this.icon = "./assets/layers/bird_hide/birdhide.svg"
}
}

View file

@ -103,8 +103,9 @@ class OnlyShowIf extends UIElement implements TagDependantUIElement {
return this._embedded.IsQuestioning();
}
Activate(): void {
Activate(): UIElement {
this._embedded.Activate();
return this;
}
Update(): void {

View file

@ -46,7 +46,7 @@ export class RegexTag extends TagsFilter {
matches(tags: { k: string; v: string }[]): boolean {
for (const tag of tags) {
if (RegexTag.doesMatch(tag.k, this.key)){
return RegexTag.doesMatch(tag.v, this.value);
return RegexTag.doesMatch(tag.v, this.value) != this.invert;
}
}
return false;

View file

@ -22,7 +22,7 @@ export default class CustomGeneratorPanel extends UIElement {
private mainPanel: UIElement;
private loginButton: UIElement;
private connection: OsmConnection;
private readonly connection: OsmConnection;
constructor(connection: OsmConnection, layout: LayoutConfigJson) {
super(connection.userDetails);
@ -40,7 +40,7 @@ export default class CustomGeneratorPanel extends UIElement {
private InitMainPanel(layout: LayoutConfigJson, userDetails: UserDetails, connection: OsmConnection) {
const es = new UIEventSource(layout);
const encoded = es.map(config => btoa(JSON.stringify(config)));
encoded.addCallback(encoded => LocalStorageSource.Get("\"last-custom-theme\""))
encoded.addCallback(encoded => LocalStorageSource.Get("last-custom-theme"))
const liveUrl = encoded.map(encoded => `./index.html?userlayout=${es.data.id}#${encoded}`)
const iframe = liveUrl.map(url => `<iframe src='${url}' width='100%' height='99%' style="box-sizing: border-box" title='Theme Preview'></iframe>`);
const currentSetting = new UIEventSource<SingleSetting<any>>(undefined)

View file

@ -11,7 +11,7 @@ export class GenerateEmpty {
overpassTags: {and: [""]},
title: undefined,
description: {},
tagRenderings: []
tagRenderings: [],
}
}

View file

@ -41,7 +41,7 @@ export default class TagRenderingPanel extends InputElement<TagRenderingConfigJs
this.options = options ?? {};
const questionsNotUnlocked = userDetails.csCount < State.userJourney.themeGeneratorFullUnlock;
this.options.disableQuestions =
(this.options.disableQuestions ?? false) &&
(this.options.disableQuestions ?? false) ||
questionsNotUnlocked;
this.intro = new Combine(["<h3>", options?.title ?? "TagRendering", "</h3>", options?.description ?? ""])

View file

@ -85,7 +85,7 @@ export class ImageCarousel extends TagDependantUIElement {
this._deleteButton = new ConfirmDialog(showDeleteButton,
"<img src='assets/delete.svg' alt='Afbeelding verwijderen' class='delete-image'>",
"<img src='./assets/delete.svg' alt='Afbeelding verwijderen' class='delete-image'>",
"<span>Afbeelding verwijderen</span>",
"<span>Terug</span>",
deleteCurrent,
@ -149,6 +149,7 @@ export class ImageCarousel extends TagDependantUIElement {
Activate() {
super.Activate();
this.searcher.Activate();
return this;
}
}

View file

@ -53,6 +53,7 @@ class ImageCarouselWithUpload extends TagDependantUIElement {
super.Activate();
this._imageElement.Activate();
this._pictureUploader.Activate();
return this;
}
Update() {

View file

@ -16,7 +16,7 @@ export default class SingleTagInput extends InputElement<string> {
constructor(value: UIEventSource<string> = undefined) {
super(undefined);
this._value = value ?? new UIEventSource<string>(undefined);
this._value = value ?? new UIEventSource<string>("");
this.key = TextField.KeyInput();

View file

@ -86,6 +86,7 @@ export class SlideShow extends UIElement {
}
this._next.Update();
this._prev.Update();
return this;
}
}

View file

@ -47,7 +47,7 @@ export class UserBadge extends UIElement {
this._homeButton = new VariableUiElement(
this._userDetails.map((userinfo) => {
if (userinfo.home) {
return "<img id='home' src='./assets/home.svg' alt='home' class='small-userbadge-icon'> ";
return "<img src='./assets/home.svg' alt='home' class='small-userbadge-icon'> ";
}
return "";
})

View file

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View file

@ -0,0 +1,251 @@
{
"id": "birdhides",
"name": {
"nl": "Vogelkijkhutten"
},
"minzoom": 14,
"overpassTags": {
"and": [
"leisure=bird_hide"
]
},
"title": {
"render": {
"nl": "Vogelkijkplaats"
},
"mappings": [
{
"if": {
"and": [
"name~((V|v)ogel.*).*"
]
},
"then": {
"nl": "{name}"
}
},
{
"if": {
"and": [
"name~*",
{
"or": [
"building!~no",
"shelter=yes"
]
}
]
},
"then": {
"nl": "Vogelkijkhut {name}"
}
},
{
"if": {
"and": [
"name~*"
]
},
"then": {
"nl": "Vogelkijkwand {name}"
}
}
]
},
"description": {
"nl": "Een vogelkijkhut"
},
"tagRenderings": [
{
"question": {
"nl": "Is dit een kijkwand of kijkhut?"
},
"mappings": [
{
"if": {
"and": [
"shelter=no",
"building=",
"amenity="
]
},
"then": {
"nl": "Vogelkijkwand"
}
},
{
"if": {
"and": [
"amenity=shelter",
"building=yes",
"shelter=yes"
]
},
"then": {
"nl": "Vogelkijkhut"
}
},
{
"if": {
"or": [
"amenity=shelter",
"building=yes",
"shelter=yes"
]
},
"then": {
"nl": "Vogelkijkhut"
},
"hideInAnswer": true
}
]
},
{
"question": {
"nl": "Is deze vogelkijkplaats rolstoeltoegankelijk?"
},
"mappings": [
{
"if": {
"and": [
"wheelchair=designated"
]
},
"then": {
"nl": "Er zijn speciale voorzieningen voor rolstoelen"
}
},
{
"if": {
"and": [
"wheelchair=yes"
]
},
"then": {
"nl": "Een rolstoel raakt er vlot"
}
},
{
"if": {
"and": [
"wheelchair=limited"
]
},
"then": {
"nl": "Je kan er raken met een rolstoel, maar het is niet makkelijk"
}
},
{
"if": {
"and": [
"wheelchair=no"
]
},
"then": {
"nl": "Niet rolstoeltoegankelijk"
}
}
]
},
{
"render": {
"nl": "Beheer door {operator}"
},
"freeform": {
"key": "operator"
},
"question": {
"nl": "Wie beheert deze vogelkijkplaats?"
},
"mappings": [
{
"if": {
"and": [
"operator~Natuurpunt"
]
},
"then": {
"nl": "Beheer door Natuurpunt"
}
},
{
"if": {
"and": [
"operator=Agentschap Natuur en Bos"
]
},
"then": {
"nl": "Beheer door het Agentschap Natuur en Bos "
}
}
]
}
],
"icon": {
"render": {
"nl": "./assets/layers/bird_hide/birdhide.svg"
},
"mappings": [
{
"if": {
"or": [
"building=yes",
"shelter=yes",
"amenity=shelter"
]
},
"then": "./assets/layers/bird_hide/birdshelter.svg"
}
]
},
"size": {
"question": {},
"freeform": {
"addExtraTags": []
},
"render": {
"nl": "40,40,center"
},
"mappings": []
},
"color": {
"render": {
"nl": "#94bb28"
},
},
"stroke": {
"render": {
"nl": "3"
},
},
"presets": [
{
"tags": [
"leisure=bird_hide",
"building=yes",
"shelter=yes",
"amenity=shelter"
],
"title": {
"nl": "Vogelkijkhut"
},
"description": {
"nl": "Een overdekte hut waarbinnen er warm en droog naar vogels gekeken kan worden"
}
},
{
"tags": [
"leisure=bird_hide",
"building=no",
"shelter=no"
],
"title": {
"nl": "Vogelkijkwand"
},
"description": {
"nl": "Een vogelkijkwand waarachter men kan staan om vogels te kijken"
}
}
],
"wayHandling": 2
}

View file

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

84
css/slideshow.css Normal file
View file

@ -0,0 +1,84 @@
.image-slideshow {
position: relative;
text-align: center;
max-width: 100%;
margin-top: 1em;
margin-bottom: 1em;
}
.slides {
overflow: hidden;
}
.prev-button {
background-color: black;
opacity: 0.3;
width: 4.0em;
height: 100%;
padding-left: 0.5em;
padding-right: 0.5em;
position: absolute;
top: 50%;
left: 0;
transform: translate(0, -50%);
z-index: 5060;
border-radius: 1em;
}
.next-button {
background-color: black;
opacity: 0.3;
width: 4.0em;
height: 100%;
padding-left: 0.5em;
padding-right: 0.5em;
position: absolute;
top: 50%;
right: 0;
transform: translate(0, -50%);
border-radius: 1em;
z-index: 5060;
}
.vspan {
height: calc(50% - 3em);
}
.prev-button img {
margin-left: -1em;
width: 6em;
text-align: center;
}
.next-button img {
margin-left: -1em;
width: 6em;
text-align: center;
}
.slide > span {
max-height: 40vh;
}
.slide > span img {
height: auto;
width: auto;
max-width: 100%;
max-height: 30vh;
border-radius: 1em;
}
.hidden {
/* This is used by the slideshow, to hide non-active slides*/
display: none !important;
}

89
css/userbadge.css Normal file
View file

@ -0,0 +1,89 @@
.small-userbadge-icon {
width: 1em;
height: 1em;
fill: black;
border-radius: 0;
}
#profile-pic {
float: left;
width: 4em;
height: 4em;
padding: 0;
margin: 0;
-webkit-border-radius: 50%;
-moz-border-radius: 50%;
border-radius: 50%;
opacity: 0;
transition: opacity 500ms linear;
}
#username {
text-decoration: none;
color: black;
}
#usertext {
width: max-content;
margin: 0;
padding: 0.9em;
padding-left: 4.7em; /* Should be half of profile-pic's width + actual padding (same as padding-right)*/
padding-right: 1.5em;
border-radius: 2em; /*Half border radius width/height*/
height: 2.2em; /*SHould equal profile-pic height - padding*/
z-index: 5000;
text-align: left;
background-color: white;
background-size: 100%;
display: block;
line-height: 0.75em;
}
#usertext a {
text-decoration: none;
color: black;
}
#userbadge {
display: inline-block;
text-align: center;
background-color: white;
-webkit-border-radius: 2em;
-moz-border-radius: 2em;
border-radius: 2em;
border-bottom-right-radius: 1.5em;
border-top-right-radius: 1.5em;
margin: 0;
margin-bottom: 0.5em;
min-width: 20em;
pointer-events: all;
}
.userbadge-login {
font-weight: bold;
font-size: large;
background-color: #e5f5ff !important;
height:3em;
display: inline-block;
text-align: center;
-webkit-border-radius: 2em;
-moz-border-radius: 2em;
border-radius: 2em;
border-bottom-right-radius: 1.5em;
border-top-right-radius: 1.5em;
margin: 0;
min-width: 20em;
pointer-events: all;
}
#userbadge-and-search {
display: inline-block;
width: min-content;
}

View file

@ -12,6 +12,7 @@ if (window.location.hash.length > 10) {
} else {
const hash = LocalStorageSource.Get("last-custom-theme").data
if (hash !== undefined) {
console.log("Using theme from local storage")
layout = JSON.parse(atob(hash)) as LayoutConfigJson;
}
}

217
index.css
View file

@ -4,23 +4,10 @@
padding: 0;
}
body {
font-family: 'Helvetica Neue', Arial, sans-serif;
}
form {
display: inline;
}
.invalid {
box-shadow: 0 0 10px #ff5353;
}
.shadow {
box-shadow: 0 0 10px #00000066;
}
#leafletDiv {
height: 100%;
@ -40,10 +27,6 @@
display: none; /*Hidden by default, only visible on mobile*/
}
#help-button-mobile {
display: none;
}
#geolocate-button img {
width: 31px;
height: 31px;
@ -54,8 +37,9 @@
display: block;
}
.selected-element {
fill: black
#help-button-mobile {
display: none;
}
/**************** GENERIC ****************/
@ -78,6 +62,18 @@
padding-bottom: 0.15em;
}
form {
display: inline;
}
.invalid {
box-shadow: 0 0 10px #ff5353;
}
.shadow {
box-shadow: 0 0 10px #00000066;
}
.soft {
background-color: #e5f5ff;
font-weight: bold;
@ -126,103 +122,6 @@
}
/**************** USER BADGE ****************/
.small-userbadge-icon {
width: 1em;
height: 1em;
fill: black;
border-radius: 0;
}
#home {
cursor: pointer;
}
#profile-pic {
float: left;
width: 4em;
height: 4em;
padding: 0;
margin: 0;
-webkit-border-radius: 50%;
-moz-border-radius: 50%;
border-radius: 50%;
opacity: 0;
transition: opacity 500ms linear;
}
#username {
text-decoration: none;
color: black;
}
#usertext {
width: max-content;
margin: 0;
padding: 0.9em;
padding-left: 4.7em; /* Should be half of profile-pic's width + actual padding (same as padding-right)*/
padding-right: 1.5em;
border-radius: 2em; /*Half border radius width/height*/
height: 2.2em; /*SHould equal profile-pic height - padding*/
z-index: 5000;
text-align: left;
background-color: white;
background-size: 100%;
display: block;
line-height: 0.75em;
}
#usertext a {
text-decoration: none;
color: black;
}
#userbadge {
display: inline-block;
text-align: center;
background-color: white;
-webkit-border-radius: 2em;
-moz-border-radius: 2em;
border-radius: 2em;
border-bottom-right-radius: 1.5em;
border-top-right-radius: 1.5em;
margin: 0;
margin-bottom: 0.5em;
min-width: 20em;
pointer-events: all;
}
.userbadge-login {
font-weight: bold;
font-size: large;
background-color: #e5f5ff !important;
height:3em;
display: inline-block;
text-align: center;
-webkit-border-radius: 2em;
-moz-border-radius: 2em;
border-radius: 2em;
border-bottom-right-radius: 1.5em;
border-top-right-radius: 1.5em;
margin: 0;
min-width: 20em;
pointer-events: all;
}
#userbadge-and-search {
display: inline-block;
width: min-content;
}
#searchbox {
display: inline-block;
@ -614,7 +513,6 @@
#welcomeMessage {
display: inline-block;
background-color: white;
padding: 1em;
border-radius: 0;
width: 100%;
max-width: 100%;
@ -719,90 +617,6 @@
/************ Slideshow *****************/
.image-slideshow {
position: relative;
text-align: center;
max-width: 100%;
margin-top: 1em;
margin-bottom: 1em;
}
.slides {
overflow: hidden;
}
.prev-button {
background-color: black;
opacity: 0.3;
width: 4.0em;
height: 100%;
padding-left: 0.5em;
padding-right: 0.5em;
position: absolute;
top: 50%;
left: 0;
transform: translate(0, -50%);
z-index: 5060;
border-radius: 1em;
}
.next-button {
background-color: black;
opacity: 0.3;
width: 4.0em;
height: 100%;
padding-left: 0.5em;
padding-right: 0.5em;
position: absolute;
top: 50%;
right: 0;
transform: translate(0, -50%);
border-radius: 1em;
z-index: 5060;
}
.vspan {
height: calc(50% - 3em);
}
.prev-button img {
margin-left: -1em;
width: 6em;
text-align: center;
}
.next-button img {
margin-left: -1em;
width: 6em;
text-align: center;
}
.slide > span {
max-height: 40vh;
}
.slide > span img {
height: auto;
width: auto;
max-width: 100%;
max-height: 30vh;
border-radius: 1em;
}
.hidden {
/* This is used by the slideshow, to hide non-active slides*/
display: none !important;
}
.imgWithAttr {
max-height: 20em;
@ -1101,7 +915,6 @@
padding: 0.5em;
padding-left: 0.75em;
height: 3em;
width: 14em;
border-radius: 1em;
border-top-left-radius: 0;

View file

@ -7,6 +7,8 @@
<title>MapComplete</title>
<link rel="stylesheet" href="./vendor/leaflet.css"/>
<link rel="stylesheet" href="./index.css"/>
<link rel="stylesheet" href="./css/userbadge.css"/>
<link rel="stylesheet" href="./css/slideshow.css"/>
<link rel="manifest" href="./manifest.manifest">
<link rel="icon" href="assets/add.svg" sizes="any" type="image/svg+xml">