diff --git a/Customizations/LayerDefinition.ts b/Customizations/LayerDefinition.ts index 138102c..20cec0a 100644 --- a/Customizations/LayerDefinition.ts +++ b/Customizations/LayerDefinition.ts @@ -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,6 +90,7 @@ export class LayerDefinition { title?: TagRenderingOptions, elementsToShow?: TagDependantUIElementConstructor[], maxAllowedOverlapPercentage?: number, + wayHandling?: number, style?: (tags: any) => { color: string, icon: any @@ -99,16 +108,19 @@ export class LayerDefinition { this.title = options.title; this.elementsToShow = options.elementsToShow; this.style = options.style; + this.wayHandling = options.wayHandling ?? LayerDefinition.WAYHANDLING_DEFAULT; } - asLayer(basemap: Basemap, allElements: ElementStorage, changes: Changes, userDetails: UIEventSource, selectedElement: UIEventSource, - showOnPopup: (tags: UIEventSource<(any)>) => UIElement): + asLayer(basemap: Basemap, allElements: ElementStorage, changes: Changes, userDetails: UIEventSource, + selectedElement: UIEventSource<{feature: any}>, + showOnPopup: (tags: UIEventSource<(any)>, feature: any) => UIElement): FilteredLayer { return new FilteredLayer( this.name, basemap, allElements, changes, this.overpassFilter, this.maxAllowedOverlapPercentage, + this.wayHandling, this.style, selectedElement, showOnPopup); diff --git a/Customizations/Layers/BikeParkings.ts b/Customizations/Layers/BikeParkings.ts index 56fab00..3f07869 100644 --- a/Customizations/Layers/BikeParkings.ts +++ b/Customizations/Layers/BikeParkings.ts @@ -13,7 +13,7 @@ import ParkingOperator from "../Questions/bike/ParkingOperator"; export default class BikeParkings extends LayerDefinition { constructor() { super(); - this.name = Translations.t.cyclofix.parking.name.txt; + this.name = Translations.t.cyclofix.parking.name; this.icon = "./assets/bike/parking.svg"; this.overpassFilter = new Tag("amenity", "bicycle_parking"); this.newElementTags = [ @@ -29,6 +29,7 @@ export default class BikeParkings extends LayerDefinition { //new ParkingOperator(), new ParkingType() ]; + this.wayHandling = LayerDefinition.WAYHANDLING_CENTER_AND_WAY; } diff --git a/Customizations/Layers/BikeShop.ts b/Customizations/Layers/BikeShop.ts deleted file mode 100644 index d1b1bea..0000000 --- a/Customizations/Layers/BikeShop.ts +++ /dev/null @@ -1,122 +0,0 @@ -import {TagRenderingOptions} from "../TagRendering"; -import {LayerDefinition} from "../LayerDefinition"; -import {And, Tag} from "../../Logic/TagsFilter"; -import L from "leaflet"; -import {ImageCarouselWithUploadConstructor} from "../../UI/Image/ImageCarouselWithUpload"; -import {NameQuestion} from "../Questions/NameQuestion"; - -export class BikeShop extends LayerDefinition { - - - private readonly sellsBikes = new Tag("service:bicycle:retail", "yes"); - private readonly repairsBikes = new Tag("service:bicycle:repair", "yes"); - - constructor() { - super( - { - name: "bike shop or repair", - icon: "assets/bike/repair_shop.svg", - minzoom: 14, - overpassFilter: new Tag("shop", "bicycle"), - newElementTags: [new Tag("shop", "bicycle")] - } - ); - - this.title = new TagRenderingOptions({ - mappings: [ - {k: new And([new Tag("name", "*"), this.sellsBikes]), txt: "Bicycle shop {name}"}, - { - k: new And([new Tag("name", "*"), new Tag("service:bicycle:retail", "no")]), - txt: "Bicycle repair {name}", - }, - { - k: new And([new Tag("name", "*"), new Tag("service:bicycle:retail", "")]), - txt: "Bicycle repair {name}" - }, - - {k: this.sellsBikes, txt: "Bicycle shop"}, - {k: new Tag("service:bicycle:retail", "no"), txt: "Bicycle repair"}, - {k: new Tag("service:bicycle:retail", ""), txt: "Bicycle repair/shop"}, - ] - }) - - - this.elementsToShow = [ - new ImageCarouselWithUploadConstructor(), - new TagRenderingOptions({ - question: "What is the name of this bicycle shop?", - freeform: { - key: "name", - renderTemplate: "The name of this bicycle shop is {name}", - template: "The name of this bicycle shop is $$$" - } - }), - - new TagRenderingOptions({ - question: "Can one buy a bike here?", - mappings: [ - {k: this.sellsBikes, txt: "Bikes are sold here"}, - {k: new Tag("service:bicycle:retail", "no"), txt: "No bikes are sold here"}, - ] - }), - - new TagRenderingOptions({ - question: "Can one buy a new bike here?", - mappings: [ - {k: new Tag("service:bicycle:second_hand", "yes"), txt: "Second-hand bikes are sold here"}, - {k: new Tag("service:bicycle:second_hand", "only"), txt: "All bicycles sold here are second-hand"}, - {k: new Tag("service:bicycle:second_hand", "no"), txt: "Only brand new bikes are sold here"}, - ] - }).OnlyShowIf(this.sellsBikes), - - - new TagRenderingOptions({ - question: "Does this shop repair bicycles?", - mappings: [ - {k: this.repairsBikes, txt: "Bikes are repaired here, by the shop owner (for a fee)"}, - {k: new Tag("service:bicycle:repair", "only_sold"), txt: "Only bikes that were bought here, are repaired"}, - {k: new Tag("service:bicycle:repair", "brand"), txt: "Only bikes of a fixed brand are repaired here"}, - {k: new Tag("service:bicycle:repair", "no"), txt: "Bikes are not repaired here"}, - ] - }), - - new TagRenderingOptions({ - question: "Can one hire a new bike here?", - mappings: [ - {k: new Tag("service:bicycle:rental", "yes"), txt: "Bikes can be rented here"}, - {k: new Tag("service:bicycle:rental", "no"), txt: "Bikes cannot be rented here"}, - ] - }).OnlyShowIf(this.sellsBikes), - - new TagRenderingOptions({ - question: "Are there tools here so that one can repair their own bike?", - mappings: [ - {k: new Tag("service:bicycle:diy", "yes"), txt: "Tools for DIY are available here"}, - {k: new Tag("service:bicycle:diy", "no"), txt: "No tools for DIY are available here"}, - ] - }), - ] - - - this.style = (tags) => { - let icon = "assets/bike/repair_shop.svg"; - - if (this.sellsBikes.matchesProperties(tags)) { - icon = "assets/bike/shop.svg"; - } - - return { - color: "#ff0000", - icon: L.icon({ - iconUrl: icon, - iconSize: [50, 50], - iconAnchor: [25, 50] - }) - } - } - - - } - - -} \ No newline at end of file diff --git a/Customizations/Layers/BikeShops.ts b/Customizations/Layers/BikeShops.ts index 9e51887..0cec27f 100644 --- a/Customizations/Layers/BikeShops.ts +++ b/Customizations/Layers/BikeShops.ts @@ -1,6 +1,6 @@ import { LayerDefinition } from "../LayerDefinition"; import Translations from "../../UI/i18n/Translations"; -import { Tag } from "../../Logic/TagsFilter"; +import {And, Tag} from "../../Logic/TagsFilter"; import FixedText from "../Questions/FixedText"; import { ImageCarouselWithUploadConstructor } from "../../UI/Image/ImageCarouselWithUpload"; import * as L from "leaflet"; @@ -20,33 +20,42 @@ export default class BikeShops extends LayerDefinition { constructor() { super(); - this.name = Translations.t.cyclofix.shop.name.txt + 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: this.sellsBikes, txt: "Bicycle shop"}, + {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}, - {k: new Tag("service:bicycle:retail", ""), txt: Translations.t.cyclofix.shop.title}, ] }) this.elementsToShow = [ new ImageCarouselWithUploadConstructor(), - //new ParkingOperator(), + new ShopName(), new ShopRetail(), new ShopRental(), new ShopRepair(), new ShopPump(), new ShopDiy(), - new ShopName(), new ShopSecondHand() ] } diff --git a/Customizations/Layers/BikeStations.ts b/Customizations/Layers/BikeStations.ts index 4d4ae71..2fd80c2 100644 --- a/Customizations/Layers/BikeStations.ts +++ b/Customizations/Layers/BikeStations.ts @@ -23,7 +23,7 @@ export default class BikeStations extends LayerDefinition { constructor() { super(); - this.name = Translations.t.cyclofix.station.name.txt; + this.name = Translations.t.cyclofix.station.name; this.icon = "./assets/wrench.svg"; this.overpassFilter = new And([ @@ -37,7 +37,8 @@ export default class BikeStations extends LayerDefinition { this.minzoom = 13; this.style = this.generateStyleFunction(); - this.title = new FixedText(Translations.t.cyclofix.station.title.txt) + this.title = new FixedText(Translations.t.cyclofix.station.title) + this.wayHandling = LayerDefinition.WAYHANDLING_CENTER_AND_WAY this.elementsToShow = [ new ImageCarouselWithUploadConstructor(), @@ -62,24 +63,19 @@ 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" - } else { - if (isOperational) { - iconName = "pump.svg" - } else { - iconName = "pump_broken.svg" - } - } - } else { - if (!self.pump.matchesProperties(properties)) { + 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 { - iconName = "repair_station.svg" + iconName = "broken_pump.svg" } } + const iconUrl = `./assets/bike/${iconName}` return { color: "#00bb00", diff --git a/Customizations/Layers/DrinkingWater.ts b/Customizations/Layers/DrinkingWater.ts index 7bdd632..1dd0c89 100644 --- a/Customizations/Layers/DrinkingWater.ts +++ b/Customizations/Layers/DrinkingWater.ts @@ -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(); diff --git a/Customizations/Layers/NatureReserves.ts b/Customizations/Layers/NatureReserves.ts index 6adde31..a0a1d94 100644 --- a/Customizations/Layers/NatureReserves.ts +++ b/Customizations/Layers/NatureReserves.ts @@ -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(), diff --git a/Customizations/Layouts/Cyclofix.ts b/Customizations/Layouts/Cyclofix.ts index 87753c0..82d56dd 100644 --- a/Customizations/Layouts/Cyclofix.ts +++ b/Customizations/Layouts/Cyclofix.ts @@ -2,10 +2,8 @@ import {Layout} from "../Layout"; import BikeParkings from "../Layers/BikeParkings"; import BikeServices from "../Layers/BikeStations"; import BikeShops from "../Layers/BikeShops"; -import {GhostBike} from "../Layers/GhostBike"; import Translations from "../../UI/i18n/Translations"; import {DrinkingWater} from "../Layers/DrinkingWater"; -import {BikeShop} from "../Layers/BikeShop" import Combine from "../../UI/Base/Combine"; @@ -15,7 +13,7 @@ export default class Cyclofix extends Layout { "pomp", ["en", "nl", "fr"], Translations.t.cyclofix.title, - [new BikeServices(), new BikeShop(), new DrinkingWater(), new BikeParkings()], + [new BikeServices(), new BikeShops(), new DrinkingWater(), new BikeParkings()], 16, 50.8465573, 4.3516970, diff --git a/Customizations/Questions/bike/ParkingOperator.ts b/Customizations/Questions/bike/ParkingOperator.ts index effd863..251bcb5 100644 --- a/Customizations/Questions/bike/ParkingOperator.ts +++ b/Customizations/Questions/bike/ParkingOperator.ts @@ -11,9 +11,9 @@ export default class ParkingOperator extends TagRenderingOptions { question: to.question.Render(), freeform: { key: "operator", - template: to.template.txt, - renderTemplate: to.render.txt, - placeholder: Translations.t.cyclofix.freeFormPlaceholder.txt + template: to.template, + renderTemplate: to.render, + placeholder: Translations.t.cyclofix.freeFormPlaceholder }, mappings: [ {k: new Tag("operator", "KU Leuven"), txt: "KU Leuven"}, diff --git a/Customizations/Questions/bike/PumpManual.ts b/Customizations/Questions/bike/PumpManual.ts index c87ba23..44aa4ee 100644 --- a/Customizations/Questions/bike/PumpManual.ts +++ b/Customizations/Questions/bike/PumpManual.ts @@ -8,10 +8,10 @@ export default class PumpManual extends TagRenderingOptions { const to = Translations.t.cyclofix.station.electric super({ priority: 5, - question: to.question.Render(), + question: to.question, mappings: [ - {k: new Tag("manual", "yes"), txt: to.manual.Render()}, - {k: new Tag("manual", "no"), txt: to.electric.Render()} + {k: new Tag("manual", "yes"), txt: to.manual}, + {k: new Tag("manual", "no"), txt: to.electric} ] }); } diff --git a/Customizations/Questions/bike/PumpOperational.ts b/Customizations/Questions/bike/PumpOperational.ts index a539b0a..ac84f0c 100644 --- a/Customizations/Questions/bike/PumpOperational.ts +++ b/Customizations/Questions/bike/PumpOperational.ts @@ -7,10 +7,10 @@ export default class PumpOperational extends TagRenderingOptions { constructor() { const to = Translations.t.cyclofix.station.operational super({ - question: to.question.Render(), + question: to.question, mappings: [ - {k: new Tag("service:bicycle:pump:operational_status","broken"), txt: to.broken.txt}, - {k: new Tag("service:bicycle:pump:operational_status",""), txt: to.operational.txt} + {k: new Tag("service:bicycle:pump:operational_status","broken"), txt: to.broken}, + {k: new Tag("service:bicycle:pump:operational_status",""), txt: to.operational} ] }); } diff --git a/Customizations/Questions/bike/PumpValves.ts b/Customizations/Questions/bike/PumpValves.ts index 800a9b3..51247d0 100644 --- a/Customizations/Questions/bike/PumpValves.ts +++ b/Customizations/Questions/bike/PumpValves.ts @@ -7,21 +7,21 @@ export default class PumpValves extends TagRenderingOptions{ constructor() { const to = Translations.t.cyclofix.station.valves super({ - question: to.question.Render(), + question: to.question, mappings: [ { k: new Tag("valves", " sclaverand;schrader;dunlop"), - txt: to.default.Render() + txt: to.default }, - {k: new Tag("valves", "dunlop"), txt: to.dunlop.Render()}, - {k: new Tag("valves", "sclaverand"), txt: to.sclaverand.Render()}, - {k: new Tag("valves", "auto"), txt: to.auto.Render()}, + {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: to.template.txt, - renderTemplate: to.render.txt + template: to.template, + renderTemplate: to.render } }); } diff --git a/Customizations/Questions/bike/ShopDiy.ts b/Customizations/Questions/bike/ShopDiy.ts index d4e7714..4821731 100644 --- a/Customizations/Questions/bike/ShopDiy.ts +++ b/Customizations/Questions/bike/ShopDiy.ts @@ -6,13 +6,13 @@ import Translations from "../../../UI/i18n/Translations"; export default class ShopPump extends TagRenderingOptions { constructor() { const key = 'service:bicycle:diy' - const to = Translations.t.cylofix.shop.diy + const to = Translations.t.cyclofix.shop.diy super({ priority: 5, - question: to.question.Render(), + question: to.question, mappings: [ - {k: new Tag(key, "yes"), txt: to.yes.Render()}, - {k: new Tag(key, "no"), txt: to.no.Render()}, + {k: new Tag(key, "yes"), txt: to.yes}, + {k: new Tag(key, "no"), txt: to.no}, ] }); } diff --git a/Customizations/Questions/bike/ShopName.ts b/Customizations/Questions/bike/ShopName.ts index 7f849fc..f270268 100644 --- a/Customizations/Questions/bike/ShopName.ts +++ b/Customizations/Questions/bike/ShopName.ts @@ -4,14 +4,14 @@ import Translations from "../../../UI/i18n/Translations"; export default class ShopPump extends TagRenderingOptions { constructor() { - const to = Translations.t.cylofix.shop.qName + const to = Translations.t.cyclofix.shop.qName super({ priority: 5, - question: to.question.Render(), + question: to.question, freeform: { key: "name", - renderTemplate: to.render.txt, - template: to.template.txt + renderTemplate: to.render, + template: to.template } }) } diff --git a/Customizations/Questions/bike/ShopPump.ts b/Customizations/Questions/bike/ShopPump.ts index a64f13b..6ed14da 100644 --- a/Customizations/Questions/bike/ShopPump.ts +++ b/Customizations/Questions/bike/ShopPump.ts @@ -9,10 +9,10 @@ export default class ShopPump extends TagRenderingOptions { const to = Translations.t.cyclofix.shop.pump super({ priority: 5, - question: to.question.Render(), + question: to.question, mappings: [ - {k: new Tag(key, "yes"), txt: to.yes.Render()}, - {k: new Tag(key, "no"), txt: to.no.Render()}, + {k: new Tag(key, "yes"), txt: to.yes}, + {k: new Tag(key, "no"), txt: to.no}, ] }); } diff --git a/Customizations/Questions/bike/ShopRental.ts b/Customizations/Questions/bike/ShopRental.ts index 7eabfd3..eac2677 100644 --- a/Customizations/Questions/bike/ShopRental.ts +++ b/Customizations/Questions/bike/ShopRental.ts @@ -9,10 +9,10 @@ export default class ShopRental extends TagRenderingOptions { const to = Translations.t.cyclofix.shop.rental super({ priority: 5, - question: to.question.Render(), + question: to.question, mappings: [ - {k: new Tag(key, "yes"), txt: to.yes.Render()}, - {k: new Tag(key, "no"), txt: to.no.Render()}, + {k: new Tag(key, "yes"), txt: to.yes}, + {k: new Tag(key, "no"), txt: to.no}, ] }); } diff --git a/Customizations/Questions/bike/ShopRepair.ts b/Customizations/Questions/bike/ShopRepair.ts index e3caa32..2a1a375 100644 --- a/Customizations/Questions/bike/ShopRepair.ts +++ b/Customizations/Questions/bike/ShopRepair.ts @@ -9,12 +9,12 @@ export default class ShopRepair extends TagRenderingOptions { const to = Translations.t.cyclofix.shop.repair super({ priority: 5, - question: to.question.Render(), + question: to.question, mappings: [ - {k: new Tag(key, "yes"), txt: to.yes.Render()}, - {k: new Tag(key, "only_sold"), txt: to.sold.Render()}, - {k: new Tag(key, "brand"), txt: to.brand.Render()}, - {k: new Tag(key, "no"), txt: to.no.Render()}, + {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}, ] }); } diff --git a/Customizations/Questions/bike/ShopSecondHand.ts b/Customizations/Questions/bike/ShopSecondHand.ts index 90b9780..b95da49 100644 --- a/Customizations/Questions/bike/ShopSecondHand.ts +++ b/Customizations/Questions/bike/ShopSecondHand.ts @@ -6,14 +6,14 @@ import Translations from "../../../UI/i18n/Translations"; export default class ShopPump extends TagRenderingOptions { constructor() { const key = 'service:bicycle:second_hand' - const to = Translations.t.cylofix.shop.secondHand + const to = Translations.t.cyclofix.shop.secondHand super({ priority: 5, - question: to.question.Render(), + question: to.question, mappings: [ - {k: new Tag(key, "yes"), txt: to.yes.Render()}, - {k: new Tag(key, "no"), txt: to.no.Render()}, - {k: new Tag(key, "only"), txt: to.only.Render()}, + {k: new Tag(key, "yes"), txt: to.yes}, + {k: new Tag(key, "no"), txt: to.no}, + {k: new Tag(key, "only"), txt: to.only}, ] }); } diff --git a/Customizations/Questions/bike/StationChain.ts b/Customizations/Questions/bike/StationChain.ts index 2d76275..7930d3b 100644 --- a/Customizations/Questions/bike/StationChain.ts +++ b/Customizations/Questions/bike/StationChain.ts @@ -8,10 +8,10 @@ export default class StationChain extends TagRenderingOptions { const to = Translations.t.cyclofix.station.chain super({ priority: 5, - question: to.question.Render(), + question: to.question, mappings: [ - {k: new Tag("service:bicycle:chain_tool", "yes"), txt: to.yes.Render()}, - {k: new Tag("service:bicycle:chain_tool", "no"), txt: to.no.Render()}, + {k: new Tag("service:bicycle:chain_tool", "yes"), txt: to.yes}, + {k: new Tag("service:bicycle:chain_tool", "no"), txt: to.no}, ] }); } diff --git a/Customizations/Questions/bike/StationOperator.ts b/Customizations/Questions/bike/StationOperator.ts index 94f1efb..5d09434 100644 --- a/Customizations/Questions/bike/StationOperator.ts +++ b/Customizations/Questions/bike/StationOperator.ts @@ -18,8 +18,8 @@ export default class BikeStationOperator extends TagRenderingOptions { ], freeform: { key: "operator", - template: to.template.txt, - renderTemplate: to.render.txt, + template: to.template, + renderTemplate: to.render, placeholder: "organisatie" } }); diff --git a/Customizations/TagRendering.ts b/Customizations/TagRendering.ts index dad40bf..a426fa3 100644 --- a/Customizations/TagRendering.ts +++ b/Customizations/TagRendering.ts @@ -28,8 +28,8 @@ export class TagRenderingOptions implements TagDependantUIElementConstructor { freeform?: { key: string; tagsPreprocessor?: (tags: any) => any; - template: string; - renderTemplate: string; + template: string | UIElement; + renderTemplate: string | UIElement; placeholder?: string | UIElement; extraTags?: TagsFilter }; @@ -77,8 +77,9 @@ export class TagRenderingOptions implements TagDependantUIElementConstructor { * In the question, it'll offer a textfield */ freeform?: { - key: string, template: string, - renderTemplate: string + key: string, + template: string | UIElement, + renderTemplate: string | UIElement placeholder?: string | UIElement, extraTags?: TagsFilter, }, @@ -141,14 +142,13 @@ class TagRendering extends UIElement implements TagDependantUIElement { private _question: UIElement; - private _mapping: { k: TagsFilter, txt: UIElement, priority?: number }[]; - private _renderMapping: { k: TagsFilter, txt: UIElement, priority?: number }[]; + private _mapping: { k: TagsFilter, txt: string | UIElement, priority?: number }[]; private _tagsPreprocessor?: ((tags: any) => any); private _freeform: { - key: string, template: string, - renderTemplate: string, - + key: string, + template: string | UIElement, + renderTemplate: string | UIElement, placeholder?: string | UIElement, extraTags?: TagsFilter }; @@ -171,8 +171,9 @@ class TagRendering extends UIElement implements TagDependantUIElement { question?: string | UIElement, freeform?: { - key: string, template: string, - renderTemplate: string + key: string, + template: string | UIElement, + renderTemplate: string | UIElement, placeholder?: string | UIElement, extraTags?: TagsFilter, }, @@ -205,14 +206,13 @@ class TagRendering extends UIElement implements TagDependantUIElement { }; this._mapping = []; - this._renderMapping = []; this._freeform = options.freeform; for (const choice of options.mappings ?? []) { let choiceSubbed = { k: choice.k, - txt: this.ApplyTemplate(choice.txt), + txt: choice.txt, priority: choice.priority }; @@ -220,7 +220,7 @@ class TagRendering extends UIElement implements TagDependantUIElement { choiceSubbed = { k: choice.k.substituteValues( options.tagsPreprocessor(this._source.data)), - txt: this.ApplyTemplate(choice.txt), + txt: choice.txt, priority: choice.priority } } @@ -270,7 +270,7 @@ class TagRendering extends UIElement implements TagDependantUIElement { } else { return ""+Translations.t.general.skip.R()+""; } - }); + }, [Locale.language]); // And at last, set up the skip button this._skipButton = new VariableUiElement(cancelContents).onClick(cancel) ; } @@ -278,8 +278,9 @@ class TagRendering extends UIElement implements TagDependantUIElement { private InputElementFor(options: { freeform?: { - key: string, template: string, - renderTemplate: string + key: string, + template: string | UIElement, + renderTemplate: string | UIElement, placeholder?: string | UIElement, extraTags?: TagsFilter, }, @@ -368,7 +369,7 @@ class TagRendering extends UIElement implements TagDependantUIElement { toString: toString }); - const prepost = freeform.template.split("$$$"); + const prepost = Translations.W(freeform.template).InnerRender().split("$$$"); return new InputElementWrapper(prepost[0], textField, prepost[1]); } @@ -376,7 +377,7 @@ class TagRendering extends UIElement implements TagDependantUIElement { 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; } @@ -386,10 +387,9 @@ class TagRendering extends UIElement implements TagDependantUIElement { } private CurrentValue(): TagsFilter { - console.log("Creating a current value...") 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 oneOnOneElement.k; } @@ -398,7 +398,6 @@ class TagRendering extends UIElement implements TagDependantUIElement { return undefined; } - console.log("Got a freeform tag:", new Tag(this._freeform.key, this._source.data[this._freeform.key])) return new Tag(this._freeform.key, this._source.data[this._freeform.key]); } @@ -431,7 +430,7 @@ class TagRendering extends UIElement implements TagDependantUIElement { let highestScore = -100; let highestTemplate = undefined; - for (const oneOnOneElement of this._mapping.concat(this._renderMapping)) { + 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 @@ -457,7 +456,6 @@ class TagRendering extends UIElement implements TagDependantUIElement { } InnerRender(): string { - if (this.IsQuestioning() || this._editMode.data) { // Not yet known or questioning, we have to ask a question @@ -499,10 +497,13 @@ class TagRendering extends UIElement implements TagDependantUIElement { } private ApplyTemplate(template: string | UIElement): UIElement { - if (template instanceof UIElement) { - return template; + 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)); } diff --git a/Logic/FilteredLayer.ts b/Logic/FilteredLayer.ts index 76a9d9c..f1b530c 100644 --- a/Logic/FilteredLayer.ts +++ b/Logic/FilteredLayer.ts @@ -6,6 +6,7 @@ import { Changes } from "./Changes"; import L from "leaflet" import { GeoOperations } from "./GeoOperations"; import { UIElement } from "../UI/UIElement"; +import {LayerDefinition} from "../Customizations/LayerDefinition"; /*** * A filtered layer is a layer which offers a 'set-data' function @@ -18,7 +19,7 @@ import { UIElement } from "../UI/UIElement"; */ export class FilteredLayer { - public readonly name: string; + public readonly name: string | UIElement; public readonly filters: TagsFilter; public readonly isDisplayed: UIEventSource = new UIEventSource(true); @@ -32,6 +33,7 @@ export class FilteredLayer { /** The featurecollection from overpass */ private _dataFromOverpass; + private _wayHandling: number; /** List of new elements, geojson features */ private _newElements = []; @@ -39,19 +41,21 @@ export class FilteredLayer { * The leaflet layer object which should be removed on rerendering */ private _geolayer; - private _selectedElement: UIEventSource; - private _showOnPopup: (tags: UIEventSource) => UIElement; + private _selectedElement: UIEventSource<{feature: any}>; + private _showOnPopup: (tags: UIEventSource, feature: any) => UIElement; constructor( - name: string, + name: string | UIElement, map: Basemap, storage: ElementStorage, changes: Changes, filters: TagsFilter, maxAllowedOverlap: number, + wayHandling: number, style: ((properties) => any), - selectedElement: UIEventSource, - showOnPopup: ((tags: UIEventSource) => UIElement) + selectedElement: UIEventSource<{feature: any}>, + showOnPopup: ((tags: UIEventSource, feature: any) => UIElement) ) { + this._wayHandling = wayHandling; this._selectedElement = selectedElement; this._showOnPopup = showOnPopup; @@ -66,6 +70,7 @@ export class FilteredLayer { this._style = style; this._storage = storage; this._maxAllowedOverlap = maxAllowedOverlap; + const self = this; this.isDisplayed.addCallback(function (isDisplayed) { if (self._geolayer !== undefined && self._geolayer !== null) { @@ -86,10 +91,18 @@ export class FilteredLayer { public SetApplicableData(geojson: any): any { const leftoverFeatures = []; const selfFeatures = []; - for (const feature of geojson.features) { + for (let feature of geojson.features) { // feature.properties contains all the properties var tags = TagUtils.proprtiesToKV(feature.properties); if (this.filters.matches(tags)) { + feature.properties["_surface"] = GeoOperations.surfaceAreaInSqMeters(feature); + if(feature.geometry.type !== "Point"){ + if(this._wayHandling === LayerDefinition.WAYHANDLING_CENTER_AND_WAY){ + selfFeatures.push(GeoOperations.centerpoint(feature)); + }else if(this._wayHandling === LayerDefinition.WAYHANDLING_CENTER_ONLY){ + feature = GeoOperations.centerpoint(feature); + } + } selfFeatures.push(feature); } else { leftoverFeatures.push(feature); @@ -201,8 +214,8 @@ export class FilteredLayer { layer.on("click", function (e) { console.log("Selected ", feature) - self._selectedElement.setData(feature.properties); - const uiElement = self._showOnPopup(eventSource); + self._selectedElement.setData({feature: feature}); + const uiElement = self._showOnPopup(eventSource, feature); const popup = L.popup() .setContent(uiElement.Render()) .setLatLng(e.latlng) diff --git a/Logic/GeoOperations.ts b/Logic/GeoOperations.ts index 91687e9..4ed677e 100644 --- a/Logic/GeoOperations.ts +++ b/Logic/GeoOperations.ts @@ -6,6 +6,15 @@ export class GeoOperations { return turf.area(feature); } + static centerpoint(feature: any) + { + const newFeature= turf.center(feature); + newFeature.properties = feature.properties; + newFeature.id = feature.id; + + return newFeature; + } + static featureIsContainedInAny(feature: any, shouldNotContain: any[], maxOverlapPercentage: number): boolean { diff --git a/Logic/Imgur.ts b/Logic/Imgur.ts index b41929f..7d1b0ed 100644 --- a/Logic/Imgur.ts +++ b/Logic/Imgur.ts @@ -8,6 +8,7 @@ export class Imgur { title: string, description: string, blobs: FileList, handleSuccessfullUpload: ((imageURL: string) => void), allDone: (() => void), + onFail: ((reason: string) => void), offset:number = 0) { if (blobs.length == offset) { @@ -24,7 +25,8 @@ export class Imgur { handleSuccessfullUpload, allDone, offset + 1); - } + }, + onFail ); @@ -74,7 +76,8 @@ export class Imgur { } static uploadImage(title: string, description: string, blob, - handleSuccessfullUpload: ((imageURL: string) => void)) { + handleSuccessfullUpload: ((imageURL: string) => void), + onFail: (reason:string) => void) { const apiUrl = 'https://api.imgur.com/3/image'; const apiKey = '7070e7167f0a25a'; @@ -105,7 +108,8 @@ export class Imgur { response = JSON.parse(response); handleSuccessfullUpload(response.data.link); }).fail((reason) => { - console.log("Uploading to IMGUR failed", reason) + console.log("Uploading to IMGUR failed", reason); + onFail(reason) }); } diff --git a/Logic/OsmConnection.ts b/Logic/OsmConnection.ts index 04d2979..e07751d 100644 --- a/Logic/OsmConnection.ts +++ b/Logic/OsmConnection.ts @@ -17,7 +17,6 @@ export class UserDetails { export class OsmConnection { - private auth = new osmAuth({ oauth_consumer_key: 'hivV7ec2o49Two8g9h8Is1VIiVOgxQ1iYexCbvem', oauth_secret: 'wDBRTCem0vxD7txrg1y6p5r8nvmz8tAhET7zDASI', diff --git a/Logic/OsmImageUploadHandler.ts b/Logic/OsmImageUploadHandler.ts index 3cabdbc..cd242c7 100644 --- a/Logic/OsmImageUploadHandler.ts +++ b/Logic/OsmImageUploadHandler.ts @@ -48,12 +48,18 @@ export class OsmImageUploadHandler { title: title, description: description, handleURL: function (url) { - let freeIndex = 0; - while (tags["image:" + freeIndex] !== undefined) { - freeIndex++; + + let key = "image"; + if (tags["image"] !== undefined) { + + let freeIndex = 0; + while (tags["image:" + freeIndex] !== undefined) { + freeIndex++; + } + key = "image:" + freeIndex; } - console.log("Adding image:" + freeIndex, url); - changes.addChange(tags.id, "image:" + freeIndex, url); + console.log("Adding image:" + key, url); + changes.addChange(tags.id, key, url); self._slideShow.MoveTo(-1); // set the last (thus newly added) image) to view }, allDone: function () { diff --git a/Logic/StrayClickHandler.ts b/Logic/StrayClickHandler.ts index 8509fd4..59c50de 100644 --- a/Logic/StrayClickHandler.ts +++ b/Logic/StrayClickHandler.ts @@ -15,7 +15,7 @@ export class StrayClickHandler { constructor( basemap: Basemap, - selectElement: UIEventSource, + selectElement: UIEventSource<{ feature: any }>, fullScreenMessage: UIEventSource, uiToShow: (() => UIElement)) { this._basemap = basemap; diff --git a/README.md b/README.md index 4ca29f4..4a48c3a 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,9 @@ When a map feature is clicked, a popup shows the information, images and questio The answers given by the user are sent (after a few seconds) to OpenStreetMap directly - if the user is logged in. If not logged in, the user is prompted to do so. +The UI-event-source is a class where the entire system is built upon, it acts as an observable object: another object can register for changes to update when needed. + + ### Searching images Images are fetched from: diff --git a/UI/CenterMessageBox.ts b/UI/CenterMessageBox.ts index ff19db4..25ca95c 100644 --- a/UI/CenterMessageBox.ts +++ b/UI/CenterMessageBox.ts @@ -41,11 +41,11 @@ export class CenterMessageBox extends UIElement { return this._centermessage.data; } if (this._queryRunning.data) { - return Translations.t.centerMessage.loadingData.txt; + return Translations.t.centerMessage.loadingData.Render(); } else if (this._zoomInMore.data) { - return Translations.t.centerMessage.zoomIn.txt; + return Translations.t.centerMessage.zoomIn.Render(); } - return Translations.t.centerMessage.ready.txt; + return Translations.t.centerMessage.ready.Render(); } diff --git a/UI/FeatureInfoBox.ts b/UI/FeatureInfoBox.ts index 89d7bce..c9477ef 100644 --- a/UI/FeatureInfoBox.ts +++ b/UI/FeatureInfoBox.ts @@ -11,9 +11,17 @@ import {OsmLink} from "../Customizations/Questions/OsmLink"; import {WikipediaLink} from "../Customizations/Questions/WikipediaLink"; import {And} from "../Logic/TagsFilter"; import {TagDependantUIElement, TagDependantUIElementConstructor} from "../Customizations/UIElementConstructor"; +import Translations from "./i18n/Translations"; export class FeatureInfoBox extends UIElement { + /** + * The actual GEOJSON-object, with geometry and stuff + */ + private _feature: any; + /** + * The tags, wrapped in a global event source + */ private _tagsES: UIEventSource; private _changes: Changes; private _userDetails: UIEventSource; @@ -24,11 +32,14 @@ export class FeatureInfoBox extends UIElement { private _wikipedialink: UIElement; - private _infoboxes: TagDependantUIElement[]; private _questions: QuestionPicker; + private _oneSkipped = Translations.t.general.oneSkippedQuestion.Clone(); + private _someSkipped = Translations.t.general.skippedQuestions.Clone(); + constructor( + feature: any, tagsES: UIEventSource, title: TagRenderingOptions | UIElement, elementsToShow: TagDependantUIElementConstructor[], @@ -36,19 +47,34 @@ export class FeatureInfoBox extends UIElement { userDetails: UIEventSource ) { super(tagsES); + this._feature = feature; this._tagsES = tagsES; this._changes = changes; this._userDetails = userDetails; this.ListenTo(userDetails); - const deps = {tags:this._tagsES , changes:this._changes} - + const deps = {tags: this._tagsES, changes: this._changes} + this._infoboxes = []; elementsToShow = elementsToShow ?? [] + + const self = this; for (const tagRenderingOption of elementsToShow) { - this._infoboxes.push( + self._infoboxes.push( tagRenderingOption.construct(deps)); } + function initTags() { + self._infoboxes = [] + for (const tagRenderingOption of elementsToShow) { + self._infoboxes.push( + tagRenderingOption.construct(deps)); + } + self.Update(); + } + + this._someSkipped.onClick(initTags) + this._oneSkipped.onClick(initTags) + title = title ?? new TagRenderingOptions( { @@ -71,13 +97,16 @@ export class FeatureInfoBox extends UIElement { const info = []; - const questions : TagDependantUIElement[] = []; - + const questions: TagDependantUIElement[] = []; + let skippedQuestions = 0; for (const infobox of this._infoboxes) { if (infobox.IsKnown()) { info.push(infobox); } else if (infobox.IsQuestioning()) { questions.push(infobox); + } else { + // This question is neither known nor questioning -> it was skipped + skippedQuestions++; } } @@ -97,6 +126,10 @@ export class FeatureInfoBox extends UIElement { } questionsHtml = mostImportantQuestion.Render(); + } else if (skippedQuestions == 1) { + questionsHtml = this._oneSkipped.Render(); + } else if (skippedQuestions > 0) { + questionsHtml = this._someSkipped.Render(); } return "
" + diff --git a/UI/ImageUploadFlow.ts b/UI/ImageUploadFlow.ts index 110fcd0..048b32a 100644 --- a/UI/ImageUploadFlow.ts +++ b/UI/ImageUploadFlow.ts @@ -11,12 +11,13 @@ export class ImageUploadFlow extends UIElement { private _licensePicker: UIElement; private _selectedLicence: UIEventSource; private _isUploading: UIEventSource = new UIEventSource(0) + private _didFail: UIEventSource = new UIEventSource(false); private _uploadOptions: (license: string) => { title: string; description: string; handleURL: (url: string) => void; allDone: (() => void) }; private _userdetails: UIEventSource; constructor( userInfo: UIEventSource, - preferedLicense : UIEventSource, + preferedLicense: UIEventSource, uploadOptions: ((license: string) => { title: string, @@ -30,6 +31,7 @@ export class ImageUploadFlow extends UIElement { this.ListenTo(userInfo); this._uploadOptions = uploadOptions; this.ListenTo(this._isUploading); + this.ListenTo(this._didFail); const licensePicker = new DropDown(Translations.t.image.willBePublished, [ @@ -59,6 +61,10 @@ export class ImageUploadFlow extends UIElement { if (this._isUploading.data > 0) { uploadingMessage = "Uploading multiple pictures, " + this._isUploading.data + " left..." } + + if(this._didFail.data){ + uploadingMessage += "Some images failed to upload. Imgur migth be down or you might block third-party API's (e.g. by using Brave or UMatrix)
" + } return "" + "
" + @@ -68,20 +74,20 @@ export class ImageUploadFlow extends UIElement { "
" + "upload image " + `${Translations.t.image.addPicture.R()}` + - "
"+ + "
" + "
" + this._licensePicker.Render() + "
" + uploadingMessage + "" + - + "" + - + "
" ; } @@ -116,11 +122,12 @@ export class ImageUploadFlow extends UIElement { function () { console.log("All uploads completed") opts.allDone(); + }, + function(failReason) { + } ) } } } - - } \ No newline at end of file diff --git a/UI/SimpleAddUI.ts b/UI/SimpleAddUI.ts index 2aa3e0e..5d0bc6e 100644 --- a/UI/SimpleAddUI.ts +++ b/UI/SimpleAddUI.ts @@ -15,17 +15,17 @@ export class SimpleAddUI extends UIElement { private _addButtons: UIElement[]; private _lastClickLocation: UIEventSource<{ lat: number; lon: number }>; private _changes: Changes; - private _selectedElement: UIEventSource; + private _selectedElement: UIEventSource<{feature: any}>; private _dataIsLoading: UIEventSource; private _userDetails: UIEventSource; constructor(zoomlevel: UIEventSource<{ zoom: number }>, lastClickLocation: UIEventSource<{ lat: number, lon: number }>, changes: Changes, - selectedElement: UIEventSource, + selectedElement: UIEventSource<{feature: any}>, dataIsLoading: UIEventSource, userDetails: UIEventSource, - addButtons: { name: string; icon: string; tags: Tag[]; layerToAddTo: FilteredLayer }[], + addButtons: { name: UIElement; icon: string; tags: Tag[]; layerToAddTo: FilteredLayer }[], ) { super(zoomlevel); this._zoomlevel = zoomlevel; @@ -42,21 +42,20 @@ export class SimpleAddUI extends UIElement { //