Lots of styling, mobile friendliness, better UI flows

This commit is contained in:
Pieter Vander Vennet 2020-06-28 00:06:23 +02:00
parent 0b4016b65d
commit 57c9fcc5aa
28 changed files with 440 additions and 117 deletions

View file

@ -27,10 +27,34 @@ export class CommonTagMappings {
public static osmLink = new TagMappingOptions({ public static osmLink = new TagMappingOptions({
key: "id", key: "id",
mapping: { mapping: {
"node/-1": "<span class='osmlink'>Over enkele momenten sturen we je punt naar OpenStreetMap</span>" "node/-1": ""
}, },
template: "<span class='osmlink'><a href='https://osm.org/{id}' target='_blank'>" + template: "<span class='osmlink'><a href='https://osm.org/{id}' target='_blank'>" +
Img.osmAbstractLogo + Img.osmAbstractLogo +
"</a></span>" "</a></span>"
}) });
public static wikipediaLink = new TagMappingOptions({
key: "wikipedia",
missing: "",
freeform: (value: string) => {
let link = "";
// @ts-ignore
if (value.startsWith("https")) {
link = value;
} else {
const splitted = value.split(":");
const language = splitted[0];
splitted.shift();
const page = splitted.join(":");
link = 'https://' + language + '.wikipedia.org/wiki/' + page;
}
return "<span class='wikipedialink'>" +
"<a href='" + link + "' target='_blank'>" +
"<img width='64px' src='./assets/wikipedia.svg' alt='wikipedia'" +
"</a></span>";
}
});
} }

View file

@ -65,8 +65,8 @@ export class KnownSet {
"\n" + "\n" +
"<p>De data komt van <b>OpenStreetMap</b> en je antwoorden worden daar ook opgeslaan. " + "<p>De data komt van <b>OpenStreetMap</b> en je antwoorden worden daar ook opgeslaan. " +
"Omdat iedereen aan deze data bijdraagt, kunnen we geen garantie op correctheid bieden en heeft deze data geen juridische waarde</p>\n" + "Omdat iedereen aan deze data bijdraagt, kunnen we geen garantie op correctheid bieden en heeft deze data geen juridische waarde</p>\n" +
"<p>Je <b>privacy</b> is belangrijk. We tellen wel hoeveel personen de website bezoeken. Om je niet dubbel te tellen wordt er één coockie bijgehouden waar geen persoonlijke informatie in staat. " + "<p>Je <b>privacy</b> is belangrijk. We tellen wel hoeveel personen de website bezoeken. Om je niet dubbel te tellen wordt er één cookie bijgehouden waar geen persoonlijke informatie in staat. " +
"Als je inlogt, komt er een tweede coockie bij met je inloggegevens.</p>\n", "Als je inlogt, komt er een tweede cookie bij met je inloggegevens.</p>\n",
" <p>Wil je meehelpen? <br/>\n" + " <p>Wil je meehelpen? <br/>\n" +
" Begin dan met <a href=\"https://www.openstreetmap.org/user/new\" target=\"_blank\">een account\n" + " Begin dan met <a href=\"https://www.openstreetmap.org/user/new\" target=\"_blank\">een account\n" +
" te maken</a> of\n" + " te maken</a> of\n" +

View file

@ -3,7 +3,7 @@ import {Quests} from "../Quests";
import {TagMappingOptions} from "../UI/TagMapping"; import {TagMappingOptions} from "../UI/TagMapping";
import L from "leaflet" import L from "leaflet"
import {CommonTagMappings} from "./CommonTagMappings"; import {CommonTagMappings} from "./CommonTagMappings";
import {Tag} from "../Logic/TagsFilter"; import {Or, Tag} from "../Logic/TagsFilter";
export class Park extends LayerDefinition { export class Park extends LayerDefinition {
@ -11,7 +11,8 @@ export class Park extends LayerDefinition {
super(); super();
this.name = "park"; this.name = "park";
this.icon = "./assets/tree_white_background.svg"; this.icon = "./assets/tree_white_background.svg";
this.overpassFilter = new Tag("leisure","park"); this.overpassFilter =
new Or([new Tag("leisure","park"), new Tag("landuse","village_green")]);
this.newElementTags = [new Tag("leisure", "park"), this.newElementTags = [new Tag("leisure", "park"),
new Tag("fixme", "Toegevoegd met MapComplete, geometry nog uit te tekenen")]; new Tag("fixme", "Toegevoegd met MapComplete, geometry nog uit te tekenen")];
this.removeTouchingElements = true; this.removeTouchingElements = true;

View file

@ -62,22 +62,21 @@ export class Basemap {
center: [location.data.lat, location.data.lon], center: [location.data.lat, location.data.lon],
zoom: location.data.zoom, zoom: location.data.zoom,
layers: [this.osmLayer], layers: [this.osmLayer],
attributionControl: false
}); });
L.control.attribution({
position: 'bottomleft'
}).addTo(this.map);
this.Location = location; this.Location = location;
const layerControl = L.control.layers(this.baseLayers, null, const layerControl = L.control.layers(this.baseLayers, null,
{ {
position: 'bottomleft', position: 'bottomright',
hideSingleBase: true hideSingleBase: true
}) })
layerControl.addTo(this.map); layerControl.addTo(this.map);
this.map.zoomControl.setPosition("bottomleft"); this.map.zoomControl.setPosition("bottomright");
const self = this; const self = this;
this.map.on("moveend", function () { this.map.on("moveend", function () {

View file

@ -15,7 +15,7 @@ export class ImageSearcher extends UIEventSource<string[]> {
private readonly _wdItem = new UIEventSource<string>(""); private readonly _wdItem = new UIEventSource<string>("");
private readonly _commons = new UIEventSource<string>(""); private readonly _commons = new UIEventSource<string>("");
private _activated: boolean = false; private _activated: boolean = false;
constructor(tags: UIEventSource<any>) { constructor(tags: UIEventSource<any>) {
super([]); super([]);

View file

@ -60,6 +60,8 @@ export class Imgur {
$.ajax(settings).done(function (response) { $.ajax(settings).done(function (response) {
response = JSON.parse(response); response = JSON.parse(response);
handleSuccessfullUpload(response.data.link); handleSuccessfullUpload(response.data.link);
}).fail((reason) => {
console.log("Uploading to IMGUR failed", reason)
}); });
} }

View file

@ -90,12 +90,13 @@ export class OsmConnection {
*/ */
registerActivateOsmAUthenticationClass() { registerActivateOsmAUthenticationClass() {
const self = this;
const authElements = document.getElementsByClassName("activate-osm-authentication"); const authElements = document.getElementsByClassName("activate-osm-authentication");
for (let i = 0; i < authElements.length; i++) { for (let i = 0; i < authElements.length; i++) {
let element = authElements.item(i); let element = authElements.item(i);
// @ts-ignore // @ts-ignore
element.onclick = function () { element.onclick = function () {
this.AttemptLogin(); self.AttemptLogin();
} }
} }
} }

View file

@ -5,16 +5,20 @@ import {UIEventSource} from "../UI/UIEventSource";
import {ImageUploadFlow} from "../UI/ImageUploadFlow"; import {ImageUploadFlow} from "../UI/ImageUploadFlow";
import {Changes} from "./Changes"; import {Changes} from "./Changes";
import {UserDetails} from "./OsmConnection"; import {UserDetails} from "./OsmConnection";
import {SlideShow} from "../UI/SlideShow";
export class OsmImageUploadHandler { export class OsmImageUploadHandler {
private _tags: UIEventSource<any>; private _tags: UIEventSource<any>;
private _changeHandler: Changes; private _changeHandler: Changes;
private _userdetails: UIEventSource<UserDetails>; private _userdetails: UIEventSource<UserDetails>;
private _slideShow: SlideShow;
constructor(tags: UIEventSource<any>, constructor(tags: UIEventSource<any>,
userdetails: UIEventSource<UserDetails>, userdetails: UIEventSource<UserDetails>,
changeHandler: Changes changeHandler: Changes,
slideShow : SlideShow
) { ) {
this._slideShow = slideShow; // To move the slideshow (if any) to the last, just added element
if (tags === undefined || userdetails === undefined || changeHandler === undefined) { if (tags === undefined || userdetails === undefined || changeHandler === undefined) {
throw "Something is undefined" throw "Something is undefined"
} }
@ -25,6 +29,7 @@ export class OsmImageUploadHandler {
private generateOptions(license: string) { private generateOptions(license: string) {
const tags = this._tags.data; const tags = this._tags.data;
const self = this;
const title = tags.name ?? "Unknown area"; const title = tags.name ?? "Unknown area";
const description = [ const description = [
@ -46,6 +51,7 @@ export class OsmImageUploadHandler {
} }
console.log("Adding image:" + freeIndex, url); console.log("Adding image:" + freeIndex, url);
changes.addChange(tags.id, "image:" + freeIndex, url); changes.addChange(tags.id, "image:" + freeIndex, url);
self._slideShow.MoveTo(-1); // set the last (thus newly added) image) to view
}, },
allDone: function () { allDone: function () {
changes.uploadAll(function () { changes.uploadAll(function () {

View file

@ -55,7 +55,7 @@ export class Quests {
static nameOf(name: string) : QuestionDefinition { static nameOf(name: string) : QuestionDefinition {
return QuestionDefinition.noNameOrNameQuestion("<b>Wat is de <i>officiële</i> naam van dit " + name + "?</b><br />" + return QuestionDefinition.noNameOrNameQuestion("<b>Wat is de <i>officiële</i> naam van dit " + name + "?</b><br />" +
"Gelieve geen naam uit te vinden", "Veel gebieden hebben geen naam. Duid dit dan ook zo aan.",
"Dit " + name + " heeft geen naam", 20); "Dit " + name + " heeft geen naam", 20);
} }
@ -66,7 +66,7 @@ export class Quests {
"operator", "operator",
[{text: "Natuurpunt", value: "Natuurpunt"}, [{text: "Natuurpunt", value: "Natuurpunt"},
{text: "Het Agenschap voor Natuur en Bos", value: "Agentschap Natuur en Bos"}, {text: "Het Agenschap voor Natuur en Bos", value: "Agentschap Natuur en Bos"},
{text: "Een prive-eigenaar beheert dit", value: "private"} {text: "Een prive-eigenaar", value: "private"}
] ]
).addUnrequiredTag("access", "private") ).addUnrequiredTag("access", "private")
.addUnrequiredTag("access", "no"); .addUnrequiredTag("access", "no");

View file

@ -56,4 +56,14 @@ Images are fetched from:
Images are uplaoded to imgur, as their API was way easier to handle. The URL is written into the changes Images are uplaoded to imgur, as their API was way easier to handle. The URL is written into the changes
The idea is that one in a while, the images are transfered to wikipedia The idea is that one in a while, the images are transfered to wikipedia
# Attributions:
Data from OpenStreetMap
Images from Wikipedia/Wikimedia
https://commons.wikimedia.org/wiki/File:Camera_font_awesome.svg

47
UI/DropDownUI.ts Normal file
View file

@ -0,0 +1,47 @@
import {UIElement} from "./UIElement";
import {UIEventSource} from "./UIEventSource";
export class DropDownUI extends UIElement {
selectedElement: UIEventSource<string>
private _label: string;
private _values: { value: string; shown: string }[];
constructor(label: string, values: { value: string, shown: string }[]) {
super(undefined);
this._label = label;
this._values = values;
this.selectedElement = new UIEventSource<string>(values[0].value);
}
protected InnerRender(): string {
let options = "";
for (const value of this._values) {
options += "<option value='" + value.value + "'>" + value.shown + "</option>"
}
return "<form>" +
"<label for='dropdown-" + this.id + "'>" + this._label + "</label>" +
"<select name='dropdown-" + this.id + "' id='dropdown-" + this.id + "'>" +
options +
"</select>" +
"</form>";
}
InnerUpdate(htmlElement: HTMLElement) {
super.InnerUpdate(htmlElement);
const self = this;
const e = document.getElementById("dropdown-" + this.id);
e.onchange = function () {
// @ts-ignore
const selectedValue = e.options[e.selectedIndex].value;
self.selectedElement.setData(selectedValue);
}
}
}

View file

@ -11,6 +11,7 @@ import {UserDetails} from "../Logic/OsmConnection";
import {Img} from "./Img"; import {Img} from "./Img";
import {CommonTagMappings} from "../Layers/CommonTagMappings"; import {CommonTagMappings} from "../Layers/CommonTagMappings";
import {Tag} from "../Logic/TagsFilter"; import {Tag} from "../Logic/TagsFilter";
import {ImageUploadFlow} from "./ImageUploadFlow";
export class FeatureInfoBox extends UIElement { export class FeatureInfoBox extends UIElement {
@ -27,6 +28,8 @@ export class FeatureInfoBox extends UIElement {
private _changes: Changes; private _changes: Changes;
private _userDetails: UIEventSource<UserDetails>; private _userDetails: UIEventSource<UserDetails>;
private _imageElement: ImageCarousel; private _imageElement: ImageCarousel;
private _pictureUploader: UIElement;
private _wikipedialink: UIElement;
constructor( constructor(
@ -66,27 +69,37 @@ export class FeatureInfoBox extends UIElement {
this._infoElements = infoboxes; this._infoElements = infoboxes;
this._osmLink = new TagMapping(CommonTagMappings.osmLink, this._tagsES); this._osmLink = new TagMapping(CommonTagMappings.osmLink, this._tagsES);
this._wikipedialink = new TagMapping(CommonTagMappings.wikipediaLink, this._tagsES);
this._pictureUploader = new OsmImageUploadHandler(tagsES, userDetails, changes, this._imageElement.slideshow).getUI();
} }
InnerRender(): string { InnerRender(): string {
let questions = "";
if (this._userDetails.data.loggedIn) {
questions = this._questions.HideOnEmpty(true).Render();
}
return "<div class='featureinfobox'>" + return "<div class='featureinfobox'>" +
"<div class='featureinfoboxtitle'>" + "<div class='featureinfoboxtitle'>" +
"<span>" + this._title.Render() + "</span>" + "<span>" + this._title.Render() + "</span>" +
this._wikipedialink.Render() +
this._osmLink.Render() + this._osmLink.Render() +
"</div>" + "</div>" +
"<div class='infoboxcontents'>" + "<div class='infoboxcontents'>" +
this._imageElement.Render() + this._imageElement.Render() +
this._pictureUploader.Render() +
new VerticalCombine(this._infoElements, 'infobox-information').HideOnEmpty(true).Render() +
questions +
new VerticalCombine(this._infoElements).Render() +
" <span class='infobox-questions'>" +
this._questions.Render() +
" </span>" +
"</div>" + "</div>" +
"" + "" +
"</div>"; "</div>";
@ -95,21 +108,12 @@ export class FeatureInfoBox extends UIElement {
Activate() { Activate() {
super.Activate(); super.Activate();
this._imageElement.Activate(); this._imageElement.Activate();
this._pictureUploader.Activate();
} }
Update() { Update() {
super.Update(); super.Update();
this._imageElement.Update(); this._imageElement.Update();
} this._pictureUploader.Update();
private generateInfoBox() {
var infoboxes: UIElement[] = [];
infoboxes.push(new OsmImageUploadHandler(
this._tagsES, this._userDetails, this._changes
).getUI());
return new VerticalCombine(infoboxes);
} }
} }

View file

@ -18,7 +18,7 @@ export class ImageCarousel extends UIElement {
*/ */
private readonly searcher: ImageSearcher; private readonly searcher: ImageSearcher;
private readonly slideshow: SlideShow; public readonly slideshow: SlideShow;
constructor(tags: UIEventSource<any>) { constructor(tags: UIEventSource<any>) {
super(tags); super(tags);
@ -32,8 +32,8 @@ export class ImageCarousel extends UIElement {
} }
return uiElements; return uiElements;
}); });
this.slideshow = new SlideShow( this.slideshow = new SlideShow(
new FixedUiElement("<b>Afbeeldingen</b>"),
uiElements, uiElements,
new FixedUiElement("")).HideOnEmpty(true); new FixedUiElement("")).HideOnEmpty(true);
} }

View file

@ -1,13 +1,14 @@
import {UIElement} from "./UIElement"; import {UIElement} from "./UIElement";
import {UIEventSource} from "./UIEventSource"; import {UIEventSource} from "./UIEventSource";
import {UIRadioButton} from "./UIRadioButton";
import {VariableUiElement} from "./VariableUIElement"; import {VariableUiElement} from "./VariableUIElement";
import $ from "jquery" import $ from "jquery"
import {Imgur} from "../Logic/Imgur"; import {Imgur} from "../Logic/Imgur";
import {UserDetails} from "../Logic/OsmConnection"; import {UserDetails} from "../Logic/OsmConnection";
import {DropDownUI} from "./DropDownUI";
export class ImageUploadFlow extends UIElement { export class ImageUploadFlow extends UIElement {
private _licensePicker: UIRadioButton; private _licensePicker: UIElement;
private _selectedLicence: UIEventSource<string>;
private _licenseExplanation: UIElement; private _licenseExplanation: UIElement;
private _isUploading: UIEventSource<number> = new UIEventSource<number>(0) private _isUploading: UIEventSource<number> = new UIEventSource<number>(0)
private _uploadOptions: (license: string) => { title: string; description: string; handleURL: (url: string) => void; allDone: (() => void) }; private _uploadOptions: (license: string) => { title: string; description: string; handleURL: (url: string) => void; allDone: (() => void) };
@ -28,26 +29,32 @@ export class ImageUploadFlow extends UIElement {
this.ListenTo(userInfo); this.ListenTo(userInfo);
this._uploadOptions = uploadOptions; this._uploadOptions = uploadOptions;
this.ListenTo(this._isUploading); this.ListenTo(this._isUploading);
this._licensePicker = UIRadioButton.FromStrings(
const licensePicker = new DropDownUI("Jouw foto wordt gepubliceerd ",
[ [
"CC-BY-SA", {value: "CC0", shown: "in het publiek domein"},
"CC-BY", {value: "CC-BY-SA 4.0", shown: "onder een CC-BY-SA-licentie"},
"CC0" {value: "CC-BY 4.0", shown: "onder een CC-BY-licentie"}
] ]
); );
this._licensePicker = licensePicker;
this._selectedLicence = licensePicker.selectedElement;
const licenseExplanations = { const licenseExplanations = {
"CC-BY-SA": "CC-BY-SA 4.0":
"<b>Creative Commonse met naamsvermelding en gelijk delen</b><br/>" + "<b>Creative Commonse met naamsvermelding en gelijk delen</b><br/>" +
"Je foto mag door iedereen gratis gebruikt worden, als ze je naam vermelden én ze afgeleide werken met deze licentie en attributie delen.", "Je foto mag door iedereen gratis gebruikt worden, als ze je naam vermelden én ze afgeleide werken met deze licentie en attributie delen.",
"CC-BY": "CC-BY 4.0":
"<b>Creative Commonse met naamsvermelding</b> <br/>" + "<b>Creative Commonse met naamsvermelding</b> <br/>" +
"Je foto mag door iedereen gratis gebruikt worden, als ze je naam vermelden", "Je foto mag door iedereen gratis gebruikt worden, als ze je naam vermelden",
"CC0": "CC0":
"<b>Geen copyright</b><br/> Je foto mag door iedereen voor alles gebruikt worden" "<b>Geen copyright</b><br/> Je foto mag door iedereen voor alles gebruikt worden"
} }
this._licenseExplanation = new VariableUiElement( this._licenseExplanation = new VariableUiElement(
this._licensePicker.SelectedElementIndex.map((license) => { this._selectedLicence.map((license) => {
return licenseExplanations[license?.value] return licenseExplanations[license]
}) })
); );
} }
@ -58,37 +65,57 @@ export class ImageUploadFlow extends UIElement {
if (!this._userdetails.data.loggedIn) { if (!this._userdetails.data.loggedIn) {
return "<div class='activate-osm-authentication'>Gelieve je aan te melden om een foto toe te voegen</div>"; return "<div class='activate-osm-authentication'>Gelieve je aan te melden om een foto toe te voegen</div>";
} }
if (this._isUploading.data == 1) {
return "<b>Bezig met een foto te uploaden...</b>"
}
if (this._isUploading.data > 0) { if (this._isUploading.data > 0) {
return "<b>Bezig met uploaden, nog " + this._isUploading.data + " foto's te gaan...</b>" return "<b>Bezig met uploaden, nog " + this._isUploading.data + " foto's te gaan...</b>"
} }
return "<b>Foto's toevoegen</b><br/>" + return "" +
'Kies een licentie:<br/>' + "<div class='imageflow'>" +
"<label for='fileselector-" + this.id + "'>" +
"<div class='imageflow-file-input-wrapper'>" +
"<img src='./assets/camera-plus.svg' alt='upload image'/> " +
"<span class='imageflow-add-picture'>Voeg foto toe</span>" +
"<div class='break'></div>"+
"</div>" +
this._licensePicker.Render() + this._licensePicker.Render() +
this._licenseExplanation.Render() + "<br/>" +
'<input type="file" accept="image/*" name="picField" id="fileselector-' + this.id + '" size="24" multiple="multiple" alt=""/><br/>' "</label>" +
"<input id='fileselector-" + this.id + "' " +
"type='file' " +
"class='imageflow-file-input' " +
"accept='image/*' name='picField' size='24' multiple='multiple' alt=''" +
"/>" +
"</div>"
; ;
} }
InnerUpdate(htmlElement: HTMLElement) { InnerUpdate(htmlElement: HTMLElement) {
super.InnerUpdate(htmlElement); super.InnerUpdate(htmlElement);
const user = this._userdetails.data; const user = this._userdetails.data;
if(!user.loggedIn){
htmlElement.onclick = function(){ htmlElement.onclick = function () {
if (!user.loggedIn) {
user.osmConnection.AttemptLogin(); user.osmConnection.AttemptLogin();
} }
} }
this._licensePicker.Update(); this._licensePicker.Update();
const selector = document.getElementById('fileselector-' + this.id); const selector = document.getElementById('fileselector-' + this.id);
const self = this; const self = this;
if (selector != null) { if (selector != null) {
selector.onchange = function (event) { selector.onchange = function () {
const files = $(this).get(0).files; const files = $(this).get(0).files;
self._isUploading.setData(files.length); self._isUploading.setData(files.length);
const opts = self._uploadOptions(self._licensePicker.SelectedElementIndex.data.value); const opts = self._uploadOptions(self._selectedLicence.data);
Imgur.uploadMultiple(opts.title, opts.description, files, Imgur.uploadMultiple(opts.title, opts.description, files,
function (url) { function (url) {

View file

@ -15,9 +15,9 @@ export class MessageBoxHandler {
this.listenTo(uielement); this.listenTo(uielement);
this.update(); this.update();
new VariableUiElement(new UIEventSource<string>("<h2>Naar de kaart > </h2>"), new VariableUiElement(new UIEventSource<string>("<h2>Naar de kaart</h2>"),
(htmlElement) => { (htmlElement) => {
htmlElement.onclick = function () { document.getElementById("to-the-map").onclick = function () {
uielement.setData(undefined); uielement.setData(undefined);
onClear(); onClear();
} }

View file

@ -39,7 +39,9 @@ export class QuestionPicker extends UIElement {
return ""; return "";
} }
return highestQ.CreateHtml(this.source).Render(); return "<div class='infobox-questions'>" +
highestQ.CreateHtml(this.source).Render() +
"</div>";
} }
InnerUpdate(htmlElement: HTMLElement) { InnerUpdate(htmlElement: HTMLElement) {
} }

View file

@ -9,7 +9,6 @@ export class SlideShow extends UIElement {
private readonly _noimages: UIElement; private readonly _noimages: UIElement;
constructor( constructor(
title: UIElement,
embeddedElements: UIEventSource<UIElement[]>, embeddedElements: UIEventSource<UIElement[]>,
noImages: UIElement) { noImages: UIElement) {
super(embeddedElements); super(embeddedElements);
@ -24,7 +23,9 @@ export class SlideShow extends UIElement {
} }
if (this._embeddedElements.data.length == 1) { if (this._embeddedElements.data.length == 1) {
return "<div class='image-slideshow'>"+this._embeddedElements.data[0].Render()+"</div>"; return "<div class='image-slideshow'><div class='slides'><div class='slide'>" +
this._embeddedElements.data[0].Render() +
"</div></div></div>";
} }
const prevBtn = "<div class='prev-button' id='prevbtn-"+this.id+"'></div>" const prevBtn = "<div class='prev-button' id='prevbtn-"+this.id+"'></div>"
@ -46,26 +47,29 @@ export class SlideShow extends UIElement {
+ "</div>"; + "</div>";
} }
public MoveTo(index: number) {
if (index < 0) {
index = this._embeddedElements.data.length - 1;
}
index = index % this._embeddedElements.data.length;
this._currentSlide.setData(index);
}
InnerUpdate(htmlElement) { InnerUpdate(htmlElement) {
const nextButton = document.getElementById("nextbtn-"+this.id); const nextButton = document.getElementById("nextbtn-" + this.id);
if(nextButton === undefined || nextButton === null){ if (nextButton === undefined || nextButton === null) {
return; return;
} }
const prevButton = document.getElementById("prevbtn-"+this.id); const prevButton = document.getElementById("prevbtn-" + this.id);
const self = this; const self = this;
nextButton.onclick = () => { nextButton.onclick = () => {
const current = self._currentSlide.data; const current = self._currentSlide.data;
const next = (current + 1) % self._embeddedElements.data.length; self.MoveTo(current + 1);
self._currentSlide.setData(next);
} }
prevButton.onclick = () => { prevButton.onclick = () => {
const current = self._currentSlide.data; const current = self._currentSlide.data;
let prev = (current - 1); self.MoveTo(current - 1);
if (prev < 0) {
prev = self._embeddedElements.data.length - 1;
}
self._currentSlide.setData(prev);
} }
} }

View file

@ -7,17 +7,20 @@ export class TagMappingOptions {
mapping?: any;// dictionary for specific values, the values are substituted mapping?: any;// dictionary for specific values, the values are substituted
template?: string; // The template, where {key} will be substituted template?: string; // The template, where {key} will be substituted
missing?: string// What to show when the key is not there missing?: string// What to show when the key is not there
freeform?: ((string) => string) // Freeform template function, only applied on the value if nothing matches
constructor(options: { constructor(options: {
key: string, key: string,
mapping?: any, mapping?: any,
template?: string, template?: string,
missing?: string missing?: string
freeform?: ((string) => string)
}) { }) {
this.key = options.key; this.key = options.key;
this.mapping = options.mapping; this.mapping = options.mapping;
this.template = options.template; this.template = options.template;
this.missing = options.missing; this.missing = options.missing;
this.freeform = options.freeform;
} }
} }
@ -60,12 +63,16 @@ export class TagMapping extends UIElement {
} }
} }
if (o.template === undefined) { if (o.template !== undefined) {
console.log("Warning: no match for " + o.key + "=" + v); return o.template.replace("{" + o.key + "}", v);
return v;
} }
return o.template.replace("{" + o.key + "}", v); if(o.freeform !== undefined){
return o.freeform(v);
}
console.log("Warning: no match for " + o.key + "=" + v);
return v;
} }
InnerUpdate(htmlElement: HTMLElement) { InnerUpdate(htmlElement: HTMLElement) {
} }

View file

@ -2,20 +2,28 @@ import {UIElement} from "./UIElement";
export class VerticalCombine extends UIElement { export class VerticalCombine extends UIElement {
private _elements: UIElement[]; private _elements: UIElement[];
private _className: string;
constructor(elements: UIElement[]) {
constructor(elements: UIElement[], className: string = undefined) {
super(undefined); super(undefined);
this._elements = elements; this._elements = elements;
this._className = className;
} }
protected InnerRender(): string { protected InnerRender(): string {
let html = ""; let html = "";
for (const element of this._elements){ for (const element of this._elements) {
if (!element.IsEmpty()) { if (!element.IsEmpty()) {
html += "<div>" + element.Render() + "</div><br />"; html += "<div>" + element.Render() + "</div>";
} }
} }
return html; if(html === ""){
return "";
}
if (this._className === undefined) {
return html;
}
return "<div class='"+this._className+"'>" + html + "</div>";
} }
InnerUpdate(htmlElement: HTMLElement) { InnerUpdate(htmlElement: HTMLElement) {
for (const element of this._elements){ for (const element of this._elements){

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -256 1950 1950"><path d="m975.19 549.05q119 0 203.5 84.5t84.5 203.5-84.5 203.5-203.5 84.5-203.5-84.5-84.5-203.5 84.5-203.5 203.5-84.5m704-416q106 0 181 75t75 181v896q0 106-75 181t-181 75h-1408q-106 0-181-75t-75-181v-896q0-106 75-181t181-75h224l51-136q19-49 69.5-84.5t103.5-35.5h512q53 0 103.5 35.5t69.5 84.5l51 136h224m-704 1152q185 0 316.5-131.5t131.5-316.5-131.5-316.5-316.5-131.5-316.5 131.5-131.5 316.5 131.5 316.5 316.5 131.5"/></svg>

After

Width:  |  Height:  |  Size: 529 B

79
assets/camera-plus.svg Normal file
View file

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
viewBox="0 -256 1950 1950"
version="1.1"
id="svg4"
sodipodi:docname="camera.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
<metadata
id="metadata10">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs8" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1680"
inkscape:window-height="1013"
id="namedview6"
showgrid="false"
inkscape:zoom="0.1711561"
inkscape:cx="1154.0868"
inkscape:cy="749.93142"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<path
d="m 881.19,449.05 c 79.33333,0 147.1667,28.16667 203.5,84.5 56.3333,56.33333 84.5,124.16667 84.5,203.5 0,79.33333 -28.1667,147.16667 -84.5,203.5 -56.3333,56.3333 -124.16667,84.5 -203.5,84.5 -79.33333,0 -147.16667,-28.1667 -203.5,-84.5 -56.33333,-56.33333 -84.5,-124.16667 -84.5,-203.5 0,-79.33333 28.16667,-147.16667 84.5,-203.5 56.33333,-56.33333 124.16667,-84.5 203.5,-84.5 m 798,-316 c 70.6667,0 131,25 181,75 50,50 75,110.33333 75,181 v 896 c 0,70.6667 -25,131 -75,181 -50,50 -110.3333,75 -181,75 h -1408 c -70.66667,0 -131,-25 -181,-75 -50,-50 -75,-110.3333 -75,-181 v -896 c 0,-70.66667 25,-131 75,-181 50,-50 110.33333,-75 181,-75 h 130 l 51,-136 c 12.66667,-32.666667 35.83333,-60.833333 69.5,-84.5 33.66667,-23.66667 68.16667,-35.5 103.5,-35.5 h 512 c 35.3333,0 69.8333,11.83333 103.5,35.5 33.6667,23.666667 56.8333,51.833333 69.5,84.5 l 51,136 h 318 m -798,1052 c 123.3333,0 228.8333,-43.8333 316.5,-131.5 87.6667,-87.6667 131.5,-193.16667 131.5,-316.5 0,-123.33333 -43.8333,-228.83333 -131.5,-316.5 -87.6667,-87.66667 -193.1667,-131.5 -316.5,-131.5 -123.33333,0 -228.83333,43.83333 -316.5,131.5 -87.66667,87.66667 -131.5,193.16667 -131.5,316.5 0,123.33333 43.83333,228.8333 131.5,316.5 87.66667,87.6667 193.16667,131.5 316.5,131.5"
id="path2"
inkscape:connector-curvature="0"
sodipodi:nodetypes="csssssssccsssssssssssccssssccccsssssssc" />
<g
id="g854"
transform="translate(259.86064,302.45965)">
<g
id="g847">
<circle
style="fill:#7ebc6f;fill-opacity:1;stroke:none;stroke-width:21.93531036;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="path819"
cx="1351.3682"
cy="1044.5065"
r="335.30353" />
</g>
<g
id="g844">
<path
style="fill:none;stroke:#fffff0;stroke-width:95.51803589;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 1154.7797,1044.3443 h 389.1358"
id="path821"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#fffff0;stroke-width:95.51803589;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 1349.9206,849.36269 V 1238.4985"
id="path821-3"
inkscape:connector-curvature="0" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View file

@ -577,7 +577,7 @@
], ],
"tags": { "tags": {
"leisure": "park", "leisure": "park",
"name:etymology:wikidata": "Q12974", "name:etymology:wikidata": "Q12974",
"surface": "wood", "surface": "wood",
"wikidata": "Q54817470", "wikidata": "Q54817470",
"wikipedia": "nl:Koning Albertpark (Brugge)" "wikipedia": "nl:Koning Albertpark (Brugge)"

3
assets/wikipedia.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 164 KiB

130
index.css
View file

@ -144,7 +144,7 @@ img {
#welcomeMessage { #welcomeMessage {
display: inline-block; display: inline-block;
width: 30em; max-width: 30em;
} }
@ -152,14 +152,6 @@ img {
display: none; /*Only shown on small screens*/ display: none; /*Only shown on small screens*/
} }
#messagesboxmobile {
padding-left: 1.5em;
padding-right: 1.5em;
padding-top: 1em;
padding-bottom: 0;
height: 90%;
}
#messagesbox { #messagesbox {
/*Only shown on big screens*/ /*Only shown on big screens*/
padding: 2em; padding: 2em;
@ -192,12 +184,40 @@ img {
width: 100%; width: 100%;
height: 100%; height: 100%;
display: block; display: block;
overflow-y: auto;
background-color: white; background-color: white;
}
#messagesboxmobile-scroll {
display: block;
overflow-y: auto;
height: calc(100vh - 9em);
padding-top: 1em;
padding-bottom: 2em;
margin-bottom: 0;
}
#messagesboxmobile {
margin: 1em;
margin-bottom: 2em;
} }
} }
#to-the-map {
margin: 0;
padding: 1em;
padding-right: 2em;
color: white;
background-color: #7ebc6f;
position: absolute;
bottom: 0;
right: 0;
text-align: right;
width: 100%;
height: 4em;
cursor: pointer;
}
#logo { #logo {
position: relative; position: relative;
display: flex; display: flex;
@ -279,6 +299,7 @@ img {
} }
.slides { .slides {
overflow: hidden;
} }
.prev-button { .prev-button {
@ -380,6 +401,54 @@ img {
color: white; color: white;
} }
/**************** Image upload flow ***************/
.imageflow {
margin-top: 1em;
margin-bottom: 2em;
text-align: center;
}
.imageflow-file-input-wrapper {
display: flex;
flex-wrap: wrap;
padding: 0.5em;
border-radius: 1em;
border: 3px solid black;
}
.imageflow-add-picture {
font-size: 28px;
font-weight: bold;
float: left;
margin-top: 4px;
padding-top: 4px;
padding-bottom: 4px;
padding-left: 13px;
}
.imageflow-file-input-wrapper img {
width: 36px;
height: 36px;
padding: 0.1em;
margin-top: 5px;
border-radius: 0;
float: left;
}
.license-picker {
background-color: orange;
float: left;
}
.imageflow > input {
display: none;
}
/***************** Info box (box containing features and questions ******************/ /***************** Info box (box containing features and questions ******************/
.featureinfobox { .featureinfobox {
@ -394,9 +463,21 @@ img {
right: 0; right: 0;
} }
.osm-logo path { .osm-logo path {
fill: #80cf36; fill: #7ebc6f;
}
.wikipedialink {
position: absolute;
right: 24px;
width: 24px;
height: 24px;
padding-right: 12px;
}
.wikipedialink img {
width: 24px;
height: 24px;
} }
@ -412,24 +493,21 @@ img {
.infoboxcontents { .infoboxcontents {
margin-top: 1em;
margin-bottom: 0.5em;
} }
.infboxcontents-left { .infobox-information {
width: auto; width: 100%;
display: inline-block; margin-top: 1em;
float: left;
padding: 0.1em;
}
.infboxcontents-right {
padding: 0.1em;
overflow: hidden;
display: inline-block;
} }
.infobox-questions { .infobox-questions {
max-width: 25em; margin-top: 1em;
display: inline-block; background-color: #e5f5ff;
padding: 1em;
border-radius: 1em;
margin-right: 1em;
} }

View file

@ -3,6 +3,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MapComplete</title> <title>MapComplete</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css" <link rel="stylesheet" href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css"
integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ==" integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ=="
@ -13,12 +14,15 @@
</head> </head>
<body> <body>
<div id="messagesboxmobilewrapper"> <div id="messagesboxmobilewrapper">
<div id="to-the-map">Return to map</div> <div id="messagesboxmobile-scroll">
<div id="messagesboxmobile"> </div> <div id="messagesboxmobile"> </div>
</div>
<div id="to-the-map">Loading... If this message persists, check if javascript is enabled and if no extension (uMatrix) is blocking it.</div>
</div> </div>
<div id="topleft-tools"> <div id="topleft-tools">
<div id="userbadge"> <div id="userbadge">
Loading... If this message persists, check if javascript is enabled and if no extension (uMatrix) is blocking it.
</div> </div>
<br/> <br/>
<div id="messagesbox"> <div id="messagesbox">

View file

@ -26,7 +26,7 @@ if (location.hostname === "localhost" || location.hostname === "127.0.0.1") {
dryRun = true; dryRun = true;
// If you have a testfile somewhere, enable this to spoof overpass // If you have a testfile somewhere, enable this to spoof overpass
// This should be hosted independantly, e.g. with `cd assets; webfsd -p 8080` + a CORS plugin to disable cors rules // This should be hosted independantly, e.g. with `cd assets; webfsd -p 8080` + a CORS plugin to disable cors rules
Overpass.testUrl = "http://127.0.0.1:8080/test.json"; // Overpass.testUrl = "http://127.0.0.1:8080/test.json";
} }
@ -124,7 +124,6 @@ addButton.Update();
* Show the questions and information for the selected element on the leftMessage * Show the questions and information for the selected element on the leftMessage
*/ */
selectedElement.addCallback((data) => { selectedElement.addCallback((data) => {
console.log("Got selection");
// Which is the applicable set? // Which is the applicable set?
for (const layer of questSetToRender.layers) { for (const layer of questSetToRender.layers) {

View file

@ -5,7 +5,7 @@
<link href="index.css" rel="stylesheet"/> <link href="index.css" rel="stylesheet"/>
</head> </head>
<body> <body>
<div id="maindiv" style="border: 5px dashed red">Hello World</div> <div id="maindiv"e>Hello World</div>
<script src="./test.ts"></script> <script src="./test.ts"></script>
</body> </body>
</html> </html>

24
test.ts
View file

@ -12,6 +12,8 @@ import {TagMapping, TagMappingOptions} from "./UI/TagMapping";
import {CommonTagMappings} from "./Layers/CommonTagMappings"; import {CommonTagMappings} from "./Layers/CommonTagMappings";
import {ImageCarousel} from "./UI/Image/ImageCarousel"; import {ImageCarousel} from "./UI/Image/ImageCarousel";
import {WikimediaImage} from "./UI/Image/WikimediaImage"; import {WikimediaImage} from "./UI/Image/WikimediaImage";
import {OsmImageUploadHandler} from "./Logic/OsmImageUploadHandler";
import {DropDownUI} from "./UI/DropDownUI";
const centerMessage = new UIEventSource<string>(""); const centerMessage = new UIEventSource<string>("");
@ -60,11 +62,25 @@ const tags ={
operator: "Natuurpunt Brugge", operator: "Natuurpunt Brugge",
phone: "+32 50 82 26 97", phone: "+32 50 82 26 97",
website: "https://natuurpuntbrugge.be/schobbejakshoogte/", website: "https://natuurpuntbrugge.be/schobbejakshoogte/",
wikidata: "Q4499623", // wikidata: "Q4499623",
wikipedia: "nl:Schobbejakshoogte" wikipedia: "nl:Schobbejakshoogte",
}; };
const tagsES = allElements.addElement({properties: tags}); const tagsES = allElements.addElement({properties: tags});
new ImageCarousel(tagsES).AttachTo("maindiv").Activate();
// new WikimediaImage(new UIEventSource<string>("File:Brugge_cobblestone_path.jpg")).AttachTo("maindiv"); /*
new OsmImageUploadHandler(tagsES, osmConnection.userDetails, changes)
.getUI().AttachTo("maindiv");
/*/
new FeatureInfoBox(
tagsES,
layer.elementsToShow,
layer.questions,
changes,
osmConnection.userDetails
).AttachTo("maindiv").Activate();
//*/