Better bookcase quests

This commit is contained in:
Pieter Vander Vennet 2020-07-12 23:19:05 +02:00
parent 1ef2459d54
commit 49cab66a72
11 changed files with 247 additions and 63 deletions

View file

@ -4,16 +4,19 @@ import {GRB} from "./Layouts/GRB";
import {Statues} from "./Layouts/Statues"; import {Statues} from "./Layouts/Statues";
import {Bookcases} from "./Layouts/Bookcases"; import {Bookcases} from "./Layouts/Bookcases";
import Cyclofix from "./Layouts/Cyclofix"; import Cyclofix from "./Layouts/Cyclofix";
import {All} from "./Layouts/All";
export class AllKnownLayouts { export class AllKnownLayouts {
public static allSets: any = AllKnownLayouts.AllLayouts(); public static allSets: any = AllKnownLayouts.AllLayouts();
private static AllLayouts() { private static AllLayouts() {
const all = new All();
const layouts = [ const layouts = [
new Groen(), new Groen(),
new GRB(), new GRB(),
new Cyclofix(), new Cyclofix(),
new Bookcases() new Bookcases(),
all
/*new Toilets(), /*new Toilets(),
new Statues(), new Statues(),
*/ */
@ -21,6 +24,7 @@ export class AllKnownLayouts {
const allSets = {}; const allSets = {};
for (const layout of layouts) { for (const layout of layouts) {
allSets[layout.name] = layout; allSets[layout.name] = layout;
all.layers = all.layers.concat(layout.layers);
} }
return allSets; return allSets;
} }

View file

@ -7,6 +7,7 @@ import {FilteredLayer} from "../Logic/FilteredLayer";
import {Changes} from "../Logic/Changes"; import {Changes} from "../Logic/Changes";
import {UserDetails} from "../Logic/OsmConnection"; import {UserDetails} from "../Logic/OsmConnection";
import {TagRenderingOptions} from "./TagRendering"; import {TagRenderingOptions} from "./TagRendering";
import {TagDependantUIElementConstructor} from "./UIElementConstructor";
export class LayerDefinition { export class LayerDefinition {
@ -27,7 +28,7 @@ export class LayerDefinition {
/** /**
* These are the questions/shown attributes in the popup * These are the questions/shown attributes in the popup
*/ */
elementsToShow: TagRenderingOptions[]; elementsToShow: TagDependantUIElementConstructor[];
style: (tags: any) => { color: string, icon: any }; style: (tags: any) => { color: string, icon: any };

View file

@ -1,6 +1,6 @@
import {LayerDefinition} from "../LayerDefinition"; import {LayerDefinition} from "../LayerDefinition";
import L from "leaflet"; import L from "leaflet";
import {Tag} from "../../Logic/TagsFilter"; import {And, Regex, Tag} from "../../Logic/TagsFilter";
import {QuestionDefinition} from "../../Logic/Question"; import {QuestionDefinition} from "../../Logic/Question";
import {TagRenderingOptions} from "../TagRendering"; import {TagRenderingOptions} from "../TagRendering";
import {NameInline} from "../Questions/NameInline"; import {NameInline} from "../Questions/NameInline";
@ -21,7 +21,6 @@ export class Bookcases extends LayerDefinition {
this.elementsToShow = [ this.elementsToShow = [
new TagRenderingOptions({ new TagRenderingOptions({
priority: 13,
question: "Heeft dit boekenruilkastje een naam?", question: "Heeft dit boekenruilkastje een naam?",
freeform: { freeform: {
key: "name", key: "name",
@ -44,66 +43,114 @@ export class Bookcases extends LayerDefinition {
key: "capacity", key: "capacity",
placeholder: "aantal" placeholder: "aantal"
}, },
priority: 15
} }
), ),
new TagRenderingOptions({ new TagRenderingOptions({
question: "Wat voor soort boeken heeft dit boekenruilkastje?", question: "Wat voor soort boeken heeft dit boekenruilkastje?",
mappings:[ mappings: [
{k: new Tag("books","children"), txt: "Voornamelijk kinderboeken"}, {k: new Tag("books", "children"), txt: "Voornamelijk kinderboeken"},
{k: new Tag("books","adults"), txt: "Voornamelijk boeken voor volwassenen"}, {k: new Tag("books", "adults"), txt: "Voornamelijk boeken voor volwassenen"},
{k: new Tag("books","children;adults"), txt: "Zowel kinderboeken als boeken voor volwassenen"} {k: new Tag("books", "children;adults"), txt: "Zowel kinderboeken als boeken voor volwassenen"}
], ],
priority: 14
}), }),
new TagRenderingOptions({ new TagRenderingOptions({
question: "", question: "Staat dit boekenruilkastje binnen of buiten?",
freeform:{ mappings: [
{k: new Tag("indoor", "yes"), txt: "Dit boekenruilkastje staat binnen"},
{k: new Tag("indoor", "no"), txt: "Dit boekenruilkastje staat buiten"},
{k: new Tag("indoor", ""), txt: "Dit boekenruilkastje staat buiten"}
]
}),
new TagRenderingOptions({
question: "Is dit boekenruilkastje vrij toegankelijk?",
mappings: [
{k: new Tag("access", "yes"), txt: "Ja, vrij toegankelijk"},
{k: new Tag("access", "customers"), txt: "Enkel voor klanten"},
]
}).OnlyShowIf(new Tag("indoor", "yes")),
new TagRenderingOptions({
question: "Wie (welke organisatie) beheert dit boekenruilkastje?",
freeform: {
key: "opeartor",
renderTemplate: "Dit boekenruilkastje wordt beheerd door {operator}",
template: "Dit boekenruilkastje wordt beheerd door $$$"
}
}),
new TagRenderingOptions({
question: "Zijn er openingsuren voor dit boekenruilkastje?",
mappings: [
{k: new Tag("opening_hours", "24/7"), txt: "Dag en nacht toegankelijk"},
{k: new Tag("opening_hours", ""), txt: "Dag en nacht toegankelijk"},
{k: new Tag("opening_hours", "sunrise-sunset"), txt: "Van zonsopgang tot zonsondergang"},
],
freeform: {
key: "opening_hours",
renderTemplate: "De openingsuren zijn {opening_hours}",
template: "De openingsuren zijn $$$"
}
}),
new TagRenderingOptions({
question: "Is dit boekenruilkastje deel van een netwerk?",
freeform: {
key: "brand",
renderTemplate: "Deel van het netwerk {brand}",
template: "Deel van het netwerk $$$"
},
mappings: [{
k: new And([new Tag("brand", "Little Free Library"), new Tag("nobrand", "")]),
txt: "Little Free Library"
},
{
k: new And([new Tag("brand", ""), new Tag("nobrand", "yes")]),
txt: "Maakt geen deel uit van een groter netwerk"
}]
}).OnlyShowIf(new And(
[new Tag("brand", "!(Little Free Library)"),
new Tag("ref", "")])),
new TagRenderingOptions({
question: "Wat is het LFL-referentienummer van dit boekenruilkastje?",
freeform: {
key: "ref",
template: "Het refernetienummer is $$$",
renderTemplate: "Gekend als Little Free Library <b>{ref}</b>"
}
}).OnlyShowIf(new Tag("brand", "Little Free Library")),
new TagRenderingOptions({
question: "Wanneer werd dit boekenruilkastje geinstalleerd?",
priority: -1,
freeform: {
key: "start_date", key: "start_date",
renderTemplate: "Geplaatst op {start_date}", renderTemplate: "Geplaatst op {start_date}",
template: "Geplaatst op $$$" template: "Geplaatst op $$$"
} }
}), }),
new TagRenderingOptions({ new TagRenderingOptions({
question: "Is er een website waar we er meer informatie is over dit boekenruilkastje?", question: "Is er een website waar we er meer informatie is over dit boekenruilkastje?",
freeform:{ freeform: {
key:"website", key: "website",
renderTemplate: "<a href='{website}' target='_blank'>Meer informatie over dit boekenruilkastje</a>", renderTemplate: "<a href='{website}' target='_blank'>Meer informatie over dit boekenruilkastje</a>",
template: "$$$", template: "$$$",
placeholder:"website" placeholder: "website"
}
},
priority: 5
}), }),
new TagRenderingOptions({
freeform: {
key: "description",
renderTemplate: "<b>Beschrijving door de uitbater</b><br>{description}",
template: "$$$",
}
})
]; ];
/*
this.elementsToShow = [
new TagMappingOptions({key: "operator", template: "Onder de hoede van {operator}"}),
new TagMappingOptions({key: "brand", template: "Deel van het netwerk {brand}"}),
new TagMappingOptions({key: "ref", template: "Referentienummer {ref}"}),
new TagMappingOptions({key: "description", template: "Extra beschrijving: <br /> <p>{description}</p>"}),
]
;*/
/* this.questions = [
QuestionDefinition.textQuestion("Heeft dit boekenkastje een peter, meter of voogd?", "operator", 10),
// QuestionDefinition.textQuestion("Wie kunnen we (per email) contacteren voor dit boekenruilkastje?", "email", 5),
]
;
*/
this.style = function (tags) { this.style = function (tags) {
return { return {

View file

@ -0,0 +1,18 @@
import {Layout} from "../Layout";
export class All extends Layout{
constructor() {
super(
"all",
"All quest layers",
[],
15,
51.2,
3.2,
"<h3>All quests of MapComplete</h3>" +
"This is a mixed bag. Some quests might be hard or for experts to answer only",
"Please log in",
""
);
}
}

View file

@ -0,0 +1,79 @@
/**
* Wrapper around another TagDependandElement, which only shows if the filters match
*/
import {TagDependantUIElement, TagDependantUIElementConstructor} from "./UIElementConstructor";
import {TagsFilter, TagUtils} from "../Logic/TagsFilter";
import {UIElement} from "../UI/UIElement";
import {UIEventSource} from "../UI/UIEventSource";
import {Changes} from "../Logic/Changes";
export class OnlyShowIfConstructor implements TagDependantUIElementConstructor{
private _tagsFilter: TagsFilter;
private _embedded: TagDependantUIElementConstructor;
constructor(tagsFilter : TagsFilter, embedded: TagDependantUIElementConstructor) {
this._tagsFilter = tagsFilter;
this._embedded = embedded;
}
construct(tags: UIEventSource<any>, changes: Changes): TagDependantUIElement {
return new OnlyShowIf(tags,
this._embedded.construct(tags, changes),
this._tagsFilter);
}
}
class OnlyShowIf extends UIElement implements TagDependantUIElement {
private _embedded: TagDependantUIElement;
private _filter: TagsFilter;
constructor(
tags: UIEventSource<any>,
embedded: TagDependantUIElement, filter: TagsFilter) {
super(tags);
this._filter = filter;
this._embedded = embedded;
}
private Matches() : boolean{
return this._filter.matches(TagUtils.proprtiesToKV(this._source.data));
}
protected InnerRender(): string {
if (this.Matches()) {
return this._embedded.Render();
} else {
return "";
}
}
Priority(): number {
return this._embedded.Priority();
}
IsKnown(): boolean {
if(!this.Matches()){
return false;
}
return this._embedded.IsKnown();
}
IsQuestioning(): boolean {
if(!this.Matches()){
return false;
}
return this._embedded.IsQuestioning();
}
Activate(): void {
this._embedded.Activate();
}
Update(): void {
this._embedded.Update();
}
}

View file

@ -1,4 +1,4 @@
import {TagRendering, TagRenderingOptions} from "../TagRendering"; import {TagRenderingOptions} from "../TagRendering";
import {UIEventSource} from "../../UI/UIEventSource"; import {UIEventSource} from "../../UI/UIEventSource";
import {Changes} from "../../Logic/Changes"; import {Changes} from "../../Logic/Changes";
import {And, Tag} from "../../Logic/TagsFilter"; import {And, Tag} from "../../Logic/TagsFilter";

View file

@ -9,8 +9,10 @@ import {TextField} from "../UI/Base/TextField";
import {UIInputElement} from "../UI/Base/UIInputElement"; import {UIInputElement} from "../UI/Base/UIInputElement";
import {UIRadioButtonWithOther} from "../UI/Base/UIRadioButtonWithOther"; import {UIRadioButtonWithOther} from "../UI/Base/UIRadioButtonWithOther";
import {VariableUiElement} from "../UI/Base/VariableUIElement"; import {VariableUiElement} from "../UI/Base/VariableUIElement";
import {TagDependantUIElement, TagDependantUIElementConstructor} from "./UIElementConstructor";
import {OnlyShowIfConstructor} from "./OnlyShowIf";
export class TagRenderingOptions { export class TagRenderingOptions implements TagDependantUIElementConstructor {
/** /**
* Notes: by not giving a 'question', one disables the question form alltogether * Notes: by not giving a 'question', one disables the question form alltogether
@ -66,6 +68,10 @@ export class TagRenderingOptions {
}) { }) {
this.options = options; this.options = options;
} }
OnlyShowIf(tagsFilter: TagsFilter) : TagDependantUIElementConstructor{
return new OnlyShowIfConstructor(tagsFilter, this);
}
IsQuestioning(tags: any): boolean { IsQuestioning(tags: any): boolean {
@ -87,13 +93,20 @@ export class TagRenderingOptions {
} }
construct(tags: UIEventSource<any>, changes: Changes): TagDependantUIElement {
return new TagRendering(tags, changes, this.options);
}
} }
export class TagRendering extends UIElement { class TagRendering extends UIElement implements TagDependantUIElement {
public elementPriority: number; private _priority: number;
Priority(): number {
return this._priority;
}
private _question: string; private _question: string;
private _primer: string; private _primer: string;
@ -142,12 +155,12 @@ export class TagRendering extends UIElement {
this.ListenTo(this._editMode); this.ListenTo(this._editMode);
this._question = options.question; this._question = options.question;
this._priority = options.priority ?? 0;
this._primer = options.primer ?? ""; this._primer = options.primer ?? "";
this._tagsPreprocessor = options.tagsPreprocessor; this._tagsPreprocessor = options.tagsPreprocessor;
this._mapping = []; this._mapping = [];
this._renderMapping = []; this._renderMapping = [];
this._freeform = options.freeform; this._freeform = options.freeform;
this.elementPriority = options.priority ?? 0;
// Prepare the choices for the Radio buttons // Prepare the choices for the Radio buttons
const choices: UIElement[] = []; const choices: UIElement[] = [];

View file

@ -0,0 +1,20 @@
import {UIEventSource} from "../UI/UIEventSource";
import {Changes} from "../Logic/Changes";
import {UIElement} from "../UI/UIElement";
export interface TagDependantUIElementConstructor {
construct(tags: UIEventSource<any>, changes: Changes): TagDependantUIElement;
}
export abstract class TagDependantUIElement extends UIElement {
abstract IsKnown(): boolean;
abstract IsQuestioning(): boolean;
abstract Priority() : number;
}

View file

@ -22,8 +22,10 @@ export class Regex implements TagsFilter {
// Any is allowed // Any is allowed
return true; return true;
} }
return tag.v.match(this._r).length > 0; const matchCount =tag.v.match(this._r)?.length;
return (matchCount ?? 0) > 0;
} }
} }
return false; return false;

View file

@ -6,10 +6,11 @@ import {ImageCarousel} from "./Image/ImageCarousel";
import {Changes} from "../Logic/Changes"; import {Changes} from "../Logic/Changes";
import {UserDetails} from "../Logic/OsmConnection"; import {UserDetails} from "../Logic/OsmConnection";
import {VerticalCombine} from "./Base/VerticalCombine"; import {VerticalCombine} from "./Base/VerticalCombine";
import {TagRendering, TagRenderingOptions} from "../Customizations/TagRendering"; import {TagRenderingOptions} from "../Customizations/TagRendering";
import {OsmLink} from "../Customizations/Questions/OsmLink"; import {OsmLink} from "../Customizations/Questions/OsmLink";
import {WikipediaLink} from "../Customizations/Questions/WikipediaLink"; import {WikipediaLink} from "../Customizations/Questions/WikipediaLink";
import {And} from "../Logic/TagsFilter"; import {And} from "../Logic/TagsFilter";
import {TagDependantUIElement} from "../Customizations/UIElementConstructor";
export class FeatureInfoBox extends UIElement { export class FeatureInfoBox extends UIElement {
@ -27,7 +28,7 @@ export class FeatureInfoBox extends UIElement {
private _imageElement: ImageCarousel; private _imageElement: ImageCarousel;
private _pictureUploader: UIElement; private _pictureUploader: UIElement;
private _wikipedialink: UIElement; private _wikipedialink: UIElement;
private _infoboxes: TagRendering[]; private _infoboxes: TagDependantUIElement[];
constructor( constructor(
@ -48,10 +49,8 @@ export class FeatureInfoBox extends UIElement {
this._infoboxes = []; this._infoboxes = [];
for (const tagRenderingOption of elementsToShow) { for (const tagRenderingOption of elementsToShow) {
if (tagRenderingOption.options === undefined) { this._infoboxes.push(
throw "Tagrendering.options not defined" tagRenderingOption.construct(this._tagsES, this._changes));
}
this._infoboxes.push(new TagRendering(this._tagsES, this._changes, tagRenderingOption.options))
} }
title = title ?? new TagRenderingOptions( title = title ?? new TagRenderingOptions(
@ -60,10 +59,10 @@ export class FeatureInfoBox extends UIElement {
} }
) )
this._title = new TagRendering(this._tagsES, this._changes, title.options); this._title = new TagRenderingOptions(title.options).construct(this._tagsES, this._changes);
this._osmLink = new TagRendering(this._tagsES, this._changes, new OsmLink().options); this._osmLink =new OsmLink().construct(this._tagsES, this._changes);
this._wikipedialink = new TagRendering(this._tagsES, this._changes, new WikipediaLink().options); this._wikipedialink = new WikipediaLink().construct(this._tagsES, this._changes);
this._pictureUploader = new OsmImageUploadHandler(tagsES, userDetails, preferedPictureLicense, this._pictureUploader = new OsmImageUploadHandler(tagsES, userDetails, preferedPictureLicense,
changes, this._imageElement.slideshow).getUI(); changes, this._imageElement.slideshow).getUI();
@ -73,7 +72,7 @@ export class FeatureInfoBox extends UIElement {
const info = []; const info = [];
const questions = []; const questions : TagDependantUIElement[] = [];
for (const infobox of this._infoboxes) { for (const infobox of this._infoboxes) {
if (infobox.IsKnown()) { if (infobox.IsKnown()) {
@ -92,9 +91,9 @@ export class FeatureInfoBox extends UIElement {
let score = -1000; let score = -1000;
for (const question of questions) { for (const question of questions) {
if (mostImportantQuestion === undefined || question.priority > score) { if (mostImportantQuestion === undefined || question.Priority() > score) {
mostImportantQuestion = question; mostImportantQuestion = question;
score = question.priority; score = question.Priority();
} }
} }

View file

@ -27,6 +27,7 @@ import {AllKnownLayouts} from "./Customizations/AllKnownLayouts";
// --------------------- Read the URL parameters ----------------- // --------------------- Read the URL parameters -----------------
// @ts-ignore
if(location.href.startsWith("http://buurtnatuur.be")){ if(location.href.startsWith("http://buurtnatuur.be")){
// Reload the https version. This is important for the 'locate me' button // Reload the https version. This is important for the 'locate me' button
window.location.replace("https://buurtnatuur.be"); window.location.replace("https://buurtnatuur.be");