From 8bca00678759d28120462f48fca2ccb4869653ed Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 29 Jun 2020 03:12:44 +0200 Subject: [PATCH] Lot's of small improvements --- Helpers.ts | 2 +- LayerDefinition.ts | 10 +--- Logic/Basemap.ts | 29 +++++----- Logic/FilteredLayer.ts | 3 +- Logic/GeoLocationHandler.ts | 2 +- Logic/GeoOperations.ts | 1 - Logic/OsmConnection.ts | 55 ++++++++++++++++++- Logic/StrayClickHandler.ts | 49 +++++++++++++++++ README.md | 4 ++ UI/AddButton.ts | 2 +- UI/Base/Button.ts | 38 +++++++++++++ UI/{ => Base}/DropDownUI.ts | 4 +- UI/{ => Base}/FixedUiElement.ts | 2 +- UI/{ => Base}/UIRadioButton.ts | 4 +- UI/{ => Base}/VariableUIElement.ts | 4 +- UI/{ => Base}/VerticalCombine.ts | 2 +- UI/CenterMessageBox.ts | 1 - UI/FeatureInfoBox.ts | 2 +- UI/MessageBoxHandler.ts | 21 ++++++-- UI/SimpleAddUI.ts | 77 ++++++++++++++++++++++++++ UI/UIEventSource.ts | 2 +- UI/UserBadge.ts | 39 ++++++++++++-- assets/bug.svg | 3 ++ assets/github.svg | 3 ++ assets/home.svg | 3 ++ assets/pencil.svg | 3 ++ index.css | 19 +++---- index.ts | 78 ++++++++++++++++++--------- test.ts | 86 ------------------------------ 29 files changed, 375 insertions(+), 173 deletions(-) create mode 100644 Logic/StrayClickHandler.ts create mode 100644 UI/Base/Button.ts rename UI/{ => Base}/DropDownUI.ts (93%) rename UI/{ => Base}/FixedUiElement.ts (85%) rename UI/{ => Base}/UIRadioButton.ts (97%) rename UI/{ => Base}/VariableUIElement.ts (87%) rename UI/{ => Base}/VerticalCombine.ts (96%) create mode 100644 UI/SimpleAddUI.ts create mode 100644 assets/bug.svg create mode 100644 assets/github.svg create mode 100644 assets/home.svg create mode 100644 assets/pencil.svg diff --git a/Helpers.ts b/Helpers.ts index b079ad9..1876150 100644 --- a/Helpers.ts +++ b/Helpers.ts @@ -73,5 +73,5 @@ export class Helpers { }); } - + } \ No newline at end of file diff --git a/LayerDefinition.ts b/LayerDefinition.ts index 26b4421..201beca 100644 --- a/LayerDefinition.ts +++ b/LayerDefinition.ts @@ -1,17 +1,12 @@ import {Basemap} from "./Logic/Basemap"; import {ElementStorage} from "./Logic/ElementStorage"; import {Changes} from "./Logic/Changes"; -import {Question, QuestionDefinition} from "./Logic/Question"; -import {TagMapping, TagMappingOptions} from "./UI/TagMapping"; +import {QuestionDefinition} from "./Logic/Question"; +import {TagMappingOptions} from "./UI/TagMapping"; import {UIEventSource} from "./UI/UIEventSource"; -import {QuestionPicker} from "./UI/QuestionPicker"; -import {VerticalCombine} from "./UI/VerticalCombine"; import {UIElement} from "./UI/UIElement"; import {Tag, TagsFilter} from "./Logic/TagsFilter"; 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"; @@ -37,7 +32,6 @@ export class LayerDefinition { asLayer(basemap: Basemap, allElements: ElementStorage, changes: Changes, userDetails: UIEventSource, selectedElement: UIEventSource): FilteredLayer { - const self = this; return new FilteredLayer( this.name, basemap, allElements, changes, diff --git a/Logic/Basemap.ts b/Logic/Basemap.ts index 0f4f3e6..9298dd3 100644 --- a/Logic/Basemap.ts +++ b/Logic/Basemap.ts @@ -1,5 +1,6 @@ import L from "leaflet" import {UIEventSource} from "../UI/UIEventSource"; +import {UIElement} from "../UI/UIElement"; // Contains all setup and baselayers for Leaflet stuff export class Basemap { @@ -8,6 +9,7 @@ export class Basemap { public map: Map; 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', { @@ -19,7 +21,7 @@ export class Basemap { "LAYER=omwrgbmrvl&STYLE=&FORMAT=image/png&tileMatrixSet=GoogleMapsVL&tileMatrix={z}&tileRow={y}&tileCol={x}", { // omwrgbmrvl - attribution: 'Map Data OpenStreetMap | Luchtfoto\'s van © AIV Vlaanderen (Laatste) © AGIV', + attribution: 'Map Data OpenStreetMap | Luchtfoto\'s van © AIV Vlaanderen (Laatste) © AGIV', maxZoom: 20, minZoom: 1, wmts: true @@ -28,20 +30,20 @@ export class Basemap { private osmLayer = L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", { - attribution: 'Map Data and background © OpenStreetMap', + attribution: 'Map Data and background © OpenStreetMap', maxZoom: 19, minZoom: 1 }); private osmBeLayer = L.tileLayer("https://tile.osm.be/osmbe/{z}/{x}/{y}.png", { - attribution: 'Map Data and background © OpenStreetMap | Tiles courtesy of Geo6', + attribution: 'Map Data and background © OpenStreetMap | Tiles courtesy of Geo6', maxZoom: 18, 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}", { - attribution: 'Map Data OpenStreetMap | Background Grootschalig ReferentieBestand(GRB) © AGIV', + attribution: 'Map Data OpenStreetMap | Background Grootschalig ReferentieBestand(GRB) © AGIV', maxZoom: 20, minZoom: 1, wmts: true @@ -56,25 +58,24 @@ export class Basemap { "GRB Vlaanderen": this.grbLayer }; - - constructor(leafletElementId: string, location: UIEventSource<{ zoom: number, lat: number, lon: number }>) { + constructor(leafletElementId: string, + location: UIEventSource<{ zoom: number, lat: number, lon: number }>, + extraAttribution: UIElement) { this.map = L.map(leafletElementId, { center: [location.data.lat, location.data.lon], zoom: location.data.zoom, layers: [this.osmLayer], }); - - + this.map.attributionControl.setPrefix(extraAttribution.Render()); this.Location = location; + const layerControl = L.control.layers(this.baseLayers, null, { position: 'bottomright', hideSingleBase: true }) layerControl.addTo(this.map); - - - + this.map.zoomControl.setPosition("bottomright"); const self = this; @@ -82,11 +83,13 @@ export class Basemap { this.map.on("moveend", function () { location.data.zoom = self.map.getZoom(); location.data.lat = self.map.getCenter().lat; - location.data.lon = self.map.getCenter().lon; + location.data.lon = self.map.getCenter().lng; location.ping(); }); - + this.map.on("click", function (e) { + self.LastClickLocation.setData({lat: e.latlng.lat, lon: e.latlng.lng}) + }); } diff --git a/Logic/FilteredLayer.ts b/Logic/FilteredLayer.ts index aa825d9..8d0070a 100644 --- a/Logic/FilteredLayer.ts +++ b/Logic/FilteredLayer.ts @@ -183,9 +183,10 @@ export class FilteredLayer { eventSource.addCallback(function () { self.updateStyle(); }); - layer.on("click", function(){ + layer.on("click", function(e){ console.log("Selected ",feature) self._selectedElement.setData(feature.properties); + L.DomEvent.stop(e); // Marks the event as consumed }); } }); diff --git a/Logic/GeoLocationHandler.ts b/Logic/GeoLocationHandler.ts index 408a920..8c4075c 100644 --- a/Logic/GeoLocationHandler.ts +++ b/Logic/GeoLocationHandler.ts @@ -3,6 +3,7 @@ import {UIEventSource} from "../UI/UIEventSource"; import {UIElement} from "../UI/UIElement"; import L from "leaflet"; import {Helpers} from "../Helpers"; +import {UserDetails} from "./OsmConnection"; export class GeoLocationHandler extends UIElement { @@ -78,7 +79,6 @@ export class GeoLocationHandler extends UIElement { }); this.HideOnEmpty(true); - } protected InnerRender(): string { diff --git a/Logic/GeoOperations.ts b/Logic/GeoOperations.ts index bf1d362..91687e9 100644 --- a/Logic/GeoOperations.ts +++ b/Logic/GeoOperations.ts @@ -51,7 +51,6 @@ export class GeoOperations { } const intersectionSize = turf.area(intersection); const ratio = intersectionSize / featureSurface; - console.log("Intersection ratio", ratio, "intersection:", intersectionSize, "featuresize:", featureSurface, "targetRatio", maxOverlapPercentage / 100); 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)) diff --git a/Logic/OsmConnection.ts b/Logic/OsmConnection.ts index d076713..c347fe7 100644 --- a/Logic/OsmConnection.ts +++ b/Logic/OsmConnection.ts @@ -10,8 +10,9 @@ export class UserDetails { public img: string; public unreadMessages = 0; public totalMessages = 0; - public osmConnection : OsmConnection; - public dryRun : boolean; + public osmConnection: OsmConnection; + public dryRun: boolean; + home: { lon: number; lat: number }; } @@ -66,6 +67,8 @@ export class OsmConnection { if (details == null) { return; } + + self.UpdatePreferences(); // details is an XML DOM of user details let userInfo = details.getElementsByTagName("user")[0]; @@ -84,6 +87,13 @@ export class OsmConnection { } 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]; data.unreadMessages = parseInt(messages.getAttribute("unread")); data.totalMessages = parseInt(messages.getAttribute("count")); @@ -108,6 +118,47 @@ export class OsmConnection { } } + public preferences = new UIEventSource({}); + 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) { const nodes = response.getElementsByTagName("node"); const mapping = {}; diff --git a/Logic/StrayClickHandler.ts b/Logic/StrayClickHandler.ts new file mode 100644 index 0000000..13a17d1 --- /dev/null +++ b/Logic/StrayClickHandler.ts @@ -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, + 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; + } + }) + + } + + +} \ No newline at end of file diff --git a/README.md b/README.md index cb98d20..43e289a 100644 --- a/README.md +++ b/README.md @@ -73,3 +73,7 @@ Data from OpenStreetMap Images from Wikipedia/Wikimedia 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 diff --git a/UI/AddButton.ts b/UI/AddButton.ts index ba80197..ac0a343 100644 --- a/UI/AddButton.ts +++ b/UI/AddButton.ts @@ -58,7 +58,7 @@ export class AddButton extends UIElement { basemap.map.on("mousemove", function(){ if (self.state.data === self.PLACING_POI) { - var icon = "crosshair"; + let icon = "crosshair"; for (const option of self._options) { if (option.name === self.curentAddSelection.data && option.icon !== undefined) { icon = 'url("' + option.icon + '") 32 32 ,crosshair'; diff --git a/UI/Base/Button.ts b/UI/Base/Button.ts new file mode 100644 index 0000000..3026729 --- /dev/null +++ b/UI/Base/Button.ts @@ -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 "
" + + "" + + "
"; + } + + 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(); + } + } + +} \ No newline at end of file diff --git a/UI/DropDownUI.ts b/UI/Base/DropDownUI.ts similarity index 93% rename from UI/DropDownUI.ts rename to UI/Base/DropDownUI.ts index b551eea..a8b80e6 100644 --- a/UI/DropDownUI.ts +++ b/UI/Base/DropDownUI.ts @@ -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 { diff --git a/UI/FixedUiElement.ts b/UI/Base/FixedUiElement.ts similarity index 85% rename from UI/FixedUiElement.ts rename to UI/Base/FixedUiElement.ts index 14a60a4..39fee94 100644 --- a/UI/FixedUiElement.ts +++ b/UI/Base/FixedUiElement.ts @@ -1,4 +1,4 @@ -import {UIElement} from "./UIElement"; +import {UIElement} from "../UIElement"; export class FixedUiElement extends UIElement { private _html: string; diff --git a/UI/UIRadioButton.ts b/UI/Base/UIRadioButton.ts similarity index 97% rename from UI/UIRadioButton.ts rename to UI/Base/UIRadioButton.ts index 4a60132..7d1616e 100644 --- a/UI/UIRadioButton.ts +++ b/UI/Base/UIRadioButton.ts @@ -1,5 +1,5 @@ -import {UIElement} from "./UIElement"; -import {UIEventSource} from "./UIEventSource"; +import {UIElement} from "../UIElement"; +import {UIEventSource} from "../UIEventSource"; import {FixedUiElement} from "./FixedUiElement"; import $ from "jquery" diff --git a/UI/VariableUIElement.ts b/UI/Base/VariableUIElement.ts similarity index 87% rename from UI/VariableUIElement.ts rename to UI/Base/VariableUIElement.ts index d48bc61..b1f7f05 100644 --- a/UI/VariableUIElement.ts +++ b/UI/Base/VariableUIElement.ts @@ -1,5 +1,5 @@ -import {UIElement} from "./UIElement"; -import {UIEventSource} from "./UIEventSource"; +import {UIElement} from "../UIElement"; +import {UIEventSource} from "../UIEventSource"; export class VariableUiElement extends UIElement { private _html: UIEventSource; diff --git a/UI/VerticalCombine.ts b/UI/Base/VerticalCombine.ts similarity index 96% rename from UI/VerticalCombine.ts rename to UI/Base/VerticalCombine.ts index 0d9b5a5..2174a90 100644 --- a/UI/VerticalCombine.ts +++ b/UI/Base/VerticalCombine.ts @@ -1,4 +1,4 @@ -import {UIElement} from "./UIElement"; +import {UIElement} from "../UIElement"; export class VerticalCombine extends UIElement { private _elements: UIElement[]; diff --git a/UI/CenterMessageBox.ts b/UI/CenterMessageBox.ts index c72561e..b08d0d2 100644 --- a/UI/CenterMessageBox.ts +++ b/UI/CenterMessageBox.ts @@ -1,6 +1,5 @@ import {UIElement} from "./UIElement"; import {UIEventSource} from "./UIEventSource"; -import {Helpers} from "../Helpers"; import {OsmConnection} from "../Logic/OsmConnection"; export class CenterMessageBox extends UIElement { diff --git a/UI/FeatureInfoBox.ts b/UI/FeatureInfoBox.ts index 527c7d0..3ca09b9 100644 --- a/UI/FeatureInfoBox.ts +++ b/UI/FeatureInfoBox.ts @@ -2,7 +2,6 @@ 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"; @@ -12,6 +11,7 @@ import {Img} from "./Img"; import {CommonTagMappings} from "../Layers/CommonTagMappings"; import {Tag} from "../Logic/TagsFilter"; import {ImageUploadFlow} from "./ImageUploadFlow"; +import {VerticalCombine} from "./Base/VerticalCombine"; export class FeatureInfoBox extends UIElement { diff --git a/UI/MessageBoxHandler.ts b/UI/MessageBoxHandler.ts index ee528ef..cf551cd 100644 --- a/UI/MessageBoxHandler.ts +++ b/UI/MessageBoxHandler.ts @@ -3,8 +3,7 @@ */ import {UIEventSource} from "./UIEventSource"; import {UIElement} from "./UIElement"; -import {FixedUiElement} from "./FixedUiElement"; -import {VariableUiElement} from "./VariableUIElement"; +import {VariableUiElement} from "./Base/VariableUIElement"; export class MessageBoxHandler { private _uielement: UIEventSource<() => UIElement>; @@ -15,8 +14,16 @@ export class MessageBoxHandler { this.listenTo(uielement); this.update(); + window.onhashchange = function () { + if (location.hash === "") { + // No more element: back to the map! + uielement.setData(undefined); + onClear(); + } + } + new VariableUiElement(new UIEventSource("

Naar de kaart

"), - (htmlElement) => { + () => { document.getElementById("to-the-map").onclick = function () { uielement.setData(undefined); onClear(); @@ -24,6 +31,7 @@ export class MessageBoxHandler { } ).AttachTo("to-the-map"); + } listenTo(uiEventSource: UIEventSource) { @@ -33,14 +41,19 @@ export class MessageBoxHandler { }) } + update() { const wrapper = document.getElementById("messagesboxmobilewrapper"); const gen = this._uielement.data; console.log("Generator: ", gen); if (gen === undefined) { - wrapper.classList.add("hidden"); + wrapper.classList.add("hidden") + if (location.hash !== "") { + location.hash = "" + } return; } + location.hash = "#element" wrapper.classList.remove("hidden"); gen() ?.HideOnEmpty(true) diff --git a/UI/SimpleAddUI.ts b/UI/SimpleAddUI.ts new file mode 100644 index 0000000..0462215 --- /dev/null +++ b/UI/SimpleAddUI.ts @@ -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; + + constructor(zoomlevel: UIEventSource<{ zoom: number }>, + lastClickLocation: UIEventSource<{ lat: number, lon: number }>, + changes: Changes, + selectedElement: UIEventSource, + 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) { + //