Merge branches

This commit is contained in:
Pieter Vander Vennet 2020-07-16 09:54:32 +02:00
commit c1c11ed906
33 changed files with 568 additions and 130 deletions

View file

@ -5,13 +5,14 @@ import {Statues} from "./Layouts/Statues";
import {Bookcases} from "./Layouts/Bookcases";
import Cyclofix from "./Layouts/Cyclofix";
import {All} from "./Layouts/All";
import {Layout} from "./Layout";
export class AllKnownLayouts {
public static allSets: any = AllKnownLayouts.AllLayouts();
private static AllLayouts() {
private static AllLayouts() : any{
const all = new All();
const layouts = [
const layouts : Layout[] = [
new Groen(),
new GRB(),
new Cyclofix(),

View file

@ -16,9 +16,35 @@ export class LayerDefinition {
* This name is shown in the 'add XXX button'
*/
name: string;
/**
* These tags are added whenever a new point is added by the user on the map.
* This is the ideal place to add extra info, such as "fixme=added by MapComplete, geometry should be checked"
*/
newElementTags: Tag[]
/**
* Not really used anymore
* This is meant to serve as icon in the buttons
*/
icon: string;
/**
* Only show this layer starting at this zoom level
*/
minzoom: number;
/**
* This tagfilter is used to query overpass.
* Examples are:
*
* new Tag("amenity","drinking_water")
*
* or a query for bicycle pumps which have two tagging schemes:
* new Or([
* new Tag("service:bicycle:pump","yes") ,
* new And([
* new Tag("amenity","compressed_air"),
* new Tag("bicycle","yes")])
* ])
*/
overpassFilter: TagsFilter;
/**

View file

@ -2,8 +2,9 @@ import {LayerDefinition} from "../LayerDefinition";
import {And, Or, Tag} from "../../Logic/TagsFilter";
import {OperatorTag} from "../Questions/OperatorTag";
import * as L from "leaflet";
import FixedName from "../Questions/FixedName";
import FixedText from "../Questions/FixedText";
import { BikeParkingType } from "../Questions/BikeParkingType";
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
export class BikeParkings extends LayerDefinition {
@ -19,8 +20,9 @@ export class BikeParkings extends LayerDefinition {
this.minzoom = 13;
this.style = this.generateStyleFunction();
this.title = new FixedName("Fietsparking");
this.title = new FixedText("Fietsparking");
this.elementsToShow = [
new ImageCarouselWithUploadConstructor(),
new OperatorTag(),
new BikeParkingType()
];

View file

@ -1,13 +1,15 @@
import {LayerDefinition} from "../LayerDefinition";
import {And, Tag} from "../../Logic/TagsFilter";
import * as L from "leaflet";
import FixedName from "../Questions/FixedName";
import BikeStationChain from "../Questions/BikeStationChain";
import BikeStationPumpTools from "../Questions/BikeStationPumpTools";
import BikeStationStand from "../Questions/BikeStationStand";
import PumpManual from "../Questions/PumpManual";
import BikeStationOperator from "../Questions/BikeStationOperator";
import BikeStationBrand from "../Questions/BikeStationBrand";
import FixedText from "../Questions/FixedText";
import {BikePumpManometer} from "../Questions/BikePumpManometer";
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
export default class BikeServices extends LayerDefinition {
constructor() {
@ -27,12 +29,22 @@ export default class BikeServices extends LayerDefinition {
this.minzoom = 13;
this.style = this.generateStyleFunction();
this.title = new FixedName("Bike station");
this.title = new FixedText("Bike station");
const pump = new Tag("service:bicycle:pump", "yes");
this.elementsToShow = [
new ImageCarouselWithUploadConstructor(),
new BikeStationPumpTools(),
new BikeStationChain().OnlyShowIf(new Tag("service:bicycle:tools", "yes")),
new BikeStationStand().OnlyShowIf(new Tag("service:bicycle:tools", "yes")),
new PumpManual().OnlyShowIf(new Tag("service:bicycle:pump", "yes")),
new PumpManual().OnlyShowIf(pump),
new BikePumpManometer().OnlyShowIf(pump),
new BikeStationOperator(),
new BikeStationBrand()
];

View file

@ -145,7 +145,7 @@ export class Bookcases extends LayerDefinition {
new TagRenderingOptions({
freeform: {
key: "description",
renderTemplate: "<b>Beschrijving door de uitbater</b><br>{description}",
renderTemplate: "<b>Beschrijving door de uitbater:</b><br>{description}",
template: "$$$",
}
})

View file

@ -7,6 +7,7 @@ import {TagRenderingOptions} from "../TagRendering";
import {NameQuestion} from "../Questions/NameQuestion";
import {NameInline} from "../Questions/NameInline";
import {DescriptionQuestion} from "../Questions/DescriptionQuestion";
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
export class Bos extends LayerDefinition {
@ -33,6 +34,7 @@ export class Bos extends LayerDefinition {
this.style = this.generateStyleFunction();
this.title = new NameInline("bos");
this.elementsToShow = [
new ImageCarouselWithUploadConstructor(),
new NameQuestion(),
new AccessTag(),
new OperatorTag(),

View file

@ -0,0 +1,69 @@
import {LayerDefinition} from "../LayerDefinition";
import {Tag} from "../../Logic/TagsFilter";
import {FixedUiElement} from "../../UI/Base/FixedUiElement";
import {TagRenderingOptions} from "../TagRendering";
import FixedText from "../Questions/FixedText";
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
import L from "leaflet";
export class GhostBike extends LayerDefinition {
constructor() {
super();
this.name = "ghost bike";
this.overpassFilter = new Tag("memorial", "ghost_bike")
this.title = new FixedText("Ghost bike");
this.elementsToShow = [
new FixedText("A <b>ghost bike</b> is a memorial for a cyclist who died in a traffic accident," +
" in the form of a white bicycle placed permanently near the accident location."),
new ImageCarouselWithUploadConstructor(),
new TagRenderingOptions({
question: "Whom is remembered by this ghost bike?" +
"<span class='question-subtext'>" +
"<br/>" +
"Please respect privacy - only fill out the name if it is widely published or marked on the cycle." +
"</span>",
mappings: [{k: new Tag("noname", "yes"), txt: "There is no name marked on the bike"},],
freeform: {
key: "name",
extraTags: new Tag("noname", ""),
template: "$$$",
renderTemplate: "In the remembrance of <b>{name}</b>",
}
}),
new TagRenderingOptions({
question: "When was the ghost bike installed?",
freeform: {
key: "start_date",
template: "The ghost bike was placed on $$$", // TODO create a date picker
renderTemplate: "The ghost bike was placed on <b>{start_date}</b>",
}
}),
new TagRenderingOptions({
question: "On what URL can more information be found?" +
"<span class='question-subtext'>If available, add a link to a news report about the accident or about the placing of the ghost bike</span>",
freeform: {
key: "source",
template: "More information available on $$$",
renderTemplate: "<a href='{source}' target='_blank'>More information</a>",
}
}),
];
this.style = (tags: any) => {
return {
color: "#000000",
icon: L.icon({
iconUrl: 'assets/ghost_bike.svg',
iconSize: [40, 40],
iconAnchor: [20, 20],
})
}
};
}
}

View file

@ -6,6 +6,7 @@ import {OperatorTag} from "../Questions/OperatorTag";
import {NameQuestion} from "../Questions/NameQuestion";
import {NameInline} from "../Questions/NameInline";
import {DescriptionQuestion} from "../Questions/DescriptionQuestion";
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
export class NatureReserves extends LayerDefinition {
@ -23,6 +24,7 @@ export class NatureReserves extends LayerDefinition {
this.title = new NameInline("natuurreservaat");
this.style = this.generateStyleFunction();
this.elementsToShow = [
new ImageCarouselWithUploadConstructor(),
new NameQuestion(),
new AccessTag(),
new OperatorTag(),

View file

@ -7,6 +7,7 @@ import {TagRenderingOptions} from "../TagRendering";
import {NameQuestion} from "../Questions/NameQuestion";
import {NameInline} from "../Questions/NameInline";
import {DescriptionQuestion} from "../Questions/DescriptionQuestion";
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
export class Park extends LayerDefinition {
@ -58,6 +59,7 @@ export class Park extends LayerDefinition {
this.style = this.generateStyleFunction();
this.title = new NameInline("park");
this.elementsToShow = [
new ImageCarouselWithUploadConstructor(),
new NameQuestion(),
this.accessByDefault,
this.operatorByDefault,

View file

@ -16,7 +16,21 @@ export class Layout {
public startLat: number;
public welcomeTail: string;
public locationContains: string[];
/**
*
* @param name: The name used in the query string. If in the query "quests=<name>" is defined, it will select this layout
* @param title: Will be used in the <title> of the page
* @param layers: The layers to show, a list of LayerDefinitions
* @param startzoom: The initial starting zoom of the map
* @param startLat:The initial starting latitude of the map
* @param startLon: the initial starting longitude of the map
* @param welcomeMessage: This message is shown in the collapsable box on the left
* @param gettingStartedPlzLogin: This is shown below the welcomemessage and wrapped in a login link.
* @param welcomeBackMessage: This is shown when the user is logged in
* @param welcomeTail: This text is shown below the login message. It is ideal for extra help
*/
constructor(
name: string,
title: string,
@ -25,8 +39,8 @@ export class Layout {
startLat: number,
startLon: number,
welcomeMessage: string,
gettingStartedPlzLogin: string,
welcomeBackMessage: string,
gettingStartedPlzLogin: string = "Please login to get started",
welcomeBackMessage: string = "You are logged in. Welcome back!",
welcomeTail: string = ""
) {
this.title = title;
@ -41,13 +55,5 @@ export class Layout {
this.welcomeTail = welcomeTail;
}
/*
static statues = new Layout(
);
*/
}

View file

@ -23,5 +23,6 @@ export class Bookcases extends Layout{
" </a> of door je " +
" <span onclick=\"authOsm()\" class=\"activate-osm-authentication\">aan te melden</span>.</p>",
"Klik op een boekenruilkastje om vragen te beantwoorden");
this.locationContains= ["Bookcases.html", "Bookcase.html","bookcase"]
}
}

View file

@ -2,13 +2,15 @@ import {Layout} from "../Layout";
import {GrbToFix} from "../Layers/GrbToFix";
import { BikeParkings } from "../Layers/BikeParkings";
import BikeServices from "../Layers/BikeServices";
import {GhostBike} from "../Layers/GhostBike";
export default class Cyclofix extends Layout {
constructor() {
super(
"pomp",
"Cyclofix bicycle infrastructure",
[new BikeParkings(), new BikeServices()],
// [new BikePumps()],
[new GhostBike(), new BikeParkings(), new BikeServices()],
16,
50.8465573,
4.3516970,

View file

@ -47,5 +47,7 @@ export class Groen extends Layout {
"Als je inlogt, komt er een tweede cookie bij met je inloggegevens." +
"</small>"
);
this.locationContains = ["buurtnatuur.be"]
}
}

View file

@ -0,0 +1,20 @@
import {TagRenderingOptions} from "../TagRendering";
import {Tag} from "../../Logic/TagsFilter";
export class BikePumpManometer extends TagRenderingOptions{
constructor() {
super({
question: "Does the pump have a pressure indicator or manometer?",
mappings: [
{k: new Tag("manometer", "yes"), txt: "Yes, there is a manometer"},
{k: new Tag("manometer","broken"), txt: "Yes, but it is broken"},
{k: new Tag("manometer", "yes"), txt: "No"}
]
});
}
}

View file

@ -0,0 +1,24 @@
import {TagRenderingOptions} from "../TagRendering";
import {Tag} from "../../Logic/TagsFilter";
export class BikePumpValves extends TagRenderingOptions{
constructor() {
super({
question: "What valves are supported?",
mappings: [
{
k: new Tag("valves", " sclaverand;schrader;dunlop"),
txt: "There is a default head, so Presta, Dunlop and Auto"
},
{k: new Tag("valves", "dunlop"), txt: "Only dunlop"},
{k: new Tag("valves", "sclaverand"), txt: "Only Sclaverand (also known as Dunlop)"},
{k: new Tag("valves", "auto"), txt: "Only auto"},
],
freeform: {
key: "valves",
template: "Supported valves are $$$",
renderTemplate: "Supported valves are {valves}"
}
});
}
}

View file

@ -15,6 +15,7 @@ export default class BikeStationOperator extends TagRenderingOptions {
{k: new Tag("operator", "KU Leuven"), txt: "KU Leuven"},
{k: new Tag("operator", "Stad Halle"), txt: "Stad Halle"},
{k: new Tag("operator", "Saint Gilles - Sint Gillis"), txt: "Saint Gilles - Sint Gillis"},
{k: new Tag("operator", "Jette"), txt: "Jette"},
{k: new Tag("operator", "private"), txt: "Beheer door een privépersoon"}
]
}

View file

@ -7,6 +7,7 @@ export class DescriptionQuestion extends TagRenderingOptions{
super({
question: "Zijn er bijzonderheden die we moeten weten over dit "+category+"?<br>" +
"<span class='question-subtext'>Je hoeft niet te herhalen wat je net hebt aangeduid.<br/>" +
"Een <i>naam</i> wordt in de volgende stap gevraagd.<br/>" +
"Voel je vrij om dit veld over te slaan.</span>",
freeform:{
key:"description:0",

View file

@ -1,6 +1,6 @@
import { TagRenderingOptions } from "../TagRendering";
export default class FixedName extends TagRenderingOptions {
export default class FixedText extends TagRenderingOptions {
constructor(category: string) {
super({
mappings: [

View file

@ -11,6 +11,7 @@ import {UIRadioButtonWithOther} from "../UI/Base/UIRadioButtonWithOther";
import {VariableUiElement} from "../UI/Base/VariableUIElement";
import {TagDependantUIElement, TagDependantUIElementConstructor} from "./UIElementConstructor";
import {OnlyShowIfConstructor} from "./OnlyShowIf";
import {UserDetails} from "../Logic/OsmConnection";
export class TagRenderingOptions implements TagDependantUIElementConstructor {
@ -26,11 +27,7 @@ export class TagRenderingOptions implements TagDependantUIElementConstructor {
constructor(options: {
/**
* What is the priority of the question.
* By default, in the popup of a feature, only one question is shown at the same time. If multiple questions are unanswered, the question with the highest priority is asked first
*/
priority?: number
/**
* This is the string that is shown in the popup if this tag is missing.
@ -41,17 +38,12 @@ export class TagRenderingOptions implements TagDependantUIElementConstructor {
question?: string,
/**
* Optional:
* if defined, this a common piece of tag that is shown in front of every mapping (except freeform)
* What is the priority of the question.
* By default, in the popup of a feature, only one question is shown at the same time. If multiple questions are unanswered, the question with the highest priority is asked first
*/
primer?: string,
tagsPreprocessor?: ((tags: any) => any),
freeform?: {
key: string, template: string,
renderTemplate: string
placeholder?: string,
extraTags?: TagsFilter,
},
priority?: number,
/**
* Mappings convert a well-known tag combination into a user friendly text.
* It converts e.g. 'access=yes' into 'this area can be accessed'
@ -64,7 +56,33 @@ export class TagRenderingOptions implements TagDependantUIElementConstructor {
*
*
*/
mappings?: { k: TagsFilter, txt: string, priority?: number, substitute?: boolean }[]
mappings?: { k: TagsFilter, txt: string, priority?: number, substitute?: boolean }[],
/**
* If one wants to render a freeform tag (thus no predefined key/values) or if there are a few well-known tags with a freeform object,
* use this.
* In the question, it'll offer a textfield
*/
freeform?: {
key: string, template: string,
renderTemplate: string
placeholder?: string,
extraTags?: TagsFilter,
},
/**
* Optional:
* if defined, this a common piece of tag that is shown in front of every mapping (except freeform)
*/
primer?: string,
/**
* In some very rare cases, tags have to be rewritten before displaying
* This function adds this
*/
tagsPreprocessor?: ((tags: any) => any)
}) {
this.options = options;
}
@ -111,6 +129,7 @@ class TagRendering extends UIElement implements TagDependantUIElement {
private _priority: number;
private _userDetails: UIEventSource<UserDetails>;
Priority(): number {
return this._priority;
@ -162,6 +181,9 @@ class TagRendering extends UIElement implements TagDependantUIElement {
this.ListenTo(this._questionSkipped);
this.ListenTo(this._editMode);
this._userDetails = changes.login.userDetails;
this.ListenTo(this._userDetails);
this._question = options.question;
this._priority = options.priority ?? 0;
this._primer = options.primer ?? "";
@ -397,8 +419,14 @@ class TagRendering extends UIElement implements TagDependantUIElement {
if (html == "") {
return "";
}
let editButton = "";
if(this._userDetails.data.loggedIn){
editButton = this._editButton.Render();
}
return "<span class='answer'>" +
"<span class='answer-text'>" + html + "</span>" + this._editButton.Render() +
"<span class='answer-text'>" + html + "</span>" +
editButton +
"</span>";
}

View file

@ -13,7 +13,7 @@ export class Changes {
private static _nextId = -1; // New assined ID's are negative
private readonly login: OsmConnection;
public readonly login: OsmConnection;
public readonly _allElements: ElementStorage;
private _pendingChanges: { elementId: string, key: string, value: string }[] = []; // Gets reset on uploadAll

View file

@ -13,7 +13,6 @@ export class UserDetails {
public osmConnection: OsmConnection;
public dryRun: boolean;
home: { lon: number; lat: number };
}
export class OsmConnection {
@ -121,6 +120,29 @@ export class OsmConnection {
}
public preferences = new UIEventSource<any>({});
public preferenceSources : any = {}
public GetPreference(key: string) : UIEventSource<string>{
if(this.preferenceSources[key] !== undefined){
return this.preferenceSources[key];
}
this.UpdatePreferences();
console.log("Getting preference object", key, "currently upstreamed as ",this.preferences.data[key] );
const pref = new UIEventSource<string>(this.preferences.data[key]);
pref.addCallback((v) => {
this.SetPreference(key, v);
});
this.preferences.addCallback((prefs) => {
if (prefs[key] !== undefined) {
pref.setData(prefs[key]);
}
});
this.preferenceSources[key] = pref;
return pref;
}
private UpdatePreferences() {
const self = this;
this.auth.xhr({
@ -142,13 +164,14 @@ export class OsmConnection {
});
}
public SetPreference(k:string, v:string) {
private SetPreference(k:string, v:string) {
if(!this.userDetails.data.loggedIn){
console.log("Not saving preference: user not logged in");
return;
}
if (this.preferences.data[k] === v) {
console.log("Not updating preference", k, " to ", v, "not changed");
return;
}
console.log("Updating preference", k, " to ", v);
@ -166,7 +189,7 @@ export class OsmConnection {
return;
}
console.log("Preference written!", result);
console.log("Preference written!", result == "" ? "OK" : result);
});
}

View file

@ -94,3 +94,6 @@ Trash icon by Dave Gandy, CC-BY-SA
https://commons.wikimedia.org/wiki/File:Home-icon.svg
Home icon by Timothy Miller, CC-BY-SA 3.0
https://commons.wikimedia.org/wiki/File:Map_icons_by_Scott_de_Jonge_-_bicycle-store.svg
Bicycle logo, Scott de Jonge

View file

@ -41,6 +41,9 @@ export class DropDownUI extends UIElement {
InnerUpdate() {
const self = this;
const e = document.getElementById("dropdown-" + this.id);
if(e === null){
return;
}
// @ts-ignore
if (this.selectedElement.data !== e.value) {
// @ts-ignore

View file

@ -15,29 +15,25 @@ import {TagDependantUIElement} from "../Customizations/UIElementConstructor";
export class FeatureInfoBox extends UIElement {
private _tagsES: UIEventSource<any>;
private _changes: Changes;
private _userDetails: UIEventSource<UserDetails>;
private _title: UIElement;
private _osmLink: UIElement;
private _questions: QuestionPicker;
private _changes: Changes;
private _userDetails: UIEventSource<UserDetails>;
private _imageElement: ImageCarousel;
private _pictureUploader: UIElement;
private _wikipedialink: UIElement;
private _infoboxes: TagDependantUIElement[];
private _infoboxes: TagDependantUIElement[];
private _questions: QuestionPicker;
constructor(
tagsES: UIEventSource<any>,
title: TagRenderingOptions,
elementsToShow: TagRenderingOptions[],
changes: Changes,
userDetails: UIEventSource<UserDetails>,
preferedPictureLicense: UIEventSource<string>
userDetails: UIEventSource<UserDetails>
) {
super(tagsES);
this._tagsES = tagsES;
@ -45,9 +41,9 @@ export class FeatureInfoBox extends UIElement {
this._userDetails = userDetails;
this.ListenTo(userDetails);
this._imageElement = new ImageCarousel(this._tagsES, changes);
this._infoboxes = [];
elementsToShow = elementsToShow ?? []
for (const tagRenderingOption of elementsToShow) {
this._infoboxes.push(
tagRenderingOption.construct(this._tagsES, this._changes));
@ -60,11 +56,9 @@ export class FeatureInfoBox extends UIElement {
)
this._title = new TagRenderingOptions(title.options).construct(this._tagsES, this._changes);
this._osmLink =new OsmLink().construct(this._tagsES, this._changes);
this._wikipedialink = new WikipediaLink().construct(this._tagsES, this._changes);
this._pictureUploader = new OsmImageUploadHandler(tagsES, userDetails, preferedPictureLicense,
changes, this._imageElement.slideshow).getUI();
}
@ -110,10 +104,6 @@ export class FeatureInfoBox extends UIElement {
"</div>" +
"<div class='infoboxcontents'>" +
this._imageElement.Render() +
this._pictureUploader.Render() +
new VerticalCombine(info, "infobox-information ").Render() +
questionsHtml +
@ -126,8 +116,6 @@ export class FeatureInfoBox extends UIElement {
Activate() {
super.Activate();
this._imageElement.Activate();
this._pictureUploader.Activate();
for (const infobox of this._infoboxes) {
infobox.Activate();
}
@ -135,8 +123,6 @@ export class FeatureInfoBox extends UIElement {
Update() {
super.Update();
this._imageElement.Update();
this._pictureUploader.Update();
this._title.Update();
for (const infobox of this._infoboxes) {
infobox.Update();

View file

@ -7,8 +7,30 @@ import {VerticalCombine} from "../Base/VerticalCombine";
import {Changes} from "../../Logic/Changes";
import {VariableUiElement} from "../Base/VariableUIElement";
import {ConfirmDialog} from "../ConfirmDialog";
import {UserDetails} from "../../Logic/OsmConnection";
import {TagDependantUIElement, TagDependantUIElementConstructor} from "../../Customizations/UIElementConstructor";
export class ImageCarouselConstructor implements TagDependantUIElementConstructor{
IsKnown(properties: any): boolean {
return true;
}
IsQuestioning(properties: any): boolean {
return false;
}
Priority(): number {
return 0;
}
construct(tags: UIEventSource<any>, changes: Changes): TagDependantUIElement {
return new ImageCarousel(tags, changes);
}
}
export class ImageCarousel extends TagDependantUIElement {
export class ImageCarousel extends UIElement {
private readonly searcher: ImageSearcher;
@ -19,8 +41,11 @@ export class ImageCarousel extends UIElement {
private readonly _deleteButton: UIElement;
private readonly _isDeleted: UIElement;
private readonly _userDetails : UIEventSource<UserDetails>;
constructor(tags: UIEventSource<any>, changes: Changes) {
super(tags);
this._userDetails = changes.login.userDetails;
const self = this;
this.searcher = new ImageSearcher(tags, changes);
@ -40,8 +65,11 @@ export class ImageCarousel extends UIElement {
const showDeleteButton = this.slideshow._currentSlide.map((i) => {
if(!self._userDetails.data.loggedIn){
return false;
}
return self.searcher.IsDeletable(self.searcher.data[i]);
}, [this.searcher]);
}, [this.searcher, this._userDetails]);
this.slideshow._currentSlide.addCallback(() => {
showDeleteButton.ping(); // This pings the showDeleteButton, which indicates that it has to hide it's subbuttons
})
@ -57,8 +85,7 @@ export class ImageCarousel extends UIElement {
"<span>Afbeelding verwijderen</span>",
"<span>Terug</span>",
deleteCurrent,
() => {
},
() => { },
'delete-image-confirm',
'delete-image-cancel');
@ -75,7 +102,6 @@ export class ImageCarousel extends UIElement {
this._isDeleted = new VariableUiElement(
mapping
)
// .HideOnEmpty(true);
this.searcher._deletedImages.addCallback(() => {
this.slideshow._currentSlide.ping();
@ -93,6 +119,18 @@ export class ImageCarousel extends UIElement {
"</span>";
}
IsKnown(): boolean {
return true;
}
IsQuestioning(): boolean {
return false;
}
Priority(): number {
return 0;
}
InnerUpdate(htmlElement: HTMLElement) {
super.InnerUpdate(htmlElement);
this._deleteButton.Update();

View file

@ -0,0 +1,71 @@
import {TagDependantUIElement, TagDependantUIElementConstructor} from "../../Customizations/UIElementConstructor";
import {ImageCarousel} from "./ImageCarousel";
import {OsmImageUploadHandler} from "../../Logic/OsmImageUploadHandler";
import {UIEventSource} from "../UIEventSource";
import {Changes} from "../../Logic/Changes";
import {UserDetails} from "../../Logic/OsmConnection";
import {ImageUploadFlow} from "../ImageUploadFlow";
export class ImageCarouselWithUploadConstructor implements TagDependantUIElementConstructor{
IsKnown(properties: any): boolean {
return true;
}
IsQuestioning(properties: any): boolean {
return false;
}
Priority(): number {
return 0;
}
construct(tags: UIEventSource<any>, changes: Changes): TagDependantUIElement {
return new ImageCarouselWithUpload(tags, changes);
}
}
class ImageCarouselWithUpload extends TagDependantUIElement {
private _imageElement: ImageCarousel;
private _pictureUploader: ImageUploadFlow;
constructor(tags: UIEventSource<any>, changes: Changes) {
super(tags);
this._imageElement = new ImageCarousel(tags, changes);
const userDetails = changes.login.userDetails;
const license = changes.login.GetPreference( "mapcomplete-pictures-license");
this._pictureUploader = new OsmImageUploadHandler(tags,
userDetails, license,
changes, this._imageElement.slideshow).getUI();
}
protected InnerRender(): string {
return this._imageElement.Render() +
this._pictureUploader.Render();
}
Activate() {
super.Activate();
this._imageElement.Activate();
this._pictureUploader.Activate();
}
Update() {
super.Update();
this._imageElement.Update();
this._pictureUploader.Update();
}
IsKnown(): boolean {
return true;
}
IsQuestioning(): boolean {
return false;
}
Priority(): number {
return 0;
}
}

View file

@ -63,6 +63,10 @@ export class SimpleAddUI extends UIElement {
protected InnerRender(): string {
const header = "<h2>Geen selectie</h2>" +
"Je klikte ergens waar er nog geen gezochte data is.<br/>";
if (!this._userDetails.data.loggedIn) {
return header + "<a class='activate-osm-authentication'>Gelieve je aan te melden om een nieuw punt toe te voegen</a>"
}
if (this._zoomlevel.data.zoom < 19) {
return header + "Zoom verder in om een element toe te voegen.";
}
@ -71,10 +75,6 @@ export class SimpleAddUI extends UIElement {
return header + "De data is nog aan het laden. Nog even geduld, dan kan je een punt toevoegen";
}
if (!this._userDetails.data.loggedIn) {
return header + "<a class='activate-osm-authentication'>Gelieve je aan te melden om een nieuw punt toe te voegen</a>"
}
var html = "";
for (const button of this._addButtons) {
html += button.Render();

View file

@ -0,0 +1,20 @@
Hallo,
Je maakte een bijdrage aan OpenStreetMap met BuurtNatuur! Proficiat en welkom bij de community.
Je antwoorden en toevoegingen gaan rechtsstreeks naar OpenStreetMap. OpenStreetMap is een kaart die werkt zoals Wikipedia:
het is vrij en gratis om er zelf data aan toe te voegen; in het geval van buurtnatuur.be informatie over natuur en bossen.
De data van OpenStreetMap is ook vrij en gratis te gebruiken door iedereen die dit wilt - applicaties zoals Maps.me, OsmAnd, Pokemon Go, Facebook, SnapChat, RouteYou, de Natuurpunt-app,... gebruiken OpenStreetMap. Maar ook toeristische diensten (zoals Westtoer) gebruiken steeds vaker OpenStreetMap.
Kortom, omdat je via buurtnatuur.be info gaf over natuur en bos, wordt dit zichtbaar op ál deze kaarten.
Heb je verdere vragen over OpenStreetMap? Wil je weten welke data we allemaal hebben en verzamelen?
- Je kan meer lezen op [onze wiki-website](https://wiki.openstreetmap.org/wiki/NL:Hoofdpagina).
- Je kan mij een berichtje terug sturen (dit kan door een email terug te sturen)
- Je kan terecht op [online chat](https://riot.im/app/#/room/#osmbe:matrix.org)
- Je kan naar onze [bijeenkomsten komen](https://www.meetup.com/OpenStreetMap-Belgium/)
Happy Mapping!
Pieter Vander Vennet
OpenStreetMap België

92
assets/ghost_bike.svg Normal file
View file

@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="2"
width="50"
height="50"
viewBox="0 0 50 50"
id="svg4"
sodipodi:docname="ghost_bike.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
<metadata
id="metadata10">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs8" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1001"
id="namedview6"
showgrid="false"
inkscape:zoom="4.72"
inkscape:cx="-40.53625"
inkscape:cy="33.532739"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="background"
sodipodi:insensitive="true">
<ellipse
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:50;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="path839"
cx="25"
cy="24.894068"
rx="25"
ry="25.105932" />
</g>
<g
inkscape:groupmode="layer"
id="layer1"
inkscape:label="bicycle">
<path
d="m 37.198846,28.51624 c -0.865699,0 -1.688316,0.177789 -2.435717,0.497127 l -2.103385,-3.60434 1.58506,-2.71676 c 0.215399,-0.370622 0.09027,-0.846552 -0.279675,-1.063319 -0.162747,-0.09437 -0.343955,-0.265316 -0.515591,-0.237279 l -0.134709,-0.158644 h -3.109265 c -0.428746,0 -0.776803,0.596963 -0.776803,1.02571 0,0.429429 0.348057,1.025708 0.776803,1.025708 h 2.015176 l -0.90741,1.367611 h -9.079572 l 1.813452,-3.419029 h 0.718681 c 0.428746,0 0.776802,-0.254375 0.776802,-0.683806 0,-0.42943 -0.348056,-0.683806 -0.776802,-0.683806 h -3.886069 c -0.428061,0 -0.776803,0.254376 -0.776803,0.683806 0,0.429431 0.348057,0.683806 0.776803,0.683806 h 1.366929 l -4.368835,7.634691 c -0.748084,-0.319337 -1.572071,-0.423959 -2.437085,-0.423959 -3.432704,0 -6.2157926,2.820014 -6.2157926,6.252718 0,3.433389 2.7830886,6.234257 6.2164776,6.234257 3.433388,0 6.216478,-2.774199 6.216478,-6.207588 0,-2.010389 -0.958697,-3.788967 -2.441187,-4.926136 l 1.663699,-2.848735 4.768861,8.17558 0.01847,0.02598 0.02051,0.03077 0.01572,0.02325 0.06154,0.07111 0.0076,0.0089 0.0212,0.01915 0.02188,0.01777 0.06017,0.04787 0.0253,0.01641 0.01983,0.01298 0.0082,0.0048 0.0027,0.0027 0.06564,0.03214 0.0076,0.0033 0.01573,0.0082 0.0054,0.0014 0.04992,0.01778 0.0465,0.01505 0.0095,0.0027 0.01709,0.0027 0.03008,0.0062 0.139496,0.01436 6.84e-4,0.04239 0.0027,0.04034 h 4.712106 c 0.384299,2.735223 2.9937,5.398646 6.16314,5.398646 3.433388,0 6.216478,-2.804288 6.216478,-6.237676 6.83e-4,-3.432704 -2.782407,-6.236991 -6.215794,-6.236991 z m -17.095143,6.216477 c 0,2.575896 -2.086974,4.662872 -4.662187,4.662872 -2.575213,0 -4.662188,-2.087659 -4.662188,-4.662872 0,-2.574528 2.086975,-4.661503 4.662188,-4.661503 0.579183,0 1.131698,0.110088 1.643185,0.30361 l -2.314683,3.96744 c -0.216082,0.369938 -0.09027,0.845868 0.280362,1.063319 0.123084,0.07111 0.257794,0.10531 0.390451,0.10531 0.267369,0 0.527215,-0.13813 0.672182,-0.384984 l 2.315367,-3.970175 c 1.022972,0.855441 1.675323,2.140312 1.675323,3.576987 z m 6.216477,-1.708146 -4.086422,-7.004905 h 8.173529 z m 5.438991,-5.938853 1.664383,2.918483 c -1.286923,0.987416 -2.17587,2.169715 -2.38785,4.221133 h -3.362956 z m 2.452811,4.338747 1.634979,2.800869 H 32.60709 c 0.187363,-1.367612 0.769965,-2.102702 1.604892,-2.800869 z m 2.986864,8.01352 c -2.309212,0 -4.222501,-1.793624 -4.592439,-3.84504 h 4.590387 l 0.0033,-0.04035 0.138811,-0.03624 0.03145,-0.01572 0.129239,-0.04513 0.01436,-0.01089 0.07727,-0.03966 0.01847,-0.01436 0.06907,-0.04992 0.05334,-0.04513 0.05197,-0.05675 0.0465,-0.05813 0.03898,-0.06292 0.03556,-0.06496 0.02667,-0.07111 0.0212,-0.07111 0.01164,-0.07111 0.0089,-0.08411 0.002,-0.02394 -0.0033,-0.04377 -0.0095,-0.0848 -0.01573,-0.07248 -0.02257,-0.07111 -0.03556,-0.07726 -0.01847,-0.04035 -2.314683,-3.96744 c 0.512856,-0.193517 1.064686,-0.30361 1.643186,-0.30361 2.575896,0 4.662871,2.108172 4.662871,4.682701 6.83e-4,2.575896 -2.086975,4.684753 -4.662871,4.684753 z"
id="path2"
style="fill:#ffffff;fill-opacity:1;stroke-width:0.6838057"
inkscape:connector-curvature="0" />
<g
id="g861"
transform="translate(-10.037321,-26.552854)">
<path
inkscape:connector-curvature="0"
id="path842"
d="M 25.018397,35.018924 V 47.003785"
style="fill:none;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
inkscape:connector-curvature="0"
id="path842-3"
d="M 28.86857,39.213625 H 20.748791"
style="fill:none;stroke:#ffffff;stroke-width:1.64621139;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
</g>
<g
inkscape:groupmode="layer"
id="layer3"
inkscape:label="cross" />
</svg>

After

Width:  |  Height:  |  Size: 5.7 KiB

View file

@ -573,7 +573,7 @@ form {
/***************** Info box (box containing features and questions ******************/
.leaflet-popup-content {
width: 25vw !important;
width: 40em !important;
}
.featureinfobox {
@ -669,7 +669,6 @@ form {
background-color: #e5f5ff;
padding: 1em;
border-radius: 1em;
margin-right: 1em;
font-size: larger;
}

View file

@ -48,15 +48,6 @@
<script src="./index.ts"></script>
<script src="vendor/Leaflet.AccuratePosition.js"></script>
<!-- 3 dagen eerste protoype -->
<!-- 19 juni: eerste feedbackronde, foto's -->
<!-- 23 juni: wikimedia foto's laden -->
<!-- 24 juni: foto's via imgur -->
<!-- 26 restylen infobox -->
<!-- 27 restylen infobox, flow UI verbeteren, mobile, locate-me -->
<!-- 28: user testing, fixing issues and faults, lots of small useless features, add button vs 2.0 -->
<script data-goatcounter="https://pietervdvn.goatcounter.com/count"
async src="//gc.zgo.at/count.js"></script>

View file

@ -21,6 +21,7 @@ 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";
@ -49,26 +50,47 @@ if (location.hostname === "localhost" || location.hostname === "127.0.0.1") {
// ----------------- SELECT THE RIGHT QUESTSET -----------------
let defaultQuest = "buurtnatuur"
let defaultLayout = "buurtnatuur"
// 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
for (const k in AllKnownLayouts.allSets) {
const layout = AllKnownLayouts.allSets[k];
const possibleParts = layout.locationContains ?? [];
for (const locationMatch of possibleParts) {
if (locationMatch === "") {
continue
}
if (window.location.href.toLowerCase().indexOf(locationMatch.toLowerCase()) >= 0) {
defaultLayout = layout.name;
}
}
}
// Read the query string to grap settings
let paramDict: any = {};
if (window.location.search) {
const params = window.location.search.substr(1).split("&");
const paramDict: any = {};
for (const param of params) {
var kv = param.split("=");
paramDict[kv[0]] = kv[1];
}
if (paramDict.quests) {
defaultQuest = paramDict.quests
}
if(paramDict.test){
dryRun = true;
}
}
const questSetToRender = AllKnownLayouts.allSets[defaultQuest];
console.log("Using quests: ", questSetToRender.name);
if (paramDict.layout) {
defaultLayout = paramDict.layout
}
document.title = questSetToRender.title;
if (paramDict.test) {
dryRun = true;
}
const layoutToUse = AllKnownLayouts.allSets[defaultLayout];
console.log("Using layout: ", layoutToUse.name);
document.title = layoutToUse.title;
// ----------------- Setup a few event sources -------------
@ -84,12 +106,11 @@ const leftMessage = new UIEventSource<() => UIElement>(undefined);
const selectedElement = new UIEventSource<any>(undefined);
const preferedPictureLicense = new UIEventSource<string>(undefined);
const locationControl = new UIEventSource<{ lat: number, lon: number, zoom: number }>({
zoom: questSetToRender.startzoom,
lat: questSetToRender.startLat,
lon: questSetToRender.startLon
zoom: layoutToUse.startzoom,
lat: layoutToUse.startLat,
lon: layoutToUse.startLon
});
@ -99,7 +120,7 @@ const saveTimeout = 30000; // After this many milliseconds without changes, save
const allElements = new ElementStorage();
const osmConnection = new OsmConnection(dryRun);
const changes = new Changes(
"Beantwoorden van vragen met #MapComplete voor vragenset #" + questSetToRender.name,
"Beantwoorden van vragen met #MapComplete voor vragenset #" + layoutToUse.name,
osmConnection, allElements);
const bm = new Basemap("leafletDiv", locationControl, new VariableUiElement(
locationControl.map((location) => {
@ -119,21 +140,6 @@ const bm = new Basemap("leafletDiv", locationControl, new VariableUiElement(
));
// ------------- Tie together user settings and UI -----------
const picturesPrefName = "mapcomplete-pictures-license";
preferedPictureLicense.addCallback((license) => {
osmConnection.SetPreference(picturesPrefName, license);
});
osmConnection.preferences.addCallback((prefs) => {
if (prefs[picturesPrefName] !== undefined) {
preferedPictureLicense.setData(prefs[picturesPrefName]);
}
})
// ------------- Setup the layers -------------------------------
const addButtons: {
@ -148,7 +154,7 @@ const flayers: FilteredLayer[] = []
let minZoom = 0;
for (const layer of questSetToRender.layers) {
for (const layer of layoutToUse.layers) {
const generateInfo = (tagsES) => {
@ -157,15 +163,13 @@ for (const layer of questSetToRender.layers) {
layer.title,
layer.elementsToShow,
changes,
osmConnection.userDetails,
preferedPictureLicense
osmConnection.userDetails
)
};
minZoom = Math.max(minZoom, layer.minzoom);
const flayer = layer.asLayer(bm, allElements, changes, osmConnection.userDetails, selectedElement,
generateInfo);
const flayer = layer.asLayer(bm, allElements, changes, osmConnection.userDetails, selectedElement, generateInfo);
const addButton = {
name: layer.name,
@ -199,7 +203,7 @@ new StrayClickHandler(bm, selectedElement, leftMessage, () => {
*/
selectedElement.addCallback((data) => {
// Which is the applicable set?
for (const layer of questSetToRender.layers) {
for (const layer of layoutToUse.layers) {
const applicable = layer.overpassFilter.matches(TagUtils.proprtiesToKV(data));
if (applicable) {
@ -210,8 +214,7 @@ selectedElement.addCallback((data) => {
layer.title,
layer.elementsToShow,
changes,
osmConnection.userDetails,
preferedPictureLicense
osmConnection.userDetails
));
break;
}
@ -234,12 +237,12 @@ new CollapseButton("messagesbox")
var welcomeMessage = () => {
return new VariableUiElement(
osmConnection.userDetails.map((userdetails) => {
var login = questSetToRender.gettingStartedPlzLogin;
var login = layoutToUse.gettingStartedPlzLogin;
if (userdetails.loggedIn) {
login = questSetToRender.welcomeBackMessage;
login = layoutToUse.welcomeBackMessage;
}
return "<div id='welcomeMessage'>" +
questSetToRender.welcomeMessage + login + questSetToRender.welcomeTail+
layoutToUse.welcomeMessage + login + layoutToUse.welcomeTail +
"</div>";
}),
function () {

View file

@ -6,4 +6,12 @@ import {WikipediaLink} from "./Customizations/Questions/WikipediaLink";
import {OsmLink} from "./Customizations/Questions/OsmLink";
import {ConfirmDialog} from "./UI/ConfirmDialog";
import {Imgur} from "./Logic/Imgur";
import {VariableUiElement} from "./UI/Base/VariableUIElement";
const html = new UIEventSource<string>("Some text");
const uielement = new VariableUiElement(html);
uielement.AttachTo("maindiv")
window.setTimeout(() => {html.setData("Different text")}, 1000)