From 6bb33771b40c94ad308da7e8c184600bc4212cbc Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 14 Aug 2025 15:54:33 +0200 Subject: [PATCH 01/25] Refactoring: add more types to special visualisations arguments --- src/UI/Popup/AutoApplyButtonVis.ts | 9 +++---- src/UI/Popup/DataVisualisations.ts | 1 + src/UI/Popup/HistogramViz.ts | 1 + .../ImportButtons/ConflateImportButtonViz.ts | 10 +++---- .../Popup/LanguageElement/LanguageElement.ts | 13 +++++++--- src/UI/Popup/MultiApplyViz.ts | 4 ++- src/UI/Popup/PlantNetDetectionViz.ts | 1 + src/UI/Popup/ShareLinkViz.ts | 1 + .../DataImportSpecialVisualisations.ts | 6 +++++ .../ImageVisualisations.ts | 3 ++- .../NoteVisualisations.ts | 5 ++++ .../ReviewSpecialVisualisations.ts | 8 ++++++ .../SettingsVisualisations.ts | 3 +++ src/UI/SpecialVisualisations/TagApplyViz.ts | 1 + ...deringManipulationSpecialVisualisations.ts | 2 ++ .../UISpecialVisualisations.ts | 6 +++-- ...ebAndCommunicationSpecialVisualisations.ts | 10 ++++++- src/UI/SpecialVisualization.ts | 26 +++++++++++++------ 18 files changed, 81 insertions(+), 29 deletions(-) diff --git a/src/UI/Popup/AutoApplyButtonVis.ts b/src/UI/Popup/AutoApplyButtonVis.ts index 9bfa3c365..e00f6ef41 100644 --- a/src/UI/Popup/AutoApplyButtonVis.ts +++ b/src/UI/Popup/AutoApplyButtonVis.ts @@ -2,6 +2,7 @@ import { Store, Stores, UIEventSource } from "../../Logic/UIEventSource" import ThemeConfig from "../../Models/ThemeConfig/ThemeConfig" import { Changes } from "../../Logic/Osm/Changes" import { + SpecialVisualisationArg, SpecialVisualization, SpecialVisualizationState, SpecialVisualizationSvelte, @@ -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, }, diff --git a/src/UI/Popup/DataVisualisations.ts b/src/UI/Popup/DataVisualisations.ts index 5a721e162..0a2412535 100644 --- a/src/UI/Popup/DataVisualisations.ts +++ b/src/UI/Popup/DataVisualisations.ts @@ -302,6 +302,7 @@ class PointsInTimeVis extends SpecialVisualization { args = [ { name: "key", + type:"key", required: true, doc: "The key out of which the points_in_time will be parsed", }, diff --git a/src/UI/Popup/HistogramViz.ts b/src/UI/Popup/HistogramViz.ts index 679255d65..a2f3732f2 100644 --- a/src/UI/Popup/HistogramViz.ts +++ b/src/UI/Popup/HistogramViz.ts @@ -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, }, diff --git a/src/UI/Popup/ImportButtons/ConflateImportButtonViz.ts b/src/UI/Popup/ImportButtons/ConflateImportButtonViz.ts index f72497649..9600960f6 100644 --- a/src/UI/Popup/ImportButtons/ConflateImportButtonViz.ts +++ b/src/UI/Popup/ImportButtons/ConflateImportButtonViz.ts @@ -1,4 +1,4 @@ -import { SpecialVisualization, SpecialVisualizationState } from "../../SpecialVisualization" +import { SpecialVisualisationArg, SpecialVisualization, SpecialVisualizationState } from "../../SpecialVisualization" import { UIEventSource } from "../../../Logic/UIEventSource" import { Feature, Geometry, LineString, Polygon } from "geojson" import BaseUIElement from "../../BaseUIElement" @@ -28,15 +28,11 @@ export default class ConflateImportButtonViz extends SpecialVisualization implem 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", }, ] diff --git a/src/UI/Popup/LanguageElement/LanguageElement.ts b/src/UI/Popup/LanguageElement/LanguageElement.ts index df0deecab..eb4c3786a 100644 --- a/src/UI/Popup/LanguageElement/LanguageElement.ts +++ b/src/UI/Popup/LanguageElement/LanguageElement.ts @@ -10,37 +10,44 @@ export class LanguageElement extends SpecialVisualization { 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", }, ] diff --git a/src/UI/Popup/MultiApplyViz.ts b/src/UI/Popup/MultiApplyViz.ts index 4a6254f58..c9b020822 100644 --- a/src/UI/Popup/MultiApplyViz.ts +++ b/src/UI/Popup/MultiApplyViz.ts @@ -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", diff --git a/src/UI/Popup/PlantNetDetectionViz.ts b/src/UI/Popup/PlantNetDetectionViz.ts index fdfe5dc55..09dc5b9f4 100644 --- a/src/UI/Popup/PlantNetDetectionViz.ts +++ b/src/UI/Popup/PlantNetDetectionViz.ts @@ -31,6 +31,7 @@ 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 ", }, diff --git a/src/UI/Popup/ShareLinkViz.ts b/src/UI/Popup/ShareLinkViz.ts index cc0c86ccc..e97ce55dd 100644 --- a/src/UI/Popup/ShareLinkViz.ts +++ b/src/UI/Popup/ShareLinkViz.ts @@ -17,6 +17,7 @@ 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", }, ] diff --git a/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts b/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts index 13e7fbbdd..cfb771c21 100644 --- a/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts +++ b/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts @@ -47,6 +47,7 @@ class MaprouletteSetStatusVis extends SpecialVisualizationSvelte { args = [ { name: "message", + type: "translation", doc: "A message to show to the user", }, { @@ -56,6 +57,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,11 +67,13 @@ 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: "", }, @@ -106,6 +110,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 diff --git a/src/UI/Popup/MultiApplyViz.ts b/src/UI/Popup/MultiApplyViz.ts index c9b020822..a44ef8c24 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" @@ -36,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) { @@ -71,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 09dc5b9f4..1b2f2f697 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" @@ -37,11 +37,7 @@ export class PlantNetDetectionViz extends SpecialVisualizationSvelte { }, ] - 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 e97ce55dd..15e602ec8 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" @@ -23,20 +22,19 @@ export class ShareLinkViz extends SpecialVisualizationSvelte { ] 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 a9d5eff00..9686f3ff9 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 ebabf95ff..b4d9982ab 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 cfb771c21..df7f1ac97 100644 --- a/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts +++ b/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts @@ -1,4 +1,5 @@ import { + SpecialVisualisationParams, SpecialVisualization, SpecialVisualizationState, SpecialVisualizationSvelte, @@ -79,7 +80,7 @@ class MaprouletteSetStatusVis extends SpecialVisualizationSvelte { }, ] - 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" @@ -90,7 +91,7 @@ class MaprouletteSetStatusVis extends SpecialVisualizationSvelte { statusToSet = statusToSet ?? "1" return new SvelteUIElement(MaprouletteSetStatus, { state, - tags: tagsSource, + tags, message, image, message_closed, @@ -149,21 +150,15 @@ class LinkedDataFromWebsite extends SpecialVisualization { }, ] - constr( - state: SpecialVisualizationState, - tags: UIEventSource>, - argument: string[], - feature: Feature, - layer: LayerConfig - ): SvelteUIElement { + constr({ state, tags, args, feature, layer }: SpecialVisualisationParams): SvelteUIElement { if (state.theme.enableMorePrivacy) { return undefined } - const key = argument[0] ?? "website" - const useProxy = argument[1] !== "no" - const readonly = argument[3] === "readonly" - const isClosed = (argument[4] ?? "yes") === "yes" + const key = args[0] ?? "website" + const useProxy = args[1] !== "no" + const readonly = args[3] === "readonly" + const isClosed = (args[4] ?? "yes") === "yes" const downloadInformation = new UIEventSource(false) const countryStore: Store = tags.mapD((tags) => tags._country) const sourceUrl: Store = tags.mapD((tags) => { @@ -245,7 +240,7 @@ class LinkedDataFromWebsite extends SpecialVisualization { } } -class CompareData extends SpecialVisualization { +class CompareData extends SpecialVisualizationSvelte { funcName = "compare_data" group = "data_import" needsUrls = (args) => args[1].split(";") @@ -270,20 +265,14 @@ class CompareData extends SpecialVisualization { docs = "Gives an interactive element which shows a tag comparison between the OSM-object and the upstream object. This allows to copy some or all tags into OSM" - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - args: string[], - feature: Feature, - layer: LayerConfig - ): BaseUIElement { + constr({ state, tags, args, feature, layer }: SpecialVisualisationParams): SvelteUIElement { const url = args[0] const readonly = args[3] === "yes" const externalData = UIEventSource.fromPromiseWithErr(Utils.downloadJson(url)) return new SvelteUIElement(ComparisonTool, { url, state, - tags: tagSource, + tags, layer, feature, readonly, @@ -292,7 +281,7 @@ class CompareData extends SpecialVisualization { } } export class DataImportSpecialVisualisations { - public static initList(): (SpecialVisualization & { group })[] { + public static initList(): SpecialVisualizationSvelte[] { return [ new TagApplyViz(), new PointImportButtonViz(), diff --git a/src/UI/SpecialVisualisations/FavouriteVisualisations.ts b/src/UI/SpecialVisualisations/FavouriteVisualisations.ts index 4ab3d47ab..156076cb6 100644 --- a/src/UI/SpecialVisualisations/FavouriteVisualisations.ts +++ b/src/UI/SpecialVisualisations/FavouriteVisualisations.ts @@ -1,7 +1,4 @@ -import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization" -import { UIEventSource } from "../../Logic/UIEventSource" -import { Feature } from "geojson" -import LayerConfig from "../../Models/ThemeConfig/LayerConfig" +import { SpecialVisualisationParams, SpecialVisualizationSvelte } from "../SpecialVisualization" import SvelteUIElement from "../Base/SvelteUIElement" import MarkAsFavourite from "../Popup/MarkAsFavourite.svelte" import MarkAsFavouriteMini from "../Popup/MarkAsFavouriteMini.svelte" @@ -14,19 +11,8 @@ class FavouriteStatus extends SpecialVisualizationSvelte { args = [] group = "favourites" - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - argument: string[], - feature: Feature, - layer: LayerConfig - ): SvelteUIElement { - return new SvelteUIElement(MarkAsFavourite, { - tags: tagSource, - state, - layer, - feature, - }) + constr(params: SpecialVisualisationParams): SvelteUIElement { + return new SvelteUIElement(MarkAsFavourite, params) } } @@ -37,19 +23,8 @@ class FavouriteIcon extends SpecialVisualizationSvelte { "A small button that allows a (logged in) contributor to mark a location as a favourite location, sized to fit a title-icon" args = [] - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - argument: string[], - feature: Feature, - layer: LayerConfig - ): SvelteUIElement { - return new SvelteUIElement(MarkAsFavouriteMini, { - tags: tagSource, - state, - layer, - feature, - }) + constr(params: SpecialVisualisationParams): SvelteUIElement { + return new SvelteUIElement(MarkAsFavouriteMini, params) } } diff --git a/src/UI/SpecialVisualisations/ImageVisualisations.ts b/src/UI/SpecialVisualisations/ImageVisualisations.ts index c8c0c47e2..9f08719d0 100644 --- a/src/UI/SpecialVisualisations/ImageVisualisations.ts +++ b/src/UI/SpecialVisualisations/ImageVisualisations.ts @@ -1,12 +1,9 @@ -import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization" +import { SpecialVisualisationParams, SpecialVisualizationSvelte } from "../SpecialVisualization" import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders" import SvelteUIElement from "../Base/SvelteUIElement" import ImageCarousel from "../Image/ImageCarousel.svelte" import UploadImage from "../Image/UploadImage.svelte" import { CombinedFetcher } from "../../Logic/Web/NearbyImagesSearch" -import { UIEventSource } from "../../Logic/UIEventSource" -import { Feature } from "geojson" -import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import { GeoOperations } from "../../Logic/GeoOperations" import NearbyImages from "../Image/NearbyImages.svelte" import NearbyImagesCollapsed from "../Image/NearbyImagesCollapsed.svelte" @@ -32,13 +29,7 @@ class NearbyImageVis extends SpecialVisualizationSvelte { funcName = "nearby_images" needsUrls = CombinedFetcher.apiUrls - constr( - state: SpecialVisualizationState, - tags: UIEventSource>, - args: string[], - feature: Feature, - layer: LayerConfig - ): SvelteUIElement { + constr({ state, tags, args, feature, layer }: SpecialVisualisationParams): SvelteUIElement { const isOpen = args[0] === "open" const readonly = args[1] === "readonly" || args[1] === "yes" const [lon, lat] = GeoOperations.centerpointCoordinates(feature) @@ -69,7 +60,7 @@ class ImageCarouselVis extends SpecialVisualizationSvelte { ] needsUrls = AllImageProviders.apiUrls - constr(state, tags, args, feature) { + constr({state, tags, args, feature}: SpecialVisualisationParams) { let imagePrefixes: string[] = undefined if (args.length > 0) { imagePrefixes = [].concat(...args.map((a) => a.split(";"))) @@ -114,7 +105,7 @@ class ImageUpload extends SpecialVisualizationSvelte { }, ] - constr(state, tags, args, feature) { + constr({state, tags, args, feature}: SpecialVisualisationParams) { const targetKey = args[0] === "" ? undefined : args[0] const noBlur = args[3]?.toLowerCase()?.trim() return new SvelteUIElement(UploadImage, { diff --git a/src/UI/SpecialVisualisations/NoteVisualisations.ts b/src/UI/SpecialVisualisations/NoteVisualisations.ts index 19f9e8f97..1b9bebf57 100644 --- a/src/UI/SpecialVisualisations/NoteVisualisations.ts +++ b/src/UI/SpecialVisualisations/NoteVisualisations.ts @@ -1,8 +1,4 @@ -import { - SpecialVisualization, - SpecialVisualizationState, - SpecialVisualizationSvelte, -} from "../SpecialVisualization" +import { SpecialVisualisationParams, SpecialVisualization, SpecialVisualizationSvelte } from "../SpecialVisualization" import Constants from "../../Models/Constants" import { UIEventSource } from "../../Logic/UIEventSource" import { Feature } from "geojson" @@ -57,11 +53,7 @@ class CloseNoteViz extends SpecialVisualizationSvelte { ] public readonly group = "notes" - public constr( - state: SpecialVisualizationState, - tags: UIEventSource>, - args: string[] - ): SvelteUIElement { + public constr({ state, tags, args }: SpecialVisualisationParams): SvelteUIElement { const { text, icon, idkey, comment, minZoom, zoomButton } = Utils.ParseVisArgs( this.args, args @@ -94,10 +86,7 @@ class AddNoteCommentViz extends SpecialVisualizationSvelte { ] public readonly group = "notes" - public constr( - state: SpecialVisualizationState, - tags: UIEventSource> - ): SvelteUIElement { + public constr({ state, tags }: SpecialVisualisationParams): SvelteUIElement { return new SvelteUIElement(AddNoteComment, { state, tags }) } } @@ -110,13 +99,8 @@ class OpenNote extends SpecialVisualizationSvelte { docs = "Creates a new map note on the given location. This options is placed in the 'last_click'-popup automatically if the 'notes'-layer is enabled" - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - argument: string[], - feature: Feature - ): SvelteUIElement { - const [lon, lat] = GeoOperations.centerpointCoordinates(feature) + constr({ state, feature }: SpecialVisualisationParams): SvelteUIElement { + const [lon, lat] = GeoOperations.centerpointCoordinates(feature) return new SvelteUIElement(CreateNewNote, { state, coordinate: new UIEventSource({ lon, lat }), @@ -138,7 +122,7 @@ class AddImageToNote extends SpecialVisualizationSvelte { group = "notes" needsUrls = [] - constr(state, tags, args, feature) { + constr({ state, tags, args, feature }: SpecialVisualisationParams) { const id = tags.data[args[0] ?? "id"] tags = state.featureProperties.getStore(id) return new SvelteUIElement(UploadImage, { state, tags, feature }) @@ -164,7 +148,7 @@ class VisualiseNoteComment extends SpecialVisualization { ] needsUrls = [Constants.osmAuthConfig] - constr(state, tags, args) { + constr({ state, tags, args }: SpecialVisualisationParams) { return new VariableUiElement( tags .map((tags) => tags[args[0]]) @@ -191,7 +175,7 @@ class VisualiseNoteComment extends SpecialVisualization { } export class NoteVisualisations { - public static initList(): (SpecialVisualization & { group })[] { + public static initList(): SpecialVisualization[] { return [ new AddNoteCommentViz(), new CloseNoteViz(), diff --git a/src/UI/SpecialVisualisations/ReviewSpecialVisualisations.ts b/src/UI/SpecialVisualisations/ReviewSpecialVisualisations.ts index cd9b31f40..750ae06f4 100644 --- a/src/UI/SpecialVisualisations/ReviewSpecialVisualisations.ts +++ b/src/UI/SpecialVisualisations/ReviewSpecialVisualisations.ts @@ -1,4 +1,5 @@ import { + SpecialVisualisationParams, SpecialVisualization, SpecialVisualizationState, SpecialVisualizationSvelte, @@ -16,6 +17,7 @@ import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import BaseUIElement from "../BaseUIElement" import Combine from "../Base/Combine" import { ServerSourceInfo } from "../../Models/SourceOverview" +import { WithUserRelatedState } from "../../Models/ThemeViewState/WithUserRelatedState" class CreateReview extends SpecialVisualizationSvelte { public static MangroveReviewInfo: ServerSourceInfo = { @@ -58,7 +60,7 @@ class CreateReview extends SpecialVisualizationSvelte { }, ] - constr(state, tags, args, feature, layer) { + constr({ state, tags, args, feature, layer }: SpecialVisualisationParams) { const nameKey = args[0] ?? "name" const fallbackName = args[1] const question = args[2] @@ -70,7 +72,7 @@ class CreateReview extends SpecialVisualizationSvelte { nameKey: nameKey, fallbackName, }, - state + state, ) return new SvelteUIElement(ReviewForm, { reviews, @@ -103,7 +105,7 @@ class ListReview extends SpecialVisualizationSvelte { }, ] - constr(state, tags, args, feature) { + constr({ state, tags, args, feature }: SpecialVisualisationParams) { const nameKey = args[0] ?? "name" const fallbackName = args[1] const reviews = FeatureReviews.construct( @@ -138,7 +140,7 @@ class Rating extends SpecialVisualizationSvelte { }, ] - constr(state, tags, args, feature) { + constr({ state, tags, args, feature }: SpecialVisualisationParams) { const nameKey = args[0] ?? "name" const fallbackName = args[1] const reviews = FeatureReviews.construct( @@ -171,12 +173,8 @@ class ImportMangroveKey extends SpecialVisualizationSvelte { }, ] - constr( - state: SpecialVisualizationState, - _: UIEventSource>, - argument: string[] - ): SvelteUIElement { - const [text] = argument + constr({ state, args }: SpecialVisualisationParams): SvelteUIElement { + const [text] = args return new SvelteUIElement(ImportReviewIdentity, { state, text }) } } @@ -207,22 +205,16 @@ class Reviews extends SpecialVisualization { ] needsUrls = [CreateReview.MangroveReviewInfo] - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - args: string[], - feature: Feature, - layer: LayerConfig - ): BaseUIElement { + constr(params: SpecialVisualisationParams): BaseUIElement { return new Combine([ - new CreateReview().constr(state, tagSource, args, feature, layer), - new ListReview().constr(state, tagSource, args, feature), + new CreateReview().constr(params), + new ListReview().constr(params), ]) } } export class ReviewSpecialVisualisations { - public static initList(): (SpecialVisualization & { group })[] { + public static initList(): SpecialVisualization[] { return [ new Rating(), new CreateReview(), diff --git a/src/UI/SpecialVisualisations/SettingsVisualisations.ts b/src/UI/SpecialVisualisations/SettingsVisualisations.ts index 75cd38e00..5347b8433 100644 --- a/src/UI/SpecialVisualisations/SettingsVisualisations.ts +++ b/src/UI/SpecialVisualisations/SettingsVisualisations.ts @@ -1,4 +1,8 @@ -import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization" +import { + SpecialVisualisationParams, + SpecialVisualizationState, + SpecialVisualizationSvelte, +} from "../SpecialVisualization" import SvelteUIElement from "../Base/SvelteUIElement" import Constants from "../../Models/Constants" import LogoutButton from "../Base/LogoutButton.svelte" @@ -25,7 +29,7 @@ class LanguagePickerVis extends SpecialVisualizationSvelte { group = "settings" docs = "A component to set the language of the user interface" - constr(state: SpecialVisualizationState): SvelteUIElement { + constr({ state }: SpecialVisualisationParams): SvelteUIElement { const availableLanguages = Locale.showLinkToWeblate.map((showTranslations) => showTranslations ? LanguageUtils.usedLanguagesSorted @@ -68,7 +72,7 @@ class GpsAllTags extends SpecialVisualizationSvelte { docs = "Shows the current tags of the GPS-representing object, used for debugging" args = [] - constr(state: SpecialVisualizationState): SvelteUIElement { + constr({ state }: SpecialVisualisationParams): SvelteUIElement { const tags = (state).geolocation.currentUserLocation.features.map( (features) => features[0]?.properties ) @@ -85,7 +89,7 @@ class StorageAllTags extends SpecialVisualizationSvelte { docs = "Shows the current state of storage" args = [] - constr(state: SpecialVisualizationState): SvelteUIElement { + constr({ state }: SpecialVisualisationParams): SvelteUIElement { const data = {} for (const key in localStorage) { data[key] = localStorage[key] @@ -117,13 +121,9 @@ export class ClearCachesVis extends SpecialVisualizationSvelte { ] group = "settings" - constr( - _: SpecialVisualizationState, - __: UIEventSource>, - argument: string[] - ): SvelteUIElement { + constr({ args }: SpecialVisualisationParams): SvelteUIElement { return new SvelteUIElement(ClearCaches, { - msg: argument[0] ?? "Clear local caches", + msg: args[0] ?? "Clear local caches", }) } } @@ -144,7 +144,7 @@ class LoginButtonVis extends SpecialVisualizationSvelte { docs = "Show a login button" group = "settings" - constr(state: SpecialVisualizationState, _, args): SvelteUIElement { + constr({ state, args }: SpecialVisualisationParams): SvelteUIElement { const force = args[0].toLowerCase() let msg = args[1] if (msg === "") { @@ -175,16 +175,10 @@ class QrLogin extends SpecialVisualizationSvelte { "A QR-code which shares the current URL and adds the login token. Anyone with this login token will have the same permissions as you currently have. Logging out from this session will also log them out" group = "settings" - constr( - state: SpecialVisualizationState, - tags: UIEventSource>, - argument: string[], - feature: Feature, - layer: LayerConfig - ): SvelteUIElement { + constr({ state, tags, args }: SpecialVisualisationParams): SvelteUIElement { const shared_oauth_cookie = state.osmConnection.getToken() - const sideText = argument[0] - const sideTextClass = argument[1] ?? "" + const sideText = args[0] + const sideTextClass = args[1] ?? "" return new SvelteUIElement(QrCode, { state, tags, @@ -202,8 +196,8 @@ class Logout extends SpecialVisualizationSvelte { docs = "Shows a button where the user can log out" group = "settings" - constr(state: SpecialVisualizationState): SvelteUIElement { - return new SvelteUIElement(LogoutButton, { osmConnection: state.osmConnection }) + constr({ state }: SpecialVisualisationParams): SvelteUIElement { + return new SvelteUIElement(LogoutButton,state) } } @@ -213,7 +207,7 @@ class PendingChanges extends SpecialVisualizationSvelte { group = "settings" args = [] - constr(state: SpecialVisualizationState): SvelteUIElement { + constr({ state }: SpecialVisualisationParams): SvelteUIElement { return new SvelteUIElement(PendingChangesIndicator, { state, compact: false }) } } @@ -224,8 +218,8 @@ class ClearLocationHistoryVis extends SpecialVisualizationSvelte { docs = "A button to remove the travelled track information from the device" args = [] - constr(state) { - return new SvelteUIElement(ClearGPSHistory, { state }) + constr(params: SpecialVisualisationParams) { + return new SvelteUIElement(ClearGPSHistory, params) } } @@ -233,7 +227,6 @@ export class SettingsVisualisations { public static initList(): SpecialVisualizationSvelte[] { return [ new LanguagePickerVis(), - new DisabledQuestionsVis(), new GyroscopeAllTags(), new GpsAllTags(), diff --git a/src/UI/SpecialVisualisations/TagApplyViz.ts b/src/UI/SpecialVisualisations/TagApplyViz.ts index 10ad5ec8c..50de0b06c 100644 --- a/src/UI/SpecialVisualisations/TagApplyViz.ts +++ b/src/UI/SpecialVisualisations/TagApplyViz.ts @@ -1,5 +1,5 @@ import { AutoAction } from "../Popup/AutoApplyButtonVis" -import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" +import { SpecialVisualisationParams, SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" import { Utils } from "../../Utils" import { Store, UIEventSource } from "../../Logic/UIEventSource" import { Tag } from "../../Logic/Tags/Tag" @@ -174,12 +174,7 @@ export default class TagApplyViz extends SpecialVisualization implements AutoAct } } - public constr( - state: SpecialVisualizationState, - tags: UIEventSource>, - args: string[], - feature: Feature - ): SvelteUIElement { + public constr({ state, tags, args, feature }: SpecialVisualisationParams): SvelteUIElement { const tagsToApply: Store = TagApplyViz.generateTagsToApply(args[0], tags) const msg = args[1] let image = args[2]?.trim() diff --git a/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts b/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts index 2dee3894c..b997417f7 100644 --- a/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts +++ b/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts @@ -1,4 +1,5 @@ import { + SpecialVisualisationParams, SpecialVisualization, SpecialVisualizationState, SpecialVisualizationSvelte, @@ -39,7 +40,7 @@ class StealViz extends SpecialVisualization { ] needsUrls = [] - constr(state: SpecialVisualizationState, featureTags, args) { + constr({ state, tags, args }: SpecialVisualisationParams) { const [featureIdKey, layerAndtagRenderingIds] = args const tagRenderings: [LayerConfig, TagRenderingConfig][] = [] for (const layerAndTagRenderingId of layerAndtagRenderingIds.split(";")) { @@ -52,7 +53,7 @@ class StealViz extends SpecialVisualization { throw "Could not create stolen tagrenddering: tagRenderings not found" } return new VariableUiElement( - featureTags.map( + tags.map( (tags) => { const featureId = tags[featureIdKey] if (featureId === undefined) { @@ -133,18 +134,12 @@ class Multi extends SpecialVisualization { }, ] - constr( - state: SpecialVisualizationState, - featureTags: UIEventSource>, - args: string[], - feature: Feature, - layer: LayerConfig - ) { + constr({ state, tags, args, feature, layer }: SpecialVisualisationParams) { const [key, tr, classesRaw] = args const classes = classesRaw ?? "" const translation = new Translation({ "*": tr }) return new VariableUiElement( - featureTags.map((tags) => { + tags.map((tags) => { let properties: object[] if (typeof tags[key] === "string") { properties = JSON.parse(tags[key]) @@ -198,20 +193,14 @@ class Group extends SpecialVisualizationSvelte { }, ] - constr( - state: SpecialVisualizationState, - tags: UIEventSource>, - argument: string[], - selectedElement: Feature, - layer: LayerConfig - ): SvelteUIElement { - const [header, labelsStr, blacklistStr] = argument + constr({ state, tags, args, feature, layer }: SpecialVisualisationParams): SvelteUIElement { + const [header, labelsStr, blacklistStr] = args const labels = labelsStr.split(";").map((x) => x.trim()) const blacklist = blacklistStr?.split(";")?.map((x) => x.trim()) ?? [] return new SvelteUIElement(GroupedView, { state, tags, - selectedElement, + selectedElement: feature, layer, header, labels, @@ -226,10 +215,10 @@ class OpenInId extends SpecialVisualizationSvelte { args = [] group = "tagrendering_manipulation" - constr(state, feature): SvelteUIElement { + constr({state, feature}: SpecialVisualisationParams): SvelteUIElement { return new SvelteUIElement(OpenIdEditor, { mapProperties: state.mapProperties, - objectId: feature.data.id, + objectId: feature.properties.id, }) } } @@ -254,12 +243,12 @@ class OpenInJosm extends SpecialVisualizationSvelte { }, ] - constr(state): SvelteUIElement { - return new SvelteUIElement(OpenJosm, { state }) + constr(params: SpecialVisualisationParams): SvelteUIElement { + return new SvelteUIElement(OpenJosm, params) } } export default class TagrenderingManipulationSpecialVisualisations { - public static initList(): (SpecialVisualization & { group })[] { + public static initList(): SpecialVisualization[] { return [new StealViz(), new Multi(), new Group(), new OpenInId(), new OpenInJosm()] } } diff --git a/src/UI/SpecialVisualisations/UISpecialVisualisations.ts b/src/UI/SpecialVisualisations/UISpecialVisualisations.ts index cda7fe99e..0f2a08e6c 100644 --- a/src/UI/SpecialVisualisations/UISpecialVisualisations.ts +++ b/src/UI/SpecialVisualisations/UISpecialVisualisations.ts @@ -1,4 +1,5 @@ import { + SpecialVisualisationParams, SpecialVisualization, SpecialVisualizationState, SpecialVisualizationSvelte, @@ -49,13 +50,7 @@ class QuestionViz extends SpecialVisualizationSvelte { ] group = "default" - constr( - state: SpecialVisualizationState, - tags: UIEventSource>, - args: string[], - feature: Feature, - layer: LayerConfig - ): SvelteUIElement { + constr({ state, tags, args, feature, layer }: SpecialVisualisationParams): SvelteUIElement { const labels = args[0] ?.split(";") ?.map((s) => s.trim()) @@ -110,12 +105,7 @@ class Minimap extends SpecialVisualizationSvelte { example = "`{minimap()}`, `{minimap(17, id, _list_of_embedded_feature_ids_calculated_by_calculated_tag):height:10rem; border: 2px solid black}`" - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - args: string[], - feature: Feature - ): SvelteUIElement { + constr({ state, tags, args, feature, layer }: SpecialVisualisationParams): SvelteUIElement { const minzoom = Number(args[0] ?? 18) const ids = args[1]?.split(";")?.map((s) => s.trim()) ?? ["id"] const clss = args[2] @@ -125,7 +115,7 @@ class Minimap extends SpecialVisualizationSvelte { idkeys: ids, clss, feature, - tagSource, + tags, }) } } @@ -136,12 +126,9 @@ class SplitButton extends SpecialVisualizationSvelte { args = [] group = "default" - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource> - ): SvelteUIElement { + constr({ state, tags }: SpecialVisualisationParams): SvelteUIElement { return new SvelteUIElement(SplitRoadWizard, { - id: tagSource.map((pr) => pr.id), + id: tags.map((pr) => pr.id), state, }) } @@ -154,13 +141,7 @@ class MoveButton extends SpecialVisualizationSvelte { args = [] group = "default" - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - argument: string[], - feature: Feature, - layer: LayerConfig - ): SvelteUIElement { + constr({ state, feature, layer }: SpecialVisualisationParams): SvelteUIElement { if (feature.geometry.type !== "Point") { return undefined } @@ -180,18 +161,12 @@ class DeleteButton extends SpecialVisualizationSvelte { args = [] group = "default" - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - argument: string[], - feature: Feature, - layer: LayerConfig - ): SvelteUIElement { + constr({ state, tags, feature, layer }: SpecialVisualisationParams): SvelteUIElement { if (!layer.deletion) { return undefined } return new SvelteUIElement(DeleteWizard, { - tags: tagSource, + tags, deleteConfig: layer.deletion, state, feature, @@ -215,14 +190,9 @@ class QrCodeVis extends SpecialVisualizationSvelte { group = "default" docs = "Generates a QR-code to share the selected object" - constr( - state: SpecialVisualizationState, - tags: UIEventSource>, - argument: string[], - feature: Feature - ): SvelteUIElement { - const sideText = argument[0] - const sideTextClass = argument[1] ?? "" + constr({ state, tags, args, feature }: SpecialVisualisationParams): SvelteUIElement { + const sideText = args[0] + const sideTextClass = args[1] ?? "" return new SvelteUIElement(QrCode, { state, tags, @@ -248,18 +218,12 @@ class IfNothingKnown extends SpecialVisualizationSvelte { docs = "Shows a 'nothing is currently known-message if there is at least one unanswered question and no known (answerable) question" - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - argument: string[], - feature: Feature, - layer: LayerConfig - ): SvelteUIElement { - const text = argument[0] - const cssClasses = argument[1] + constr({ state, tags, args, layer }: SpecialVisualisationParams): SvelteUIElement { + const text = args[0] + const cssClasses = args[1] return new SvelteUIElement(NothingKnown, { state, - tags: tagSource, + tags, layer, text, cssClasses, @@ -274,7 +238,7 @@ class AddNewPointVis extends SpecialVisualizationSvelte { args = [] group = "default" - constr(state: SpecialVisualizationState, _, __, feature: GeoJSON): SvelteUIElement { + constr({ state, feature }: SpecialVisualisationParams): SvelteUIElement { const [lon, lat] = GeoOperations.centerpointCoordinates(feature) return new SvelteUIElement(AddNewPoint, { state, @@ -297,14 +261,10 @@ class Translated extends SpecialVisualization { }, ] - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - argument: string[] - ): BaseUIElement { + constr({ tags, args }: SpecialVisualisationParams): BaseUIElement { return new VariableUiElement( - tagSource.map((tags) => { - const v = tags[argument[0] ?? "value"] + tags.map((tags) => { + const v = tags[args[0] ?? "value"] try { const tr = typeof v === "string" ? JSON.parse(v) : v return new Translation(tr).SetClass("font-bold") @@ -327,14 +287,8 @@ class TitleVis extends SpecialVisualizationSvelte { example = "`What is the phone number of {title()}`, which might automatically become `What is the phone number of XYZ`." - constr( - state: SpecialVisualizationState, - tags: UIEventSource>, - _: string[], - feature: Feature, - layer: LayerConfig - ) { - return new SvelteUIElement(FeatureTitle, { state, tags, feature, layer }) + constr(params: SpecialVisualisationParams): SvelteUIElement { + return new SvelteUIElement(FeatureTitle, params) } } @@ -352,13 +306,7 @@ class BracedVis extends SpecialVisualization { }, ] - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - args: string[], - feature: Feature, - layer: LayerConfig - ): BaseUIElement { + constr({ args }: SpecialVisualisationParams): BaseUIElement { return new FixedUiElement("{" + args[0] + "}") } } @@ -368,18 +316,9 @@ class CreateCopyVis extends SpecialVisualizationSvelte { funcName = "create_copy" docs = "Allow to create a copy of the current element" args = [] - constr( - state: SpecialVisualizationState, - tags: UIEventSource>, - argument: string[], - feature: Feature, - layer: LayerConfig - ): SvelteUIElement { - try { - return new SvelteUIElement(CreateCopy, { state, tags, argument, feature, layer }) - } catch (e) { - console.error(">>> failed", e) - } + + constr(params: SpecialVisualisationParams): SvelteUIElement { + return new SvelteUIElement(CreateCopy, params) } } diff --git a/src/UI/SpecialVisualisations/WebAndCommunicationSpecialVisualisations.ts b/src/UI/SpecialVisualisations/WebAndCommunicationSpecialVisualisations.ts index be8831bd0..b6a048625 100644 --- a/src/UI/SpecialVisualisations/WebAndCommunicationSpecialVisualisations.ts +++ b/src/UI/SpecialVisualisations/WebAndCommunicationSpecialVisualisations.ts @@ -1,10 +1,5 @@ -import { - SpecialVisualization, - SpecialVisualizationState, - SpecialVisualizationSvelte, -} from "../SpecialVisualization" -import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource" -import BaseUIElement from "../BaseUIElement" +import { SpecialVisualisationParams, SpecialVisualization, SpecialVisualizationSvelte } from "../SpecialVisualization" +import { ImmutableStore, Store } from "../../Logic/UIEventSource" import SvelteUIElement from "../Base/SvelteUIElement" import FediverseLink from "../Popup/FediverseLink.svelte" import Wikidata, { WikidataResponse } from "../../Logic/Web/Wikidata" @@ -18,7 +13,7 @@ import SendEmail from "../Popup/SendEmail.svelte" import DynLink from "../Base/DynLink.svelte" import { Lists } from "../../Utils/Lists" -class FediverseLinkVis extends SpecialVisualization { +class FediverseLinkVis extends SpecialVisualizationSvelte { funcName = "fediverse_link" group = "web_and_communication" docs = "Converts a fediverse username or link into a clickable link" @@ -31,17 +26,13 @@ class FediverseLinkVis extends SpecialVisualization { }, ] - constr( - state: SpecialVisualizationState, - tags: UIEventSource>, - argument: string[] - ): BaseUIElement { - const key = argument[0] + constr({ state, tags, args }: SpecialVisualisationParams): SvelteUIElement { + const key = args[0] return new SvelteUIElement(FediverseLink, { key, tags, state }) } } -class WikipediaVis extends SpecialVisualization { +class WikipediaVis extends SpecialVisualizationSvelte { funcName = "wikipedia" group = "web_and_communication" docs = "A box showing the corresponding wikipedia article(s) - based on the **wikidata** tag." @@ -58,9 +49,9 @@ class WikipediaVis extends SpecialVisualization { example = "`{wikipedia()}` is a basic example, `{wikipedia(name:etymology:wikidata)}` to show the wikipedia page of whom the feature was named after. Also remember that these can be styled, e.g. `{wikipedia():max-height: 10rem}` to limit the height" - constr(_, tagsSource, args) { + constr({ tags, args }: SpecialVisualisationParams) { const keys = args[0].split(";").map((k) => k.trim()) - const wikiIds: Store = tagsSource.map((tags) => { + const wikiIds: Store = tags.map((tags) => { const key = keys.find((k) => tags[k] !== undefined && tags[k] !== "") return tags[key]?.split(";")?.map((id) => id.trim()) ?? [] }) @@ -88,8 +79,8 @@ class WikidatalabelVis extends SpecialVisualization { example = "`{wikidata_label()}` is a basic example, `{wikipedia(name:etymology:wikidata)}` to show the label itself" - constr(_, tagsSource, args) { - const id = tagsSource + constr({ tags, args }: SpecialVisualisationParams) { + const id = tags .map((tags) => tags[args[0]]) .map((wikidata) => { const wikidataIds = Lists.noEmpty( @@ -144,8 +135,8 @@ class SendEmailVis extends SpecialVisualizationSvelte { }, ] - constr(__, tags, args) { - return new SvelteUIElement(SendEmail, { args, tags }) + constr(params: SpecialVisualisationParams) { + return new SvelteUIElement(SendEmail, params) } } @@ -184,31 +175,27 @@ class LinkVis extends SpecialVisualizationSvelte { }, ] - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - args: string[] - ): SvelteUIElement { + constr({ tags, args }: SpecialVisualisationParams): SvelteUIElement { let [text, href, classnames, download, ariaLabel, icon] = args if (download === "") { download = undefined } const newTab = download === undefined && !href.startsWith("#") - const textStore = tagSource.map((tags) => Utils.SubstituteKeys(text, tags)) - const hrefStore = tagSource.map((tags) => Utils.SubstituteKeys(href, tags)) + const textStore = tags.map((tags) => Utils.SubstituteKeys(text, tags)) + const hrefStore = tags.map((tags) => Utils.SubstituteKeys(href, tags)) return new SvelteUIElement(DynLink, { text: textStore, href: hrefStore, classnames: new ImmutableStore(classnames), - download: tagSource.map((tags) => Utils.SubstituteKeys(download, tags)), - ariaLabel: tagSource.map((tags) => Utils.SubstituteKeys(ariaLabel, tags)), + download: tags.map((tags) => Utils.SubstituteKeys(download, tags)), + ariaLabel: tags.map((tags) => Utils.SubstituteKeys(ariaLabel, tags)), newTab: new ImmutableStore(newTab), - icon: tagSource.map((tags) => Utils.SubstituteKeys(icon, tags)), + icon: tags.map((tags) => Utils.SubstituteKeys(icon, tags)), }) } } export class WebAndCommunicationSpecialVisualisations { - public static initList(): (SpecialVisualization & { group })[] { + public static initList(): SpecialVisualization[] { return [ new FediverseLinkVis(), new WikipediaVis(), diff --git a/src/UI/SpecialVisualization.ts b/src/UI/SpecialVisualization.ts index 6b4285bb2..012ce52aa 100644 --- a/src/UI/SpecialVisualization.ts +++ b/src/UI/SpecialVisualization.ts @@ -98,10 +98,20 @@ export interface SpecialVisualisationArg { export class SpecialVisualizationUtils { + static parseArgs(specs: { name: string; defaultValue?: string }[], args: string[]){ + return Utils.ParseVisArgs(specs, args) + } } +export interface SpecialVisualisationParams { + state: SpecialVisualizationState + tags: UIEventSource> + args: string[] + feature: Feature + layer: LayerConfig +} export abstract class SpecialVisualization { readonly funcName: string @@ -128,23 +138,11 @@ export abstract class SpecialVisualization { structuredExamples?(): { feature: Feature>; args: string[] }[] - abstract constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - argument: string[], - feature: Feature, - layer: LayerConfig - ): BaseUIElement + abstract constr(options: SpecialVisualisationParams): BaseUIElement } export abstract class SpecialVisualizationSvelte extends SpecialVisualization { - abstract constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - argument: string[], - feature: Feature, - layer: LayerConfig - ): SvelteUIElement + abstract constr(options: SpecialVisualisationParams): SvelteUIElement } export type RenderingSpecification = diff --git a/src/UI/SpecialVisualizations.ts b/src/UI/SpecialVisualizations.ts index 69d93a165..5a1416e1e 100644 --- a/src/UI/SpecialVisualizations.ts +++ b/src/UI/SpecialVisualizations.ts @@ -1,4 +1,4 @@ -import { RenderingSpecification, SpecialVisualization } from "./SpecialVisualization" +import { RenderingSpecification, SpecialVisualization, SpecialVisualizationSvelte } from "./SpecialVisualization" import { UploadToOsmViz } from "./Popup/UploadToOsmViz" import { MultiApplyViz } from "./Popup/MultiApplyViz" import AutoApplyButtonVis from "./Popup/AutoApplyButtonVis" @@ -176,22 +176,26 @@ export default class SpecialVisualizations { } private static initList(): SpecialVisualization[] { - const specialVisualizations: SpecialVisualization[] = [ + const specialVisualizationsSv: SpecialVisualizationSvelte[] = [ ...ImageVisualisations.initList(), - ...NoteVisualisations.initList(), ...FavouriteVisualisations.initList(), - ...UISpecialVisualisations.initList(), ...SettingsVisualisations.initList(), - ...ReviewSpecialVisualisations.initList(), ...DataImportSpecialVisualisations.initList(), - ...TagrenderingManipulationSpecialVisualisations.initList(), - ...WebAndCommunicationSpecialVisualisations.initList(), - ...DataVisualisations.initList(), ...DataExportVisualisations.initList(), new UploadToOsmViz(), new MultiApplyViz(), ] + const specialVisualizations: SpecialVisualization[] = [ + ...NoteVisualisations.initList(), + ...UISpecialVisualisations.initList(), + ...ReviewSpecialVisualisations.initList(), + ...TagrenderingManipulationSpecialVisualisations.initList(), + ...WebAndCommunicationSpecialVisualisations.initList(), + ...DataVisualisations.initList(), + ...specialVisualizationsSv + ] + specialVisualizations.push(new AutoApplyButtonVis(specialVisualizations)) if (Utils.runningFromConsole) { diff --git a/src/Utils.ts b/src/Utils.ts index 432a2fab6..df6d99199 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -1,6 +1,5 @@ import DOMPurify from "dompurify" import { Lists } from "./Utils/Lists" -import { Strings } from "./Utils/Strings" export class Utils { /** From 86f1aff148b1fc9e05e12eba1f91e4a96ed51070 Mon Sep 17 00:00:00 2001 From: Osmwithspace <> Date: Fri, 15 Aug 2025 18:37:11 +0200 Subject: [PATCH 03/25] visibility of shelters improved --- assets/themes/nature/nature.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/assets/themes/nature/nature.json b/assets/themes/nature/nature.json index 78fc28d18..626487ca5 100644 --- a/assets/themes/nature/nature.json +++ b/assets/themes/nature/nature.json @@ -56,6 +56,14 @@ "drinking_water", "birdhide", "nature_reserve", + { + "builtin": [ + "shelter" + ], + "override": { + "minzoom": 11 + } + }, { "builtin": [ "map", @@ -64,7 +72,6 @@ "picnic_table", "toilet", "guidepost", - "shelter", "bbq", "firepit", "insect_hotel" From 1bd226a6fa5f58961f49136c6eee1ad1ee1b12b7 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 15 Aug 2025 02:32:04 +0200 Subject: [PATCH 04/25] Refactoring: switch specialVis constructor to an object --- assets/layers/icons/icons.json | 4 +- assets/layers/last_click/last_click.json | 4 +- assets/layers/note/note.json | 4 +- .../osm_community_index.json | 12 +- assets/themes/grb/grb.json | 71 ++++++++++- src/Models/ThemeViewState/WithChangesState.ts | 12 +- .../ThemeViewState/WithLayoutSourceState.ts | 3 +- .../ThemeViewState/WithSpecialLayers.ts | 5 +- src/UI/Map/ShowDataLayer.ts | 5 - src/UI/Popup/AutoApplyButtonVis.ts | 18 ++- src/UI/Popup/DataExportVisualisations.ts | 20 +--- src/UI/Popup/DataVisualisations.ts | 97 +++++---------- src/UI/Popup/HistogramViz.ts | 10 +- .../ImportButtons/ConflateImportButtonViz.ts | 34 +++--- src/UI/Popup/ImportButtons/ImportFlow.ts | 9 +- .../ImportButtons/PointImportButtonViz.ts | 25 ++-- .../Popup/ImportButtons/WayImportButtonViz.ts | 30 ++--- .../Popup/LanguageElement/LanguageElement.ts | 26 ++-- src/UI/Popup/MapillaryLinkVis.ts | 12 +- src/UI/Popup/MinimapViz.svelte | 19 +-- src/UI/Popup/MultiApplyViz.ts | 14 +-- src/UI/Popup/PlantNetDetectionViz.ts | 10 +- src/UI/Popup/ShareLinkViz.ts | 18 ++- .../TagRendering/SpecialTranslation.svelte | 2 +- src/UI/Popup/UploadToOsmViz.ts | 15 +-- .../DataImportSpecialVisualisations.ts | 35 ++---- .../FavouriteVisualisations.ts | 35 +----- .../ImageVisualisations.ts | 17 +-- .../NoteVisualisations.ts | 32 ++--- .../ReviewSpecialVisualisations.ts | 32 ++--- .../SettingsVisualisations.ts | 45 +++---- src/UI/SpecialVisualisations/TagApplyViz.ts | 9 +- ...deringManipulationSpecialVisualisations.ts | 37 ++---- .../UISpecialVisualisations.ts | 113 ++++-------------- ...ebAndCommunicationSpecialVisualisations.ts | 51 +++----- src/UI/SpecialVisualization.ts | 26 ++-- src/UI/SpecialVisualizations.ts | 20 ++-- src/Utils.ts | 1 - 38 files changed, 369 insertions(+), 563 deletions(-) diff --git a/assets/layers/icons/icons.json b/assets/layers/icons/icons.json index 54ebd45d5..93e9e91ec 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 3185c513f..6aaa19115 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 b1dd36f32..eedef08c6 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 1c605ca38..150fc6ecd 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/themes/grb/grb.json b/assets/themes/grb/grb.json index 337a13723..256070bca 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/src/Models/ThemeViewState/WithChangesState.ts b/src/Models/ThemeViewState/WithChangesState.ts index a7f919648..27c2b85f1 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 cb9ceeeac..d09f41d96 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 15cdc4cb6..ff2bb58df 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 4d80c6c92..434e705fd 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 e00f6ef41..8257745e8 100644 --- a/src/UI/Popup/AutoApplyButtonVis.ts +++ b/src/UI/Popup/AutoApplyButtonVis.ts @@ -3,8 +3,8 @@ 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" @@ -83,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, @@ -102,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 81f5f3747..dc82a9125 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 0a2412535..d85ef9e71 100644 --- a/src/UI/Popup/DataVisualisations.ts +++ b/src/UI/Popup/DataVisualisations.ts @@ -1,11 +1,6 @@ -import { - SpecialVisualization, - SpecialVisualizationState, - SpecialVisualizationSvelte, -} from "../SpecialVisualization" +import { SpecialVisualisationParams, SpecialVisualization, 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 +15,15 @@ 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" -class DirectionIndicatorVis extends SpecialVisualization { +class DirectionIndicatorVis extends SpecialVisualizationSvelte { funcName = "direction_indicator" args = [] @@ -36,13 +31,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 +55,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 +105,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 +138,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 +169,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 +185,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 +199,23 @@ 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 = [] - 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 }) } } @@ -245,13 +224,7 @@ class PresetTypeSelect extends SpecialVisualizationSvelte { docs = "An editable tag rendering which allows to change the type" args = [] - 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 +250,7 @@ class PresetTypeSelect extends SpecialVisualizationSvelte { return new SvelteUIElement(TagRenderingEditable, { config, tags, - selectedElement, + selectedElement: feature, state, layer, }) @@ -290,8 +263,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) } } @@ -308,18 +281,12 @@ class PointsInTimeVis extends SpecialVisualization { }, ] - 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 })) diff --git a/src/UI/Popup/HistogramViz.ts b/src/UI/Popup/HistogramViz.ts index a2f3732f2..7cfffd1f4 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" @@ -36,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 9600960f6..fce251a7d 100644 --- a/src/UI/Popup/ImportButtons/ConflateImportButtonViz.ts +++ b/src/UI/Popup/ImportButtons/ConflateImportButtonViz.ts @@ -1,7 +1,11 @@ -import { SpecialVisualisationArg, 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,7 +27,7 @@ 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" @@ -80,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 3deea70ed..4ad16da1e 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 933310268..b36724eb8 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 61272532f..91106af08 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 eb4c3786a..4d95929e3 100644 --- a/src/UI/Popup/LanguageElement/LanguageElement.ts +++ b/src/UI/Popup/LanguageElement/LanguageElement.ts @@ -1,12 +1,8 @@ -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 = [] @@ -66,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()}" } @@ -101,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 048e99f94..b3000a697 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 e91ada494..25b95242f 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 c9b020822..a44ef8c24 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" @@ -36,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) { @@ -71,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 09dc5b9f4..1b2f2f697 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" @@ -37,11 +37,7 @@ export class PlantNetDetectionViz extends SpecialVisualizationSvelte { }, ] - 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 e97ce55dd..15e602ec8 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" @@ -23,20 +22,19 @@ export class ShareLinkViz extends SpecialVisualizationSvelte { ] 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 a9d5eff00..9686f3ff9 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 ebabf95ff..b4d9982ab 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 cfb771c21..df7f1ac97 100644 --- a/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts +++ b/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts @@ -1,4 +1,5 @@ import { + SpecialVisualisationParams, SpecialVisualization, SpecialVisualizationState, SpecialVisualizationSvelte, @@ -79,7 +80,7 @@ class MaprouletteSetStatusVis extends SpecialVisualizationSvelte { }, ] - 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" @@ -90,7 +91,7 @@ class MaprouletteSetStatusVis extends SpecialVisualizationSvelte { statusToSet = statusToSet ?? "1" return new SvelteUIElement(MaprouletteSetStatus, { state, - tags: tagsSource, + tags, message, image, message_closed, @@ -149,21 +150,15 @@ class LinkedDataFromWebsite extends SpecialVisualization { }, ] - constr( - state: SpecialVisualizationState, - tags: UIEventSource>, - argument: string[], - feature: Feature, - layer: LayerConfig - ): SvelteUIElement { + constr({ state, tags, args, feature, layer }: SpecialVisualisationParams): SvelteUIElement { if (state.theme.enableMorePrivacy) { return undefined } - const key = argument[0] ?? "website" - const useProxy = argument[1] !== "no" - const readonly = argument[3] === "readonly" - const isClosed = (argument[4] ?? "yes") === "yes" + const key = args[0] ?? "website" + const useProxy = args[1] !== "no" + const readonly = args[3] === "readonly" + const isClosed = (args[4] ?? "yes") === "yes" const downloadInformation = new UIEventSource(false) const countryStore: Store = tags.mapD((tags) => tags._country) const sourceUrl: Store = tags.mapD((tags) => { @@ -245,7 +240,7 @@ class LinkedDataFromWebsite extends SpecialVisualization { } } -class CompareData extends SpecialVisualization { +class CompareData extends SpecialVisualizationSvelte { funcName = "compare_data" group = "data_import" needsUrls = (args) => args[1].split(";") @@ -270,20 +265,14 @@ class CompareData extends SpecialVisualization { docs = "Gives an interactive element which shows a tag comparison between the OSM-object and the upstream object. This allows to copy some or all tags into OSM" - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - args: string[], - feature: Feature, - layer: LayerConfig - ): BaseUIElement { + constr({ state, tags, args, feature, layer }: SpecialVisualisationParams): SvelteUIElement { const url = args[0] const readonly = args[3] === "yes" const externalData = UIEventSource.fromPromiseWithErr(Utils.downloadJson(url)) return new SvelteUIElement(ComparisonTool, { url, state, - tags: tagSource, + tags, layer, feature, readonly, @@ -292,7 +281,7 @@ class CompareData extends SpecialVisualization { } } export class DataImportSpecialVisualisations { - public static initList(): (SpecialVisualization & { group })[] { + public static initList(): SpecialVisualizationSvelte[] { return [ new TagApplyViz(), new PointImportButtonViz(), diff --git a/src/UI/SpecialVisualisations/FavouriteVisualisations.ts b/src/UI/SpecialVisualisations/FavouriteVisualisations.ts index 4ab3d47ab..156076cb6 100644 --- a/src/UI/SpecialVisualisations/FavouriteVisualisations.ts +++ b/src/UI/SpecialVisualisations/FavouriteVisualisations.ts @@ -1,7 +1,4 @@ -import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization" -import { UIEventSource } from "../../Logic/UIEventSource" -import { Feature } from "geojson" -import LayerConfig from "../../Models/ThemeConfig/LayerConfig" +import { SpecialVisualisationParams, SpecialVisualizationSvelte } from "../SpecialVisualization" import SvelteUIElement from "../Base/SvelteUIElement" import MarkAsFavourite from "../Popup/MarkAsFavourite.svelte" import MarkAsFavouriteMini from "../Popup/MarkAsFavouriteMini.svelte" @@ -14,19 +11,8 @@ class FavouriteStatus extends SpecialVisualizationSvelte { args = [] group = "favourites" - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - argument: string[], - feature: Feature, - layer: LayerConfig - ): SvelteUIElement { - return new SvelteUIElement(MarkAsFavourite, { - tags: tagSource, - state, - layer, - feature, - }) + constr(params: SpecialVisualisationParams): SvelteUIElement { + return new SvelteUIElement(MarkAsFavourite, params) } } @@ -37,19 +23,8 @@ class FavouriteIcon extends SpecialVisualizationSvelte { "A small button that allows a (logged in) contributor to mark a location as a favourite location, sized to fit a title-icon" args = [] - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - argument: string[], - feature: Feature, - layer: LayerConfig - ): SvelteUIElement { - return new SvelteUIElement(MarkAsFavouriteMini, { - tags: tagSource, - state, - layer, - feature, - }) + constr(params: SpecialVisualisationParams): SvelteUIElement { + return new SvelteUIElement(MarkAsFavouriteMini, params) } } diff --git a/src/UI/SpecialVisualisations/ImageVisualisations.ts b/src/UI/SpecialVisualisations/ImageVisualisations.ts index c8c0c47e2..9f08719d0 100644 --- a/src/UI/SpecialVisualisations/ImageVisualisations.ts +++ b/src/UI/SpecialVisualisations/ImageVisualisations.ts @@ -1,12 +1,9 @@ -import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization" +import { SpecialVisualisationParams, SpecialVisualizationSvelte } from "../SpecialVisualization" import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders" import SvelteUIElement from "../Base/SvelteUIElement" import ImageCarousel from "../Image/ImageCarousel.svelte" import UploadImage from "../Image/UploadImage.svelte" import { CombinedFetcher } from "../../Logic/Web/NearbyImagesSearch" -import { UIEventSource } from "../../Logic/UIEventSource" -import { Feature } from "geojson" -import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import { GeoOperations } from "../../Logic/GeoOperations" import NearbyImages from "../Image/NearbyImages.svelte" import NearbyImagesCollapsed from "../Image/NearbyImagesCollapsed.svelte" @@ -32,13 +29,7 @@ class NearbyImageVis extends SpecialVisualizationSvelte { funcName = "nearby_images" needsUrls = CombinedFetcher.apiUrls - constr( - state: SpecialVisualizationState, - tags: UIEventSource>, - args: string[], - feature: Feature, - layer: LayerConfig - ): SvelteUIElement { + constr({ state, tags, args, feature, layer }: SpecialVisualisationParams): SvelteUIElement { const isOpen = args[0] === "open" const readonly = args[1] === "readonly" || args[1] === "yes" const [lon, lat] = GeoOperations.centerpointCoordinates(feature) @@ -69,7 +60,7 @@ class ImageCarouselVis extends SpecialVisualizationSvelte { ] needsUrls = AllImageProviders.apiUrls - constr(state, tags, args, feature) { + constr({state, tags, args, feature}: SpecialVisualisationParams) { let imagePrefixes: string[] = undefined if (args.length > 0) { imagePrefixes = [].concat(...args.map((a) => a.split(";"))) @@ -114,7 +105,7 @@ class ImageUpload extends SpecialVisualizationSvelte { }, ] - constr(state, tags, args, feature) { + constr({state, tags, args, feature}: SpecialVisualisationParams) { const targetKey = args[0] === "" ? undefined : args[0] const noBlur = args[3]?.toLowerCase()?.trim() return new SvelteUIElement(UploadImage, { diff --git a/src/UI/SpecialVisualisations/NoteVisualisations.ts b/src/UI/SpecialVisualisations/NoteVisualisations.ts index 19f9e8f97..1b9bebf57 100644 --- a/src/UI/SpecialVisualisations/NoteVisualisations.ts +++ b/src/UI/SpecialVisualisations/NoteVisualisations.ts @@ -1,8 +1,4 @@ -import { - SpecialVisualization, - SpecialVisualizationState, - SpecialVisualizationSvelte, -} from "../SpecialVisualization" +import { SpecialVisualisationParams, SpecialVisualization, SpecialVisualizationSvelte } from "../SpecialVisualization" import Constants from "../../Models/Constants" import { UIEventSource } from "../../Logic/UIEventSource" import { Feature } from "geojson" @@ -57,11 +53,7 @@ class CloseNoteViz extends SpecialVisualizationSvelte { ] public readonly group = "notes" - public constr( - state: SpecialVisualizationState, - tags: UIEventSource>, - args: string[] - ): SvelteUIElement { + public constr({ state, tags, args }: SpecialVisualisationParams): SvelteUIElement { const { text, icon, idkey, comment, minZoom, zoomButton } = Utils.ParseVisArgs( this.args, args @@ -94,10 +86,7 @@ class AddNoteCommentViz extends SpecialVisualizationSvelte { ] public readonly group = "notes" - public constr( - state: SpecialVisualizationState, - tags: UIEventSource> - ): SvelteUIElement { + public constr({ state, tags }: SpecialVisualisationParams): SvelteUIElement { return new SvelteUIElement(AddNoteComment, { state, tags }) } } @@ -110,13 +99,8 @@ class OpenNote extends SpecialVisualizationSvelte { docs = "Creates a new map note on the given location. This options is placed in the 'last_click'-popup automatically if the 'notes'-layer is enabled" - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - argument: string[], - feature: Feature - ): SvelteUIElement { - const [lon, lat] = GeoOperations.centerpointCoordinates(feature) + constr({ state, feature }: SpecialVisualisationParams): SvelteUIElement { + const [lon, lat] = GeoOperations.centerpointCoordinates(feature) return new SvelteUIElement(CreateNewNote, { state, coordinate: new UIEventSource({ lon, lat }), @@ -138,7 +122,7 @@ class AddImageToNote extends SpecialVisualizationSvelte { group = "notes" needsUrls = [] - constr(state, tags, args, feature) { + constr({ state, tags, args, feature }: SpecialVisualisationParams) { const id = tags.data[args[0] ?? "id"] tags = state.featureProperties.getStore(id) return new SvelteUIElement(UploadImage, { state, tags, feature }) @@ -164,7 +148,7 @@ class VisualiseNoteComment extends SpecialVisualization { ] needsUrls = [Constants.osmAuthConfig] - constr(state, tags, args) { + constr({ state, tags, args }: SpecialVisualisationParams) { return new VariableUiElement( tags .map((tags) => tags[args[0]]) @@ -191,7 +175,7 @@ class VisualiseNoteComment extends SpecialVisualization { } export class NoteVisualisations { - public static initList(): (SpecialVisualization & { group })[] { + public static initList(): SpecialVisualization[] { return [ new AddNoteCommentViz(), new CloseNoteViz(), diff --git a/src/UI/SpecialVisualisations/ReviewSpecialVisualisations.ts b/src/UI/SpecialVisualisations/ReviewSpecialVisualisations.ts index cd9b31f40..750ae06f4 100644 --- a/src/UI/SpecialVisualisations/ReviewSpecialVisualisations.ts +++ b/src/UI/SpecialVisualisations/ReviewSpecialVisualisations.ts @@ -1,4 +1,5 @@ import { + SpecialVisualisationParams, SpecialVisualization, SpecialVisualizationState, SpecialVisualizationSvelte, @@ -16,6 +17,7 @@ import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import BaseUIElement from "../BaseUIElement" import Combine from "../Base/Combine" import { ServerSourceInfo } from "../../Models/SourceOverview" +import { WithUserRelatedState } from "../../Models/ThemeViewState/WithUserRelatedState" class CreateReview extends SpecialVisualizationSvelte { public static MangroveReviewInfo: ServerSourceInfo = { @@ -58,7 +60,7 @@ class CreateReview extends SpecialVisualizationSvelte { }, ] - constr(state, tags, args, feature, layer) { + constr({ state, tags, args, feature, layer }: SpecialVisualisationParams) { const nameKey = args[0] ?? "name" const fallbackName = args[1] const question = args[2] @@ -70,7 +72,7 @@ class CreateReview extends SpecialVisualizationSvelte { nameKey: nameKey, fallbackName, }, - state + state, ) return new SvelteUIElement(ReviewForm, { reviews, @@ -103,7 +105,7 @@ class ListReview extends SpecialVisualizationSvelte { }, ] - constr(state, tags, args, feature) { + constr({ state, tags, args, feature }: SpecialVisualisationParams) { const nameKey = args[0] ?? "name" const fallbackName = args[1] const reviews = FeatureReviews.construct( @@ -138,7 +140,7 @@ class Rating extends SpecialVisualizationSvelte { }, ] - constr(state, tags, args, feature) { + constr({ state, tags, args, feature }: SpecialVisualisationParams) { const nameKey = args[0] ?? "name" const fallbackName = args[1] const reviews = FeatureReviews.construct( @@ -171,12 +173,8 @@ class ImportMangroveKey extends SpecialVisualizationSvelte { }, ] - constr( - state: SpecialVisualizationState, - _: UIEventSource>, - argument: string[] - ): SvelteUIElement { - const [text] = argument + constr({ state, args }: SpecialVisualisationParams): SvelteUIElement { + const [text] = args return new SvelteUIElement(ImportReviewIdentity, { state, text }) } } @@ -207,22 +205,16 @@ class Reviews extends SpecialVisualization { ] needsUrls = [CreateReview.MangroveReviewInfo] - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - args: string[], - feature: Feature, - layer: LayerConfig - ): BaseUIElement { + constr(params: SpecialVisualisationParams): BaseUIElement { return new Combine([ - new CreateReview().constr(state, tagSource, args, feature, layer), - new ListReview().constr(state, tagSource, args, feature), + new CreateReview().constr(params), + new ListReview().constr(params), ]) } } export class ReviewSpecialVisualisations { - public static initList(): (SpecialVisualization & { group })[] { + public static initList(): SpecialVisualization[] { return [ new Rating(), new CreateReview(), diff --git a/src/UI/SpecialVisualisations/SettingsVisualisations.ts b/src/UI/SpecialVisualisations/SettingsVisualisations.ts index 75cd38e00..5347b8433 100644 --- a/src/UI/SpecialVisualisations/SettingsVisualisations.ts +++ b/src/UI/SpecialVisualisations/SettingsVisualisations.ts @@ -1,4 +1,8 @@ -import { SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization" +import { + SpecialVisualisationParams, + SpecialVisualizationState, + SpecialVisualizationSvelte, +} from "../SpecialVisualization" import SvelteUIElement from "../Base/SvelteUIElement" import Constants from "../../Models/Constants" import LogoutButton from "../Base/LogoutButton.svelte" @@ -25,7 +29,7 @@ class LanguagePickerVis extends SpecialVisualizationSvelte { group = "settings" docs = "A component to set the language of the user interface" - constr(state: SpecialVisualizationState): SvelteUIElement { + constr({ state }: SpecialVisualisationParams): SvelteUIElement { const availableLanguages = Locale.showLinkToWeblate.map((showTranslations) => showTranslations ? LanguageUtils.usedLanguagesSorted @@ -68,7 +72,7 @@ class GpsAllTags extends SpecialVisualizationSvelte { docs = "Shows the current tags of the GPS-representing object, used for debugging" args = [] - constr(state: SpecialVisualizationState): SvelteUIElement { + constr({ state }: SpecialVisualisationParams): SvelteUIElement { const tags = (state).geolocation.currentUserLocation.features.map( (features) => features[0]?.properties ) @@ -85,7 +89,7 @@ class StorageAllTags extends SpecialVisualizationSvelte { docs = "Shows the current state of storage" args = [] - constr(state: SpecialVisualizationState): SvelteUIElement { + constr({ state }: SpecialVisualisationParams): SvelteUIElement { const data = {} for (const key in localStorage) { data[key] = localStorage[key] @@ -117,13 +121,9 @@ export class ClearCachesVis extends SpecialVisualizationSvelte { ] group = "settings" - constr( - _: SpecialVisualizationState, - __: UIEventSource>, - argument: string[] - ): SvelteUIElement { + constr({ args }: SpecialVisualisationParams): SvelteUIElement { return new SvelteUIElement(ClearCaches, { - msg: argument[0] ?? "Clear local caches", + msg: args[0] ?? "Clear local caches", }) } } @@ -144,7 +144,7 @@ class LoginButtonVis extends SpecialVisualizationSvelte { docs = "Show a login button" group = "settings" - constr(state: SpecialVisualizationState, _, args): SvelteUIElement { + constr({ state, args }: SpecialVisualisationParams): SvelteUIElement { const force = args[0].toLowerCase() let msg = args[1] if (msg === "") { @@ -175,16 +175,10 @@ class QrLogin extends SpecialVisualizationSvelte { "A QR-code which shares the current URL and adds the login token. Anyone with this login token will have the same permissions as you currently have. Logging out from this session will also log them out" group = "settings" - constr( - state: SpecialVisualizationState, - tags: UIEventSource>, - argument: string[], - feature: Feature, - layer: LayerConfig - ): SvelteUIElement { + constr({ state, tags, args }: SpecialVisualisationParams): SvelteUIElement { const shared_oauth_cookie = state.osmConnection.getToken() - const sideText = argument[0] - const sideTextClass = argument[1] ?? "" + const sideText = args[0] + const sideTextClass = args[1] ?? "" return new SvelteUIElement(QrCode, { state, tags, @@ -202,8 +196,8 @@ class Logout extends SpecialVisualizationSvelte { docs = "Shows a button where the user can log out" group = "settings" - constr(state: SpecialVisualizationState): SvelteUIElement { - return new SvelteUIElement(LogoutButton, { osmConnection: state.osmConnection }) + constr({ state }: SpecialVisualisationParams): SvelteUIElement { + return new SvelteUIElement(LogoutButton,state) } } @@ -213,7 +207,7 @@ class PendingChanges extends SpecialVisualizationSvelte { group = "settings" args = [] - constr(state: SpecialVisualizationState): SvelteUIElement { + constr({ state }: SpecialVisualisationParams): SvelteUIElement { return new SvelteUIElement(PendingChangesIndicator, { state, compact: false }) } } @@ -224,8 +218,8 @@ class ClearLocationHistoryVis extends SpecialVisualizationSvelte { docs = "A button to remove the travelled track information from the device" args = [] - constr(state) { - return new SvelteUIElement(ClearGPSHistory, { state }) + constr(params: SpecialVisualisationParams) { + return new SvelteUIElement(ClearGPSHistory, params) } } @@ -233,7 +227,6 @@ export class SettingsVisualisations { public static initList(): SpecialVisualizationSvelte[] { return [ new LanguagePickerVis(), - new DisabledQuestionsVis(), new GyroscopeAllTags(), new GpsAllTags(), diff --git a/src/UI/SpecialVisualisations/TagApplyViz.ts b/src/UI/SpecialVisualisations/TagApplyViz.ts index 10ad5ec8c..50de0b06c 100644 --- a/src/UI/SpecialVisualisations/TagApplyViz.ts +++ b/src/UI/SpecialVisualisations/TagApplyViz.ts @@ -1,5 +1,5 @@ import { AutoAction } from "../Popup/AutoApplyButtonVis" -import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" +import { SpecialVisualisationParams, SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" import { Utils } from "../../Utils" import { Store, UIEventSource } from "../../Logic/UIEventSource" import { Tag } from "../../Logic/Tags/Tag" @@ -174,12 +174,7 @@ export default class TagApplyViz extends SpecialVisualization implements AutoAct } } - public constr( - state: SpecialVisualizationState, - tags: UIEventSource>, - args: string[], - feature: Feature - ): SvelteUIElement { + public constr({ state, tags, args, feature }: SpecialVisualisationParams): SvelteUIElement { const tagsToApply: Store = TagApplyViz.generateTagsToApply(args[0], tags) const msg = args[1] let image = args[2]?.trim() diff --git a/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts b/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts index 2dee3894c..b997417f7 100644 --- a/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts +++ b/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts @@ -1,4 +1,5 @@ import { + SpecialVisualisationParams, SpecialVisualization, SpecialVisualizationState, SpecialVisualizationSvelte, @@ -39,7 +40,7 @@ class StealViz extends SpecialVisualization { ] needsUrls = [] - constr(state: SpecialVisualizationState, featureTags, args) { + constr({ state, tags, args }: SpecialVisualisationParams) { const [featureIdKey, layerAndtagRenderingIds] = args const tagRenderings: [LayerConfig, TagRenderingConfig][] = [] for (const layerAndTagRenderingId of layerAndtagRenderingIds.split(";")) { @@ -52,7 +53,7 @@ class StealViz extends SpecialVisualization { throw "Could not create stolen tagrenddering: tagRenderings not found" } return new VariableUiElement( - featureTags.map( + tags.map( (tags) => { const featureId = tags[featureIdKey] if (featureId === undefined) { @@ -133,18 +134,12 @@ class Multi extends SpecialVisualization { }, ] - constr( - state: SpecialVisualizationState, - featureTags: UIEventSource>, - args: string[], - feature: Feature, - layer: LayerConfig - ) { + constr({ state, tags, args, feature, layer }: SpecialVisualisationParams) { const [key, tr, classesRaw] = args const classes = classesRaw ?? "" const translation = new Translation({ "*": tr }) return new VariableUiElement( - featureTags.map((tags) => { + tags.map((tags) => { let properties: object[] if (typeof tags[key] === "string") { properties = JSON.parse(tags[key]) @@ -198,20 +193,14 @@ class Group extends SpecialVisualizationSvelte { }, ] - constr( - state: SpecialVisualizationState, - tags: UIEventSource>, - argument: string[], - selectedElement: Feature, - layer: LayerConfig - ): SvelteUIElement { - const [header, labelsStr, blacklistStr] = argument + constr({ state, tags, args, feature, layer }: SpecialVisualisationParams): SvelteUIElement { + const [header, labelsStr, blacklistStr] = args const labels = labelsStr.split(";").map((x) => x.trim()) const blacklist = blacklistStr?.split(";")?.map((x) => x.trim()) ?? [] return new SvelteUIElement(GroupedView, { state, tags, - selectedElement, + selectedElement: feature, layer, header, labels, @@ -226,10 +215,10 @@ class OpenInId extends SpecialVisualizationSvelte { args = [] group = "tagrendering_manipulation" - constr(state, feature): SvelteUIElement { + constr({state, feature}: SpecialVisualisationParams): SvelteUIElement { return new SvelteUIElement(OpenIdEditor, { mapProperties: state.mapProperties, - objectId: feature.data.id, + objectId: feature.properties.id, }) } } @@ -254,12 +243,12 @@ class OpenInJosm extends SpecialVisualizationSvelte { }, ] - constr(state): SvelteUIElement { - return new SvelteUIElement(OpenJosm, { state }) + constr(params: SpecialVisualisationParams): SvelteUIElement { + return new SvelteUIElement(OpenJosm, params) } } export default class TagrenderingManipulationSpecialVisualisations { - public static initList(): (SpecialVisualization & { group })[] { + public static initList(): SpecialVisualization[] { return [new StealViz(), new Multi(), new Group(), new OpenInId(), new OpenInJosm()] } } diff --git a/src/UI/SpecialVisualisations/UISpecialVisualisations.ts b/src/UI/SpecialVisualisations/UISpecialVisualisations.ts index cda7fe99e..0f2a08e6c 100644 --- a/src/UI/SpecialVisualisations/UISpecialVisualisations.ts +++ b/src/UI/SpecialVisualisations/UISpecialVisualisations.ts @@ -1,4 +1,5 @@ import { + SpecialVisualisationParams, SpecialVisualization, SpecialVisualizationState, SpecialVisualizationSvelte, @@ -49,13 +50,7 @@ class QuestionViz extends SpecialVisualizationSvelte { ] group = "default" - constr( - state: SpecialVisualizationState, - tags: UIEventSource>, - args: string[], - feature: Feature, - layer: LayerConfig - ): SvelteUIElement { + constr({ state, tags, args, feature, layer }: SpecialVisualisationParams): SvelteUIElement { const labels = args[0] ?.split(";") ?.map((s) => s.trim()) @@ -110,12 +105,7 @@ class Minimap extends SpecialVisualizationSvelte { example = "`{minimap()}`, `{minimap(17, id, _list_of_embedded_feature_ids_calculated_by_calculated_tag):height:10rem; border: 2px solid black}`" - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - args: string[], - feature: Feature - ): SvelteUIElement { + constr({ state, tags, args, feature, layer }: SpecialVisualisationParams): SvelteUIElement { const minzoom = Number(args[0] ?? 18) const ids = args[1]?.split(";")?.map((s) => s.trim()) ?? ["id"] const clss = args[2] @@ -125,7 +115,7 @@ class Minimap extends SpecialVisualizationSvelte { idkeys: ids, clss, feature, - tagSource, + tags, }) } } @@ -136,12 +126,9 @@ class SplitButton extends SpecialVisualizationSvelte { args = [] group = "default" - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource> - ): SvelteUIElement { + constr({ state, tags }: SpecialVisualisationParams): SvelteUIElement { return new SvelteUIElement(SplitRoadWizard, { - id: tagSource.map((pr) => pr.id), + id: tags.map((pr) => pr.id), state, }) } @@ -154,13 +141,7 @@ class MoveButton extends SpecialVisualizationSvelte { args = [] group = "default" - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - argument: string[], - feature: Feature, - layer: LayerConfig - ): SvelteUIElement { + constr({ state, feature, layer }: SpecialVisualisationParams): SvelteUIElement { if (feature.geometry.type !== "Point") { return undefined } @@ -180,18 +161,12 @@ class DeleteButton extends SpecialVisualizationSvelte { args = [] group = "default" - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - argument: string[], - feature: Feature, - layer: LayerConfig - ): SvelteUIElement { + constr({ state, tags, feature, layer }: SpecialVisualisationParams): SvelteUIElement { if (!layer.deletion) { return undefined } return new SvelteUIElement(DeleteWizard, { - tags: tagSource, + tags, deleteConfig: layer.deletion, state, feature, @@ -215,14 +190,9 @@ class QrCodeVis extends SpecialVisualizationSvelte { group = "default" docs = "Generates a QR-code to share the selected object" - constr( - state: SpecialVisualizationState, - tags: UIEventSource>, - argument: string[], - feature: Feature - ): SvelteUIElement { - const sideText = argument[0] - const sideTextClass = argument[1] ?? "" + constr({ state, tags, args, feature }: SpecialVisualisationParams): SvelteUIElement { + const sideText = args[0] + const sideTextClass = args[1] ?? "" return new SvelteUIElement(QrCode, { state, tags, @@ -248,18 +218,12 @@ class IfNothingKnown extends SpecialVisualizationSvelte { docs = "Shows a 'nothing is currently known-message if there is at least one unanswered question and no known (answerable) question" - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - argument: string[], - feature: Feature, - layer: LayerConfig - ): SvelteUIElement { - const text = argument[0] - const cssClasses = argument[1] + constr({ state, tags, args, layer }: SpecialVisualisationParams): SvelteUIElement { + const text = args[0] + const cssClasses = args[1] return new SvelteUIElement(NothingKnown, { state, - tags: tagSource, + tags, layer, text, cssClasses, @@ -274,7 +238,7 @@ class AddNewPointVis extends SpecialVisualizationSvelte { args = [] group = "default" - constr(state: SpecialVisualizationState, _, __, feature: GeoJSON): SvelteUIElement { + constr({ state, feature }: SpecialVisualisationParams): SvelteUIElement { const [lon, lat] = GeoOperations.centerpointCoordinates(feature) return new SvelteUIElement(AddNewPoint, { state, @@ -297,14 +261,10 @@ class Translated extends SpecialVisualization { }, ] - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - argument: string[] - ): BaseUIElement { + constr({ tags, args }: SpecialVisualisationParams): BaseUIElement { return new VariableUiElement( - tagSource.map((tags) => { - const v = tags[argument[0] ?? "value"] + tags.map((tags) => { + const v = tags[args[0] ?? "value"] try { const tr = typeof v === "string" ? JSON.parse(v) : v return new Translation(tr).SetClass("font-bold") @@ -327,14 +287,8 @@ class TitleVis extends SpecialVisualizationSvelte { example = "`What is the phone number of {title()}`, which might automatically become `What is the phone number of XYZ`." - constr( - state: SpecialVisualizationState, - tags: UIEventSource>, - _: string[], - feature: Feature, - layer: LayerConfig - ) { - return new SvelteUIElement(FeatureTitle, { state, tags, feature, layer }) + constr(params: SpecialVisualisationParams): SvelteUIElement { + return new SvelteUIElement(FeatureTitle, params) } } @@ -352,13 +306,7 @@ class BracedVis extends SpecialVisualization { }, ] - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - args: string[], - feature: Feature, - layer: LayerConfig - ): BaseUIElement { + constr({ args }: SpecialVisualisationParams): BaseUIElement { return new FixedUiElement("{" + args[0] + "}") } } @@ -368,18 +316,9 @@ class CreateCopyVis extends SpecialVisualizationSvelte { funcName = "create_copy" docs = "Allow to create a copy of the current element" args = [] - constr( - state: SpecialVisualizationState, - tags: UIEventSource>, - argument: string[], - feature: Feature, - layer: LayerConfig - ): SvelteUIElement { - try { - return new SvelteUIElement(CreateCopy, { state, tags, argument, feature, layer }) - } catch (e) { - console.error(">>> failed", e) - } + + constr(params: SpecialVisualisationParams): SvelteUIElement { + return new SvelteUIElement(CreateCopy, params) } } diff --git a/src/UI/SpecialVisualisations/WebAndCommunicationSpecialVisualisations.ts b/src/UI/SpecialVisualisations/WebAndCommunicationSpecialVisualisations.ts index be8831bd0..b6a048625 100644 --- a/src/UI/SpecialVisualisations/WebAndCommunicationSpecialVisualisations.ts +++ b/src/UI/SpecialVisualisations/WebAndCommunicationSpecialVisualisations.ts @@ -1,10 +1,5 @@ -import { - SpecialVisualization, - SpecialVisualizationState, - SpecialVisualizationSvelte, -} from "../SpecialVisualization" -import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource" -import BaseUIElement from "../BaseUIElement" +import { SpecialVisualisationParams, SpecialVisualization, SpecialVisualizationSvelte } from "../SpecialVisualization" +import { ImmutableStore, Store } from "../../Logic/UIEventSource" import SvelteUIElement from "../Base/SvelteUIElement" import FediverseLink from "../Popup/FediverseLink.svelte" import Wikidata, { WikidataResponse } from "../../Logic/Web/Wikidata" @@ -18,7 +13,7 @@ import SendEmail from "../Popup/SendEmail.svelte" import DynLink from "../Base/DynLink.svelte" import { Lists } from "../../Utils/Lists" -class FediverseLinkVis extends SpecialVisualization { +class FediverseLinkVis extends SpecialVisualizationSvelte { funcName = "fediverse_link" group = "web_and_communication" docs = "Converts a fediverse username or link into a clickable link" @@ -31,17 +26,13 @@ class FediverseLinkVis extends SpecialVisualization { }, ] - constr( - state: SpecialVisualizationState, - tags: UIEventSource>, - argument: string[] - ): BaseUIElement { - const key = argument[0] + constr({ state, tags, args }: SpecialVisualisationParams): SvelteUIElement { + const key = args[0] return new SvelteUIElement(FediverseLink, { key, tags, state }) } } -class WikipediaVis extends SpecialVisualization { +class WikipediaVis extends SpecialVisualizationSvelte { funcName = "wikipedia" group = "web_and_communication" docs = "A box showing the corresponding wikipedia article(s) - based on the **wikidata** tag." @@ -58,9 +49,9 @@ class WikipediaVis extends SpecialVisualization { example = "`{wikipedia()}` is a basic example, `{wikipedia(name:etymology:wikidata)}` to show the wikipedia page of whom the feature was named after. Also remember that these can be styled, e.g. `{wikipedia():max-height: 10rem}` to limit the height" - constr(_, tagsSource, args) { + constr({ tags, args }: SpecialVisualisationParams) { const keys = args[0].split(";").map((k) => k.trim()) - const wikiIds: Store = tagsSource.map((tags) => { + const wikiIds: Store = tags.map((tags) => { const key = keys.find((k) => tags[k] !== undefined && tags[k] !== "") return tags[key]?.split(";")?.map((id) => id.trim()) ?? [] }) @@ -88,8 +79,8 @@ class WikidatalabelVis extends SpecialVisualization { example = "`{wikidata_label()}` is a basic example, `{wikipedia(name:etymology:wikidata)}` to show the label itself" - constr(_, tagsSource, args) { - const id = tagsSource + constr({ tags, args }: SpecialVisualisationParams) { + const id = tags .map((tags) => tags[args[0]]) .map((wikidata) => { const wikidataIds = Lists.noEmpty( @@ -144,8 +135,8 @@ class SendEmailVis extends SpecialVisualizationSvelte { }, ] - constr(__, tags, args) { - return new SvelteUIElement(SendEmail, { args, tags }) + constr(params: SpecialVisualisationParams) { + return new SvelteUIElement(SendEmail, params) } } @@ -184,31 +175,27 @@ class LinkVis extends SpecialVisualizationSvelte { }, ] - constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - args: string[] - ): SvelteUIElement { + constr({ tags, args }: SpecialVisualisationParams): SvelteUIElement { let [text, href, classnames, download, ariaLabel, icon] = args if (download === "") { download = undefined } const newTab = download === undefined && !href.startsWith("#") - const textStore = tagSource.map((tags) => Utils.SubstituteKeys(text, tags)) - const hrefStore = tagSource.map((tags) => Utils.SubstituteKeys(href, tags)) + const textStore = tags.map((tags) => Utils.SubstituteKeys(text, tags)) + const hrefStore = tags.map((tags) => Utils.SubstituteKeys(href, tags)) return new SvelteUIElement(DynLink, { text: textStore, href: hrefStore, classnames: new ImmutableStore(classnames), - download: tagSource.map((tags) => Utils.SubstituteKeys(download, tags)), - ariaLabel: tagSource.map((tags) => Utils.SubstituteKeys(ariaLabel, tags)), + download: tags.map((tags) => Utils.SubstituteKeys(download, tags)), + ariaLabel: tags.map((tags) => Utils.SubstituteKeys(ariaLabel, tags)), newTab: new ImmutableStore(newTab), - icon: tagSource.map((tags) => Utils.SubstituteKeys(icon, tags)), + icon: tags.map((tags) => Utils.SubstituteKeys(icon, tags)), }) } } export class WebAndCommunicationSpecialVisualisations { - public static initList(): (SpecialVisualization & { group })[] { + public static initList(): SpecialVisualization[] { return [ new FediverseLinkVis(), new WikipediaVis(), diff --git a/src/UI/SpecialVisualization.ts b/src/UI/SpecialVisualization.ts index 6b4285bb2..012ce52aa 100644 --- a/src/UI/SpecialVisualization.ts +++ b/src/UI/SpecialVisualization.ts @@ -98,10 +98,20 @@ export interface SpecialVisualisationArg { export class SpecialVisualizationUtils { + static parseArgs(specs: { name: string; defaultValue?: string }[], args: string[]){ + return Utils.ParseVisArgs(specs, args) + } } +export interface SpecialVisualisationParams { + state: SpecialVisualizationState + tags: UIEventSource> + args: string[] + feature: Feature + layer: LayerConfig +} export abstract class SpecialVisualization { readonly funcName: string @@ -128,23 +138,11 @@ export abstract class SpecialVisualization { structuredExamples?(): { feature: Feature>; args: string[] }[] - abstract constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - argument: string[], - feature: Feature, - layer: LayerConfig - ): BaseUIElement + abstract constr(options: SpecialVisualisationParams): BaseUIElement } export abstract class SpecialVisualizationSvelte extends SpecialVisualization { - abstract constr( - state: SpecialVisualizationState, - tagSource: UIEventSource>, - argument: string[], - feature: Feature, - layer: LayerConfig - ): SvelteUIElement + abstract constr(options: SpecialVisualisationParams): SvelteUIElement } export type RenderingSpecification = diff --git a/src/UI/SpecialVisualizations.ts b/src/UI/SpecialVisualizations.ts index 69d93a165..5a1416e1e 100644 --- a/src/UI/SpecialVisualizations.ts +++ b/src/UI/SpecialVisualizations.ts @@ -1,4 +1,4 @@ -import { RenderingSpecification, SpecialVisualization } from "./SpecialVisualization" +import { RenderingSpecification, SpecialVisualization, SpecialVisualizationSvelte } from "./SpecialVisualization" import { UploadToOsmViz } from "./Popup/UploadToOsmViz" import { MultiApplyViz } from "./Popup/MultiApplyViz" import AutoApplyButtonVis from "./Popup/AutoApplyButtonVis" @@ -176,22 +176,26 @@ export default class SpecialVisualizations { } private static initList(): SpecialVisualization[] { - const specialVisualizations: SpecialVisualization[] = [ + const specialVisualizationsSv: SpecialVisualizationSvelte[] = [ ...ImageVisualisations.initList(), - ...NoteVisualisations.initList(), ...FavouriteVisualisations.initList(), - ...UISpecialVisualisations.initList(), ...SettingsVisualisations.initList(), - ...ReviewSpecialVisualisations.initList(), ...DataImportSpecialVisualisations.initList(), - ...TagrenderingManipulationSpecialVisualisations.initList(), - ...WebAndCommunicationSpecialVisualisations.initList(), - ...DataVisualisations.initList(), ...DataExportVisualisations.initList(), new UploadToOsmViz(), new MultiApplyViz(), ] + const specialVisualizations: SpecialVisualization[] = [ + ...NoteVisualisations.initList(), + ...UISpecialVisualisations.initList(), + ...ReviewSpecialVisualisations.initList(), + ...TagrenderingManipulationSpecialVisualisations.initList(), + ...WebAndCommunicationSpecialVisualisations.initList(), + ...DataVisualisations.initList(), + ...specialVisualizationsSv + ] + specialVisualizations.push(new AutoApplyButtonVis(specialVisualizations)) if (Utils.runningFromConsole) { diff --git a/src/Utils.ts b/src/Utils.ts index 432a2fab6..df6d99199 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -1,6 +1,5 @@ import DOMPurify from "dompurify" import { Lists } from "./Utils/Lists" -import { Strings } from "./Utils/Strings" export class Utils { /** From c70f249ca46d73a4d9a9c155ad5e7939038da6e5 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sat, 16 Aug 2025 12:01:33 +0200 Subject: [PATCH 05/25] UX: close 'welcome'-message when a point is selected via URL (and revive it when closed) --- src/Models/MenuState.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Models/MenuState.ts b/src/Models/MenuState.ts index 627d1e266..98ec28ba8 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 + } + }) } } From 2dc43a69a54029eaea0a77932b5b66043257631c Mon Sep 17 00:00:00 2001 From: Niels Elgaard Larsen Date: Sat, 16 Aug 2025 13:40:44 +0200 Subject: [PATCH 06/25] Also include bars and cafes after discussion on: https://community.openstreetmap.org/t/are-there-any-data-consumers-of-dog-tag/131204/21 --- assets/themes/pets/pets.json | 44 +++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/assets/themes/pets/pets.json b/assets/themes/pets/pets.json index 8740095fe..d93ab452f 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 +} From 06da7a0cf48976ae6df5cf5e9d941c55f10d6328 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sun, 17 Aug 2025 02:06:42 +0200 Subject: [PATCH 07/25] Themes(rolling_stock): add snap to 'railway' function --- .../historic_rolling_stock.json | 11 +++- assets/layers/railway/railway.json | 66 +++++++++++++++++++ .../historic_rolling_stock.json | 10 ++- .../ThemeConfig/Json/LayerConfigJson.ts | 2 +- 4 files changed, 84 insertions(+), 5 deletions(-) create mode 100644 assets/layers/railway/railway.json diff --git a/assets/layers/historic_rolling_stock/historic_rolling_stock.json b/assets/layers/historic_rolling_stock/historic_rolling_stock.json index ffeea6dfb..21eb1633f 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/railway/railway.json b/assets/layers/railway/railway.json new file mode 100644 index 000000000..50ddecf0b --- /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/historic_rolling_stock/historic_rolling_stock.json b/assets/themes/historic_rolling_stock/historic_rolling_stock.json index 0b22c6b2b..077324f27 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/src/Models/ThemeConfig/Json/LayerConfigJson.ts b/src/Models/ThemeConfig/Json/LayerConfigJson.ts index a541856b8..1602f7a5a 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? From eb072ff5a6337d21959633686f9b0c8fb2e100ae Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sun, 17 Aug 2025 02:09:14 +0200 Subject: [PATCH 08/25] Docs: improve typing and comments --- scripts/generateLayerOverview.ts | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/scripts/generateLayerOverview.ts b/scripts/generateLayerOverview.ts index ffd9e78cc..6414ed5f6 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"], []) ) From 03fe86cb8f369ce4320cc32d60722af1578b2831 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sun, 17 Aug 2025 02:45:26 +0200 Subject: [PATCH 09/25] UI: add special rendering to show icons of other questions --- src/Models/ThemeConfig/TagRenderingConfig.ts | 4 +- src/UI/Popup/DataVisualisations.ts | 69 ++++++++++++++++++- ...deringManipulationSpecialVisualisations.ts | 4 +- 3 files changed, 72 insertions(+), 5 deletions(-) diff --git a/src/Models/ThemeConfig/TagRenderingConfig.ts b/src/Models/ThemeConfig/TagRenderingConfig.ts index 5f915cd42..da027a349 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/UI/Popup/DataVisualisations.ts b/src/UI/Popup/DataVisualisations.ts index d85ef9e71..97fd3c10d 100644 --- a/src/UI/Popup/DataVisualisations.ts +++ b/src/UI/Popup/DataVisualisations.ts @@ -1,4 +1,9 @@ -import { SpecialVisualisationParams, SpecialVisualization, SpecialVisualizationSvelte } from "../SpecialVisualization" +import { + SpecialVisualisationArg, + SpecialVisualisationParams, + SpecialVisualization, + SpecialVisualizationSvelte, +} from "../SpecialVisualization" import { HistogramViz } from "./HistogramViz" import { Store } from "../../Logic/UIEventSource" import BaseUIElement from "../BaseUIElement" @@ -22,6 +27,9 @@ 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 SpecialVisualizationSvelte { funcName = "direction_indicator" @@ -209,6 +217,7 @@ class PresetDescription extends SpecialVisualizationSvelte { 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, tags }: SpecialVisualisationParams): SvelteUIElement { const translation = tags.map((tags) => { @@ -223,6 +232,7 @@ class PresetTypeSelect extends SpecialVisualizationSvelte { funcName = "preset_type_select" docs = "An editable tag rendering which allows to change the type" args = [] + group = "ui" constr({ state, tags, feature, layer }: SpecialVisualisationParams,): SvelteUIElement { const t = Translations.t.preset_type @@ -281,7 +291,7 @@ class PointsInTimeVis extends SpecialVisualization { }, ] - constr( {tags, args}: SpecialVisualisationParams): BaseUIElement { + constr({ tags, args }: SpecialVisualisationParams): BaseUIElement { const key = args[0] const points_in_time = tags.map((tags) => tags[key]) const times = points_in_time.map( @@ -294,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 [ @@ -309,6 +373,7 @@ export class DataVisualisations { new PresetDescription(), new PresetTypeSelect(), new AllTagsVis(), + new KnownIcons(), ] } } diff --git a/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts b/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts index b997417f7..bf082f3df 100644 --- a/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts +++ b/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts @@ -213,7 +213,7 @@ class OpenInId extends SpecialVisualizationSvelte { funcName = "open_in_iD" docs = "Opens the current view in the iD-editor" args = [] - group = "tagrendering_manipulation" + group = "web_and_communication" constr({state, feature}: SpecialVisualisationParams): SvelteUIElement { return new SvelteUIElement(OpenIdEditor, { @@ -225,7 +225,7 @@ class OpenInId extends SpecialVisualizationSvelte { class OpenInJosm extends SpecialVisualizationSvelte { funcName = "open_in_josm" - group = "tagrendering_manipulation" + group = "web_and_communication" docs = "Opens the current view in the JOSM-editor" args = [] needsUrls = [ From 24755670cc1576ae0477fff8655cfc824ecd2071 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sun, 17 Aug 2025 02:45:44 +0200 Subject: [PATCH 10/25] Themes(food): collapsable group now has icons of supported diets --- assets/layers/diets/diets.json | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/assets/layers/diets/diets.json b/assets/layers/diets/diets.json index 0bc379560..cac1388a3 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" + } } }, { From f7bf6d0e1fc274069a4fc693bd78a5e30c406577 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sun, 17 Aug 2025 13:45:15 +0200 Subject: [PATCH 11/25] Chore: reset translations to fix the build --- langs/layers/cs.json | 4 +++- langs/layers/en.json | 12 +++++++++++- langs/layers/nl.json | 4 +++- langs/themes/en.json | 7 +++++++ 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/langs/layers/cs.json b/langs/layers/cs.json index 067e08c2a..2b661de30 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 f6476e968..6365bf5d6 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 03ff99a8e..6e47acc32 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 a23dea472..c2dd87052 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": { From b74c0d2768288e0039b45b3be3198b7baebd454c Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sun, 17 Aug 2025 14:56:02 +0200 Subject: [PATCH 12/25] Themes(pets): attempt to fix build --- assets/themes/pets/pets.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/themes/pets/pets.json b/assets/themes/pets/pets.json index d93ab452f..6453e5790 100644 --- a/assets/themes/pets/pets.json +++ b/assets/themes/pets/pets.json @@ -104,7 +104,7 @@ "builtin": "cafe_pub", "override": { "id": "cafe_pub_dog_friendly", - "name": { + "name=": { "en": "Dog friendly drinking places", "da": "Hundevenlige værtshuse" }, @@ -139,7 +139,7 @@ "filter": { "sameAs": "cafe_pub_dog_friendly" }, - "name": null + "name=": null } }, { From ecf88f332c4cf5098146c48fa57a2faaca2a33cd Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sun, 17 Aug 2025 15:15:01 +0200 Subject: [PATCH 13/25] Chore: attempt to fix translations --- langs/themes/ca.json | 2 +- langs/themes/cs.json | 14 +++++++------- langs/themes/da.json | 17 +++++++++++------ langs/themes/de.json | 12 ++++++------ langs/themes/en.json | 17 +++++++++++------ langs/themes/es.json | 2 +- langs/themes/fr.json | 2 +- langs/themes/it.json | 14 +++++++------- langs/themes/ko.json | 2 +- langs/themes/nb_NO.json | 2 +- langs/themes/nl.json | 14 +++++++------- langs/themes/pa_PK.json | 2 +- langs/themes/pl.json | 2 +- langs/themes/uk.json | 12 ++++++------ langs/themes/zh_Hant.json | 14 +++++++------- 15 files changed, 69 insertions(+), 59 deletions(-) diff --git a/langs/themes/ca.json b/langs/themes/ca.json index c80a56d96..6dc6023d2 100644 --- a/langs/themes/ca.json +++ b/langs/themes/ca.json @@ -794,7 +794,7 @@ "name": "Restaurants que accepten gossos" } }, - "7": { + "9": { "override": { "name": "Botigues amigues dels gossos" } diff --git a/langs/themes/cs.json b/langs/themes/cs.json index e43f994e3..667a50b1e 100644 --- a/langs/themes/cs.json +++ b/langs/themes/cs.json @@ -1092,12 +1092,17 @@ "pets": { "description": "Na této mapě najdete různá zajímavá místa pro vaše domácí mazlíčky: veterináře, psí parky, obchody pro zvířata, restaurace pro psy, ...", "layers": { + "11": { + "override": { + "name=": "Odpadkové koše se sáčky na exkrementy" + } + }, "4": { "override": { "name": "Restaurace vhodné pro vstup se psy" } }, - "6": { + "8": { "override": { "=presets": { "0": { @@ -1107,14 +1112,9 @@ "name": "Obchody se zvířecími potřebami" } }, - "7": { - "override": { - "name": "Obchody vhodné pro vstup se psy" - } - }, "9": { "override": { - "name=": "Odpadkové koše se sáčky na exkrementy" + "name": "Obchody vhodné pro vstup se psy" } } }, diff --git a/langs/themes/da.json b/langs/themes/da.json index 427e66c98..5ef95945e 100644 --- a/langs/themes/da.json +++ b/langs/themes/da.json @@ -1092,12 +1092,22 @@ "pets": { "description": "På dette kort finder du forskellige interessante steder for dine kæledyr: dyrlæger, hundeparker, dyrehandlere, hundevenlige restauranter, ...", "layers": { + "11": { + "override": { + "name=": "Affaldskurve med dispensere til hundeposer" + } + }, "4": { "override": { "name": "Hundevenlige madsteder" } }, "6": { + "override": { + "name=": "Hundevenlige værtshuse" + } + }, + "8": { "override": { "=presets": { "0": { @@ -1107,14 +1117,9 @@ "name": "Dyrehandlere" } }, - "7": { - "override": { - "name": "hundevenlig butik" - } - }, "9": { "override": { - "name=": "Affaldskurve med dispensere til hundeposer" + "name": "hundevenlig butik" } } }, diff --git a/langs/themes/de.json b/langs/themes/de.json index 07cef8f2e..749fd3cc6 100644 --- a/langs/themes/de.json +++ b/langs/themes/de.json @@ -1046,19 +1046,19 @@ "pets": { "description": "Diese Karte zeigt interessante Orte für Haustierbesitzer: Tierärzte, Hundeparks, Tiergeschäfte, hundefreundliche Restaurants, ...", "layers": { + "11": { + "override": { + "name=": "Mülleimer mit Spender für Kotbeutel" + } + }, "4": { "override": { "name": "Hundefreundliche Restaurants" } }, - "7": { - "override": { - "name": "Hundefreundliche Geschäfte" - } - }, "9": { "override": { - "name=": "Mülleimer mit Spender für Kotbeutel" + "name": "Hundefreundliche Geschäfte" } } }, diff --git a/langs/themes/en.json b/langs/themes/en.json index c2dd87052..de5ff9837 100644 --- a/langs/themes/en.json +++ b/langs/themes/en.json @@ -1099,12 +1099,22 @@ "pets": { "description": "On this map, you'll find various interesting places for you pets: veterinarians, dog parks, pet shops, dog-friendly restaurants, ...", "layers": { + "11": { + "override": { + "name=": "Waste baskets with excrement bag dispensers" + } + }, "4": { "override": { "name": "Dog friendly eateries" } }, "6": { + "override": { + "name=": "Dog friendly drinking places" + } + }, + "8": { "override": { "=presets": { "0": { @@ -1114,14 +1124,9 @@ "name": "Pet stores" } }, - "7": { - "override": { - "name": "Dog-friendly shops" - } - }, "9": { "override": { - "name=": "Waste baskets with excrement bag dispensers" + "name": "Dog-friendly shops" } } }, diff --git a/langs/themes/es.json b/langs/themes/es.json index c90998bf9..7578f3b14 100644 --- a/langs/themes/es.json +++ b/langs/themes/es.json @@ -1025,7 +1025,7 @@ "name": "Restaurantes que admiten perros" } }, - "7": { + "9": { "override": { "name": "Tiendas que admiten perros" } diff --git a/langs/themes/fr.json b/langs/themes/fr.json index 5804caad1..d10672c9e 100644 --- a/langs/themes/fr.json +++ b/langs/themes/fr.json @@ -873,7 +873,7 @@ "name": "Restaurants acceptant les chiens" } }, - "7": { + "9": { "override": { "name": "Magasins acceptant les chiens" } diff --git a/langs/themes/it.json b/langs/themes/it.json index 04ad67df8..9a1db9c34 100644 --- a/langs/themes/it.json +++ b/langs/themes/it.json @@ -1083,12 +1083,17 @@ "pets": { "description": "Su questa mappa, troverai vari luoghi interessanti per i tuoi animali domestici: veterinari, parchi per cani, negozi di animali, ristoranti che accettano cani, ...", "layers": { + "11": { + "override": { + "name=": "Cestini per rifiuti con dispenser di sacchetti per escrementi" + } + }, "4": { "override": { "name": "Ristoranti adatti ai cani" } }, - "6": { + "8": { "override": { "=presets": { "0": { @@ -1098,14 +1103,9 @@ "name": "Negozi di animali" } }, - "7": { - "override": { - "name": "Negozi adatti ai cani" - } - }, "9": { "override": { - "name=": "Cestini per rifiuti con dispenser di sacchetti per escrementi" + "name": "Negozi adatti ai cani" } } }, diff --git a/langs/themes/ko.json b/langs/themes/ko.json index f6b3d1e02..b977d298c 100644 --- a/langs/themes/ko.json +++ b/langs/themes/ko.json @@ -1011,7 +1011,7 @@ "name": "반려견 친화적 식당" } }, - "7": { + "9": { "override": { "name": "반려견 친화적 상점" } diff --git a/langs/themes/nb_NO.json b/langs/themes/nb_NO.json index 4afa5dfce..d09caf9e7 100644 --- a/langs/themes/nb_NO.json +++ b/langs/themes/nb_NO.json @@ -368,7 +368,7 @@ "name": "Hundevennlige spisesteder" } }, - "7": { + "9": { "override": { "name": "Hundevennlige butikker" } diff --git a/langs/themes/nl.json b/langs/themes/nl.json index 0d361227e..014aa24d8 100644 --- a/langs/themes/nl.json +++ b/langs/themes/nl.json @@ -1079,12 +1079,17 @@ "pets": { "description": "Deze kaart helpt je op weg met je huisdier: dierenartsen, hondenloopzones, dierenwinkels, hondenvriendelijke restaurants, ...", "layers": { + "11": { + "override": { + "name=": "Vuilnisbakken met verdelers voor hondenpoepzakjes" + } + }, "4": { "override": { "name": "Hondvriendelijke eetgelegenheden" } }, - "6": { + "8": { "override": { "=presets": { "0": { @@ -1094,14 +1099,9 @@ "name": "Dierenwinkels" } }, - "7": { - "override": { - "name": "Hondvriendelijke winkels" - } - }, "9": { "override": { - "name=": "Vuilnisbakken met verdelers voor hondenpoepzakjes" + "name": "Hondvriendelijke winkels" } } }, diff --git a/langs/themes/pa_PK.json b/langs/themes/pa_PK.json index e1ed2c79a..d24b0ca42 100644 --- a/langs/themes/pa_PK.json +++ b/langs/themes/pa_PK.json @@ -147,7 +147,7 @@ }, "pets": { "layers": { - "7": { + "9": { "override": { "name": "کُتیاں دی اِجازت دیاں دکاناں" } diff --git a/langs/themes/pl.json b/langs/themes/pl.json index 965070c07..3beac1dcd 100644 --- a/langs/themes/pl.json +++ b/langs/themes/pl.json @@ -750,7 +750,7 @@ "name": "Restauracje przyjazne psom" } }, - "7": { + "9": { "override": { "name": "Sklepy przyjazne psom" } diff --git a/langs/themes/uk.json b/langs/themes/uk.json index ad745a0b3..d93a259c5 100644 --- a/langs/themes/uk.json +++ b/langs/themes/uk.json @@ -556,19 +556,19 @@ "pets": { "description": "На цій мапі ви знайдете різні цікаві місця для ваших домашніх улюбленців: ветеринари, парки для собак, зоомагазини, ресторани, дружні до собак, …", "layers": { + "11": { + "override": { + "name=": "Кошики для сміття з дозаторами для пакетів для екскрементів" + } + }, "4": { "override": { "name": "Заклади харчування, дружні до собак" } }, - "7": { - "override": { - "name": "Магазини, дружні до собак" - } - }, "9": { "override": { - "name=": "Кошики для сміття з дозаторами для пакетів для екскрементів" + "name": "Магазини, дружні до собак" } } }, diff --git a/langs/themes/zh_Hant.json b/langs/themes/zh_Hant.json index e7fd208a7..6b1247912 100644 --- a/langs/themes/zh_Hant.json +++ b/langs/themes/zh_Hant.json @@ -1092,12 +1092,17 @@ "pets": { "description": "在這份地圖上,你會找到與寵物有關的有趣地位:獸醫、寵物公園、寵物用品店、寵物友善餐廳、…", "layers": { + "11": { + "override": { + "name=": "帶有糞便袋分配器的垃圾籃" + } + }, "4": { "override": { "name": "寵物友善餐廳" } }, - "6": { + "8": { "override": { "=presets": { "0": { @@ -1107,14 +1112,9 @@ "name": "寵物用品店" } }, - "7": { - "override": { - "name": "寵物友善商家" - } - }, "9": { "override": { - "name=": "帶有糞便袋分配器的垃圾籃" + "name": "寵物友善商家" } } }, From e9bcc7004988b530cdacf41e1b086ff16bb0e8a0 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sun, 17 Aug 2025 15:30:11 +0200 Subject: [PATCH 14/25] Docs: add better error message when generateTranslations.ts fails --- scripts/generateTranslations.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/generateTranslations.ts b/scripts/generateTranslations.ts index 2d025b9e1..24776542f 100644 --- a/scripts/generateTranslations.ts +++ b/scripts/generateTranslations.ts @@ -567,6 +567,10 @@ function MergeTranslation(source: any, target: any, language: string, context: s const sourceV = source[key] const targetV = target[keyRemapping?.get(key) ?? key] + if(targetV === undefined){ + throw `Merging translations failed; targetV is undefined for context ${context}; but we are trying to add ${sourceV} (${key}) to it` + } + if (typeof sourceV === "string") { if (sourceV === "") { console.log("Ignoring empty string in the translations") From ccdcb8604475abffd6bed6b8704fae48c98d82c0 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 18 Aug 2025 12:56:07 +0200 Subject: [PATCH 15/25] Chore: format layers --- assets/layers/diets/diets.json | 4 +- .../historic_rolling_stock.json | 14 ++- assets/layers/railway/railway.json | 3 - assets/themes/grb/grb.json | 92 ++++++++++--------- .../mapcomplete-changes.json | 3 + src/Logic/State/UserSettingsMetaTagging.ts | 48 ++-------- 6 files changed, 73 insertions(+), 91 deletions(-) diff --git a/assets/layers/diets/diets.json b/assets/layers/diets/diets.json index cac1388a3..7b82778bf 100644 --- a/assets/layers/diets/diets.json +++ b/assets/layers/diets/diets.json @@ -13,9 +13,9 @@ ], "render": { "special": { - "type": "show_icons", + "class": "inline-flex float-right", "labels": "diets_content", - "class": "inline-flex float-right" + "type": "show_icons" }, "before": { "en": "Dietary options", diff --git a/assets/layers/historic_rolling_stock/historic_rolling_stock.json b/assets/layers/historic_rolling_stock/historic_rolling_stock.json index 21eb1633f..4d453c6fc 100644 --- a/assets/layers/historic_rolling_stock/historic_rolling_stock.json +++ b/assets/layers/historic_rolling_stock/historic_rolling_stock.json @@ -132,7 +132,9 @@ "tags": [ "historic=locomotive" ], - "snapToLayer": ["railway"] + "snapToLayer": [ + "railway" + ] }, { "title": { @@ -146,8 +148,9 @@ "tags": [ "historic=railway_car" ], - "snapToLayer": ["railway"] - + "snapToLayer": [ + "railway" + ] }, { "title": { @@ -161,8 +164,9 @@ "tags": [ "historic=minecart" ], - "snapToLayer": ["railway"] - + "snapToLayer": [ + "railway" + ] } ], "tagRenderings": [ diff --git a/assets/layers/railway/railway.json b/assets/layers/railway/railway.json index 50ddecf0b..fce3773bf 100644 --- a/assets/layers/railway/railway.json +++ b/assets/layers/railway/railway.json @@ -13,14 +13,11 @@ "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", diff --git a/assets/themes/grb/grb.json b/assets/themes/grb/grb.json index 256070bca..14f70e18e 100644 --- a/assets/themes/grb/grb.json +++ b/assets/themes/grb/grb.json @@ -45,7 +45,7 @@ }, { "or": [ - "building~*", + "building~*", "building:part~*" ] } @@ -54,12 +54,14 @@ }, "title": { "render": "OSM-building", - "mappings": [{ - "if": "building:part~*", - "then": { - "en": "Building part" + "mappings": [ + { + "if": "building:part~*", + "then": { + "en": "Building part" + } } - }] + ] }, "tagRenderings": [ { @@ -166,7 +168,7 @@ ] }, { - "condition": "building:part=", + "condition": "building:part=", "id": "grb-street", "render": { "nl": "De straat is {addr:street}" @@ -261,44 +263,48 @@ ], "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" - ] + "marker": [ + { + "icon": "circle", + "color": { + "render": "#00c", + "mappings": [ + { + "if": "fixme~*", + "then": "#ff00ff" }, - "then": "#f9bfbb" - }, - { - "if": "building=yes", - "then": "#0774f2" - }, - { - "if": "building:part~*", - "then": "#f8fc25" - } - ] + { + "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"], + ], + "location": [ + "centroid" + ], "iconSize": "10,10" } ], diff --git a/assets/themes/mapcomplete-changes/mapcomplete-changes.json b/assets/themes/mapcomplete-changes/mapcomplete-changes.json index 5b19fa6de..80382fd51 100644 --- a/assets/themes/mapcomplete-changes/mapcomplete-changes.json +++ b/assets/themes/mapcomplete-changes/mapcomplete-changes.json @@ -38,6 +38,9 @@ "zh_Hant": "顯示由MapComplete進行的變動" }, "icon": "./assets/svg/logo.svg", + "startZoom": 1, + "startLat": 0, + "startLon": 0, "hideFromOverview": true, "layers": [ { diff --git a/src/Logic/State/UserSettingsMetaTagging.ts b/src/Logic/State/UserSettingsMetaTagging.ts index 6e568c5c3..33a5ae85b 100644 --- a/src/Logic/State/UserSettingsMetaTagging.ts +++ b/src/Logic/State/UserSettingsMetaTagging.ts @@ -1,42 +1,14 @@ import { Utils } from "../../Utils" /** This code is autogenerated - do not edit. Edit ./assets/layers/usersettings/usersettings.json instead */ export class ThemeMetaTagging { - public static readonly themeName = "usersettings" + public static readonly themeName = "usersettings" - public metaTaggging_for_usersettings(feat: { properties: Record }) { - Utils.AddLazyProperty(feat.properties, "_mastodon_candidate_md", () => - feat.properties._description - .match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/) - ?.at(1) - ) - Utils.AddLazyProperty( - feat.properties, - "_d", - () => feat.properties._description?.replace(/</g, "<")?.replace(/>/g, ">") ?? "" - ) - Utils.AddLazyProperty(feat.properties, "_mastodon_candidate_a", () => - ((feat) => { - const e = document.createElement("div") - e.innerHTML = feat.properties._d - return Array.from(e.getElementsByTagName("a")).filter( - (a) => a.href.match(/mastodon|en.osm.town/) !== null - )[0]?.href - })(feat) - ) - Utils.AddLazyProperty(feat.properties, "_mastodon_link", () => - ((feat) => { - const e = document.createElement("div") - e.innerHTML = feat.properties._d - return Array.from(e.getElementsByTagName("a")).filter( - (a) => a.getAttribute("rel")?.indexOf("me") >= 0 - )[0]?.href - })(feat) - ) - Utils.AddLazyProperty( - feat.properties, - "_mastodon_candidate", - () => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a - ) - feat.properties["__current_backgroun"] = "initial_value" - } -} + public metaTaggging_for_usersettings(feat: {properties: Record}) { + Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_md', () => feat.properties._description.match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/)?.at(1) ) + Utils.AddLazyProperty(feat.properties, '_d', () => feat.properties._description?.replace(/</g,'<')?.replace(/>/g,'>') ?? '' ) + Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_a', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.href.match(/mastodon|en.osm.town/) !== null)[0]?.href }) (feat) ) + Utils.AddLazyProperty(feat.properties, '_mastodon_link', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.getAttribute("rel")?.indexOf('me') >= 0)[0]?.href})(feat) ) + Utils.AddLazyProperty(feat.properties, '_mastodon_candidate', () => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a ) + feat.properties['__current_backgroun'] = 'initial_value' + } +} \ No newline at end of file From 20f4f6b8694c48c236d23f69b3699b69cda35a1e Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 18 Aug 2025 14:00:42 +0200 Subject: [PATCH 16/25] UX: immediately download a feature that has the ID via the hash to display it faster --- src/Logic/Osm/OsmObject.ts | 10 +++---- src/Logic/Web/ThemeViewStateHashActor.ts | 30 ++++++++++++++++----- src/Models/ThemeViewState/WithImageState.ts | 7 +---- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/src/Logic/Osm/OsmObject.ts b/src/Logic/Osm/OsmObject.ts index b85bc55b8..bd60a9a28 100644 --- a/src/Logic/Osm/OsmObject.ts +++ b/src/Logic/Osm/OsmObject.ts @@ -2,7 +2,7 @@ import { Utils } from "../../Utils" import polygon_features from "../../assets/polygon-features.json" import { OsmFeature, OsmId, OsmTags, WayId } from "../../Models/OsmFeature" import OsmToGeoJson from "osmtogeojson" -import { Feature, LineString, Polygon } from "geojson" +import { Feature, LineString, Point, Polygon } from "geojson" import Constants from "../../Models/Constants" export abstract class OsmObject { @@ -131,7 +131,7 @@ export abstract class OsmObject { */ public abstract centerpoint(): [number, number] - public abstract asGeoJson(): any + public abstract asGeoJson(): OsmFeature abstract SaveExtraData(element: any, allElements: OsmObject[] | any) @@ -228,7 +228,7 @@ ${tags} return [this.lat, this.lon] } - asGeoJson(): OsmFeature { + asGeoJson(): Feature { return { type: "Feature", properties: this.tags, @@ -305,7 +305,7 @@ ${nds}${tags} this.nodes = element.nodes } - public asGeoJson(): Feature & { properties: { id: WayId } } { + public asGeoJson(): Feature & { properties: { id: WayId } } { const coordinates: [number, number][] | [number, number][][] = this.coordinates.map( ([lat, lon]) => [lon, lat] ) @@ -384,7 +384,7 @@ ${members}${tags} this.geojson = geojson } - asGeoJson(): any { + asGeoJson(): OsmFeature { if (this.geojson !== undefined) { return this.geojson } diff --git a/src/Logic/Web/ThemeViewStateHashActor.ts b/src/Logic/Web/ThemeViewStateHashActor.ts index e02f23d19..aee944c9d 100644 --- a/src/Logic/Web/ThemeViewStateHashActor.ts +++ b/src/Logic/Web/ThemeViewStateHashActor.ts @@ -4,13 +4,17 @@ import { AndroidPolyfill } from "./AndroidPolyfill" import { IndexedFeatureSource } from "../FeatureSource/FeatureSource" import { Feature } from "geojson" import { Store, UIEventSource } from "../UIEventSource" +import ThemeViewState from "../../Models/ThemeViewState" +import OsmObjectDownloader from "../Osm/OsmObjectDownloader" +import ThemeSource from "../FeatureSource/Sources/ThemeSource" export default class ThemeViewStateHashActor { - private readonly _state: { + private readonly _state: Readonly<{ indexedFeatures: IndexedFeatureSource selectedElement: UIEventSource - guistate: MenuState - } + guistate: MenuState, + osmObjectDownloader: OsmObjectDownloader + }> private isUpdatingHash = false public static readonly documentation = [ @@ -35,12 +39,13 @@ export default class ThemeViewStateHashActor { * As such, we use a change in the hash to close the appropriate windows * */ - constructor(state: { + constructor(state: Readonly<{ featureSwitches: { featureSwitchBackToThemeOverview: Store } - indexedFeatures: IndexedFeatureSource + indexedFeatures: IndexedFeatureSource & ThemeSource selectedElement: UIEventSource - guistate: MenuState - }) { + guistate: MenuState, + osmObjectDownloader: OsmObjectDownloader + }> ) { this._state = state AndroidPolyfill.onBackButton(() => this.back(), { returnToIndex: state.featureSwitches.featureSwitchBackToThemeOverview, @@ -50,6 +55,17 @@ export default class ThemeViewStateHashActor { const containsMenu = this.loadStateFromHash(hashOnLoad) // First of all, try to recover the selected element if (!containsMenu && hashOnLoad?.length > 0) { + if (hashOnLoad.startsWith("node/") || hashOnLoad.startsWith("way/") || hashOnLoad.startsWith("relation/")) { + // This is an OSM-element. Let's download it and add it to the indexedFeatures + console.log("Directly downloading item from hash") + state.osmObjectDownloader.DownloadObjectAsync(hashOnLoad) + .then(osmObj => { + if (osmObj === "deleted") { + return + } + state.indexedFeatures.addItem(osmObj.asGeoJson()) + }) + } state.indexedFeatures.featuresById.addCallbackAndRunD(() => { // once that we have found a matching element, we can be sure the indexedFeaturesource was popuplated and that the job is done return this.loadSelectedElementFromHash(hashOnLoad) diff --git a/src/Models/ThemeViewState/WithImageState.ts b/src/Models/ThemeViewState/WithImageState.ts index 79a55aa8b..179baabcd 100644 --- a/src/Models/ThemeViewState/WithImageState.ts +++ b/src/Models/ThemeViewState/WithImageState.ts @@ -51,12 +51,7 @@ export class WithImageState extends WithGuiState implements SpecialVisualization * Setup various services for which no reference are needed */ private initActors() { - new ThemeViewStateHashActor({ - featureSwitches: this.featureSwitches, - selectedElement: this.selectedElement, - indexedFeatures: this.indexedFeatures, - guistate: this.guistate, - }) + new ThemeViewStateHashActor(this) new PendingChangesUploader(this.changes, this.selectedElement, this.imageUploadManager) } } From 40caf670844271ebcec5dae341f239f203db26e4 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 18 Aug 2025 17:47:59 +0200 Subject: [PATCH 17/25] UI: style offline basemap management better, make translatable --- langs/en.json | 16 ++++++ src/UI/BigComponents/OfflineManagement.svelte | 57 ++++++++++++------- 2 files changed, 54 insertions(+), 19 deletions(-) diff --git a/langs/en.json b/langs/en.json index 52612c248..ac1e53187 100644 --- a/langs/en.json +++ b/langs/en.json @@ -751,6 +751,22 @@ "typeText": "Type some text to add a comment", "warnAnonymous": "You are not logged in. We won't be able to contact you to resolve your issue." }, + "offline": { + "actions": "Actions", + "autoCheckmark": "Automatically download the basemap when browsing around", + "autoExplanation": "If checked, MapComplete will automatically download the basemap to the cache for the area. This results in bigger initial data loads, but requires less internet over the long run. If you plan to visit a region with less connectivity, you can also select the area you want to download below.", + "autoExplanationIntro": "What does automatically downloading basemaps mean?", + "date": "Map generation data", + "delete": "Delete basemap", + "deleteAll": "Delete all basemaps", + "download": "Download area", + "installing": "Data is being downloaded", + "localOnMap": "Offline basemaps on the map", + "name": "Name", + "overview": "Offline basemaps overview", + "range": "Zoom ranges", + "size": "Size" + }, "plantDetection": { "back": "Back to species overview", "button": "Automatically detect the plant species using the AI of Plantnet.org", diff --git a/src/UI/BigComponents/OfflineManagement.svelte b/src/UI/BigComponents/OfflineManagement.svelte index ef5770ad9..a99c10724 100644 --- a/src/UI/BigComponents/OfflineManagement.svelte +++ b/src/UI/BigComponents/OfflineManagement.svelte @@ -20,6 +20,9 @@ import type { AreaDescription } from "../../Logic/OfflineBasemapManager" import { OfflineBasemapManager } from "../../Logic/OfflineBasemapManager" import Checkbox from "../Base/Checkbox.svelte" + import Translations from "../i18n/Translations" + import { default as Trans } from "../Base/Tr.svelte" + import AccordionSingle from "../Flowbite/AccordionSingle.svelte" export let state: ThemeViewState & SpecialVisualizationState = undefined export let autoDownload = state.autoDownloadOfflineBasemap @@ -147,25 +150,29 @@ ], }), }) + const t = Translations.t.offline -
+
- Automatically download the basemap when browsing around + + + +
+ +
+
- If checked, MapComplete will automatically download the basemap to the cache for the area. This - results in bigger initial data loads, but requires less internet over the long run. If you plan - to visit a region with less connectivity, you can also select the area you want to download - below. +
{#if $installed === undefined} {:else} -
+
-
Map
+
@@ -176,7 +183,9 @@
{#if $focusTileIsInstalling}
- Data is being downloaded + + +
{:else} {/if}
@@ -193,8 +202,7 @@ -
Offline tile management
- +
{Utils.toHumanByteSize(Utils.sum($installed.map((area) => area.size)))} - - - - - + + + + + {#each $installed ?? [] as area} @@ -229,7 +247,8 @@ From b07ebf0cac34ff0098d3a87ecb915ee8c61a8be4 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 19 Aug 2025 16:53:11 +0200 Subject: [PATCH 18/25] UX: add white stroke to 'north arrow' to accomodate for dark background maps --- assets/svg/north_arrow.svg | 8 ++++---- src/assets/svg/North_arrow.svelte | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/assets/svg/north_arrow.svg b/assets/svg/north_arrow.svg index 20e124970..2580424c9 100644 --- a/assets/svg/north_arrow.svg +++ b/assets/svg/north_arrow.svg @@ -24,8 +24,8 @@ inkscape:deskcolor="#d1d1d1" showguides="true" inkscape:zoom="31.809268" - inkscape:cx="10.374335" - inkscape:cy="10.327179" + inkscape:cx="7.4506587" + inkscape:cy="14.445475" inkscape:window-width="1920" inkscape:window-height="1005" inkscape:window-x="0" @@ -35,13 +35,13 @@ + style="fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-opacity:0.82973289;stroke-width:0.40276519;stroke-dasharray:none"> + style="fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-opacity:0.82973289;stroke-width:0.40276519;stroke-dasharray:none" /> export let color = "#000000" - \ No newline at end of file + \ No newline at end of file From 4a63ca8cb63a7a556f745edcf50382722238b8a6 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 19 Aug 2025 16:54:07 +0200 Subject: [PATCH 19/25] Fix: fix rendering of 'scissors' icon when splitting way --- src/UI/BigComponents/WaySplitMap.svelte | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/UI/BigComponents/WaySplitMap.svelte b/src/UI/BigComponents/WaySplitMap.svelte index 021e3cdb3..7dc53796c 100644 --- a/src/UI/BigComponents/WaySplitMap.svelte +++ b/src/UI/BigComponents/WaySplitMap.svelte @@ -77,7 +77,7 @@ Feature< Point, { - id: number + id: string index: number dist: number location: number @@ -101,8 +101,8 @@ }) let id = 0 adaptor.lastClickLocation.addCallbackD(({ lon, lat }) => { - let projected: Feature = - GeoOperations.nearestPoint(wayGeojson, [lon, lat]) + let projected: Feature = + GeoOperations.nearestPoint(wayGeojson, [lon, lat]) console.log("Added splitpoint", projected, id) @@ -126,6 +126,7 @@ }, properties: { index: i + 1, + id: "" + id, reuse: "yes", }, } @@ -139,14 +140,14 @@ }, properties: { index: i, + id: "" + id, reuse: "yes", }, } } - projected.properties["id"] = id id++ - splitPoints.data.push(projected) + splitPoints.data.push(projected) splitPoints.ping() }) From d8f7fe3b01e268a883e80b7345b1106d98654db0 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 19 Aug 2025 17:21:43 +0200 Subject: [PATCH 20/25] UX: update 'distance' indicator faster while dragging the map --- .../InputElement/Helpers/LocationInput.svelte | 1 + src/UI/Map/MapLibreAdaptor.ts | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/UI/InputElement/Helpers/LocationInput.svelte b/src/UI/InputElement/Helpers/LocationInput.svelte index 8059cc633..7993e20c4 100644 --- a/src/UI/InputElement/Helpers/LocationInput.svelte +++ b/src/UI/InputElement/Helpers/LocationInput.svelte @@ -50,6 +50,7 @@ mla.lastClickLocation.addCallbackAndRunD((lastClick) => { dispatch("click", lastClick) }) + mla.installQuicklocation() mapProperties.location.syncWith(value) if (onCreated) { onCreated(value, map, mla) diff --git a/src/UI/Map/MapLibreAdaptor.ts b/src/UI/Map/MapLibreAdaptor.ts index 68ceba8ac..3d224e512 100644 --- a/src/UI/Map/MapLibreAdaptor.ts +++ b/src/UI/Map/MapLibreAdaptor.ts @@ -757,4 +757,28 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { }) }) } + + /** + * In general, the 'location'-attribute is only updated when the map stops moving. + * In some cases, we'd like to update the map faster, especially when the map is used for an input-element + * such as distance, snap-to, ... + * + * In that case, calling this method will install an extra handler on 'drag', updating the location faster. + * To avoid rendering artefacts or too frequenting pinging, this is ratelimited to one update every 'rateLimitMs' milliseconds + */ + public installQuicklocation(ratelimitMs = 50) { + this._maplibreMap.addCallbackAndRunD(map => { + let lastUpdate = new Date().getTime() + map.on("drag", e => { + let now = new Date().getTime() + if(now - lastUpdate < ratelimitMs){ + return + } + lastUpdate = now; + const center = map.getCenter() + this.location.set({lon: center.lng, lat: center.lat}) + }) + }) + + } } From a073b9de94bb0295a5828224c3b6a5fb59e69fa4 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 19 Aug 2025 17:22:02 +0200 Subject: [PATCH 21/25] Fix: fix small background crashes --- src/Logic/FeatureSource/Sources/FavouritesFeatureSource.ts | 3 +++ src/Utils.ts | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Logic/FeatureSource/Sources/FavouritesFeatureSource.ts b/src/Logic/FeatureSource/Sources/FavouritesFeatureSource.ts index 5a3e7d2e8..174c9ee3b 100644 --- a/src/Logic/FeatureSource/Sources/FavouritesFeatureSource.ts +++ b/src/Logic/FeatureSource/Sources/FavouritesFeatureSource.ts @@ -209,6 +209,9 @@ export default class FavouritesFeatureSource extends StaticFeatureSource { continue } const store = featureProperties.getStore(id) + if(store === undefined){ + continue + } const origValue = store.data._favourite if (detected.indexOf(id) >= 0) { if (origValue !== "yes") { diff --git a/src/Utils.ts b/src/Utils.ts index df6d99199..4dbb40ee1 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -291,7 +291,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be seen.add(ks) uniq.push(img) } - } else { + } else if(ks){ const ksNoNull = Lists.noNull(ks) const hasBeenSeen = ksNoNull.some((k) => seen.has(k)) if (!hasBeenSeen) { From 145350bdbbb7740e119a9855d915cd86a9ac49ce Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 19 Aug 2025 17:22:50 +0200 Subject: [PATCH 22/25] Docs: update types, reorder items --- src/UI/InputElement/Validators.ts | 2 +- src/UI/Map/RasterLayerHandler.ts | 12 ++++++------ src/UI/Map/ShowDataLayerOptions.ts | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/UI/InputElement/Validators.ts b/src/UI/InputElement/Validators.ts index a53a74f59..d49848b2d 100644 --- a/src/UI/InputElement/Validators.ts +++ b/src/UI/InputElement/Validators.ts @@ -81,6 +81,7 @@ export default class Validators { new ColorValidator(), new DirectionValidator(), + new DistanceValidator(), new SlopeValidator(), new UrlValidator(), @@ -107,7 +108,6 @@ export default class Validators { new NameSuggestionIndexValidator(), - new DistanceValidator(), ] private static _byType = Validators._byTypeConstructor() diff --git a/src/UI/Map/RasterLayerHandler.ts b/src/UI/Map/RasterLayerHandler.ts index a5dcc67ee..3ccc4a456 100644 --- a/src/UI/Map/RasterLayerHandler.ts +++ b/src/UI/Map/RasterLayerHandler.ts @@ -59,14 +59,14 @@ class SingleBackgroundHandler { return } - console.debug( - "Removing raster layer", - this._targetLayer.properties.id, - "map moved and not been used for", - SingleBackgroundHandler.DEACTIVATE_AFTER - ) try { if (map.getLayer(this._targetLayer.properties.id)) { + console.debug( + "Removing raster layer", + this._targetLayer.properties.id, + "map moved and not been used for", + SingleBackgroundHandler.DEACTIVATE_AFTER + ) map.removeLayer(this._targetLayer.properties.id) } } catch (e) { diff --git a/src/UI/Map/ShowDataLayerOptions.ts b/src/UI/Map/ShowDataLayerOptions.ts index 8894c61ac..543b13855 100644 --- a/src/UI/Map/ShowDataLayerOptions.ts +++ b/src/UI/Map/ShowDataLayerOptions.ts @@ -7,7 +7,7 @@ export interface ShowDataLayerOptions { /** * Features to show */ - features: FeatureSource> + features: FeatureSource & {id: string}>> /** * Indication of the current selected element; overrides some filters. * When a feature is tapped, the feature will be put in there From 763e8f06167e1ba3ebe9428c9d283ab6d0ba4dc4 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 19 Aug 2025 17:26:04 +0200 Subject: [PATCH 23/25] UX: tweaks to distance input: make button lose primary status when a first point is selected, use projected centerpoint instead of centerpoint --- langs/en.json | 1 + src/UI/InputElement/Helpers/DistanceInput.svelte | 14 +++++++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/langs/en.json b/langs/en.json index ac1e53187..ddb288373 100644 --- a/langs/en.json +++ b/langs/en.json @@ -674,6 +674,7 @@ }, "input_helpers": { "distance": { + "measureAgain": "Start new measurement from current location", "setFirst": "Measure from current location" } }, diff --git a/src/UI/InputElement/Helpers/DistanceInput.svelte b/src/UI/InputElement/Helpers/DistanceInput.svelte index e501d210a..0a4750979 100644 --- a/src/UI/InputElement/Helpers/DistanceInput.svelte +++ b/src/UI/InputElement/Helpers/DistanceInput.svelte @@ -7,7 +7,7 @@ import { UIEventSource, Store } from "../../../Logic/UIEventSource" import type { MapProperties } from "../../../Models/MapProperties" import ThemeViewState from "../../../Models/ThemeViewState" - import type { Feature } from "geojson" + import type { Feature, LineString } from "geojson" import type { RasterLayerPolygon } from "../../../Models/RasterLayers" import { RasterLayerUtils } from "../../../Models/RasterLayers" import { eliCategory } from "../../../Models/RasterLayerProperties" @@ -22,13 +22,16 @@ import Tr from "../../Base/Tr.svelte" import { onDestroy } from "svelte" - export let value: UIEventSource + export let value: UIEventSource export let feature: Feature export let args: { background?: string; zoom?: number } export let state: ThemeViewState = undefined export let map: UIEventSource = new UIEventSource(undefined) let center = GeoOperations.centerpointCoordinates(feature) + if (feature.geometry.type === "LineString") { + center = <[number, number]>GeoOperations.nearestPoint(>feature, center).geometry.coordinates + } export let initialCoordinate: { lon: number; lat: number } = { lon: center[0], lat: center[1] } let mapLocation: UIEventSource<{ lon: number; lat: number }> = new UIEventSource( initialCoordinate @@ -66,7 +69,7 @@ // A bit of a double task: calculate the actual value _and_ the map rendering const end = mapLocation.data const distance = GeoOperations.distanceBetween([start.lon, start.lat], [end.lon, end.lat]) - value.set(distance.toFixed(2)) + value.set(distance.toFixed(1)) return [ { @@ -93,6 +96,7 @@ layer: new LayerConfig(conflation), features: new StaticFeatureSource(lengthFeature), }) + const t = Translations.t.input_helpers.distance
@@ -102,6 +106,6 @@
-
+ From 0afb38c0649ac5cdf1001c6023e2b75b5d7a97ea Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 19 Aug 2025 17:31:42 +0200 Subject: [PATCH 24/25] UX: hide buttons for 'background' and 'filter' from the menu if those are disabled by the feature switches --- src/UI/BigComponents/MenuDrawer.svelte | 12 ++++++++---- src/UI/BigComponents/MenuDrawerIndex.svelte | 7 ++++--- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/UI/BigComponents/MenuDrawer.svelte b/src/UI/BigComponents/MenuDrawer.svelte index 07b266081..3e6134ecb 100644 --- a/src/UI/BigComponents/MenuDrawer.svelte +++ b/src/UI/BigComponents/MenuDrawer.svelte @@ -51,6 +51,8 @@ } }) let hotkeys = Hotkeys._docs + let showBackground = state.featureSwitches.featureSwitchBackgroundSelection + let showFilters = state.featureSwitches.featureSwitchFilter
@@ -75,10 +77,12 @@ - - - - + {#if $showFilters} + + {/if} + {#if $showBackground} + + {/if} diff --git a/src/UI/BigComponents/MenuDrawerIndex.svelte b/src/UI/BigComponents/MenuDrawerIndex.svelte index d33cc131a..08f78e420 100644 --- a/src/UI/BigComponents/MenuDrawerIndex.svelte +++ b/src/UI/BigComponents/MenuDrawerIndex.svelte @@ -38,7 +38,7 @@ import SidebarUnit from "../Base/SidebarUnit.svelte" import Squares2x2 from "@babeard/svelte-heroicons/mini/Squares2x2" import EnvelopeOpen from "@babeard/svelte-heroicons/mini/EnvelopeOpen" - import { UIEventSource } from "../../Logic/UIEventSource" + import { ImmutableStore, UIEventSource } from "../../Logic/UIEventSource" import MagnifyingGlassCircle from "@babeard/svelte-heroicons/mini/MagnifyingGlassCircle" import { AndroidPolyfill } from "../../Logic/Web/AndroidPolyfill" import Forgejo from "../../assets/svg/Forgejo.svelte" @@ -74,7 +74,7 @@ let usersettingslayer = new LayerConfig(usersettings, "usersettings", true) let featureSwitches = state.featureSwitches - let showHome = featureSwitches?.featureSwitchBackToThemeOverview + let showHome = featureSwitches?.featureSwitchBackToThemeOverview ?? new ImmutableStore(true) let pg = state.guistate.pageStates export let onlyLink: boolean const t = Translations.t.general.menu @@ -218,11 +218,12 @@
+ {#if $showHome} - + {/if} From 01e713b1e5ab074aecb5a9d02d3a5f78dcbb2b4b Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 19 Aug 2025 17:32:15 +0200 Subject: [PATCH 25/25] Themes(width): add more editing options --- assets/themes/width/width.json | 72 +++++++++++++++++++++++++++++++--- 1 file changed, 67 insertions(+), 5 deletions(-) diff --git a/assets/themes/width/width.json b/assets/themes/width/width.json index 47e007b3b..e2de717aa 100644 --- a/assets/themes/width/width.json +++ b/assets/themes/width/width.json @@ -26,9 +26,9 @@ "_car_width:=2 /* The width that a single car needs */", "_cyclistWidth:=1.5 /* The width a single cyclist needs to be safely overtaken */", "_pedestrianWidth:=0.75 /* The width a pedestrian needs if sidewalks are missing */", - "_has_left_parking=(feat.properties['parking:lane:left'] ?? feat.properties['parking:lane:both']) === 'parallel'", - "_has_right_parking=(feat.properties['parking:lane:right'] ?? feat.properties['parking:lane:both']) === 'parallel'", - "_has_other_parking= ['parking:lane:left','parking:lane:right','parking:lane:both'].some(key => ['perpendicular','diagonal'].indexOf(feat.properties[key]) >= 0)", + "_has_left_parking=['lane','yes', 'parallel'].indexOf(feat.properties['parking:left'] ?? feat.properties['parking:both']) >= 0", + "_has_right_parking=['lane','yes','parallel'].indexOf(feat.properties['parking:right'] ?? feat.properties['parking:both']) >= 0", + "_has_other_parking= ['parking:left:orientation','parking:right:orientation','parking:both:orientation'].some(key => ['perpendicular','diagonal'].indexOf(feat.properties[key]) >= 0)", "_parallel_parking_count=get(feat)('_has_right_parking') + get(feat)('_has_left_parking') /* in javascript logic: true + true == 2*/", "_width:needed:parking=get(feat)('_parallel_parking_count') * get(feat)('_car_width')", "_has_sidewalk_left=['left','both'].indexOf(feat.properties['sidewalk']) >= 0", @@ -154,7 +154,6 @@ }, { "id": "has_sidewalks", - "condition": "id=disabled", "question": { "nl": "Heeft deze straat voetpaden?" }, @@ -184,7 +183,9 @@ } } ] - } + }, + "questions", + "{nearby_images(closed,readonly)}" ], "pointRendering": [ { @@ -239,6 +240,67 @@ } } ] + }, + { + "id": "street_no_width", + "description": "Typical city streets with width", + "name": { + "nl": "Straten zonder breedte" + }, + "source": { + "osmTags": { + "and": [ + "width:carriageway=", + { + "or": [ + "highway=residential", + "highway=unclassified", + "highway=tertiary", + "highway=living_street" + ] + } + ] + } + }, + "minzoom": 19, + "title": { + "render": { + "nl": "{name}" + }, + "mappings": [ + { + "if": "name=", + "then": { + "nl": "Naamloos segment" + } + } + ] + }, + "tagRenderings": [ + { + "id": "carriageway_width", + "render": "Deze straat is {width:carriageway}m breed", + "question": "Hoe breed is deze straat?", + "freeform": { + "key": "width:carriageway", + "type": "distance", + "helperArgs": { + "zoom": 21, + "background": "map" + } + } + } + ], + "pointRendering": null, + "lineRendering": [ + { + "color": "#ff00ff", + "width": "4" + } + ], + "allowMove": false, + "allowSplit": true, + "allowDeletion": false } ], "lockLocation": [
NameMap generation dateSizeZoom rangesActions + + + + + + + + + +