Lot's of small improvements

This commit is contained in:
Pieter Vander Vennet 2020-06-29 03:12:44 +02:00
parent 9bd37d9cde
commit 8bca006787
29 changed files with 375 additions and 173 deletions

View file

@ -1,17 +1,12 @@
import {Basemap} from "./Logic/Basemap"; import {Basemap} from "./Logic/Basemap";
import {ElementStorage} from "./Logic/ElementStorage"; import {ElementStorage} from "./Logic/ElementStorage";
import {Changes} from "./Logic/Changes"; import {Changes} from "./Logic/Changes";
import {Question, QuestionDefinition} from "./Logic/Question"; import {QuestionDefinition} from "./Logic/Question";
import {TagMapping, TagMappingOptions} from "./UI/TagMapping"; import {TagMappingOptions} from "./UI/TagMapping";
import {UIEventSource} from "./UI/UIEventSource"; import {UIEventSource} from "./UI/UIEventSource";
import {QuestionPicker} from "./UI/QuestionPicker";
import {VerticalCombine} from "./UI/VerticalCombine";
import {UIElement} from "./UI/UIElement"; import {UIElement} from "./UI/UIElement";
import {Tag, TagsFilter} from "./Logic/TagsFilter"; import {Tag, TagsFilter} from "./Logic/TagsFilter";
import {FilteredLayer} from "./Logic/FilteredLayer"; import {FilteredLayer} from "./Logic/FilteredLayer";
import {ImageCarousel} from "./UI/Image/ImageCarousel";
import {FixedUiElement} from "./UI/FixedUiElement";
import {OsmImageUploadHandler} from "./Logic/OsmImageUploadHandler";
import {UserDetails} from "./Logic/OsmConnection"; import {UserDetails} from "./Logic/OsmConnection";
@ -37,7 +32,6 @@ export class LayerDefinition {
asLayer(basemap: Basemap, allElements: ElementStorage, changes: Changes, userDetails: UIEventSource<UserDetails>, selectedElement: UIEventSource<any>): asLayer(basemap: Basemap, allElements: ElementStorage, changes: Changes, userDetails: UIEventSource<UserDetails>, selectedElement: UIEventSource<any>):
FilteredLayer { FilteredLayer {
const self = this;
return new FilteredLayer( return new FilteredLayer(
this.name, this.name,
basemap, allElements, changes, basemap, allElements, changes,

View file

@ -1,5 +1,6 @@
import L from "leaflet" import L from "leaflet"
import {UIEventSource} from "../UI/UIEventSource"; import {UIEventSource} from "../UI/UIEventSource";
import {UIElement} from "../UI/UIElement";
// Contains all setup and baselayers for Leaflet stuff // Contains all setup and baselayers for Leaflet stuff
export class Basemap { export class Basemap {
@ -8,6 +9,7 @@ export class Basemap {
public map: Map; public map: Map;
public Location: UIEventSource<{ zoom: number, lat: number, lon: number }>; public Location: UIEventSource<{ zoom: number, lat: number, lon: number }>;
public LastClickLocation: UIEventSource<{ lat: number, lon: number }> = new UIEventSource<{lat: number, lon: number}>(undefined)
private aivLucht2013Layer = L.tileLayer.wms('https://geoservices.informatievlaanderen.be/raadpleegdiensten/OGW/wms?s', private aivLucht2013Layer = L.tileLayer.wms('https://geoservices.informatievlaanderen.be/raadpleegdiensten/OGW/wms?s',
{ {
@ -19,7 +21,7 @@ export class Basemap {
"LAYER=omwrgbmrvl&STYLE=&FORMAT=image/png&tileMatrixSet=GoogleMapsVL&tileMatrix={z}&tileRow={y}&tileCol={x}", "LAYER=omwrgbmrvl&STYLE=&FORMAT=image/png&tileMatrixSet=GoogleMapsVL&tileMatrix={z}&tileRow={y}&tileCol={x}",
{ {
// omwrgbmrvl // omwrgbmrvl
attribution: 'Map Data <a href="osm.org">OpenStreetMap</a> | Luchtfoto\'s van © AIV Vlaanderen (Laatste) © AGIV', attribution: 'Map Data <a href="https://osm.org">OpenStreetMap</a> | Luchtfoto\'s van © AIV Vlaanderen (Laatste) © AGIV',
maxZoom: 20, maxZoom: 20,
minZoom: 1, minZoom: 1,
wmts: true wmts: true
@ -28,20 +30,20 @@ export class Basemap {
private osmLayer = L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", private osmLayer = L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png",
{ {
attribution: 'Map Data and background © <a href="osm.org">OpenStreetMap</a>', attribution: 'Map Data and background © <a href="https://osm.org">OpenStreetMap</a>',
maxZoom: 19, maxZoom: 19,
minZoom: 1 minZoom: 1
}); });
private osmBeLayer = L.tileLayer("https://tile.osm.be/osmbe/{z}/{x}/{y}.png", private osmBeLayer = L.tileLayer("https://tile.osm.be/osmbe/{z}/{x}/{y}.png",
{ {
attribution: 'Map Data and background © <a href="osm.org">OpenStreetMap</a> | <a href="https://geo6.be/">Tiles courtesy of Geo6</a>', attribution: 'Map Data and background © <a href="https://osm.org">OpenStreetMap</a> | <a href="https://geo6.be/">Tiles courtesy of Geo6</a>',
maxZoom: 18, maxZoom: 18,
minZoom: 1 minZoom: 1
}); });
private grbLayer = L.tileLayer("https://tile.informatievlaanderen.be/ws/raadpleegdiensten/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=grb_bsk&STYLE=&FORMAT=image/png&tileMatrixSet=GoogleMapsVL&tileMatrix={z}&tileCol={x}&tileRow={y}", private grbLayer = L.tileLayer("https://tile.informatievlaanderen.be/ws/raadpleegdiensten/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=grb_bsk&STYLE=&FORMAT=image/png&tileMatrixSet=GoogleMapsVL&tileMatrix={z}&tileCol={x}&tileRow={y}",
{ {
attribution: 'Map Data <a href="osm.org">OpenStreetMap</a> | Background <i>Grootschalig ReferentieBestand</i>(GRB) © AGIV', attribution: 'Map Data <a href="https://osm.org">OpenStreetMap</a> | Background <i>Grootschalig ReferentieBestand</i>(GRB) © AGIV',
maxZoom: 20, maxZoom: 20,
minZoom: 1, minZoom: 1,
wmts: true wmts: true
@ -56,16 +58,17 @@ export class Basemap {
"GRB Vlaanderen": this.grbLayer "GRB Vlaanderen": this.grbLayer
}; };
constructor(leafletElementId: string,
constructor(leafletElementId: string, location: UIEventSource<{ zoom: number, lat: number, lon: number }>) { location: UIEventSource<{ zoom: number, lat: number, lon: number }>,
extraAttribution: UIElement) {
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],
}); });
this.map.attributionControl.setPrefix(extraAttribution.Render());
this.Location = location; this.Location = location;
const layerControl = L.control.layers(this.baseLayers, null, const layerControl = L.control.layers(this.baseLayers, null,
{ {
position: 'bottomright', position: 'bottomright',
@ -74,19 +77,19 @@ export class Basemap {
layerControl.addTo(this.map); layerControl.addTo(this.map);
this.map.zoomControl.setPosition("bottomright"); this.map.zoomControl.setPosition("bottomright");
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().lng;
location.ping(); location.ping();
}); });
this.map.on("click", function (e) {
self.LastClickLocation.setData({lat: e.latlng.lat, lon: e.latlng.lng})
});
} }

View file

@ -183,9 +183,10 @@ export class FilteredLayer {
eventSource.addCallback(function () { eventSource.addCallback(function () {
self.updateStyle(); self.updateStyle();
}); });
layer.on("click", function(){ layer.on("click", function(e){
console.log("Selected ",feature) console.log("Selected ",feature)
self._selectedElement.setData(feature.properties); self._selectedElement.setData(feature.properties);
L.DomEvent.stop(e); // Marks the event as consumed
}); });
} }
}); });

View file

@ -3,6 +3,7 @@ import {UIEventSource} from "../UI/UIEventSource";
import {UIElement} from "../UI/UIElement"; import {UIElement} from "../UI/UIElement";
import L from "leaflet"; import L from "leaflet";
import {Helpers} from "../Helpers"; import {Helpers} from "../Helpers";
import {UserDetails} from "./OsmConnection";
export class GeoLocationHandler extends UIElement { export class GeoLocationHandler extends UIElement {
@ -78,7 +79,6 @@ export class GeoLocationHandler extends UIElement {
}); });
this.HideOnEmpty(true); this.HideOnEmpty(true);
} }
protected InnerRender(): string { protected InnerRender(): string {

View file

@ -51,7 +51,6 @@ export class GeoOperations {
} }
const intersectionSize = turf.area(intersection); const intersectionSize = turf.area(intersection);
const ratio = intersectionSize / featureSurface; const ratio = intersectionSize / featureSurface;
console.log("Intersection ratio", ratio, "intersection:", intersectionSize, "featuresize:", featureSurface, "targetRatio", maxOverlapPercentage / 100);
if (ratio * 100 >= maxOverlapPercentage) { if (ratio * 100 >= maxOverlapPercentage) {
console.log("Refused", poly.id, " due to ", shouldNotContainElement.id, "intersection ratio is ", ratio, "which is bigger then the target ratio of ", (maxOverlapPercentage / 100)) console.log("Refused", poly.id, " due to ", shouldNotContainElement.id, "intersection ratio is ", ratio, "which is bigger then the target ratio of ", (maxOverlapPercentage / 100))

View file

@ -10,8 +10,9 @@ export class UserDetails {
public img: string; public img: string;
public unreadMessages = 0; public unreadMessages = 0;
public totalMessages = 0; public totalMessages = 0;
public osmConnection : OsmConnection; public osmConnection: OsmConnection;
public dryRun : boolean; public dryRun: boolean;
home: { lon: number; lat: number };
} }
@ -66,6 +67,8 @@ export class OsmConnection {
if (details == null) { if (details == null) {
return; return;
} }
self.UpdatePreferences();
// 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];
@ -84,6 +87,13 @@ export class OsmConnection {
} }
data.img = data.img ?? "./assets/osm-logo.svg"; data.img = data.img ?? "./assets/osm-logo.svg";
const homeEl = userInfo.getElementsByTagName("home");
if (homeEl !== undefined && homeEl[0] !== undefined) {
const lat = parseFloat(homeEl[0].getAttribute("lat"));
const lon = parseFloat(homeEl[0].getAttribute("lon"));
data.home = {lat: lat, lon: lon};
}
const messages = userInfo.getElementsByTagName("messages")[0].getElementsByTagName("received")[0]; const messages = userInfo.getElementsByTagName("messages")[0].getElementsByTagName("received")[0];
data.unreadMessages = parseInt(messages.getAttribute("unread")); data.unreadMessages = parseInt(messages.getAttribute("unread"));
data.totalMessages = parseInt(messages.getAttribute("count")); data.totalMessages = parseInt(messages.getAttribute("count"));
@ -108,6 +118,47 @@ export class OsmConnection {
} }
} }
public preferences = new UIEventSource<any>({});
private UpdatePreferences() {
const self = this;
this.auth.xhr({
method: 'GET',
path: '/api/0.6/user/preferences'
}, function (error, value: XMLDocument) {
if(error){
console.log("Could not load preferences", error);
return;
}
const prefs = value.getElementsByTagName("preference");
for (let i = 0; i < prefs.length; i++) {
const pref = prefs[i];
const k = pref.getAttribute("k");
const v = pref.getAttribute("v");
self.preferences.data[k] = v;
}
self.preferences.ping();
});
}
public SetPreference(k:string, v:string){
this.preferences.data[k] = v;
this.preferences.ping();
this.auth.xhr({
method: 'PUT',
path: '/api/0.6/user/preferences/'+k,
options: { header: { 'Content-Type': 'text/plain' } },
content: v
},function(error, result) {
if(error){
console.log("Could not set preference", error);
return;
}
console.log("Preference written!", result);
});
}
private static parseUploadChangesetResponse(response: XMLDocument) { private static parseUploadChangesetResponse(response: XMLDocument) {
const nodes = response.getElementsByTagName("node"); const nodes = response.getElementsByTagName("node");
const mapping = {}; const mapping = {};

View file

@ -0,0 +1,49 @@
import {Basemap} from "./Basemap";
import L from "leaflet";
import {UIEventSource} from "../UI/UIEventSource";
import {UIElement} from "../UI/UIElement";
/**
* The stray-click-hanlders adds a marker to the map if no feature was clicked.
* Shows the given uiToShow-element in the messagebox
*/
export class StrayClickHandler {
private _basemap: Basemap;
private _lastMarker;
private _leftMessage: UIEventSource<() => UIElement>;
private _uiToShow: (() => UIElement);
constructor(
basemap: Basemap,
selectElement: UIEventSource<any>,
leftMessage: UIEventSource<() => UIElement>,
uiToShow: (() => UIElement)) {
this._basemap = basemap;
this._leftMessage = leftMessage;
this._uiToShow = uiToShow;
const self = this;
const map = basemap.map;
basemap.LastClickLocation.addCallback(function (lastClick) {
if (self._lastMarker !== undefined) {
map.removeLayer(self._lastMarker);
}
self._lastMarker = L.marker([lastClick.lat, lastClick.lon]);
self._lastMarker.addTo(map);
leftMessage.setData(self._uiToShow);
});
selectElement.addCallback(() => {
if (self._lastMarker !== undefined) {
map.removeLayer(self._lastMarker);
this._lastMarker = undefined;
}
})
}
}

View file

@ -73,3 +73,7 @@ Data from OpenStreetMap
Images from Wikipedia/Wikimedia Images from Wikipedia/Wikimedia
https://commons.wikimedia.org/wiki/File:Camera_font_awesome.svg https://commons.wikimedia.org/wiki/File:Camera_font_awesome.svg
Camera Icon, Dave Gandy, CC-BY-SA 3.0
https://commons.wikimedia.org/wiki/File:Home-icon.svg
Home icon by Timothy Miller, CC-BY-SA 3.0

View file

@ -58,7 +58,7 @@ export class AddButton extends UIElement {
basemap.map.on("mousemove", function(){ basemap.map.on("mousemove", function(){
if (self.state.data === self.PLACING_POI) { if (self.state.data === self.PLACING_POI) {
var icon = "crosshair"; let icon = "crosshair";
for (const option of self._options) { for (const option of self._options) {
if (option.name === self.curentAddSelection.data && option.icon !== undefined) { if (option.name === self.curentAddSelection.data && option.icon !== undefined) {
icon = 'url("' + option.icon + '") 32 32 ,crosshair'; icon = 'url("' + option.icon + '") 32 32 ,crosshair';

38
UI/Base/Button.ts Normal file
View file

@ -0,0 +1,38 @@
import {UIElement} from "../UIElement";
export class Button extends UIElement {
private _text: UIElement;
private _onclick: () => void;
private _clss: string;
constructor(text: UIElement, onclick: (() => void), clss: string = "") {
super(undefined);
this._text = text;
this._onclick = onclick;
if (clss !== "") {
this._clss = "class='" + clss + "'";
}else{
this._clss = "";
}
}
protected InnerRender(): string {
return "<form>" +
"<button id='button-"+this.id+"' type='button' "+this._clss+">" + this._text.Render() + "</button>" +
"</form>";
}
InnerUpdate(htmlElement: HTMLElement) {
super.InnerUpdate(htmlElement);
const self = this;
console.log("Update for ", htmlElement)
document.getElementById("button-"+this.id).onclick = function(){
console.log("Clicked");
self._onclick();
}
}
}

View file

@ -1,5 +1,5 @@
import {UIElement} from "./UIElement"; import {UIEventSource} from "../UIEventSource";
import {UIEventSource} from "./UIEventSource"; import {UIElement} from "../UIElement";
export class DropDownUI extends UIElement { export class DropDownUI extends UIElement {

View file

@ -1,4 +1,4 @@
import {UIElement} from "./UIElement"; import {UIElement} from "../UIElement";
export class FixedUiElement extends UIElement { export class FixedUiElement extends UIElement {
private _html: string; private _html: string;

View file

@ -1,5 +1,5 @@
import {UIElement} from "./UIElement"; import {UIElement} from "../UIElement";
import {UIEventSource} from "./UIEventSource"; import {UIEventSource} from "../UIEventSource";
import {FixedUiElement} from "./FixedUiElement"; import {FixedUiElement} from "./FixedUiElement";
import $ from "jquery" import $ from "jquery"

View file

@ -1,5 +1,5 @@
import {UIElement} from "./UIElement"; import {UIElement} from "../UIElement";
import {UIEventSource} from "./UIEventSource"; import {UIEventSource} from "../UIEventSource";
export class VariableUiElement extends UIElement { export class VariableUiElement extends UIElement {
private _html: UIEventSource<string>; private _html: UIEventSource<string>;

View file

@ -1,4 +1,4 @@
import {UIElement} from "./UIElement"; import {UIElement} from "../UIElement";
export class VerticalCombine extends UIElement { export class VerticalCombine extends UIElement {
private _elements: UIElement[]; private _elements: UIElement[];

View file

@ -1,6 +1,5 @@
import {UIElement} from "./UIElement"; import {UIElement} from "./UIElement";
import {UIEventSource} from "./UIEventSource"; import {UIEventSource} from "./UIEventSource";
import {Helpers} from "../Helpers";
import {OsmConnection} from "../Logic/OsmConnection"; import {OsmConnection} from "../Logic/OsmConnection";
export class CenterMessageBox extends UIElement { export class CenterMessageBox extends UIElement {

View file

@ -2,7 +2,6 @@ import {UIElement} from "./UIElement";
import {TagMapping, TagMappingOptions} from "./TagMapping"; import {TagMapping, TagMappingOptions} from "./TagMapping";
import {Question, QuestionDefinition} from "../Logic/Question"; import {Question, QuestionDefinition} from "../Logic/Question";
import {UIEventSource} from "./UIEventSource"; import {UIEventSource} from "./UIEventSource";
import {VerticalCombine} from "./VerticalCombine";
import {QuestionPicker} from "./QuestionPicker"; import {QuestionPicker} from "./QuestionPicker";
import {OsmImageUploadHandler} from "../Logic/OsmImageUploadHandler"; import {OsmImageUploadHandler} from "../Logic/OsmImageUploadHandler";
import {ImageCarousel} from "./Image/ImageCarousel"; import {ImageCarousel} from "./Image/ImageCarousel";
@ -12,6 +11,7 @@ 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"; import {ImageUploadFlow} from "./ImageUploadFlow";
import {VerticalCombine} from "./Base/VerticalCombine";
export class FeatureInfoBox extends UIElement { export class FeatureInfoBox extends UIElement {

View file

@ -3,8 +3,7 @@
*/ */
import {UIEventSource} from "./UIEventSource"; import {UIEventSource} from "./UIEventSource";
import {UIElement} from "./UIElement"; import {UIElement} from "./UIElement";
import {FixedUiElement} from "./FixedUiElement"; import {VariableUiElement} from "./Base/VariableUIElement";
import {VariableUiElement} from "./VariableUIElement";
export class MessageBoxHandler { export class MessageBoxHandler {
private _uielement: UIEventSource<() => UIElement>; private _uielement: UIEventSource<() => UIElement>;
@ -15,8 +14,16 @@ export class MessageBoxHandler {
this.listenTo(uielement); this.listenTo(uielement);
this.update(); this.update();
window.onhashchange = function () {
if (location.hash === "") {
// No more element: back to the map!
uielement.setData(undefined);
onClear();
}
}
new VariableUiElement(new UIEventSource<string>("<h2>Naar de kaart</h2>"), new VariableUiElement(new UIEventSource<string>("<h2>Naar de kaart</h2>"),
(htmlElement) => { () => {
document.getElementById("to-the-map").onclick = function () { document.getElementById("to-the-map").onclick = function () {
uielement.setData(undefined); uielement.setData(undefined);
onClear(); onClear();
@ -24,6 +31,7 @@ export class MessageBoxHandler {
} }
).AttachTo("to-the-map"); ).AttachTo("to-the-map");
} }
listenTo(uiEventSource: UIEventSource<any>) { listenTo(uiEventSource: UIEventSource<any>) {
@ -33,14 +41,19 @@ export class MessageBoxHandler {
}) })
} }
update() { update() {
const wrapper = document.getElementById("messagesboxmobilewrapper"); const wrapper = document.getElementById("messagesboxmobilewrapper");
const gen = this._uielement.data; const gen = this._uielement.data;
console.log("Generator: ", gen); console.log("Generator: ", gen);
if (gen === undefined) { if (gen === undefined) {
wrapper.classList.add("hidden"); wrapper.classList.add("hidden")
if (location.hash !== "") {
location.hash = ""
}
return; return;
} }
location.hash = "#element"
wrapper.classList.remove("hidden"); wrapper.classList.remove("hidden");
gen() gen()
?.HideOnEmpty(true) ?.HideOnEmpty(true)

77
UI/SimpleAddUI.ts Normal file
View file

@ -0,0 +1,77 @@
import {UIElement} from "./UIElement";
import {UIEventSource} from "./UIEventSource";
import {Tag} from "../Logic/TagsFilter";
import {FilteredLayer} from "../Logic/FilteredLayer";
import {Changes} from "../Logic/Changes";
import {FixedUiElement} from "./Base/FixedUiElement";
import {Button} from "./Base/Button";
/**
* Asks to add a feature at the last clicked location, at least if zoom is sufficient
*/
export class SimpleAddUI extends UIElement {
private _zoomlevel: UIEventSource<{ zoom: number }>;
private _addButtons: UIElement[];
private _lastClickLocation: UIEventSource<{ lat: number; lon: number }>;
private _changes: Changes;
private _selectedElement: UIEventSource<any>;
constructor(zoomlevel: UIEventSource<{ zoom: number }>,
lastClickLocation: UIEventSource<{ lat: number, lon: number }>,
changes: Changes,
selectedElement: UIEventSource<any>,
addButtons: { name: string; icon: string; tags: Tag[]; layerToAddTo: FilteredLayer }[],
) {
super(zoomlevel);
this._zoomlevel = zoomlevel;
this._lastClickLocation = lastClickLocation;
this._changes = changes;
this._selectedElement = selectedElement;
this._addButtons = [];
for (const option of addButtons) {
// <button type='button'> looks SO retarded
// the default type of button is 'submit', which performs a POST and page reload
const button =
new Button(new FixedUiElement("Voeg hier een " + option.name + " toe"),
this.CreatePoint(option));
this._addButtons.push(button);
}
}
private CreatePoint(option: { name: string; icon: string; tags: Tag[]; layerToAddTo: FilteredLayer }) {
const self = this;
return () => {
console.log("Creating a new ", option.name, " at last click location");
const loc = self._lastClickLocation.data;
let feature = self._changes.createElement(option.tags, loc.lat, loc.lon);
option.layerToAddTo.AddNewElement(feature);
self._selectedElement.setData(feature.properties);
}
}
protected InnerRender(): string {
const header = "<h2>Geen selectie</h2>" +
"Je klikte ergens waar er nog geen gezochte data is.<br/>"
if (this._zoomlevel.data.zoom < 19) {
return header + "Zoom verder in om een element toe te voegen."
}
var html = "";
for (const button of this._addButtons) {
// <button type='button'> looks SO retarded
// the default type of button is 'submit', which performs a POST and page reload
html += button.Render();
}
return header + html;
}
InnerUpdate(htmlElement: HTMLElement) {
super.InnerUpdate(htmlElement);
for (const button of this._addButtons) {
button.Update();
}
}
}

View file

@ -8,7 +8,7 @@ export class UIEventSource<T>{
} }
public addCallback(callback: ((latestData) => void)) { public addCallback(callback: ((latestData : T) => void)) {
this._callbacks.push(callback); this._callbacks.push(callback);
return this; return this;
} }

View file

@ -1,6 +1,8 @@
import {UIElement} from "./UIElement"; import {UIElement} from "./UIElement";
import {UserDetails} from "../Logic/OsmConnection"; import {UserDetails} from "../Logic/OsmConnection";
import {UIEventSource} from "./UIEventSource"; import {UIEventSource} from "./UIEventSource";
import {Basemap} from "../Logic/Basemap";
import L from "leaflet";
/** /**
* Handles and updates the user badge * Handles and updates the user badge
@ -8,13 +10,16 @@ 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; private _pendingChanges: UIElement;
private _basemap: Basemap;
constructor(userDetails: UIEventSource<UserDetails>, constructor(userDetails: UIEventSource<UserDetails>,
pendingChanges : UIElement) { pendingChanges: UIElement,
basemap: Basemap) {
super(userDetails); super(userDetails);
this._userDetails = userDetails; this._userDetails = userDetails;
this._pendingChanges = pendingChanges; this._pendingChanges = pendingChanges;
this._basemap = basemap;
userDetails.addCallback(function () { userDetails.addCallback(function () {
const profilePic = document.getElementById("profile-pic"); const profilePic = document.getElementById("profile-pic");
@ -33,13 +38,13 @@ export class UserBadge extends UIElement {
let messageSpan = "<span id='messages'>" + let messageSpan = "<span id='messages'>" +
" <a href='https://www.openstreetmap.org/messages/inbox' target='_blank'><img class='envelope' src='./assets/envelope.svg'/>" + " <a href='https://www.openstreetmap.org/messages/inbox' target='_blank'><img class='small-userbadge-icon' src='./assets/envelope.svg' alt='msgs'>" +
user.totalMessages + user.totalMessages +
"</a></span>"; "</a></span>";
if (user.unreadMessages > 0) { if (user.unreadMessages > 0) {
messageSpan = "<span id='messages' class='alert'>" + messageSpan = "<span id='messages' class='alert'>" +
" <a href='https://www.openstreetmap.org/messages/inbox' target='_blank'><img class='envelope' src='./assets/envelope.svg'/>" + " <a href='https://www.openstreetmap.org/messages/inbox' target='_blank'><img class='small-userbadge-icon' src='./assets/envelope.svg' alt='msgs'/>" +
" " + " " +
"" + "" +
user.unreadMessages.toString() + user.unreadMessages.toString() +
@ -51,16 +56,28 @@ export class UserBadge extends UIElement {
dryrun = " <span class='alert'>TESTING</span>"; dryrun = " <span class='alert'>TESTING</span>";
} }
return "<img id='profile-pic' src='" + user.img + "'/> " + let home = "";
if (user.home !== undefined) {
home = "<img id='home' src='./assets/home.svg' alt='home' class='small-userbadge-icon'>";
const icon = L.icon({
iconUrl: 'assets/home.svg',
iconSize: [20, 20],
iconAnchor: [10, 10]
});
L.marker([user.home.lat, user.home.lon], {icon: icon}).addTo(this._basemap.map);
}
return "<img id='profile-pic' src='" + user.img + "' alt='profile-pic'/> " +
"<div id='usertext'>" + "<div id='usertext'>" +
"<p id='username'>" + "<p id='username'>" +
"<a href='https://www.openstreetmap.org/user/" + user.name + "' target='_blank'>" + user.name + "</a>" + "<a href='https://www.openstreetmap.org/user/" + user.name + "' target='_blank'>" + user.name + "</a>" +
dryrun + dryrun +
"</p> " + "</p> " +
"<p id='userstats'>" + "<p id='userstats'>" +
home +
messageSpan + messageSpan +
"<span id='csCount'> " + "<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 href='https://www.openstreetmap.org/user/" + user.name + "/history' target='_blank'><img class='small-userbadge-icon' src='./assets/star.svg' alt='star'/> " + user.csCount +
"</a></span> " + "</a></span> " +
this._pendingChanges.Render() + this._pendingChanges.Render() +
"</p>" + "</p>" +
@ -70,6 +87,18 @@ export class UserBadge extends UIElement {
InnerUpdate(htmlElement: HTMLElement) { InnerUpdate(htmlElement: HTMLElement) {
this._pendingChanges.Update(); this._pendingChanges.Update();
var btn = document.getElementById("home");
if (btn) {
const self = this;
btn.onclick = function () {
const home = self._userDetails?.data?.home;
if (home === undefined) {
return;
}
self._basemap.map.flyTo([home.lat, home.lon], 18);
}
}
} }
Activate() { Activate() {

3
assets/bug.svg Normal file
View file

@ -0,0 +1,3 @@
<svg height="1024" width="733.886" xmlns="http://www.w3.org/2000/svg">
<path d="M243.621 156.53099999999995C190.747 213.312 205.34 304 205.34 304s53.968 64 160 64c106.031 0 160.031-64 160.031-64s14.375-89.469-37.375-146.312c32.375-18.031 51.438-44.094 43.562-61.812-8.938-19.969-48.375-21.75-88.25-3.969-14.812 6.594-27.438 14.969-37.25 23.875-12.438-2.25-25.625-3.781-40.72-3.781-14.061 0-26.561 1.344-38.344 3.25-9.656-8.75-22.062-16.875-36.531-23.344-39.875-17.719-79.375-15.938-88.25 3.969C194.465 113.21900000000005 212.497 138.562 243.621 156.53099999999995zM644.746 569.75c-8.25-1.75-16.125-2.75-23.75-3.5 0-2.125 0.375-4.125 0.375-6.312 0-33.594-4.75-65.654-12.438-96.125 16.438 1.406 37.375-2.375 58.562-11.779 39.875-17.781 65-48.375 56.125-68.219-8.875-19.969-48.375-21.75-88.25-3.969-18.625 8.312-33.812 19.469-44 30.906-7.75-18.25-16.5-35.781-26.812-51.719-30.188 25.156-87.312 62.719-167.062 71.062v321.781c0 0-0.25 32-32.031 32-31.75 0-32-32-32-32V430.219c-79.811-8.344-136.968-45.969-167.093-71.062-9.875 15.312-18.375 32-25.938 49.344-10.281-10.625-24.625-20.844-41.969-28.594-39.875-17.719-79.375-15.938-88.25 3.969-8.906 19.906 16.25 50.438 56.125 68.219 19.844 8.846 39.531 12.812 55.469 12.096-7.656 30.404-12.469 62.344-12.469 95.812 0 2.188 0.375 4.25 0.438 6.5-6.719 0.75-13.688 1.75-20.781 3.25-51.969 10.75-91.781 37.625-88.844 59.812 2.938 22.312 47.5 31.5 99.594 20.688 6.781-1.375 13.438-3.125 19.781-5.062C128.684 686 143.34 723.875 163.622 756.5c-12.031 6.062-24.531 15-36.031 26.625C95.715 815 82.779 853.75 98.715 869.688c15.938 15.937 54.656 3 86.531-28.812 9.344-9.375 16.844-19.25 22.656-29C251.434 854.5 305.965 880 365.465 880c60.343 0 115.781-26.25 159.531-69.938 5.875 10.312 13.75 20.812 23.625 30.688 31.812 31.875 70.625 44.812 86.562 28.875s3-54.625-28.875-86.5c-12.312-12.375-25.688-21.75-38.438-27.938 20.125-32.5 34.625-70.375 43.688-111.062 7.188 2.25 14.688 4.375 22.562 6.062 52.061 10.812 96.625 1.562 99.625-20.688C736.558 607.375 696.746 580.5 644.746 569.75z"/>
</svg>

After

Width:  |  Height:  |  Size: 2 KiB

3
assets/github.svg Normal file
View file

@ -0,0 +1,3 @@
<svg width="1024" height="1024" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8C0 11.54 2.29 14.53 5.47 15.59C5.87 15.66 6.02 15.42 6.02 15.21C6.02 15.02 6.01 14.39 6.01 13.72C4 14.09 3.48 13.23 3.32 12.78C3.23 12.55 2.84 11.84 2.5 11.65C2.22 11.5 1.82 11.13 2.49 11.12C3.12 11.11 3.57 11.7 3.72 11.94C4.44 13.15 5.59 12.81 6.05 12.6C6.12 12.08 6.33 11.73 6.56 11.53C4.78 11.33 2.92 10.64 2.92 7.58C2.92 6.71 3.23 5.99 3.74 5.43C3.66 5.23 3.38 4.41 3.82 3.31C3.82 3.31 4.49 3.1 6.02 4.13C6.66 3.95 7.34 3.86 8.02 3.86C8.7 3.86 9.38 3.95 10.02 4.13C11.55 3.09 12.22 3.31 12.22 3.31C12.66 4.41 12.38 5.23 12.3 5.43C12.81 5.99 13.12 6.7 13.12 7.58C13.12 10.65 11.25 11.33 9.47 11.53C9.76 11.78 10.01 12.26 10.01 13.01C10.01 14.08 10 14.94 10 15.21C10 15.42 10.15 15.67 10.55 15.59C13.71 14.53 16 11.53 16 8C16 3.58 12.42 0 8 0Z" transform="scale(64)" fill="#1B1F23"/>
</svg>

After

Width:  |  Height:  |  Size: 967 B

3
assets/home.svg Normal file
View file

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg height="16px" id="Layer_1" style="enable-background:new 0 0 16 16;" version="1.1" viewBox="0 0 16 16" width="16px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M15.45,7L14,5.551V2c0-0.55-0.45-1-1-1h-1c-0.55,0-1,0.45-1,1v0.553L9,0.555C8.727,0.297,8.477,0,8,0S7.273,0.297,7,0.555 L0.55,7C0.238,7.325,0,7.562,0,8c0,0.563,0.432,1,1,1h1v6c0,0.55,0.45,1,1,1h3v-5c0-0.55,0.45-1,1-1h2c0.55,0,1,0.45,1,1v5h3 c0.55,0,1-0.45,1-1V9h1c0.568,0,1-0.437,1-1C16,7.562,15.762,7.325,15.45,7z"/></svg>

After

Width:  |  Height:  |  Size: 689 B

3
assets/pencil.svg Normal file
View file

@ -0,0 +1,3 @@
<svg height="1024" width="896" xmlns="http://www.w3.org/2000/svg">
<path d="M704 64L576 192l192 192 128-128L704 64zM0 768l0.688 192.562L192 960l512-512L512 256 0 768zM192 896H64V768h64v64h64V896z"/>
</svg>

After

Width:  |  Height:  |  Size: 207 B

View file

@ -12,11 +12,6 @@ body {
height: 100%; height: 100%;
} }
img {
border-radius: 1em;
}
#geolocate-button { #geolocate-button {
position: absolute; position: absolute;
bottom: 27px; bottom: 27px;
@ -67,19 +62,17 @@ img {
/**************** USER BADGE ****************/ /**************** USER BADGE ****************/
.small-userbadge-icon {
.star { width: 1em;
height: 1em;
fill: black; fill: black;
width: 1em; border-radius: 0;
height: 1em;
} }
.envelope { #home {
width: 1em; cursor: pointer;
height: 1em;
} }
#profile-pic { #profile-pic {
float: left; float: left;
width: 4em; width: 4em;

View file

@ -8,16 +8,17 @@ import {PendingChanges} from "./UI/PendingChanges";
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 {Tag, TagsFilter, TagUtils} from "./Logic/TagsFilter"; import {Tag, TagUtils} from "./Logic/TagsFilter";
import {FilteredLayer} from "./Logic/FilteredLayer"; import {FilteredLayer} from "./Logic/FilteredLayer";
import {LayerUpdater} from "./Logic/LayerUpdater"; import {LayerUpdater} from "./Logic/LayerUpdater";
import {VariableUiElement} from "./UI/VariableUIElement";
import {UIElement} from "./UI/UIElement"; import {UIElement} from "./UI/UIElement";
import {MessageBoxHandler} from "./UI/MessageBoxHandler"; import {MessageBoxHandler} from "./UI/MessageBoxHandler";
import {Overpass} from "./Logic/Overpass"; import {Overpass} from "./Logic/Overpass";
import {FixedUiElement} from "./UI/FixedUiElement";
import {FeatureInfoBox} from "./UI/FeatureInfoBox"; import {FeatureInfoBox} from "./UI/FeatureInfoBox";
import {GeoLocationHandler} from "./Logic/GeoLocationHandler"; import {GeoLocationHandler} from "./Logic/GeoLocationHandler";
import {StrayClickHandler} from "./Logic/StrayClickHandler";
import {SimpleAddUI} from "./UI/SimpleAddUI";
import {VariableUiElement} from "./UI/Base/VariableUIElement";
let dryRun = false; let dryRun = false;
@ -27,7 +28,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";
} }
@ -66,7 +67,7 @@ const leftMessage = new UIEventSource<() => UIElement>(undefined);
const selectedElement = new UIEventSource<any>(undefined); const selectedElement = new UIEventSource<any>(undefined);
const locationControl = new UIEventSource({ const locationControl = new UIEventSource<{ lat: number, lon: number, zoom: number }>({
zoom: questSetToRender.startzoom, zoom: questSetToRender.startzoom,
lat: questSetToRender.startLat, lat: questSetToRender.startLat,
lon: questSetToRender.startLon lon: questSetToRender.startLon
@ -81,7 +82,22 @@ const osmConnection = new OsmConnection(dryRun);
const changes = new Changes( const changes = new Changes(
"Beantwoorden van vragen met MapComplete voor vragenset #" + questSetToRender.name, "Beantwoorden van vragen met MapComplete voor vragenset #" + questSetToRender.name,
osmConnection, allElements, centerMessage); osmConnection, allElements, centerMessage);
const bm = new Basemap("leafletDiv", locationControl); const bm = new Basemap("leafletDiv", locationControl, new VariableUiElement(
locationControl.map((location) => {
const mapComplete = "<a href='https://github.com/pietervdvn/MapComplete' target='_blank'>Mapcomple</a> " +
" " +
"<a href='https://github.com/pietervdvn/MapComplete/issues' target='_blank'><img src='./assets/bug.svg' alt='Report bug' class='small-userbadge-icon'></a>";
let editHere = "";
if (location !== undefined) {
editHere = " | " +
"<a href='https://www.openstreetmap.org/edit?editor=id#map=" + location.zoom + "/" + location.lat + "/" + location.lon + "' target='_blank'>" +
"<img src='./assets/pencil.svg' alt='edit here' class='small-userbadge-icon'>" +
"</a>"
}
return mapComplete + editHere;
})
));
// ------------- Setup the layers ------------------------------- // ------------- Setup the layers -------------------------------
@ -115,42 +131,53 @@ 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();*/
*/
new StrayClickHandler(bm, selectedElement, leftMessage, () => {
return new SimpleAddUI(bm.Location,
bm.LastClickLocation,
changes,
selectedElement,
addButtons);
}
);
/** /**
* 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) => {
// Which is the applicable set? // Which is the applicable set?
for (const layer of questSetToRender.layers) { for (const layer of questSetToRender.layers) {
const applicable = layer.overpassFilter.matches(TagUtils.proprtiesToKV(data)); const applicable = layer.overpassFilter.matches(TagUtils.proprtiesToKV(data));
if (applicable) { if (applicable) {
// This layer is the layer that gives the questions // This layer is the layer that gives the questions
leftMessage.setData(() => leftMessage.setData(() =>
new FeatureInfoBox( new FeatureInfoBox(
allElements.getElement(data.id), allElements.getElement(data.id),
layer.elementsToShow, layer.elementsToShow,
layer.questions, layer.questions,
changes, changes,
osmConnection.userDetails osmConnection.userDetails
)); ));
break; break;
}
} }
}
} }
); );
const pendingChanges = new PendingChanges( const pendingChanges = new PendingChanges(
changes.pendingChangesES, secondsTillChangesAreSaved, changes.isSaving); changes.pendingChangesES, secondsTillChangesAreSaved, changes.isSaving);
new UserBadge(osmConnection.userDetails, pendingChanges) new UserBadge(osmConnection.userDetails, pendingChanges, bm)
.AttachTo('userbadge'); .AttachTo('userbadge');
var welcomeMessage = () => { var welcomeMessage = () => {
@ -164,7 +191,7 @@ var welcomeMessage = () => {
questSetToRender.welcomeMessage + login + questSetToRender.welcomeMessage + login +
"</div>"; "</div>";
}), }),
function (html) { function () {
osmConnection.registerActivateOsmAUthenticationClass() osmConnection.registerActivateOsmAUthenticationClass()
}); });
} }
@ -185,7 +212,6 @@ new CenterMessageBox(
Helpers.SetupAutoSave(changes, secondsTillChangesAreSaved, saveTimeout); Helpers.SetupAutoSave(changes, secondsTillChangesAreSaved, saveTimeout);
Helpers.LastEffortSave(changes); Helpers.LastEffortSave(changes);
osmConnection.registerActivateOsmAUthenticationClass(); osmConnection.registerActivateOsmAUthenticationClass();

86
test.ts
View file

@ -1,86 +0,0 @@
// The message that should be shown at the center of the screen
import {UIEventSource} from "./UI/UIEventSource";
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";
import {OsmImageUploadHandler} from "./Logic/OsmImageUploadHandler";
import {DropDownUI} from "./UI/DropDownUI";
const centerMessage = new UIEventSource<string>("");
const dryRun = true;
// 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
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);
const leftMessage = new UIEventSource<() => UIElement>(undefined);
const selectedElement = new UIEventSource<any>(undefined);
const questSetToRender = KnownSet.groen;
const locationControl = new UIEventSource({
zoom: questSetToRender.startzoom,
lat: questSetToRender.startLat,
lon: questSetToRender.startLon
});
// ----------------- 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 OsmImageUploadHandler(tagsES, osmConnection.userDetails, changes)
.getUI().AttachTo("maindiv");
/*/
new FeatureInfoBox(
tagsES,
layer.elementsToShow,
layer.questions,
changes,
osmConnection.userDetails
).AttachTo("maindiv").Activate();
//*/