Merge branches

This commit is contained in:
Pieter Vander Vennet 2020-07-22 11:05:04 +02:00
commit 00fb99defe
117 changed files with 3104 additions and 1424 deletions

View file

@ -14,9 +14,9 @@ import {NatureReserves} from "./Layers/NatureReserves";
import {Natuurpunt} from "./Layouts/Natuurpunt";
export class AllKnownLayouts {
public static allSets: any = AllKnownLayouts.AllLayouts();
public static allSets = AllKnownLayouts.AllLayouts();
private static AllLayouts(): any {
private static AllLayouts(): Map<string, Layout> {
const all = new All();
const layouts: Layout[] = [
new Groen(),
@ -32,7 +32,7 @@ export class AllKnownLayouts {
new Statues(),
*/
];
const allSets = {};
const allSets: Map<string, Layout> = new Map();
for (const layout of layouts) {
allSets[layout.name] = layout;
all.layers = all.layers.concat(layout.layers);

View file

@ -15,7 +15,7 @@ export class LayerDefinition {
/**
* This name is shown in the 'add XXX button'
*/
name: string;
name: string | UIElement;
/**
* 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"
@ -72,7 +72,15 @@ export class LayerDefinition {
*/
maxAllowedOverlapPercentage: number = undefined;
/**
* If true, then ways (and polygons) will be converted to a 'point' at the center instead before further processing
*/
wayHandling: number = 0;
static WAYHANDLING_DEFAULT = 0;
static WAYHANDLING_CENTER_ONLY = 1;
static WAYHANDLING_CENTER_AND_WAY = 2;
constructor(options: {
name: string,
newElementTags: Tag[],
@ -82,13 +90,13 @@ export class LayerDefinition {
title?: TagRenderingOptions,
elementsToShow?: TagDependantUIElementConstructor[],
maxAllowedOverlapPercentage?: number,
wayHandling?: number,
style?: (tags: any) => {
color: string,
icon: any
}
} = undefined) {
if (options === undefined) {
console.log("No options!")
return;
}
this.name = options.name;
@ -100,11 +108,12 @@ export class LayerDefinition {
this.title = options.title;
this.elementsToShow = options.elementsToShow;
this.style = options.style;
console.log(this)
this.wayHandling = options.wayHandling ?? LayerDefinition.WAYHANDLING_DEFAULT;
}
asLayer(basemap: Basemap, allElements: ElementStorage, changes: Changes, userDetails: UIEventSource<UserDetails>, selectedElement: UIEventSource<any>,
showOnPopup: (tags: UIEventSource<(any)>) => UIElement):
asLayer(basemap: Basemap, allElements: ElementStorage, changes: Changes, userDetails: UIEventSource<UserDetails>,
selectedElement: UIEventSource<{feature: any}>,
showOnPopup: (tags: UIEventSource<(any)>, feature: any) => UIElement):
FilteredLayer {
return new FilteredLayer(
this,

View file

@ -5,12 +5,15 @@ import * as L from "leaflet";
import FixedText from "../Questions/FixedText";
import ParkingType from "../Questions/bike/ParkingType";
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
import BikeStationOperator from "../Questions/bike/StationOperator";
import Translations from "../../UI/i18n/Translations";
import ParkingOperator from "../Questions/bike/ParkingOperator";
export default class BikeParkings extends LayerDefinition {
constructor() {
super();
this.name = "bike_parking";
this.name = Translations.t.cyclofix.parking.name;
this.icon = "./assets/bike/parking.svg";
this.overpassFilter = new Tag("amenity", "bicycle_parking");
this.newElementTags = [
@ -20,12 +23,13 @@ export default class BikeParkings extends LayerDefinition {
this.minzoom = 13;
this.style = this.generateStyleFunction();
this.title = new FixedText("Fietsparking");
this.title = new FixedText(Translations.t.cyclofix.parking.title)
this.elementsToShow = [
new ImageCarouselWithUploadConstructor(),
new OperatorTag(),
//new ParkingOperator(),
new ParkingType()
];
this.wayHandling = LayerDefinition.WAYHANDLING_CENTER_AND_WAY;
}
@ -36,7 +40,8 @@ export default class BikeParkings extends LayerDefinition {
color: "#00bb00",
icon: L.icon({
iconUrl: self.icon,
iconSize: [50, 50]
iconSize: [50, 50],
iconAnchor: [25,50]
})
};
};

View file

@ -0,0 +1,82 @@
import { LayerDefinition } from "../LayerDefinition";
import Translations from "../../UI/i18n/Translations";
import {And, Tag} from "../../Logic/TagsFilter";
import FixedText from "../Questions/FixedText";
import { ImageCarouselWithUploadConstructor } from "../../UI/Image/ImageCarouselWithUpload";
import * as L from "leaflet";
import ShopRetail from "../Questions/bike/ShopRetail";
import ShopPump from "../Questions/bike/ShopPump";
import ShopRental from "../Questions/bike/ShopRental";
import ShopRepair from "../Questions/bike/ShopRepair";
import ShopDiy from "../Questions/bike/ShopDiy";
import ShopName from "../Questions/bike/ShopName";
import ShopSecondHand from "../Questions/bike/ShopSecondHand";
import { TagRenderingOptions } from "../TagRendering";
export default class BikeShops extends LayerDefinition {
private readonly sellsBikes = new Tag("service:bicycle:retail", "yes")
private readonly repairsBikes = new Tag("service:bicycle:repair", "yes")
constructor() {
super();
this.name = Translations.t.cyclofix.shop.name
this.icon = "./assets/bike/repair_shop.svg"
this.overpassFilter = new Tag("shop", "bicycle");
this.newElementTags = [
new Tag("shop", "bicycle"),
]
this.maxAllowedOverlapPercentage = 10
this.wayHandling = LayerDefinition.WAYHANDLING_CENTER_AND_WAY
this.minzoom = 13;
this.style = this.generateStyleFunction();
this.title = new TagRenderingOptions({
mappings: [
{k: new And([new Tag("name", "*"), this.sellsBikes]), txt: Translations.t.cyclofix.shop.titleShopNamed},
{
k: new And([new Tag("name", "*"), new Tag("service:bicycle:retail", "")]),
txt: Translations.t.cyclofix.shop.titleShop
},
{
k: new And([new Tag("name", "*"), new Tag("service:bicycle:retail", "no")]),
txt: Translations.t.cyclofix.shop.titleRepairNamed
},
{k: this.sellsBikes, txt: Translations.t.cyclofix.shop.titleShop},
{k: new Tag("service:bicycle:retail", " "), txt: Translations.t.cyclofix.shop.title},
{k: new Tag("service:bicycle:retail", "no"), txt: Translations.t.cyclofix.shop.titleRepair},
]
})
this.elementsToShow = [
new ImageCarouselWithUploadConstructor(),
new ShopName(),
new ShopRetail(),
new ShopRental(),
new ShopRepair(),
new ShopPump(),
new ShopDiy(),
new ShopSecondHand()
]
}
private generateStyleFunction() {
const self = this;
return function (tags: any) {
let icon = "assets/bike/repair_shop.svg";
if (self.sellsBikes.matchesProperties(tags)) {
icon = "assets/bike/shop.svg";
}
return {
color: "#00bb00",
icon: L.icon({
iconUrl: self.icon,
iconSize: [50, 50],
iconAnchor: [25, 50]
})
}
}
}
}

View file

@ -12,6 +12,7 @@ import PumpManometer from "../Questions/bike/PumpManometer";
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
import PumpOperational from "../Questions/bike/PumpOperational";
import PumpValves from "../Questions/bike/PumpValves";
import Translations from "../../UI/i18n/Translations";
export default class BikeStations extends LayerDefinition {
@ -22,7 +23,7 @@ export default class BikeStations extends LayerDefinition {
constructor() {
super();
this.name = "bike station or pump";
this.name = Translations.t.cyclofix.station.name;
this.icon = "./assets/wrench.svg";
this.overpassFilter = new And([
@ -36,7 +37,8 @@ export default class BikeStations extends LayerDefinition {
this.minzoom = 13;
this.style = this.generateStyleFunction();
this.title = new FixedText("Bike station");
this.title = new FixedText(Translations.t.cyclofix.station.title)
this.wayHandling = LayerDefinition.WAYHANDLING_CENTER_AND_WAY
this.elementsToShow = [
new ImageCarouselWithUploadConstructor(),
@ -50,7 +52,7 @@ export default class BikeStations extends LayerDefinition {
new PumpValves().OnlyShowIf(this.pump),
new PumpOperational().OnlyShowIf(this.pump),
new BikeStationOperator(),
// new BikeStationOperator(),
// new BikeStationBrand() DISABLED
];
}
@ -61,26 +63,26 @@ export default class BikeStations extends LayerDefinition {
const hasPump = self.pump.matchesProperties(properties)
const isOperational = self.pumpOperationalOk.matchesProperties(properties)
const hasTools = self.tools.matchesProperties(properties)
let iconName = ""
if (hasPump) {
if (hasTools) {
iconName = "repair_station_pump.svg"
let iconName = "repair_station.svg";
if (hasTools && hasPump && isOperational) {
iconName = "repair_station_pump.svg"
}else if(hasTools){
iconName = "repair_station.svg"
}else if(hasPump){
if (isOperational) {
iconName = "pump.svg"
} else {
if (isOperational) {
iconName = "pump.svg"
} else {
iconName = "pump_broken.svg"
}
iconName = "broken_pump.svg"
}
} else {
iconName = "repair_station.svg"
}
const iconUrl = `./assets/bike/${iconName}`
return {
color: "#00bb00",
icon: L.icon({
iconUrl: iconUrl,
iconSize: [50, 50]
iconSize: [50, 50],
iconAnchor: [25,50]
})
};
};

View file

@ -1,10 +1,8 @@
import {LayerDefinition} from "../LayerDefinition";
import L from "leaflet";
import {And, Or, Regex, Tag} from "../../Logic/TagsFilter";
import {QuestionDefinition} from "../../Logic/Question";
import {And, Or, Tag} from "../../Logic/TagsFilter";
import {TagRenderingOptions} from "../TagRendering";
import {NameInline} from "../Questions/NameInline";
import {NameQuestion} from "../Questions/NameQuestion";
import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload";
export class Bookcases extends LayerDefinition {
@ -121,7 +119,10 @@ export class Bookcases extends LayerDefinition {
key: "ref",
template: "Het referentienummer is $$$",
renderTemplate: "Gekend als {brand} <b>{ref}</b>"
}
},
mappings: [
{k: new And([new Tag("brand",""), new Tag("nobrand","yes"), new Tag("ref", "")]), txt: "Maakt geen deel uit van een groter netwerk"}
]
}).OnlyShowIf(new Tag("brand","*")),
new TagRenderingOptions({

View file

@ -10,8 +10,8 @@ export class DrinkingWater extends LayerDefinition {
constructor() {
super();
this.name = "drinking_water";
this.icon = "./assets/bug.svg";
this.name = "drinking water";
this.icon = "./assets/bike/drinking_water.svg";
this.overpassFilter = new Or([
new And([
@ -24,6 +24,7 @@ export class DrinkingWater extends LayerDefinition {
new Tag("amenity", "drinking_water"),
];
this.maxAllowedOverlapPercentage = 10;
this.wayHandling = LayerDefinition.WAYHANDLING_CENTER_AND_WAY
this.minzoom = 13;
this.style = this.generateStyleFunction();
@ -52,7 +53,8 @@ export class DrinkingWater extends LayerDefinition {
color: "#00bb00",
icon: new L.icon({
iconUrl: self.icon,
iconSize: [40, 40]
iconSize: [50, 50],
iconAnchor: [25,50]
})
};
};

View file

@ -25,6 +25,13 @@ export class NatureReserves extends LayerDefinition {
this.style = this.generateStyleFunction();
this.elementsToShow = [
new ImageCarouselWithUploadConstructor(),
new TagRenderingOptions({
freeform: {
key: "_surface",
renderTemplate: "{_surface}m²",
template: "$$$"
}
}),
new NameQuestion(),
new AccessTag(),
new OperatorTag(),

View file

@ -1,20 +1,30 @@
import {LayerDefinition} from "./LayerDefinition";
import {UIElement} from "../UI/UIElement";
import {FixedUiElement} from "../UI/Base/FixedUiElement";
import Translation from "../UI/i18n/Translation";
import Translations from "../UI/i18n/Translations";
import Locale from "../UI/i18n/Locale";
import {VariableUiElement} from "../UI/Base/VariableUIElement";
import {OsmConnection, UserDetails} from "../Logic/OsmConnection";
import {UIEventSource} from "../UI/UIEventSource";
/**
* A layout is a collection of settings of the global view (thus: welcome text, title, selection of layers).
*/
export class Layout {
public name: string;
public title: string;
public title: UIElement;
public layers: LayerDefinition[];
public welcomeMessage: string;
public gettingStartedPlzLogin: string;
public welcomeBackMessage: string;
public welcomeMessage: UIElement;
public gettingStartedPlzLogin: UIElement;
public welcomeBackMessage: UIElement;
public welcomeTail: UIElement;
public startzoom: number;
public supportedLanguages: string[];
public startLon: number;
public startLat: number;
public welcomeTail: string;
public locationContains: string[];
@ -33,26 +43,79 @@ export class Layout {
*/
constructor(
name: string,
title: string,
supportedLanguages: string[],
title: UIElement | string,
layers: LayerDefinition[],
startzoom: number,
startLat: number,
startLon: number,
welcomeMessage: string,
gettingStartedPlzLogin: string = "Please login to get started",
welcomeBackMessage: string = "You are logged in. Welcome back!",
welcomeTail: string = ""
welcomeMessage: UIElement | string,
gettingStartedPlzLogin: UIElement | string = Translations.t.general.getStarted,
welcomeBackMessage: UIElement | string = Translations.t.general.welcomeBack,
welcomeTail: UIElement | string = ""
) {
this.title = title;
this.supportedLanguages = supportedLanguages;
this.title = typeof (title) === 'string' ? new FixedUiElement(title) : title;
this.startLon = startLon;
this.startLat = startLat;
this.startzoom = startzoom;
this.name = name;
this.layers = layers;
this.welcomeMessage = welcomeMessage;
this.gettingStartedPlzLogin = gettingStartedPlzLogin;
this.welcomeBackMessage = welcomeBackMessage;
this.welcomeTail = welcomeTail;
this.welcomeMessage = Translations.W(welcomeMessage)
this.gettingStartedPlzLogin = Translations.W(gettingStartedPlzLogin);
this.welcomeBackMessage = Translations.W(welcomeBackMessage);
this.welcomeTail = Translations.W(welcomeTail);
}
}
export class WelcomeMessage extends UIElement {
private readonly layout: Layout;
private readonly userDetails: UIEventSource<UserDetails>;
private osmConnection: OsmConnection;
private readonly description: UIElement;
private readonly plzLogIn: UIElement;
private readonly welcomeBack: UIElement;
private readonly tail: UIElement;
constructor(layout: Layout, osmConnection: OsmConnection) {
super(osmConnection.userDetails);
this.ListenTo(Locale.language);
this.osmConnection = osmConnection;
this.layout = layout;
this.userDetails = osmConnection.userDetails;
this.description = layout.welcomeMessage;
console.log(" >>>>",this.description, "DESCR ")
this.plzLogIn = layout.gettingStartedPlzLogin;
this.welcomeBack = layout.welcomeBackMessage;
this.tail = layout.welcomeTail;
}
InnerRender(): string {
return "<div id='welcomeMessage'>" +
this.description.Render() +
"<br/>"+
(this.userDetails.data.loggedIn ? this.welcomeBack : this.plzLogIn).Render() +
"<br/>"+
this.tail.Render() +
"</div>"
;
/*
return new VariableUiElement(
this.userDetails.map((userdetails) => {
}),
function () {
}).ListenTo(Locale.language);*/
}
protected InnerUpdate(htmlElement: HTMLElement) {
this.osmConnection.registerActivateOsmAUthenticationClass()
}
}

View file

@ -4,6 +4,7 @@ export class All extends Layout{
constructor() {
super(
"all",
["en"],
"All quest layers",
[],
15,

View file

@ -4,6 +4,7 @@ import * as Layer from "../Layers/Bookcases";
export class Bookcases extends Layout{
constructor() {
super( "bookcases",
["nl"],
"Open Bookcase Map",
[new Layer.Bookcases()],
14,

View file

@ -1,30 +1,30 @@
import {Layout} from "../Layout";
import BikeParkings from "../Layers/BikeParkings";
import BikeServices from "../Layers/BikeStations";
import {GhostBike} from "../Layers/GhostBike";
import {DrinkingWater, DrinkingWaterLayer} from "../Layers/DrinkingWater";
import BikeShops from "../Layers/BikeShops";
import Translations from "../../UI/i18n/Translations";
import {DrinkingWater} from "../Layers/DrinkingWater";
import Combine from "../../UI/Base/Combine";
export default class Cyclofix extends Layout {
constructor() {
super(
"pomp",
"Cyclofix bicycle infrastructure",
[new GhostBike(), new BikeServices(), new BikeParkings(), new DrinkingWater()],
["en", "nl", "fr"],
Translations.t.cyclofix.title,
[new BikeServices(), new BikeShops(), new DrinkingWater(), new BikeParkings()],
16,
50.8465573,
4.3516970,
"<h3>Cyclofix bicycle infrastructure</h3>\n" +
"\n" +
"<p><b>EN&gt;</b> On this map we want to collect data about the whereabouts of bicycle pumps and public racks in Brussels." +
"As a result, cyclists will be able to quickly find the nearest infrastructure for their needs.</p>" +
"<p><b>NL&gt;</b> Op deze kaart willen we gegevens verzamelen over de locatie van fietspompen en openbare stelplaatsen in Brussel." +
"Hierdoor kunnen fietsers snel de dichtstbijzijnde infrastructuur vinden die voldoet aan hun behoeften.</p>" +
"<p><b>FR&gt;</b> Sur cette carte, nous voulons collecter des données sur la localisation des pompes à vélo et des supports publics à Bruxelles." +
"Les cyclistes pourront ainsi trouver rapidement l'infrastructure la plus proche de leurs besoins.</p>"
,
"", "");
/* Translations.t.cyclofix.title/*/
new Combine([
"<h3>",
Translations.t.cyclofix.title,
"</h3><br/><p>",
Translations.t.cyclofix.description,
"</p>"
])//*/
);
}
}

View file

@ -4,6 +4,7 @@ import {GrbToFix} from "../Layers/GrbToFix";
export class GRB extends Layout {
constructor() {
super("grb",
["en"],
"Grb import fix tool",
[new GrbToFix()],
15,

View file

@ -7,6 +7,7 @@ export class Groen extends Layout {
constructor() {
super("buurtnatuur",
["nl"],
"Buurtnatuur",
[new NatureReserves(), new Park(), new Bos()],
10,

View file

@ -5,6 +5,7 @@ import {Map} from "../Layers/Map";
export class MetaMap extends Layout{
constructor() {
super( "metamap",
["en"],
"Open Map Map",
[new Map()],
1,

View file

@ -7,6 +7,7 @@ export class Natuurpunt extends Layout{
constructor() {
super(
"natuurpunt",
["nl"],
"De natuur in",
[new Birdhide(), new InformationBoard(), new NatureReserves(true)],
12,

View file

@ -5,6 +5,7 @@ export class Statues extends Layout{
constructor() {
super( "statues",
"Open Artwork Map",
["en"],
[new Artwork()],
10,
50.8435,

View file

@ -7,6 +7,7 @@ export class StreetWidth extends Layout{
constructor() {
super( "width",
["nl"],
"Straatbreedtes in Brugge",
[new Widths(
2,

View file

@ -4,6 +4,7 @@ import * as Layer from "../Layers/Toilets";
export class Toilets extends Layout{
constructor() {
super( "toilets",
["en"],
"Open Toilet Map",
[new Layer.Toilets()],
12,

View file

@ -6,6 +6,7 @@ import { Park } from "../Layers/Park";
export class WalkByBrussels extends Layout {
constructor() {
super("walkbybrussels",
["en","fr","nl"],
"Drinking Water Spots",
[new DrinkingWater(), new Park(), new NatureReserves()],
10,

View file

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

View file

@ -1,5 +1,6 @@
import {TagRenderingOptions} from "../TagRendering";
import {And, Tag} from "../../Logic/TagsFilter";
import {UIElement} from "../../UI/UIElement";
export class NameInline extends TagRenderingOptions{
@ -8,7 +9,7 @@ export class NameInline extends TagRenderingOptions{
return string.charAt(0).toUpperCase() + string.slice(1);
}
constructor(category: string) {
constructor(category: string ) {
super({
question: "",

View file

@ -0,0 +1,27 @@
import {TagRenderingOptions} from "../../TagRendering";
import {Tag, And} from "../../../Logic/TagsFilter";
import Translations from "../../../UI/i18n/Translations";
export default class ParkingOperator extends TagRenderingOptions {
constructor() {
const to = Translations.t.cyclofix.parking.operator
super({
priority: 15,
question: to.question.Render(),
freeform: {
key: "operator",
template: to.template,
renderTemplate: to.render,
placeholder: Translations.t.cyclofix.freeFormPlaceholder
},
mappings: [
{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 And([new Tag("operator", ""), new Tag("operator:type", "private")]), txt: to.private.Render()}
]
});
}
}

View file

@ -1,38 +1,61 @@
import {TagRenderingOptions} from "../../TagRendering";
import {Tag} from "../../../Logic/TagsFilter";
import Translations from "../../../UI/i18n/Translations";
import Combine from "../../../UI/Base/Combine";
class ParkingTypeHelper {
static GenerateMappings() {
const images = {
stands: "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dc/Bike_racks_at_north-west_of_Westfield_-_geograph.org.uk_-_1041057.jpg/100px-Bike_racks_at_north-west_of_Westfield_-_geograph.org.uk_-_1041057.jpg",
wall_loops: "https://wiki.openstreetmap.org/w/images/thumb/c/c2/Bike-parking-wheelbender.jpg/100px-Bike-parking-wheelbender.jpg",
handlebar_holder: "https://upload.wikimedia.org/wikipedia/commons/thumb/2/2c/Bicycle_parking_handlebar_holder.jpg/100px-Bicycle_parking_handlebar_holder.jpg",
shed: "https://wiki.openstreetmap.org/w/images/thumb/b/b2/Bike-shelter.jpg/100px-Bike-shelter.jpg",
rack: "https://wiki.openstreetmap.org/w/images/thumb/4/41/Triton_Bike_Rack.png/100px-Triton_Bike_Rack.png",
"two-tier": "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3f/Bicis_a_l%27estaci%C3%B3_de_Leiden.JPG/100px-Bicis_a_l%27estaci%C3%B3_de_Leiden.JPG"
};
const toImg = (url) => `<img src=${url}>`
const mappings = [];
const to = Translations.t.cyclofix.parking.type
for (const imagesKey in images) {
const mapping =
{
k: new Tag("bicycle_parking", imagesKey),
txt: new Combine([
to[imagesKey],
to.eg,
toImg(images[imagesKey])
])
};
mappings.push(mapping);
}
return mappings;
}
}
export default class ParkingType extends TagRenderingOptions {
private static images = {
stands: "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dc/Bike_racks_at_north-west_of_Westfield_-_geograph.org.uk_-_1041057.jpg/100px-Bike_racks_at_north-west_of_Westfield_-_geograph.org.uk_-_1041057.jpg",
wall_loops: "https://wiki.openstreetmap.org/w/images/thumb/c/c2/Bike-parking-wheelbender.jpg/100px-Bike-parking-wheelbender.jpg",
handlebar_holder: "https://upload.wikimedia.org/wikipedia/commons/thumb/2/2c/Bicycle_parking_handlebar_holder.jpg/100px-Bicycle_parking_handlebar_holder.jpg",
shed: "https://wiki.openstreetmap.org/w/images/thumb/b/b2/Bike-shelter.jpg/100px-Bike-shelter.jpg",
"two-tier": "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3f/Bicis_a_l%27estaci%C3%B3_de_Leiden.JPG/100px-Bicis_a_l%27estaci%C3%B3_de_Leiden.JPG"
}
private static toImgTxt(url: string) {
return `<img src=${url}>`
}
constructor() {
const to = Translations.t.cyclofix.parking.type
super({
priority: 5,
question: "Van welk type is deze fietsenparking?",
question: to.question,
freeform: {
key: "bicycle_parking",
extraTags: new Tag("fixme", "Freeform bicycle_parking= tag used: possibly a wrong value"),
template: "Iets anders: $$$",
renderTemplate: "Dit is een fietsenparking van het type: {bicycle_parking}",
placeholder: "Specifieer"
template: to.template.txt,
renderTemplate: to.render.txt,
placeholder: Translations.t.cyclofix.freeFormPlaceholder,
},
mappings: [
{k: new Tag("bicycle_parking", "stands"), txt: ParkingType.toImgTxt(ParkingType.images.stands)},
{k: new Tag("bicycle_parking", "wall_loops"), txt: ParkingType.toImgTxt(ParkingType.images.wall_loops)},
{k: new Tag("bicycle_parking", "handlebar_holder"), txt: ParkingType.toImgTxt(ParkingType.images.handlebar_holder)},
{k: new Tag("bicycle_parking", "shed"), txt: ParkingType.toImgTxt(ParkingType.images.shed)},
{k: new Tag("bicycle_parking", "two-tier"), txt: ParkingType.toImgTxt(ParkingType.images["two-tier"])}
]
mappings: ParkingTypeHelper.GenerateMappings()
});
}
}

View file

@ -1,16 +1,18 @@
import {TagRenderingOptions} from "../../TagRendering";
import {Tag} from "../../../Logic/TagsFilter";
import Translations from "../../../UI/i18n/Translations";
export default class PumpManometer extends TagRenderingOptions {
constructor() {
const to = Translations.t.cyclofix.station.manometer
super({
question: "Does the pump have a pressure indicator or manometer?",
question: to.question,
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"}
{k: new Tag("manometer", "yes"), txt: to.yes},
{k: new Tag("manometer", "no"), txt: to.no},
{k: new Tag("manometer", "broken"), txt: to.broken}
]
});
}
}
}

View file

@ -1,15 +1,17 @@
import {TagRenderingOptions} from "../../TagRendering";
import {Tag} from "../../../Logic/TagsFilter";
import Translations from "../../../UI/i18n/Translations";
export default class PumpManual extends TagRenderingOptions {
constructor() {
const to = Translations.t.cyclofix.station.electric
super({
priority: 5,
question: "Is this an electric bike pump?",
question: to.question,
mappings: [
{k: new Tag("manual", "yes"), txt: "Manual pump"},
{k: new Tag("manual", "no"), txt: "Electric pump"}
{k: new Tag("manual", "yes"), txt: to.manual},
{k: new Tag("manual", "no"), txt: to.electric}
]
});
}

View file

@ -1,14 +1,16 @@
import {TagRenderingOptions} from "../../TagRendering";
import {Tag} from "../../../Logic/TagsFilter";
import Translations from "../../../UI/i18n/Translations";
export default class PumpOperational extends TagRenderingOptions {
constructor() {
const to = Translations.t.cyclofix.station.operational
super({
question: "Is the bicycle pump still operational?",
question: to.question,
mappings: [
{k: new Tag("service:bicycle:pump:operational_status","broken"), txt: "This pump is broken"},
{k: new Tag("service:bicycle:pump:operational_status",""), txt: "This pump is operational"}
{k: new Tag("service:bicycle:pump:operational_status","broken"), txt: to.broken},
{k: new Tag("service:bicycle:pump:operational_status",""), txt: to.operational}
]
});
}

View file

@ -1,24 +1,27 @@
import {TagRenderingOptions} from "../../TagRendering";
import {Tag} from "../../../Logic/TagsFilter";
import Translations from "../../../UI/i18n/Translations";
export default class PumpValves extends TagRenderingOptions{
constructor() {
const to = Translations.t.cyclofix.station.valves
super({
question: "What valves are supported?",
question: to.question,
mappings: [
{
k: new Tag("valves", " sclaverand;schrader;dunlop"),
txt: "There is a default head, so Presta, Dunlop and Auto"
txt: to.default
},
{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"},
{k: new Tag("valves", "dunlop"), txt: to.dunlop},
{k: new Tag("valves", "sclaverand"), txt: to.sclaverand},
{k: new Tag("valves", "auto"), txt: to.auto},
],
freeform: {
extraTags: new Tag("fixme", "Freeform valves= tag used: possibly a wrong value"),
key: "valves",
template: "Supported valves are $$$",
renderTemplate: "Supported valves are {valves}"
template: to.template,
renderTemplate: to.render
}
});
}

View file

@ -0,0 +1,19 @@
import {TagRenderingOptions} from "../../TagRendering";
import {Tag} from "../../../Logic/TagsFilter";
import Translations from "../../../UI/i18n/Translations";
export default class ShopPump extends TagRenderingOptions {
constructor() {
const key = 'service:bicycle:diy'
const to = Translations.t.cyclofix.shop.diy
super({
priority: 5,
question: to.question,
mappings: [
{k: new Tag(key, "yes"), txt: to.yes},
{k: new Tag(key, "no"), txt: to.no},
]
});
}
}

View file

@ -0,0 +1,18 @@
import {TagRenderingOptions} from "../../TagRendering";
import Translations from "../../../UI/i18n/Translations";
export default class ShopPump extends TagRenderingOptions {
constructor() {
const to = Translations.t.cyclofix.shop.qName
super({
priority: 5,
question: to.question,
freeform: {
key: "name",
renderTemplate: to.render,
template: to.template
}
})
}
}

View file

@ -0,0 +1,19 @@
import {TagRenderingOptions} from "../../TagRendering";
import {Tag} from "../../../Logic/TagsFilter";
import Translations from "../../../UI/i18n/Translations";
export default class ShopPump extends TagRenderingOptions {
constructor() {
const key = 'service:bicycle:pump'
const to = Translations.t.cyclofix.shop.pump
super({
priority: 5,
question: to.question,
mappings: [
{k: new Tag(key, "yes"), txt: to.yes},
{k: new Tag(key, "no"), txt: to.no},
]
});
}
}

View file

@ -0,0 +1,19 @@
import {TagRenderingOptions} from "../../TagRendering";
import {Tag} from "../../../Logic/TagsFilter";
import Translations from "../../../UI/i18n/Translations";
export default class ShopRental extends TagRenderingOptions {
constructor() {
const key = 'service:bicycle:rental'
const to = Translations.t.cyclofix.shop.rental
super({
priority: 5,
question: to.question,
mappings: [
{k: new Tag(key, "yes"), txt: to.yes},
{k: new Tag(key, "no"), txt: to.no},
]
});
}
}

View file

@ -0,0 +1,21 @@
import {TagRenderingOptions} from "../../TagRendering";
import {Tag} from "../../../Logic/TagsFilter";
import Translations from "../../../UI/i18n/Translations";
export default class ShopRepair extends TagRenderingOptions {
constructor() {
const key = 'service:bicycle:repair'
const to = Translations.t.cyclofix.shop.repair
super({
priority: 5,
question: to.question,
mappings: [
{k: new Tag(key, "yes"), txt: to.yes},
{k: new Tag(key, "only_sold"), txt: to.sold},
{k: new Tag(key, "brand"), txt: to.brand},
{k: new Tag(key, "no"), txt: to.no},
]
});
}
}

View file

@ -0,0 +1,19 @@
import {TagRenderingOptions} from "../../TagRendering";
import {Tag} from "../../../Logic/TagsFilter";
import Translations from "../../../UI/i18n/Translations";
export default class ShopRetail extends TagRenderingOptions {
constructor() {
const key = 'service:bicycle:retail'
const to = Translations.t.cyclofix.shop.retail
super({
priority: 5,
question: to.question,
mappings: [
{k: new Tag(key, "yes"), txt: to.yes},
{k: new Tag(key, "no"), txt: to.no},
]
});
}
}

View file

@ -0,0 +1,20 @@
import {TagRenderingOptions} from "../../TagRendering";
import {Tag} from "../../../Logic/TagsFilter";
import Translations from "../../../UI/i18n/Translations";
export default class ShopPump extends TagRenderingOptions {
constructor() {
const key = 'service:bicycle:second_hand'
const to = Translations.t.cyclofix.shop.secondHand
super({
priority: 5,
question: to.question,
mappings: [
{k: new Tag(key, "yes"), txt: to.yes},
{k: new Tag(key, "no"), txt: to.no},
{k: new Tag(key, "only"), txt: to.only},
]
});
}
}

View file

@ -4,6 +4,8 @@ import {Tag} from "../../../Logic/TagsFilter";
/**
* Currently not used in Cyclofix because it's a little vague
*
* TODO: Translations
*/
export default class BikeStationBrand extends TagRenderingOptions {
private static options = {

View file

@ -1,15 +1,17 @@
import {TagRenderingOptions} from "../../TagRendering";
import {Tag} from "../../../Logic/TagsFilter";
import Translations from "../../../UI/i18n/Translations";
export default class StationChain extends TagRenderingOptions {
constructor() {
const to = Translations.t.cyclofix.station.chain
super({
priority: 5,
question: "Does this bike station have a special tool to repair your bike chain?",
question: to.question,
mappings: [
{k: new Tag("service:bicycle:chain_tool", "yes"), txt: "There is a chain tool."},
{k: new Tag("service:bicycle:chain_tool", "no"), txt: "There is no chain tool."},
{k: new Tag("service:bicycle:chain_tool", "yes"), txt: to.yes},
{k: new Tag("service:bicycle:chain_tool", "no"), txt: to.no},
]
});
}

View file

@ -1,25 +1,27 @@
import {TagRenderingOptions} from "../../TagRendering";
import {Tag} from "../../../Logic/TagsFilter";
import Translations from "../../../UI/i18n/Translations";
export default class BikeStationOperator extends TagRenderingOptions {
constructor() {
const to = Translations.t.cyclofix.station.operator
super({
priority: 15,
question: "Who operates this bike station (name of university, shop, city...)?",
freeform: {
key: "operator",
template: "This bike station is operated by $$$",
renderTemplate: "This bike station is operated by {operator}",
placeholder: "organisatie"
},
question: to.question,
mappings: [
{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"}
]
{k: new Tag("operator", "private"), txt: to.private}
],
freeform: {
key: "operator",
template: to.template,
renderTemplate: to.render,
placeholder: "organisatie"
}
});
}
}

View file

@ -1,16 +1,18 @@
import {TagRenderingOptions} from "../../TagRendering";
import {Tag, And} from "../../../Logic/TagsFilter";
import Translations from "../../../UI/i18n/Translations";
export default class BikeStationPumpTools extends TagRenderingOptions {
constructor() {
const to = Translations.t.cyclofix.station.services
super({
priority: 15,
question: "Which services are available at this bike station?",
question: to.question,
mappings: [
{k: new And([new Tag("service:bicycle:tools", "no"), new Tag("service:bicycle:pump", "yes")]), txt: "There is only a pump available."},
{k: new And([new Tag("service:bicycle:tools", "yes"), new Tag("service:bicycle:pump", "no")]), txt: "There are only tools (screwdrivers, pliers...) available."},
{k: new And([new Tag("service:bicycle:tools", "yes"), new Tag("service:bicycle:pump", "yes")]), txt: "There are both tools and a pump available."}
{k: new And([new Tag("service:bicycle:tools", "no"), new Tag("service:bicycle:pump", "yes")]), txt: to.pump},
{k: new And([new Tag("service:bicycle:tools", "yes"), new Tag("service:bicycle:pump", "no")]), txt: to.tools},
{k: new And([new Tag("service:bicycle:tools", "yes"), new Tag("service:bicycle:pump", "yes")]), txt: to.both}
]
});
}

View file

@ -1,14 +1,16 @@
import {TagRenderingOptions} from "../../TagRendering";
import {Tag} from "../../../Logic/TagsFilter";
import Translations from "../../../UI/i18n/Translations";
export default class BikeStationStand extends TagRenderingOptions {
constructor() {
const to = Translations
super({
priority: 10,
question: "Does this bike station have a hook to suspend your bike with or a stand to elevate it?",
mappings: [
{k: new Tag("service:bicycle:stand", "yes"), txt: "There is a hook or stand."},
{k: new Tag("service:bicycle:stand", "yes"), txt: "There is a hook or stand"},
{k: new Tag("service:bicycle:stand", "no"), txt: "There is no hook or stand"},
]
});

View file

@ -1,17 +1,20 @@
import {UIElement} from "../UI/UIElement";
import {UIEventSource} from "../UI/UIEventSource";
import {And, Tag, TagsFilter, TagUtils} from "../Logic/TagsFilter";
import {UIRadioButton} from "../UI/Base/UIRadioButton";
import {FixedUiElement} from "../UI/Base/FixedUiElement";
import {SaveButton} from "../UI/SaveButton";
import {Changes} from "../Logic/Changes";
import {TextField} from "../UI/Base/TextField";
import {UIInputElement} from "../UI/Base/UIInputElement";
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";
import {TextField} from "../UI/Input/TextField";
import {InputElement} from "../UI/Input/InputElement";
import {InputElementWrapper} from "../UI/Input/InputElementWrapper";
import {FixedInputElement} from "../UI/Input/FixedInputElement";
import {RadioButton} from "../UI/Input/RadioButton";
import Translations from "../UI/i18n/Translations";
import Locale from "../UI/i18n/Locale";
export class TagRenderingOptions implements TagDependantUIElementConstructor {
@ -20,8 +23,17 @@ export class TagRenderingOptions implements TagDependantUIElementConstructor {
*/
public options: {
priority?: number; question?: string; primer?: string;
freeform?: { key: string; tagsPreprocessor?: (tags: any) => any; template: string; renderTemplate: string; placeholder?: string; extraTags?: TagsFilter }; mappings?: { k: TagsFilter; txt: string; priority?: number, substitute?: boolean }[]
priority?: number;
question?: string | UIElement;
freeform?: {
key: string;
tagsPreprocessor?: (tags: any) => any;
template: string | UIElement;
renderTemplate: string | UIElement;
placeholder?: string | UIElement;
extraTags?: TagsFilter
};
mappings?: { k: TagsFilter; txt: string | UIElement; priority?: number, substitute?: boolean }[]
};
@ -35,7 +47,7 @@ export class TagRenderingOptions implements TagDependantUIElementConstructor {
* If 'question' is undefined, then the question is never asked at all
* If the question is "" (empty string) then the question is
*/
question?: string,
question?: UIElement | string,
/**
* What is the priority of the question.
@ -56,7 +68,7 @@ export class TagRenderingOptions implements TagDependantUIElementConstructor {
*
*
*/
mappings?: { k: TagsFilter, txt: string, priority?: number, substitute?: boolean }[],
mappings?: { k: TagsFilter, txt: UIElement | string, priority?: number, substitute?: boolean }[],
/**
@ -65,19 +77,14 @@ export class TagRenderingOptions implements TagDependantUIElementConstructor {
* In the question, it'll offer a textfield
*/
freeform?: {
key: string, template: string,
renderTemplate: string
placeholder?: string,
key: string,
template: string | UIElement,
renderTemplate: string | UIElement
placeholder?: string | UIElement,
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 can be used for that.
@ -85,6 +92,7 @@ export class TagRenderingOptions implements TagDependantUIElementConstructor {
*/
tagsPreprocessor?: ((tags: any) => void)
}) {
this.options = options;
}
@ -129,29 +137,24 @@ export class TagRenderingOptions implements TagDependantUIElementConstructor {
class TagRendering extends UIElement implements TagDependantUIElement {
private _priority: number;
private _userDetails: UIEventSource<UserDetails>;
private _priority: number;
Priority(): number {
return this._priority;
}
private _question: string;
private _primer: string;
private _mapping: { k: TagsFilter, txt: string, priority?: number, substitute?: boolean }[];
private _renderMapping: { k: TagsFilter, txt: string, priority?: number, substitute?: boolean }[];
private _question: UIElement;
private _mapping: { k: TagsFilter, txt: string | UIElement, priority?: number }[];
private _tagsPreprocessor?: ((tags: any) => any);
private _freeform: {
key: string, template: string,
renderTemplate: string,
placeholder?: string,
key: string,
template: string | UIElement,
renderTemplate: string | UIElement,
placeholder?: string | UIElement,
extraTags?: TagsFilter
};
private readonly _questionElement: UIElement;
private readonly _textField: TextField<TagsFilter>; // Only here to update
private readonly _questionElement: InputElement<TagsFilter>;
private readonly _saveButton: UIElement;
private readonly _skipButton: UIElement;
@ -165,19 +168,20 @@ class TagRendering extends UIElement implements TagDependantUIElement {
constructor(tags: UIEventSource<any>, changes: Changes, options: {
priority?: number
question?: string,
primer?: string,
question?: string | UIElement,
freeform?: {
key: string, template: string,
renderTemplate: string
placeholder?: string,
key: string,
template: string | UIElement,
renderTemplate: string | UIElement,
placeholder?: string | UIElement,
extraTags?: TagsFilter,
},
tagsPreprocessor?: ((tags: any) => any),
mappings?: { k: TagsFilter, txt: string, priority?: number, substitute?: boolean }[]
mappings?: { k: TagsFilter, txt: string | UIElement, priority?: number, substitute?: boolean }[]
}) {
super(tags);
this.ListenTo(Locale.language);
const self = this;
this.ListenTo(this._questionSkipped);
this.ListenTo(this._editMode);
@ -185,9 +189,10 @@ class TagRendering extends UIElement implements TagDependantUIElement {
this._userDetails = changes.login.userDetails;
this.ListenTo(this._userDetails);
this._question = options.question;
if (options.question !== undefined) {
this._question = Translations.W(options.question);
}
this._priority = options.priority ?? 0;
this._primer = options.primer ?? "";
this._tagsPreprocessor = function (properties) {
if (options.tagsPreprocessor === undefined) {
return properties;
@ -201,97 +206,38 @@ class TagRendering extends UIElement implements TagDependantUIElement {
};
this._mapping = [];
this._renderMapping = [];
this._freeform = options.freeform;
// Prepare the choices for the Radio buttons
const choices: UIElement[] = [];
const usedChoices: string [] = [];
for (const choice of options.mappings ?? []) {
if (choice.k === null) {
this._mapping.push(choice);
continue;
}
let choiceSubbed = choice;
let choiceSubbed = {
k: choice.k,
txt: choice.txt,
priority: choice.priority
};
if (choice.substitute) {
choiceSubbed = {
k: choice.k.substituteValues(
options.tagsPreprocessor(this._source.data)),
txt: this.ApplyTemplate(choice.txt),
substitute: false,
txt: choice.txt,
priority: choice.priority
}
}
const txt = choiceSubbed.txt
// Choices is what is shown in the radio buttons
if (usedChoices.indexOf(txt) < 0) {
choices.push(new FixedUiElement(txt));
usedChoices.push(txt);
// This is used to convert the radio button index into tags needed to add
this._mapping.push(choiceSubbed);
} else {
this._renderMapping.push(choiceSubbed); // only used while rendering
}
this._mapping.push({
k: choiceSubbed.k,
txt: choiceSubbed.txt
});
}
// Map radiobutton choice and textfield answer onto tagfilter. That tagfilter will be pushed into the changes later on
const pickChoice = (i => {
if (i === undefined || i === null) {
return undefined
}
return self._mapping[i].k
});
const pickString =
(string) => {
if (string === "" || string === undefined) {
return undefined;
}
const tag = new Tag(self._freeform.key, string);
if (self._freeform.extraTags === undefined) {
return tag;
}
return new And([
self._freeform.extraTags,
tag
]
);
};
// Prepare the actual input element -> pick an appropriate implementation
let inputElement: UIInputElement<TagsFilter>;
if (this._freeform !== undefined && this._mapping !== undefined) {
// Radio buttons with 'other'
inputElement = new UIRadioButtonWithOther(
choices,
this._freeform.template,
this._freeform.placeholder,
pickChoice,
pickString
);
this._questionElement = inputElement;
} else if (this._mapping !== [] && this._mapping.length > 0) {
// This is a classic radio selection element
inputElement = new UIRadioButton(new UIEventSource(choices), pickChoice, false)
this._questionElement = inputElement;
} else if (this._freeform !== undefined) {
this._textField = new TextField(new UIEventSource<string>(this._freeform.placeholder), pickString);
inputElement = this._textField;
this._questionElement = new FixedUiElement(
"<div>" + this._freeform.template.replace("$$$", inputElement.Render()) + "</div>")
} else {
throw "Invalid questionRendering, expected at least choices or a freeform"
}
this._questionElement = this.InputElementFor(options);
const save = () => {
const selection = inputElement.GetValue().data;
const selection = self._questionElement.GetValue().data;
if (selection) {
changes.addTag(tags.data.id, selection);
}
@ -305,54 +251,157 @@ class TagRendering extends UIElement implements TagDependantUIElement {
}
// Setup the save button and it's action
this._saveButton = new SaveButton(inputElement.GetValue())
this._saveButton = new SaveButton(this._questionElement.GetValue())
.onClick(save);
this._editButton = new FixedUiElement("");
if (this._question !== undefined) {
this._editButton = new FixedUiElement("<img class='editbutton' src='./assets/pencil.svg' alt='edit'>")
.onClick(() => {
console.log("Click", self._editButton);
if (self._textField) {
self._textField.value.setData(self._source.data["name"] ?? "");
}
self._editMode.setData(true);
self._questionElement.GetValue().setData(self.CurrentValue());
});
} else {
this._editButton = new FixedUiElement("");
}
const cancelContents = this._editMode.map((isEditing) => {
if (isEditing) {
return "<span class='skip-button'>Annuleren</span>";
return "<span class='skip-button'>"+Translations.t.general.cancel.R()+"</span>";
} else {
return "<span class='skip-button'>Overslaan (Ik weet het niet zeker...)</span>";
return "<span class='skip-button'>"+Translations.t.general.skip.R()+"</span>";
}
});
}, [Locale.language]);
// And at last, set up the skip button
this._skipButton = new VariableUiElement(cancelContents).onClick(cancel);
this._skipButton = new VariableUiElement(cancelContents).onClick(cancel) ;
}
private InputElementFor(options: {
freeform?: {
key: string,
template: string | UIElement,
renderTemplate: string | UIElement,
placeholder?: string | UIElement,
extraTags?: TagsFilter,
},
mappings?: { k: TagsFilter, txt: string | UIElement, priority?: number, substitute?: boolean }[]
}):
InputElement<TagsFilter> {
const elements = [];
if (options.mappings !== undefined) {
const previousTexts= [];
for (const mapping of options.mappings) {
if(mapping.k === null){
continue;
}
if(previousTexts.indexOf(mapping.txt) >= 0){
continue;
}
previousTexts.push(mapping.txt);
elements.push(this.InputElementForMapping(mapping));
}
}
if (options.freeform !== undefined) {
elements.push(this.InputForFreeForm(options.freeform));
}
if (elements.length == 0) {
console.warn("WARNING: no tagrendering with following options:", options);
return new FixedInputElement("This should not happen: no tag renderings defined", undefined);
}
if (elements.length == 1) {
return elements[0];
}
return new RadioButton(elements, false);
}
private ApplyTemplate(template: string): string {
const tags = this._tagsPreprocessor(this._source.data);
return TagUtils.ApplyTemplate(template, tags);
private InputElementForMapping(mapping: { k: TagsFilter, txt: string | UIElement }) {
return new FixedInputElement(mapping.txt, mapping.k);
}
private InputForFreeForm(freeform): InputElement<TagsFilter> {
if (freeform === undefined) {
return undefined;
}
const pickString =
(string) => {
if (string === "" || string === undefined) {
return undefined;
}
const tag = new Tag(freeform.key, string);
if (freeform.extraTags === undefined) {
return tag;
}
return new And([
tag,
freeform.extraTags
]
);
};
const toString =
(tag) => {
if (tag instanceof And) {
return toString(tag.and[0])
} else if (tag instanceof Tag) {
return tag.value
}
return undefined;
}
let inputElement: InputElement<TagsFilter>;
const textField = new TextField({
placeholder: this._freeform.placeholder,
fromString: pickString,
toString: toString
});
const prepost = Translations.W(freeform.template).InnerRender().split("$$$");
return new InputElementWrapper(prepost[0], textField, prepost[1]);
}
IsKnown(): boolean {
const tags = TagUtils.proprtiesToKV(this._source.data);
for (const oneOnOneElement of this._mapping.concat(this._renderMapping)) {
for (const oneOnOneElement of this._mapping) {
if (oneOnOneElement.k === null || oneOnOneElement.k.matches(tags)) {
return true;
}
}
return this._freeform !== undefined && this._source.data[this._freeform.key] !== undefined;
}
private CurrentValue(): TagsFilter {
const tags = TagUtils.proprtiesToKV(this._source.data);
for (const oneOnOneElement of this._mapping) {
if (oneOnOneElement.k !== null && oneOnOneElement.k.matches(tags)) {
return oneOnOneElement.k;
}
}
if (this._freeform === undefined) {
return undefined;
}
return new Tag(this._freeform.key, this._source.data[this._freeform.key]);
}
IsQuestioning(): boolean {
if (this.IsKnown()) {
return false;
@ -368,10 +417,10 @@ class TagRendering extends UIElement implements TagDependantUIElement {
return true;
}
private RenderAnwser(): string {
private RenderAnwser(): UIElement {
const tags = TagUtils.proprtiesToKV(this._source.data);
let freeform = "";
let freeform: UIElement = new FixedUiElement("");
let freeformScore = -10;
if (this._freeform !== undefined && this._source.data[this._freeform.key] !== undefined) {
freeform = this.ApplyTemplate(this._freeform.renderTemplate);
@ -379,58 +428,59 @@ class TagRendering extends UIElement implements TagDependantUIElement {
}
let highestScore = -100;
let highestTemplate = undefined;
for (const oneOnOneElement of this._mapping.concat(this._renderMapping)) {
if (oneOnOneElement.k == null ||
oneOnOneElement.k.matches(tags)) {
// We have found a matching key -> we use the template, but only if it scores better
let score = oneOnOneElement.priority ??
(oneOnOneElement.k === null ? -1 : 0);
if (score > highestScore) {
highestScore = score;
highestTemplate = oneOnOneElement.txt
}
let highestScore = -100;
let highestTemplate = undefined;
for (const oneOnOneElement of this._mapping) {
if (oneOnOneElement.k == null ||
oneOnOneElement.k.matches(tags)) {
// We have found a matching key -> we use the template, but only if it scores better
let score = oneOnOneElement.priority ??
(oneOnOneElement.k === null ? -1 : 0);
if (score > highestScore) {
highestScore = score;
highestTemplate = oneOnOneElement.txt
}
}
}
if (freeformScore > highestScore) {
return freeform;
}
if (freeformScore > highestScore) {
return freeform;
}
if (highestTemplate !== undefined) {
// we render the found template
return this.ApplyTemplate(highestTemplate);
}
if (highestTemplate !== undefined) {
// we render the found template
return this._primer + this.ApplyTemplate(highestTemplate);
}
}
protected InnerRender(): string {
InnerRender(): string {
if (this.IsQuestioning() || this._editMode.data) {
// Not yet known or questioning, we have to ask a question
const question = this._question.Render();
return "<div class='question'>" +
"<span class='question-text'>" + this._question + "</span>" +
(this._question !== "" ? "<br/>" : "") +
this._questionElement.Render() +
"<span class='question-text'>" + question + "</span>" +
(this._question.IsEmpty() ? "" : "<br/>") +
"<div>" + this._questionElement.Render() + "</div>" +
this._skipButton.Render() +
this._saveButton.Render() +
"</div>"
}
if (this.IsKnown()) {
const html = this.RenderAnwser();
if (html == "") {
const answer = this.RenderAnwser()
if (answer.IsEmpty()) {
return "";
}
const html = answer.Render();
let editButton = "";
if(this._userDetails.data.loggedIn){
if (this._userDetails.data.loggedIn && this._question !== undefined) {
editButton = this._editButton.Render();
}
return "<span class='answer'>" +
"<span class='answer-text'>" + html + "</span>" +
editButton +
@ -441,13 +491,26 @@ class TagRendering extends UIElement implements TagDependantUIElement {
}
Priority(): number {
return this._priority;
}
private ApplyTemplate(template: string | UIElement): UIElement {
if(template === undefined || template === null){
throw "Trying to apply a template, but the template is null/undefined"
}
const tags = this._tagsPreprocessor(this._source.data);
if (template instanceof UIElement) {
template = template.Render();
}
return new FixedUiElement(TagUtils.ApplyTemplate(template, tags));
}
InnerUpdate(htmlElement: HTMLElement) {
super.InnerUpdate(htmlElement);
this._questionElement.Update();
this._saveButton.Update();
this._skipButton.Update();
this._textField?.Update();
this._editButton.Update();
this._questionElement.Update(); // Another manual update for them
}
}