Further work on infobox, styling everything, removing clutter

This commit is contained in:
Pieter Vander Vennet 2020-06-27 03:06:51 +02:00
parent 2acd53d150
commit 0b4016b65d
48 changed files with 1283 additions and 454 deletions

2
.gitignore vendored
View file

@ -1,2 +1,4 @@
dist/* dist/*
node_modules node_modules
.cache/*
.idea/*

View file

@ -1,25 +1,23 @@
import {OsmConnection} from "./Logic/OsmConnection"; import {OsmConnection} from "./Logic/OsmConnection";
import {Changes} from "./Logic/Changes"; import {Changes} from "./Logic/Changes";
import {UIEventSource} from "./UI/UIEventSource"; import {UIEventSource} from "./UI/UIEventSource";
import {PendingChanges} from "./UI/PendingChanges";
export class Helpers { export class Helpers {
static SetupAutoSave(changes: Changes, secondsTillChangesAreSaved : UIEventSource<number>) { static SetupAutoSave(changes: Changes, millisTillChangesAreSaved : UIEventSource<number>, saveAfterXMillis : number) {
// This little function triggers the actual upload: // This little function triggers the actual upload:
// Either when more then three answers are selected, or when no new answer has been added for the last 20s // Either when more then three answers are selected, or when no new answer has been added for the last 20s
// @ts-ignore // @ts-ignore
window.decreaseTime = function () { window.decreaseTime = function () {
var time = secondsTillChangesAreSaved.data; var time = millisTillChangesAreSaved.data;
if (time <= 0) { if (time <= 0) {
if (changes._pendingChanges.length > 0) { if (changes.pendingChangesES.data > 0) {
changes.uploadAll(undefined); changes.uploadAll(undefined);
} }
} else { } else {
secondsTillChangesAreSaved.setData(time - 1000); millisTillChangesAreSaved.setData(time - 1000);
} }
window.setTimeout('decreaseTime()', 1000); window.setTimeout('decreaseTime()', 1000);
}; };
@ -27,15 +25,15 @@ export class Helpers {
changes.pendingChangesES.addCallback(function () { changes.pendingChangesES.addCallback(function () {
var c = changes._pendingChanges.length; var c = changes.pendingChangesES.data;
if (c > 10) { if (c > 10) {
secondsTillChangesAreSaved.setData(0); millisTillChangesAreSaved.setData(0);
changes.uploadAll(undefined); changes.uploadAll(undefined);
return; return;
} }
if (c > 0) { if (c > 0) {
secondsTillChangesAreSaved.setData(5000); millisTillChangesAreSaved.setData(saveAfterXMillis);
} }
}); });
@ -44,22 +42,6 @@ export class Helpers {
window.decreaseTime(); // The timer keeps running... window.decreaseTime(); // The timer keeps running...
} }
/**
* All elements with class 'activate-osm-authentication' are loaded and get an 'onclick' to authenticate
* @param osmConnection
*/
static registerActivateOsmAUthenticationClass(osmConnection: OsmConnection) {
const authElements = document.getElementsByClassName("activate-osm-authentication");
for (let i = 0; i < authElements.length; i++) {
let element = authElements.item(i);
// @ts-ignore
element.onclick = function () {
osmConnection.AttemptLogin();
}
}
}
/* /*
@ -72,7 +54,7 @@ export class Helpers {
window.addEventListener("beforeunload", function (e) { window.addEventListener("beforeunload", function (e) {
// Quickly save everyting! // Quickly save everyting!
if (changes._pendingChanges.length == 0) { if (changes.pendingChangesES.data == 0) {
return ""; return "";
} }

View file

@ -29,53 +29,20 @@ export class LayerDefinition {
style: (tags: any) => any; style: (tags: any) => any;
removeContainedElements : boolean = false; removeContainedElements: boolean = false;
removeTouchingElements: boolean = false; removeTouchingElements: boolean = false;
asLayer(basemap: Basemap, allElements: ElementStorage, changes: Changes, userDetails: UserDetails): asLayer(basemap: Basemap, allElements: ElementStorage, changes: Changes, userDetails: UIEventSource<UserDetails>, selectedElement: UIEventSource<any>):
FilteredLayer { FilteredLayer {
const self = this; const self = this;
function generateInfoBox(tagsES: UIEventSource<any>) {
var infoboxes: UIElement[] = [];
for (const uiElement of self.elementsToShow) {
if (uiElement instanceof QuestionDefinition) {
const questionDef = uiElement as QuestionDefinition;
const question = new Question(changes, questionDef);
infoboxes.push(question.CreateHtml(tagsES));
} else if (uiElement instanceof TagMappingOptions) {
const tagMappingOpt = uiElement as TagMappingOptions;
infoboxes.push(new TagMapping(tagMappingOpt, tagsES))
} else {
const ui = uiElement as UIElement;
infoboxes.push(ui);
}
}
infoboxes.push(new ImageCarousel(tagsES));
infoboxes.push(new FixedUiElement("<div style='width:750px'></div>"));
infoboxes.push(new OsmImageUploadHandler(
tagsES, userDetails, changes
).getUI());
const qbox = new QuestionPicker(changes.asQuestions(self.questions), tagsES);
infoboxes.push(qbox);
return new VerticalCombine(infoboxes);
}
return new FilteredLayer( return new FilteredLayer(
this.name, this.name,
basemap, allElements, changes, basemap, allElements, changes,
this.overpassFilter, this.overpassFilter,
this.removeContainedElements, this.removeTouchingElements, this.removeContainedElements, this.removeTouchingElements,
generateInfoBox, this.style,
this.style); selectedElement);
} }

View file

@ -58,9 +58,6 @@ export class Bookcases extends LayerDefinition {
new TagMappingOptions({key: "ref", template: "Referentienummer {ref}"}), new TagMappingOptions({key: "ref", template: "Referentienummer {ref}"}),
new TagMappingOptions({key: "description", template: "Extra beschrijving: <br /> <p>{description}</p>"}), new TagMappingOptions({key: "description", template: "Extra beschrijving: <br /> <p>{description}</p>"}),
CommonTagMappings.osmLink
] ]
; ;
} }

View file

@ -32,14 +32,12 @@ export class Bos extends LayerDefinition {
this.elementsToShow = [ this.elementsToShow = [
new TagMappingOptions({ new TagMappingOptions({
key: "name", key: "name",
template: "<h2>{name}</h2>", template: "{name}",
missing: "<h2>Naamloos bos</h2>" missing: "Naamloos bos"
}), }),
CommonTagMappings.access, CommonTagMappings.access,
CommonTagMappings.operator, CommonTagMappings.operator,
CommonTagMappings.osmLink
]; ];
} }

View file

@ -1,4 +1,5 @@
import {TagMappingOptions} from "../UI/TagMapping"; import {TagMappingOptions} from "../UI/TagMapping";
import {Img} from "../UI/Img";
export class CommonTagMappings { export class CommonTagMappings {
@ -28,6 +29,8 @@ export class CommonTagMappings {
mapping: { mapping: {
"node/-1": "<span class='osmlink'>Over enkele momenten sturen we je punt naar OpenStreetMap</span>" "node/-1": "<span class='osmlink'>Over enkele momenten sturen we je punt naar OpenStreetMap</span>"
}, },
template: "<span class='osmlink'><a href='https://osm.org/{id}'> Op OSM</a></span>" template: "<span class='osmlink'><a href='https://osm.org/{id}' target='_blank'>" +
Img.osmAbstractLogo +
"</a></span>"
}) })
} }

View file

@ -49,7 +49,7 @@ export class KnownSet {
static groen = new KnownSet("groen", static groen = new KnownSet("groen",
"Buurtnatuur", "Buurtnatuur",
[new NatureReserves(), new Park(), new Bos(), new Playground()], [new NatureReserves(), new Park(), new Bos()],
14, 14,
51.2, 51.2,
3.2, 3.2,

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 NatureReserves extends LayerDefinition { export class NatureReserves extends LayerDefinition {
@ -11,7 +11,8 @@ export class NatureReserves extends LayerDefinition {
super(); super();
this.name = "natuurgebied"; this.name = "natuurgebied";
this.icon = "./assets/tree_white_background.svg"; this.icon = "./assets/tree_white_background.svg";
this.overpassFilter = new Tag("leisure", "nature_reserve"); this.overpassFilter =
new Or([new Tag("leisure", "nature_reserve"), new Tag("boundary","protected_area")]);
this.removeTouchingElements = true; this.removeTouchingElements = true;
this.newElementTags = [new Tag("leisure", "nature_reserve"), this.newElementTags = [new Tag("leisure", "nature_reserve"),
@ -22,12 +23,11 @@ export class NatureReserves extends LayerDefinition {
this.elementsToShow = [ this.elementsToShow = [
new TagMappingOptions({ new TagMappingOptions({
key: "name", key: "name",
template: "<h2>{name}</h2>", template: "{name}",
missing: "<h2>Naamloos gebied</h2>" missing: "Naamloos gebied"
}), }),
CommonTagMappings.access, CommonTagMappings.access,
CommonTagMappings.operator, CommonTagMappings.operator,
CommonTagMappings.osmLink
]; ];
} }

View file

@ -22,15 +22,12 @@ export class Park extends LayerDefinition {
this.elementsToShow = [ this.elementsToShow = [
new TagMappingOptions({ new TagMappingOptions({
key: "name", key: "name",
template: "<h2>{name}</h2>", template: "{name}",
missing: "<h2>Naamloos park</h2>" missing: "Naamloos park"
}), }),
CommonTagMappings.access, CommonTagMappings.access,
CommonTagMappings.operator, CommonTagMappings.operator,
CommonTagMappings.osmLink,
]; ];
} }

View file

@ -82,9 +82,6 @@ export class Toilets extends LayerDefinition{
} }
}), }),
CommonTagMappings.osmLink
]; ];
} }

View file

@ -58,21 +58,36 @@ export class Basemap {
constructor(leafletElementId: string, location: UIEventSource<{ zoom: number, lat: number, lon: number }>) { constructor(leafletElementId: string, location: UIEventSource<{ zoom: number, lat: number, lon: number }>) {
this. map = L.map(leafletElementId, { this.map = L.map(leafletElementId, {
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;
L.control.layers(this.baseLayers).addTo(this.map); const layerControl = L.control.layers(this.baseLayers, null,
{
position: 'bottomleft',
hideSingleBase: true
})
layerControl.addTo(this.map);
this.map.zoomControl.setPosition("bottomleft"); this.map.zoomControl.setPosition("bottomleft");
const self = this; const self = this;
this.map.on("moveend", function () { this.map.on("moveend", function () {
location.data.zoom = self.map.getZoom(); location.data.zoom = self.map.getZoom();
location.data.lat = self.map.getCenter().lat; location.data.lat = self.map.getCenter().lat;
location.data.lon = self.map.getCenter().lon; location.data.lon = self.map.getCenter().lon;
location.ping(); location.ping();
}) });
} }

View file

@ -16,16 +16,23 @@ export class Changes {
private readonly login: OsmConnection; private readonly login: OsmConnection;
public readonly _allElements: ElementStorage; public readonly _allElements: ElementStorage;
public _pendingChanges: { elementId: string, key: string, value: string }[] = []; // Gets reset on uploadAll private _pendingChanges: { elementId: string, key: string, value: string }[] = []; // Gets reset on uploadAll
private newElements: OsmObject[] = []; // Gets reset on uploadAll private newElements: OsmObject[] = []; // Gets reset on uploadAll
public readonly pendingChangesES = new UIEventSource(this._pendingChanges); public readonly pendingChangesES = new UIEventSource<number>(this._pendingChanges.length);
private readonly centerMessage: UIEventSource<string>; public readonly isSaving = new UIEventSource(false);
private readonly _changesetComment: string;
private readonly _centerMessage: UIEventSource<string>;
constructor(login: OsmConnection, allElements: ElementStorage, centerMessage: UIEventSource<string>) { constructor(
changesetComment: string,
login: OsmConnection,
allElements: ElementStorage,
centerMessage: UIEventSource<string>) {
this._changesetComment = changesetComment;
this.login = login; this.login = login;
this._allElements = allElements; this._allElements = allElements;
this.centerMessage = centerMessage; this._centerMessage = centerMessage;
} }
/** /**
@ -37,15 +44,15 @@ export class Changes {
addChange(elementId: string, key: string, value: string) { addChange(elementId: string, key: string, value: string) {
if (!this.login.userDetails.data.loggedIn) { if (!this.login.userDetails.data.loggedIn) {
this.centerMessage.setData( this._centerMessage.setData(
"<p>Bedankt voor je antwoord!</p>" + "<p>Bedankt voor je antwoord!</p>" +
"<p>Gelieve <span class='activate-osm-authentication'>in te loggen op OpenStreetMap</span> om dit op te slaan.</p>"+ "<p>Gelieve <span class='activate-osm-authentication'>in te loggen op OpenStreetMap</span> om dit op te slaan.</p>" +
"<p>Nog geen account? <a href=\'https://www.openstreetmap.org/user/new\' target=\'_blank\'>Registreer hier</a></p>" "<p>Nog geen account? <a href=\'https://www.openstreetmap.org/user/new\' target=\'_blank\'>Registreer hier</a></p>"
); );
const self = this; const self = this;
this.login.userDetails.addCallback(() => { this.login.userDetails.addCallback(() => {
if (self.login.userDetails.data.loggedIn) { if (self.login.userDetails.data.loggedIn) {
self.centerMessage.setData(""); self._centerMessage.setData("");
} }
}); });
return; return;
@ -67,7 +74,7 @@ export class Changes {
eventSource.ping(); eventSource.ping();
// We get the id from the event source, as that ID might be rewritten // We get the id from the event source, as that ID might be rewritten
this._pendingChanges.push({elementId: eventSource.data.id, key: key, value: value}); this._pendingChanges.push({elementId: eventSource.data.id, key: key, value: value});
this.pendingChangesES.ping(); this.pendingChangesES.setData(this._pendingChanges.length);
} }
@ -114,9 +121,18 @@ export class Changes {
public uploadAll(optionalContinuation: (() => void)) { public uploadAll(optionalContinuation: (() => void)) {
const self = this; const self = this;
this.isSaving.setData(true);
const optionalContinuationWrapped = function () {
self.isSaving.setData(false);
if (optionalContinuation) {
optionalContinuation();
}
}
const pending: { elementId: string; key: string; value: string }[] = this._pendingChanges; const pending: { elementId: string; key: string; value: string }[] = this._pendingChanges;
this._pendingChanges = []; this._pendingChanges = [];
this.pendingChangesES.setData(this._pendingChanges); this.pendingChangesES.setData(this._pendingChanges.length);
const newElements = this.newElements; const newElements = this.newElements;
this.newElements = []; this.newElements = [];
@ -203,7 +219,7 @@ export class Changes {
console.log("Beginning upload..."); console.log("Beginning upload...");
// At last, we build the changeset and upload // At last, we build the changeset and upload
self.login.UploadChangeset("Updaten van metadata met Mapcomplete", self.login.UploadChangeset(self._changesetComment,
function (csId) { function (csId) {
let modifications = ""; let modifications = "";
@ -243,7 +259,7 @@ export class Changes {
return changes; return changes;
}, },
handleMapping, handleMapping,
optionalContinuation); optionalContinuationWrapped);
}); });
} }

View file

@ -25,8 +25,6 @@ export class FilteredLayer {
private readonly _removeContainedElements; private readonly _removeContainedElements;
private readonly _removeTouchingElements; private readonly _removeTouchingElements;
private readonly _popupContent: ((source: UIEventSource<any>) => UIElement);
private readonly _style: (properties) => any; private readonly _style: (properties) => any;
private readonly _storage: ElementStorage; private readonly _storage: ElementStorage;
@ -41,6 +39,7 @@ export class FilteredLayer {
* The leaflet layer object which should be removed on rerendering * The leaflet layer object which should be removed on rerendering
*/ */
private _geolayer; private _geolayer;
private _selectedElement: UIEventSource<any>;
constructor( constructor(
name: string, name: string,
@ -49,8 +48,9 @@ export class FilteredLayer {
filters: TagsFilter, filters: TagsFilter,
removeContainedElements: boolean, removeContainedElements: boolean,
removeTouchingElements: boolean, removeTouchingElements: boolean,
popupContent: ((source: UIEventSource<any>) => UIElement), style: ((properties) => any),
style: ((properties) => any)) { selectedElement: UIEventSource<any>) {
this._selectedElement = selectedElement;
if (style === undefined) { if (style === undefined) {
style = function () { style = function () {
@ -60,7 +60,6 @@ export class FilteredLayer {
this.name = name; this.name = name;
this._map = map; this._map = map;
this.filters = filters; this.filters = filters;
this._popupContent = popupContent;
this._style = style; this._style = style;
this._storage = storage; this._storage = storage;
this._removeContainedElements = removeContainedElements; this._removeContainedElements = removeContainedElements;
@ -167,8 +166,6 @@ export class FilteredLayer {
}, },
pointToLayer: function (feature, latLng) { pointToLayer: function (feature, latLng) {
const eventSource = self._storage.addOrGetElement(feature);
const style = self._style(feature.properties); const style = self._style(feature.properties);
let marker; let marker;
if (style.icon === undefined) { if (style.icon === undefined) {
@ -180,19 +177,6 @@ export class FilteredLayer {
}); });
} }
eventSource.addCallback(function () {
self.updateStyle();
});
const content = self._popupContent(eventSource)
marker.bindPopup(
"<div class='popupcontent'>" +
content.Render() +
"</div>"
).on("popupopen", function () {
content.Activate();
content.Update();
});
return marker; return marker;
}, },
@ -203,14 +187,9 @@ export class FilteredLayer {
eventSource.addCallback(function () { eventSource.addCallback(function () {
self.updateStyle(); self.updateStyle();
}); });
const content = self._popupContent(eventSource) layer.on("click", function(){
layer.bindPopup( console.log("Selected ",feature)
"<div class='popupcontent'>" + self._selectedElement.setData(feature.properties);
content.Render() +
"</div>"
).on("popupopen", function () {
content.Activate();
content.Update();
}); });
} }
}); });

View file

@ -34,7 +34,9 @@ export class ImageSearcher extends UIEventSource<string[]> {
self.AddImage(wd.image); self.AddImage(wd.image);
Wikimedia.GetCategoryFiles(wd.commonsWiki, (images: ImagesInCategory) => { Wikimedia.GetCategoryFiles(wd.commonsWiki, (images: ImagesInCategory) => {
for (const image of images.images) { for (const image of images.images) {
self.AddImage(image.filename); if (image.startsWith("File:")) {
self.AddImage(image);
}
} }
}) })
}) })
@ -48,7 +50,10 @@ export class ImageSearcher extends UIEventSource<string[]> {
if (commons.startsWith("Category:")) { if (commons.startsWith("Category:")) {
Wikimedia.GetCategoryFiles(commons, (images: ImagesInCategory) => { Wikimedia.GetCategoryFiles(commons, (images: ImagesInCategory) => {
for (const image of images.images) { for (const image of images.images) {
self.AddImage(image.filename); // @ts-ignore
if (image.startsWith("File:")) {
self.AddImage(image);
}
} }
}) })
} else { // @ts-ignore } else { // @ts-ignore
@ -125,7 +130,7 @@ export class ImageSearcher extends UIEventSource<string[]> {
const urlSource = new UIEventSource<string>(url); const urlSource = new UIEventSource<string>(url);
// @ts-ignore // @ts-ignore
if (url.startsWith("File:")) { if (url.startsWith("File:")) {
return new WikimediaImage(urlSource); return new WikimediaImage(urlSource.data);
} else { } else {
return new SimpleImageElement(urlSource); return new SimpleImageElement(urlSource);
} }

View file

@ -57,9 +57,13 @@ export class LayerUpdater {
} }
private handleFail(reason: any) { private handleFail(reason: any) {
this.runningQuery.setData(false);
console.log("QUERY FAILED", reason); console.log("QUERY FAILED", reason);
// TODO console.log("Retrying in 1s")
this.previousBounds = undefined;
const self = this;
window.setTimeout(
function(){self.update()}, 1000
)
} }
@ -89,7 +93,7 @@ export class LayerUpdater {
} }
buildBboxFor(): string { private buildBboxFor(): string {
const b = this._map.map.getBounds(); const b = this._map.map.getBounds();
const latDiff = Math.abs(b.getNorth() - b.getSouth()); const latDiff = Math.abs(b.getNorth() - b.getSouth());
const lonDiff = Math.abs(b.getEast() - b.getWest()); const lonDiff = Math.abs(b.getEast() - b.getWest());
@ -101,8 +105,7 @@ export class LayerUpdater {
this.previousBounds = {north: n, east: e, south: s, west: w}; this.previousBounds = {north: n, east: e, south: s, west: w};
const bbox = "[bbox:" + s + "," + w + "," + n + "," + e + "]"; return "[bbox:" + s + "," + w + "," + n + "," + e + "]";
return bbox;
} }
private IsInBounds(): boolean { private IsInBounds(): boolean {

View file

@ -9,6 +9,9 @@ export class UserDetails {
public csCount = 0; public csCount = 0;
public img: string; public img: string;
public unreadMessages = 0; public unreadMessages = 0;
public totalMessages = 0;
public osmConnection : OsmConnection;
public dryRun : boolean;
} }
@ -26,10 +29,9 @@ export class OsmConnection {
constructor(dryRun: boolean) { constructor(dryRun: boolean) {
this.userDetails = new UIEventSource<UserDetails>(new UserDetails()); this.userDetails = new UIEventSource<UserDetails>(new UserDetails());
this.userDetails.data.osmConnection = this;
this.userDetails.data.dryRun = dryRun;
this._dryRun = dryRun; this._dryRun = dryRun;
if(dryRun){
alert("Opgelet: testmode actief. Wijzigingen worden NIET opgeslaan")
}
if (this.auth.authenticated()) { if (this.auth.authenticated()) {
this.AttemptLogin(); // Also updates the user badge this.AttemptLogin(); // Also updates the user badge
@ -61,23 +63,43 @@ export class OsmConnection {
self.userDetails.ping(); self.userDetails.ping();
} }
if(details == null){ if (details == null) {
return; return;
} }
// details is an XML DOM of user details // details is an XML DOM of user details
let userInfo = details.getElementsByTagName("user")[0]; let userInfo = details.getElementsByTagName("user")[0];
// let moreDetails = new DOMParser().parseFromString(userInfo.innerHTML, "text/xml");
let data = self.userDetails.data; let data = self.userDetails.data;
data.loggedIn = true; data.loggedIn = true;
console.log(userInfo); console.log(userInfo);
data.name = userInfo.getAttribute('display_name'); data.name = userInfo.getAttribute('display_name');
data.csCount = userInfo.getElementsByTagName("changesets")[0].getAttribute("count"); data.csCount = userInfo.getElementsByTagName("changesets")[0].getAttribute("count");
data.img = userInfo.getElementsByTagName("img")[0].getAttribute("href"); data.img = userInfo.getElementsByTagName("img")[0].getAttribute("href");
data.unreadMessages = userInfo.getElementsByTagName("received")[0].getAttribute("unread"); const messages = userInfo.getElementsByTagName("messages")[0].getElementsByTagName("received")[0];
data.unreadMessages = parseInt(messages.getAttribute("unread"));
data.totalMessages = parseInt(messages.getAttribute("count"));
self.userDetails.ping(); self.userDetails.ping();
}); });
} }
/**
* All elements with class 'activate-osm-authentication' are loaded and get an 'onclick' to authenticate
* @param osmConnection
*/
registerActivateOsmAUthenticationClass() {
const authElements = document.getElementsByClassName("activate-osm-authentication");
for (let i = 0; i < authElements.length; i++) {
let element = authElements.item(i);
// @ts-ignore
element.onclick = function () {
this.AttemptLogin();
}
}
}
private static parseUploadChangesetResponse(response: XMLDocument) { private static parseUploadChangesetResponse(response: XMLDocument) {
const nodes = response.getElementsByTagName("node"); const nodes = response.getElementsByTagName("node");
const mapping = {}; const mapping = {};
@ -102,6 +124,7 @@ export class OsmConnection {
console.log("NOT UPLOADING as dryrun is true"); console.log("NOT UPLOADING as dryrun is true");
var changesetXML = generateChangeXML("123456"); var changesetXML = generateChangeXML("123456");
console.log(changesetXML); console.log(changesetXML);
continuation();
return; return;
} }

View file

@ -9,29 +9,26 @@ import {UserDetails} from "./OsmConnection";
export class OsmImageUploadHandler { export class OsmImageUploadHandler {
private _tags: UIEventSource<any>; private _tags: UIEventSource<any>;
private _changeHandler: Changes; private _changeHandler: Changes;
private _userdetails: UserDetails; private _userdetails: UIEventSource<UserDetails>;
constructor(tags: UIEventSource<any>, constructor(tags: UIEventSource<any>,
userdetails: UserDetails, userdetails: UIEventSource<UserDetails>,
changeHandler: Changes changeHandler: Changes
) { ) {
if (tags === undefined || userdetails === undefined || changeHandler === undefined) { if (tags === undefined || userdetails === undefined || changeHandler === undefined) {
throw "Something is undefined" throw "Something is undefined"
} }
console.log(tags, changeHandler, userdetails)
this._tags = tags; this._tags = tags;
this._changeHandler = changeHandler; this._changeHandler = changeHandler;
this._userdetails = userdetails; this._userdetails = userdetails;
} }
private generateOptions(license: string) { private generateOptions(license: string) {
console.log(this)
console.log(this._tags, this._changeHandler, this._userdetails)
const tags = this._tags.data; const tags = this._tags.data;
const title = tags.name ?? "Unknown area"; const title = tags.name ?? "Unknown area";
const description = [ const description = [
"author:" + this._userdetails.name, "author:" + this._userdetails.data.name,
"license:" + license, "license:" + license,
"wikidata:" + tags.wikidata, "wikidata:" + tags.wikidata,
"osmid:" + tags.id, "osmid:" + tags.id,
@ -60,7 +57,9 @@ export class OsmImageUploadHandler {
getUI(): ImageUploadFlow { getUI(): ImageUploadFlow {
const self = this; const self = this;
return new ImageUploadFlow(function (license) { return new ImageUploadFlow(
this._userdetails,
function (license) {
return self.generateOptions(license) return self.generateOptions(license)
} }
); );

View file

@ -26,8 +26,7 @@ export class Overpass {
const query = const query =
'[out:json][timeout:25]' + bbox + ';(' + filter + ');out body;>;out skel qt;'; '[out:json][timeout:25]' + bbox + ';(' + filter + ');out body;>;out skel qt;';
console.log(query); console.log(query);
const url = "https://overpass-api.de/api/interpreter?data=" + encodeURIComponent(query); return "https://overpass-api.de/api/interpreter?data=" + encodeURIComponent(query);
return url;
} }

View file

@ -81,7 +81,7 @@ export class QuestionUI extends UIElement {
const embeddedScriptSave = 'questionAnswered(' + this._qid + ', "' + this._tags.data.id + '", false )'; const embeddedScriptSave = 'questionAnswered(' + this._qid + ', "' + this._tags.data.id + '", false )';
const embeddedScriptSkip = 'questionAnswered(' + this._qid + ', "' + this._tags.data.id + '", true )'; const embeddedScriptSkip = 'questionAnswered(' + this._qid + ', "' + this._tags.data.id + '", true )';
const saveButton = "<input class='save-button' type='button' onclick='" + embeddedScriptSave + "' value='Opslaan' />"; const saveButton = "<input class='save-button' type='button' onclick='" + embeddedScriptSave + "' value='Opslaan' />";
const skip = "<input class='skip-button' type='button' onclick='" + embeddedScriptSkip + "' value='Ik ben het niet zeker (vraag overslaan)' />"; const skip = "<input class='skip-button' type='button' onclick='" + embeddedScriptSkip + "' value='Ik ben niet zeker (vraag overslaan)' />";
return q.question + "<br/> " + answers + saveButton + skip; return q.question + "<br/> " + answers + saveButton + skip;
} }

View file

@ -68,8 +68,7 @@ export class Wikimedia {
for (const member of members) { for (const member of members) {
imageOverview.images.push( imageOverview.images.push(member.title);
{filename: member.title, fileid: member.pageid});
} }
if (response.continue === undefined || alreadyLoaded > 30) { if (response.continue === undefined || alreadyLoaded > 30) {
handleCategory(imageOverview); handleCategory(imageOverview);
@ -96,7 +95,10 @@ export class Wikimedia {
wd.commonsWiki = commons?.title; wd.commonsWiki = commons?.title;
// P18 is the claim 'depicted in this image' // P18 is the claim 'depicted in this image'
wd.image = "File:" + entity.claims.P18?.[0]?.mainsnak?.datavalue?.value; const image = entity.claims.P18?.[0]?.mainsnak?.datavalue?.value;
if (image) {
wd.image = "File:" + image;
}
handleWikidata(wd); handleWikidata(wd);
}); });
} }
@ -114,7 +116,7 @@ export class Wikidata {
export class ImagesInCategory { export class ImagesInCategory {
// Filenames of relevant images // Filenames of relevant images
images: { filename: string, fileid: number }[] = []; images: string[] = [];
} }
export class LicenseInfo { export class LicenseInfo {

View file

@ -54,7 +54,8 @@ export class Quests {
).addUnrequiredTag("seamark:type", "restricted_area"); ).addUnrequiredTag("seamark:type", "restricted_area");
static nameOf(name: string) : QuestionDefinition { static nameOf(name: string) : QuestionDefinition {
return QuestionDefinition.noNameOrNameQuestion("Wat is de naam van dit " + name + "?", return QuestionDefinition.noNameOrNameQuestion("<b>Wat is de <i>officiële</i> naam van dit " + name + "?</b><br />" +
"Gelieve geen naam uit te vinden",
"Dit " + name + " heeft geen naam", 20); "Dit " + name + " heeft geen naam", 20);
} }

View file

@ -62,7 +62,7 @@ export class CenterMessageBox extends UIElement {
if (this._centermessage.data != "") { if (this._centermessage.data != "") {
pstyle.opacity = "1"; pstyle.opacity = "1";
pstyle.pointerEvents = "all"; pstyle.pointerEvents = "all";
Helpers.registerActivateOsmAUthenticationClass(this._osmConnection); this._osmConnection.registerActivateOsmAUthenticationClass();
return; return;
} }
pstyle.pointerEvents = "none"; pstyle.pointerEvents = "none";

115
UI/FeatureInfoBox.ts Normal file
View file

@ -0,0 +1,115 @@
import {UIElement} from "./UIElement";
import {TagMapping, TagMappingOptions} from "./TagMapping";
import {Question, QuestionDefinition} from "../Logic/Question";
import {UIEventSource} from "./UIEventSource";
import {VerticalCombine} from "./VerticalCombine";
import {QuestionPicker} from "./QuestionPicker";
import {OsmImageUploadHandler} from "../Logic/OsmImageUploadHandler";
import {ImageCarousel} from "./Image/ImageCarousel";
import {Changes} from "../Logic/Changes";
import {UserDetails} from "../Logic/OsmConnection";
import {Img} from "./Img";
import {CommonTagMappings} from "../Layers/CommonTagMappings";
import {Tag} from "../Logic/TagsFilter";
export class FeatureInfoBox extends UIElement {
private _tagsES: UIEventSource<any>;
private _title: UIElement;
private _osmLink: UIElement;
private _infoElements: UIElement[]
private _questions: QuestionPicker;
private _changes: Changes;
private _userDetails: UIEventSource<UserDetails>;
private _imageElement: ImageCarousel;
constructor(
tagsES: UIEventSource<any>,
elementsToShow: (TagMappingOptions | QuestionDefinition | UIElement)[],
questions: QuestionDefinition[],
changes: Changes,
userDetails: UIEventSource<UserDetails>,
) {
super(tagsES);
this._tagsES = tagsES;
this._changes = changes;
this._userDetails = userDetails;
this._imageElement = new ImageCarousel(this._tagsES);
this._questions = new QuestionPicker(
this._changes.asQuestions(questions), this._tagsES);
var infoboxes: UIElement[] = [];
for (const uiElement of elementsToShow) {
if (uiElement instanceof QuestionDefinition) {
const questionDef = uiElement as QuestionDefinition;
const question = new Question(this._changes, questionDef);
infoboxes.push(question.CreateHtml(this._tagsES));
} else if (uiElement instanceof TagMappingOptions) {
const tagMappingOpt = uiElement as TagMappingOptions;
infoboxes.push(new TagMapping(tagMappingOpt, this._tagsES))
} else {
const ui = uiElement as UIElement;
infoboxes.push(ui);
}
}
this._title = infoboxes.shift();
this._infoElements = infoboxes;
this._osmLink = new TagMapping(CommonTagMappings.osmLink, this._tagsES);
}
InnerRender(): string {
return "<div class='featureinfobox'>" +
"<div class='featureinfoboxtitle'>" +
"<span>" + this._title.Render() + "</span>" +
this._osmLink.Render() +
"</div>" +
"<div class='infoboxcontents'>" +
this._imageElement.Render() +
new VerticalCombine(this._infoElements).Render() +
" <span class='infobox-questions'>" +
this._questions.Render() +
" </span>" +
"</div>" +
"" +
"</div>";
}
Activate() {
super.Activate();
this._imageElement.Activate();
}
Update() {
super.Update();
this._imageElement.Update();
}
private generateInfoBox() {
var infoboxes: UIElement[] = [];
infoboxes.push(new OsmImageUploadHandler(
this._tagsES, this._userDetails, this._changes
).getUI());
return new VerticalCombine(infoboxes);
}
}

View file

@ -35,7 +35,7 @@ export class ImageCarousel extends UIElement {
this.slideshow = new SlideShow( this.slideshow = new SlideShow(
new FixedUiElement("<b>Afbeeldingen</b>"), new FixedUiElement("<b>Afbeeldingen</b>"),
uiElements, uiElements,
new FixedUiElement("<i>Geen afbeeldingen gevonden</i>")); new FixedUiElement("")).HideOnEmpty(true);
} }
InnerRender(): string { InnerRender(): string {

View file

@ -1,36 +1,52 @@
import {UIEventSource} from "../UIEventSource"; import {UIEventSource} from "../UIEventSource";
import {UIElement} from "../UIElement"; import {UIElement} from "../UIElement";
import {SimpleImageElement} from "./SimpleImageElement";
import {LicenseInfo, Wikimedia} from "../../Logic/Wikimedia"; import {LicenseInfo, Wikimedia} from "../../Logic/Wikimedia";
export class WikimediaImage extends UIElement { export class WikimediaImage extends UIElement {
static allLicenseInfos: any = {};
private _imageMeta: UIEventSource<LicenseInfo>; private _imageMeta: UIEventSource<LicenseInfo>;
private _imageLocation : string;
constructor(source: string) {
super(undefined)
this._imageLocation = source;
if (WikimediaImage.allLicenseInfos[source] !== undefined) {
this._imageMeta = WikimediaImage.allLicenseInfos[source];
} else {
this._imageMeta = new UIEventSource<LicenseInfo>(new LicenseInfo());
WikimediaImage.allLicenseInfos[source] = this._imageMeta;
}
constructor(source: UIEventSource<string>) { this.ListenTo(this._imageMeta);
super(source)
const meta = new UIEventSource<LicenseInfo>(new LicenseInfo()); const self = this;
this.ListenTo(meta); Wikimedia.LicenseData(source, (info) => {
this._imageMeta = meta; self._imageMeta.setData(info);
this._source.addCallback(() => {
Wikimedia.LicenseData(source.data, (info) => {
meta.setData(info);
}) })
});
this._source.ping();
} }
protected InnerRender(): string { protected InnerRender(): string {
let url = Wikimedia.ImageNameToUrl(this._source.data); let url = Wikimedia.ImageNameToUrl(this._imageLocation, 500, 400);
url = url.replace(/'/g, '%27'); url = url.replace(/'/g, '%27');
return "<div class='imgWithAttr'><img class='attributedImage' src='" + url + "' " +
"alt='" + this._imageMeta.data.description + "' >" + const wikimediaLink =
"<br /><span class='attribution'>" + "<a href='https://commons.wikimedia.org/wiki/" + this._imageLocation + "' target='_blank'>" +
"<a href='https://commons.wikimedia.org/wiki/"+this._source.data+"' target='_blank'><b>" + (this._source.data) + "</b></a> <br />" + "<img class='wikimedia-link' src='./assets/wikimedia-commons-white.svg' alt='Wikimedia Commons Logo'/>" +
(this._imageMeta.data.artist ?? "Unknown artist") + " " + (this._imageMeta.data.licenseShortName ?? "") + "</a> ";
"</span>" +
const attribution =
"<span class='attribution-author'>" + (this._imageMeta.data.artist ?? "") + "</span>" + " <span class='license'>" + (this._imageMeta.data.licenseShortName ?? "") + "</span>";
const image = "<img src='" + url + "' " + "alt='" + this._imageMeta.data.description + "' >";
return "<div class='imgWithAttr'>" +
image +
"<div class='attribution'>" +
wikimediaLink +
attribution +
"</div>" +
"</div>"; "</div>";
} }

View file

@ -4,14 +4,18 @@ 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";
export class ImageUploadFlow extends UIElement { export class ImageUploadFlow extends UIElement {
private _licensePicker: UIRadioButton; private _licensePicker: UIRadioButton;
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) };
private _userdetails: UIEventSource<UserDetails>;
constructor(uploadOptions: ((license: string) => constructor(
userInfo: UIEventSource<UserDetails>,
uploadOptions: ((license: string) =>
{ {
title: string, title: string,
description: string, description: string,
@ -20,6 +24,8 @@ export class ImageUploadFlow extends UIElement {
}) })
) { ) {
super(undefined); super(undefined);
this._userdetails = userInfo;
this.ListenTo(userInfo);
this._uploadOptions = uploadOptions; this._uploadOptions = uploadOptions;
this.ListenTo(this._isUploading); this.ListenTo(this._isUploading);
this._licensePicker = UIRadioButton.FromStrings( this._licensePicker = UIRadioButton.FromStrings(
@ -49,6 +55,10 @@ export class ImageUploadFlow extends UIElement {
protected InnerRender(): string { protected InnerRender(): string {
if (!this._userdetails.data.loggedIn) {
return "<div class='activate-osm-authentication'>Gelieve je aan te melden om een foto toe te voegen</div>";
}
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>"
} }
@ -63,6 +73,13 @@ export class ImageUploadFlow extends UIElement {
InnerUpdate(htmlElement: HTMLElement) { InnerUpdate(htmlElement: HTMLElement) {
super.InnerUpdate(htmlElement); super.InnerUpdate(htmlElement);
const user = this._userdetails.data;
if(!user.loggedIn){
htmlElement.onclick = function(){
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;

14
UI/Img.ts Normal file
View file

@ -0,0 +1,14 @@
export class Img {
static osmAbstractLogo: string =
"<svg class='osm-logo' xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\" width=\"24px\" version=\"1.1\" viewBox=\"0 0 66 64\">" +
" <g transform=\"translate(-0.849, -61)\">\n" +
" <path d=\"M0.849,61 L6.414,75.609 L0.849,90.217 L6.414,104.826 L0.849,119.435 L4.266,120.739 L22.831,102.183 L26.162,102.696 L30.205,98.652 C27.819,95.888 26.033,92.59 25.057,88.948 L26.953,87.391 C26.627,85.879 26.449,84.313 26.449,82.704 C26.449,74.67 30.734,67.611 37.136,63.696 L30.066,61 L15.457,66.565 L0.849,61 z\"></path>" +
" <path d=\"M48.71,64.617 C48.406,64.617 48.105,64.629 47.805,64.643 C47.52,64.657 47.234,64.677 46.953,64.704 C46.726,64.726 46.499,64.753 46.275,64.783 C46.039,64.814 45.811,64.847 45.579,64.887 C45.506,64.9 45.434,64.917 45.362,64.93 C45.216,64.958 45.072,64.987 44.927,65.017 C44.812,65.042 44.694,65.06 44.579,65.087 C44.442,65.119 44.307,65.156 44.17,65.191 C43.943,65.25 43.716,65.315 43.492,65.383 C43.323,65.433 43.155,65.484 42.988,65.539 C42.819,65.595 42.65,65.652 42.483,65.713 C42.475,65.716 42.466,65.719 42.457,65.722 C35.819,68.158 31.022,74.369 30.649,81.774 C30.633,82.083 30.622,82.391 30.622,82.704 C30.622,83.014 30.631,83.321 30.649,83.626 C30.649,83.629 30.648,83.632 30.649,83.635 C30.662,83.862 30.681,84.088 30.701,84.313 C31.466,93.037 38.377,99.948 47.101,100.713 C47.326,100.733 47.552,100.754 47.779,100.765 C47.782,100.765 47.785,100.765 47.788,100.765 C48.093,100.783 48.399,100.791 48.709,100.791 C53.639,100.791 58.096,98.833 61.353,95.652 C61.532,95.477 61.712,95.304 61.883,95.122 C61.913,95.09 61.941,95.058 61.97,95.026 C61.98,95.015 61.987,95.002 61.996,94.991 C62.132,94.845 62.266,94.698 62.396,94.548 C62.449,94.487 62.501,94.426 62.553,94.365 C62.594,94.316 62.634,94.267 62.675,94.217 C62.821,94.04 62.961,93.861 63.101,93.678 C63.279,93.444 63.456,93.199 63.622,92.956 C63.956,92.471 64.267,91.97 64.553,91.452 C64.661,91.257 64.757,91.06 64.857,90.861 C64.89,90.796 64.93,90.735 64.962,90.67 C64.98,90.633 64.996,90.594 65.014,90.556 C65.125,90.324 65.234,90.09 65.336,89.852 C65.349,89.82 65.365,89.789 65.379,89.756 C65.48,89.517 65.575,89.271 65.666,89.026 C65.678,88.994 65.689,88.962 65.701,88.93 C65.792,88.679 65.881,88.43 65.962,88.174 C65.97,88.148 65.98,88.122 65.988,88.096 C66.069,87.832 66.144,87.564 66.214,87.296 C66.219,87.275 66.226,87.255 66.231,87.235 C66.301,86.962 66.365,86.686 66.423,86.409 C66.426,86.391 66.428,86.374 66.431,86.356 C66.445,86.291 66.453,86.223 66.466,86.156 C66.511,85.925 66.552,85.695 66.588,85.461 C66.632,85.169 66.671,84.878 66.701,84.583 C66.701,84.574 66.701,84.565 66.701,84.556 C66.731,84.258 66.755,83.955 66.77,83.652 C66.77,83.646 66.77,83.641 66.77,83.635 C66.786,83.326 66.797,83.017 66.797,82.704 C66.797,72.69 58.723,64.617 48.71,64.617 z\"></path>" +
" <path d=\"M62.936,99.809 C59.074,103.028 54.115,104.965 48.71,104.965 C47.101,104.965 45.535,104.787 44.023,104.461 L42.466,106.357 C39.007,105.43 35.855,103.781 33.179,101.574 L28.996,105.765 L29.51,108.861 L13.953,124.426 L15.457,125 L30.066,119.435 L44.675,125 L59.283,119.435 L64.849,104.826 L62.936,99.809 z\"></path>" +
" </g>" +
"</svg>";
}

View file

@ -1,31 +0,0 @@
import {UIElement} from "./UIElement";
import {UserDetails} from "../Logic/OsmConnection";
import {UIEventSource} from "./UIEventSource";
export class LoginDependendMessage extends UIElement {
private _noLoginMsg: string;
private _loginMsg: string;
private _userDetails: UserDetails;
constructor(loginData: UIEventSource<UserDetails>,
noLoginMsg: string,
loginMsg: string) {
super(loginData);
this._userDetails = loginData.data;
this._noLoginMsg = noLoginMsg;
this._loginMsg = loginMsg;
}
protected InnerRender(): string {
if (this._userDetails.loggedIn) {
return this._loginMsg;
} else {
return this._noLoginMsg;
}
}
InnerUpdate(htmlElement: HTMLElement) {
// pass
}
}

58
UI/MessageBoxHandler.ts Normal file
View file

@ -0,0 +1,58 @@
/**
* Keeps 'messagebox' and 'messageboxmobile' in sync, shows a 'close' button on the latter one
*/
import {UIEventSource} from "./UIEventSource";
import {UIElement} from "./UIElement";
import {FixedUiElement} from "./FixedUiElement";
import {VariableUiElement} from "./VariableUIElement";
export class MessageBoxHandler {
private _uielement: UIEventSource<() => UIElement>;
constructor(uielement: UIEventSource<() => UIElement>,
onClear: (() => void)) {
this._uielement = uielement;
this.listenTo(uielement);
this.update();
new VariableUiElement(new UIEventSource<string>("<h2>Naar de kaart > </h2>"),
(htmlElement) => {
htmlElement.onclick = function () {
uielement.setData(undefined);
onClear();
}
}
).AttachTo("to-the-map");
}
listenTo(uiEventSource: UIEventSource<any>) {
const self = this;
uiEventSource.addCallback(function () {
self.update();
})
}
update() {
const wrapper = document.getElementById("messagesboxmobilewrapper");
const gen = this._uielement.data;
console.log("Generator: ", gen);
if (gen === undefined) {
wrapper.classList.add("hidden");
return;
}
wrapper.classList.remove("hidden");
gen()
?.HideOnEmpty(true)
?.AttachTo("messagesbox")
?.Activate();
gen()
?.HideOnEmpty(true)
?.AttachTo("messagesboxmobile")
?.Activate();
}
}

View file

@ -1,65 +1,37 @@
import {UIElement} from "./UIElement"; import {UIElement} from "./UIElement";
import {UIEventSource} from "./UIEventSource"; import {UIEventSource} from "./UIEventSource";
import {Changes} from "../Logic/Changes";
export class PendingChanges extends UIElement{ export class PendingChanges extends UIElement {
private _pendingChangesCount: UIEventSource<number>;
private _countdown: UIEventSource<number>;
private _isSaving: UIEventSource<boolean>;
private readonly changes; constructor(pendingChangesCount: UIEventSource<number>,
countdown: UIEventSource<number>,
constructor(changes: Changes, countdown: UIEventSource<number>) { isSaving: UIEventSource<boolean>) {
super(undefined); // We do everything manually here! super(pendingChangesCount);
this.changes = changes; this.ListenTo(isSaving);
this.ListenTo(countdown);
this._pendingChangesCount = pendingChangesCount;
countdown.addCallback(function () { this._countdown = countdown;
this._isSaving = isSaving;
const percentage = Math.max(0, 100 * countdown.data / 20000);
let bar = document.getElementById("pending-bar");
if (bar === undefined) {
return;
}
const style = bar.style;
style.width = percentage + "%";
style["margin-left"] = (50 - (percentage / 2)) + "%";
});
changes.pendingChangesES.addCallback(function () {
const c = changes._pendingChanges.length;
const text = document.getElementById("pending-text");
if (c == 0) {
text.style.opacity = "0";
text.innerText = "Saving...";
} else {
text.innerText = c + " pending";
text.style.opacity = "1";
}
const bar = document.getElementById("pending-bar");
if (bar === null) {
return;
}
if (c == 0) {
bar.style.opacity = "0";
} else {
bar.style.opacity = "0.5";
}
});
} }
protected InnerRender(): string { protected InnerRender(): string {
return "<div id='pending-bar' style='width:100%; margin-left:0%'></div>" + if (this._isSaving.data) {
"<div id='pending-text'></div>"; return "<span class='alert'>Saving</span>";
}
if (this._pendingChangesCount.data == 0) {
return "";
} }
InnerUpdate(htmlElement: HTMLElement) { var restingSeconds = this._countdown.data / 1000;
var dots = "";
while (restingSeconds > 0) {
dots += ".";
restingSeconds = restingSeconds - 1;
}
return "Saving "+this._pendingChangesCount.data;
} }
} }

View file

@ -36,7 +36,7 @@ export class QuestionPicker extends UIElement {
if (highestQ === undefined) { if (highestQ === undefined) {
return "De vragen zijn op!"; return "";
} }
return highestQ.CreateHtml(this.source).Render(); return highestQ.CreateHtml(this.source).Render();

View file

@ -6,7 +6,6 @@ export class SlideShow extends UIElement {
private readonly _embeddedElements: UIEventSource<UIElement[]> private readonly _embeddedElements: UIEventSource<UIElement[]>
private readonly _currentSlide: UIEventSource<number> = new UIEventSource<number>(0); private readonly _currentSlide: UIEventSource<number> = new UIEventSource<number>(0);
private readonly _title: UIElement;
private readonly _noimages: UIElement; private readonly _noimages: UIElement;
constructor( constructor(
@ -14,7 +13,6 @@ export class SlideShow extends UIElement {
embeddedElements: UIEventSource<UIElement[]>, embeddedElements: UIEventSource<UIElement[]>,
noImages: UIElement) { noImages: UIElement) {
super(embeddedElements); super(embeddedElements);
this._title = title;
this._embeddedElements = embeddedElements; this._embeddedElements = embeddedElements;
this.ListenTo(this._currentSlide); this.ListenTo(this._currentSlide);
this._noimages = noImages; this._noimages = noImages;
@ -24,30 +22,37 @@ export class SlideShow extends UIElement {
if (this._embeddedElements.data.length == 0) { if (this._embeddedElements.data.length == 0) {
return this._noimages.Render(); return this._noimages.Render();
} }
const prevBtn = "<input class='prev-button' type='button' onclick='console.log(\"prev\")' value='<' />"
const nextBtn = "<input class='next-button' type='button' onclick='console.log(\"nxt\")' value='>' />" if (this._embeddedElements.data.length == 1) {
let header = this._title.Render(); return "<div class='image-slideshow'>"+this._embeddedElements.data[0].Render()+"</div>";
if (this._embeddedElements.data.length > 1) {
header = header + prevBtn + (this._currentSlide.data + 1) + "/" + this._embeddedElements.data.length + nextBtn;
} }
let body = ""
const prevBtn = "<div class='prev-button' id='prevbtn-"+this.id+"'></div>"
const nextBtn = "<div class='next-button' id='nextbtn-"+this.id+"'></div>"
let slides = ""
for (let i = 0; i < this._embeddedElements.data.length; i++) { for (let i = 0; i < this._embeddedElements.data.length; i++) {
let embeddedElement = this._embeddedElements.data[i]; let embeddedElement = this._embeddedElements.data[i];
let state = "hidden" let state = "hidden"
if (this._currentSlide.data === i) { if (this._currentSlide.data === i) {
state = "active-slide"; state = "active-slide";
} }
body += " <div class=\"slide " + state + "\">" + embeddedElement.Render() + "</div>\n"; slides += " <div class=\"slide " + state + "\">" + embeddedElement.Render() + "</div>\n";
} }
return "<span class='image-slideshow'>" + header + body + "</span>"; return "<div class='image-slideshow'>"
+ prevBtn
+ "<div class='slides'>" + slides + "</div>"
+ nextBtn
+ "</div>";
} }
InnerUpdate(htmlElement) { InnerUpdate(htmlElement) {
const nextButton = htmlElement.getElementsByClassName('next-button')[0]; const nextButton = document.getElementById("nextbtn-"+this.id);
if(nextButton === undefined){ if(nextButton === undefined || nextButton === null){
return; return;
} }
const prevButton = htmlElement.getElementsByClassName('prev-button')[0];
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;

View file

@ -7,6 +7,8 @@ export abstract class UIElement {
public readonly id: string; public readonly id: string;
public readonly _source: UIEventSource<any>; public readonly _source: UIEventSource<any>;
private _hideIfEmpty = false;
protected constructor(source: UIEventSource<any>) { protected constructor(source: UIEventSource<any>) {
this.id = "ui-element-" + UIElement.nextId; this.id = "ui-element-" + UIElement.nextId;
this._source = source; this._source = source;
@ -33,9 +35,23 @@ export abstract class UIElement {
} }
element.innerHTML = this.InnerRender(); element.innerHTML = this.InnerRender();
if(this._hideIfEmpty){
if(element.innerHTML === ""){
element.parentElement.style.display = "none";
}else{
element.parentElement.style.display = undefined;
}
}
this.InnerUpdate(element); this.InnerUpdate(element);
} }
HideOnEmpty(hide : boolean){
this._hideIfEmpty = hide;
this.Update();
return this;
}
// Called after the HTML has been replaced. Can be used for css tricks // Called after the HTML has been replaced. Can be used for css tricks
InnerUpdate(htmlElement : HTMLElement){} InnerUpdate(htmlElement : HTMLElement){}
@ -45,6 +61,10 @@ export abstract class UIElement {
AttachTo(divId: string) { AttachTo(divId: string) {
let element = document.getElementById(divId); let element = document.getElementById(divId);
if(element === null){
console.log("SEVERE: could not attach UIElement to ", divId);
return;
}
element.innerHTML = this.Render(); element.innerHTML = this.Render();
this.Update(); this.Update();
return this; return this;

View file

@ -8,7 +8,7 @@ export class UIEventSource<T>{
} }
public addCallback(callback: (() => void)) { public addCallback(callback: ((latestData) => void)) {
this._callbacks.push(callback); this._callbacks.push(callback);
return this; return this;
} }
@ -23,18 +23,19 @@ export class UIEventSource<T>{
public ping(): void { public ping(): void {
for (let i in this._callbacks) { for (let i in this._callbacks) {
this._callbacks[i](); this._callbacks[i](this.data);
} }
} }
public map<J>(f: ((T) => J)): UIEventSource<J> { public map<J>(f: ((T) => J)): UIEventSource<J> {
const newSource = new UIEventSource<J>(
f(this.data)
);
const self = this; const self = this;
this.addCallback(function () { this.addCallback(function () {
newSource.setData(f(self.data)); newSource.setData(f(self.data));
newSource.ping();
}); });
const newSource = new UIEventSource<J>(
f(this.data)
);
return newSource; return newSource;

View file

@ -7,11 +7,14 @@ import {UIEventSource} from "./UIEventSource";
*/ */
export class UserBadge extends UIElement { export class UserBadge extends UIElement {
private _userDetails: UIEventSource<UserDetails>; private _userDetails: UIEventSource<UserDetails>;
private _pendingChanges: UIElement;
constructor(userDetails: UIEventSource<UserDetails>) { constructor(userDetails: UIEventSource<UserDetails>,
pendingChanges : UIElement) {
super(userDetails); super(userDetails);
this._userDetails = userDetails; this._userDetails = userDetails;
this._pendingChanges = pendingChanges;
userDetails.addCallback(function () { userDetails.addCallback(function () {
const profilePic = document.getElementById("profile-pic"); const profilePic = document.getElementById("profile-pic");
@ -28,16 +31,49 @@ export class UserBadge extends UIElement {
return "<div class='activate-osm-authentication'>Klik hier om aan te melden bij OSM</div>"; return "<div class='activate-osm-authentication'>Klik hier om aan te melden bij OSM</div>";
} }
let messageSpan = "<span id='messages'>" +
" <a href='https://www.openstreetmap.org/messages/inbox' target='_blank'><img class='envelope' src='./assets/envelope.svg'/>" +
user.totalMessages +
"</a></span>";
if (user.unreadMessages > 0) {
messageSpan = "<span id='messages' class='alert'>" +
" <a href='https://www.openstreetmap.org/messages/inbox' target='_blank'><img class='envelope' src='./assets/envelope.svg'/>" +
" " +
"" +
user.unreadMessages.toString() +
"</a></span>";
}
let dryrun = "";
if (user.dryRun) {
dryrun = " <span class='alert'>TESTING</span>";
}
return "<img id='profile-pic' src='" + user.img + "'/> " + return "<img id='profile-pic' src='" + user.img + "'/> " +
"<div id='usertext'>"+ "<div id='usertext'>" +
"<div id='username'>" + "<p id='username'>" +
"<a href='https://www.openstreetmap.org/user/"+user.name+"' target='_blank'>" + user.name + "</a></div> <br />" + "<a href='https://www.openstreetmap.org/user/" + user.name + "' target='_blank'>" + user.name + "</a>" +
"<div id='csCount'> " + dryrun +
" <a href='https://www.openstreetmap.org/user/"+user.name+"/history' target='_blank'><img class='star' src='./assets/star.svg'/>" + user.csCount + "</div></a>" + "</p> " +
"<p id='userstats'>" +
messageSpan +
"<span id='csCount'> " +
" <a href='https://www.openstreetmap.org/user/" + user.name + "/history' target='_blank'><img class='star' src='./assets/star.svg'/> " + user.csCount +
"</a></span> " +
this._pendingChanges.Render() +
"</p>" +
"</div>"; "</div>";
} }
InnerUpdate(htmlElement: HTMLElement) { InnerUpdate(htmlElement: HTMLElement) {
this._pendingChanges.Update();
}
Activate() {
this._pendingChanges.Activate();
} }
} }

View file

@ -3,15 +3,25 @@ import {UIEventSource} from "./UIEventSource";
export class VariableUiElement extends UIElement { export class VariableUiElement extends UIElement {
private _html: UIEventSource<string>; private _html: UIEventSource<string>;
private _innerUpdate: (htmlElement: HTMLElement) => void;
constructor(html: UIEventSource<string>) { constructor(html: UIEventSource<string>, innerUpdate : ((htmlElement : HTMLElement) => void) = undefined) {
super(html); super(html);
this._html = html; this._html = html;
this._innerUpdate = innerUpdate;
} }
protected InnerRender(): string { protected InnerRender(): string {
return this._html.data; return this._html.data;
} }
InnerUpdate(htmlElement: HTMLElement) {
super.InnerUpdate(htmlElement);
if(this._innerUpdate !== undefined){
this._innerUpdate(htmlElement);
}
}
} }

View file

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<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"
width="100"
height="100"
viewBox="0 0 26.458333 26.458334"
version="1.1"
id="svg8"
sodipodi:docname="arrow-left-smooth.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="4"
inkscape:cx="19.262262"
inkscape:cy="36.323203"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1">
<sodipodi:guide
position="13.229167,23.859748"
orientation="1,0"
id="guide815"
inkscape:locked="false" />
<sodipodi:guide
position="14.944824,13.229167"
orientation="0,1"
id="guide817"
inkscape:locked="false" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-270.54165)">
<path
style="fill:none;stroke:#ffffff;stroke-width:3.59588718;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 20.139011,294.16029 c 0,0 -13.7995299,-7.53922 -13.8484369,-10.36091 -0.04891,-2.82169 13.8484369,-10.38607 13.8484369,-10.38607"
id="path821"
inkscape:connector-curvature="0" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<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"
width="100"
height="100"
viewBox="0 0 26.458333 26.458334"
version="1.1"
id="svg8"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="arrow-right-both.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="4"
inkscape:cx="19.262262"
inkscape:cy="36.323203"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1">
<sodipodi:guide
position="13.229167,23.859748"
orientation="1,0"
id="guide815"
inkscape:locked="false" />
<sodipodi:guide
position="14.944824,13.229167"
orientation="0,1"
id="guide817"
inkscape:locked="false" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-270.54165)">
<path
style="fill:none;stroke:#000000;stroke-width:2.89327955;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 6.741713,274.0738 9.596044,9.69249 -9.5719331,9.66812"
id="path819"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccc" />
<path
style="fill:none;stroke:#000000;stroke-width:3.96875;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 31.419271,269.58152 c 0,0 13.197107,9.60301 13.243879,13.19711 0.04677,3.5941 -13.243879,13.22916 -13.243879,13.22916"
id="path821"
inkscape:connector-curvature="0" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<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"
width="100"
height="100"
viewBox="0 0 26.458333 26.458334"
version="1.1"
id="svg8"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="arrow-right-sharp.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="4"
inkscape:cx="19.262262"
inkscape:cy="36.323203"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1">
<sodipodi:guide
position="13.229167,23.859748"
orientation="1,0"
id="guide815"
inkscape:locked="false" />
<sodipodi:guide
position="14.944824,13.229167"
orientation="0,1"
id="guide817"
inkscape:locked="false" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-270.54165)">
<path
style="fill:none;stroke:#000000;stroke-width:2.89327955;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 6.741713,274.0738 9.596044,9.69249 -9.5719331,9.66812"
id="path819"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<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"
width="100"
height="100"
viewBox="0 0 26.458333 26.458334"
version="1.1"
id="svg8"
sodipodi:docname="arrow-right-smooth.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="4"
inkscape:cx="-22.237738"
inkscape:cy="36.323203"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1">
<sodipodi:guide
position="13.229167,23.859748"
orientation="1,0"
id="guide815"
inkscape:locked="false" />
<sodipodi:guide
position="14.944824,13.229167"
orientation="0,1"
id="guide817"
inkscape:locked="false" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-270.54165)">
<path
style="fill:none;stroke:#ffffff;stroke-width:3.59588718;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 6.3128214,273.41335 c 0,0 13.7995296,7.53922 13.8484366,10.36091 0.04891,2.82169 -13.8484366,10.38607 -13.8484366,10.38607"
id="path821"
inkscape:connector-curvature="0" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -577,7 +577,6 @@
], ],
"tags": { "tags": {
"leisure": "park", "leisure": "park",
"name": "Koning Albert I-park",
"name:etymology:wikidata": "Q12974", "name:etymology:wikidata": "Q12974",
"surface": "wood", "surface": "wood",
"wikidata": "Q54817470", "wikidata": "Q54817470",
@ -634,7 +633,6 @@
], ],
"tags": { "tags": {
"leisure": "park", "leisure": "park",
"name": "Begijnvest",
"wikidata": "Q4499623", "wikidata": "Q4499623",
"wikipedia": "nl:Begijnenvest (Brugge)" "wikipedia": "nl:Begijnenvest (Brugge)"
} }
@ -17215,7 +17213,6 @@
], ],
"tags": { "tags": {
"leisure": "park", "leisure": "park",
"name": "Begijnvest",
"type": "multipolygon", "type": "multipolygon",
"wikidata": "Q4499623", "wikidata": "Q4499623",
"wikipedia": "nl:Begijnenvest (Brugge)" "wikipedia": "nl:Begijnenvest (Brugge)"

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="1024" height="1376" viewBox="-305 -516 610 820">
<title>Wikimedia Commons Logo</title>
<defs>
<clipPath id="c"><circle r="298"/></clipPath>
</defs>
<circle r="100" fill="#fff"/>
<g fill="#fff">
<g id="arrow" clip-path="url(#c)">
<path d="m-11 180v118h22v-118"/>
<path d="m-43 185l43-75 43 75"/>
</g>
<g id="arrows3">
<use xlink:href="#arrow" transform="rotate(45)"/>
<use xlink:href="#arrow" transform="rotate(90)"/>
<use xlink:href="#arrow" transform="rotate(135)"/>
</g>
<use xlink:href="#arrows3" transform="scale(-1 1)"/>
<path id="blue_path" transform="rotate(-45)" stroke="#fff" stroke-width="84" fill="none" d="M 0,-256 A 256 256 0 1 0 256,0 C 256,-100 155,-150 250,-275"/>
<path id="arrow_top" d="m-23-515s-36 135-80 185 116-62 170-5-90-180-90-180z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 933 B

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="1024" height="1376" viewBox="-305 -516 610 820">
<title>Wikimedia Commons Logo</title>
<defs>
<clipPath id="c"><circle r="298"/></clipPath>
</defs>
<circle r="100" fill="#900"/>
<g fill="#069">
<g id="arrow" clip-path="url(#c)">
<path d="m-11 180v118h22v-118"/>
<path d="m-43 185l43-75 43 75"/>
</g>
<g id="arrows3">
<use xlink:href="#arrow" transform="rotate(45)"/>
<use xlink:href="#arrow" transform="rotate(90)"/>
<use xlink:href="#arrow" transform="rotate(135)"/>
</g>
<use xlink:href="#arrows3" transform="scale(-1 1)"/>
<path id="blue_path" transform="rotate(-45)" stroke="#069" stroke-width="84" fill="none" d="M 0,-256 A 256 256 0 1 0 256,0 C 256,-100 155,-150 250,-275"/>
<path id="arrow_top" d="m-23-515s-36 135-80 185 116-62 170-5-90-180-90-180z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 932 B

335
index.css
View file

@ -12,18 +12,59 @@ body {
height: 100%; height: 100%;
} }
img {
border-radius: 1em;
}
/**************** GENERIC ****************/
.uielement {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
.alert {
background-color: #fee4d1;
font-weight: bold;
border-radius: 1em;
padding: 0.3em;
margin: 0.25em;
text-align: center;
padding-top: 0.15em;
padding-bottom: 0.15em;
}
.activate-osm-authentication {
cursor: pointer;
color: blue;
text-decoration: underline;
}
/**************** USER BADGE ****************/
.star { .star {
fill: black; fill: black;
width: 1em; width: 1em;
height: 1em; height: 1em;
} }
.envelope {
width: 1em;
height: 1em;
}
#profile-pic { #profile-pic {
float: left; float: left;
width: 4em; width: 4em;
height: 4em; height: 4em;
padding:0; padding: 0;
margin:0; margin: 0;
-webkit-border-radius: 50%; -webkit-border-radius: 50%;
-moz-border-radius: 50%; -moz-border-radius: 50%;
border-radius: 50%; border-radius: 50%;
@ -31,11 +72,12 @@ body {
transition: opacity 500ms linear; transition: opacity 500ms linear;
} }
#usertext a { #username {
text-decoration: none; text-decoration: none;
color: black; color: black;
} }
#usertext { #usertext {
width: auto; width: auto;
margin:0; margin:0;
@ -52,79 +94,74 @@ body {
display: block; display: block;
line-height: 0.75em; line-height: 0.75em;
} }
#username { #usertext a {
display: block ruby;
text-decoration: none; text-decoration: none;
color: black; color: black;
} }
.imgWithAttr { #userbadge {
max-height: 20em; display: inline-block;
}
#userbadge{
text-align: center; text-align: center;
background-color: white; background-color: white;
-webkit-border-radius: 2em; -webkit-border-radius: 2em;
-moz-border-radius: 2em; -moz-border-radius: 2em;
border-radius: 2em; border-radius: 2em;
transition: all 500ms linear; transition: all 500ms linear;
margin: 1em;
margin-left: 0;
margin-top: 0;
min-width: 20em;
pointer-events: all;
} }
#authbox {
#userbadge p {
margin: 0;
padding-top: 0.2em;
padding-bottom: 0.2em;
}
/**************************************/
#topleft-tools {
display: block;
position: absolute; position: absolute;
margin: 1em; margin: 1em;
margin-right: 0; margin-right: 0;
margin-bottom: 0; margin-bottom: 0;
padding:0; padding: 0;
z-index: 5000; z-index: 5000;
transition: all 500ms linear; transition: all 500ms linear;
max-width: 25%;
overflow: hidden; overflow: hidden;
pointer-events: none;
}
#pendingchangesbox {
position:relative;
}
#pending-bar {
height: 1.2em;
margin: 0;
z-index: 5005;
background-color: white;
border-radius:1.2em;
padding: 0;
transition: all 1s linear;
opacity: 0;
}
#pending-text {
margin: 0;
margin-top: -1.2em;
z-index: 5000;
border-radius:1.2em;
height: 1.2em;
text-align: center;
padding: 0;
opacity: 0;
transition: opacity 1s linear;
} }
#welcomeMessage {
display: inline-block;
width: 30em;
}
#zoomin {
#messagesboxmobilewrapper {
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*/
padding: 2em; padding: 2em;
padding-top: 1em; padding-top: 1em;
padding-bottom: 1em; padding-bottom: 1em;
@ -132,6 +169,33 @@ body {
transition: all 500ms linear; transition: all 500ms linear;
background-color: white; background-color: white;
border-radius: 2em; border-radius: 2em;
pointer-events: all;
}
@media only screen and (max-width: 600px) {
#messagesbox {
display: none;
}
}
@media only screen and (max-width: 600px) {
#messagesboxmobilewrapper {
position: absolute;
padding: 0;
margin: 0;
z-index: 5050;
transition: all 500ms linear;
overflow: hidden;
border-radius: 0;
width: 100%;
height: 100%;
display: block;
overflow-y: auto;
background-color: white;
}
} }
#logo { #logo {
@ -202,41 +266,170 @@ body {
font-weight: bold; font-weight: bold;
} }
.uielement{
width:100%; /************ Slideshow *****************/
height:100%;
margin:0;
padding:0; .image-slideshow {
position: relative;
text-align: center;
max-width: 100%;
margin-top: 1em;
margin-bottom: 1em;
} }
.activate-osm-authentication { .slides {
cursor: pointer;
color: blue;
text-decoration: underline;
} }
.popupImg { .prev-button {
max-height: 150px; background-color: black;
max-width: 300px; opacity: 30%;
width: 3.0em;
height: 100%;
padding-left: 0.5em;
padding-right: 0.5em;
position: absolute;
top: 50%;
left: 0;
transform: translate(0, -50%);
z-index: 5060;
content:url(assets/arrow-left-smooth.svg);
border-bottom-left-radius: 1em;
border-top-left-radius: 1em;
} }
.image-slidehow{
.next-button {
background-color: black;
opacity: 30%;
width: 3.0em;
height: 100%;
padding-left: 0.5em;
padding-right: 0.5em;
position: absolute;
top: 50%;
right: 0;
transform: translate(0, -50%);
border-bottom-right-radius: 1em;
border-top-right-radius: 1em;
z-index: 5060;
content:url(assets/arrow-right-smooth.svg);
}
.slide > span > img {
width: 500px; width: 500px;
height: 250px; max-width: 100%;
height: auto;
} }
.slide {
}
.slide img{
max-height: 200px;
max-width: 500px;
}
.hidden { .hidden {
display: none; /* This is used by the slideshow, to hide non-active slides*/
display: none !important;
} }
.osmlink{
font-size: xx-small; .imgWithAttr {
max-height: 20em;
position: relative;
overflow: hidden;
}
.wikimedia-link {
width: 1.5em;
height: auto;
}
.attribution {
background-color: rgba(0, 0, 0, 0.5);
color: white;
font-weight: bold;
font-size: smaller;
position: absolute;
bottom: 0;
left: 5em; /* Offset for the go left button*/
padding: 0.25em;
margin-bottom: 0.25em;
border-radius: 0.5em;
}
.attribution-author {
display: inline-block;
}
.license {
font-size: small;
font-weight: lighter;
}
.attribution a {
color: white;
}
/***************** Info box (box containing features and questions ******************/
.featureinfobox {
}
.featureinfoboxtitle {
position: relative;
}
.osmlink {
position: absolute;
right: 0;
}
.osm-logo path {
fill: #80cf36;
}
.featureinfoboxtitle span {
font-weight: bold;
font-size: x-large;
}
.featureinfoboxtitle a {
float: right;
margin-left: 1em;
}
.infoboxcontents {
}
.infboxcontents-left {
width: auto;
display: inline-block;
float: left;
padding: 0.1em;
}
.infboxcontents-right {
padding: 0.1em;
overflow: hidden;
display: inline-block;
}
.infobox-questions {
max-width: 25em;
display: inline-block;
} }

View file

@ -12,42 +12,34 @@
</head> </head>
<body> <body>
<div id="messagesboxmobilewrapper">
<div id="to-the-map">Return to map</div>
<div id="messagesboxmobile"> </div>
</div>
<div id="authbox"> <div id="topleft-tools">
<div id="userbadge"> <div id="userbadge">
</div> </div>
<br/> <br/>
<div id="pendingchangesbox"></div>
<br/>
<div id="messagesbox"> <div id="messagesbox">
<div id="welcomeMessage"></div>
<div id="gettingStartedBox"></div>
</div> </div>
</div> </div>
<div id="centermessage"></div> <div id="centermessage"></div>
<div id="bottomRight">ADD</div> <div id="bottomRight" style="display: none">ADD</div>
<div id="leafletDiv"></div> <div id="leafletDiv"></div>
<script src="./index.ts"></script> <script src="./index.ts"></script>
<!-- TODO -->
<!-- Fotos -->
<!-- KLeuren/icoontjes -->
<!-- Aanpassingen van tagmapping/tagmapping verwijderen -->
<!-- 3 dagen eerste protoype --> <!-- 3 dagen eerste protoype -->
<!-- 19 juni: eerste feedbackronde, foto's --> <!-- 19 juni: eerste feedbackronde, foto's -->
<!-- 23 juni: wikimedia foto's laden --> <!-- 23 juni: wikimedia foto's laden -->
<!-- 24 juni: foto's via imgur --> <!-- 24 juni: foto's via imgur -->
<!-- 26 restylen infobox -->
<script data-goatcounter="https://pietervdvn.goatcounter.com/count" <script data-goatcounter="https://pietervdvn.goatcounter.com/count"
async src="//gc.zgo.at/count.js"></script> async src="//gc.zgo.at/count.js"></script>

View file

@ -5,30 +5,34 @@ import {UIEventSource} from "./UI/UIEventSource";
import {UserBadge} from "./UI/UserBadge"; import {UserBadge} from "./UI/UserBadge";
import {Basemap} from "./Logic/Basemap"; import {Basemap} from "./Logic/Basemap";
import {PendingChanges} from "./UI/PendingChanges"; import {PendingChanges} from "./UI/PendingChanges";
import {FixedUiElement} from "./UI/FixedUiElement";
import {CenterMessageBox} from "./UI/CenterMessageBox"; import {CenterMessageBox} from "./UI/CenterMessageBox";
import {Helpers} from "./Helpers"; import {Helpers} from "./Helpers";
import {KnownSet} from "./Layers/KnownSet"; import {KnownSet} from "./Layers/KnownSet";
import {AddButton} from "./UI/AddButton"; import {Tag, TagsFilter, TagUtils} from "./Logic/TagsFilter";
import {Tag} from "./Logic/TagsFilter";
import {FilteredLayer} from "./Logic/FilteredLayer"; import {FilteredLayer} from "./Logic/FilteredLayer";
import {LayerUpdater} from "./Logic/LayerUpdater"; import {LayerUpdater} from "./Logic/LayerUpdater";
import {LoginDependendMessage} from "./UI/LoginDependendMessage"; import {VariableUiElement} from "./UI/VariableUIElement";
import {UIElement} from "./UI/UIElement";
import {MessageBoxHandler} from "./UI/MessageBoxHandler";
import {Overpass} from "./Logic/Overpass";
import {FixedUiElement} from "./UI/FixedUiElement";
import {FeatureInfoBox} from "./UI/FeatureInfoBox";
let dryRun = false; let dryRun = false;
if (location.hostname === "localhost" || location.hostname === "127.0.0.1") { if (location.hostname === "localhost" || location.hostname === "127.0.0.1") {
// Set to true if testing and changes should NOT be saved // Set to true if testing and changes should NOT be saved
// 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";
} }
// ----------------- SELECT THE RIGHT QUESTSET ----------------- // ----------------- SELECT THE RIGHT QUESTSET -----------------
let questSetToRender = KnownSet.groen; let questSetToRender = KnownSet.groen;
if (window.location.search) { if (window.location.search) {
const params = window.location.search.substr(1).split("&"); const params = window.location.search.substr(1).split("&");
@ -56,7 +60,12 @@ const centerMessage = new UIEventSource<string>("");
// The countdown: if set to e.g. ten, it'll start counting down. When reaching zero, changes will be saved. NB: this is implemented later, not in the eventSource // The countdown: if set to e.g. ten, it'll start counting down. When reaching zero, changes will be saved. NB: this is implemented later, not in the eventSource
const secondsTillChangesAreSaved = new UIEventSource<number>(0); const secondsTillChangesAreSaved = new UIEventSource<number>(0);
var locationControl = new UIEventSource({ const leftMessage = new UIEventSource<() => UIElement>(undefined);
const selectedElement = new UIEventSource<any>(undefined);
const locationControl = new UIEventSource({
zoom: questSetToRender.startzoom, zoom: questSetToRender.startzoom,
lat: questSetToRender.startLat, lat: questSetToRender.startLat,
lon: questSetToRender.startLon lon: questSetToRender.startLon
@ -65,10 +74,12 @@ var locationControl = new UIEventSource({
// ----------------- Prepare the important objects ----------------- // ----------------- Prepare the important objects -----------------
const saveTimeout = 5000; // After this many milliseconds without changes, saves are sent of to OSM
const allElements = new ElementStorage(); const allElements = new ElementStorage();
const osmConnection = new OsmConnection(dryRun); const osmConnection = new OsmConnection(dryRun);
const changes = new Changes(osmConnection, allElements, centerMessage); const changes = new Changes(
"Beantwoorden van vragen met MapComplete voor vragenset #" + questSetToRender.name,
osmConnection, allElements, centerMessage);
const bm = new Basemap("leafletDiv", locationControl); const bm = new Basemap("leafletDiv", locationControl);
@ -86,7 +97,7 @@ const flayers: FilteredLayer[] = []
for (const layer of questSetToRender.layers) { for (const layer of questSetToRender.layers) {
const flayer = layer.asLayer(bm, allElements, changes, osmConnection.userDetails.data); const flayer = layer.asLayer(bm, allElements, changes, osmConnection.userDetails, selectedElement);
const addButton = { const addButton = {
name: layer.name, name: layer.name,
@ -103,23 +114,64 @@ const layerUpdater = new LayerUpdater(bm, questSetToRender.startzoom, flayers);
// ------------------ Setup various UI elements ------------ // ------------------ Setup various UI elements ------------
/*
const addButton = new AddButton(bm, changes, addButtons); const addButton = new AddButton(bm, changes, addButtons);
addButton.AttachTo("bottomRight"); addButton.AttachTo("bottomRight");
addButton.Update(); addButton.Update();
*/
/**
* Show the questions and information for the selected element on the leftMessage
*/
selectedElement.addCallback((data) => {
console.log("Got selection");
// Which is the applicable set?
for (const layer of questSetToRender.layers) {
const applicable = layer.overpassFilter.matches(TagUtils.proprtiesToKV(data));
if (applicable) {
// This layer is the layer that gives the questions
leftMessage.setData(() =>
new FeatureInfoBox(
allElements.getElement(data.id),
layer.elementsToShow,
layer.questions,
changes,
osmConnection.userDetails
));
break;
}
}
new UserBadge(osmConnection.userDetails) }
);
const pendingChanges = new PendingChanges(
changes.pendingChangesES, secondsTillChangesAreSaved, changes.isSaving);
new UserBadge(osmConnection.userDetails, pendingChanges)
.AttachTo('userbadge'); .AttachTo('userbadge');
new FixedUiElement(questSetToRender.welcomeMessage) var welcomeMessage = () => {
.AttachTo("welcomeMessage"); return new VariableUiElement(
osmConnection.userDetails.map((userdetails) => {
var login = questSetToRender.gettingStartedPlzLogin;
if (userdetails.loggedIn) {
login = questSetToRender.welcomeBackMessage;
}
return "<div id='welcomeMessage'>" +
questSetToRender.welcomeMessage + login +
"</div>";
}),
function (html) {
osmConnection.registerActivateOsmAUthenticationClass()
});
}
leftMessage.setData(welcomeMessage);
new LoginDependendMessage(osmConnection.userDetails, questSetToRender.gettingStartedPlzLogin, questSetToRender.welcomeBackMessage)
.AttachTo("gettingStartedBox");
new PendingChanges(changes, secondsTillChangesAreSaved) var messageBox = new MessageBoxHandler(leftMessage, () => {selectedElement.setData(undefined)});
.AttachTo("pendingchangesbox");
new CenterMessageBox( new CenterMessageBox(
questSetToRender.startzoom, questSetToRender.startzoom,
@ -130,11 +182,14 @@ new CenterMessageBox(
.AttachTo("centermessage"); .AttachTo("centermessage");
Helpers.SetupAutoSave(changes, secondsTillChangesAreSaved); Helpers.SetupAutoSave(changes, secondsTillChangesAreSaved, saveTimeout);
Helpers.LastEffortSave(changes); Helpers.LastEffortSave(changes);
Helpers.registerActivateOsmAUthenticationClass(osmConnection);
osmConnection.registerActivateOsmAUthenticationClass();
// --------------- Send a ping to start various action -------- // --------------- Send a ping to start various action --------
locationControl.ping(); locationControl.ping();
messageBox.update();

View file

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

102
test.ts
View file

@ -1,50 +1,70 @@
import {FixedUiElement} from "./UI/FixedUiElement"; // The message that should be shown at the center of the screen
import $ from "jquery"
import {Imgur} from "./Logic/Imgur";
import {ImageUploadFlow} from "./UI/ImageUploadFlow";
import {UserDetails} from "./Logic/OsmConnection";
import {UIEventSource} from "./UI/UIEventSource"; import {UIEventSource} from "./UI/UIEventSource";
import {UIRadioButton} from "./UI/UIRadioButton";
import {UIElement} from "./UI/UIElement"; import {UIElement} from "./UI/UIElement";
import {ElementStorage} from "./Logic/ElementStorage";
import {OsmConnection} from "./Logic/OsmConnection";
import {Changes} from "./Logic/Changes";
import {Basemap} from "./Logic/Basemap";
import {KnownSet} from "./Layers/KnownSet";
import {Overpass} from "./Logic/Overpass";
import {FeatureInfoBox} from "./UI/FeatureInfoBox";
import {TagMapping, TagMappingOptions} from "./UI/TagMapping";
import {CommonTagMappings} from "./Layers/CommonTagMappings";
import {ImageCarousel} from "./UI/Image/ImageCarousel";
import {WikimediaImage} from "./UI/Image/WikimediaImage";
const centerMessage = new UIEventSource<string>("");
var tags = { const dryRun = true;
"name": "Astridpark Brugge", // If you have a testfile somewhere, enable this to spoof overpass
"wikidata":"Q1234", // This should be hosted independantly, e.g. with `cd assets; webfsd -p 8080` + a CORS plugin to disable cors rules
"leisure":"park" Overpass.testUrl = "http://127.0.0.1:8080/test.json";
} // The countdown: if set to e.g. ten, it'll start counting down. When reaching zero, changes will be saved. NB: this is implemented later, not in the eventSource
const secondsTillChangesAreSaved = new UIEventSource<number>(0);
var userdetails = new UserDetails() const leftMessage = new UIEventSource<() => UIElement>(undefined);
userdetails.loggedIn = true;
userdetails.name = "Pietervdvn";
const selectedElement = new UIEventSource<any>(undefined);
new ImageUploadFlow( const questSetToRender = KnownSet.groen;
).AttachTo("maindiv") //*/
const locationControl = new UIEventSource({
zoom: questSetToRender.startzoom,
/* lat: questSetToRender.startLat,
$('document').ready(function () { lon: questSetToRender.startLon
$('input[type=file]').on('change', function () {
var $files = $(this).get(0).files;
if ($files.length) {
// Reject big files
if ($files[0].size > $(this).data('max-size') * 1024) {
console.log('Please select a smaller file');
return false;
}
// Begin file upload
console.log('Uploading file to Imgur..');
const imgur = new Imgur();
imgur.uploadImage("KorenBloem", "Een korenbloem, ergens", $files[0],
(url) => {
console.log("URL: ", url);
})
}
});
}); });
*/
// ----------------- Prepare the important objects -----------------
const saveTimeout = 5000; // After this many milliseconds without changes, saves are sent of to OSM
const allElements = new ElementStorage();
const osmConnection = new OsmConnection(dryRun);
const changes = new Changes(
"Beantwoorden van vragen met MapComplete voor vragenset #" + questSetToRender.name,
osmConnection, allElements, centerMessage);
const layer = questSetToRender.layers[0];
const tags ={
id: "way/123",
// access: "yes",
barrier: "fence",
curator: "Arnout Zwaenepoel",
description: "Heide en heischraal landschap met landduin en grote soortenverscheidenheid",
dog: "no",
email: "arnoutenregine@skynet.be",
image: "https://natuurpuntbrugge.be/wp-content/uploads/2017/05/Schobbejakshoogte-schapen-PDG-1024x768.jpg",
leisure: "nature_reserve",
name: "Schobbejakshoogte",
operator: "Natuurpunt Brugge",
phone: "+32 50 82 26 97",
website: "https://natuurpuntbrugge.be/schobbejakshoogte/",
wikidata: "Q4499623",
wikipedia: "nl:Schobbejakshoogte"
};
const tagsES = allElements.addElement({properties: tags});
new ImageCarousel(tagsES).AttachTo("maindiv").Activate();
// new WikimediaImage(new UIEventSource<string>("File:Brugge_cobblestone_path.jpg")).AttachTo("maindiv");