Merge pull request #27 from oSoc20/filter-ui

Filter ui
This commit is contained in:
Pieter Vander Vennet 2020-07-22 17:55:21 +02:00 committed by GitHub
commit 66282b289c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 331 additions and 126 deletions

View file

@ -116,12 +116,8 @@ export class LayerDefinition {
showOnPopup: (tags: UIEventSource<(any)>, feature: any) => UIElement):
FilteredLayer {
return new FilteredLayer(
this.name,
this,
basemap, allElements, changes,
this.overpassFilter,
this.maxAllowedOverlapPercentage,
this.wayHandling,
this.style,
selectedElement,
showOnPopup);

View file

@ -2,22 +2,13 @@ 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 {
// @ts-ignore
public map: Map;
export class BaseLayers {
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',
{
layers: "OGWRGB13_15VL",
attribution: "Luchtfoto's van © AIV Vlaanderen (2013-2015) | "
});
private aivLuchtLatestLayer = L.tileLayer("https://tile.informatievlaanderen.be/ws/raadpleegdiensten/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&" +
public static readonly defaultLayerName = "Kaart van OpenStreetMap";
public static readonly baseLayers = {
["Luchtfoto Vlaanderen (recentste door AIV)"]:
L.tileLayer("https://tile.informatievlaanderen.be/ws/raadpleegdiensten/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&" +
"LAYER=omwrgbmrvl&STYLE=&FORMAT=image/png&tileMatrixSet=GoogleMapsVL&tileMatrix={z}&tileRow={y}&tileCol={x}",
{
// omwrgbmrvl
@ -25,57 +16,63 @@ export class Basemap {
maxZoom: 20,
minZoom: 1,
wmts: true
});
private osmLayer = L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png",
}),
["Luchtfoto Vlaanderen (2013-2015, door AIV)"]: L.tileLayer.wms('https://geoservices.informatievlaanderen.be/raadpleegdiensten/OGW/wms?s',
{
layers: "OGWRGB13_15VL",
attribution: "Luchtfoto's van © AIV Vlaanderen (2013-2015) | "
}),
[BaseLayers.defaultLayerName]: L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png",
{
attribution: '',
maxZoom: 19,
minZoom: 1
});
private osmBeLayer = L.tileLayer("https://tile.osm.be/osmbe/{z}/{x}/{y}.png",
{
attribution: '<a href="https://geo6.be/">Tile hosting courtesy of Geo6</a>',
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}",
}),
["Kaart Grootschalig ReferentieBestand Vlaanderen (GRB) door AIV"]: 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: 'Achtergrond <i>Grootschalig ReferentieBestand</i>(GRB) © AGIV',
maxZoom: 20,
minZoom: 1,
wmts: true
});
private baseLayers = {
"Luchtfoto Vlaanderen (recentste door AIV)": this.aivLuchtLatestLayer,
"Luchtfoto Vlaanderen (2013-2015, door AIV)": this.aivLucht2013Layer,
"Kaart van OpenStreetMap": this.osmLayer,
"Kaart Grootschalig ReferentieBestand Vlaanderen (GRB) door AIV": this.grbLayer
}),
};
public static readonly defaultLayer = BaseLayers.baseLayers[BaseLayers.defaultLayerName];
}
// Contains all setup and baselayers for Leaflet stuff
export class Basemap {
// @ts-ignore
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 _previousLayer : L.tileLayer= undefined;
public CurrentLayer: UIEventSource<{
name: string,
layer: L.tileLayer
}> = new UIEventSource<L.tileLayer>(
{name: BaseLayers.defaultLayerName, layer: BaseLayers.defaultLayer}
);
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],
layers: [BaseLayers.defaultLayer],
});
this.map.attributionControl.setPrefix(
extraAttribution.Render() + " | <a href='https://osm.org'>OpenStreetMap</a>");
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;
@ -87,6 +84,14 @@ export class Basemap {
location.ping();
});
this.CurrentLayer.addCallback((layer:{layer: L.tileLayer}) => {
if(self._previousLayer !== undefined){
self.map.removeLayer(self._previousLayer);
}
self._previousLayer = layer.layer;
self.map.addLayer(layer.layer);
});
this.map.on("click", function (e) {
self.LastClickLocation.setData({lat: e.latlng.lat, lon: e.latlng.lng})
});

View file

@ -22,7 +22,7 @@ export class FilteredLayer {
public readonly name: string | UIElement;
public readonly filters: TagsFilter;
public readonly isDisplayed: UIEventSource<boolean> = new UIEventSource(true);
public readonly layerDef: LayerDefinition;
private readonly _map: Basemap;
private readonly _maxAllowedOverlap: number;
@ -45,32 +45,28 @@ export class FilteredLayer {
private _showOnPopup: (tags: UIEventSource<any>, feature: any) => UIElement;
constructor(
name: string | UIElement,
layerDef: LayerDefinition,
map: Basemap, storage: ElementStorage,
changes: Changes,
filters: TagsFilter,
maxAllowedOverlap: number,
wayHandling: number,
style: ((properties) => any),
selectedElement: UIEventSource<{feature: any}>,
showOnPopup: ((tags: UIEventSource<any>, feature: any) => UIElement)
selectedElement: UIEventSource<any>,
showOnPopup: ((tags: UIEventSource<any>) => UIElement)
) {
this._wayHandling = wayHandling;
this.layerDef = layerDef;
this._wayHandling = layerDef.wayHandling;
this._selectedElement = selectedElement;
this._showOnPopup = showOnPopup;
if (style === undefined) {
style = function () {
return {};
this._style = layerDef.style;
if (this._style === undefined) {
this._style = function () {
return {icon: "", color: "#000000"};
}
}
this.name = name;
this._map = map;
this.filters = filters;
this._style = style;
this.filters = layerDef.overpassFilter;
this._storage = storage;
this._maxAllowedOverlap = maxAllowedOverlap;
this._maxAllowedOverlap = layerDef.maxAllowedOverlapPercentage;
const self = this;
this.isDisplayed.addCallback(function (isDisplayed) {
if (self._geolayer !== undefined && self._geolayer !== null) {

View file

@ -1,19 +1,35 @@
import {UIElement} from "../UIElement";
import {UIEventSource} from "../UIEventSource";
import { FilteredLayer } from "../../Logic/FilteredLayer";
import Translations from "../../UI/i18n/Translations";
export class CheckBox extends UIElement{
private data: UIEventSource<boolean>;
constructor(data: UIEventSource<boolean>) {
super(data);
this.data = data;
private readonly _data: UIEventSource<boolean>;
private readonly _showEnabled: string|UIElement;
private readonly _showDisabled: string|UIElement;
constructor(showEnabled: string|UIElement, showDisabled: string|UIElement, data: UIEventSource<boolean> = undefined) {
super(undefined);
this._data = data ?? new UIEventSource<boolean>(false);
this.ListenTo(this._data);
this._showEnabled = showEnabled;
this._showDisabled = showDisabled;
const self = this;
this.onClick(() => {
self._data.setData(!self._data.data);
})
}
protected InnerRender(): string {
return "Current val: "+this.data.data;
InnerRender(): string {
if (this._data.data) {
return Translations.W(this._showEnabled).Render();
} else {
return Translations.W(this._showDisabled).Render();
}
}
}

View file

@ -21,7 +21,7 @@ export default class Combine extends UIElement {
return elements;
}
protected InnerUpdate(htmlElement: HTMLElement) {
InnerUpdate(htmlElement: HTMLElement) {
for (const element of this.uiElements) {
if (element instanceof UIElement) {
element.Update();

View file

@ -10,5 +10,15 @@ export class Img {
"</svg>";
static closedFilterButton: string = `<svg width="27" height="27" viewBox="0 0 27 27" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M26.5353 8.13481C26.4422 8.35428 26.2683 8.47598 26.0632 8.58537C21.9977 10.7452 17.935 12.9085 13.8758 15.0799C13.6475 15.2016 13.4831 15.1962 13.2568 15.0751C9.19822 12.903 5.13484 10.7404 1.07215 8.5758C0.490599 8.26608 0.448478 7.52562 0.991303 7.13796C1.0803 7.07438 1.17813 7.0231 1.2746 6.97045C5.15862 4.86462 9.04536 2.7629 12.9246 0.648187C13.3805 0.399316 13.7779 0.406837 14.2311 0.65434C18.0954 2.76153 21.9658 4.85779 25.8383 6.94926C26.1569 7.12155 26.411 7.32872 26.5353 7.67604C26.5353 7.82919 26.5353 7.98166 26.5353 8.13481Z" fill="#003B8B"/>
<path d="M13.318 26.535C12.1576 25.9046 10.9972 25.2736 9.83614 24.6439C6.96644 23.0877 4.09674 21.533 1.22704 19.9762C0.694401 19.6876 0.466129 19.2343 0.669943 18.7722C0.759621 18.5691 0.931505 18.3653 1.11969 18.2512C1.66659 17.9182 2.23727 17.6228 2.80863 17.3329C2.89423 17.2892 3.04981 17.3206 3.14493 17.3712C6.40799 19.1031 9.66969 20.837 12.9239 22.5845C13.3703 22.8238 13.7609 22.83 14.208 22.59C17.4554 20.8472 20.7117 19.1202 23.9605 17.3801C24.1493 17.2789 24.2838 17.283 24.4632 17.3876C24.8926 17.6386 25.3301 17.8772 25.7751 18.1001C26.11 18.2683 26.3838 18.4857 26.5346 18.8385C26.5346 18.9916 26.5346 19.1441 26.5346 19.2972C26.4049 19.6528 26.1399 19.8613 25.8152 20.0363C22.9964 21.5549 20.1831 23.0829 17.3684 24.609C16.1863 25.2496 15.0055 25.893 13.8248 26.535C13.6556 26.535 13.4865 26.535 13.318 26.535Z" fill="#003B8B"/>
<path d="M26.3988 13.7412C26.2956 13.9661 26.1026 14.081 25.8927 14.1924C21.8198 16.3577 17.749 18.5258 13.6815 20.7013C13.492 20.8025 13.3602 20.7902 13.1795 20.6938C9.09638 18.5114 5.01059 16.3359 0.924798 14.1582C0.399637 13.8786 0.307921 13.2646 0.735251 12.838C0.829005 12.7443 0.947217 12.6705 1.06407 12.6055C1.56545 12.3279 2.07635 12.0654 2.57297 11.7789C2.74214 11.6812 2.86579 11.6921 3.03291 11.7817C6.27492 13.5155 9.52303 15.2378 12.761 16.9792C13.2352 17.2343 13.6394 17.2322 14.1129 16.9772C17.3509 15.2358 20.5996 13.5142 23.8416 11.7796C24.0095 11.69 24.1338 11.6818 24.3016 11.7789C24.7384 12.0339 25.1821 12.2794 25.6352 12.5037C25.9701 12.6691 26.2426 12.8831 26.3995 13.2304C26.3988 13.4014 26.3988 13.5716 26.3988 13.7412Z" fill="#003B8B"/>
</svg> `
static openFilterButton: string = `<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20 2L2 20M20 20L2 2" stroke="#003B8B" stroke-width="4"/>
</svg> `
}

View file

@ -28,7 +28,10 @@ export class DropDown<T> extends InputElement<T> {
for (const v of this._values) {
this.ListenTo(v.shown._source);
}
this.ListenTo(this._value)
this.ListenTo(this._value);
this.onClick(() => {}) // by registering a click, the click event is consumed and doesn't bubble furter to other elements, e.g. checkboxes
}

45
UI/LayerSelection.ts Normal file
View file

@ -0,0 +1,45 @@
import { UIElement } from "./UIElement";
import { FilteredLayer } from "../Logic/FilteredLayer";
import { CheckBox } from "./Base/CheckBox";
import Combine from "./Base/Combine";
export class LayerSelection extends UIElement{
private readonly _checkboxes: UIElement[];
constructor(layers: FilteredLayer[]) {
super(undefined);
this._checkboxes = [];
for (const layer of layers) {
this._checkboxes.push(new CheckBox(
new Combine([
`<svg width="26" height="18" viewBox="0 0 26 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 7.28571L10.8261 15L23 3" stroke="#003B8B" stroke-width="4" stroke-linejoin="round"/>
</svg>`,
`<img width="20" height="20" src="${layer.layerDef.icon}" alt="layer.layerDef.icon">`,
layer.layerDef.name]),
new Combine([
`<svg width="26" height="18" viewBox="0 0 26 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 7.28571L10.8261 15L23 3" stroke="#ffffff" stroke-width="4" stroke-linejoin="round"/>
</svg>`,
`<img width="20" height="20" src="${layer.layerDef.icon}" alt="layer.layerDef.icon">`,
layer.layerDef.name]),
layer.isDisplayed));
}
}
InnerRender(): string {
let html = ``;
for (const checkBox of this._checkboxes) {
const checkBoxHTML = checkBox.Render();
const checkBoxListItem = `<li>${checkBoxHTML}</li>`;
html = html + checkBoxListItem;
}
return `<ul>${html}</ul>`;
}
}

View file

@ -53,8 +53,12 @@ export abstract class UIElement {
if (this._onClick !== undefined) {
const self = this;
element.onclick = () => {
element.onclick = (e) => {
if(e.consumed){
return;
}
self._onClick();
e.consumed = true;
}
element.style.pointerEvents = "all";
element.style.cursor = "pointer";

116
index.css
View file

@ -261,6 +261,122 @@ form {
pointer-events: all;
}
/* filter ui */
.filter__popup {
position: absolute;
bottom: 0;
z-index: 500;
padding-left: 10px;
padding-bottom: 10px;
}
.filter__button {
outline: none;
border: none;
width: 60px;
height: 60px;
border-radius: 50%;
background-color: white;
position: relative;
}
.filter__button--shadow {
box-shadow: 0px 2px 6px rgba(0, 0, 0, 0.25);
}
.filter__button svg {
vertical-align: middle;
}
#filter__selection form {
display: flex;
flex-flow: column;
width: 100%;
background-color: #ffffff;
border-radius: 15px;
border: none;
font-size: 16px;
transform: translateY(60px);
padding: 15px 0 60px 0;
}
#filter__selection label {
font-size: 16px;
background-color: #ffffff;
padding: 0 15px 12px 15px;
margin: 0;
color: #003B8B;
font-weight: 600;
}
#filter__selection select {
outline: none;
background-color: #F0EFEF;
border: none;
border-radius: 5px;
font-size: 14px;
padding: 5px;
margin: 0 15px;
max-width: 250px;
}
#filter__selection ul {
background-color: #ffffff;
padding: 10px 25px 18px 18px;
list-style: none;
margin: 0;
font-weight: 600;
transform: translateY(75px);
}
#filter__selection ul li span > span {
display: flex;
align-items: center;
/* padding: 10px 0; */
}
#filter__selection ul svg {
padding: 10px 14px 10px 0;
border-right: 1px solid #003B8B;
}
#filter__selection ul img {
width: 20px;
height: auto;
margin: 0 10px 0 18px;
}
.filter__label {
font-size: 16px;
transform: translateY(75px);
background-color: #ffffff;
padding: 10px 15px;
margin: 0;
color: #003B8B;
font-weight: 600;
border-radius: 15px 15px 0 0;
}
#filter__layers {
pointer-events: all;
list-style: none;
padding-left: 0;
}
#filter__layers li span {
display: flex;
align-items: center;
}
.checkbox__label--checked {
margin-left: .7rem;
}
.checkbox__label--unchecked {
margin-left: 2.45rem;
}
@media only screen and (max-width: 600px) {
#messagesbox-wrapper {

View file

@ -44,6 +44,9 @@
</div>
</div>
<div id="filter__popup" class="filter__popup">
<div id="filter__selection"></div>
</div>
<div id="centermessage"></div>
<div id="bottomRight" style="display: none">ADD</div>

View file

@ -3,7 +3,7 @@ import {Changes} from "./Logic/Changes";
import {ElementStorage} from "./Logic/ElementStorage";
import {UIEventSource} from "./UI/UIEventSource";
import {UserBadge} from "./UI/UserBadge";
import {Basemap} from "./Logic/Basemap";
import {Basemap, BaseLayers} from "./Logic/Basemap";
import {PendingChanges} from "./UI/PendingChanges";
import {CenterMessageBox} from "./UI/CenterMessageBox";
import {Helpers} from "./Helpers";
@ -12,7 +12,6 @@ import {FilteredLayer} from "./Logic/FilteredLayer";
import {LayerUpdater} from "./Logic/LayerUpdater";
import {UIElement} from "./UI/UIElement";
import {FullScreenMessageBoxHandler} from "./UI/FullScreenMessageBoxHandler";
import {Overpass} from "./Logic/Overpass";
import {FeatureInfoBox} from "./UI/FeatureInfoBox";
import {GeoLocationHandler} from "./Logic/GeoLocationHandler";
import {StrayClickHandler} from "./Logic/StrayClickHandler";
@ -21,15 +20,15 @@ import {VariableUiElement} from "./UI/Base/VariableUIElement";
import {SearchAndGo} from "./UI/SearchAndGo";
import {CollapseButton} from "./UI/Base/CollapseButton";
import {AllKnownLayouts} from "./Customizations/AllKnownLayouts";
import {All} from "./Customizations/Layouts/All";
import {CheckBox} from "./UI/Base/CheckBox";
import Translations from "./UI/i18n/Translations";
import Translation from "./UI/i18n/Translation";
import Locale from "./UI/i18n/Locale";
import {Layout, WelcomeMessage} from "./Customizations/Layout";
import {DropDown} from "./UI/Input/DropDown";
import {FixedInputElement} from "./UI/Input/FixedInputElement";
import {FixedUiElement} from "./UI/Base/FixedUiElement";
import ParkingType from "./Customizations/Questions/bike/ParkingType";
import {LayerSelection} from "./UI/LayerSelection";
import Combine from "./UI/Base/Combine";
import {Img} from "./UI/Img";
// --------------------- Read the URL parameters -----------------
@ -56,7 +55,7 @@ if (location.hostname === "localhost" || location.hostname === "127.0.0.1") {
// ----------------- SELECT THE RIGHT QUESTSET -----------------
let defaultLayout = "buurtnatuur"
let defaultLayout = "walkbybrussels"
// Run over all questsets. If a part of the URL matches a searched-for part in the layout, it'll take that as the default
@ -204,11 +203,38 @@ for (const layer of layoutToUse.layers) {
}
addButtons.push(addButton);
flayers.push(flayer);
console.log(flayers);
}
const layerUpdater = new LayerUpdater(bm, minZoom, flayers);
// --------------- Setting up filter ui --------
// buttons
const closedFilterButton = `<button id="filter__button" class="filter__button filter__button--shadow">${Img.closedFilterButton}</button>`;
const openFilterButton = `
<button id="filter__button" class="filter__button">${Img.openFilterButton}</button>`;
// basemap dropdown
let baseLayerOptions = [];
for (const key in BaseLayers.baseLayers) {
baseLayerOptions.push({value: {name: key, layer: BaseLayers.baseLayers[key]}, shown: key});
}
if (flayers.length > 1) {
new CheckBox(new Combine([`<p class="filter__label">Maplayers</p>`, new LayerSelection(flayers), new DropDown(`Background map`, baseLayerOptions, bm.CurrentLayer), openFilterButton]), closedFilterButton).AttachTo("filter__selection");
} else {
new CheckBox(new Combine([new DropDown(`Background map`, baseLayerOptions, bm.CurrentLayer), openFilterButton]), closedFilterButton).AttachTo("filter__selection");
}
// ------------------ Setup various UI elements ------------
let languagePicker = new DropDown(" ", layoutToUse.supportedLanguages.map(lang => {
@ -304,3 +330,6 @@ new GeoLocationHandler(bm).AttachTo("geolocate-button");
// --------------- Send a ping to start various action --------
locationControl.ping();

38
test.ts
View file

@ -1,32 +1,14 @@
import { DropDown } from "./UI/Input/DropDown";
import Locale from "./UI/i18n/Locale";
import Combine from "./UI/Base/Combine";
import Translations from "./UI/i18n/Translations";
import {TagRenderingOptions} from "./Customizations/TagRendering";
import {UIEventSource} from "./UI/UIEventSource";
import {Tag} from "./Logic/TagsFilter";
import {Changes} from "./Logic/Changes";
import {OsmConnection} from "./Logic/OsmConnection";
import Translation from "./UI/i18n/Translation";
import { BaseLayers, Basemap } from "./Logic/Basemap";
console.log("Hello world")
Locale.language.setData("en");
let languagePicker = new DropDown("", ["en", "nl"].map(lang => {
return {value: lang, shown: lang}
}
), Locale.language).AttachTo("maindiv");
let baseLayerOptions = [];
Object.entries(BaseLayers.baseLayers).forEach(([key, value], i) => {
// console.log(key, value, i);
baseLayerOptions.push({value: i, shown: key});
});
console.log(Basemap);
let tags = new UIEventSource({
x:"y"
})
new TagRenderingOptions({
mappings: [{k: new Tag("x","y"), txt: new Translation({en: "ENG", nl: "NED"})}]
}).construct({
tags: tags,
changes: new Changes(
"cs",
new OsmConnection(true)
)
}).AttachTo("extradiv")
new DropDown(`label`, baseLayerOptions, Basemap.CurrentLayer).AttachTo("maindiv");