diff --git a/assets/layers/diets/diets.json b/assets/layers/diets/diets.json index 0bc3795600..cac1388a31 100644 --- a/assets/layers/diets/diets.json +++ b/assets/layers/diets/diets.json @@ -12,9 +12,16 @@ "hidden" ], "render": { - "en": "Dietary options", - "cs": "Dietní možnosti", - "nl": "Dieetopties" + "special": { + "type": "show_icons", + "labels": "diets_content", + "class": "inline-flex float-right" + }, + "before": { + "en": "Dietary options", + "cs": "Dietní možnosti", + "nl": "Dieetopties" + } } }, { diff --git a/assets/layers/historic_rolling_stock/historic_rolling_stock.json b/assets/layers/historic_rolling_stock/historic_rolling_stock.json index ffeea6dfbc..21eb1633f3 100644 --- a/assets/layers/historic_rolling_stock/historic_rolling_stock.json +++ b/assets/layers/historic_rolling_stock/historic_rolling_stock.json @@ -131,7 +131,8 @@ }, "tags": [ "historic=locomotive" - ] + ], + "snapToLayer": ["railway"] }, { "title": { @@ -144,7 +145,9 @@ }, "tags": [ "historic=railway_car" - ] + ], + "snapToLayer": ["railway"] + }, { "title": { @@ -157,7 +160,9 @@ }, "tags": [ "historic=minecart" - ] + ], + "snapToLayer": ["railway"] + } ], "tagRenderings": [ diff --git a/assets/layers/icons/icons.json b/assets/layers/icons/icons.json index 54ebd45d54..93e9e91ec0 100644 --- a/assets/layers/icons/icons.json +++ b/assets/layers/icons/icons.json @@ -430,10 +430,10 @@ } }, { - "id": "favourite_icon", + "condition": "_favourite=yes", "description": "Only for rendering", "icon": "circle:white;heart:red", - "condition": "_favourite=yes", + "id": "favourite_icon", "metacondition": "__showTimeSensitiveIcons!=no" }, { diff --git a/assets/layers/last_click/last_click.json b/assets/layers/last_click/last_click.json index 3185c513fb..6aaa191159 100644 --- a/assets/layers/last_click/last_click.json +++ b/assets/layers/last_click/last_click.json @@ -217,8 +217,8 @@ }, { "id": "debug", - "render": "{all_tags()}", - "metacondition": "__featureSwitchIsDebugging=true" + "metacondition": "__featureSwitchIsDebugging=true", + "render": "{all_tags()}" } ], "filter": [ diff --git a/assets/layers/note/note.json b/assets/layers/note/note.json index b1dd36f327..eedef08c68 100644 --- a/assets/layers/note/note.json +++ b/assets/layers/note/note.json @@ -114,9 +114,9 @@ "lineRendering": [], "tagRenderings": [ { + "classes": "p-0", "id": "conversation", - "render": "{visualize_note_comments()}", - "classes": "p-0" + "render": "{visualize_note_comments()}" }, { "id": "add_image", diff --git a/assets/layers/osm_community_index/osm_community_index.json b/assets/layers/osm_community_index/osm_community_index.json index 1c605ca388..150fc6ecd9 100644 --- a/assets/layers/osm_community_index/osm_community_index.json +++ b/assets/layers/osm_community_index/osm_community_index.json @@ -66,16 +66,16 @@ ], "tagRenderings": [ { - "id": "country_name", + "condition": "level=country", "description": "The name of the country", - "render": "{nameEn} {emojiFlag}", - "condition": "level=country" + "id": "country_name", + "render": "{nameEn} {emojiFlag}" }, { - "id": "community_links", + "condition": "_community_links~*", "description": "Community Links (Discord, meetups, Slack groups, IRC channels, mailing lists etc...)", - "render": "{_community_links}", - "condition": "_community_links~*" + "id": "community_links", + "render": "{_community_links}" } ], "filter": [ diff --git a/assets/layers/railway/railway.json b/assets/layers/railway/railway.json new file mode 100644 index 0000000000..50ddecf0ba --- /dev/null +++ b/assets/layers/railway/railway.json @@ -0,0 +1,66 @@ +{ + "id": "railway", + "name": { + "en": "Railway" + }, + "description": { + "en": "Railways and disused railways" + }, + "source": { + "osmTags": { + "or": [ + "railway=rail", + "railway=tram", + "railway=subway", + "railway=light_rail", + + "railway=disused", + + "disused:railway=rail", + "disused:railway=tram", + "disused:railway=subway", + "disused:railway=light_rail", + + "abandoned:railway=rail", + "abandoned:railway=tram", + "abandoned:railway=subway", + "abandoned:railway=light_rail" + ] + } + }, + "minzoom": 14, + "title": { + "render": { + "en": "Railway" + }, + "icon": "./assets/svg/train.svg" + }, + "pointRendering": [ + { + "location": [ + "point" + ], + "marker": [ + { + "icon": "circle" + } + ] + } + ], + "lineRendering": [ + { + "width": 1, + "color": "black" + } + ], + "tagRenderings": [ + "images" + ], + "allowMove": false, + "#": "In first instance to snap historic rolling stock", + "snapName": { + "en": "railway track" + }, + "credits": "mnalis ALTernative", + "credits:uid": 172435 +} diff --git a/assets/themes/grb/grb.json b/assets/themes/grb/grb.json index 337a137231..256070bca7 100644 --- a/assets/themes/grb/grb.json +++ b/assets/themes/grb/grb.json @@ -43,11 +43,24 @@ "id~relation/.*" ] }, - "building~*" + { + "or": [ + "building~*", + "building:part~*" + ] + } ] } }, - "title": "OSM-gebouw", + "title": { + "render": "OSM-building", + "mappings": [{ + "if": "building:part~*", + "then": { + "en": "Building part" + } + }] + }, "tagRenderings": [ { "id": "building type", @@ -106,10 +119,12 @@ "if": "building=yes", "then": "A building - no specification" } - ] + ], + "condition": "building:part=" }, { "id": "grb-housenumber", + "condition": "building:part=", "render": { "nl": "Het huisnummer is {addr:housenumber}" }, @@ -134,6 +149,7 @@ ] }, { + "condition": "building:part=", "id": "grb-unit", "question": "Wat is de wooneenheid-aanduiding?", "render": { @@ -150,6 +166,7 @@ ] }, { + "condition": "building:part=", "id": "grb-street", "render": { "nl": "De straat is {addr:street}" @@ -242,7 +259,49 @@ ] } ], - "pointRendering": null, + "pointRendering": [ + { + "marker": [{ + "icon": "circle", + "color": { + "render": "#00c", + "mappings": [ + { + "if": "fixme~*", + "then": "#ff00ff" + }, + { + "if": "building=house", + "then": "#a00" + }, + { + "if": "building=shed", + "then": "#563e02" + }, + { + "if": { + "or": [ + "building=garage", + "building=garages" + ] + }, + "then": "#f9bfbb" + }, + { + "if": "building=yes", + "then": "#0774f2" + }, + { + "if": "building:part~*", + "then": "#f8fc25" + } + ] + } + }], + "location": ["centroid"], + "iconSize": "10,10" + } + ], "lineRendering": [ { "width": { @@ -281,6 +340,10 @@ { "if": "building=yes", "then": "#0774f2" + }, + { + "if": "building:part~*", + "then": "#f8fc25" } ] } diff --git a/assets/themes/historic_rolling_stock/historic_rolling_stock.json b/assets/themes/historic_rolling_stock/historic_rolling_stock.json index 0b22c6b2b9..077324f278 100644 --- a/assets/themes/historic_rolling_stock/historic_rolling_stock.json +++ b/assets/themes/historic_rolling_stock/historic_rolling_stock.json @@ -18,6 +18,14 @@ }, "icon": "./assets/layers/historic_rolling_stock/steam_locomotive.svg", "layers": [ - "historic_rolling_stock" + "historic_rolling_stock", + { + "builtin": "railway", + "override": { + "name": null, + "presets": null, + "shownByDefault": false + } + } ] } diff --git a/assets/themes/nature/nature.json b/assets/themes/nature/nature.json index 08571b3f80..7158a1d5e6 100644 --- a/assets/themes/nature/nature.json +++ b/assets/themes/nature/nature.json @@ -58,6 +58,14 @@ "drinking_water", "birdhide", "nature_reserve", + { + "builtin": [ + "shelter" + ], + "override": { + "minzoom": 11 + } + }, { "builtin": [ "map", @@ -66,7 +74,6 @@ "picnic_table", "toilet", "guidepost", - "shelter", "bbq", "firepit", "insect_hotel", diff --git a/assets/themes/pets/pets.json b/assets/themes/pets/pets.json index 8740095fe2..d93ab452f0 100644 --- a/assets/themes/pets/pets.json +++ b/assets/themes/pets/pets.json @@ -100,6 +100,48 @@ "name": null } }, + { + "builtin": "cafe_pub", + "override": { + "id": "cafe_pub_dog_friendly", + "name": { + "en": "Dog friendly drinking places", + "da": "Hundevenlige værtshuse" + }, + "pointRendering": [ + { + "iconBadges+": [ + "icons.dogicon" + ] + } + ], + "=presets": [], + "source": { + "osmTags": { + "and+": [ + { + "or": [ + "dog=unleashed", + "dog=leashed", + "dog=yes" + ] + } + ] + } + } + } + }, + { + "builtin": "cafe_pub", + "override": { + "minzoom": 18, + "isCounted": false, + "filter": { + "sameAs": "cafe_pub_dog_friendly" + }, + "name": null + } + }, { "builtin": "shops", "override": { @@ -227,4 +269,4 @@ } } ] -} \ No newline at end of file +} diff --git a/langs/layers/cs.json b/langs/layers/cs.json index 067e08c2a8..2b661de30c 100644 --- a/langs/layers/cs.json +++ b/langs/layers/cs.json @@ -4751,7 +4751,9 @@ "diets": { "tagRenderings": { "diets_title": { - "render": "Dietní možnosti" + "render": { + "before": "Dietní možnosti" + } }, "gluten_free": { "mappings": { diff --git a/langs/layers/en.json b/langs/layers/en.json index f6476e968c..6365bf5d67 100644 --- a/langs/layers/en.json +++ b/langs/layers/en.json @@ -4768,7 +4768,9 @@ "diets": { "tagRenderings": { "diets_title": { - "render": "Dietary options" + "render": { + "before": "Dietary options" + } }, "gluten_free": { "mappings": { @@ -10092,6 +10094,14 @@ } } }, + "railway": { + "description": "Railways and disused railways", + "name": "Railway", + "snapName": "railway track", + "title": { + "render": "Railway" + } + }, "railway_platforms": { "description": "Find every platform in the station, and the train routes that use them.", "name": "Railway Platforms", diff --git a/langs/layers/nl.json b/langs/layers/nl.json index 03ff99a8e4..6e47acc32c 100644 --- a/langs/layers/nl.json +++ b/langs/layers/nl.json @@ -4561,7 +4561,9 @@ "diets": { "tagRenderings": { "diets_title": { - "render": "Dieetopties" + "render": { + "before": "Dieetopties" + } }, "gluten_free": { "mappings": { diff --git a/langs/themes/en.json b/langs/themes/en.json index a23dea4728..c2dd87052c 100644 --- a/langs/themes/en.json +++ b/langs/themes/en.json @@ -646,6 +646,13 @@ "grb-reference": { "render": "Has been imported from GRB, reference number is {source:geometry:ref}" } + }, + "title": { + "mappings": { + "0": { + "then": "Building part" + } + } } }, "1": { diff --git a/scripts/generateLayerOverview.ts b/scripts/generateLayerOverview.ts index ffd9e78cca..6414ed5f6c 100644 --- a/scripts/generateLayerOverview.ts +++ b/scripts/generateLayerOverview.ts @@ -37,15 +37,8 @@ import { Translatable } from "../src/Models/ThemeConfig/Json/Translatable" import { ValidateThemeAndLayers } from "../src/Models/ThemeConfig/Conversion/ValidateThemeAndLayers" import { ExtractImages } from "../src/Models/ThemeConfig/Conversion/FixImages" import { TagRenderingConfigJson } from "../src/Models/ThemeConfig/Json/TagRenderingConfigJson" -import { - LayerConfigDependencyGraph, - LevelInfo, -} from "../src/Models/ThemeConfig/LayerConfigDependencyGraph" import { Lists } from "../src/Utils/Lists" -import { - LayerConfigDependencyGraph, - LevelInfo, -} from "../src/Models/ThemeConfig/LayerConfigDependencyGraph" +import { LayerConfigDependencyGraph, LevelInfo } from "../src/Models/ThemeConfig/LayerConfigDependencyGraph" import { AddContextToTranslations } from "../src/Models/ThemeConfig/Conversion/AddContextToTranslations" // This scripts scans 'src/assets/layers/*.json' for layer definition files and 'src/assets/themes/*.json' for theme definition files. @@ -121,7 +114,7 @@ class AddIconSummary extends DesugaringStep<{ raw: LayerConfigJson; parsed: Laye static singleton = new AddIconSummary() constructor() { - super("AddIconSummary", "Adds an icon summary for quick reference") + super("AddIconSummary", "Adds an icon summary ('_layerIcon') for quick reference. This previews how the layer should be shown in e.g. the filter menu") } convert(json: { raw: LayerConfigJson; parsed: LayerConfig }) { @@ -706,8 +699,8 @@ class LayerOverviewUtils extends Script { ) const path = "assets/layers/questions/questions.json" - const sharedQuestionsRaw = this.parseLayer(doesImageExist, prepareLayer, path).raw - const sharedQuestions = new AddContextToTranslations("").convertStrict( + const sharedQuestionsRaw: LayerConfigJson = this.parseLayer(doesImageExist, prepareLayer, path).raw + const sharedQuestions: LayerConfigJson = new AddContextToTranslations("").convertStrict( sharedQuestionsRaw, ConversionContext.construct(["layers:questions"], []) ) diff --git a/src/Models/MenuState.ts b/src/Models/MenuState.ts index 627d1e2669..98ec28ba86 100644 --- a/src/Models/MenuState.ts +++ b/src/Models/MenuState.ts @@ -90,6 +90,18 @@ export class MenuState { if (!visitedBefore.data && shouldShowWelcomeMessage) { this.pageStates.about_theme.set(true) visitedBefore.set(true) + this._selectedElement.addCallbackD(() => { + if(this.pageStates.about_theme.data){ + this.pageStates.about_theme.set(false) + this._selectedElement.addCallbackAndRun(selected => { + if(!selected){ + this.pageStates.about_theme.set(true) + return true + } + }) + return true + } + }) } } diff --git a/src/Models/ThemeConfig/Json/LayerConfigJson.ts b/src/Models/ThemeConfig/Json/LayerConfigJson.ts index a541856b8b..1602f7a5a5 100644 --- a/src/Models/ThemeConfig/Json/LayerConfigJson.ts +++ b/src/Models/ThemeConfig/Json/LayerConfigJson.ts @@ -299,7 +299,7 @@ export interface LayerConfigJson { /** * Advanced option - might be set by the theme compiler * - * If true, this data will _always_ be loaded, even if the theme is disabled by a filter or hidden. + * If true, this data will _always_ be loaded, even if the layer is disabled by a filter or hidden. * The opposite of `doNotDownload` * * question: Should this layer be forcibly loaded? diff --git a/src/Models/ThemeConfig/TagRenderingConfig.ts b/src/Models/ThemeConfig/TagRenderingConfig.ts index 5f915cd424..da027a3499 100644 --- a/src/Models/ThemeConfig/TagRenderingConfig.ts +++ b/src/Models/ThemeConfig/TagRenderingConfig.ts @@ -523,7 +523,9 @@ export default class TagRenderingConfig { /** * Gets all the render values. Will return multiple render values if 'multianswer' is enabled. - * The result will equal [GetRenderValue] if not 'multiAnswer' + * The result will equal [GetRenderValue] if not 'multiAnswer'. + * + * If an iconsource is given, will return an icon too (but not necessarly an iconSize) * @param tags * @constructor */ diff --git a/src/Models/ThemeViewState/WithChangesState.ts b/src/Models/ThemeViewState/WithChangesState.ts index a7f919648c..27c2b85f1e 100644 --- a/src/Models/ThemeViewState/WithChangesState.ts +++ b/src/Models/ThemeViewState/WithChangesState.ts @@ -21,6 +21,7 @@ import SelectedElementTagsUpdater from "../../Logic/Actors/SelectedElementTagsUp import NoElementsInViewDetector, { FeatureViewState, } from "../../Logic/Actors/NoElementsInViewDetector" +import { features } from "monaco-editor/esm/metadata" export class WithChangesState extends WithLayoutSourceState { readonly changes: Changes @@ -226,15 +227,10 @@ export class WithChangesState extends WithLayoutSourceState { metaTags: this.userRelatedState.preferencesAsTags, selectedElement: this.selectedElement, fetchStore: (id) => this.featureProperties.getStore(id), + onClick: feature => { + this.setSelectedElement(feature) + } }) - /*new ShowDataLayer(map, { - layer: fs.layer.layerDef, - features: filtered, - doShowLayer, - metaTags: this.userRelatedState.preferencesAsTags, - selectedElement: this.selectedElement, - fetchStore: (id) => this.featureProperties.getStore(id), - })*/ }) return filteringFeatureSource } diff --git a/src/Models/ThemeViewState/WithLayoutSourceState.ts b/src/Models/ThemeViewState/WithLayoutSourceState.ts index cb9ceeeac2..d09f41d965 100644 --- a/src/Models/ThemeViewState/WithLayoutSourceState.ts +++ b/src/Models/ThemeViewState/WithLayoutSourceState.ts @@ -130,7 +130,8 @@ export class WithLayoutSourceState extends WithSelectedElementState { } protected setSelectedElement(feature: Feature) { - // The given feature might be a partial one from the cache + // The given feature might be a partial one from the cache or might be a centroid point instead of the way + // We lookup a version by IDP if (feature !== undefined) { feature = this.indexedFeatures.featuresById.data?.get(feature?.properties?.id) ?? feature diff --git a/src/Models/ThemeViewState/WithSpecialLayers.ts b/src/Models/ThemeViewState/WithSpecialLayers.ts index 15cdc4cb62..ff2bb58df5 100644 --- a/src/Models/ThemeViewState/WithSpecialLayers.ts +++ b/src/Models/ThemeViewState/WithSpecialLayers.ts @@ -4,7 +4,7 @@ import FavouritesFeatureSource from "../../Logic/FeatureSource/Sources/Favourite import Constants from "../Constants" import { FeatureSource } from "../../Logic/FeatureSource/FeatureSource" import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource" -import { Feature } from "geojson" +import { Feature, Geometry } from "geojson" import { BBox } from "../../Logic/BBox" import ShowDataLayer from "../../UI/Map/ShowDataLayer" import MetaTagging from "../../Logic/MetaTagging" @@ -22,6 +22,7 @@ import { } from "../../Logic/FeatureSource/TiledFeatureSource/SummaryTileSource" import { ShowDataLayerOptions } from "../../UI/Map/ShowDataLayerOptions" import { ClusterGrouping } from "../../Logic/FeatureSource/TiledFeatureSource/ClusteringFeatureSource" +import { OsmTags } from "../OsmFeature" export class WithSpecialLayers extends WithChangesState { readonly favourites: FavouritesFeatureSource @@ -180,7 +181,7 @@ export class WithSpecialLayers extends WithChangesState { }) ) // show last click = new point/note marker - const features = new StaticFeatureSource(lastClickFiltered) + const features: StaticFeatureSource & {id: string}>> = new StaticFeatureSource(lastClickFiltered) this.featureProperties.trackFeatureSource(features) new ShowDataLayer(this.map, { features, diff --git a/src/UI/Map/ShowDataLayer.ts b/src/UI/Map/ShowDataLayer.ts index 4d80c6c926..434e705fd7 100644 --- a/src/UI/Map/ShowDataLayer.ts +++ b/src/UI/Map/ShowDataLayer.ts @@ -459,11 +459,6 @@ export default class ShowDataLayer { preprocessPoints, } = this._options let onClick = this._options.onClick - if (!onClick && selectedElement && layer.title !== undefined) { - onClick = (feature: Feature) => { - selectedElement?.setData(feature) - } - } if (drawLines !== false) { for (let i = 0; i < layer.lineRendering.length; i++) { const lineRenderingConfig = layer.lineRendering[i] diff --git a/src/UI/Popup/AutoApplyButtonVis.ts b/src/UI/Popup/AutoApplyButtonVis.ts index 9bfa3c3659..8257745e82 100644 --- a/src/UI/Popup/AutoApplyButtonVis.ts +++ b/src/UI/Popup/AutoApplyButtonVis.ts @@ -2,8 +2,9 @@ import { Store, Stores, UIEventSource } from "../../Logic/UIEventSource" import ThemeConfig from "../../Models/ThemeConfig/ThemeConfig" import { Changes } from "../../Logic/Osm/Changes" import { + SpecialVisualisationArg, + SpecialVisualisationParams, SpecialVisualization, - SpecialVisualizationState, SpecialVisualizationSvelte, } from "../SpecialVisualization" import { IndexedFeatureSource } from "../../Logic/FeatureSource/FeatureSource" @@ -31,12 +32,7 @@ export default class AutoApplyButtonVis extends SpecialVisualizationSvelte { public readonly funcName: string = "auto_apply" public readonly needsUrls = [] public readonly group = "data_import" - public readonly args: { - name: string - defaultValue?: string - doc: string - required?: boolean - }[] = [ + public readonly args: SpecialVisualisationArg[] = [ { name: "target_layer", doc: "The layer that the target features will reside in", @@ -54,6 +50,7 @@ export default class AutoApplyButtonVis extends SpecialVisualizationSvelte { }, { name: "text", + type:"translation", doc: "The text to show on the button", required: true, }, @@ -86,15 +83,11 @@ export default class AutoApplyButtonVis extends SpecialVisualizationSvelte { 4. At last, add this component` } - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - argument: string[] - ): SvelteUIElement { - const target_layer_id = argument[0] - const targetTagRendering = argument[2] - const text = argument[3] - const icon = argument[4] + constr({ state, tags, args }: SpecialVisualisationParams): SvelteUIElement { + const target_layer_id = args[0] + const targetTagRendering = args[2] + const text = args[3] + const icon = args[4] const options = { target_layer_id, targetTagRendering, @@ -105,7 +98,7 @@ export default class AutoApplyButtonVis extends SpecialVisualizationSvelte { const to_parse: UIEventSource = new UIEventSource(undefined) Stores.chronic(500, () => to_parse.data === undefined) .map(() => { - const applicable = tagSource.data[argument[1]] + const applicable = tags.data[args[1]] if (typeof applicable === "string") { return JSON.parse(applicable) } else { diff --git a/src/UI/Popup/DataExportVisualisations.ts b/src/UI/Popup/DataExportVisualisations.ts index 81f5f3747f..dc82a9125e 100644 --- a/src/UI/Popup/DataExportVisualisations.ts +++ b/src/UI/Popup/DataExportVisualisations.ts @@ -1,11 +1,5 @@ -import { - SpecialVisualization, - SpecialVisualizationState, - SpecialVisualizationSvelte, -} from "../SpecialVisualization" -import { UIEventSource } from "../../Logic/UIEventSource" +import { SpecialVisualisationParams, SpecialVisualization, SpecialVisualizationSvelte } from "../SpecialVisualization" import { Feature, LineString } from "geojson" -import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import Translations from "../i18n/Translations" import SvelteUIElement from "../Base/SvelteUIElement" import ExportFeatureButton from "./ExportFeatureButton.svelte" @@ -17,13 +11,7 @@ class ExportAsGpxVis extends SpecialVisualizationSvelte { args = [] needsUrls = [] - constr( - state: SpecialVisualizationState, - tags: UIEventSource>, - argument: string[], - feature: Feature, - layer: LayerConfig - ) { + constr({ tags, feature, layer }: SpecialVisualisationParams) { if (feature.geometry.type !== "LineString") { return undefined } @@ -48,7 +36,7 @@ class ExportAsGeojsonVis extends SpecialVisualizationSvelte { docs = "Exports the selected feature as GeoJson-file" args = [] - constr(state, tags, args, feature, layer) { + constr({ tags, feature, layer }: SpecialVisualisationParams) { const t = Translations.t.general.download return new SvelteUIElement(ExportFeatureButton, { tags, @@ -64,7 +52,7 @@ class ExportAsGeojsonVis extends SpecialVisualizationSvelte { } export class DataExportVisualisations { - public static initList(): SpecialVisualization[] { + public static initList(): SpecialVisualizationSvelte[] { return [new ExportAsGpxVis(), new ExportAsGeojsonVis()] } } diff --git a/src/UI/Popup/DataVisualisations.ts b/src/UI/Popup/DataVisualisations.ts index 5a721e162f..97fd3c10d3 100644 --- a/src/UI/Popup/DataVisualisations.ts +++ b/src/UI/Popup/DataVisualisations.ts @@ -1,11 +1,11 @@ import { + SpecialVisualisationArg, + SpecialVisualisationParams, SpecialVisualization, - SpecialVisualizationState, SpecialVisualizationSvelte, } from "../SpecialVisualization" import { HistogramViz } from "./HistogramViz" -import { Store, UIEventSource } from "../../Logic/UIEventSource" -import { Feature } from "geojson" +import { Store } from "../../Logic/UIEventSource" import BaseUIElement from "../BaseUIElement" import SvelteUIElement from "../Base/SvelteUIElement" import DirectionIndicator from "../Base/DirectionIndicator.svelte" @@ -20,15 +20,18 @@ import NextChangeViz from "../OpeningHours/NextChangeViz.svelte" import { Unit } from "../../Models/Unit" import AllFeaturesStatistics from "../Statistics/AllFeaturesStatistics.svelte" import { LanguageElement } from "./LanguageElement/LanguageElement" -import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import { QuestionableTagRenderingConfigJson } from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson" import { And } from "../../Logic/Tags/And" import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig" import TagRenderingEditable from "./TagRendering/TagRenderingEditable.svelte" import AllTagsPanel from "./AllTagsPanel/AllTagsPanel.svelte" import CollectionTimes from "../CollectionTimes/CollectionTimes.svelte" +import Tr from "../Base/Tr.svelte" +import Combine from "../Base/Combine" +import Marker from "../Map/Marker.svelte" +import { twJoin } from "tailwind-merge" -class DirectionIndicatorVis extends SpecialVisualization { +class DirectionIndicatorVis extends SpecialVisualizationSvelte { funcName = "direction_indicator" args = [] @@ -36,13 +39,8 @@ class DirectionIndicatorVis extends SpecialVisualization { "Gives a distance indicator and a compass pointing towards the location from your GPS-location. If clicked, centers the map on the object" group = "data" - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - argument: string[], - feature: Feature - ): BaseUIElement { - return new SvelteUIElement(DirectionIndicator, { state, feature }) + constr(params: SpecialVisualisationParams): SvelteUIElement { + return new SvelteUIElement(DirectionIndicator, params) } } @@ -65,17 +63,15 @@ class DirectionAbsolute extends SpecialVisualization { ] group = "data" - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - args: string[] - ): BaseUIElement { + constr({ + tags, + args, + }: SpecialVisualisationParams): BaseUIElement { const key = args[0] === "" ? "_direction:centerpoint" : args[0] const offset = args[1] === "" ? 0 : Number(args[1]) return new VariableUiElement( - tagSource - .map((tags) => { + tags.map((tags) => { console.log("Direction value", tags[key], key) return tags[key] }) @@ -117,14 +113,12 @@ class OpeningHoursTableVis extends SpecialVisualizationSvelte { example = "A normal opening hours table can be invoked with `{opening_hours_table()}`. A table for e.g. conditional access with opening hours can be `{opening_hours_table(access:conditional, no @ &LPARENS, &RPARENS)}`" - constr(state, tagSource: UIEventSource, args) { + constr({ tags, args }: SpecialVisualisationParams): SvelteUIElement { const [key, prefix, postfix] = args const openingHoursStore: Store = - OH.CreateOhObjectStore(tagSource, key, prefix, postfix) + OH.CreateOhObjectStore(tags, key, prefix, postfix) return new SvelteUIElement(OpeningHoursWithError, { - tags: tagSource, - key, - opening_hours_obj: openingHoursStore, + tags, key, opening_hours_obj: openingHoursStore, }) } } @@ -152,11 +146,7 @@ class OpeningHoursState extends SpecialVisualizationSvelte { }, ] - constr( - state: SpecialVisualizationState, - tags: UIEventSource>, - args: string[] - ): SvelteUIElement { + constr({ state, tags, args }: SpecialVisualisationParams): SvelteUIElement { const keyToUse = args[0] const prefix = args[1] const postfix = args[2] @@ -187,10 +177,10 @@ class Canonical extends SpecialVisualization { }, ] - constr(state, tagSource, args) { + constr({ state, tags, args }: SpecialVisualisationParams) { const key = args[0] return new VariableUiElement( - tagSource + tags .map((tags) => tags[key]) .map((value) => { if (value === undefined) { @@ -203,7 +193,7 @@ class Canonical extends SpecialVisualization { if (unit === undefined) { return value } - const getCountry = () => tagSource.data._country + const getCountry = () => tags.data._country return unit.asHumanLongValue(value, getCountry) }) ) @@ -217,26 +207,24 @@ class StatisticsVis extends SpecialVisualizationSvelte { "Show general statistics about all the elements currently in view. Intended to use on the `current_view`-layer. They will be split per layer" args = [] - constr(state) { - return new SvelteUIElement(AllFeaturesStatistics, { state }) + constr(params: SpecialVisualisationParams) { + return new SvelteUIElement(AllFeaturesStatistics, params) } } -class PresetDescription extends SpecialVisualization { +class PresetDescription extends SpecialVisualizationSvelte { funcName = "preset_description" docs = "Shows the extra description from the presets of the layer, if one matches. It will pick the most specific one (e.g. if preset `A` implies `B`, but `B` does not imply `A`, it'll pick B) or the first one if no ordering can be made. Might be empty" args = [] + group = "UI" - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource> - ): BaseUIElement { - const translation = tagSource.map((tags) => { + constr({ state, tags }: SpecialVisualisationParams): SvelteUIElement { + const translation = tags.map((tags) => { const layer = state.theme.getMatchingLayer(tags) return layer?.getMostMatchingPreset(tags)?.description }) - return new VariableUiElement(translation) + return new SvelteUIElement(Tr, { t: translation }) } } @@ -244,14 +232,9 @@ class PresetTypeSelect extends SpecialVisualizationSvelte { funcName = "preset_type_select" docs = "An editable tag rendering which allows to change the type" args = [] + group = "ui" - constr( - state: SpecialVisualizationState, - tags: UIEventSource>, - argument: string[], - selectedElement: Feature, - layer: LayerConfig - ): SvelteUIElement { + constr({ state, tags, feature, layer }: SpecialVisualisationParams,): SvelteUIElement { const t = Translations.t.preset_type if (layer._basedOn !== layer.id) { console.warn("Trying to use the _original_ layer") @@ -277,7 +260,7 @@ class PresetTypeSelect extends SpecialVisualizationSvelte { return new SvelteUIElement(TagRenderingEditable, { config, tags, - selectedElement, + selectedElement: feature, state, layer, }) @@ -290,8 +273,8 @@ class AllTagsVis extends SpecialVisualizationSvelte { args = [] group = "data" - constr(state, tags: UIEventSource>, _, __, layer: LayerConfig) { - return new SvelteUIElement(AllTagsPanel, { tags, layer }) + constr(params: SpecialVisualisationParams) { + return new SvelteUIElement(AllTagsPanel, params) } } @@ -302,23 +285,18 @@ class PointsInTimeVis extends SpecialVisualization { args = [ { name: "key", + type:"key", required: true, doc: "The key out of which the points_in_time will be parsed", }, ] - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - args: string[], - feature: Feature, - layer: LayerConfig - ): BaseUIElement { + constr({ tags, args }: SpecialVisualisationParams): BaseUIElement { const key = args[0] - const points_in_time = tagSource.map((tags) => tags[key]) + const points_in_time = tags.map((tags) => tags[key]) const times = points_in_time.map( - (times) => OH.createOhObject(tagSource.data, times, tagSource.data["_country"], 1), - [tagSource] + (times) => OH.createOhObject(tags.data, times, tags.data["_country"], 1), + [tags] ) return new VariableUiElement( times.map((times) => new SvelteUIElement(CollectionTimes, { times })) @@ -326,6 +304,60 @@ class PointsInTimeVis extends SpecialVisualization { } } +class KnownIcons extends SpecialVisualization { + docs = "Displays all icons from the specified tagRenderings (if they are known and have an icon) together, e.g. to give a summary of the dietary options" + needsUrls = [] + group = "UI" + funcName = "show_icons" + args: SpecialVisualisationArg[] = [{ + name: "labels", + doc: "A ';'-separated list of labels and/or ids of tagRenderings", + type: "key", + required: true, + }, { + name: "class", + doc: "CSS-classes of the container, space-separated", + type: "css", + required: false, + defaultValue: "inline-flex mx-4", + }] + + private static readonly emojiHeights = { + small: "2rem", + medium: "3rem", + large: "5rem", + } + + constr(options: SpecialVisualisationParams): BaseUIElement { + const labels = new Set(options.args[0].split(";").map(s => s.trim())) + const matchingTrs = options.layer.tagRenderings.filter( + tr => labels.has(tr.id) || tr.labels.some(l => labels.has(l)), + ) + return new VariableUiElement(options.tags.map(tags => + new Combine(matchingTrs.map(tr => { + const mapping = tr.GetRenderValueWithImage(tags) + if (!mapping?.icon) { + return undefined + } + + return new SvelteUIElement(Marker, { + emojiHeight: KnownIcons.emojiHeights[mapping.iconClass] ?? "2rem", + clss: `mapping-icon-${mapping.iconClass ?? "small"}`, + icons: mapping.icon, + size: twJoin( + "shrink-0", + `mapping-icon-${mapping.iconClass ?? "small"}-height mapping-icon-${ + mapping.iconClass ?? "small" + }-width`), + + + }) + }) + ).SetClass(options.args[1] ?? "inline-flex mx-4") + )) + } +} + export class DataVisualisations { public static initList(): SpecialVisualization[] { return [ @@ -341,6 +373,7 @@ export class DataVisualisations { new PresetDescription(), new PresetTypeSelect(), new AllTagsVis(), + new KnownIcons(), ] } } diff --git a/src/UI/Popup/HistogramViz.ts b/src/UI/Popup/HistogramViz.ts index 679255d652..7cfffd1f4b 100644 --- a/src/UI/Popup/HistogramViz.ts +++ b/src/UI/Popup/HistogramViz.ts @@ -1,5 +1,5 @@ import { Store, UIEventSource } from "../../Logic/UIEventSource" -import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" +import { SpecialVisualisationParams, SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" import { Feature } from "geojson" import SvelteUIElement from "../Base/SvelteUIElement" import Histogram from "../BigComponents/Histogram.svelte" @@ -14,6 +14,7 @@ export class HistogramViz extends SpecialVisualization { args = [ { name: "key", + type:"key", doc: "The key to be read and to generate a histogram from", required: true, }, @@ -35,12 +36,8 @@ export class HistogramViz extends SpecialVisualization { ] } - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - args: string[] - ) { - const values: Store = tagSource.map((tags) => { + constr( {tags, args}: SpecialVisualisationParams): SvelteUIElement { + const values: Store = tags.map((tags) => { const value = tags[args[0]] try { if (value === "" || value === undefined) { diff --git a/src/UI/Popup/ImportButtons/ConflateImportButtonViz.ts b/src/UI/Popup/ImportButtons/ConflateImportButtonViz.ts index f72497649a..fce251a7dc 100644 --- a/src/UI/Popup/ImportButtons/ConflateImportButtonViz.ts +++ b/src/UI/Popup/ImportButtons/ConflateImportButtonViz.ts @@ -1,7 +1,11 @@ -import { SpecialVisualization, SpecialVisualizationState } from "../../SpecialVisualization" +import { + SpecialVisualisationArg, + SpecialVisualisationParams, + SpecialVisualizationSvelte, + SpecialVisualizationUtils, +} from "../../SpecialVisualization" import { UIEventSource } from "../../../Logic/UIEventSource" import { Feature, Geometry, LineString, Polygon } from "geojson" -import BaseUIElement from "../../BaseUIElement" import { ImportFlowArguments, ImportFlowUtils } from "./ImportFlow" import Translations from "../../i18n/Translations" import { Utils } from "../../../Utils" @@ -14,6 +18,7 @@ import { Changes } from "../../../Logic/Osm/Changes" import ThemeConfig from "../../../Models/ThemeConfig/ThemeConfig" import { OsmConnection } from "../../../Logic/Osm/OsmConnection" import { OsmTags } from "../../../Models/OsmFeature" +import Tr from "../../Base/Tr.svelte" export interface ConflateFlowArguments extends ImportFlowArguments { way_to_conflate: string @@ -22,21 +27,17 @@ export interface ConflateFlowArguments extends ImportFlowArguments { snap_onto_layers?: string } -export default class ConflateImportButtonViz extends SpecialVisualization implements AutoAction { +export default class ConflateImportButtonViz extends SpecialVisualizationSvelte implements AutoAction { supportsAutoAction: boolean = true needsUrls = [] group = "data_import" public readonly funcName: string = "conflate_button" - public readonly args: { - name: string - defaultValue?: string - doc: string - required?: boolean - }[] = [ + public readonly args: SpecialVisualisationArg[] = [ ...ImportFlowUtils.generalArguments, { name: "way_to_conflate", + type:"key", doc: "The key, of which the corresponding value is the id of the OSM-way that must be conflated; typically a calculatedTag", }, ] @@ -84,32 +85,25 @@ export default class ConflateImportButtonViz extends SpecialVisualization implem await state.changes.applyAction(action) } - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource, - argument: string[], - feature: Feature - ): BaseUIElement { + constr({ state, tags, args, feature }: SpecialVisualisationParams): SvelteUIElement { const canBeImported = feature.geometry.type === "LineString" || (feature.geometry.type === "Polygon" && feature.geometry.coordinates.length === 1) if (!canBeImported) { - return Translations.t.general.add.import.wrongTypeToConflate.SetClass("alert") + return new SvelteUIElement(Tr, { t: Translations.t.general.add.import.wrongTypeToConflate, cls: "alert" }) } - const args: ConflateFlowArguments = Utils.ParseVisArgs(this.args, argument) - const tagsToApply = ImportFlowUtils.getTagsToApply(tagSource, args) - const idOfWayToReplaceGeometry = tagSource.data[args.way_to_conflate] + const argsParsed: ConflateFlowArguments = SpecialVisualizationUtils.parseArgs(this.args, args) + const tagsToApply = ImportFlowUtils.getTagsToApply(>tags, argsParsed) + const idOfWayToReplaceGeometry = tags.data[argsParsed.way_to_conflate] const importFlow = new ConflateImportFlowState( state, >feature, - args, + argsParsed, tagsToApply, - tagSource, + tags, idOfWayToReplaceGeometry ) - return new SvelteUIElement(WayImportFlow, { - importFlow, - }) + return new SvelteUIElement(WayImportFlow, { importFlow }) } getLayerDependencies = (args: string[]) => diff --git a/src/UI/Popup/ImportButtons/ImportFlow.ts b/src/UI/Popup/ImportButtons/ImportFlow.ts index 3deea70ed5..4ad16da1e2 100644 --- a/src/UI/Popup/ImportButtons/ImportFlow.ts +++ b/src/UI/Popup/ImportButtons/ImportFlow.ts @@ -66,13 +66,14 @@ ${Utils.special_visualizations_importRequirementDocs} * Given the tagsstore of the point which represents the challenge, creates a new store with tags that should be applied onto the newly created point, */ public static getTagsToApply( - originalFeatureTags: UIEventSource, + originalFeatureTags: Store, args: { tags: string } ): Store { if (originalFeatureTags === undefined) { return undefined } let newTags: Store + // Listing of the keys that should be transferred const tags = args.tags if ( tags.indexOf(" ") < 0 && @@ -81,12 +82,6 @@ ${Utils.special_visualizations_importRequirementDocs} ) { // This is a property to expand... const items: string = originalFeatureTags.data[tags] - console.debug( - "The import button is using tags from properties[" + - tags + - "] of this object, namely ", - items - ) if (items.startsWith("{")) { // This is probably a JSON diff --git a/src/UI/Popup/ImportButtons/PointImportButtonViz.ts b/src/UI/Popup/ImportButtons/PointImportButtonViz.ts index 9333102682..b36724eb89 100644 --- a/src/UI/Popup/ImportButtons/PointImportButtonViz.ts +++ b/src/UI/Popup/ImportButtons/PointImportButtonViz.ts @@ -1,7 +1,5 @@ import { Feature, Point } from "geojson" -import { UIEventSource } from "../../../Logic/UIEventSource" -import { SpecialVisualization, SpecialVisualizationState } from "../../SpecialVisualization" -import BaseUIElement from "../../BaseUIElement" +import { SpecialVisualisationParams, SpecialVisualizationSvelte } from "../../SpecialVisualization" import SvelteUIElement from "../../Base/SvelteUIElement" import PointImportFlow from "./PointImportFlow.svelte" import { PointImportFlowArguments, PointImportFlowState } from "./PointImportFlowState" @@ -9,12 +7,14 @@ import { Utils } from "../../../Utils" import { ImportFlowUtils } from "./ImportFlow" import Translations from "../../i18n/Translations" import { GeoOperations } from "../../../Logic/GeoOperations" +import Tr from "../../Base/Tr.svelte" +import { UIEventSource } from "../../../Logic/UIEventSource" import { OsmTags } from "../../../Models/OsmFeature" /** * The wrapper to make the special visualisation for the PointImportFlow */ -export class PointImportButtonViz extends SpecialVisualization { +export class PointImportButtonViz extends SpecialVisualizationSvelte { public readonly funcName = "import_button" public readonly docs: string = "This button will copy the point from an external dataset into OpenStreetMap" + @@ -47,29 +47,24 @@ export class PointImportButtonViz extends SpecialVisualization { public needsUrls = [] group = "data_import" - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource, - argument: string[], - feature: Feature - ): BaseUIElement { + constr({ state, tags, args, feature }: SpecialVisualisationParams): SvelteUIElement { const to_point_index = this.args.findIndex((arg) => arg.name === "to_point") - const summarizePointArg = argument[to_point_index].toLowerCase() + const summarizePointArg = args[to_point_index].toLowerCase() if (feature.geometry.type !== "Point") { if (summarizePointArg !== "no" && summarizePointArg !== "false") { feature = GeoOperations.centerpoint(feature) } else { - return Translations.t.general.add.import.wrongType.SetClass("alert") + return new SvelteUIElement(Tr, { t: Translations.t.general.add.import.wrongType.SetClass("alert") }) } } - const baseArgs: PointImportFlowArguments = Utils.ParseVisArgs(this.args, argument) - const tagsToApply = ImportFlowUtils.getTagsToApply(tagSource, baseArgs) + const baseArgs: PointImportFlowArguments = Utils.ParseVisArgs(this.args, args) + const tagsToApply = ImportFlowUtils.getTagsToApply(>tags, baseArgs) const importFlow = new PointImportFlowState( state, >feature, baseArgs, tagsToApply, - tagSource + tags ) return new SvelteUIElement(PointImportFlow, { diff --git a/src/UI/Popup/ImportButtons/WayImportButtonViz.ts b/src/UI/Popup/ImportButtons/WayImportButtonViz.ts index 61272532f8..91106af08f 100644 --- a/src/UI/Popup/ImportButtons/WayImportButtonViz.ts +++ b/src/UI/Popup/ImportButtons/WayImportButtonViz.ts @@ -1,10 +1,12 @@ -import { SpecialVisualization, SpecialVisualizationState } from "../../SpecialVisualization" +import { + SpecialVisualisationParams, + SpecialVisualizationSvelte, + SpecialVisualizationUtils, +} from "../../SpecialVisualization" import { AutoAction } from "../AutoApplyButtonVis" import { Feature, LineString, Polygon } from "geojson" import { UIEventSource } from "../../../Logic/UIEventSource" -import BaseUIElement from "../../BaseUIElement" import { ImportFlowUtils } from "./ImportFlow" -import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" import SvelteUIElement from "../../Base/SvelteUIElement" import WayImportFlow from "./WayImportFlow.svelte" import WayImportFlowState, { WayImportFlowArguments } from "./WayImportFlowState" @@ -13,12 +15,11 @@ import ThemeConfig from "../../../Models/ThemeConfig/ThemeConfig" import { Changes } from "../../../Logic/Osm/Changes" import { IndexedFeatureSource } from "../../../Logic/FeatureSource/FeatureSource" import FullNodeDatabaseSource from "../../../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource" -import { OsmTags } from "../../../Models/OsmFeature" /** * Wrapper around 'WayImportFlow' to make it a special visualisation */ -export default class WayImportButtonViz extends SpecialVisualization implements AutoAction { +export default class WayImportButtonViz extends SpecialVisualizationSvelte implements AutoAction { public readonly funcName: string = "import_way_button" needsUrls = [] group = "data_import" @@ -60,25 +61,20 @@ export default class WayImportButtonViz extends SpecialVisualization implements public readonly supportsAutoAction = true public readonly needsNodeDatabase = true - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource, - argument: string[], - feature: Feature, - _: LayerConfig - ): BaseUIElement { + constr({ state, tags, args, feature }: SpecialVisualisationParams): SvelteUIElement { const geometry = feature.geometry if (!(geometry.type == "LineString" || geometry.type === "Polygon")) { - throw "Invalid type to import " + geometry.type + throw "Invalid type to import, expected linestring of polygon but got " + geometry.type } - const args: WayImportFlowArguments = Utils.ParseVisArgs(this.args, argument) - const tagsToApply = ImportFlowUtils.getTagsToApply(tagSource, args) + const parsedArgs: WayImportFlowArguments = SpecialVisualizationUtils.parseArgs(this.args, args) + console.log("Parsed args are", parsedArgs) + const tagsToApply = ImportFlowUtils.getTagsToApply(tags, parsedArgs) const importFlow = new WayImportFlowState( state, >feature, - args, + parsedArgs, tagsToApply, - tagSource + tags, ) return new SvelteUIElement(WayImportFlow, { importFlow, diff --git a/src/UI/Popup/LanguageElement/LanguageElement.ts b/src/UI/Popup/LanguageElement/LanguageElement.ts index df0deecab5..4d95929e3c 100644 --- a/src/UI/Popup/LanguageElement/LanguageElement.ts +++ b/src/UI/Popup/LanguageElement/LanguageElement.ts @@ -1,46 +1,49 @@ -import { SpecialVisualization, SpecialVisualizationState } from "../../SpecialVisualization" -import BaseUIElement from "../../BaseUIElement" -import { UIEventSource } from "../../../Logic/UIEventSource" +import { SpecialVisualisationParams, SpecialVisualizationSvelte } from "../../SpecialVisualization" import SvelteUIElement from "../../Base/SvelteUIElement" -import { Feature } from "geojson" -import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" import { default as LanguageElementSvelte } from "./LanguageElement.svelte" -export class LanguageElement extends SpecialVisualization { +export class LanguageElement extends SpecialVisualizationSvelte { funcName: string = "language_chooser" needsUrls = [] - docs: string | BaseUIElement = + docs: string = "The language element allows to show and pick all known (modern) languages. The key can be set" - args: { name: string; defaultValue?: string; doc: string; required?: boolean }[] = [ + args: { name: string; defaultValue?: string; doc: string; required?: boolean; type?: string }[] = [ { name: "key", required: true, + type:"key", doc: "What key to use, e.g. `language`, `tactile_writing:braille:language`, ... If a language is supported, the language code will be appended to this key, resulting in `:nl=yes` if _nl_ is picked ", }, { name: "question", required: true, + type: "translation", doc: "What to ask if no questions are known", }, { name: "render_list_item", + type: "translation", + doc: "How a single language will be shown in the list of languages. Use `{language}` to indicate the language (which it must contain).", defaultValue: "{language()}", }, { name: "render_single_language", + type: "translation", doc: "What will be shown if the feature only supports a single language", required: true, }, { + type: "translation", name: "render_all", - doc: "The full rendering. Use `{list}` to show where the list of languages must come. Optional if mode=single", + doc: "The full rendering. U0se `{list}` to show where the list of languages must come. Optional if mode=single", defaultValue: "{list()}", }, { name: "no_known_languages", + type: "translation", doc: "The text that is shown if no languages are known for this key. If this text is omitted, the languages will be prompted instead", }, ] @@ -59,14 +62,16 @@ export class LanguageElement extends SpecialVisualization { ` constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - argument: string[], - feature: Feature, - layer: LayerConfig - ): BaseUIElement { + { + state, + tags, + args, + feature, + layer, + }: SpecialVisualisationParams, + ): SvelteUIElement { let [key, question, item_render, single_render, all_render, on_no_known_languages] = - argument + args if (item_render === undefined || item_render.trim() === "") { item_render = "{language()}" } @@ -94,7 +99,7 @@ export class LanguageElement extends SpecialVisualization { return new SvelteUIElement(LanguageElementSvelte, { key, - tags: tagSource, + tags, state, feature, layer, diff --git a/src/UI/Popup/MapillaryLinkVis.ts b/src/UI/Popup/MapillaryLinkVis.ts index 048e99f94c..b3000a697c 100644 --- a/src/UI/Popup/MapillaryLinkVis.ts +++ b/src/UI/Popup/MapillaryLinkVis.ts @@ -1,7 +1,6 @@ import { GeoOperations } from "../../Logic/GeoOperations" -import { ImmutableStore, UIEventSource } from "../../Logic/UIEventSource" -import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization" -import { Feature } from "geojson" +import { ImmutableStore } from "../../Logic/UIEventSource" +import { SpecialVisualisationParams, SpecialVisualizationSvelte } from "../SpecialVisualization" import SvelteUIElement from "../Base/SvelteUIElement" import MapillaryLink from "../BigComponents/MapillaryLink.svelte" @@ -20,12 +19,7 @@ export class MapillaryLinkVis extends SpecialVisualizationSvelte { }, ] - public constr( - state: SpecialVisualizationState, - tagsSource: UIEventSource>, - args: string[], - feature: Feature - ): SvelteUIElement { + public constr({ args, feature }: SpecialVisualisationParams): SvelteUIElement { const [lon, lat] = GeoOperations.centerpointCoordinates(feature) let zoom = Number(args[0]) if (isNaN(zoom)) { diff --git a/src/UI/Popup/MinimapViz.svelte b/src/UI/Popup/MinimapViz.svelte index e91ada4942..25b95242fb 100644 --- a/src/UI/Popup/MinimapViz.svelte +++ b/src/UI/Popup/MinimapViz.svelte @@ -1,6 +1,6 @@ diff --git a/src/UI/Popup/MultiApplyViz.ts b/src/UI/Popup/MultiApplyViz.ts index 4a6254f58e..a44ef8c243 100644 --- a/src/UI/Popup/MultiApplyViz.ts +++ b/src/UI/Popup/MultiApplyViz.ts @@ -1,6 +1,6 @@ -import { Store, UIEventSource } from "../../Logic/UIEventSource" +import { Store } from "../../Logic/UIEventSource" import { MultiApplyParams } from "./MultiApply" -import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization" +import { SpecialVisualisationParams, SpecialVisualizationSvelte } from "../SpecialVisualization" import SvelteUIElement from "../Base/SvelteUIElement" import MultiApplyButton from "./MultiApplyButton.svelte" @@ -19,7 +19,9 @@ export class MultiApplyViz extends SpecialVisualizationSvelte { doc: "One key (or multiple keys, seperated by ';') of the attribute that should be copied onto the other features.", required: true, }, - { name: "text", doc: "The text to show on the button" }, + { name: "text", + type: "translation", + doc: "The text to show on the button" }, { name: "autoapply", doc: "A boolean indicating wether this tagging should be applied automatically if the relevant tags on this object are changed. A visual element indicating the multi_apply is still shown", @@ -34,17 +36,13 @@ export class MultiApplyViz extends SpecialVisualizationSvelte { example = "{multi_apply(_features_with_the_same_name_within_100m, name:etymology:wikidata;name:etymology, Apply etymology information on all nearby objects with the same name)}" - constr( - state: SpecialVisualizationState, - tagsSource: UIEventSource>, - args: string[] - ): SvelteUIElement { + constr({ state, tags, args }: SpecialVisualisationParams): SvelteUIElement { const featureIdsKey = args[0] const keysToApply = args[1].split(";") const text = args[2] const autoapply = args[3]?.toLowerCase() === "true" const overwrite = args[4]?.toLowerCase() === "true" - const featureIds: Store = tagsSource.map((tags) => { + const featureIds: Store = tags.map((tags) => { const ids = tags[featureIdsKey] try { if (ids === undefined) { @@ -69,7 +67,7 @@ export class MultiApplyViz extends SpecialVisualizationSvelte { text, autoapply, overwrite, - tagsSource, + tagsSource: tags, state, } return new SvelteUIElement(MultiApplyButton, { params }) diff --git a/src/UI/Popup/PlantNetDetectionViz.ts b/src/UI/Popup/PlantNetDetectionViz.ts index fdfe5dc55f..1b2f2f697d 100644 --- a/src/UI/Popup/PlantNetDetectionViz.ts +++ b/src/UI/Popup/PlantNetDetectionViz.ts @@ -1,11 +1,11 @@ -import { Store, UIEventSource } from "../../Logic/UIEventSource" +import { Store } from "../../Logic/UIEventSource" import { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider" import Wikidata from "../../Logic/Web/Wikidata" import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction" import { And } from "../../Logic/Tags/And" import { Tag } from "../../Logic/Tags/Tag" import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders" -import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization" +import { SpecialVisualisationParams, SpecialVisualizationSvelte } from "../SpecialVisualization" import SvelteUIElement from "../Base/SvelteUIElement" import PlantNet from "../PlantNet/PlantNet.svelte" import { default as PlantNetCode } from "../../Logic/Web/PlantNet" @@ -31,16 +31,13 @@ export class PlantNetDetectionViz extends SpecialVisualizationSvelte { args = [ { name: "image_key", + type:"key", defaultValue: AllImageProviders.defaultKeys.join(","), doc: "The keys given to the images, e.g. if image is given, the first picture URL will be added as image, the second as image:0, the third as image:1, etc... Multiple values are allowed if ';'-separated ", }, ] - public constr( - state: SpecialVisualizationState, - tags: UIEventSource>, - args: string[] - ): SvelteUIElement { + public constr({ state, tags, args }: SpecialVisualisationParams): SvelteUIElement { let imagePrefixes: string[] = undefined if (args.length > 0) { imagePrefixes = [].concat(...args.map((a) => a.split(","))) diff --git a/src/UI/Popup/ShareLinkViz.ts b/src/UI/Popup/ShareLinkViz.ts index cc0c86ccc3..15e602ec8e 100644 --- a/src/UI/Popup/ShareLinkViz.ts +++ b/src/UI/Popup/ShareLinkViz.ts @@ -1,6 +1,5 @@ -import { UIEventSource } from "../../Logic/UIEventSource" import LayerConfig from "../../Models/ThemeConfig/LayerConfig" -import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization" +import { SpecialVisualisationParams, SpecialVisualizationSvelte } from "../SpecialVisualization" import SvelteUIElement from "../Base/SvelteUIElement" import ShareButton from "../Base/ShareButton.svelte" @@ -17,25 +16,25 @@ export class ShareLinkViz extends SpecialVisualizationSvelte { }, { name: "text", + type:"translation", doc: "The text to show on the button. If none is given, will act as a titleIcon", }, ] needsUrls = [] - public constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - args: string[] + public constr({ + state, + tags, + args}:SpecialVisualisationParams ) { const text = args[1] const generateShareData = () => { const title = state?.theme?.title?.txt ?? "MapComplete" - - const matchingLayer: LayerConfig = state?.theme?.getMatchingLayer(tagSource?.data) + const matchingLayer: LayerConfig = state?.theme?.getMatchingLayer(tags?.data) let name = - matchingLayer?.title?.GetRenderValue(tagSource.data)?.Subs(tagSource.data)?.txt ?? - tagSource.data?.name ?? + matchingLayer?.title?.GetRenderValue(tags.data)?.Subs(tags.data)?.txt ?? + tags.data?.name ?? "POI" if (name) { name = `${name} (${title})` diff --git a/src/UI/Popup/TagRendering/SpecialTranslation.svelte b/src/UI/Popup/TagRendering/SpecialTranslation.svelte index a9d5eff003..9686f3ff9f 100644 --- a/src/UI/Popup/TagRendering/SpecialTranslation.svelte +++ b/src/UI/Popup/TagRendering/SpecialTranslation.svelte @@ -51,7 +51,7 @@ { try { return specpart.func - .constr(state, tags, specpart.args, feature, layer) + .constr({state, tags, args : specpart.args, feature, layer}) ?.SetClass(specpart.style) } catch (e) { console.error( diff --git a/src/UI/Popup/UploadToOsmViz.ts b/src/UI/Popup/UploadToOsmViz.ts index ebabf95ff4..b4d9982ab6 100644 --- a/src/UI/Popup/UploadToOsmViz.ts +++ b/src/UI/Popup/UploadToOsmViz.ts @@ -1,4 +1,9 @@ -import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" +import { + SpecialVisualisationParams, + SpecialVisualization, + SpecialVisualizationState, + SpecialVisualizationSvelte, +} from "../SpecialVisualization" import { UIEventSource } from "../../Logic/UIEventSource" import { GeoOperations } from "../../Logic/GeoOperations" import Constants from "../../Models/Constants" @@ -9,18 +14,14 @@ import { ServerSourceInfo } from "../../Models/SourceOverview" /** * Wrapper around 'UploadTraceToOsmUI' */ -export class UploadToOsmViz extends SpecialVisualization { +export class UploadToOsmViz extends SpecialVisualizationSvelte { funcName = "upload_to_osm" docs = "Uploads the GPS-history as GPX to OpenStreetMap.org; clears the history afterwards. The actual feature is ignored." args = [] needsUrls: ServerSourceInfo[] = [Constants.osmAuthConfig] - constr( - state: SpecialVisualizationState, - _: UIEventSource>, - __: string[] - ) { + constr({ state }: SpecialVisualisationParams): SvelteUIElement { const locations = state.historicalUserLocations.features.data return new SvelteUIElement(UploadTraceToOsmUI, { state, diff --git a/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts b/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts index 13e7fbbddd..df7f1ac973 100644 --- a/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts +++ b/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts @@ -1,4 +1,5 @@ import { + SpecialVisualisationParams, SpecialVisualization, SpecialVisualizationState, SpecialVisualizationSvelte, @@ -47,6 +48,7 @@ class MaprouletteSetStatusVis extends SpecialVisualizationSvelte { args = [ { name: "message", + type: "translation", doc: "A message to show to the user", }, { @@ -56,6 +58,7 @@ class MaprouletteSetStatusVis extends SpecialVisualizationSvelte { }, { name: "message_confirm", + type: "translation", doc: "What to show when the task is closed, either by the user or was already closed.", }, { @@ -65,17 +68,19 @@ class MaprouletteSetStatusVis extends SpecialVisualizationSvelte { }, { name: "maproulette_id", + type:"key", doc: "The property name containing the maproulette id", defaultValue: "mr_taskId", }, { name: "ask_feedback", + type: "translation", doc: "If not an empty string, this will be used as question to ask some additional feedback. A text field will be added", defaultValue: "", }, ] - constr(state, tagsSource, args) { + constr({ state, tags, args }: SpecialVisualisationParams): SvelteUIElement { let [message, image, message_closed, statusToSet, maproulette_id_key, askFeedback] = args if (image === "") { image = "confirm" @@ -86,7 +91,7 @@ class MaprouletteSetStatusVis extends SpecialVisualizationSvelte { statusToSet = statusToSet ?? "1" return new SvelteUIElement(MaprouletteSetStatus, { state, - tags: tagsSource, + tags, message, image, message_closed, @@ -106,6 +111,7 @@ class LinkedDataFromWebsite extends SpecialVisualization { { name: "key", defaultValue: "website", + type:"key", doc: "Attempt to load ld+json from the specified URL. This can be in an embedded