From 06aa8a34061ec1e607b8f0de4ab8cf12213b8dae Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sun, 3 Aug 2025 16:35:38 +0200 Subject: [PATCH 01/92] Feature: offline: more features to be able to work fully offline --- 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/layers/usersettings/usersettings.json | 40 +++++++++++++++++++ index.html | 2 - langs/en.json | 1 + langs/layers/en.json | 6 +++ scripts/generateIncludedImages.ts | 4 +- scripts/generateLayerOverview.ts | 15 +++++-- scripts/prepareServiceWorker.ts | 2 +- src/InstallServiceWorker.ts | 24 ++++++----- src/Logic/Osm/OsmConnection.ts | 18 +++++---- src/UI/Base/LoginToggle.svelte | 21 ++++++---- src/UI/BigComponents/MenuDrawerIndex.svelte | 8 ++-- src/UI/Popup/ClearCaches.svelte | 2 +- .../TagRendering/TagRenderingEditable.svelte | 2 +- .../TagRendering/TagRenderingQuestion.svelte | 4 +- src/UI/SingleThemeGui.svelte | 22 ++++++++++ .../SettingsVisualisations.ts | 26 +++++++++++- src/all_themes_index.ts | 3 +- src/service-worker/SWGenerated.ts | 2 +- src/service-worker/index.ts | 37 ++++++++++++++++- 23 files changed, 203 insertions(+), 60 deletions(-) diff --git a/assets/layers/icons/icons.json b/assets/layers/icons/icons.json index 93e9e91ec..54ebd45d5 100644 --- a/assets/layers/icons/icons.json +++ b/assets/layers/icons/icons.json @@ -430,10 +430,10 @@ } }, { - "condition": "_favourite=yes", + "id": "favourite_icon", "description": "Only for rendering", "icon": "circle:white;heart:red", - "id": "favourite_icon", + "condition": "_favourite=yes", "metacondition": "__showTimeSensitiveIcons!=no" }, { diff --git a/assets/layers/last_click/last_click.json b/assets/layers/last_click/last_click.json index 6aaa19115..3185c513f 100644 --- a/assets/layers/last_click/last_click.json +++ b/assets/layers/last_click/last_click.json @@ -217,8 +217,8 @@ }, { "id": "debug", - "metacondition": "__featureSwitchIsDebugging=true", - "render": "{all_tags()}" + "render": "{all_tags()}", + "metacondition": "__featureSwitchIsDebugging=true" } ], "filter": [ diff --git a/assets/layers/note/note.json b/assets/layers/note/note.json index eedef08c6..b1dd36f32 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()}" + "render": "{visualize_note_comments()}", + "classes": "p-0" }, { "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 150fc6ecd..1c605ca38 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": [ { - "condition": "level=country", - "description": "The name of the country", "id": "country_name", - "render": "{nameEn} {emojiFlag}" + "description": "The name of the country", + "render": "{nameEn} {emojiFlag}", + "condition": "level=country" }, { - "condition": "_community_links~*", - "description": "Community Links (Discord, meetups, Slack groups, IRC channels, mailing lists etc...)", "id": "community_links", - "render": "{_community_links}" + "description": "Community Links (Discord, meetups, Slack groups, IRC channels, mailing lists etc...)", + "render": "{_community_links}", + "condition": "_community_links~*" } ], "filter": [ diff --git a/assets/layers/usersettings/usersettings.json b/assets/layers/usersettings/usersettings.json index a82beeaaf..907b37c2d 100644 --- a/assets/layers/usersettings/usersettings.json +++ b/assets/layers/usersettings/usersettings.json @@ -1898,6 +1898,46 @@ "render": { "*": "{storage_all_tags()}" } + }, + { + "id": "debug_serviceworker_accordeon", + "render": { + "special": { + "header": "debug_serviceworker_accordeon_title", + "labels": "debug_serviceworker", + "type": "group" + } + }, + "condition": "mapcomplete-show_debug=yes" + }, + { + "id": "debug_serviceworker_accordeon_title", + "labels": [ + "hidden" + ], + "render": { + "en": "Debug information about the service worker" + } + }, + { + "id": "expl", + "labels": [ + "debug_serviceworker", + "hidden" + ], + "render": { + "en": "To clear the service worker data, use the 'clear caches' button" + } + }, + { + "id": "service_worker_tags", + "labels": [ + "debug_serviceworker", + "hidden" + ], + "render": { + "*": "{serviceworker_all_tags()}" + } } ], "allowMove": false diff --git a/index.html b/index.html index 04cf21dcf..796ba73ec 100644 --- a/index.html +++ b/index.html @@ -43,8 +43,6 @@ - - diff --git a/langs/en.json b/langs/en.json index c5b3db254..1c368e3f5 100644 --- a/langs/en.json +++ b/langs/en.json @@ -335,6 +335,7 @@ "next": "Next", "noTagsSelected": "No tags selected", "number": "number", + "offline": "Your device is offline", "openTheMap": "Open the map", "openTheMapReason": "to view, edit and add information", "opening_hours": { diff --git a/langs/layers/en.json b/langs/layers/en.json index c9632acd7..7ccbab67b 100644 --- a/langs/layers/en.json +++ b/langs/layers/en.json @@ -14095,6 +14095,9 @@ "debug_accordeon_title": { "render": "Debug information" }, + "debug_serviceworker_accordeon_title": { + "render": "Debug information about the service worker" + }, "debug_storage_accordeon_title": { "render": "Debug information about local storage" }, @@ -14105,6 +14108,9 @@ } } }, + "expl": { + "render": "To clear the service worker data, use the 'clear caches' button" + }, "fixate-north": { "mappings": { "0": { diff --git a/scripts/generateIncludedImages.ts b/scripts/generateIncludedImages.ts index 69d15f0b9..6b4972b1f 100644 --- a/scripts/generateIncludedImages.ts +++ b/scripts/generateIncludedImages.ts @@ -1,7 +1,7 @@ import * as fs from "fs" import Script from "./Script" -function genImages(dryrun = false) { +function genImages() { console.log("Generating images") const dir = fs.readdirSync("./assets/svg") for (const path of dir) { @@ -64,7 +64,7 @@ class GenerateIncludedImages extends Script { super("Converts all images from assets/svg into svelte-classes.") } - async main(args: string[]): Promise { + async main(): Promise { genImages() } } diff --git a/scripts/generateLayerOverview.ts b/scripts/generateLayerOverview.ts index 503c50454..15af107cb 100644 --- a/scripts/generateLayerOverview.ts +++ b/scripts/generateLayerOverview.ts @@ -8,7 +8,7 @@ import { DoesImageExist, PrevalidateTheme, ValidateLayer, - ValidateThemeEnsemble, + ValidateThemeEnsemble } from "../src/Models/ThemeConfig/Conversion/Validation" import { Translation } from "../src/UI/i18n/Translation" import { OrderLayer, PrepareLayer } from "../src/Models/ThemeConfig/Conversion/PrepareLayer" @@ -19,7 +19,7 @@ import { DesugaringStep, Each, Fuse, - On, + On } from "../src/Models/ThemeConfig/Conversion/Conversion" import { Utils } from "../src/Utils" import Script from "./Script" @@ -182,7 +182,7 @@ class LayerBuilder extends Conversion> { return `./assets/layers/${id}/${id}.json` } - writeLayer(layer: LayerConfigJson) { + public writeLayer(layer: LayerConfigJson) { if (layer.labels?.some((l) => this._labelBlacklist.has(l))) { console.log("Not writing layer " + layer.id + ", censored") return @@ -191,6 +191,15 @@ class LayerBuilder extends Conversion> { if (!existsSync(LayerOverviewUtils.layerPath)) { mkdirSync(LayerOverviewUtils.layerPath) } + + const usedImages = Lists.dedup(new ExtractImages(true, new Set(this._desugaringState.tagRenderings.keys())) + .convertStrict({ layers: [layer], id: "dummy", icon: undefined, title: undefined }) + .map((x) => x.path)) + usedImages.sort() + + layer["_usedImages"] = usedImages + + writeFileSync(LayerBuilder.targetPath(layer.id), JSON.stringify(layer, null, " "), { encoding: "utf8", }) diff --git a/scripts/prepareServiceWorker.ts b/scripts/prepareServiceWorker.ts index 71f3ebbc7..d43184e66 100644 --- a/scripts/prepareServiceWorker.ts +++ b/scripts/prepareServiceWorker.ts @@ -9,7 +9,7 @@ class PrepareServiceWorker extends Script { } public async main() { - const v = Constants.vNumber + const v = Constants.vNumber + "-" + new Date().getTime() writeFileSync("./src/service-worker/SWGenerated.ts", ["export class SWGenerated {", "// generated by scripts/prepareServiceWorker.ts", diff --git a/src/InstallServiceWorker.ts b/src/InstallServiceWorker.ts index 85a0d76a7..c7fdbfbd2 100644 --- a/src/InstallServiceWorker.ts +++ b/src/InstallServiceWorker.ts @@ -1,13 +1,17 @@ -export {} -window.addEventListener("load", async () => { - if (!("serviceWorker" in navigator)) { - console.log("Service workers are not supported") - return - } - try { +export class InstallServiceWorker { + + static async installServiceWorker() { + if (!("serviceWorker" in navigator)) { + throw ("Service workers are not supported") + } await navigator.serviceWorker.register("/service-worker.js", { type: "module" }) console.log("Service worker registration successful") - } catch (err) { - console.error("Service worker registration failed", err) + } -}) + + static async precache(assets: string[]) { + if (assets?.length > 0) { + await fetch("./service-worker/precache?assets=" + assets.join(";")) + } + } +} diff --git a/src/Logic/Osm/OsmConnection.ts b/src/Logic/Osm/OsmConnection.ts index 733c4e5ec..0f44110cd 100644 --- a/src/Logic/Osm/OsmConnection.ts +++ b/src/Logic/Osm/OsmConnection.ts @@ -8,6 +8,7 @@ import Constants from "../../Models/Constants" import { Feature, Point } from "geojson" import { AndroidPolyfill } from "../Web/AndroidPolyfill" import { QueryParameters } from "../Web/QueryParameters" +import { IsOnline } from "../Web/IsOnline" interface OsmUserInfo { id: number @@ -131,6 +132,7 @@ export class OsmConnection { * Details of the currently logged-in user; undefined if not logged in */ public userDetails: UIEventSource + public isLoggedIn: Store public gpxServiceIsOnline: UIEventSource = new UIEventSource( "unknown" @@ -182,7 +184,7 @@ export class OsmConnection { this._oauth_config.oauth_secret = import.meta.env.VITE_OSM_OAUTH_SECRET } - this.userDetails = new UIEventSource(undefined, "userDetails") + this.userDetails = UIEventSource.asObject(LocalStorageSource.get("user_details"), undefined) if (options.fakeUser) { const ud = this.userDetails.data ud.csCount = 5678 @@ -197,13 +199,7 @@ export class OsmConnection { } this.updateCapabilities() - this.isLoggedIn = this.userDetails.map( - (user) => - !!user && - (this.apiIsOnline.data === "unknown" || this.apiIsOnline.data === "online"), - [this.apiIsOnline] - ) - + this.isLoggedIn = this.userDetails.map((user) => !!user) this._dryRun = options.dryRun ?? new UIEventSource(false) if (options?.shared_cookie) { @@ -284,6 +280,9 @@ export class OsmConnection { } public async AttemptLogin() { + if (!IsOnline.isOnline.data) { + return + } this.updateCapabilities() if (this.loadingStatus.data !== "logged-in") { this.loadingStatus.setData("loading") @@ -308,6 +307,9 @@ export class OsmConnection { } private async loadUserInfo() { + if (!IsOnline.isOnline.data) { + return + } try { const result = await this.interact("user/details.json") if (result === null) { diff --git a/src/UI/Base/LoginToggle.svelte b/src/UI/Base/LoginToggle.svelte index d4fa4fc6f..859f8fb0c 100644 --- a/src/UI/Base/LoginToggle.svelte +++ b/src/UI/Base/LoginToggle.svelte @@ -14,10 +14,15 @@ osmConnection: OsmConnection featureSwitches?: { featureSwitchEnableLogin?: UIEventSource } } + /** + * Do show this element when in offline mode + */ + export let offline = false /** * If set, 'loading' will act as if we are already logged in. */ - export let ignoreLoading: boolean = false + export let ignoreLoading: boolean = offline // If it works in offline mode, it'll work while we are logging in too + /** * If set and the OSM-api fails, do _not_ show any error messages nor the successful state, just hide. * Will still show the "not-logged-in"-slot @@ -32,23 +37,26 @@ unknown: t.loginFailedUnreachableMode, readonly: t.loginFailedReadonlyMode, } - const apiState: Store = + const apiState: Store = state?.osmConnection?.apiIsOnline ?? new ImmutableStore("online") const online = IsOnline.isOnline + let loggedIn = state?.osmConnection?.isLoggedIn {#if $badge} - {#if !$online} + {#if !$online && !offline} {#if !hiddenFail}
- Your device is offline +
{/if} {:else if !ignoreLoading && !hiddenFail && $loadingStatus === "loading"} - {:else if ($loadingStatus === "error" || $apiState === "readonly" || $apiState === "offline")} + {:else if $loggedIn} + + {:else if ($loadingStatus === "error" || $apiState === "readonly" || $apiState === "offline" || $apiState === "unreachable")} {#if !hiddenFail}
@@ -63,8 +71,7 @@
{/if} - {:else if $loadingStatus === "logged-in"} - + {:else if $loadingStatus === "not-attempted"} {/if} diff --git a/src/UI/BigComponents/MenuDrawerIndex.svelte b/src/UI/BigComponents/MenuDrawerIndex.svelte index 809068735..d9d0871e6 100644 --- a/src/UI/BigComponents/MenuDrawerIndex.svelte +++ b/src/UI/BigComponents/MenuDrawerIndex.svelte @@ -77,11 +77,9 @@ let usersettingslayer = new LayerConfig(usersettings, "usersettings", true) - let theme = state.theme let featureSwitches = state.featureSwitches let showHome = featureSwitches?.featureSwitchBackToThemeOverview let pg = state.guistate.pageStates - let location = state.mapProperties?.location export let onlyLink: boolean const t = Translations.t.general.menu let shown = new UIEventSource(state.guistate.pageStates.menu.data || !onlyLink) @@ -133,7 +131,7 @@ - +
{#if $userdetails.img} @@ -143,7 +141,7 @@ {/if}
- {$userdetails.name} + {$userdetails?.name ?? ''}
@@ -281,7 +279,7 @@ - {#if !state.theme} + {#if !state?.theme} diff --git a/src/UI/Popup/ClearCaches.svelte b/src/UI/Popup/ClearCaches.svelte index 0bc83e5e1..2adcffe7c 100644 --- a/src/UI/Popup/ClearCaches.svelte +++ b/src/UI/Popup/ClearCaches.svelte @@ -4,7 +4,7 @@ function clearCaches() { IdbLocalStorage.clearAll() - Utils.download("./service-worker-clear") + Utils.download("./service-worker/clear_caches.json") window.location.reload() } export let msg: string diff --git a/src/UI/Popup/TagRendering/TagRenderingEditable.svelte b/src/UI/Popup/TagRendering/TagRenderingEditable.svelte index 45d84ea9e..593488436 100644 --- a/src/UI/Popup/TagRendering/TagRenderingEditable.svelte +++ b/src/UI/Popup/TagRendering/TagRenderingEditable.svelte @@ -128,7 +128,7 @@ {layer} extraClasses="my-2" /> - {#if (!editingEnabled || $editingEnabled) && $apiState !== "readonly" && $apiState !== "offline"} + {#if !editingEnabled || $editingEnabled} {#if layer?.isNormal()} - + {#if $disabledInTheme.indexOf(config.id) >= 0} @@ -538,7 +538,7 @@ {/if} - +
{#if config.alwaysForceSaveButton}
- + {#if $recentThemes.length > 2}

diff --git a/src/UI/Base/Avatar.svelte b/src/UI/Base/Avatar.svelte new file mode 100644 index 000000000..47834b9d3 --- /dev/null +++ b/src/UI/Base/Avatar.svelte @@ -0,0 +1,20 @@ + + +{#if !$userdetails.img || !($loaded || $isOnline)} + +{:else} + avatar {loaded.set(true)}} /> +{/if} diff --git a/src/UI/BigComponents/MenuDrawerIndex.svelte b/src/UI/BigComponents/MenuDrawerIndex.svelte index d9d0871e6..3093909aa 100644 --- a/src/UI/BigComponents/MenuDrawerIndex.svelte +++ b/src/UI/BigComponents/MenuDrawerIndex.svelte @@ -63,6 +63,7 @@ import OfflineManagement from "./OfflineManagement.svelte" import { GlobeEuropeAfrica } from "@babeard/svelte-heroicons/solid/GlobeEuropeAfrica" import { onDestroy } from "svelte" + import Avatar from "../Base/Avatar.svelte" export let state: { favourites: FavouritesFeatureSource @@ -134,11 +135,7 @@
- {#if $userdetails.img} - avatar - {:else} - - {/if} +
{$userdetails?.name ?? ''} diff --git a/src/UI/BigComponents/StateIndicator.svelte b/src/UI/BigComponents/StateIndicator.svelte index bb465a943..7d00ea0e8 100644 --- a/src/UI/BigComponents/StateIndicator.svelte +++ b/src/UI/BigComponents/StateIndicator.svelte @@ -3,6 +3,7 @@ import Translations from "../i18n/Translations" import Tr from "../Base/Tr.svelte" import Loading from "../Base/Loading.svelte" + import { IsOnline } from "../../Logic/Web/IsOnline" export let state: ThemeViewState /** @@ -14,6 +15,7 @@ let dataIsLoading = state.dataIsLoading let currentState = state.hasDataInView + let online = IsOnline.isOnline const t = Translations.t.centerMessage const showingSearch = state.searchState.showSearchDrawer @@ -34,6 +36,10 @@
+{:else if $currentState === "no-data" && !$online} +
+ +
{:else if $currentState === "no-data"}
diff --git a/src/UI/BigComponents/WelcomeBack.svelte b/src/UI/BigComponents/WelcomeBack.svelte index 4541f987a..80714b11d 100644 --- a/src/UI/BigComponents/WelcomeBack.svelte +++ b/src/UI/BigComponents/WelcomeBack.svelte @@ -3,6 +3,7 @@ import { fade } from "svelte/transition" import { OsmConnection } from "../../Logic/Osm/OsmConnection" import { onDestroy } from "svelte" + import Avatar from "../Base/Avatar.svelte" let open = false export let state: { osmConnection: OsmConnection } @@ -28,9 +29,7 @@ > {#if $username !== undefined}
- {#if $userdetails.img} - avatar - {/if} +
Welcome back
From 7155cd7f6184073bf641f1524ae438a803710b94 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 5 Aug 2025 23:49:15 +0200 Subject: [PATCH 06/92] Feature(offline): better support for making changes while offline --- assets/layers/icons/icons.json | 4 +- langs/en.json | 5 +- src/Logic/Osm/Changes.ts | 5 ++ src/UI/BigComponents/MenuDrawerIndex.svelte | 12 +++- .../BigComponents/PendingChangesView.svelte | 58 +++++++++++++++++++ src/UI/Image/QueuedImagesView.svelte | 4 +- src/UI/Image/UploadImage.svelte | 2 +- src/UI/Image/UploadingImageCounter.svelte | 9 ++- 8 files changed, 89 insertions(+), 10 deletions(-) create mode 100644 src/UI/BigComponents/PendingChangesView.svelte 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/langs/en.json b/langs/en.json index f9fbf3379..0f25afbfe 100644 --- a/langs/en.json +++ b/langs/en.json @@ -18,7 +18,7 @@ "allFilteredAway": "No feature in view meets all filters", "loadingData": "Loading data…", "noData": "There are no relevant features in the current view", - "noDataOffline": "No data is loaded and you are offline", + "noDataOffline": "No data is loaded and you are offline", "ready": "Done!", "retrying": "Loading data failed. Trying again in {count} seconds…", "zoomIn": "Zoom in to view or edit the data" @@ -639,6 +639,7 @@ "uploading": "{count} images are being uploaded…" }, "noBlur": "Images will not be blurred. Do not photograph people", + "offline": "You are currently offline. Uploading images be attempted when your internet is back", "one": { "done": "Your image was successfully uploaded. Thank you!", "failed": "Sorry, we could not upload your image", @@ -653,7 +654,7 @@ "confirmDeleteTitle": "Delete this image?", "delete": "Delete this image", "intro": "The following images are queued for upload", - "menu": "Image upload queue ({count})", + "menu": "Pending changes and image uploads ({count})", "noFailedImages": "There are currently no images in the upload queue", "retryAll": "Retry uploading all images" }, diff --git a/src/Logic/Osm/Changes.ts b/src/Logic/Osm/Changes.ts index 14dae7e0c..bd2b942df 100644 --- a/src/Logic/Osm/Changes.ts +++ b/src/Logic/Osm/Changes.ts @@ -19,6 +19,7 @@ import MarkdownUtils from "../../Utils/MarkdownUtils" import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore" import { Feature, Point } from "geojson" import { Lists } from "../../Utils/Lists" +import { IsOnline } from "../Web/IsOnline" /** * Handles all changes made to OSM. @@ -287,6 +288,10 @@ export class Changes { if (this.pendingChanges.data.length === 0) { return } + if(!IsOnline.isOnline.data){ + // No use to upload, we aren't connected anyway + return + } if (this.isUploading.data) { console.log("Is already uploading... Abort") return diff --git a/src/UI/BigComponents/MenuDrawerIndex.svelte b/src/UI/BigComponents/MenuDrawerIndex.svelte index 3093909aa..a8952cfdc 100644 --- a/src/UI/BigComponents/MenuDrawerIndex.svelte +++ b/src/UI/BigComponents/MenuDrawerIndex.svelte @@ -64,6 +64,10 @@ import { GlobeEuropeAfrica } from "@babeard/svelte-heroicons/solid/GlobeEuropeAfrica" import { onDestroy } from "svelte" import Avatar from "../Base/Avatar.svelte" + import { SpecialVisualizationSvelte } from "../SpecialVisualization" + import ThemeViewState from "../../Models/ThemeViewState" + import { Changes } from "../../Logic/Osm/Changes" + import PendingChangesView from "./PendingChangesView.svelte" export let state: { favourites: FavouritesFeatureSource @@ -73,6 +77,7 @@ featureSwitches: Partial mapProperties?: MapProperties userRelatedState?: UserRelatedState + changes?: Changes } let userdetails = state.osmConnection.userDetails @@ -81,6 +86,7 @@ let featureSwitches = state.featureSwitches let showHome = featureSwitches?.featureSwitchBackToThemeOverview let pg = state.guistate.pageStates + let pendingChanges = state?.changes?.pendingChanges export let onlyLink: boolean const t = Translations.t.general.menu let shown = new UIEventSource(state.guistate.pageStates.menu.data || !onlyLink) @@ -164,12 +170,14 @@ /> - {#if $nrOfFailedImages.length > 0 || $failedImagesOpen} + {#if $nrOfFailedImages.length > 0 || $failedImagesOpen || $pendingChanges?.length > 0 } - + + {/if} diff --git a/src/UI/BigComponents/PendingChangesView.svelte b/src/UI/BigComponents/PendingChangesView.svelte new file mode 100644 index 000000000..3aa888c9e --- /dev/null +++ b/src/UI/BigComponents/PendingChangesView.svelte @@ -0,0 +1,58 @@ + +{#if $pending?.length > 0} +
+ +

Pending changes

+ + There are currently {$pending.length} pending changes: + + + + + + + + {#each $pending as change} + + + + + + + {/each} +
+ Theme + + Type + + Object +
{change.meta.theme}{change.meta.changeType} + + {change.type}/{change.id} + +
+ + {#if $debug} + {#each $pending as change} + {JSON.stringify(change)} + {/each} + {/if} +
+ +{/if} + + + diff --git a/src/UI/Image/QueuedImagesView.svelte b/src/UI/Image/QueuedImagesView.svelte index 3c813ffa5..ab2e98847 100644 --- a/src/UI/Image/QueuedImagesView.svelte +++ b/src/UI/Image/QueuedImagesView.svelte @@ -8,9 +8,11 @@ import type { ImageUploadArguments } from "../../Logic/ImageProviders/ImageUploadQueue" import { Store } from "../../Logic/UIEventSource" import UploadingImageCounter from "./UploadingImageCounter.svelte" + import { IsOnline } from "../../Logic/Web/IsOnline" export let state: WithImageState let queued: Store = state.imageUploadManager.queuedArgs let isUploading = state.imageUploadManager.isUploading + let online = IsOnline.isOnline const t = Translations.t const q = t.imageQueue @@ -27,7 +29,7 @@ {#if $isUploading} - {:else} + {:else if $online}
{/if} - -{#if $failed > dismissed} +{#if !$online} +
+ +
+{:else if $failed > dismissed} (dismissed = $failed)} {state} /> {/if} From f1da97285fe68c72b13299ebc97c28428fc95230 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 5 Aug 2025 23:55:10 +0200 Subject: [PATCH 07/92] Feature(offline): don't attempt to upload images if offline --- src/Logic/ImageProviders/ImageUploadManager.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Logic/ImageProviders/ImageUploadManager.ts b/src/Logic/ImageProviders/ImageUploadManager.ts index 88bad0bcc..86e4cf82b 100644 --- a/src/Logic/ImageProviders/ImageUploadManager.ts +++ b/src/Logic/ImageProviders/ImageUploadManager.ts @@ -16,6 +16,7 @@ import OsmObjectDownloader from "../Osm/OsmObjectDownloader" import ExifReader from "exifreader" import { Utils } from "../../Utils" import { Lists } from "../../Utils/Lists" +import { IsOnline } from "../Web/IsOnline" /** * The ImageUploadManager has a @@ -172,6 +173,9 @@ export class ImageUploadManager { if (this.uploadingAll) { return } + if(!IsOnline.isOnline){ + return + } try { let queue: ImageUploadArguments[] const failed: Set = new Set() From 561e4cb00990cb1e1e38f9459ff858256b9acfaf Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 8 Aug 2025 13:19:49 +0200 Subject: [PATCH 08/92] Feature(offline): more offline hardening --- src/Models/ThemeConfig/TagRenderingConfig.ts | 22 ++++-- src/UI/Comparison/ComparisonTool.svelte | 73 ++++++++++---------- src/UI/Image/UploadingImageCounter.svelte | 2 +- src/UI/Popup/DeleteFlow/DeleteFlowState.ts | 6 +- src/UI/Popup/DeleteFlow/DeleteWizard.svelte | 11 ++- src/UI/Popup/MarkAsFavourite.svelte | 48 +++++++------ src/UI/SingleThemeGui.svelte | 10 +++ 7 files changed, 103 insertions(+), 69 deletions(-) diff --git a/src/Models/ThemeConfig/TagRenderingConfig.ts b/src/Models/ThemeConfig/TagRenderingConfig.ts index c4bbdb7f1..f034e3d1a 100644 --- a/src/Models/ThemeConfig/TagRenderingConfig.ts +++ b/src/Models/ThemeConfig/TagRenderingConfig.ts @@ -19,6 +19,7 @@ import LayerConfig from "./LayerConfig" import ComparingTag from "../../Logic/Tags/ComparingTag" import { Unit } from "../Unit" import { Lists } from "../../Utils/Lists" +import { IsOnline } from "../../Logic/Web/IsOnline" export interface Mapping { readonly if: UploadableTag @@ -1154,10 +1155,10 @@ export class TagRenderingConfigUtils { const extraMappings = tags.bindD((tags) => { const country = tags._country if (country === undefined) { - return undefined + return undefined } const center = GeoOperations.centerpointCoordinates(feature) - return UIEventSource.fromPromise( + return UIEventSource.fromPromiseWithErr( NameSuggestionIndex.generateMappings( config.freeform.key, tags, @@ -1167,7 +1168,20 @@ export class TagRenderingConfigUtils { ) ) }) - return extraMappings.mapD((extraMappings) => { + return extraMappings.map((extraMappingsErr) => { + if(extraMappingsErr?.["error"]){ + console.log("Could not download the NSI: ", extraMappingsErr["error"]) + return config + } + const extraMappings = extraMappingsErr?.success + if(extraMappings === undefined){ + if(!IsOnline.isOnline.data){ + // The 'extraMappings' will still attempt to download the NSI - it might be in the service worker's cache + // As such, if they happen to come through anyway, they'll be shown + return config + } + return undefined + } if (extraMappings.length == 0) { return config } @@ -1187,6 +1201,6 @@ export class TagRenderingConfigUtils { }) ?? [] clone.mappings = [...oldMappingsCloned, ...extraMappings] return clone - }) + }, [IsOnline.isOnline]) } } diff --git a/src/UI/Comparison/ComparisonTool.svelte b/src/UI/Comparison/ComparisonTool.svelte index 58f90f27c..fb8b3fff0 100644 --- a/src/UI/Comparison/ComparisonTool.svelte +++ b/src/UI/Comparison/ComparisonTool.svelte @@ -13,9 +13,10 @@ import Translations from "../i18n/Translations" import Tr from "../Base/Tr.svelte" import AccordionSingle from "../Flowbite/AccordionSingle.svelte" - import GlobeAlt from "@babeard/svelte-heroicons/mini/GlobeAlt" import { ComparisonState } from "./ComparisonState" import LoginToggle from "../Base/LoginToggle.svelte" + import { IsOnline } from "../../Logic/Web/IsOnline" + import GlobeAlt from "@babeard/svelte-heroicons/mini/GlobeAlt" export let externalData: Store< | { success: { content: Record } } @@ -33,7 +34,7 @@ * A switch that signals that the information should be downloaded. * The actual 'download' code is _not_ implemented here */ - export let downloadInformation : UIEventSource + export let downloadInformation: UIEventSource export let collapsed: boolean const t = Translations.t.external @@ -48,45 +49,47 @@ let propertyKeysExternal = comparisonState.mapD((ct) => ct.propertyKeysExternal) let hasDifferencesAtStart = comparisonState.mapD((ct) => ct.hasDifferencesAtStart) let enableLogin = state.featureSwitches.featureSwitchEnableLogin + const online = IsOnline.isOnline - - - {#if !$sourceUrl || !$enableLogin} - +{#if $online} + + {#if !$sourceUrl || !$enableLogin} + {:else if !$downloadInformation} - - {:else if $externalData === undefined} -
- -
- {:else if $externalData["error"] !== undefined} -
- -
- {:else if $propertyKeysExternal.length === 0 && $knownImages.size + $unknownImages.length === 0} - - {:else if !$hasDifferencesAtStart} + + {:else if $externalData === undefined} +
+ +
+ {:else if $externalData["error"] !== undefined} +
+ +
+ {:else if $propertyKeysExternal.length === 0 && $knownImages.size + $unknownImages.length === 0} + + {:else if !$hasDifferencesAtStart} - {:else if $comparisonState !== undefined} - + {:else if $comparisonState !== undefined} + - - - {/if} -
+ + + {/if} +
+{/if} diff --git a/src/UI/Image/UploadingImageCounter.svelte b/src/UI/Image/UploadingImageCounter.svelte index e8aac6e49..4e214a4d2 100644 --- a/src/UI/Image/UploadingImageCounter.svelte +++ b/src/UI/Image/UploadingImageCounter.svelte @@ -93,7 +93,7 @@
{/if} -{#if !$online} +{#if !$online && $pending > 0}
diff --git a/src/UI/Popup/DeleteFlow/DeleteFlowState.ts b/src/UI/Popup/DeleteFlow/DeleteFlowState.ts index 53f815290..6f750e7f5 100644 --- a/src/UI/Popup/DeleteFlow/DeleteFlowState.ts +++ b/src/UI/Popup/DeleteFlow/DeleteFlowState.ts @@ -17,20 +17,18 @@ export class DeleteFlowState { private readonly _id: OsmId private readonly _allowDeletionAtChangesetCount: number private readonly _osmConnection: OsmConnection - private readonly state: SpecialVisualizationState constructor( id: OsmId, state: SpecialVisualizationState, allowDeletionAtChangesetCount?: number ) { - this.state = state this.objectDownloader = state.osmObjectDownloader this._id = id this._osmConnection = state.osmConnection this._allowDeletionAtChangesetCount = allowDeletionAtChangesetCount ?? Number.MAX_VALUE - this.CheckDeleteability(false) + this.checkDeleteability(false) } /** @@ -39,7 +37,7 @@ export class DeleteFlowState { * @constructor * @private */ - public CheckDeleteability(useTheInternet: boolean): void { + public checkDeleteability(useTheInternet: boolean): void { console.log("Checking deleteability (internet?", useTheInternet, ")") const t = Translations.t.delete const id = this._id diff --git a/src/UI/Popup/DeleteFlow/DeleteWizard.svelte b/src/UI/Popup/DeleteFlow/DeleteWizard.svelte index 11dc23a4f..27c9ce11e 100644 --- a/src/UI/Popup/DeleteFlow/DeleteWizard.svelte +++ b/src/UI/Popup/DeleteFlow/DeleteWizard.svelte @@ -22,6 +22,7 @@ import Invalid from "../../../assets/svg/Invalid.svelte" import { And } from "../../../Logic/Tags/And" import type { UploadableTag } from "../../../Logic/Tags/TagTypes" + import { IsOnline } from "../../../Logic/Web/IsOnline" export let state: SpecialVisualizationState export let deleteConfig: DeleteConfig @@ -39,9 +40,10 @@ const canBeDeletedReason = deleteAbility.canBeDeletedReason const hasSoftDeletion = deleteConfig.softDeletionTags !== undefined + const online = IsOnline.isOnline let currentState: "confirm" | "applying" | "deleted" = "confirm" $: { - deleteAbility.CheckDeleteability(true) + deleteAbility.checkDeleteability(true) } const t = Translations.t.delete @@ -97,8 +99,10 @@ currentState = "deleted" } - - +{#if !$online} +
You are offline. Deleting points is not possible
+ {:else} + {#if $canBeDeleted === false && !hasSoftDeletion}
@@ -171,3 +175,4 @@ {/if} + {/if} diff --git a/src/UI/Popup/MarkAsFavourite.svelte b/src/UI/Popup/MarkAsFavourite.svelte index b6df673a6..e15f28d3c 100644 --- a/src/UI/Popup/MarkAsFavourite.svelte +++ b/src/UI/Popup/MarkAsFavourite.svelte @@ -7,41 +7,45 @@ import LoginToggle from "../Base/LoginToggle.svelte" import type { Feature } from "geojson" import LayerConfig from "../../Models/ThemeConfig/LayerConfig" + import { IsOnline } from "../../Logic/Web/IsOnline" + import { Store } from "../../Logic/UIEventSource.js" /** * A full-blown 'mark as favourite'-button */ export let state: SpecialVisualizationState export let feature: Feature - export let tags: Record + export let tags: Store> export let layer: LayerConfig let isFavourite = tags?.map((tags) => tags._favourite === "yes") const t = Translations.t.favouritePoi + const online = IsOnline.isOnline function markFavourite(isFavourite: boolean) { state.favourites.markAsFavourite(feature, layer.id, state.theme.id, tags, isFavourite) } - - - {#if $isFavourite} -
- +
+ + {:else} + -
- - {:else} - - {/if} - + {/if} + +{/if} diff --git a/src/UI/SingleThemeGui.svelte b/src/UI/SingleThemeGui.svelte index a7e83c5d0..7b0a76b61 100644 --- a/src/UI/SingleThemeGui.svelte +++ b/src/UI/SingleThemeGui.svelte @@ -71,6 +71,16 @@ window.requestIdleCallback(() => { InstallServiceWorker.precache(layer["_usedImages"]?.filter(i => i.startsWith("./"))) }) + + // The NSI + window.requestIdleCallback(() => { + InstallServiceWorker.precache( + [Constants.nsiLogosEndpoint + "nsi.min.json", + Constants.nsiLogosEndpoint + "featureCollection.min.json", + ], + ) + }) + } }).catch(e => console.error("Could not install service worker:", e)) From 4658bf42a6c92d7305f4cca190442b426e620f9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Constantino=E2=80=93Bodin?= Date: Sun, 10 Aug 2025 18:05:44 +0200 Subject: [PATCH 09/92] Adding support for panorama cameras. --- .../surveillance_camera/surveillance_camera.json | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/assets/layers/surveillance_camera/surveillance_camera.json b/assets/layers/surveillance_camera/surveillance_camera.json index 6f52f9c6b..0295d4524 100644 --- a/assets/layers/surveillance_camera/surveillance_camera.json +++ b/assets/layers/surveillance_camera/surveillance_camera.json @@ -340,7 +340,7 @@ ] }, { - "id": "Camera type: fixed; panning; dome", + "id": "Camera type: fixed; panning; dome; panorama", "question": { "en": "What kind of camera is this?", "ca": "Quin tipus de càmera és aquesta?", @@ -388,7 +388,7 @@ }, "icon": "./assets/themes/surveillance/dome.svg" }, - { + { "if": "camera:type=panning", "then": { "en": "A panning camera", @@ -403,6 +403,13 @@ "ru": "Панорамная камера" } }, + { + "if": "camera:type=panorama", + "then": { + "en": "A camera with a wide field of view", + "fr": "Une caméra 360°" + } + }, { "if": "camera:type=doorbell", "icon": { @@ -958,4 +965,4 @@ "enableRelocation": false }, "enableMorePrivacy": true -} \ No newline at end of file +} From 3dc528f0daeb0f75c098ea2d39ef722efc55231e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Constantino=E2=80=93Bodin?= Date: Sun, 10 Aug 2025 18:11:26 +0200 Subject: [PATCH 10/92] Adding an image for panorama cameras. --- .../surveillance_camera.json | 4 + assets/themes/surveillance/panorama.license | 2 + assets/themes/surveillance/panorama.svg | 105 ++++++++++++++++++ 3 files changed, 111 insertions(+) create mode 100644 assets/themes/surveillance/panorama.license create mode 100644 assets/themes/surveillance/panorama.svg diff --git a/assets/layers/surveillance_camera/surveillance_camera.json b/assets/layers/surveillance_camera/surveillance_camera.json index 0295d4524..ac7500215 100644 --- a/assets/layers/surveillance_camera/surveillance_camera.json +++ b/assets/layers/surveillance_camera/surveillance_camera.json @@ -86,6 +86,10 @@ "if": "camera:type=doorbell", "then": "./assets/layers/surveillance_camera/doorbell.svg" }, + { + "if": "camera:type=panorama", + "then": "./assets/themes/surveillance/panorama.svg" + }, { "if": "_direction:leftright=right", "then": "./assets/themes/surveillance/cam_right.svg" diff --git a/assets/themes/surveillance/panorama.license b/assets/themes/surveillance/panorama.license new file mode 100644 index 000000000..f725fff05 --- /dev/null +++ b/assets/themes/surveillance/panorama.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: Martin Bodin +SPDX-License-Identifier: CC0-1.0 diff --git a/assets/themes/surveillance/panorama.svg b/assets/themes/surveillance/panorama.svg new file mode 100644 index 000000000..0a21207b0 --- /dev/null +++ b/assets/themes/surveillance/panorama.svg @@ -0,0 +1,105 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + From ef16f5ccef821ee80ef18fb49f65ab2951f5c6b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Constantino=E2=80=93Bodin?= Date: Sun, 10 Aug 2025 18:17:00 +0200 Subject: [PATCH 11/92] Translation suggestions for panorama cameras. --- assets/layers/surveillance_camera/surveillance_camera.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/assets/layers/surveillance_camera/surveillance_camera.json b/assets/layers/surveillance_camera/surveillance_camera.json index ac7500215..03008ae9b 100644 --- a/assets/layers/surveillance_camera/surveillance_camera.json +++ b/assets/layers/surveillance_camera/surveillance_camera.json @@ -410,7 +410,9 @@ { "if": "camera:type=panorama", "then": { - "en": "A camera with a wide field of view", + "en": "A 360° camera", + "de": "Eine 360°-Kamera", + "es": "Una cámara de 360°", "fr": "Une caméra 360°" } }, @@ -537,7 +539,7 @@ "da": "Hvilken form for overvågning er dette kamera?", "de": "Was überwacht diese Kamera?", "es": "¿Qué tipo de vigilancia es esta cámara?", - "fr": "De quel genre de surveillance cette caméra est-elle ?", + "fr": "De quel genre de surveillance cette caméra est-elle ?", "it": "Che tipo di sorveglianza è questa telecamera?", "nl": "Wat soort bewaking wordt hier uitgevoerd?", "sl": "Kaj nadzoruje ta kamera?" From c0ee578df1b0db3c9670b6c8f722787e6a9b72f3 Mon Sep 17 00:00:00 2001 From: Robin van der Linde Date: Tue, 12 Aug 2025 23:54:01 +0200 Subject: [PATCH 12/92] Themes(parkings): Add charge points --- assets/themes/parkings/parkings.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/assets/themes/parkings/parkings.json b/assets/themes/parkings/parkings.json index 0e3480033..bb1ec8692 100644 --- a/assets/themes/parkings/parkings.json +++ b/assets/themes/parkings/parkings.json @@ -65,7 +65,10 @@ "parking_spaces", "parking_ticket_machine", { - "builtin": "charging_station", + "builtin": [ + "charging_station", + "charge_point" + ], "override": { "minzoom": 18 } From 2cf0bc1866cd8df4bc7006b29a5f49210761ccb3 Mon Sep 17 00:00:00 2001 From: Robin van der Linde Date: Wed, 13 Aug 2025 01:32:34 +0200 Subject: [PATCH 13/92] Themes(shops): Add self_checkout question --- assets/layers/filters/filters.json | 19 ++++++- assets/layers/questions/questions.json | 75 ++++++++++++++++++++++++++ assets/layers/shops/shops.json | 5 +- 3 files changed, 96 insertions(+), 3 deletions(-) diff --git a/assets/layers/filters/filters.json b/assets/layers/filters/filters.json index 40270aea1..fa436162b 100644 --- a/assets/layers/filters/filters.json +++ b/assets/layers/filters/filters.json @@ -545,7 +545,24 @@ "osmTags": "kids_area!=no" } ] + }, + { + "id": "self_checkout", + "options": [ + { + "question": { + "en": "Has self-checkout", + "nl": "Heeft zelfscan" + }, + "osmTags": { + "or": [ + "self_checkout=yes", + "self_checkout=only" + ] + } + } + ] } ], "allowMove": false -} +} \ No newline at end of file diff --git a/assets/layers/questions/questions.json b/assets/layers/questions/questions.json index a6ccc291a..a5c92897f 100644 --- a/assets/layers/questions/questions.json +++ b/assets/layers/questions/questions.json @@ -3543,6 +3543,81 @@ "filter": [ "filters.kids_area" ] + }, + { + "id": "self_checkout", + "labels": [ + "self_checkout_questions" + ], + "question": { + "en": "Does this place offer self-checkout?", + "nl": "Biedt deze plaats zelfscannen aan?" + }, + "questionHint": { + "en": "e.g. handheld scanners or a self-checkout kiosk", + "nl": "bijv. handscanners of een zelfscankassa" + }, + "mappings": [ + { + "if": "self_checkout=yes", + "then": { + "en": "This place offers self-checkout", + "nl": "Deze plaats biedt zelfscannen aan" + } + }, + { + "if": "self_checkout=no", + "then": { + "en": "This place does not offer self-checkout", + "nl": "Deze plaats biedt geen zelfscannen aan" + } + }, + { + "if": "self_checkout=only", + "then": { + "en": "This place only offers self-checkout", + "nl": "Deze plaats biedt enkel zelfscannen aan" + } + } + ], + "filter": [ + "filters.self_checkout" + ] + }, + { + "id": "self_checkout_type", + "labels": [ + "self_checkout_questions" + ], + "question": { + "en": "What kind of self-checkout does this place offer?", + "nl": "Wat voor soort zelfscannen biedt deze plaats aan?" + }, + "mappings": [ + { + "if": "self_checkout:handheld=yes", + "ifnot": "self_checkout:handheld=no", + "then": { + "en": "This place offers self-checkout using a handheld scanner", + "nl": "Deze plaats biedt zelfscannen met een handscanner aan" + } + }, + { + "if": "self_checkout:self_scan=yes", + "ifnot": "self_checkout:self_scan=no", + "then": { + "en": "This place offers self-checkout using a self-checkout kiosk", + "nl": "Deze plaats biedt zelfscannen met een zelfscankassa aan" + } + } + ], + "condition": { + "or": [ + "self_checkout=yes", + "self_checkout=only" + ] + }, + "multiAnswer": true } ], "allowMove": false, diff --git a/assets/layers/shops/shops.json b/assets/layers/shops/shops.json index a17a015ed..cb835446e 100644 --- a/assets/layers/shops/shops.json +++ b/assets/layers/shops/shops.json @@ -1644,7 +1644,8 @@ } }, "description", - "toilet_at_amenity_lib.all" + "toilet_at_amenity_lib.all", + "self_checkout_questions" ], "filter": [ { @@ -1715,4 +1716,4 @@ ] }, "allowMove": true -} +} \ No newline at end of file From bd3c266a4d1ce344b613431e5704a91207347b8c Mon Sep 17 00:00:00 2001 From: Robin van der Linde Date: Wed, 13 Aug 2025 01:54:38 +0200 Subject: [PATCH 14/92] Fix: check for type in TagLink --- src/UI/Popup/AllTagsPanel/TagLink.svelte | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/UI/Popup/AllTagsPanel/TagLink.svelte b/src/UI/Popup/AllTagsPanel/TagLink.svelte index 453a4f6a3..1af12f757 100644 --- a/src/UI/Popup/AllTagsPanel/TagLink.svelte +++ b/src/UI/Popup/AllTagsPanel/TagLink.svelte @@ -1,12 +1,11 @@ -{#if url} +{#if url && values.length > 0} {#each values as value, index} {#if index > 0}; {/if} From 07a181aa1e8f758e88a91bcd55eaca1788a558b0 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 14 Aug 2025 14:45:44 +0200 Subject: [PATCH 15/92] Offline: don't attempt to load reviews when offline --- src/Logic/Web/MangroveReviews.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Logic/Web/MangroveReviews.ts b/src/Logic/Web/MangroveReviews.ts index 38ee66321..d043b539f 100644 --- a/src/Logic/Web/MangroveReviews.ts +++ b/src/Logic/Web/MangroveReviews.ts @@ -5,6 +5,7 @@ import { Feature, Position } from "geojson" import { GeoOperations } from "../GeoOperations" import { SpecialVisualizationState } from "../../UI/SpecialVisualization" import { WithUserRelatedState } from "../../Models/ThemeViewState/WithUserRelatedState" +import { IsOnline } from "./IsOnline" export interface ReviewCollection { readonly subjectUri?: Store @@ -238,11 +239,14 @@ export default class FeatureReviews implements ReviewCollection { if (!loadingAllowed.data) { return } + if (!IsOnline.isOnline.data) { + return + } const reviews = await MangroveReviews.getReviews({ sub }) console.debug("Got reviews for", feature, reviews, sub) this.addReviews(reviews.reviews, this._name.data) }, - [this._name, loadingAllowed] + [this._name, loadingAllowed, IsOnline.isOnline] ) /* We also construct all subject queries _without_ encoding the name to work around a previous bug * See https://github.com/giggls/opencampsitemap/issues/30 From 427cbb30063e1791ce0940681b9ee7315c117edb Mon Sep 17 00:00:00 2001 From: Robin van der Linde Date: Sun, 17 Aug 2025 13:29:12 +0200 Subject: [PATCH 16/92] Chore: reset translations --- assets/layers/diets/diets.json | 6 +- .../historic_rolling_stock.json | 14 ++- assets/layers/railway/railway.json | 3 - assets/themes/grb/grb.json | 92 ++++++++++--------- langs/layers/cs.json | 4 +- langs/layers/en.json | 45 ++++++++- langs/layers/nl.json | 37 +++++++- langs/themes/en.json | 7 ++ 8 files changed, 151 insertions(+), 57 deletions(-) diff --git a/assets/layers/diets/diets.json b/assets/layers/diets/diets.json index cac1388a3..e5acaef47 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", @@ -692,4 +692,4 @@ } } ] -} +} \ No newline at end of file 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/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..ee422ee16 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": { @@ -5839,6 +5841,13 @@ } } }, + "22": { + "options": { + "0": { + "question": "Has self-checkout" + } + } + }, "3": { "options": { "0": { @@ -9976,6 +9985,32 @@ }, "question": "What kind of seating does {title()} have?" }, + "self_checkout": { + "mappings": { + "0": { + "then": "This place offers self-checkout" + }, + "1": { + "then": "This place does not offer self-checkout" + }, + "2": { + "then": "This place only offers self-checkout" + } + }, + "question": "Does this place offer self-checkout?", + "questionHint": "e.g. handheld scanners or a self-checkout kiosk" + }, + "self_checkout_type": { + "mappings": { + "0": { + "then": "This place offers self-checkout using a handheld scanner" + }, + "1": { + "then": "This place offers self-checkout using a self-checkout kiosk" + } + }, + "question": "What kind of self-checkout does this place offer?" + }, "service:electricity": { "mappings": { "0": { @@ -10092,6 +10127,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..bac059917 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": { @@ -5517,6 +5519,13 @@ } } }, + "22": { + "options": { + "0": { + "question": "Heeft zelfscan" + } + } + }, "5": { "options": { "0": { @@ -8653,6 +8662,32 @@ }, "question": "Wat voor zitplaatsen heeft {title()}?" }, + "self_checkout": { + "mappings": { + "0": { + "then": "Deze plaats biedt zelfscannen aan" + }, + "1": { + "then": "Deze plaats biedt geen zelfscannen aan" + }, + "2": { + "then": "Deze plaats biedt enkel zelfscannen aan" + } + }, + "question": "Biedt deze plaats zelfscannen aan?", + "questionHint": "bijv. handscanners of een zelfscankassa" + }, + "self_checkout_type": { + "mappings": { + "0": { + "then": "Deze plaats biedt zelfscannen met een handscanner aan" + }, + "1": { + "then": "Deze plaats biedt zelfscannen met een zelfscankassa aan" + } + }, + "question": "Wat voor soort zelfscannen biedt deze plaats aan?" + }, "service:electricity": { "mappings": { "0": { 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 aecc36dfbe0972eec5f76d11a5b526a1992ecb2c Mon Sep 17 00:00:00 2001 From: Robin van der Linde Date: Sun, 17 Aug 2025 14:37:54 +0200 Subject: [PATCH 17/92] Themes(shops): change conditions for self_checkout question --- assets/layers/shops/shops.json | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/assets/layers/shops/shops.json b/assets/layers/shops/shops.json index 39523b933..17f1ed37f 100644 --- a/assets/layers/shops/shops.json +++ b/assets/layers/shops/shops.json @@ -1665,9 +1665,30 @@ } } }, + { + "builtin": "self_checkout", + "override": { + "+mappings": [ + { + "if": { + "and": [ + "self_checkout=", + "shop!=supermarket", + "shop!=convenience", + "shop!=chemist" + ] + }, + "then": { + "en": "This shop (probably) does not offer self-checkout" + }, + "hideInAnswer": true + } + ] + } + }, + "self_checkout_type", "description", - "toilet_at_amenity_lib.all", - "self_checkout_questions" + "toilet_at_amenity_lib.all" ], "filter": [ { From 6967196ade6467eff7b58fc462b5bed04031d427 Mon Sep 17 00:00:00 2001 From: Robin van der Linde Date: Sun, 17 Aug 2025 14:43:08 +0200 Subject: [PATCH 18/92] Chore: remove old import --- src/UI/Popup/AllTagsPanel/TagLink.svelte | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/UI/Popup/AllTagsPanel/TagLink.svelte b/src/UI/Popup/AllTagsPanel/TagLink.svelte index 1af12f757..bc41ddb1b 100644 --- a/src/UI/Popup/AllTagsPanel/TagLink.svelte +++ b/src/UI/Popup/AllTagsPanel/TagLink.svelte @@ -1,7 +1,6 @@ {#if url && values.length > 0} From 6e1aaf6be1950a50eee8c01ade244821bb221563 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Wed, 20 Aug 2025 01:09:11 +0200 Subject: [PATCH 19/92] Fix: fix crash in GRB theme when replacing geometry --- src/UI/Popup/AutoApplyButton.svelte | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/UI/Popup/AutoApplyButton.svelte b/src/UI/Popup/AutoApplyButton.svelte index 6a0ba5791..5bf7cdc7d 100644 --- a/src/UI/Popup/AutoApplyButton.svelte +++ b/src/UI/Popup/AutoApplyButton.svelte @@ -1,7 +1,6 @@
@@ -9,7 +12,7 @@
-
+
diff --git a/src/UI/BigComponents/MenuDrawerIndex.svelte b/src/UI/BigComponents/MenuDrawerIndex.svelte index 1ceb57421..cac998937 100644 --- a/src/UI/BigComponents/MenuDrawerIndex.svelte +++ b/src/UI/BigComponents/MenuDrawerIndex.svelte @@ -101,8 +101,8 @@ {#if onlyLink} {/if} -
-

+
+

-
+
{#if $tags._deleted === "yes"}

{:else} -
+

@@ -40,6 +40,7 @@ {/if}

+ {#if layer.titleIcons.length > 0} + {/if}
{/if}
-
+
state.selectedElement.setData(undefined)} />
diff --git a/src/UI/Favourites/Favourites.svelte b/src/UI/Favourites/Favourites.svelte index 581a21aaa..2b1d08f13 100644 --- a/src/UI/Favourites/Favourites.svelte +++ b/src/UI/Favourites/Favourites.svelte @@ -40,11 +40,9 @@ -
- - - -
+ + +
{#if $favourites.length === 0} diff --git a/src/UI/Popup/AddNewPoint/PresetList.svelte b/src/UI/Popup/AddNewPoint/PresetList.svelte index 984372534..91f676209 100644 --- a/src/UI/Popup/AddNewPoint/PresetList.svelte +++ b/src/UI/Popup/AddNewPoint/PresetList.svelte @@ -100,9 +100,8 @@ }>() - + - {#each presets as preset} dispatch("select", preset)}> preset.icon} /> diff --git a/src/UI/Popup/LanguageElement/LanguageAnswer.svelte b/src/UI/Popup/LanguageElement/LanguageAnswer.svelte index 564ecd51d..9e44dd526 100644 --- a/src/UI/Popup/LanguageElement/LanguageAnswer.svelte +++ b/src/UI/Popup/LanguageElement/LanguageAnswer.svelte @@ -6,6 +6,7 @@ import type { Feature } from "geojson" import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" import * as all_languages from "../../../assets/language_translations.json" + import Locale from "../../i18n/Locale" /** * Visualizes a list of the known languages @@ -22,6 +23,7 @@ export let layer: LayerConfig | undefined let [beforeListing, afterListing] = (render_all ?? "{list()}").split("{list()}") + let currentLanguage: Store = Locale.language {#if $languages.length === 1} @@ -30,9 +32,8 @@ {tags} {feature} {layer} - t={new TypedTranslation({ "*": single_render }).PartialSubsTr( - "language()", - new Translation(all_languages[$languages[0]], undefined) + t={new TypedTranslation({ "*": single_render }).PartialSubs( + {"language()": new Translation(all_languages[$languages[0]]).textFor($currentLanguage)} )} /> {:else} @@ -45,9 +46,8 @@ {tags} {feature} {layer} - t={new TypedTranslation({ "*": item_render }).PartialSubsTr( - "language()", - new Translation(all_languages[language], undefined) + t={new TypedTranslation({ "*": item_render }).PartialSubs( + {"language()": new Translation(all_languages[language]).textFor($currentLanguage)} )} /> diff --git a/src/UI/Popup/TagRendering/Questionbox.svelte b/src/UI/Popup/TagRendering/Questionbox.svelte index 48c44dfc1..d20e83870 100644 --- a/src/UI/Popup/TagRendering/Questionbox.svelte +++ b/src/UI/Popup/TagRendering/Questionbox.svelte @@ -15,6 +15,7 @@ import TagRenderingQuestionDynamic from "./TagRenderingQuestionDynamic.svelte" import LoginToggle from "../../Base/LoginToggle.svelte" import AccordionSingle from "../../Flowbite/AccordionSingle.svelte" + import { ChevronRightIcon } from "@rgossiaux/svelte-heroicons/solid" export let layer: LayerConfig export let tags: UIEventSource> @@ -180,7 +181,10 @@ }} slot="cancel" > +
+ +
{/if} diff --git a/src/UI/Popup/TagRendering/TagRenderingEditable.svelte b/src/UI/Popup/TagRendering/TagRenderingEditable.svelte index 45d84ea9e..c0b16be48 100644 --- a/src/UI/Popup/TagRendering/TagRenderingEditable.svelte +++ b/src/UI/Popup/TagRendering/TagRenderingEditable.svelte @@ -148,6 +148,6 @@ } .answer:has(.edit-button:hover) { - border: 1px solid var(--catch-detail-color-contrast); + border: 1px solid var(--interactive-contrast); } diff --git a/src/UI/Popup/TagRendering/TagRenderingMapping.svelte b/src/UI/Popup/TagRendering/TagRenderingMapping.svelte index 94f255758..44b99286f 100644 --- a/src/UI/Popup/TagRendering/TagRenderingMapping.svelte +++ b/src/UI/Popup/TagRendering/TagRenderingMapping.svelte @@ -21,7 +21,7 @@ /** * Css classes to apply */ - export let clss: string = "ml-2" + export let clss: string = "" export let mapping: { readonly if?: TagsFilter readonly then: Translation diff --git a/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte b/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte index dd15c7b86..70f7511f4 100644 --- a/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte +++ b/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte @@ -13,7 +13,6 @@ import SpecialTranslation from "./SpecialTranslation.svelte" import TagHint from "../TagHint.svelte" import LoginToggle from "../../Base/LoginToggle.svelte" - import SubtleButton from "../../Base/SubtleButton.svelte" import TagRenderingMappingInput from "./TagRenderingMappingInput.svelte" import { Translation } from "../../i18n/Translation" import { Unit } from "../../../Models/Unit" @@ -21,9 +20,8 @@ import { TagUtils } from "../../../Logic/Tags/TagUtils" import Search from "../../../assets/svg/Search.svelte" - import Login from "../../../assets/svg/Login.svelte" import { placeholder } from "../../../Utils/placeholder" - import { TrashIcon } from "@rgossiaux/svelte-heroicons/solid" + import { ChevronRightIcon, TrashIcon } from "@rgossiaux/svelte-heroicons/solid" import { Tag } from "../../../Logic/Tags/Tag" import { And } from "../../../Logic/Tags/And" import { get } from "svelte/store" @@ -36,6 +34,7 @@ import DotMenu from "../../Base/DotMenu.svelte" import SidebarUnit from "../../Base/SidebarUnit.svelte" import { Lists } from "../../../Utils/Lists" + import LoginButton from "../../Base/LoginButton.svelte" export let config: TagRenderingConfig export let tags: UIEventSource> @@ -360,7 +359,7 @@ {#if question !== undefined && $apiState !== "readonly" && $apiState !== "offline"}
{#if layer?.isNormal()} - + {#if $disabledInTheme.indexOf(config.id) >= 0} @@ -538,7 +537,7 @@
{/if} - +
{#if config.alwaysForceSaveButton} @@ -549,10 +548,9 @@ {:else} - state?.osmConnection?.AttemptLogin()}> - + - + {/if}
{#if $feedback !== undefined} @@ -561,6 +559,7 @@
{/if} +

@@ -588,7 +587,7 @@

-
+
{#if $onMarkUnknown?.length > 0 && $isKnown && !matchesEmpty} {/if} +
diff --git a/src/UI/SpecialVisualisations/SettingsVisualisations.ts b/src/UI/SpecialVisualisations/SettingsVisualisations.ts index c8b19da2e..6a8d9172f 100644 --- a/src/UI/SpecialVisualisations/SettingsVisualisations.ts +++ b/src/UI/SpecialVisualisations/SettingsVisualisations.ts @@ -1,8 +1,4 @@ -import { - SpecialVisualisationParams, - SpecialVisualizationState, - SpecialVisualizationSvelte, -} from "../SpecialVisualization" +import { SpecialVisualisationParams, SpecialVisualizationSvelte } from "../SpecialVisualization" import SvelteUIElement from "../Base/SvelteUIElement" import Constants from "../../Models/Constants" import LogoutButton from "../Base/LogoutButton.svelte" @@ -17,8 +13,6 @@ import LanguageUtils from "../../Utils/LanguageUtils" import LanguagePicker from "../InputElement/LanguagePicker.svelte" import PendingChangesIndicator from "../BigComponents/PendingChangesIndicator.svelte" import { Utils } from "../../Utils" -import { Feature } from "geojson" -import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import QrCode from "../Popup/QrCode.svelte" import ClearGPSHistory from "../BigComponents/ClearGPSHistory.svelte" import DisabledQuestions from "../Popup/DisabledQuestions.svelte" diff --git a/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts b/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts index 14022bd95..135a6d039 100644 --- a/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts +++ b/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts @@ -5,7 +5,6 @@ import { SpecialVisualizationSvelte, } from "../SpecialVisualization" import { ImmutableStore, UIEventSource } from "../../Logic/UIEventSource" -import { Feature } from "geojson" import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import { Translation } from "../i18n/Translation" import { VariableUiElement } from "../Base/VariableUIElement" diff --git a/src/UI/StylesheetTestGui.svelte b/src/UI/StylesheetTestGui.svelte index 3284fc2c0..daeb90794 100644 --- a/src/UI/StylesheetTestGui.svelte +++ b/src/UI/StylesheetTestGui.svelte @@ -4,32 +4,23 @@ import Login from "../assets/svg/Login.svelte" import Dropdown from "./Base/Dropdown.svelte" import { UIEventSource } from "../Logic/UIEventSource" + import StylesheetTestUnit from "./StylesheetTestUnit.svelte" -
+

Stylesheet testing grounds

This document exists to explore the style hierarchy. +
+

Normal background

There are a few styles, such as the normal-background -style which is used if there is nothing special going on. Some general information, with at most - a link to someplace -
Subtle
- -
Alert: something went wrong
-
Warning
-
Some important information
-
Thank you! Operation successful
- - Loading... - Dropdown: - - - - + +

Low interaction

@@ -38,77 +29,9 @@ areas, where some buttons might appear.

-
- Highly interactive area (mostly: active question) -
-
Subtle
- Dropdown: - - - - + -
- - - - - - - -
-
- - - -
- - -
- - - -
- -
Alert: something went wrong
-
Warning
-
Some important information
-
Thank you! Operation successful
- - - Loading...
@@ -117,64 +40,9 @@ There are interactive areas, where many buttons and input elements will appear.

-
Subtle
-
- - - -
+ -
- - -
-
Alert: something went wrong
-
Warning
-
Some important information
-
Thank you! Operation successful
- - - Loading... -
- - - -
- -
- Area with extreme high interactivity due to `border-interactive` -
- -
diff --git a/src/UI/StylesheetTestUnit.svelte b/src/UI/StylesheetTestUnit.svelte new file mode 100644 index 000000000..819d2bda4 --- /dev/null +++ b/src/UI/StylesheetTestUnit.svelte @@ -0,0 +1,76 @@ + a link to someplace +
Subtle
+
Alert: something went wrong
+
Warning
+
Thank you! Operation successful
+
+ Area with extreme high interactivity due to
border-interactive interactive
+
+ + Loading... + Dropdown: + + + +
+ + + + + + + +
+
+ + + +
+ + +
+ + + +
diff --git a/src/UI/ThemeViewGUI.svelte b/src/UI/ThemeViewGUI.svelte index 8431ad8d1..0169c7507 100644 --- a/src/UI/ThemeViewGUI.svelte +++ b/src/UI/ThemeViewGUI.svelte @@ -432,7 +432,9 @@ {/if} - +
+ +
diff --git a/src/index.css b/src/index.css index 3f5f17952..d1dc9a284 100644 --- a/src/index.css +++ b/src/index.css @@ -32,27 +32,28 @@ --low-interaction-background: #eeeeee; --low-interaction-background-50: #eeeeee90; --low-interaction-foreground: black; - --low-interaction-contrast: #ff00ff; --low-interaction-border: #dcdcdc; + --interactive-background: #dddddd; --interactive-foreground: black; - --interactive-contrast: #ff00ff; + --interactive-contrast: #cd1dcd; --interaction-border: #bfbfbf; - --button-background: #282828; + --button-background-primary: #191919; --button-background-hover: #484848; - --button-primary-background-hover: #353535; + --button-primary-background-hover: rgba(48, 47, 47, 0.94); --button-foreground: white; - --button-border-color: #F7F7F7; + --button-background: #fafafa; + --button-border: #B8B8B8; --disabled: #B8B8B8; --disabled-font: #B8B8B8; - --catch-detail-color: black; /*#3a3aeb;*/ - --catch-detail-foregroundcolor: white; - --catch-detail-color-contrast: #fb3afb; + --catch-detail-color: var(--background-color); + --catch-detail-foregroundcolor: var(--foreground-color); + --catch-detail-color-contrast: var(--interactive-contrast); --image-carousel-height: 350px; @@ -161,7 +162,7 @@ input[type="text"] { } .border-interactive { - border: 2px dashed var(--catch-detail-color-contrast); + border: 2px dashed var(--interactive-contrast); border-radius: 0.5rem; } @@ -198,16 +199,15 @@ button, .button { padding: 0.25rem 1rem; margin: 0.25rem; - background: var(--background-color); - border: 1px solid var(--button-background-hover); - box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); - border-radius: 15px; - - background: var(--background-color); - transition: all 200ms; - + background: var(--button-background); + border: 2px solid var(--button-border); + border-radius: 7px; + transition: background-color 200ms; } +.low-interaction button{ + background: var(--background-color); +} .group > button { padding-right: 1rem !important; /*Flowbite workaround */ @@ -217,7 +217,13 @@ button.w-full { margin-left: 0; } -button:hover:not(.disabled):not(.as-link), .button:hover:not(.disabled):not(.as-link) { + +button.primary:hover:not(.disabled), .button.primary:hover:not(.disabled) { + background-color: var(--button-primary-background-hover); + border: 2px solid var(--interactive-contrast) +} + +button:hover:not(.disabled):not(.as-link):not(.primary), .button:hover:not(.disabled):not(.as-link):not(.primary) { background-color: var(--low-interaction-background); } @@ -232,13 +238,10 @@ button:focus, .button:focus { button.primary, .button.primary { color: var(--button-foreground); - background-color: var(--button-background); - border-color: var(--button-border-color); + background-color: var(--button-background-primary); + border: 2px solid var(--button-background-primary) } -button.primary:hover:not(.disabled), .button.primary:hover:not(.disabled) { - background-color: var(--button-primary-background-hover); -} button.disabled { border-color: var(--disabled-font); @@ -293,7 +296,7 @@ button.unstyled, .button-unstyled button { /******* Other input elements ******/ .hover-alert:hover { - color: var(--catch-detail-color-contrast) + color: var(--interactive-contrast) } .links-w-full a:not(.weblate-link), .links-w-full button.as-link { @@ -317,7 +320,7 @@ select { } select:hover { - border-color: var(--catch-detail-color-contrast); + border-color: var(--interactive-contrast); } .neutral-label { @@ -420,17 +423,6 @@ h2.group { background-color: var(--interactive-background); } -.information { - /* The class to convey important information which does _not_ denote an error... */ - background-color: var(--low-interaction-background); - color: var(--alert-foreground-color); - border-radius: 1em; - margin: 0.25em; - text-align: center; - padding: 0.15em 0.3em; - border: 3px dotted var(--catch-detail-color-contrast); -} - .low-interaction .interactive { background-color: var(--interactive-background); } From 877dd260aed3a21ffbb1f638b07e35277df835b6 Mon Sep 17 00:00:00 2001 From: Robin van der Linde Date: Fri, 22 Aug 2025 22:18:54 +0200 Subject: [PATCH 23/92] Themes(shops): Remove brand tags if marked as without brand --- assets/layers/shops/shops.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/assets/layers/shops/shops.json b/assets/layers/shops/shops.json index 5b562d9be..4835174d6 100644 --- a/assets/layers/shops/shops.json +++ b/assets/layers/shops/shops.json @@ -486,7 +486,12 @@ "es": "Esta tienda no tiene una marca específica, no forma parte de una cadena más grande", "it": "Questo negozio non ha un marchio specifico, non fa parte di una catena più grande", "uk": "Цей магазин не має певного бренду, він не є частиною великої мережі" - } + }, + "addExtraTags": [ + "brand=", + "brand:wikidata=", + "brand:wikipedia=" + ] } ] }, From 06ac28dab98777ba91c06ec46800f08c52d14bdc Mon Sep 17 00:00:00 2001 From: Robin van der Linde Date: Fri, 22 Aug 2025 22:25:25 +0200 Subject: [PATCH 24/92] Themes(shops): Exlcude shop=no --- assets/layers/shops/shops.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/assets/layers/shops/shops.json b/assets/layers/shops/shops.json index 4835174d6..a8c63e6ef 100644 --- a/assets/layers/shops/shops.json +++ b/assets/layers/shops/shops.json @@ -43,7 +43,8 @@ "craft=key_cutter" ] }, - "shop!=mall" + "shop!=mall", + "shop!=no" ] } }, From 17f097851aafeb4db9c8cc0590abde9efe8bf599 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sat, 23 Aug 2025 13:36:42 +0200 Subject: [PATCH 25/92] Fix: panoramax attribution now filters out empty strings (for some edge cases on non-mapcomplete panoramax servers) --- src/Logic/ImageProviders/Panoramax.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Logic/ImageProviders/Panoramax.ts b/src/Logic/ImageProviders/Panoramax.ts index a1bea5f5a..362a4ab25 100644 --- a/src/Logic/ImageProviders/Panoramax.ts +++ b/src/Logic/ImageProviders/Panoramax.ts @@ -11,6 +11,9 @@ import { Feature, Point } from "geojson" import { AddImageOptions } from "panoramax-js/dist/Panoramax" import { ServerSourceInfo } from "../../Models/SourceOverview" import { ComponentType } from "svelte/types/runtime/internal/dev" +import { Strings } from "../../Utils/Strings" +import { Utils } from "../../Utils" +import { Lists } from "../../Utils/Lists" export default class PanoramaxImageProvider extends ImageProvider { public static readonly singleton: PanoramaxImageProvider = new PanoramaxImageProvider() @@ -194,9 +197,13 @@ export default class PanoramaxImageProvider extends ImageProvider { public async DownloadAttribution(providedImage: { id: string }): Promise { const meta = await this.getInfoFor(providedImage.id) + const artists = Lists.noEmpty(meta.data.providers.map(p => p.name)) + + // We take the last provider, as that one probably contain the username of the uploader + const artist = artists.at(-1) return { - artist: meta.data.providers.at(-1).name, // We take the last provider, as that one probably contain the username of the uploader + artist, date: new Date(meta.data.properties["datetime"]), licenseShortName: meta.data.properties["geovisio:license"], } From a570e292423785dd7532feb28facc46e4ade5e6d Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sat, 23 Aug 2025 14:08:04 +0200 Subject: [PATCH 26/92] Fix: fix crash --- src/UI/Map/PointRenderingLayer.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/UI/Map/PointRenderingLayer.ts b/src/UI/Map/PointRenderingLayer.ts index a0153389a..da2fdac74 100644 --- a/src/UI/Map/PointRenderingLayer.ts +++ b/src/UI/Map/PointRenderingLayer.ts @@ -231,7 +231,8 @@ export class PointRenderingLayer { let store: Store> if (this._fetchStore) { store = this._fetchStore(feature.properties.id) - } else { + } + if(!store){ store = new ImmutableStore(feature.properties) } const { html, iconAnchor } = this._config.RenderIcon(store, { metatags: this._metatags }) From 6ef78f66391f5b831acb3672de70b1e5c28ce37d Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sat, 23 Aug 2025 14:32:53 +0200 Subject: [PATCH 27/92] UI: fix 'clear search' icon that seemingly dissappeared --- src/UI/Base/Searchbar.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/UI/Base/Searchbar.svelte b/src/UI/Base/Searchbar.svelte index bc0e8efc8..25879b7dc 100644 --- a/src/UI/Base/Searchbar.svelte +++ b/src/UI/Base/Searchbar.svelte @@ -76,7 +76,7 @@ value.set("") e.preventDefault() }} - color="var(--button-background)" + color="var(--foreground-color)" class="mr-3 h-6 w-6 cursor-pointer" /> {:else} From 2a3e164669d780b3a97bb860a1137070100b01a2 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sat, 23 Aug 2025 17:44:40 +0200 Subject: [PATCH 28/92] Refactoring: switch to TagHint, remove TagExplanation.svelte --- src/UI/Popup/AddNewPoint/CreateCopy.svelte | 5 ++--- src/UI/Popup/TagExplanation.svelte | 15 --------------- .../SpecialVisualisations/TagApplyButton.svelte | 4 ++-- 3 files changed, 4 insertions(+), 20 deletions(-) delete mode 100644 src/UI/Popup/TagExplanation.svelte diff --git a/src/UI/Popup/AddNewPoint/CreateCopy.svelte b/src/UI/Popup/AddNewPoint/CreateCopy.svelte index ab5e4bcff..f24bff47d 100644 --- a/src/UI/Popup/AddNewPoint/CreateCopy.svelte +++ b/src/UI/Popup/AddNewPoint/CreateCopy.svelte @@ -17,8 +17,6 @@ import OpenBackgroundSelectorButton from "../../BigComponents/OpenBackgroundSelectorButton.svelte" import Tr from "../../Base/Tr.svelte" import ThemeViewState from "../../../Models/ThemeViewState" - import TagExplanation from "../TagExplanation.svelte" - import { And } from "../../../Logic/Tags/And" import Loading from "../../Base/Loading.svelte" import CreateNewNodeAction from "../../../Logic/Osm/Actions/CreateNewNodeAction" import DocumentDuplicate from "@babeard/svelte-heroicons/solid/DocumentDuplicate" @@ -26,6 +24,7 @@ import { EyeOffIcon } from "@rgossiaux/svelte-heroicons/solid" import Layers from "../../../assets/svg/Layers.svelte" import { onDestroy } from "svelte" + import TagHint from "../TagHint.svelte" export let state: ThemeViewState export let layer: LayerConfig @@ -180,7 +179,7 @@
{#if showTags}
- +
{/if}
diff --git a/src/UI/Popup/TagExplanation.svelte b/src/UI/Popup/TagExplanation.svelte deleted file mode 100644 index bcacc0447..000000000 --- a/src/UI/Popup/TagExplanation.svelte +++ /dev/null @@ -1,15 +0,0 @@ - - -{#if tagsFilter !== undefined} - -{/if} diff --git a/src/UI/SpecialVisualisations/TagApplyButton.svelte b/src/UI/SpecialVisualisations/TagApplyButton.svelte index 350284f8b..8c969fbb0 100644 --- a/src/UI/SpecialVisualisations/TagApplyButton.svelte +++ b/src/UI/SpecialVisualisations/TagApplyButton.svelte @@ -7,9 +7,9 @@ import Loading from "../Base/Loading.svelte" import TagApplyViz from "./TagApplyViz" import Icon from "../Map/Icon.svelte" - import TagExplanation from "../Popup/TagExplanation.svelte" import { And } from "../../Logic/Tags/And" import Translations from "../i18n/Translations" + import TagHint from "../Popup/TagHint.svelte" /** * Works closely together with 'TagApplyViz @@ -53,7 +53,7 @@ })} /> {:else} - + {/if}
From fe31af4b15446fc7787ba3e2ecc677c46d6c8de2 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sat, 23 Aug 2025 17:46:07 +0200 Subject: [PATCH 29/92] Feature: include (all) sign languages in the 'LanguageElement' special rendering --- scripts/fetchLanguages.ts | 41 +- .../LanguageElement/LanguageElement.svelte | 2 +- .../Popup/LanguageElement/LanguageElement.ts | 2 +- .../LanguageElement/LanguageOptions.svelte | 2 +- .../LanguageElement/LanguageQuestion.svelte | 11 +- src/Utils/WikidataUtils.ts | 6 +- src/assets/language_native.json | 2195 ++++++++++++++++- 7 files changed, 2251 insertions(+), 8 deletions(-) diff --git a/scripts/fetchLanguages.ts b/scripts/fetchLanguages.ts index 8f58b3805..2b001df70 100644 --- a/scripts/fetchLanguages.ts +++ b/scripts/fetchLanguages.ts @@ -18,7 +18,7 @@ interface value { } interface LanguageSpecResult { - directionalityLabel: value + directionalityLabel?: value lang: value code: value label: value @@ -77,6 +77,29 @@ async function fetchRegularLanguages() { return result.results.bindings } +async function fetchSignLanguages() { + const query = ` + + SELECT ?lang ?label ?code +WHERE +{ + ?lang wdt:P31 wd:Q34228. + OPTIONAL { + ?lang wdt:P1813 ?code. + } + ?lang rdfs:label ?label. + SERVICE wikibase:label { bd:serviceParam wikibase:language "en". } +}` + const url = Wikidata.wds.sparqlQuery(query) + + // request the generated URL with your favorite HTTP request library + const result = await Utils.downloadJson<{ results: { bindings: any[] } }>(url, { + "User-Agent": "MapComplete script", + }) + return result.results.bindings + +} + /** * Fetches the object as is. Sets a 'code' binding as predifined value * @param id @@ -169,7 +192,20 @@ async function getOfficialLanguagesPerCountryCached( return officialLanguages } +async function generateSignLanguageOverview(){ + const signLanguages = await fetchSignLanguages() + const signPerId = WikidataUtils.extractLanguageData(signLanguages, WikidataUtils.languageRemapping) + const asRecord : Record> = {} + for (const lng of signPerId.keys()) { + asRecord[lng.toLowerCase()] = Utils.MapToObj(signPerId.get(lng).translations) + } + return asRecord + +} + async function main(wipeCache = false) { + const signLanguages = await generateSignLanguageOverview() + const cacheFile = "./src/assets/generated/languages-wd.json" if (wipeCache || !existsSync(cacheFile)) { console.log("Refreshing cache") @@ -181,7 +217,8 @@ async function main(wipeCache = false) { const data = JSON.parse(readFileSync(cacheFile, { encoding: "utf8" })) const perId = WikidataUtils.extractLanguageData(data, WikidataUtils.languageRemapping) const nativeList = getNativeList(perId) - writeFileSync("./src/assets/language_native.json", JSON.stringify(nativeList, null, " ")) + writeFileSync("./src/assets/language_native.json", JSON.stringify({ ...nativeList, ...signLanguages }, null, " ")) + const languagesPerCountry = Utils.TransposeMap( await getOfficialLanguagesPerCountryCached(wipeCache) ) diff --git a/src/UI/Popup/LanguageElement/LanguageElement.svelte b/src/UI/Popup/LanguageElement/LanguageElement.svelte index a013e245e..745ce5f45 100644 --- a/src/UI/Popup/LanguageElement/LanguageElement.svelte +++ b/src/UI/Popup/LanguageElement/LanguageElement.svelte @@ -42,7 +42,7 @@ {#if $foundLanguages.length === 0 && on_no_known_languages && !$forceInputMode}
-
+
{on_no_known_languages}
forceInputMode.setData(true)} /> diff --git a/src/UI/Popup/LanguageElement/LanguageElement.ts b/src/UI/Popup/LanguageElement/LanguageElement.ts index f1cc0a870..8b7156d4b 100644 --- a/src/UI/Popup/LanguageElement/LanguageElement.ts +++ b/src/UI/Popup/LanguageElement/LanguageElement.ts @@ -7,7 +7,7 @@ export class LanguageElement extends SpecialVisualizationSvelte { needsUrls = [] docs: string = - "The language element allows to show and pick all known (modern) languages. The key can be set" + "The language element allows to show and pick all known (modern) languages (includes sign languages). The key can be set" args: { name: string diff --git a/src/UI/Popup/LanguageElement/LanguageOptions.svelte b/src/UI/Popup/LanguageElement/LanguageOptions.svelte index bfabc2b37..48724b51c 100644 --- a/src/UI/Popup/LanguageElement/LanguageOptions.svelte +++ b/src/UI/Popup/LanguageElement/LanguageOptions.svelte @@ -106,7 +106,7 @@ -
+
{#each knownLanguagecodes as lng} {#if isChecked[lng] && $newlyChecked.indexOf(lng) < 0 && probableLanguages.indexOf(lng) < 0}
diff --git a/src/Utils/WikidataUtils.ts b/src/Utils/WikidataUtils.ts index 9b0f2e25e..ff13066c6 100644 --- a/src/Utils/WikidataUtils.ts +++ b/src/Utils/WikidataUtils.ts @@ -30,7 +30,11 @@ export default class WikidataUtils { { translations: Map; directionality?: string[] } >() for (const element of data) { - let id = element.code.value + let id = element.code?.value + if(!id){ + console.warn("No language code for", JSON.stringify(element),"ignoring") + continue + } id = remapLanguages[id] ?? id let labelLang = element.label["xml:lang"] labelLang = remapLanguages[labelLang] ?? labelLang diff --git a/src/assets/language_native.json b/src/assets/language_native.json index 096f8ac6c..033f6d018 100644 --- a/src/assets/language_native.json +++ b/src/assets/language_native.json @@ -30,5 +30,2198 @@ "sv": "svenska", "uk": "українська мова", "zh_Hans": "简体中文", - "zh_Hant": "繁體中文" + "zh_Hant": "繁體中文", + "ржя": { + "af": "Russiese Gebaretaal", + "az": "rus jest dili", + "bn": "রুশ ইশারা ভাষা", + "ca": "llengua de signes russa", + "de": "russische Gebärdensprache", + "en": "Russian Sign Language", + "en-ca": "Russian Sign Language", + "en-gb": "Russian Sign Language", + "eo": "rusa signolingvo", + "es": "lengua de señas de Rusia", + "eu": "Errusiako keinu hizkuntza", + "fa": "زبان اشاره روسی", + "fi": "venäläinen viittomakieli", + "fr": "langue des signes russe", + "ga": "teanga chomharthaíochta na Rúise", + "he": "שפת הסימנים הרוסית", + "hi": "रूसी सांकेतिक भाषा", + "hy": "ռուսերեն ժեստերի լեզու", + "id": "Bahasa Isyarat Rusia", + "is": "rússneskt táknmál", + "it": "lingua dei segni russa", + "ja": "ロシア手話", + "lfn": "lingua de sinia rusce", + "lmo": "Lèngua d'i sègn rüssa", + "lv": "Krievu zīmju valoda", + "nap": "Lenguaggio d' 'e signe russa", + "nb_NO": "russisk tegnspråk", + "nl": "Russische gebarentaal", + "pa": "ਰੂਸ ਦੀ ਚਿੰਨ੍ਹ ਭਾਸ਼ਾ", + "pms": "Lenga dij segn d'El Salvador", + "pt": "Língua gestual russa", + "pt_BR": "Língua gestual russa", + "rm": "Lingua da segns russa", + "ru": "русский жестовый язык", + "sc": "Limba de sos Sinnos Russa", + "scn": "Lingua dî Signa Russa", + "sl": "ruski znakovni jezik", + "sv": "ryskt teckenspråk", + "tok": "toki luka Losi", + "tr": "Rus İşaret Dili", + "tt": "рус ишарә теле", + "uk": "російська жестова мова", + "vec": "łéngua dei segni rusa" + }, + "isl": { + "bn": "ভারতীয় ইশারা ভাষা", + "br": "yezh ar sinoù iwerzhonek", + "de": "Irische Gebärdensprache", + "en": "Indian Sign Language", + "en-gb": "Irish Sign Language", + "eo": "barata signolingvo", + "es": "lengua de señas india", + "eu": "Irlandako keinu hizkuntza", + "fa": "زبان اشاره ایرلندی", + "fr": "langue des signes indienne", + "ga": "teanga chomharthaíochta na hIndia", + "he": "שפת הסימנים ההודית", + "hi": "भारतीय सांकेतिक भाषा", + "hif": "Indian Sign Language", + "hy": "Իռլանդական ժեստերի լեզու", + "is": "írskt táknmál", + "it": "lingua dei segni indiana", + "ja": "インド手話", + "lfn": "lingua de sinia barati", + "nl": "Indiase Gebarentaal", + "pa": "ਭਾਰਤੀ ਇਸ਼ਾਰਾ", + "pms": "lenga dij segn indian-a", + "pnb": "بھارتی اشارہ", + "pt": "língua de sinais indiana", + "rm": "lingua da segns irlandaisa", + "ru": "ирландский жестовый язык", + "scn": "Lingua dî Signa Irlannisi", + "sl": "indijski znakovni jezik", + "sv": "Indiskt teckenspråk", + "tok": "toki luka Alan", + "tr": "Hint İşaret Dili", + "tt": "Ирландия ишарә теле", + "ur": "بھارتیہ سانکیتک بھاشا", + "vec": "łéngua dei segni indiana", + "vi": "Irish Sign Language", + "zh": "印度手语" + }, + "lsq": { + "bn": "কুইবেক ইশারা ভাষা", + "br": "yezh ar sinoù kwebekek", + "ca": "llengua de signes quebequesa", + "cy": "Iaith Arwyddo Québec", + "de": "Frankokanadische Gebärdensprache", + "eml": "Lèngva di sègn quebechéśa", + "en": "Quebec Sign Language", + "en-ca": "Quebec Sign Language", + "eo": "kebekia signolingvo", + "es": "lengua de señas quebequesa", + "fa": "زبان اشاره کبکی", + "fr": "langue des signes québécoise", + "ga": "teanga chomharthaíochta Québec", + "gl": "Lingua de signos quebechesa", + "ha": "Harshen Kurame na Quebec", + "hi": "क्यूबेक साइन लैंग्वेज", + "hif": "Quebec Sign Language", + "it": "lingua dei segni quebechese", + "ja": "ケベック手話", + "la": "lingua gesticulatoria Quebecensis", + "lfn": "lingua de sinia quebecan", + "pms": "Lenga dij segn dël Québec", + "pt": "língua de sinais quebequiana", + "pt_BR": "Língua de Sinais Quebequiana", + "ru": "квебекский жестовый язык", + "sc": "Limba de sos Sinnos Quebechesa", + "scn": "Lingua dî Signa Quebechisa", + "sco": "Quebecer Sign Leid", + "sl": "quebeški znakovni jezik", + "tr": "Québec İşaret Dili", + "vec": "Lengua de i segni quebechexe", + "yue": "魁北克手語" + }, + "rsl": { + "af": "Russiese Gebaretaal", + "az": "rus jest dili", + "bn": "রুশ ইশারা ভাষা", + "ca": "llengua de signes russa", + "de": "russische Gebärdensprache", + "en": "Russian Sign Language", + "en-ca": "Russian Sign Language", + "en-gb": "Russian Sign Language", + "eo": "rusa signolingvo", + "es": "lengua de señas de Rusia", + "eu": "Errusiako keinu hizkuntza", + "fa": "زبان اشاره روسی", + "fi": "venäläinen viittomakieli", + "fr": "langue des signes russe", + "ga": "teanga chomharthaíochta na Rúise", + "he": "שפת הסימנים הרוסית", + "hi": "रूसी सांकेतिक भाषा", + "hy": "ռուսերեն ժեստերի լեզու", + "id": "Bahasa Isyarat Rusia", + "is": "rússneskt táknmál", + "it": "lingua dei segni russa", + "ja": "ロシア手話", + "lfn": "lingua de sinia rusce", + "lmo": "Lèngua d'i sègn rüssa", + "lv": "Krievu zīmju valoda", + "nap": "Lenguaggio d' 'e signe russa", + "nb_NO": "russisk tegnspråk", + "nl": "Russische gebarentaal", + "pa": "ਰੂਸ ਦੀ ਚਿੰਨ੍ਹ ਭਾਸ਼ਾ", + "pms": "Lenga dij segn d'El Salvador", + "pt": "Língua gestual russa", + "pt_BR": "Língua gestual russa", + "rm": "Lingua da segns russa", + "ru": "русский жестовый язык", + "sc": "Limba de sos Sinnos Russa", + "scn": "Lingua dî Signa Russa", + "sl": "ruski znakovni jezik", + "sv": "ryskt teckenspråk", + "tok": "toki luka Losi", + "tr": "Rus İşaret Dili", + "tt": "рус ишарә теле", + "uk": "російська жестова мова", + "vec": "łéngua dei segni rusa" + }, + "asl": { + "ar": "لغة الإشارة الأمريكية", + "ase": "𝣷𝪜 𝤃𝪜 𝣜𝪜", + "ast": "llingua de señes americana", + "azb": "آمریکا ایشاره دیلی", + "be": "амслен", + "be-tarask": "амэрыканская жэставая мова", + "bn": "মার্কিন ইশারা ভাষা", + "br": "yezh ar sinoù amerikanek", + "ca": "llengua de signes americana", + "ckb": "زمانی نیشانەی ئەمریکی", + "co": "lingua di i cenni americana", + "cs": "americký znakový jazyk", + "cv": "Амслен", + "cy": "Iaith Arwyddo Americanaidd", + "da": "ASL", + "de": "American Sign Language", + "el": "Νοηματική Γλώσσα ΗΠΑ", + "eml": "lèngva di sègn americàna", + "en": "American Sign Language", + "en-ca": "American Sign Language", + "en-us": "American Sign Language", + "eo": "usona signolingvo", + "es": "lengua de señas estadounidense", + "eu": "amerikar keinu hizkuntza", + "fa": "زبان اشاره آمریکایی", + "fi": "amerikkalainen viittomakieli", + "fr": "langue des signes américaine", + "fur": "lenghe dai signse americane", + "ga": "Teanga Chomharthaíochta Mheiriceánach", + "gl": "lingua de signos americana", + "gv": "Çhengey Chowree Americaanagh", + "ha": "Harshen Kurame na Amurka", + "he": "שפת הסימנים האמריקאית", + "hi": "अमेरिकी सांकेतिक भाषा", + "hif": "American Sign Language", + "hr": "američki znakovni jezik", + "hu": "amerikai jelnyelv", + "hy": "ամերիկյան ժեստերի լեզու", + "id": "Bahasa Isyarat Amerika", + "is": "amerískt táknmál", + "it": "lingua dei segni americana", + "ja": "アメリカ手話", + "ko": "미국 수어", + "la": "lingua gesticulatoria Americana", + "lfn": "lingua de sinia american", + "lij": "Lengua di Segni American-a", + "lmo": "lèngua dei sègn americàna", + "lv": "Amerikāņu zīmju valoda", + "ms": "Abjad tangan Amerika Syarikat", + "mt": "lingwa tas-sinjali Amerikana", + "nap": "lenguaggio d' 'e signe mmerecana", + "nb_NO": "amerikansk tegnspråk", + "nl": "Amerikaanse Gebarentaal", + "pa": "ਅਮਰੀਕੀ ਚਿਹਨ ਭਾਸ਼ਾ", + "pl": "amerykański język migowy", + "pms": "lenga dij segn american-a", + "pt": "língua de sinais americana", + "pt_BR": "língua de sinais americana", + "rm": "lingua da segns americana", + "ro": "Limba Semnelor Americane", + "roa-tara": "lenghe de le signe americhene", + "ru": "амслен", + "sc": "limba de sos sinnos americana", + "scn": "Lingua dî Signa Miricana", + "sco": "American Sign Leid", + "sd": "ايَ ايس ايل", + "se": "amerihkalaš seavagiella", + "sl": "ameriški znakovni jezik", + "smn": "ameriklâš seevvimkielâ", + "sv": "amerikanskt teckenspråk", + "th": "ภาษามืออเมริกัน", + "tok": "toki luka Mewika", + "tr": "Amerikan İşaret Dili", + "uk": "Американська жестова мова", + "ur": "امریکی اشاراتی زبان", + "vec": "łéngua dei segni americana", + "vi": "Ngôn ngữ ký hiệu Mỹ", + "war": "Linggwahe hin Sinyales Amerikana", + "wuu": "美国手语", + "yue": "美國手語", + "zh": "美國手語", + "zh-tw": "美國手語", + "zh_Hant": "美國手語" + }, + "ksl": { + "anp": "कोरियाई सांकेतिक भाषा", + "az": "koreya işarə dili", + "bn": "কোরীয় ইশারা ভাষা", + "ca": "llengua de signes coreana", + "cy": "Iaith Arwyddo Coreeg", + "de": "Koreanische Gebärdensprache", + "en": "Kazakh-Russian Sign Language", + "en-gb": "Korean Sign Language", + "eo": "korea signolingvo", + "es": "lengua de señas coreana", + "eu": "korear keinu hizkuntza", + "fa": "زبان اشاره کرهای", + "fi": "korealainen viittomakieli", + "fr": "langue des signes kazakho-russe", + "ga": "teanga chomharthaíochta Chasacach-Rúiseach", + "gl": "lingua de sinais coreana", + "he": "שפת הסימנים הקוריאנית", + "hi": "कोरीआई इशारों की ज़ुबान", + "hy": "կորեերեն ժեստերի լեզու", + "id": "Bahasa Isyarat Korea", + "ig": "Asụsụ Ogbi nke Kazakh", + "it": "lingua dei segni coreana", + "ja": "韓国手話", + "kk-cyrl": "Қазақ-орыс ымдау тілі", + "ko": "한국 수어", + "la": "lingua gesticulatoria Coreana", + "lfn": "lingua de sinia hangugo", + "nl": "koreaanse gebarentaal", + "pa": "ਕੋਰੀਆਈ ਸੈਨਤ ਬੋਲੀ", + "pl": "Koreański język migowy", + "pms": "Lenga dij segn corean-a", + "pnb": "کوریائی سینت بولی", + "pt": "língua de sinais coreana", + "ru": "корейский жестовый язык", + "scn": "Lingua dî Signa Corèana", + "sd": "ڪوريائي سينت ٻولي", + "sk": "Kórejský posunkový jazyk", + "tok": "toki luka Anku", + "tr": "Kore İşaret Dili", + "tt": "кореяле ишарә теле", + "ur": "کوریائی اشاروں کی زبان", + "vi": "Ngôn ngữ ký hiệu Hàn Quốc", + "zh": "і哈萨克-俄罗斯手语", + "zh-cn": "韩国手语", + "zh_Hans": "韩文手语", + "zh_Hant": "韓國手語" + }, + "ai̇d": { + "az": "Azərbaycan işarət dili", + "bn": "আজারবাইজানি ইশারা ভাষা", + "bs": "Azerbejdžanski znakovni jezik", + "ca": "Llengua de signes àzeri", + "en": "Azerbaijani Sign Language", + "es": "Lengua de signos azerbaiyana", + "fa": "زبان اشاره آذربایجانی", + "fi": "azerbaidžanilainen viittomakieli", + "fr": "langue des signes azérie", + "ga": "teanga chomharthaíochta na hAsarbaiseáine", + "he": "שפת הסימנים האזרבייג'נית", + "hif": "Azerbaijani Sign bhasa", + "hr": "Azerski znakovni jezik", + "ig": "Asụsụ Ogbi nke Azerbaijani", + "ja": "アゼルバイジャン手話", + "lij": "lengua di segni azera", + "lmo": "Lèngua d'i sègn azera", + "nap": "Lenguaggio d' 'e signe azera", + "nb_NO": "Aserbajdsjansk tegnspråk", + "pms": "Lenga dij segn aser-a", + "pt": "Língua gestual azerbaijana", + "roa-tara": "Lenghe de le signe azere", + "ru": "азербайджанский жестовый язык", + "scn": "Lingua dî Signa Azzira", + "sh": "Azerski znakovni jezik", + "sr": "Азерски знаковни језик", + "tok": "toki luka Asepajan", + "tr": "Azerbaycan İşaret Dili", + "vec": "Łéngoa dei ségni azera" + }, + "azsl": { + "az": "Azərbaycan işarət dili", + "bn": "আজারবাইজানি ইশারা ভাষা", + "bs": "Azerbejdžanski znakovni jezik", + "ca": "Llengua de signes àzeri", + "en": "Azerbaijani Sign Language", + "es": "Lengua de signos azerbaiyana", + "fa": "زبان اشاره آذربایجانی", + "fi": "azerbaidžanilainen viittomakieli", + "fr": "langue des signes azérie", + "ga": "teanga chomharthaíochta na hAsarbaiseáine", + "he": "שפת הסימנים האזרבייג'נית", + "hif": "Azerbaijani Sign bhasa", + "hr": "Azerski znakovni jezik", + "ig": "Asụsụ Ogbi nke Azerbaijani", + "ja": "アゼルバイジャン手話", + "lij": "lengua di segni azera", + "lmo": "Lèngua d'i sègn azera", + "nap": "Lenguaggio d' 'e signe azera", + "nb_NO": "Aserbajdsjansk tegnspråk", + "pms": "Lenga dij segn aser-a", + "pt": "Língua gestual azerbaijana", + "roa-tara": "Lenghe de le signe azere", + "ru": "азербайджанский жестовый язык", + "scn": "Lingua dî Signa Azzira", + "sh": "Azerski znakovni jezik", + "sr": "Азерски знаковни језик", + "tok": "toki luka Asepajan", + "tr": "Azerbaycan İşaret Dili", + "vec": "Łéngoa dei ségni azera" + }, + "bsl": { + "ar": "لغة الإشارة البريطانية", + "ast": "llingua de señes británica", + "bn": "ব্রিটিশ ইশারা ভাষা", + "br": "yezh ar sinoù breizhveurek", + "ca": "llengau de signes britànica", + "co": "lingua di i cenni brettagnica", + "cs": "britský znakový jazyk", + "cy": "Iaith Arwyddion Prydain", + "da": "brittisk tegnsprog", + "de": "Britische Gebärdensprache", + "el": "Βρετανική Νοηματική Γλώσσα", + "eml": "lèngva di sègn britànica", + "en": "British Sign Language", + "en-gb": "British Sign Language", + "eo": "brita signolingvo", + "es": "lengua de señas británica", + "fa": "زبان اشاره انگلیسی", + "fi": "brittiläinen viittomakieli", + "fr": "langue des signes britannique", + "fur": "lenghe dai signse britaniche", + "ga": "Teanga Comhartha na Breataine", + "he": "שפת הסימנים הבריטית", + "hi": "ब्रिटिश सांकेतिक भाषा", + "hr": "britanski znakovni jezik", + "id": "Bahasa Isyarat Inggris", + "ig": "British Sign Language", + "is": "breskt táknmál", + "it": "lingua dei segni britannica", + "ja": "イギリス手話", + "ko": "영국 수화", + "la": "lingua gesticulatoria Britannica", + "lfn": "lingua de sinia brites", + "lij": "Lengua di Segni Britannega", + "lmo": "lèngua dei sègn britànica", + "ms": "Bahasa Isyarat British", + "mt": "Lingwa tas-Sinjali Brittanika", + "nap": "lenguaggio d' 'e signe ngrese", + "nb_NO": "britisk tegnspråk", + "nl": "Britse gebarentaal", + "nn": "britisk teiknspråk", + "pa": "ਬਰਤਾਨਵੀ ਚਿਹਨ ਬੋਲੀ", + "pl": "brytyjski język migowy", + "pms": "lenga dij segn britànica", + "pnb": "برطانوی سائین بولی", + "pt": "língua de sinais britânica", + "rm": "lingua da segns britannica", + "roa-tara": "lenghe de le signe bretagniche", + "ru": "британский жестовый язык", + "sc": "Limba de sos Sinnos Britànnica", + "scn": "Lingua dî Signa Ngrisi", + "sco": "Breetish Sign Leid", + "se": "brihttalaš seavagiella", + "skr": "برطانوی سائین بولی", + "sl": "britanski znakovni jezik", + "smn": "brittilâš seevvimkielâ", + "sv": "brittiskt teckenspråk", + "te": "బ్రిటిష్ సంకేత భాష", + "tok": "toki luka Juke", + "tr": "İngiliz İşaret Dili", + "uk": "британська жестова мова", + "ur": "برطانوی اشاروں کی زبان", + "vec": "łéngua dei segni britànega", + "vi": "Ngôn ngữ ký hiệu Anh", + "war": "Linggwahe hin Sinyales Ininglisa", + "yue": "英國手語", + "zh": "英國手語", + "zh_Hans": "英国手语", + "zh_Hant": "英國手語" + }, + "lsm": { + "ar": "لغة الإشارة المكسيكية", + "ast": "llingua de señes mexicana", + "bn": "মেক্সিকান ইশারা ভাষা", + "br": "yezh ar sinoù maltek", + "de": "Mexikanische Gebärdensprache", + "el": "Μαλτεζική Νοηματική Γλώσσα", + "en": "Mexican Sign Language", + "en-gb": "Mexican Sign Language", + "eo": "meksika signolingvo", + "es": "lengua de señas mexicana", + "fa": "زبان اشاره مکزیکی", + "fr": "langue des signes mexicaine", + "fur": "lenghe dai segns maltese", + "ga": "teanga chomharthaíochta Mheicsiceo", + "he": "שפת הסימנים המקסיקנית", + "hi": "मैक्सिकन सांकेतिक भाषा", + "hif": "Mexican Sign Language", + "it": "lingua dei segni maltese", + "ja": "メキシコ手話", + "lfn": "lingua de sinia mexican", + "mt": "Lingwa tas-Sinjali Maltija", + "nb_NO": "meksikansk tegnspråk", + "nl": "mexicaanse gebarentaal", + "pms": "lenga dij segn messican-a", + "pt": "língua de sinais mexicana", + "rm": "lingua da segns maltaisa", + "ru": "мальтийский язык жестов", + "scn": "Lingua dî Signa Mìssicana", + "sco": "Maltese Sign Leid", + "sl": "mehiški znakovni jezik", + "tok": "toki luka Mesiko", + "tr": "Meksika İşaret Dili", + "tt": "мальталы ишарә теле", + "zh-hk": "馬耳他手語", + "zh-tw": "馬爾他手語", + "zh_Hans": "墨西哥手语", + "zh_Hant": "墨西哥手語" + }, + "lsf": { + "ar": "لغة الإشارة الفرنسية", + "ast": "llingua de señes francesa", + "be-tarask": "француская жэставая мова", + "bn": "ফরাসি ইশারা ভাষা", + "br": "yezh ar Sinoù Gallek", + "ca": "llengua de signes francesa", + "co": "Lingua di i cenni francese", + "cy": "Iaith Arwyddo Ffrangeg", + "da": "fransk tegnsprog", + "de": "Langue des signes française", + "eml": "Lèngva di sègn francéśa", + "en": "French Sign Language", + "en-gb": "French Sign Language", + "eo": "franca signolingvo", + "es": "lengua de señas francesa", + "eu": "frantziako keinu hizkuntza", + "fa": "زبان اشاره فرانسوی", + "fi": "ranskalainen viittomakieli", + "fr": "langue des signes française", + "fur": "Lenghe dai segns francese", + "ga": "teanga chomharthaíochta na Fraince", + "gl": "Lingua de signos francesa", + "ha": "Harshen Kurame na Faransanci", + "he": "שפת הסימנים הצרפתית", + "hi": "फ्रेंच साइन लैंग्वेज", + "hif": "French Sign bhasa", + "hy": "Ֆրանսիական ժեստերի լեզու", + "id": "Bahasa Isyarat Prancis", + "is": "Franskt táknmál", + "it": "lingua dei segni francese", + "ja": "フランス手話", + "ko": "프랑스 수화", + "la": "lingua gesticulatoria Gallica", + "lfn": "lingua de sinia franses", + "lij": "Lengua di Segni Françeise", + "lmo": "Lèngua dei sègn franséza", + "ms": "Bahasa Isyarat Perancis", + "ms-arab": "بهاس اشارت ڤرنچيس", + "mt": "lingwa tas-sinjali Franċiża", + "nap": "Lenguaggio d' 'e signe franzese", + "nl": "Franse gebarentaal", + "nn": "Fransk teiknspråk", + "oc": "Lenga dels signes francesa", + "pa": "ਫਰਾਂਸੀਸੀ ਚਿਹਨ ਭਾਸ਼ਾ", + "pl": "Francuski Język Migowy", + "pms": "Lenga dij segn fransèisa", + "pt": "língua de sinais francesa", + "rm": "Lingua da segns franzosa", + "ro": "Alfabetul manual francez", + "roa-tara": "Lenghe de le signe frangese", + "ru": "французский жестовый язык", + "sc": "Limba de sos Sinnos Frantzesa", + "scn": "Lingua dî Signa Francisi", + "sco": "French Sign Leid", + "sl": "francoski znakovni jezik", + "sr": "француски знаковни језик", + "sv": "franskt teckenspråk", + "te": "ఫ్రెంచ్ సంకేత భాష", + "tok": "toki luka Kanse", + "tr": "Fransız İşaret Dili", + "uk": "французька мова жестів", + "ur": "اشاراتی فرانسیسی زبان", + "vec": "Lengua de i segni fransexe", + "war": "Linggwahe hin Sinyales Frinanses", + "yue": "法國手語", + "zh": "法语手语", + "zh-cn": "法国手语", + "zh_Hans": "法国手语", + "zh_Hant": "法國手語" + }, + "bim": { + "bn": "মালয়েশীয় ইশারা ভাষা", + "de": "Malaiische Gebärdensprache", + "en": "Malaysian Sign Language", + "en-ca": "Malaysian Sign Language", + "en-gb": "Malaysian Sign Language", + "eo": "malajzia signolingvo", + "fa": "زبان اشاره مالزیایی", + "fr": "langue des signes malaisienne", + "ga": "teanga chomharthaíochta na Malaeisia", + "gl": "Lingua de sinais malaisia", + "hi": "मलेशियाई सांकेतिक भाषा", + "id": "Bahasa Isyarat Malaysia", + "ig": "Asụsụ Ogbi nke Malaysia", + "it": "lingua dei segni malesiana", + "ja": "マレーシア手話", + "lfn": "lingua de sinia malaisian", + "ms": "Bahasa Isyarat Malaysia", + "nl": "Maleise gebarentaal", + "pms": "Lenga dij segn malesian-a", + "pt": "Língua de sinais malasiana", + "pt_BR": "Língua de sinais malasiana", + "ru": "малайский жестовый язык", + "sl": "malezijski znakovni jezik", + "th": "ภาษาสัญลักษณ์มาเลเซีย", + "tok": "toki luka Malasija", + "tr": "Malezya İşaret Dili", + "tt": "малай ишарә теле", + "zh": "马来西亚手语" + }, + "mvsl": { + "de": "Maledivische Gebärdensprachedie", + "dv": "ދިވެހި އިޝާރާތް ބަހުގެ", + "en": "Maldives Sign Language", + "ga": "teanga chomharthaíochta Oileáin Mhaildíve", + "pl": "Malediwski język migowy", + "ru": "мальдивский язык жестов", + "so": "Luuqadda Calaamadaha ee Maldives", + "tr": "Maldivler İşaret Dili" + }, + "lsmy": { + "ast": "llingua de señes yucateca", + "bn": "মায়া ইশারা ভাষা", + "de": "Maya-Gebärdensprache", + "en": "Yucatec Maya Sign Language", + "en-gb": "Yucatec Maya Sign Language", + "es": "lengua de señas maya yucateca", + "fa": "زبان اشاره مایایی", + "fi": "jukatekin viittomakieli", + "fr": "langue des signes maya yucatèque", + "ga": "teanga chomharthaíochta Mháigheach", + "ha": "Harshen Kurame na Maya", + "hi": "मय साइन लैंग्वेज", + "ig": "Yucatec Maya Sign Language", + "ja": "ユカテク・マヤ手話", + "pms": "lenga dij segn yucatec maya", + "pt": "línguas gestuais maias", + "sl": "majevski znakovni jezik", + "tr": "Yucatec Maya İşaret Dili" + }, + "lse": { + "ar": "لغة إشارة الإسبانية", + "ast": "llingua de señes española", + "bn": "স্পেনীয় ইশারা ভাষা", + "br": "yezh ar sinoù spagnolek", + "ca": "llengua de signes espanyola", + "co": "lingua di i cenni spagnola", + "de": "Spanische Gebärdensprache", + "en": "Spanish Sign Language", + "en-gb": "Spanish Sign Language", + "eo": "hispana signolingvo", + "es": "lengua de signos española", + "eu": "Espainiako keinu hizkuntza", + "fa": "زبان اشاره اسپانیایی", + "fi": "espanjalainen viittomakieli", + "fr": "langue des signes espagnole", + "ga": "teanga chomharthaíochta na Spáinne", + "gl": "Lingua de sinais española", + "he": "שפת הסימנים הספרדית", + "hi": "स्पेनिश सांकेतिक भाषा", + "hif": "Spanish Sign Language", + "id": "Bahasa Isyarat Spanyol", + "it": "lingua dei segni spagnola", + "ja": "スペイン手話", + "ko": "스페인어 수화", + "ku": "zimanê hêmayan ê spanî", + "ku-latn": "zimanê hêmayan ê spanî", + "la": "lingua gesticulatoria Hispanica", + "lfn": "lingua de sinia espaniol", + "nap": "Lenguaggio d' 'e signe spagnuola", + "nl": "Spaanse gebarentaal", + "oc": "lenga dels signes espanhòla", + "pms": "Lenga dij segn spagneula", + "sc": "limba de sos sinnos ispagnola", + "scn": "Lingua dî Signa Spagnula", + "sco": "Spainish Sign Leid", + "sl": "španski znakovni jezik", + "sw": "Lugha ya ishara ya Uhispania", + "tl": "Wikang pasenyas ng mga Kastila", + "tok": "toki luka Epanja", + "tr": "İspanyol İşaret Dili", + "tt": "испан ишарә теле", + "vec": "łéngua dei segni spagnola", + "war": "Linggwahe hin Sinyales Kinatsila", + "zh_Hans": "西班牙手语", + "zh_Hant": "西班牙手語" + }, + "lsu": { + "ast": "llingua de señes uruguaya", + "ca": "llengua de signes uruguaiana", + "de": "Uruguayische Gebärdensprache", + "en": "Uruguayan Sign Language", + "en-gb": "Uruguayan Sign Language", + "eo": "urugvaja signolingvo", + "es": "lengua de señas uruguaya", + "fa": "زبان اشاره اوروگوئهای", + "fi": "uruguaylainen viittomakieli", + "fr": "langue des signes uruguayenne", + "ga": "teanga chomharthaíochta Uragua", + "ja": "ウルグアイ手話", + "lfn": "lingua de sinia uruguaia", + "pms": "lenga dij segn uruguaian-a", + "pt": "Língua de Sinais do Uruguai", + "scn": "Lingua dî Signa Uruguai", + "sl": "urugvajski znakovni jezik", + "tok": "toki luka Ulukawi", + "tr": "Uruguay İşaret Dili", + "tt": "уругвайлы ишарә теле" + }, + "tsl": { + "bn": "তানজানীয় সাংকেতিক ভাষাসমূহ", + "ca": "llengua de signes taiwanesa", + "cs": "tchajwanský znakový jazyk", + "de": "Thailändische Gebärdensprache", + "en": "Thai Sign Language", + "en-gb": "Thai Sign Language", + "eo": "taja signolingvo", + "es": "lengua de señas taiwanesa", + "fa": "زبان اشاره تایلندی", + "fi": "taiwanilainen viittomakieli", + "fr": "langue des signes tanzanienne", + "ga": "teanga chomharthaíochta na Téalainne", + "he": "שפת הסימנים התאילנדית", + "hi": "थाई सांकेतिक भाषा", + "hif": "Thae Sign Language", + "id": "Bahasa Isyarat Thai", + "ig": "Asụsụ Ndị Ogbi Tanzania", + "ja": "タンザニア手話", + "ko": "대만 수화", + "lfn": "lingua de sinia tanzanian", + "lmo": "Lèngua d'i sègn d'la Thailandia", + "mt": "Lingwa tas-Sinjali Tajlandiża", + "nan": "Tâi-oân Chhiú-gí", + "nl": "Tanziaanse gebarentaal", + "oc": "lenga dels signes taiwanesa", + "pms": "Lenga dij segn tanzanian-a", + "pt": "Língua de Sinais da Tanzânia", + "ru": "тайваньский жестовый язык", + "sc": "Limba de sos Sinnos Thailandesa", + "scn": "Lingua dî Signa Tanzania", + "sk": "Taiwanský posunkový jazyk", + "sw": "Lugha ya Alama Tanzania", + "th": "ภาษามือไทย", + "tok": "toki luka Tansanija", + "tr": "Tanzanya İşaret Dili", + "tt": "Танзания ишарә теле", + "vi": "Ngôn ngữ ký hiệu Đài Loan", + "yue": "臺灣手語", + "zh": "泰國手語", + "zh-tw": "臺灣手語", + "zh_Hans": "台湾手语", + "zh_Hant": "臺灣手語" + }, + "ymsl": { + "ast": "llingua de señes yucateca", + "bn": "মায়া ইশারা ভাষা", + "de": "Maya-Gebärdensprache", + "en": "Yucatec Maya Sign Language", + "en-gb": "Yucatec Maya Sign Language", + "es": "lengua de señas maya yucateca", + "fa": "زبان اشاره مایایی", + "fi": "jukatekin viittomakieli", + "fr": "langue des signes maya yucatèque", + "ga": "teanga chomharthaíochta Mháigheach", + "ha": "Harshen Kurame na Maya", + "hi": "मय साइन लैंग्वेज", + "ig": "Yucatec Maya Sign Language", + "ja": "ユカテク・マヤ手話", + "pms": "lenga dij segn yucatec maya", + "pt": "línguas gestuais maias", + "sl": "majevski znakovni jezik", + "tr": "Yucatec Maya İşaret Dili" + }, + "is": { + "ar": "الإشارة الدولية", + "ast": "llingua internacional de señes", + "bg": "Жестуно", + "bn": "আন্তর্জাতিক ইশারা ভাষা", + "cs": "Mezinárodní znakový systém", + "de": "International Sign", + "en": "International Sign", + "en-gb": "International Sign", + "eo": "Gestuno", + "es": "lengua internacional de signos", + "eu": "gestuno", + "fa": "زبان اشاره بینالمللی", + "fi": "gestuno", + "fr": "langue des signes internationale", + "ga": "an Teanga Chomharthaíochta Idirnáisiúnta", + "he": "שפת הסימנים הבינלאומית", + "hu": "gestuno", + "id": "Isyarat Internasional", + "ig": "Ihe ịrịba ama mba ụwa", + "it": "lingua dei segni internazionale", + "ja": "国際手話", + "mt": "lingwa tas-sinjali internazzjonali", + "nap": "Lenguaggio d\"e signe nternazziunale", + "nl": "Gestuno", + "nov": "Gestuno", + "pl": "język gestuno", + "pt": "Gestuno", + "ru": "джестуно", + "scn": "Lingua dî Signa Ntinnazziunali", + "sl": "mednarodni znakovni jezik", + "tok": "toki luka pi ma ale", + "uk": "міжнародний жест", + "vec": "łéngua dei segni internasionałe", + "zh": "國際手語" + }, + "dgs": { + "ar": "لغة الإشارة الألمانية", + "bn": "জার্মান ইশারা ভাষা", + "br": "yezh ar sinoù alamanek", + "ca": "llengua de signes alemanya", + "co": "Lingua di i cenni tedesca", + "cy": "Iaith Arwyddo Almaeneg", + "da": "Tysk tegnsprog", + "de": "Deutsche Gebärdensprache", + "eml": "Lèngva di sègn tedèsca", + "en": "German Sign Language", + "en-gb": "German Sign Language", + "eo": "germana signolingvo", + "es": "lengua de señas alemana", + "et": "Saksa viipekeel", + "fa": "زبان اشاره آلمانی", + "fi": "saksalainen viittomakieli", + "fr": "langue des signes allemande - DGS", + "fur": "Lenghe dai segns todescje", + "ga": "teanga chomharthaíochta na Gearmáine", + "gl": "lingua de sinais alemá", + "he": "שפת הסימנים הגרמנית", + "hu": "német jelnyelv", + "id": "Bahasa Isyarat Jerman", + "is": "þýskt táknmál", + "it": "lingua dei segni tedesca", + "ja": "ドイツ手話", + "ko": "독일 수화", + "la": "lingua gesticulatoria Theodisca", + "lfn": "lingua de sinia deutx", + "lij": "Lengua di Segni Todesca", + "lmo": "Lèngua dei sègn todèsca", + "mk": "германски знаковен јазик", + "ms": "Bahasa Isyarat Jerman", + "mt": "lingwa tas-sinjali Ġermaniża", + "nap": "Lenguaggio d' 'e signe turesca", + "nb_NO": "tysk tegnspråk", + "nl": "Duitse gebarentaal", + "oc": "Lenga dels signes alemanda", + "pa": "ਜਰਮਨ ਸੈਨਤ ਬੋਲੀ", + "pl": "niemiecki język migowy", + "pms": "Lenga dij segn tedesca", + "pnb": "جرمن سیںت بولی", + "pt": "língua gestual alemã", + "pt_BR": "língua de sinais alemã", + "rm": "Lingua da segns tudestga", + "roa-tara": "Lenghe de le signe tedesche", + "ru": "германский жестовый язык", + "sc": "Limba de sos Sinnos Tedesca", + "scn": "Lingua dî Signa Tidesca", + "sco": "German Sign Leid", + "sl": "nemški znakovni jezik", + "sr": "немачки знаковни језик", + "sv": "tyskt teckenspråk", + "tok": "toki luka Tosi", + "tr": "Alman İşaret Dili", + "tt": "Германия ишарә теле", + "uk": "німецька мова жестів", + "vec": "Lengua de i segni todesca", + "vi": "Ngôn ngữ ký hiệu Đức", + "war": "Linggwahe hin Sinyales Inaleman", + "zh": "德国手语", + "zh_Hans": "德国手语", + "zh_Hant": "德國手語" + }, + "ögs": { + "ar": "لغة الإشارة النمساوية", + "ary": "لغة لإشارة د النمسا", + "ast": "llingua austriaca de señes", + "be-tarask": "аўстрыйская мова жэстаў", + "bn": "অস্ট্রীয় ইশারা ভাষা", + "ca": "llengua de signes austríaca", + "cy": "Iaith Arwyddo Awstria", + "de": "Österreichische Gebärdensprache", + "en": "Austrian Sign Language", + "en-gb": "Austrian Sign Language", + "eo": "aŭstra signolingvo", + "es": "lengua austríaca de signos", + "fa": "زبان اشاره اتریشی", + "fr": "langue des signes autrichienne", + "ga": "teanga chomharthaíochta na hOstaire", + "he": "שפת הסימנים האוסטרית", + "hr": "austrijski znakovni jezik", + "hu": "osztrák jelnyelv", + "ja": "オーストリア手話", + "ko": "오스트리아 수어", + "la": "lingua gesticulatoria Austriaca", + "lfn": "lingua de sinia osteraices", + "nb_NO": "østerriksk tegnspråk", + "nl": "oostenrijkse gebarentaal", + "pl": "austriacki język migowy", + "pms": "Lenga dij segn austrìaca", + "pt": "língua de sinais austríaca", + "rm": "Lingua da segns austriaca", + "roa-tara": "Lenghe de le signe austrieche", + "ru": "австрийский жестовый язык", + "scn": "Lingua dî Signa Austriaca", + "sco": "Austrick Sign Leid", + "sk": "Rakúsky posunkový jazyk", + "sl": "avstrijski znakovni jezik", + "tok": "toki luka Esalasi", + "tr": "Avusturya İşaret Dili", + "tt": "Австрия ишарәләр теле", + "zh": "奥地利手语", + "zh_Hans": "奥地利手语", + "zh_Hant": "奧地利手語" + }, + "lis": { + "ar": "لغة الإشارة السورية", + "ast": "llingua de señes italiana", + "bn": "ইতালীয় ইশারা ভাষা", + "br": "yezh ar sinoù italianek", + "co": "lingua di i cenni taliana", + "cy": "Iaith Arwyddo Eidaleg", + "de": "italienische Gebärdensprache", + "el": "Ιταλική Νοηματική Γλώσσα", + "eml": "lèngva di sègn itagliàna", + "en": "Syrian Sign Language", + "en-gb": "Syrian Sign Language", + "eo": "itala signolingvo", + "es": "lengua de signos italiana", + "eu": "Italiako keinu hizkuntza", + "fa": "زبان اشاره ایتالیایی", + "fi": "italialainen viittomakieli", + "fr": "langue des signes syrienne", + "fur": "lenghe dai segns italiane", + "ga": "teanga chomharthaíochta na Siria", + "gl": "lingua de signos italiana", + "he": "שפת הסימנים הסורית", + "hif": "Italian Sign Language", + "id": "Bahasa Isyarat Italia", + "is": "ítalskt táknmál", + "it": "lingua dei segni italiana", + "ja": "シリア手話", + "ko": "이탈리아 수화", + "la": "lingua gesticulatoria Italica", + "lfn": "lingua de sinia italian", + "lij": "Lengua di Segni Italian-a", + "lmo": "lèngua dei sègn italiana", + "mt": "lingwa tas-sinjali Taljana", + "nap": "lenguaggio d' 'e signe taliane", + "nl": "italiaanse gebarentaal", + "nn": "Italiensk teiknspråk", + "oc": "lenga dels signes italiana", + "pms": "lenga dij segn italian-a", + "rm": "lingua da segns taliana", + "roa-tara": "lenghe de le signe tagliane", + "sc": "Limba de sos Sinnos Italiana", + "scn": "Lingua dî Signa Taliana", + "sco": "Italian Sign Leid", + "sl": "italijanski znakovni jezik", + "tok": "toki luka Italija", + "tr": "Suriye İşaret Dili", + "uk": "італійська жестова мова", + "vec": "łéngua dei segni italiana", + "war": "Linggwahe hin Sinyales Initalyana", + "zh": "義大利手語", + "zh_Hans": "叙利亚手语", + "zh_Hant": "敘利亞手語" + }, + "csl": { + "bn": "চীনা ইশারা ভাষা", + "ca": "llengua de signes xinesa", + "cs": "čínský znakový jazyk", + "de": "Chinesische Gebärdensprache", + "en": "Chinese Sign Language", + "en-gb": "Chinese Sign Language", + "eo": "ĉina signolingvo", + "fa": "زبان اشاره چینی", + "fr": "langue des signes chinoise", + "ga": "teanga chomharthaíochta na Síne", + "he": "שפת הסימנים הסינית", + "hi": "चीनी सांकेतिक भाषा", + "id": "Bahasa Isyarat Tiongkok", + "it": "lingua dei segni cinese", + "ja": "中国手話", + "ko": "중국 수어", + "lfn": "lingua de sinia jonguo", + "nap": "Lenguaggio d' 'e signe d' 'a Cina", + "nl": "chinese gebarentaal", + "pa": "ਚੀਨੀ ਚਿਹਨ ਭਾਸ਼ਾ", + "pms": "Lenga dij segn cinèisa", + "pt": "língua de sinais chinesa", + "roa-tara": "Lenghe de le signe cenise", + "ru": "китайский жестовый язык", + "scn": "Lingua dî Signa Cinisa", + "tok": "toki luka Sonko", + "tr": "Çin İşaret Dili", + "zh": "中國手語", + "zh-hk": "中國手語", + "zh_Hans": "中国手语", + "zh_Hant": "中國手語" + }, + "dsgs": { + "bn": "সুইস-জার্মান ইশারা ভাষা", + "ca": "llengua de signes germanosuïssa", + "de": "Deutschschweizer Gebärdensprache", + "en": "Swiss-German Sign Language", + "en-gb": "Swiss-German Sign Language", + "eo": "svisgermana gestlingvo", + "fa": "زبان اشاره سوئیسی-آلمانی", + "fr": "langue des signes de Suisse alémanique", + "ga": "teanga chomharthaíochta Eilvéiseach-Gearmánach", + "gsw": "Deutschschweizer Gebärdensprache", + "ha": "Harshen Kurame na Switzerland da Jamusanci", + "he": "שפת הסימנים השווייצרית-גרמנית", + "hi": "स्विस-जर्मन सांकेतिक भाषा", + "id": "Bahasa Isyarat Jerman-Swiss", + "ja": "スイス・ドイツ手話", + "pms": "Lenga dij segn dla Svìssera tedesca", + "sl": "švicarskonemški znakovni jezik", + "tr": "İsviçre-Alman İşaret Dili", + "tt": "швейцарияле немец ишарә теле", + "zh": "瑞士德语手语", + "zh_Hans": "瑞士德语手语", + "zh_Hant": "瑞士德語手語" + }, + "gsl": { + "bn": "ঘানাই ইশারা ভাষা", + "cy": "Iaith Arwyddo Ghana", + "de": "Ghanaische Gebärdensprache", + "en": "Ghanaian Sign Language", + "en-gb": "Ghanaian Sign Language", + "eo": "ganaa signolingvo", + "es": "lenguaje de signos de Ghana", + "fr": "langue des signes ghanéenne", + "ga": "teanga chomharthaíochta Ghána", + "hi": "घाना की सांकेतिक भाषा", + "ja": "ガーナ手話", + "lfn": "lingua de sinia ganaian", + "pms": "Lenga dij segn dël Ghana", + "pt": "Língua de Sinais de Gana", + "scn": "Lingua dî Signa Ghanisa", + "tok": "toki luka Kana", + "tr": "Gana İşaret Dili" + }, + "jsl": { + "ar": "لغة الاشارة اليابانية", + "bn": "জাপানি ইশারা ভাষা", + "br": "yezh ar sinoù japanek", + "ca": "llengua de signes japonesa", + "cs": "japonský znakový jazyk", + "cy": "Iaith Arwyddo Japaneg", + "de": "Japanische Gebärdensprache", + "en": "Japanese Sign Language", + "en-gb": "Japanese Sign Language", + "eo": "japana signolingvo", + "es": "lengua de señas japonesa", + "fa": "زبان اشاره ژاپنی", + "fi": "japanilainen viittomakieli", + "fr": "langue des signes japonaise", + "ga": "teanga chomharthaíochta na Seapáine", + "he": "שפת הסימנים היפנית", + "hi": "जापानी सांकेतिक भाषा", + "hif": "Japanese Sign Language", + "hu": "japán jelnyelv", + "id": "Bahasa Isyarat Jepang", + "it": "lingua dei segni giapponese", + "ja": "日本手話", + "ko": "일본 수화", + "la": "lingua gesticulatoria Iaponica", + "lfn": "lingua de sinia nion", + "ms": "Bahasa Isyarat Jepun", + "nl": "Japanse gebarentaal", + "pa": "ਜਪਾਨੀ ਚਿਹਨੀ ਭਾਸ਼ਾ", + "pms": "Lenga dij segn giaponèisa", + "pt": "Língua de Sinais Japonesa", + "ru": "японский жестовый язык", + "sc": "Limba de sos Sinnos Giapponesa", + "scn": "Lingua dî Signa Giappunisa", + "sl": "japonski znakovni jezik", + "sv": "Japanskt teckenspråk", + "tok": "toki luka Nijon", + "tr": "Japon İşaret Dili", + "uk": "Японська жестова мова", + "vec": "Lengua de i segni giaponexe", + "vi": "ngôn ngữ ký hiệu tiếng Nhật", + "zh": "日本手语", + "zh_Hans": "日本手语", + "zh_Hant": "日本手話" + }, + "thsl": { + "de": "Thailändische Gebärdensprache", + "en": "Thai Sign Language", + "en-gb": "Thai Sign Language", + "eo": "taja signolingvo", + "fa": "زبان اشاره تایلندی", + "ga": "teanga chomharthaíochta na Téalainne", + "he": "שפת הסימנים התאילנדית", + "hi": "थाई सांकेतिक भाषा", + "hif": "Thae Sign Language", + "id": "Bahasa Isyarat Thai", + "ja": "タイ手話", + "lfn": "lingua de sinia tai", + "lmo": "Lèngua d'i sègn d'la Thailandia", + "mt": "Lingwa tas-Sinjali Tajlandiża", + "nl": "Thaise gebarentaal", + "pms": "Lenga dij segn tailandèisa", + "pt": "Língua de sinais tailandesa", + "sc": "Limba de sos Sinnos Thailandesa", + "scn": "Lingua dî Signa Thai", + "th": "ภาษามือไทย", + "tok": "toki luka Tawi", + "tr": "Tay İşaret Dili", + "zh": "泰國手語" + }, + "ttsl": { + "de": "Trinidad-und-Tobago-Gebärdensprache", + "en": "Trinidadian Sign Language", + "en-gb": "Trinidadian Sign Language", + "fa": "زبان اشاره ترینیداد و توباگویی", + "fr": "langue des signes trinidadienne", + "ga": "teanga chomharthaíochta Oileán na Tríonóide", + "it": "lingua dei segni trinidadiana", + "ja": "トリニダード・トバゴ手話", + "tok": "toki luka Sinita", + "tr": "Trinidad ve Tobago İşaret Dili" + }, + "usl": { + "de": "Ugandische Gebärdensprache", + "en": "Uzbek Sign Language", + "en-gb": "Uzbek Sign Language", + "eo": "uganda signolingvo", + "fa": "زبان اشاره اوگاندایی", + "fr": "langue des signes ougandaise", + "ga": "teanga chomharthaíochta na hÚisbéiceastáine", + "ig": "Asụsụ Ogbi nke Uganda", + "ja": "ウガンダ手話", + "lfn": "lingua de sinia ugandan", + "lg": "olulimi olukozesebwa abantu abalina obuzibu mu kwogera oba okuwulira", + "nl": "Ugandese gebarentaal", + "pms": "Lenga dij segn ugandèisa", + "pt": "Língua de Sinais do Uganda", + "ru": "узбекский жестовый язык", + "scn": "Lingua dî Signa Uganna", + "tok": "toki luka Ukanta", + "tr": "Özbek İşaret Dili", + "tt": "Уганда ишарә теле", + "uz-cyrl": "ўзбек имо-ишора тили", + "uz-latn": "o‘zbek imo-ishora tili" + }, + "msl": { + "ast": "llingua de señes mongola", + "bn": "মঙ্গোলীয় ইশারা ভাষা", + "de": "Mongolische Gebärdensprache", + "en": "Mongolian Sign Language", + "en-gb": "Mongolian Sign Language", + "eo": "mongola signolingvo", + "es": "lengua de signos mongola", + "fa": "زبان اشاره مغولستانی", + "fr": "langue des signes mongole", + "ga": "teanga chomharthaíochta na Mongóile", + "ig": "Asụsụ Ogbi nke Mongolia", + "ja": "モンゴル手話", + "lfn": "lingua de sinia mongol", + "mn": "Монгол дохионы хэл", + "nl": "Mongoolse gebarentaal", + "pms": "Lenga dij segn dla Mongolia", + "pt": "Língua de Sinais da Mongólia", + "ru": "монгольский жестовый язык", + "scn": "Lingua dî Signa Mòngola", + "tok": "toki luka Monko", + "tr": "Moğolistan İşaret Dili", + "tt": "монгол ишарә теле" + }, + "krsl": { + "en": "Kazakh-Russian Sign Language", + "fr": "langue des signes kazakho-russe", + "ga": "teanga chomharthaíochta Chasacach-Rúiseach", + "ig": "Asụsụ Ogbi nke Kazakh", + "kk-cyrl": "Қазақ-орыс ымдау тілі", + "zh": "і哈萨克-俄罗斯手语" + }, + "kossl": { + "bn": "কসোভো ইশারা ভাষা", + "en": "Kosovar Sign Language", + "en-gb": "Kosovar Sign Language", + "fi": "kosovolainen viittomakieli", + "fr": "langue des signe kosovare", + "ga": "teanga chomharthaíochta na Cosaive", + "ja": "コソボ手話", + "se": "kosovolaš seavagiella", + "smn": "kosovolâš seevvimkielâ", + "sq": "Gjuha e Shenjave Kosovare", + "sr-el": "kosovski znakovni jezik", + "sv": "kosovanskt teckenspråk", + "tr": "Kosova İşaret Dili", + "zh_Hans": "科索沃手语", + "zh_Hant": "科索沃手語" + }, + "gjshk": { + "bn": "কসোভো ইশারা ভাষা", + "en": "Kosovar Sign Language", + "en-gb": "Kosovar Sign Language", + "fi": "kosovolainen viittomakieli", + "fr": "langue des signe kosovare", + "ga": "teanga chomharthaíochta na Cosaive", + "ja": "コソボ手話", + "se": "kosovolaš seavagiella", + "smn": "kosovolâš seevvimkielâ", + "sq": "Gjuha e Shenjave Kosovare", + "sr-el": "kosovski znakovni jezik", + "sv": "kosovanskt teckenspråk", + "tr": "Kosova İşaret Dili", + "zh_Hans": "科索沃手语", + "zh_Hant": "科索沃手語" + }, + "сзј": { + "ca": "llengua de signes sèrbia", + "cy": "Iaith arwyddion Serbia", + "de": "Serbische Gebärdensprache", + "en": "Serbian Sign Language", + "en-gb": "Serbian Sign Language", + "es": "lengua de signos serbia", + "eu": "serbiar keinu hizkuntza", + "fr": "langue des signes serbe", + "ga": "teanga chomharthaíochta na Seirbia", + "gn": "ñe'ẽ rechaukaha Sérvia", + "nl": "Servische Gebarentaal", + "pt": "língua gestual sérvia", + "ru": "сербский язык жестов", + "sr-ec": "српски знаковни језик", + "sr-el": "srpski znakovni jezik", + "tr": "Sırp İşaret Dili" + }, + "szj": { + "ca": "llengua de signes sèrbia", + "cy": "Iaith arwyddion Serbia", + "de": "Serbische Gebärdensprache", + "en": "Serbian Sign Language", + "en-gb": "Serbian Sign Language", + "es": "lengua de signos serbia", + "eu": "serbiar keinu hizkuntza", + "fr": "langue des signes serbe", + "ga": "teanga chomharthaíochta na Seirbia", + "gn": "ñe'ẽ rechaukaha Sérvia", + "nl": "Servische Gebarentaal", + "pt": "língua gestual sérvia", + "ru": "сербский язык жестов", + "sr-ec": "српски знаковни језик", + "sr-el": "srpski znakovni jezik", + "tr": "Sırp İşaret Dili" + }, + "lis-si": { + "de": "Lingua dei segni della Svizzera italiana", + "en": "Swiss-Italian Sign Language", + "en-gb": "Swiss-Italian Sign Language", + "eo": "svis-itala signolingvo", + "fr": "langue des signes de la Suisse italienne", + "ga": "teanga chomharthaíochta Eilvéiseach-Iodálach", + "it": "Lingua dei segni della Svizzera italiana", + "ja": "スイス=イタリア手話", + "pms": "Lenga dij segn dla Svìssera italian-a", + "sl": "švicarskoitalijanski znakovni jezik", + "tr": "İsviçre-İtalyan İşaret Dili", + "tt": "швейцарияле итальян ишарә теле" + }, + "lsf-sr": { + "bn": "সুইস-ফরাসি ইশারা ভাষা", + "ca": "llengua de signes suïssa de parla francesa", + "de": "Westschweizer Gebärdensprache", + "en": "Swiss-French Sign Language", + "en-gb": "Swiss-French Sign Language", + "fr": "langue des signes de Suisse romande", + "ga": "teanga chomharthaíochta Eilvéiseach-Francach", + "hr": "Švicarski-francuski znakovni jezik", + "ja": "スイス・フランス手話", + "pms": "Lenga dij segn dla Svìssera fransèisa", + "sl": "švicarski francoski znakovni jezik", + "tr": "İsviçre-Fransız İşaret Dili", + "tt": "швейцарияле француз ишарә теле", + "zh": "瑞士法语手语", + "zh_Hans": "瑞士法语手语", + "zh_Hant": "瑞士法語手語" + }, + "lsh": { + "en": "Haitian Sign Language", + "en-gb": "Haitian Sign Language", + "fr": "langue des signes haïtienne", + "ga": "teanga chomharthaíochta Háítí", + "tr": "Haiti İşaret Dili" + }, + "psl": { + "ar": "لغة الاشارات الفلسطينية", + "bcc": "پاکستان اشارہ", + "bgn": "پاکستان اشارہ", + "bn": "পাকিস্তান ইশারা ভাষা", + "brh": "پاکستان اشارہ", + "ca": "llengua de signes pakistanesa", + "de": "Pakistanische Gebärdensprache", + "en": "Pakistan Sign Language", + "en-gb": "Palestinian Sign Language", + "eo": "pakistana signolingvo", + "es": "lengua de señas pakistaní", + "fa": "پاکستان اشاره", + "fr": "langue des signes pakistanaise", + "ga": "teanga chomharthaíochta na Pacastáine", + "he": "שפת הסימנים הפלסטינית", + "hi": "पाकिसतान इशारा", + "hno": "پاکستان اشارہ", + "ja": "パキスタン手話", + "khw": "پاکستان اشارہ", + "lfn": "lingua de sinia pacistani", + "pa": "ਪਾਕਿਸਤਾਨ ਇਸ਼ਾਰਾ", + "pms": "Lenga dij segn pakistan-a", + "pnb": "پاکستان اشارہ", + "ps": "پاکستان اشاره", + "ru": "пакистанский жестовый язык", + "sd": "پاڪستان اشاره", + "skr": "پاکستان اشارہ", + "sl": "pakistanski znakovni jezik", + "sv": "pakistanskt teckenspråk", + "tok": "toki luka Pakisan", + "tr": "Pakistan İşaret Dili", + "ur": "پاکستان اشارہ", + "zh": "巴基斯坦手语", + "zh_Hans": "巴勒斯坦手语", + "zh_Hant": "巴勒斯坦手語" + }, + "lif": { + "ar": "لغة الاشارات الفلسطينية", + "en": "Palestinian Sign Language", + "en-gb": "Palestinian Sign Language", + "fr": "langue des signes palestinienne", + "ga": "teanga chomharthaíochta na Palaistíne", + "he": "שפת הסימנים הפלסטינית", + "ja": "パレスチナ手話", + "tr": "Filistin İşaret Dili", + "zh_Hans": "巴勒斯坦手语", + "zh_Hant": "巴勒斯坦手語" + }, + "sagt": { + "af": "Suid-Afrikaanse Gebaretaal", + "ast": "llingua de señes sudafricana", + "bn": "দক্ষিণ আফ্রিকীয় ইশারা ভাষা", + "cy": "Iaith Arwyddo De Affrica", + "de": "Südafrikanische Gebärdensprache", + "en": "South African Sign Language", + "en-gb": "South African Sign Language", + "es": "Lenguaje de Signos Sudafricano", + "fa": "زبان اشاره آفریقای جنوبی", + "fr": "langue des signes sud-africaine", + "ga": "teanga chomharthaíochta na hAfraice Theas", + "gl": "Lingua de sinais surafricana", + "hif": "South African Sign Language", + "hr": "Južnoafrički znakovni jezik", + "ig": "South African Sign Language", + "it": "lingua dei segni sudafricana", + "ja": "南アフリカ手話", + "lfn": "lingua de sinia sudafrican", + "nb_NO": "sørafrikansk tegnspråk", + "nl": "Zuid-Afrikaanse gebarentaal", + "nso": "South African Sign Language", + "pms": "Lenga dij segn dël Sudàfrica", + "pt": "Língua gestual sul-africana", + "ru": "южноафриканский жестовый язык", + "scn": "Lingua dî Signa Sudàfricana", + "sk": "Juhoafrický posunkový jazyk", + "sl": "južnoafriški znakovni jezik", + "tok": "toki luka Setapika", + "tr": "Güney Afrika İşaret Dili", + "vec": "łéngua dei segni sudafricana", + "xh": "Ulwimi lwezandla lwase Mzantsi Afrika", + "zh": "南非手語", + "zh-cn": "南非手语", + "zh_Hant": "南非手語", + "zu": "Ulimi lwezandla lwaseNingizimu Afrika" + }, + "sasl": { + "af": "Suid-Afrikaanse Gebaretaal", + "ast": "llingua de señes sudafricana", + "bn": "দক্ষিণ আফ্রিকীয় ইশারা ভাষা", + "cy": "Iaith Arwyddo De Affrica", + "de": "Südafrikanische Gebärdensprache", + "en": "South African Sign Language", + "en-gb": "South African Sign Language", + "es": "Lenguaje de Signos Sudafricano", + "fa": "زبان اشاره آفریقای جنوبی", + "fr": "langue des signes sud-africaine", + "ga": "teanga chomharthaíochta na hAfraice Theas", + "gl": "Lingua de sinais surafricana", + "hif": "South African Sign Language", + "hr": "Južnoafrički znakovni jezik", + "ig": "South African Sign Language", + "it": "lingua dei segni sudafricana", + "ja": "南アフリカ手話", + "lfn": "lingua de sinia sudafrican", + "nb_NO": "sørafrikansk tegnspråk", + "nl": "Zuid-Afrikaanse gebarentaal", + "nso": "South African Sign Language", + "pms": "Lenga dij segn dël Sudàfrica", + "pt": "Língua gestual sul-africana", + "ru": "южноафриканский жестовый язык", + "scn": "Lingua dî Signa Sudàfricana", + "sk": "Juhoafrický posunkový jazyk", + "sl": "južnoafriški znakovni jezik", + "tok": "toki luka Setapika", + "tr": "Güney Afrika İşaret Dili", + "vec": "łéngua dei segni sudafricana", + "xh": "Ulwimi lwezandla lwase Mzantsi Afrika", + "zh": "南非手語", + "zh-cn": "南非手语", + "zh_Hant": "南非手語", + "zu": "Ulimi lwezandla lwaseNingizimu Afrika" + }, + "lsfb": { + "bn": "ফরাসি বেলজীয় ইশারা ভাষা", + "ca": "llengua de signes belga francès", + "de": "französisch-belgische Gebärdensprache", + "en": "French Belgian Sign Language", + "en-gb": "French Belgian Sign Language", + "eo": "franca belga gestolingvo", + "fa": "زبان اشاره بلژیکی فرانسوی", + "fr": "langue des signes de Belgique francophone", + "ga": "teanga chomharthaíochta na Beilge Francaí", + "hr": "Belgijski frankofonski znakovni jezik", + "ig": "Asụsụ Ogbi French Belgian", + "ja": "ワロン手話", + "nl": "Frans-Belgische Gebarentaal", + "sl": "francoskobelgijski znakovni jezik", + "tt": "Бельгиянең француз телле җәмгыятенең ишарә теле" + }, + "ipsl": { + "ar": "لغة الإشارة الهندية الباكستانية", + "bcc": "اشارہ", + "bgn": "اشارہ", + "bn": "ইন্দো-পাকিস্তানি ইশারা ভাষা", + "brh": "اشارہ", + "ca": "lengua de signes indopakistanesa", + "de": "Indo-Pakistan Sign Language", + "en": "Indo-Pakistani Sign Language", + "en-gb": "Indo-Pakistani Sign Language", + "es": "lengua de señas indo-pakistaní", + "fa": "زبان اشاره هندوپاکستانی", + "fr": "langue des signes indo-pakistanaise", + "ga": "teanga chomharthaíochta na hInd-Phacastáine", + "he": "שפת הסימנים ההודית-פקיסטנית", + "hi": "इशारा", + "hno": "اشارہ", + "id": "Bahasa Isyarat India-Pakistan", + "ja": "インド・パキスタン手話", + "ne": "भारतीय सांकेतिक भाषा", + "pa": "ਇਸ਼ਾਰਾ", + "pms": "Lenga dij segn pakistan-a", + "pnb": "اشارہ", + "ps": "اشاره", + "ru": "индо-пакистанский жестовый язык", + "sd": "اشاره", + "skr": "اشارہ", + "sl": "indopakistanski znakovni jezik", + "ta": "இந்திய-பாக்கித்தான் சைகை மொழி்", + "tok": "toki luka Palata", + "tr": "Hint-Pakistan İşaret Dili", + "ur": "پاک و ہند اشاراتی زبان", + "zh": "印度-巴基斯坦手语" + }, + "nsl": { + "de": "Namibische Gebärdensprache", + "en": "Namibian Sign Language", + "en-gb": "Namibian Sign Language", + "eo": "namibia signolingvo", + "fa": "زبان اشاره نامیبیایی", + "fi": "Namibialainen viittomakieli", + "fr": "langue des signes namibienne", + "ga": "teanga chomharthaíochta na Namaibe", + "ha": "Harshen kuramen Namibiya", + "hu": "namíbiai jelnyelv", + "ig": "Asụsụ Ogbi nke Namibia", + "ja": "ナミビア手話", + "lfn": "lingua de sinia namibian", + "pms": "Lenga dij segn dla Namibia", + "pt": "Língua de Sinais da Namíbia", + "scn": "Lingua dî Signa Namibbiana", + "tok": "toki luka Namipija", + "tr": "Namibya İşaret Dili", + "tt": "Намибия ишарә теле" + }, + "nisl": { + "de": "Northern Ireland Sign Language", + "en": "Northern Ireland Sign Language", + "fa": "زبان اشاره ایرلند شمالی", + "fr": "langue des signes nord-irlandaise", + "ga": "teanga chomharthaíochta Thuaisceart Éireann", + "hi": "उत्तरी आयरलैंड सांकेतिक भाषा", + "it": "lingua dei segni nordirlandese", + "ja": "北アイルランド手話", + "scn": "Lingua dî Signa Irlanna dû Nord", + "tr": "Kuzey İrlanda İşaret Dili" + }, + "ítm": { + "anp": "आइसलैंडिक सांकेतिक भाषा", + "bn": "আইসল্যান্ডীয় ইশারা ভাষা", + "ckb": "زمانی ئاماژەی ئایسلەندی", + "co": "lingua di i cenni islandesa", + "da": "Islandsk tegnsprog", + "de": "Isländische Gebärdensprache", + "en": "Icelandic Sign Language", + "en-gb": "Icelandic Sign Language", + "eo": "islanda signolingvo", + "es": "Lengua de señas islandesa", + "fa": "زبان اشاره ایسلندی", + "fi": "islantilainen viittomakieli", + "fr": "langue des signes islandaise", + "fur": "lenghe dai signse islandese", + "ga": "teanga chomharthaíochta na hÍoslainne", + "hr": "Islandski znakovni jezik", + "is": "íslenskt táknmál", + "it": "lingua dei segni islandese", + "ja": "アイスランド手話", + "ku": "zimanê hêmayan ê îslendî", + "ku-latn": "zimanê hêmayan ê îslendî", + "lfn": "lingua de sinia islansce", + "nb_NO": "islandsk tegnspråk", + "nl": "IJslandse gebarentaal", + "oc": "lenga dels signes islandesa", + "pms": "lenga dij segn islandèisa", + "ru": "исландский жестовый язык", + "scn": "Lingua dî Signa Islannisi", + "sv": "Isländskt teckenspråk", + "tok": "toki luka Isilan", + "tr": "İzlanda İşaret Dili", + "tt": "Исландия ишарә теле", + "vec": "lengua de i segni ìxlèndexe" + }, + "libras": { + "ast": "llingua de señes brasiliana", + "bn": "ব্রাজিলীয় ইশারা ভাষা", + "br": "yezh ar sinoù brazilek", + "ca": "llengua de signes brasilera", + "co": "lingua di i cenni brasiliana", + "cy": "Iaith arwyddo Brasil", + "da": "brasiliansk tegnsprog", + "de": "Brasilianische Gebärdensprache", + "eml": "Lèngva di sègn braśigliàna", + "en": "Brazilian Sign Language", + "en-gb": "Brazilian Sign Language", + "eo": "brazila signolingvo", + "es": "lengua de señas brasileña", + "eu": "Brasilgo keinu hizkuntza", + "fa": "زبان اشاره برزیلی", + "fr": "langue des signes brésilienne", + "ga": "teanga chomharthaíochta na Brasaíle", + "he": "שפת הסימנים הברזילאית", + "hi": "ब्राज़ीलियाई सांकेतिक भाषा", + "hr": "brazilski znakovni jezik", + "id": "Bahasa Isyarat Brasil", + "it": "lingua dei segni brasiliana", + "ja": "ブラジル手話", + "la": "lingua gesticulatoria Brasiliensis", + "lfn": "lingua de sinia brasilera", + "ms": "Bahasa Isyarat Brazil", + "nb_NO": "brasiliansk tegnspråk", + "nl": "Braziliaanse gebarentaal", + "pa": "ਲਿਬਰਾਸ", + "pms": "Lenga dij segn brasilian-a", + "pnb": "لبراس", + "pt": "língua brasileira de sinais", + "pt_BR": "língua brasileira de sinais", + "ru": "бразильский жестовый язык", + "sat": "ᱵᱨᱟᱡᱤᱞᱤᱭᱟᱱ ᱤᱥᱟᱹᱨᱟ ᱯᱟᱹᱨᱥᱤ", + "scn": "Lingua dî Signa Brasiliana", + "sl": "brazilski znakovni jezik", + "sv": "brasilianskt teckenspråk", + "tok": "toki luka Pasiju", + "tr": "Brezilya İşaret Dili", + "tt": "Брәзил ишарәләр теле", + "uk": "Бразильська мова жестів", + "vec": "łéngua dei segni braziłegna", + "zh": "巴西手語", + "zu": "isi-Brazilian Sign Language" + }, + "hksl": { + "ary": "اللوغة د لإشارات د هونكونڭ", + "ca": "llengua de signes de Hong Kong", + "cs": "hongkongský znakový jazyk", + "en": "Hong Kong Sign Language", + "en-gb": "Hong Kong Sign Language", + "fa": "زبان اشاره هنگ کنگی", + "fi": "hongkongilainen viittomakieli", + "fr": "langue des signes hongkongaise", + "ga": "teanga chomharthaíochta Hong Cong", + "ha": "Harshen Kurame na Hong Kong", + "hi": "हांगकांग सांकेतिक भाषा", + "ja": "香港手話", + "pt": "Língua de Sinais de Hong-Kong", + "ru": "гонконгский жестовый язык", + "sv": "Hongkongs teckenspråk", + "tok": "toki luka Enkon", + "tr": "Hong Kong İşaret Dili", + "yue": "香港手語", + "zh": "香港手语", + "zh-hk": "香港手語", + "zh_Hant": "香港手語" + }, + "basl": { + "ar": "لغة الإشارة الخاصة بالأمريكيين السود", + "ca": "llengua de signes americana negra", + "en": "Black American Sign Language", + "fa": "زبان اشاره آمریکایی سیاهپوستان", + "hi": "ब्लैक अमेरिकन सांकेतिक भाषा", + "vi": "Ngôn ngữ ký hiệu người Mỹ gốc Phi", + "zh": "美国黑人手语" + }, + "bhsl": { + "el": "Μπουτανική Νοηματική Γλώσσα", + "en": "Bhutanese Sign Language", + "en-ca": "Bhutanese Sign Language", + "en-gb": "Bhutanese Sign Language", + "eo": "butana signolingvo", + "fa": "زبان اشاره بوتانی", + "ga": "teanga chomharthaíochta na Bútáine", + "tok": "toki luka Tuku", + "tr": "Butan İşaret Dili" + }, + "liu": { + "ar": "لغة الإشارة الأردنية", + "de": "Jordanische Gebärdensprache", + "en": "Jordanian Sign Language", + "eo": "jordania signolingvo", + "fr": "langue des signes jordanienne", + "ga": "teanga chomharthaíochta na hIordáine", + "he": "שפת הסימנים הירדנית", + "ja": "ヨルダン手話", + "lfn": "lingua de sinia urduni", + "pms": "lenga dij segn dla Giordania", + "tr": "Ürdün İşaret Dili", + "zh_Hans": "约旦手语", + "zh_Hant": "約旦手語" + }, + "bksl": { + "bn": "বান খোর ইশারা ভাষা", + "en": "Ban Khor Sign Language", + "en-gb": "Ban Khor Sign Language", + "fr": "langue des signes de Ban Khor", + "ga": "teanga chomharthaíochta Ban Khor", + "hi": "बान खोर सांकेतिक भाषा", + "hr": "Ban khor znakovni jezik", + "ja": "バンコール手話", + "pms": "Lenga dij segn ban khor", + "pt": "Língua de sinais de Ban Khor", + "ru": "бан-кхорский жестовый язык", + "th": "ภาษามือบ้านค้อ", + "tr": "Ban Khor İşaret Dili" + }, + "arsl": { + "bn": "আর্মেনীয় ইশারা ভাষা", + "cs": "arménský znakový jazyk", + "de": "Armenische Gebärdensprache", + "en": "Armenian Sign Language", + "en-gb": "Armenian Sign Language", + "eo": "armena signolingvo", + "es": "lengua de señas armenia", + "fa": "زبان اشاره ارمنستانی", + "fi": "armenialainen viittomakieli", + "fr": "langue des signes arménienne", + "ga": "teanga chomharthaíochta na hAirméine", + "he": "שפת הסימנים הארמנית", + "hr": "Armenski znakovni jezik", + "hy": "հայերեն ժեստերի լեզու", + "ig": "Asụsụ Ogbi nke Armenian", + "ja": "アルメニア手話", + "lfn": "lingua de sinia haiaren", + "nl": "Armeense gebarentaal", + "pms": "Lenga dij segn armen-a", + "pt": "Língua gestual armeniana", + "ru": "армянский жестовый язык", + "sl": "armenski znakovni jezik", + "tok": "toki luka Aja", + "tr": "Ermeni İşaret Dili", + "tt": "әрмән ишарә теле" + }, + "nts": { + "bcl": "Pansinyal na tataramong Malgatse", + "bn": "নরওয়েজীয় ইশারা ভাষা", + "ca": "llengua de signes noruega", + "de": "Norwegische Gebärdensprache", + "en": "Norwegian Sign Language", + "en-gb": "Norwegian Sign Language", + "eo": "norvega signolingvo", + "es": "Lengua de señas noruega", + "et": "Norra viipekeel", + "fa": "زبان اشاره نروژی", + "fi": "norjalainen viittomakieli", + "fr": "langue des signes norvégienne", + "ga": "teanga chomharthaíochta na hIorua", + "hif": "Norwegian Sign Language", + "hr": "norveški znakovni jezik", + "it": "lingua dei segni norvegese", + "ja": "ノルウェー手話", + "lfn": "lingua de sinia norsce", + "nb_NO": "norsk tegnspråk", + "nl": "noorse gebarentaal", + "nn": "norsk teiknspråk", + "pms": "lenga dij segn norvegèisa", + "rm": "lingua da segns norvegiaisa", + "scn": "Lingua dî Signa Nurviggisi", + "sco": "Norse Sign Leid", + "se": "dárogiel seavagiella", + "sl": "norveški znakovni jezik", + "sv": "norskt teckenspråk", + "tok": "toki luka Nosiki", + "tr": "Norveç İşaret Dili", + "tt": "Норвегия ишарә теле" + }, + "vgt": { + "bn": "ফ্লেমিশ ইশারা ভাষা", + "br": "yezh ar sinoù flandrezek", + "ca": "llengua de signes flamenca", + "de": "Flämische Gebärdensprache", + "en": "Flemish Sign Language", + "en-gb": "Flemish Sign Language", + "eo": "flandra signolingvo", + "es": "lengua de señas flamenca", + "fa": "زبان اشاره فلاندری", + "fr": "langue des signes flamande", + "ga": "teanga chomharthaíochta Phléimeannach", + "gl": "Lingua de signos flamenga", + "he": "שפת הסימנים הפלמית", + "hif": "Flemish Sign Language", + "hr": "Belgijski znakovni jezik", + "hy": "ֆլամանդերեն ժեստերի լեզու", + "ja": "フラマン手話", + "lfn": "lingua de sinia flames", + "nl": "Vlaamse Gebarentaal", + "pms": "Lenga dij segn belga", + "roa-tara": "Lenghe de le signe bèlge", + "ru": "фламандский язык жестов", + "sl": "flamski znakovni jezik", + "tr": "Flaman İşaret Dili", + "tt": "фламанд телле җәмгыятенең ишарә теле", + "zh_Hans": "弗拉芒手语", + "zh_Hant": "弗拉芒手語" + }, + "lsc": { + "ast": "llingua de señes catalana", + "be-tarask": "каталянская жэставая мова", + "bn": "কাতালান ইশারা ভাষা", + "br": "yezh ar sinoù katalan", + "ca": "llengua de signes catalana", + "da": "catalansk tegnesprog", + "de": "Llengua de Signes Catalana", + "en": "Catalan Sign Language", + "en-gb": "Catalan Sign Language", + "eo": "kataluna signolingvo", + "es": "lengua de signos catalana", + "fa": "زبان اشاره کاتالان", + "fr": "langue des signes catalane", + "fur": "Lenghe dai segns catalene", + "ga": "teanga chomharthaíochta na Catalóine", + "gl": "Lingua de sinais catalá", + "he": "שפת הסימנים הקטלאנית", + "hi": "कैटलन सांकेतिक भाषा", + "id": "Bahasa Isyarat Katalan", + "it": "lingua dei segni catalana", + "ja": "カタルーニャ手話", + "ko": "카탈루냐어 수화", + "ku": "zimanê hêmayan ê ketelanî", + "ku-latn": "zimanê hêmayan ê ketelanî", + "lfn": "lingua de sinia catalan", + "nb_NO": "katalansk tegnspråk", + "nl": "Catalaanse gebarentaal", + "nn": "katalansk teiknspråk", + "pl": "Kataloński język migowy", + "pms": "Lenga dij segn catalan-a", + "pt": "Língua de sinais catalã", + "rm": "Lingua da segns catalana", + "ru": "каталонский жестовый язык", + "scn": "Lingua dî Signa Catalana", + "sl": "katalonski znakovni jezik", + "sv": "Katalanskt teckenspråk", + "tok": "toki luka Katala", + "tr": "Katalan İşaret Dili", + "tt": "каталан ишарә теле", + "vec": "łéngua dei segni catełana", + "zh": "加泰隆尼亞手語", + "zh_Hans": "加泰罗尼亚手语", + "zh_Hant": "加泰隆尼亞手語" + }, + "nzsl": { + "anp": "न्यूजीलैंड सांकेतिक भाषा", + "ar": "لغة الإشارة النيوزيلندية", + "ary": "اللوغة دليشارة ديال نيوزيلاندا", + "ast": "llingua de señes de Nueva Zelanda", + "bn": "নিউ জিল্যান্ড ইশারা ভাষা", + "br": "yezh ar sinoù zelandnevezek", + "ca": "llengua de signes de Nova Zelanda", + "co": "lingua di i cenni neuzilandese", + "cy": "Iaith Arwyddo Seland Newydd", + "de": "neuseeländische Gebärdensprache", + "el": "Νεοζηλανδική Νοηματική Γλώσσα", + "eml": "Lèngva di sègn neośelandéśa", + "en": "New Zealand Sign Language", + "en-gb": "New Zealand Sign Language", + "eo": "novzelanda gestlingvo", + "es": "lengua de señas neozelandesa", + "et": "Uus-Meremaa viipekeel", + "fa": "زبان اشاره نیوزیلندی", + "fi": "uusiseelantilainen viittomakieli", + "fr": "langue des signes néo-zélandaise", + "fur": "Lenghe dai segns gnovezelandese", + "ga": "teanga chomharthaíochta na Nua-Shéalainne", + "he": "שפת הסימנים הניו זילנדית", + "hi": "न्यूज़ीलैण्ड हस्ताक्षर भाषा", + "hy": "նորզելանդական ժեստերի լեզու", + "hyw": "Նոր Զելանտայի շարժումներու լեզու", + "id": "Bahasa Isyarat Selandia Baru", + "is": "nýsjálenskt táknmál", + "it": "lingua dei segni neozelandese", + "ja": "ニュージーランド手話", + "ka": "ახალზელანდიური ნიშნების ენა", + "la": "lingua gesticulatoria Novozelandensis", + "lmo": "Lèngua d'i sègn neozelandesa", + "ms": "Bahasa Isyarat New Zealand", + "mt": "lingwa tas-sinjali Newzealander", + "nap": "Lenguaggio d' 'e signe novazelandese", + "nb_NO": "nyzealandsk tegnspråk", + "nl": "Nieuw-Zeelandse Gebarentaal", + "nn": "Newzealandsk teiknspråk", + "oc": "lenga dels signs de Nòva Zelanda", + "pl": "nowozelandzki język migowy", + "pms": "Lenga dij segn dla Neuva Zelanda", + "pt": "língua de sinais da Nova Zelândia", + "pt_BR": "língua de sinais da Nova Zelândia", + "rm": "Lingua da segns nova zelandaisa", + "ru": "новозеландский язык жестов", + "sc": "limba de sos sinnos neozelandesa", + "scn": "lingua dî signa nova zilannisi", + "sco": "New Zealand Sign Leid", + "sk": "novozélandský posunkový jazyk", + "sl": "novozelandski znakovni jezik", + "sr": "новозеландски знаковни језик", + "sv": "nyzeeländskt teckenspråk", + "tok": "toki luka Nusilan", + "tr": "Yeni Zelanda İşaret Dili", + "tt": "Яңа Зеландия ымнар теле", + "uk": "новозеландська мова жестів", + "ur": "نیوزی لینڈ کی اشارتی زبان", + "vec": "Lengua de i segni neozełandexe", + "zh": "新西兰标记语言", + "zh-cn": "新西蘭手語", + "zh_Hans": "新西兰手语", + "zh_Hant": "紐西蘭手語" + }, + "aslg": { + "ar": "قواعد لغة الإشارة الأمريكية", + "en": "American Sign Language grammar", + "zh": "美國手語語法" + }, + "lil": { + "ar": "لغة الإشارات اللبنانية", + "de": "Libanesische Gebärdensprache", + "en": "Lebanese Sign Language", + "fr": "langue des signes libanaise", + "ga": "teanga chomharthaíochta na Liobáine", + "he": "שפת הסימנים הלבנונית", + "ja": "レバノン手話", + "tok": "toki luka Lunpan", + "tr": "Lübnan İşaret Dili", + "zh_Hans": "黎巴嫩手语", + "zh_Hant": "黎巴嫩手語" + }, + "ngt": { + "bn": "ওলন্দাজ ইশারা ভাষা", + "br": "yezh ar sinoù izelvroek", + "ca": "llengua de signes neerlandesa", + "de": "Niederländische Gebärdensprache", + "en": "Dutch Sign Language", + "en-gb": "Dutch Sign Language", + "eo": "nederlanda signolingvo", + "fa": "زبان اشاره هلندی", + "fi": "hollantilainen viittomakieli", + "fr": "langue des signes néerlandaise", + "fur": "Lenghe dai segns olandese", + "ga": "teanga chomharthaíochta na hOllainne", + "id": "Bahasa Isyarat Belanda", + "ja": "オランダ手話", + "lfn": "lingua de sinia nederlandes", + "mt": "Lingwa tas-Sinjali Olandiża", + "nl": "Nederlandse gebarentaal", + "pms": "Lenga dij segn olandèisa", + "pt": "língua de sinais neerlandesa", + "rm": "Lingua da segns neerlandaisa", + "scn": "Lingua dî Signa Ulannisi", + "sco": "Dutch Sign Leid", + "sl": "nizozemski znakovni jezik", + "tok": "toki luka Netelan", + "tr": "Hollanda İşaret Dili", + "war": "Linggwahe hin Sinyales Inolandes", + "zh_Hans": "荷兰手语", + "zh_Hant": "荷蘭手語" + }, + "ssl": { + "be-tarask": "швэдзкая жэставая мова", + "bn": "সুয়েডীয় ইশারা ভাষা", + "br": "yezh ar sinoù svedek", + "ca": "llengua de signes sueca", + "cs": "švédský znakový jazyk", + "de": "Svenskt teckenspråk", + "en": "Shanghai Sign Language", + "en-gb": "Shanghai Sign Language", + "eo": "sveda signolingvo", + "fa": "زبان اشاره سوئدی", + "fi": "ruotsalainen viittomakieli", + "fr": "langue des signes suédoise", + "ga": "teanga chomharthaíochta Shang-hai", + "hr": "švedski znakovni jezik", + "ja": "上海手話", + "ku": "zimanê hêmayan ê swêdî", + "ku-latn": "zimanê hêmayan ê swêdî", + "la": "lingua gesticulatoria Suecica", + "lfn": "lingua de sinia svensce", + "nb_NO": "svensk tegnspråk", + "nl": "Swedish Sign Language", + "nn": "svensk teiknspråk", + "pl": "szwedzki język migowy", + "pms": "Lenga dij segn svedèisa", + "rm": "Lingua da segns svedaisa", + "sco": "Swadish Sign Leid", + "se": "ruoŧŧelaš seavagiella", + "sl": "švedski znakovni jezik", + "smn": "ruátálâš seevvimkielâ", + "sv": "svenskt teckenspråk", + "tok": "toki luka Sensa", + "tr": "Şanghay İşaret Dili", + "tt": "швед ишарә теле", + "zh": "瑞典手语", + "zh_Hans": "上海手语", + "zh_Hant": "上海手語" + }, + "sts": { + "be-tarask": "швэдзкая жэставая мова", + "bn": "সুয়েডীয় ইশারা ভাষা", + "br": "yezh ar sinoù svedek", + "ca": "llengua de signes sueca", + "cs": "švédský znakový jazyk", + "de": "Svenskt teckenspråk", + "en": "Swedish Sign Language", + "en-gb": "Swedish Sign Language", + "eo": "sveda signolingvo", + "fa": "زبان اشاره سوئدی", + "fi": "ruotsalainen viittomakieli", + "fr": "langue des signes suédoise", + "ga": "teanga chomharthaíochta na Sualainne", + "hr": "švedski znakovni jezik", + "ja": "スウェーデン手話", + "ku": "zimanê hêmayan ê swêdî", + "ku-latn": "zimanê hêmayan ê swêdî", + "la": "lingua gesticulatoria Suecica", + "lfn": "lingua de sinia svensce", + "nb_NO": "svensk tegnspråk", + "nl": "Swedish Sign Language", + "nn": "svensk teiknspråk", + "pl": "szwedzki język migowy", + "pms": "Lenga dij segn svedèisa", + "rm": "Lingua da segns svedaisa", + "sco": "Swadish Sign Leid", + "se": "ruoŧŧelaš seavagiella", + "sl": "švedski znakovni jezik", + "smn": "ruátálâš seevvimkielâ", + "sv": "svenskt teckenspråk", + "tok": "toki luka Sensa", + "tr": "İsveç İşaret Dili", + "tt": "швед ишарә теле", + "zh": "瑞典手语" + }, + "uzsl": { + "en": "Uzbek Sign Language", + "en-gb": "Uzbek Sign Language", + "ga": "teanga chomharthaíochta na hÚisbéiceastáine", + "ru": "узбекский жестовый язык", + "tr": "Özbek İşaret Dili", + "uz-cyrl": "ўзбек имо-ишора тили", + "uz-latn": "o‘zbek imo-ishora tili" + }, + "ужя": { + "en": "Uzbek Sign Language", + "en-gb": "Uzbek Sign Language", + "ga": "teanga chomharthaíochta na hÚisbéiceastáine", + "ru": "узбекский жестовый язык", + "tr": "Özbek İşaret Dili", + "uz-cyrl": "ўзбек имо-ишора тили", + "uz-latn": "o‘zbek imo-ishora tili" + }, + "ўит": { + "en": "Uzbek Sign Language", + "en-gb": "Uzbek Sign Language", + "ga": "teanga chomharthaíochta na hÚisbéiceastáine", + "ru": "узбекский жестовый язык", + "tr": "Özbek İşaret Dili", + "uz-cyrl": "ўзбек имо-ишора тили", + "uz-latn": "o‘zbek imo-ishora tili" + }, + "o‘it": { + "en": "Uzbek Sign Language", + "en-gb": "Uzbek Sign Language", + "ga": "teanga chomharthaíochta na hÚisbéiceastáine", + "ru": "узбекский жестовый язык", + "tr": "Özbek İşaret Dili", + "uz-cyrl": "ўзбек имо-ишора тили", + "uz-latn": "o‘zbek imo-ishora tili" + }, + "scsl": { + "en": "Shanghai Sign Language", + "en-gb": "Shanghai Sign Language", + "ga": "teanga chomharthaíochta Shang-hai", + "ja": "上海手話", + "tr": "Şanghay İşaret Dili", + "zh_Hans": "上海手语", + "zh_Hant": "上海手語" + }, + "shsl": { + "en": "Shanghai Sign Language", + "en-gb": "Shanghai Sign Language", + "ga": "teanga chomharthaíochta Shang-hai", + "ja": "上海手話", + "tr": "Şanghay İşaret Dili", + "zh_Hans": "上海手语", + "zh_Hant": "上海手語" + }, + "pngsl": { + "anp": "पीएनजी सांकेतिक भाषा", + "ar": "لغة الإشارة لبابوا غينيا الجديدة", + "ary": "لغة لإشارة د پاپوا غينيا الجديدة", + "be-tarask": "папуанская жэставая мова", + "ca": "Llengua de signes de Papua Nova Guinea", + "de": "Papua-Neuguinea-Gebärdensprache", + "el": "Νοηματική γλώσσα της Παπούα Νέας Γουινέας", + "en": "Papua New Guinean Sign Language", + "en-ca": "Papua New Guinean Sign Language", + "en-gb": "Papua New Guinean Sign Language", + "eo": "Papu-Nov-Gvinea gestolingvo", + "es": "Lengua de signos de Papúa Nueva Guinea", + "fa": "زبان اشاره پاپوآ گینه نویی", + "fi": "papuauusiguinealainen viittomakieli", + "fr": "langue des signes papouasienne", + "ga": "teanga chomharthaíochta Nua-Ghuine Phapua", + "gl": "Lingua de sinais de Papúa-Nova Guinea", + "he": "שפת הסימנים של פפואה גינאה החדשה", + "hi": "पापुआ न्यू गिनी सांकेतिक भाषा", + "ilo": "Pagsasao a Senias ti Papua Baro a Guinea", + "ja": "パプア・ニューギニア手話", + "pt": "Língua de sinais da Papua-Nova Guiné", + "sk": "Papuánsky znakový jazyk", + "sl": "papuanski znakovni jezik", + "tok": "toki luka Papuwanijukini", + "tr": "Papua Yeni Gine İşaret Dili", + "tt": "Папуа — Яңа Гвинея ишарә теле", + "vec": "Łéngua dei segni de Pàpua Nova Guinea" + }, + "hzj": { + "bn": "ক্রোয়েশীয় ইশারা ভাষা", + "de": "Kroatische Gebärdensprache", + "el": "Κροατική νοηματική γλώσσα", + "en": "Croatian Sign Language", + "en-ca": "Croatian Sign Language", + "en-gb": "Croatian Sign Language", + "eo": "kroata signolingvo", + "et": "Horvaadi viipekeel", + "fa": "زبان اشاره کرواتی", + "fr": "langue des signes croate", + "ga": "teanga chomharthaíochta na Cróite", + "hr": "hrvatski znakovni jezik", + "ig": "Asụsụ Ogbi nke Croatia", + "ja": "クロアチア手話", + "lfn": "lingua de sinia corvatsce", + "nl": "Kroatische gebarentaal", + "pms": "Lenga dij segn croata", + "pt": "Língua de sinais croata", + "pt_BR": "Língua de sinais croata", + "ru": "хорватский жестовый язык", + "tok": "toki luka Lowasi", + "tr": "Hırvat İşaret Dili", + "tt": "хорват ишарә теле", + "uk": "хорватська мова жестів", + "zh": "克罗地亚手语" + }, + "sgsl": { + "en": "Singapore Sign Language", + "en-gb": "Singapore Sign Language", + "eo": "singapura signolingvo", + "fa": "زبان اشاره سنگاپوری", + "ga": "teanga chomharthaíochta Shingeapór", + "id": "Bahasa Isyarat Singapura", + "ja": "シンガポール手話", + "lfn": "lingua de sinia singapor", + "pms": "Lenga dij segn ëd Singapor", + "pt": "Língua de sinais de Singapura", + "tok": "toki luka Sinkapula", + "tr": "Singapor İşaret Dili", + "zh": "新加坡手语" + }, + "tal": { + "bn": "তানজানীয় সাংকেতিক ভাষাসমূহ", + "de": "Tansanische Gebärdensprache", + "en": "Tanzanian Sign Language", + "en-gb": "Tanzanian Sign Language", + "eo": "tanzania signolingvo", + "fa": "زبانهای اشاره تانزانیایی", + "fr": "langue des signes tanzanienne", + "ga": "teanga chomharthaíochta na Tansáine", + "ig": "Asụsụ Ndị Ogbi Tanzania", + "ja": "タンザニア手話", + "lfn": "lingua de sinia tanzanian", + "nl": "Tanziaanse gebarentaal", + "pms": "Lenga dij segn tanzanian-a", + "pt": "Língua de Sinais da Tanzânia", + "scn": "Lingua dî Signa Tanzania", + "sw": "Lugha ya Alama Tanzania", + "tok": "toki luka Tansanija", + "tr": "Tanzanya İşaret Dili", + "tt": "Танзания ишарә теле" + }, + "pjm": { + "bn": "পোলীয় ইশারা ভাষা", + "br": "yezh ar sinoù polonek", + "ca": "llengua de signes polonesa", + "de": "Polnische Gebärdensprache", + "el": "πολωνική νοηματική γλώσσα", + "en": "Polish Sign Language", + "en-gb": "Polish Sign Language", + "eo": "pola signolingvo", + "fa": "زبان اشاره لهستانی", + "fr": "langue des signes polonaise", + "ga": "teanga chomharthaíochta na Polainne", + "ja": "ポーランド手話", + "ku": "zimanê hêmayan ê polonî", + "ku-latn": "zimanê hêmayan ê polonî", + "lfn": "lingua de sinia polsce", + "nan": "Pho-lân Chhiú-gí", + "nl": "Pools gebarentaal", + "pl": "Polski Język Migowy", + "pms": "Lenga dij segn dla Polònia", + "ru": "польский жестовый язык", + "sl": "poljski znakovni jezik", + "tok": "toki luka Posuka", + "tr": "Polonya İşaret Dili", + "zh-tw": "波蘭手語", + "zh_Hans": "波兰手语", + "zh_Hant": "波蘭手語" + }, + "hsl": { + "bn": "হাউসা ইশারা ভাষা", + "de": "Hausa-Gebärdensprache", + "en": "Hausa Sign Language", + "en-gb": "Hausa Sign Language", + "eo": "haŭsa signolingvo", + "ff": "Hausa Sign Language", + "fr": "langue des signes haoussa", + "ga": "teanga chomharthaíochta Hásaise", + "ha": "Maganar hannu", + "hi": "हौसा सांकेतिक भाषा", + "ig": "Hausa Sign Language", + "ja": "ハウサ手話", + "lfn": "lingua de sinia hausa", + "lmo": "lèngua d'i sègn hausa", + "mt": "Lingwa tas-Sinjali Ħawsija", + "pa": "ਹੌਸਾ ਚਿਹਨ ਭਾਸ਼ਾ", + "pms": "lenga dij segn hausa", + "pt": "Língua Gestual Hausa", + "scn": "Lingua dî Signa Hausa", + "tr": "Hevsâ İşaret Dili" + }, + "lgp": { + "bn": "পর্তুগীজ ইশারা ভাষা", + "br": "yezh ar sinoù portugalek", + "ca": "llengua de signes portuguesa", + "cy": "Iaith Arwyddo Portiwgal", + "de": "Portugiesische Gebärdensprache", + "eml": "lèngva di sègn purtughéśa", + "en": "Portuguese Sign Language", + "en-gb": "Portuguese Sign Language", + "eo": "portugala signolingvo", + "es": "lengua de señas portuguesa", + "eu": "Portugalgo keinu hizkuntza", + "fa": "زبان اشاره پرتغالی", + "fi": "portugalilainen viittomakieli", + "fr": "langue des signes portugaise", + "fur": "lenghe dai segns portughêse", + "ga": "teanga chomharthaíochta na Portaingéile", + "id": "Bahasa Isyarat Portugis", + "it": "Lingua dei segni portoghese", + "ja": "ポルトガル手話", + "la": "lingua gesticulatoria Portugallensis", + "lfn": "lingua de sinia portuges", + "nb_NO": "portugisisk tegnspråk", + "nl": "Portugese gebarentaal", + "pms": "lenga dij segn portoghèisa", + "pnb": "پرتگالی سینت بولی", + "pt": "língua gestual portuguesa", + "sc": "Limba de sos Sinnos Portughesa", + "scn": "Lingua dî Signa Purtughisi", + "sco": "Portuguese Sign Leid", + "sl": "portugalski znakovni jezik", + "tok": "toki luka Potuke", + "tr": "Portekiz İşaret Dili", + "tt": "португал ишарә теле", + "zh": "葡萄牙语手语", + "zh_Hans": "葡萄牙语手语", + "zh_Hant": "葡萄牙語手語" + }, + "finssl": { + "bn": "ফিনল্যান্ড-সুয়েডীয় ইশারা ভাষা", + "cs": "Finsko-švédský znakový jazyk", + "de": "finnisch-schwedische Gebärdensprache", + "en": "Finland-Swedish Sign Language", + "en-gb": "Finland-Swedish Sign Language", + "eo": "finna-sveda gestolingvo", + "fa": "زبان اشاره فنلاند-سوئدی", + "fi": "suomenruotsalainen viittomakieli", + "fr": "langue des signes finno-suédoise", + "ga": "teanga chomharthaíochta Fhionlannach-Shualannach", + "ha": "Finland-Yaren Kurame na Sweden", + "he": "שפת הסימנים פינלנד-שוודית", + "hi": "फिनलैंड-स्वेडिश सांकेतिक भाषा", + "hr": "finsko-švedski znakovni jezik", + "it": "lingua dei segni svedese-finlandese", + "ja": "フィンランド=スウェーデン手話", + "nb_NO": "finlandssvensk tegnspråk", + "pms": "lenga dij segn finlandèisa-svedèisa", + "se": "suomaruoŧŧelaš seavagiella", + "sl": "finsko-švedski znakovni jezik", + "smn": "suomâruátálâš seevvimkielâ", + "sv": "finlandssvenskt teckenspråk", + "zh": "芬兰瑞典语手语", + "zh_Hans": "芬兰瑞典语手语", + "zh_Hant": "芬蘭瑞典語手語" + } } \ No newline at end of file From 03d07b670de7fa84c0e58f647073f67fb6d60046 Mon Sep 17 00:00:00 2001 From: Robin van der Linde Date: Sat, 23 Aug 2025 19:21:09 +0200 Subject: [PATCH 30/92] Themes(parking): Add support for access tags (#1797) --- .../layers/parking_spaces/parking_spaces.json | 89 ++++++++++++++++++- 1 file changed, 87 insertions(+), 2 deletions(-) diff --git a/assets/layers/parking_spaces/parking_spaces.json b/assets/layers/parking_spaces/parking_spaces.json index 64e5f907e..0c36e859b 100644 --- a/assets/layers/parking_spaces/parking_spaces.json +++ b/assets/layers/parking_spaces/parking_spaces.json @@ -69,6 +69,15 @@ "icon": { "render": "./assets/layers/parking_spaces/parking_space.svg", "mappings": [ + { + "if": { + "or": [ + "access=private", + "access=no" + ] + }, + "then": "./assets/layers/parking_spaces/parking_space_private.svg" + }, { "if": "parking_space=disabled", "then": "./assets/layers/toilet/wheelchair.svg" @@ -99,7 +108,7 @@ ], "lineRendering": [ { - "color": "#696969", + "color": "dimgray", "width": "1" } ], @@ -295,6 +304,69 @@ "it": "Questo è un posto auto riservato al car sharing.", "nl": "Deze parkeerplek is gereserveerd voor autodelen." } + }, + { + "if": "parking_space=women", + "then": { + "en": "This is a parking space reserved for women.", + "nl": "Deze parkeerplek is gereserveerd voor vrouwen." + } + } + ] + }, + { + "id": "access", + "question": { + "en": "Who can use this parking space?", + "nl": "Wie mag deze parkeerplek gebruiken?" + }, + "render": { + "en": "Access of parking space: {access}", + "nl": "Toegang tot parkeerplek: {access}" + }, + "freeform": { + "key": "access", + "type": "string", + "addExtraTags": [ + "fixme=Freeform used on 'access'-tag: possibly a wrong value" + ] + }, + "mappings": [ + { + "if": "access=", + "then": { + "en": "Anyone can use this parking space.", + "nl": "Iedereen kan deze parkeerplek gebruiken." + }, + "hideInAnswer": true + }, + { + "if": "access=yes", + "then": { + "en": "Anyone can use this parking space.", + "nl": "Iedereen kan deze parkeerplek gebruiken." + } + }, + { + "if": "access=customers", + "then": { + "en": "This parking space is reserved for customers.", + "nl": "Deze parkeerplek is gereserveerd voor klanten." + } + }, + { + "if": "access=private", + "then": { + "en": "This parking space is private and cannot be used by the general public.", + "nl": "Deze parkeerplek is privé en mag niet door het grote publiek worden gebruikt." + } + }, + { + "if": "access=permit", + "then": { + "en": "This parking space is reserved for permit holders.", + "nl": "Deze parkeerplek is gereserveerd voor vergunninghouders." + } } ] }, @@ -310,6 +382,19 @@ "nl": "Deze parkeerplek heeft {capacity} plaatsen." }, "mappings": [ + { + "if": "capacity=", + "then": { + "en": "This parking space has 1 space.", + "ca": "Aquest espai d'aparcament té 1 plaça.", + "cs": "Toto parkoviště má 1 místo.", + "de": "Dieser Parkplatz hat 1 Stellplatz.", + "es": "Esta plaza de aparcamiento tiene 1 plaza.", + "it": "Questo posto auto ha 1 spazio.", + "nl": "Deze parkeerplek heeft 1 plaats." + }, + "hideInAnswer": true + }, { "if": "capacity=1", "then": { @@ -329,4 +414,4 @@ "enableImproveAccuracy": true, "enableRelocation": false } -} +} \ No newline at end of file From 4294e4930509bb8157597d3d6242046a5fa14e8d Mon Sep 17 00:00:00 2001 From: Robin van der Linde Date: Sun, 24 Aug 2025 00:20:29 +0200 Subject: [PATCH 31/92] Themes(nature): Add picnic sites (#1849) --- assets/layers/picnic_site/picnic_site.json | 320 +++++++++++++++++++++ assets/themes/nature/nature.json | 3 +- 2 files changed, 322 insertions(+), 1 deletion(-) create mode 100644 assets/layers/picnic_site/picnic_site.json diff --git a/assets/layers/picnic_site/picnic_site.json b/assets/layers/picnic_site/picnic_site.json new file mode 100644 index 000000000..d29d94bb4 --- /dev/null +++ b/assets/layers/picnic_site/picnic_site.json @@ -0,0 +1,320 @@ +{ + "id": "picnic_site", + "name": { + "en": "Picnic sites", + "nl": "Picknickplaatsen" + }, + "description": { + "en": "Picnic sites for eating outdoors, featuring amenities like toilets, water taps, BBQ, benches and shelters", + "nl": "Picknickplaatsen voor het eten in de buitenlucht, met voorzieningen zoals toiletten, waterkranen, BBQ, banken en schuilplaatsen" + }, + "source": { + "osmTags": "tourism=picnic_site" + }, + "minzoom": 10, + "title": { + "render": { + "en": "Picnic site", + "nl": "Picknickplaats" + } + }, + "pointRendering": [ + { + "iconSize": "35,35", + "location": [ + "point", + "centroid" + ], + "anchor": "center", + "marker": [ + { + "color": "#3984e6", + "icon": "circle" + }, + { + "icon": "./assets/layers/picnic_table/picnic_table.svg" + } + ] + } + ], + "lineRendering": [ + { + "color": "#3984e6", + "fillColor": "#3984e6bd", + "width": 5 + } + ], + "presets": [ + { + "tags": [ + "tourism=picnic_site" + ], + "title": { + "en": "a picnic site", + "nl": "een picknickplaats" + }, + "description": { + "en": "A picnic site for eating outdoors, featuring amenities like toilets, water taps, BBQ, benches and shelters", + "nl": "Een picknickplaats voor het eten in de buitenlucht, met voorzieningen zoals toiletten, waterkranen, BBQ, banken en schuilplaatsen" + } + } + ], + "tagRenderings": [ + "images", + { + "builtin": "name", + "override": { + "render": { + "en": "This picnic site is called {name}", + "nl": "Deze picknickplaats heet {name}" + } + } + }, + { + "id": "shelter", + "question": { + "en": "Does this picnic site have a shelter?", + "nl": "Heeft deze picknickplaats een schuilplaats?" + }, + "mappings": [ + { + "if": "shelter=yes", + "then": { + "en": "This picnic site has a shelter.", + "nl": "Deze picknickplaats heeft een schuilplaats." + } + }, + { + "if": "shelter=no", + "then": { + "en": "This picnic site does not have a shelter.", + "nl": "Deze picknickplaats heeft geen schuilplaats." + } + }, + { + "if": "shelter=separate", + "then": { + "en": "This picnic site has a shelter, but is is mapped as a different icon.", + "nl": "Deze picknickplaats heeft een schuilplaats, maar deze staat los op de kaart." + } + } + ] + }, + { + "id": "fireplace", + "question": { + "en": "Does this picnic site have a firepit?", + "nl": "Heeft deze picknickplaats een vuurplaats?" + }, + "mappings": [ + { + "if": "fireplace=yes", + "then": { + "en": "This picnic site has a firepit.", + "nl": "Deze picknickplaats heeft een vuurplaats." + } + }, + { + "if": "fireplace=no", + "then": { + "en": "This picnic site does not have a firepit.", + "nl": "Deze picknickplaats heeft geen vuurplaats." + } + }, + { + "if": "fireplace=separate", + "then": { + "en": "This picnic site has a firepit, but it is mapped as a different icon.", + "nl": "Deze picknickplaats heeft een vuurplaats, maar deze staat los op de kaart." + } + } + ] + }, + { + "id": "bbq", + "question": { + "en": "Does this picnic site have a BBQ?", + "nl": "Heeft deze picknickplaats een BBQ?" + }, + "mappings": [ + { + "if": "bbq=yes", + "then": { + "en": "This picnic site has a BBQ.", + "nl": "Deze picknickplaats heeft een BBQ." + } + }, + { + "if": "bbq=no", + "then": { + "en": "This picnic site does not have a BBQ.", + "nl": "Deze picknickplaats heeft geen BBQ." + } + }, + { + "if": "bbq=separate", + "then": { + "en": "This picnic site has a BBQ, but it is mapped as a different icon.", + "nl": "Deze picknickplaats heeft een BBQ, maar deze staat los op de kaart." + } + } + ] + }, + { + "id": "covered", + "question": { + "en": "Is this picnic site covered?", + "nl": "Is deze picknickplaats overdekt?" + }, + "mappings": [ + { + "if": "covered=yes", + "then": { + "en": "This picnic site is covered.", + "nl": "Deze picknickplaats is overdekt." + } + }, + { + "if": "covered=no", + "then": { + "en": "This picnic site is not covered.", + "nl": "Deze picknickplaats is niet overdekt." + } + } + ] + }, + { + "id": "drinking_water", + "question": { + "en": "Does this picnic site have drinking water?", + "nl": "Heeft deze picknickplaats drinkwater?" + }, + "mappings": [ + { + "if": "drinking_water=yes", + "then": { + "en": "This picnic site has drinking water.", + "nl": "Deze picknickplaats heeft drinkwater." + } + }, + { + "if": "drinking_water=no", + "then": { + "en": "This picnic site does not have drinking water.", + "nl": "Deze picknickplaats heeft geen drinkwater." + } + }, + { + "if": "drinking_water=separate", + "then": { + "en": "This picnic site has drinking water, but it is mapped as a different icon.", + "nl": "Deze picknickplaats heeft drinkwater, maar deze staat los op de kaart." + } + } + ] + }, + { + "id": "openfire", + "question": { + "en": "Is open fire allowed at this picnic site?", + "nl": "Is open vuur toegestaan op deze picknickplaats?" + }, + "mappings": [ + { + "if": "openfire=yes", + "then": { + "en": "Open fire is allowed at this picnic site.", + "nl": "Open vuur is toegestaan op deze picknickplaats." + } + }, + { + "if": "openfire=no", + "then": { + "en": "Open fire is not allowed at this picnic site.", + "nl": "Open vuur is niet toegestaan op deze picknickplaats." + } + }, + { + "if": "openfire=permit", + "then": { + "en": "Open fire is allowed at this picnic site with a permit.", + "nl": "Open vuur is toegestaan op deze picknickplaats met een vergunning." + } + } + ] + } + ], + "filter": [ + { + "id": "shelter", + "options": [ + { + "question": { + "en": "With a shelter", + "nl": "Met een schuilplaats" + }, + "osmTags": { + "or": [ + "shelter=yes", + "shelter=separate" + ] + } + } + ] + }, + { + "id": "fireplace", + "options": [ + { + "question": { + "en": "With a firepit", + "nl": "Met een vuurplaats" + }, + "osmTags": { + "or": [ + "fireplace=yes", + "fireplace=separate" + ] + } + } + ] + }, + { + "id": "bbq", + "options": [ + { + "question": { + "en": "With a BBQ", + "nl": "Met een BBQ" + }, + "osmTags": { + "or": [ + "bbq=yes", + "bbq=separate" + ] + } + } + ] + }, + { + "id": "drinking_water", + "options": [ + { + "question": { + "en": "With drinking water", + "nl": "Met drinkwater" + }, + "osmTags": { + "or": [ + "drinking_water=yes", + "drinking_water=separate" + ] + } + } + ] + } + ], + "allowMove": { + "enableImproveAccuracy": true + } +} \ No newline at end of file diff --git a/assets/themes/nature/nature.json b/assets/themes/nature/nature.json index 7158a1d5e..72798228d 100644 --- a/assets/themes/nature/nature.json +++ b/assets/themes/nature/nature.json @@ -66,6 +66,7 @@ "minzoom": 11 } }, + "picnic_site", { "builtin": [ "map", @@ -95,4 +96,4 @@ "observation_tower", "viewpoint" ] -} +} \ No newline at end of file From 48a0a5b5205f345edf32d2144fca77a723553664 Mon Sep 17 00:00:00 2001 From: Robin van der Linde Date: Sun, 24 Aug 2025 11:28:46 +0200 Subject: [PATCH 32/92] WIP: test workaround --- src/Logic/Tags/And.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Logic/Tags/And.ts b/src/Logic/Tags/And.ts index b1b69b8ab..77ec8e5bb 100644 --- a/src/Logic/Tags/And.ts +++ b/src/Logic/Tags/And.ts @@ -314,6 +314,14 @@ export class And extends TagsFilter { for (let j = i + 1; j < optimized.length; j++) { const ti = optimized[i] const tj = optimized[j] + if ( + !ti || + !tj || + typeof ti.shadows !== "function" || + typeof tj.shadows !== "function" + ) { + continue + } if (ti.shadows(tj)) { // if 'ti' is true, this implies 'tj' is always true as well. // if 'ti' is false, then 'tj' might be true or false @@ -322,6 +330,7 @@ export class And extends TagsFilter { // If 'ti' is true, then 'tj' will be true too and 'tj' can be ignored // If 'ti' is false, then the entire expression will be false and it doesn't matter what 'tj' yields optimized.splice(j, 1) + j-- } else if (tj.shadows(ti)) { optimized.splice(i, 1) i-- From 2c6574762c30d60440806ace3f9e3320f771c9a4 Mon Sep 17 00:00:00 2001 From: Robin van der Linde Date: Sun, 24 Aug 2025 23:08:16 +0200 Subject: [PATCH 33/92] Themes: New arcade theme --- assets/layers/arcade/arcade.json | 115 ++++++++++++++++++++++++ assets/layers/arcade/arcade.svg | 3 + assets/layers/arcade/arcade.svg.license | 2 + assets/layers/arcade/license_info.json | 12 +++ assets/themes/arcade/arcade.json | 13 +++ 5 files changed, 145 insertions(+) create mode 100644 assets/layers/arcade/arcade.json create mode 100644 assets/layers/arcade/arcade.svg create mode 100644 assets/layers/arcade/arcade.svg.license create mode 100644 assets/layers/arcade/license_info.json create mode 100644 assets/themes/arcade/arcade.json diff --git a/assets/layers/arcade/arcade.json b/assets/layers/arcade/arcade.json new file mode 100644 index 000000000..2ab70ba65 --- /dev/null +++ b/assets/layers/arcade/arcade.json @@ -0,0 +1,115 @@ +{ + "id": "arcade", + "name": { + "en": "Arcades" + }, + "description": { + "en": "Layer showing arcades" + }, + "source": { + "osmTags": "leisure=amusement_arcade" + }, + "minzoom": 10, + "title": { + "render": { + "en": "Arcade" + }, + "mappings": [ + { + "if": "name~*", + "then": { + "*": "{name}" + } + } + ] + }, + "pointRendering": [ + { + "location": [ + "point", + "centroid" + ], + "marker": [ + { + "icon": "square", + "color": "white" + }, + { + "icon": "./assets/layers/arcade/arcade.svg" + } + ] + } + ], + "lineRendering": [ + { + "width": 3, + "color": "#0e8517" + } + ], + "presets": [ + { + "title": { + "en": "an arcade" + }, + "tags": [ + "leisure=amusement_arcade" + ] + } + ], + "tagRenderings": [ + "images", + "reviews", + { + "builtin": "name", + "override": { + "question": { + "en": "What is the name of this arcade?" + }, + "render": { + "en": "This arcade is called {name}" + } + } + }, + { + "id": "virtual_reality", + "question": { + "en": "Does this arcade offer virtual-reality gaming?" + }, + "mappings": [ + { + "if": "virtual_reality=yes", + "then": { + "en": "This arcade offers virtual-reality gaming." + } + }, + { + "if": "virtual_reality=only", + "then": { + "en": "This arcade only offers virtual-reality gaming." + } + }, + { + "if": "virtual_reality=", + "then": { + "en": "This arcade doesn't offer virtual-reality gaming" + } + } + ] + }, + "brand", + "opening_hours", + "website", + "email", + "phone", + "payment-options", + "level", + "description", + "toilet_at_amenity_lib.all" + ], + "allowMove": { + "enableImproveAccuracy": true, + "enableRelocation": true + }, + "credits": "Robin van der Linde", + "credits:uid": 5093765 +} \ No newline at end of file diff --git a/assets/layers/arcade/arcade.svg b/assets/layers/arcade/arcade.svg new file mode 100644 index 000000000..5f4031683 --- /dev/null +++ b/assets/layers/arcade/arcade.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/assets/layers/arcade/arcade.svg.license b/assets/layers/arcade/arcade.svg.license new file mode 100644 index 000000000..ff6ee9492 --- /dev/null +++ b/assets/layers/arcade/arcade.svg.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: meased +SPDX-License-Identifier: CC0-1.0 \ No newline at end of file diff --git a/assets/layers/arcade/license_info.json b/assets/layers/arcade/license_info.json new file mode 100644 index 000000000..f82b95ae4 --- /dev/null +++ b/assets/layers/arcade/license_info.json @@ -0,0 +1,12 @@ +[ + { + "path": "arcade.svg", + "license": "CC0-1.0", + "authors": [ + "meased" + ], + "sources": [ + "https://github.com/gravitystorm/openstreetmap-carto/blob/master/symbols/leisure/amusement_arcade.svg" + ] + } +] \ No newline at end of file diff --git a/assets/themes/arcade/arcade.json b/assets/themes/arcade/arcade.json new file mode 100644 index 000000000..137cdf86a --- /dev/null +++ b/assets/themes/arcade/arcade.json @@ -0,0 +1,13 @@ +{ + "id": "arcade", + "title": { + "en": "Arcades" + }, + "description": { + "en": "A map of arcades" + }, + "icon": "./assets/layers/arcade/arcade.svg", + "layers": [ + "arcade" + ] +} \ No newline at end of file From eb317d0bdabb0e5cf1dc4a871d65757a432c4de3 Mon Sep 17 00:00:00 2001 From: Robin van der Linde Date: Sun, 24 Aug 2025 23:11:19 +0200 Subject: [PATCH 34/92] Themes: move shelter filter to filters --- assets/layers/filters/filters.json | 22 ++++++++++++++++ assets/layers/picnic_site/picnic_site.json | 18 +------------ assets/layers/questions/questions.json | 5 ++-- .../layers/transit_stops/transit_stops.json | 25 ++----------------- 4 files changed, 28 insertions(+), 42 deletions(-) diff --git a/assets/layers/filters/filters.json b/assets/layers/filters/filters.json index 67556c5e1..f58d3333b 100644 --- a/assets/layers/filters/filters.json +++ b/assets/layers/filters/filters.json @@ -571,6 +571,28 @@ } } ] + }, + { + "id": "shelter", + "options": [ + { + "osmTags": { + "or": [ + "shelter=yes", + "shelter=separate" + ] + }, + "question": { + "en": "With a shelter", + "ca": "Amb refugi", + "cs": "S přístřeškem", + "de": "Mit Unterstand", + "es": "Con refugio", + "fr": "Avec un abri", + "it": "Con una pensilina" + } + } + ] } ], "allowMove": false diff --git a/assets/layers/picnic_site/picnic_site.json b/assets/layers/picnic_site/picnic_site.json index d29d94bb4..ef02be70b 100644 --- a/assets/layers/picnic_site/picnic_site.json +++ b/assets/layers/picnic_site/picnic_site.json @@ -245,23 +245,7 @@ } ], "filter": [ - { - "id": "shelter", - "options": [ - { - "question": { - "en": "With a shelter", - "nl": "Met een schuilplaats" - }, - "osmTags": { - "or": [ - "shelter=yes", - "shelter=separate" - ] - } - } - ] - }, + "shelter", { "id": "fireplace", "options": [ diff --git a/assets/layers/questions/questions.json b/assets/layers/questions/questions.json index 6b56671d2..368eb1cff 100644 --- a/assets/layers/questions/questions.json +++ b/assets/layers/questions/questions.json @@ -3116,7 +3116,8 @@ "if": "nobrand=yes", "addExtraTags": [ "brand=", - "brand:wikidata=" + "brand:wikidata=", + "brand:wikipedia=" ], "then": { "en": "Not part of a bigger brand", @@ -3652,4 +3653,4 @@ } } ] -} +} \ No newline at end of file diff --git a/assets/layers/transit_stops/transit_stops.json b/assets/layers/transit_stops/transit_stops.json index 096ce247e..822d21745 100644 --- a/assets/layers/transit_stops/transit_stops.json +++ b/assets/layers/transit_stops/transit_stops.json @@ -544,28 +544,7 @@ } ], "filter": [ - { - "id": "shelter", - "options": [ - { - "osmTags": { - "or": [ - "shelter=yes", - "shelter=separate" - ] - }, - "question": { - "en": "With a shelter", - "ca": "Amb refugi", - "cs": "S přístřeškem", - "de": "Mit Unterstand", - "es": "Con refugio", - "fr": "Avec un abri", - "it": "Con una pensilina" - } - } - ] - }, + "shelter", { "id": "bench", "options": [ @@ -613,4 +592,4 @@ "tactile_paving" ], "allowMove": false -} +} \ No newline at end of file From 25dfc80535dbcf0cc06703ccc9dbbf7b70c085ab Mon Sep 17 00:00:00 2001 From: Robin van der Linde Date: Sun, 24 Aug 2025 23:11:45 +0200 Subject: [PATCH 35/92] Chore: reset translations --- langs/layers/ca.json | 17 ++-- langs/layers/cs.json | 17 ++-- langs/layers/de.json | 17 ++-- langs/layers/en.json | 208 +++++++++++++++++++++++++++++++++++++++++-- langs/layers/es.json | 17 ++-- langs/layers/fr.json | 14 +-- langs/layers/it.json | 17 ++-- langs/layers/nl.json | 151 +++++++++++++++++++++++++++++++ langs/themes/en.json | 4 + 9 files changed, 413 insertions(+), 49 deletions(-) diff --git a/langs/layers/ca.json b/langs/layers/ca.json index e4ac8aee9..dc8729b1d 100644 --- a/langs/layers/ca.json +++ b/langs/layers/ca.json @@ -5520,6 +5520,13 @@ } } }, + "23": { + "options": { + "0": { + "question": "Amb refugi" + } + } + }, "3": { "options": { "0": { @@ -8006,6 +8013,9 @@ "mappings": { "0": { "then": "Aquest espai d'aparcament té 1 plaça." + }, + "1": { + "then": "Aquest espai d'aparcament té 1 plaça." } }, "render": "Aquests espais d'aparcament tenen {capacity} places." @@ -12449,13 +12459,6 @@ "transit_stops": { "description": "Capa que mostra diferents tipus de parades de transport públic.", "filter": { - "0": { - "options": { - "0": { - "question": "Amb refugi" - } - } - }, "1": { "options": { "0": { diff --git a/langs/layers/cs.json b/langs/layers/cs.json index 7b6cc195a..68be5705c 100644 --- a/langs/layers/cs.json +++ b/langs/layers/cs.json @@ -5824,6 +5824,13 @@ } } }, + "23": { + "options": { + "0": { + "question": "S přístřeškem" + } + } + }, "3": { "options": { "0": { @@ -8616,6 +8623,9 @@ "mappings": { "0": { "then": "Toto parkoviště má 1 místo." + }, + "1": { + "then": "Toto parkoviště má 1 místo." } }, "render": "Toto parkoviště má {capacity} míst." @@ -13507,13 +13517,6 @@ "transit_stops": { "description": "Vrstva zobrazující různé typy zastávek veřejné dopravy.", "filter": { - "0": { - "options": { - "0": { - "question": "S přístřeškem" - } - } - }, "1": { "options": { "0": { diff --git a/langs/layers/de.json b/langs/layers/de.json index 27c384de1..f79fd65c3 100644 --- a/langs/layers/de.json +++ b/langs/layers/de.json @@ -5498,6 +5498,13 @@ } } }, + "23": { + "options": { + "0": { + "question": "Mit Unterstand" + } + } + }, "3": { "options": { "0": { @@ -7970,6 +7977,9 @@ "mappings": { "0": { "then": "Dieser Parkplatz hat 1 Stellplatz." + }, + "1": { + "then": "Dieser Parkplatz hat 1 Stellplatz." } }, "render": "Dieser Parkplatz hat {capacity} Stellplätze." @@ -12438,13 +12448,6 @@ "transit_stops": { "description": "Ebene mit verschiedenen Arten von Haltestellen.", "filter": { - "0": { - "options": { - "0": { - "question": "Mit Unterstand" - } - } - }, "1": { "options": { "0": { diff --git a/langs/layers/en.json b/langs/layers/en.json index ee422ee16..97fa2e0cb 100644 --- a/langs/layers/en.json +++ b/langs/layers/en.json @@ -531,6 +531,40 @@ "render": "Animal shelter" } }, + "arcade": { + "description": "Layer showing arcades", + "name": "Arcades", + "presets": { + "0": { + "title": "an arcade" + } + }, + "tagRenderings": { + "name": { + "override": { + "question": "What is the name of this arcade?", + "render": "This arcade is called {name}" + } + }, + "virtual_reality": { + "mappings": { + "0": { + "then": "This arcade offers virtual-reality gaming." + }, + "1": { + "then": "This arcade only offers virtual-reality gaming." + }, + "2": { + "then": "This arcade doesn't offer virtual-reality gaming" + } + }, + "question": "Does this arcade offer virtual-reality gaming?" + } + }, + "title": { + "render": "Arcade" + } + }, "artwork": { "description": "An open map of statues, busts, graffitis and other artwork all over the world", "name": "Artworks", @@ -5848,6 +5882,13 @@ } } }, + "23": { + "options": { + "0": { + "question": "With a shelter" + } + } + }, "3": { "options": { "0": { @@ -8636,10 +8677,34 @@ "description": "Layer showing individual parking spaces.", "name": "Parking Spaces", "tagRenderings": { + "access": { + "mappings": { + "0": { + "then": "Anyone can use this parking space." + }, + "1": { + "then": "Anyone can use this parking space." + }, + "2": { + "then": "This parking space is reserved for customers." + }, + "3": { + "then": "This parking space is private and cannot be used by the general public." + }, + "4": { + "then": "This parking space is reserved for permit holders." + } + }, + "question": "Who can use this parking space?", + "render": "Access of parking space: {access}" + }, "capacity": { "mappings": { "0": { "then": "This parking space has 1 space." + }, + "1": { + "then": "This parking space has 1 space." } }, "render": "This parking spaces has {capacity} spaces." @@ -8664,6 +8729,9 @@ "13": { "then": "This is a parking space reserved for car sharing." }, + "14": { + "then": "This is a parking space reserved for women." + }, "2": { "then": "This is a disabled parking space." }, @@ -8795,6 +8863,130 @@ "render": "Physiotherapist {name}" } }, + "picnic_site": { + "description": "Picnic sites for eating outdoors, featuring amenities like toilets, water taps, BBQ, benches and shelters", + "filter": { + "1": { + "options": { + "0": { + "question": "With a firepit" + } + } + }, + "2": { + "options": { + "0": { + "question": "With a BBQ" + } + } + }, + "3": { + "options": { + "0": { + "question": "With drinking water" + } + } + } + }, + "name": "Picnic sites", + "presets": { + "0": { + "description": "A picnic site for eating outdoors, featuring amenities like toilets, water taps, BBQ, benches and shelters", + "title": "a picnic site" + } + }, + "tagRenderings": { + "bbq": { + "mappings": { + "0": { + "then": "This picnic site has a BBQ." + }, + "1": { + "then": "This picnic site does not have a BBQ." + }, + "2": { + "then": "This picnic site has a BBQ, but it is mapped as a different icon." + } + }, + "question": "Does this picnic site have a BBQ?" + }, + "covered": { + "mappings": { + "0": { + "then": "This picnic site is covered." + }, + "1": { + "then": "This picnic site is not covered." + } + }, + "question": "Is this picnic site covered?" + }, + "drinking_water": { + "mappings": { + "0": { + "then": "This picnic site has drinking water." + }, + "1": { + "then": "This picnic site does not have drinking water." + }, + "2": { + "then": "This picnic site has drinking water, but it is mapped as a different icon." + } + }, + "question": "Does this picnic site have drinking water?" + }, + "fireplace": { + "mappings": { + "0": { + "then": "This picnic site has a firepit." + }, + "1": { + "then": "This picnic site does not have a firepit." + }, + "2": { + "then": "This picnic site has a firepit, but it is mapped as a different icon." + } + }, + "question": "Does this picnic site have a firepit?" + }, + "name": { + "override": { + "render": "This picnic site is called {name}" + } + }, + "openfire": { + "mappings": { + "0": { + "then": "Open fire is allowed at this picnic site." + }, + "1": { + "then": "Open fire is not allowed at this picnic site." + }, + "2": { + "then": "Open fire is allowed at this picnic site with a permit." + } + }, + "question": "Is open fire allowed at this picnic site?" + }, + "shelter": { + "mappings": { + "0": { + "then": "This picnic site has a shelter." + }, + "1": { + "then": "This picnic site does not have a shelter." + }, + "2": { + "then": "This picnic site has a shelter, but is is mapped as a different icon." + } + }, + "question": "Does this picnic site have a shelter?" + } + }, + "title": { + "render": "Picnic site" + } + }, "picnic_table": { "description": "The layer showing picnic tables", "name": "Picnic tables", @@ -11263,6 +11455,15 @@ "second_hand": { "question": "Does this shop sell second-hand items?" }, + "self_checkout": { + "override": { + "+mappings": { + "0": { + "then": "This shop (probably) does not offer self-checkout" + } + } + } + }, "sells_new_bikes": { "mappings": { "0": { @@ -13589,13 +13790,6 @@ "transit_stops": { "description": "Layer showing different types of transit stops.", "filter": { - "0": { - "options": { - "0": { - "question": "With a shelter" - } - } - }, "1": { "options": { "0": { diff --git a/langs/layers/es.json b/langs/layers/es.json index 0baced4d3..183f4966a 100644 --- a/langs/layers/es.json +++ b/langs/layers/es.json @@ -5162,6 +5162,13 @@ } } }, + "23": { + "options": { + "0": { + "question": "Con refugio" + } + } + }, "3": { "options": { "0": { @@ -7594,6 +7601,9 @@ "mappings": { "0": { "then": "Esta plaza de aparcamiento tiene 1 plaza." + }, + "1": { + "then": "Esta plaza de aparcamiento tiene 1 plaza." } }, "render": "Esta plaza de aparcamiento tiene {capacity} plazas." @@ -11317,13 +11327,6 @@ "transit_stops": { "description": "Capa que muestra diferentes tipos de paradas de transporte.", "filter": { - "0": { - "options": { - "0": { - "question": "Con refugio" - } - } - }, "1": { "options": { "0": { diff --git a/langs/layers/fr.json b/langs/layers/fr.json index 8190cf247..6edfeb0ce 100644 --- a/langs/layers/fr.json +++ b/langs/layers/fr.json @@ -3609,6 +3609,13 @@ } } }, + "23": { + "options": { + "0": { + "question": "Avec un abri" + } + } + }, "6": { "options": { "0": { @@ -6837,13 +6844,6 @@ }, "transit_stops": { "filter": { - "0": { - "options": { - "0": { - "question": "Avec un abri" - } - } - }, "1": { "options": { "0": { diff --git a/langs/layers/it.json b/langs/layers/it.json index 5a210ede3..6fef93020 100644 --- a/langs/layers/it.json +++ b/langs/layers/it.json @@ -5768,6 +5768,13 @@ } } }, + "23": { + "options": { + "0": { + "question": "Con una pensilina" + } + } + }, "3": { "options": { "0": { @@ -8469,6 +8476,9 @@ "mappings": { "0": { "then": "Questo posto auto ha 1 spazio." + }, + "1": { + "then": "Questo posto auto ha 1 spazio." } }, "render": "Questo posto auto ha {capacity} spazi." @@ -13134,13 +13144,6 @@ "transit_stops": { "description": "Livello che mostra diversi tipi di fermate dei mezzi pubblici.", "filter": { - "0": { - "options": { - "0": { - "question": "Con una pensilina" - } - } - }, "1": { "options": { "0": { diff --git a/langs/layers/nl.json b/langs/layers/nl.json index bac059917..41d0f1822 100644 --- a/langs/layers/nl.json +++ b/langs/layers/nl.json @@ -7622,10 +7622,34 @@ "description": "Laag met individuele parkeerplekken.", "name": "Parkeerplekken", "tagRenderings": { + "access": { + "mappings": { + "0": { + "then": "Iedereen kan deze parkeerplek gebruiken." + }, + "1": { + "then": "Iedereen kan deze parkeerplek gebruiken." + }, + "2": { + "then": "Deze parkeerplek is gereserveerd voor klanten." + }, + "3": { + "then": "Deze parkeerplek is privé en mag niet door het grote publiek worden gebruikt." + }, + "4": { + "then": "Deze parkeerplek is gereserveerd voor vergunninghouders." + } + }, + "question": "Wie mag deze parkeerplek gebruiken?", + "render": "Toegang tot parkeerplek: {access}" + }, "capacity": { "mappings": { "0": { "then": "Deze parkeerplek heeft 1 plaats." + }, + "1": { + "then": "Deze parkeerplek heeft 1 plaats." } }, "render": "Deze parkeerplek heeft {capacity} plaatsen." @@ -7650,6 +7674,9 @@ "13": { "then": "Deze parkeerplek is gereserveerd voor autodelen." }, + "14": { + "then": "Deze parkeerplek is gereserveerd voor vrouwen." + }, "2": { "then": "Dit is een gehandicaptenparkeerplaats." }, @@ -7780,6 +7807,130 @@ "render": "Kinesist {name}" } }, + "picnic_site": { + "description": "Picknickplaatsen voor het eten in de buitenlucht, met voorzieningen zoals toiletten, waterkranen, BBQ, banken en schuilplaatsen", + "filter": { + "1": { + "options": { + "0": { + "question": "Met een vuurplaats" + } + } + }, + "2": { + "options": { + "0": { + "question": "Met een BBQ" + } + } + }, + "3": { + "options": { + "0": { + "question": "Met drinkwater" + } + } + } + }, + "name": "Picknickplaatsen", + "presets": { + "0": { + "description": "Een picknickplaats voor het eten in de buitenlucht, met voorzieningen zoals toiletten, waterkranen, BBQ, banken en schuilplaatsen", + "title": "een picknickplaats" + } + }, + "tagRenderings": { + "bbq": { + "mappings": { + "0": { + "then": "Deze picknickplaats heeft een BBQ." + }, + "1": { + "then": "Deze picknickplaats heeft geen BBQ." + }, + "2": { + "then": "Deze picknickplaats heeft een BBQ, maar deze staat los op de kaart." + } + }, + "question": "Heeft deze picknickplaats een BBQ?" + }, + "covered": { + "mappings": { + "0": { + "then": "Deze picknickplaats is overdekt." + }, + "1": { + "then": "Deze picknickplaats is niet overdekt." + } + }, + "question": "Is deze picknickplaats overdekt?" + }, + "drinking_water": { + "mappings": { + "0": { + "then": "Deze picknickplaats heeft drinkwater." + }, + "1": { + "then": "Deze picknickplaats heeft geen drinkwater." + }, + "2": { + "then": "Deze picknickplaats heeft drinkwater, maar deze staat los op de kaart." + } + }, + "question": "Heeft deze picknickplaats drinkwater?" + }, + "fireplace": { + "mappings": { + "0": { + "then": "Deze picknickplaats heeft een vuurplaats." + }, + "1": { + "then": "Deze picknickplaats heeft geen vuurplaats." + }, + "2": { + "then": "Deze picknickplaats heeft een vuurplaats, maar deze staat los op de kaart." + } + }, + "question": "Heeft deze picknickplaats een vuurplaats?" + }, + "name": { + "override": { + "render": "Deze picknickplaats heet {name}" + } + }, + "openfire": { + "mappings": { + "0": { + "then": "Open vuur is toegestaan op deze picknickplaats." + }, + "1": { + "then": "Open vuur is niet toegestaan op deze picknickplaats." + }, + "2": { + "then": "Open vuur is toegestaan op deze picknickplaats met een vergunning." + } + }, + "question": "Is open vuur toegestaan op deze picknickplaats?" + }, + "shelter": { + "mappings": { + "0": { + "then": "Deze picknickplaats heeft een schuilplaats." + }, + "1": { + "then": "Deze picknickplaats heeft geen schuilplaats." + }, + "2": { + "then": "Deze picknickplaats heeft een schuilplaats, maar deze staat los op de kaart." + } + }, + "question": "Heeft deze picknickplaats een schuilplaats?" + } + }, + "title": { + "render": "Picknickplaats" + } + }, "picnic_table": { "description": "Deze laag toont picknicktafels", "name": "Picknicktafels", diff --git a/langs/themes/en.json b/langs/themes/en.json index de5ff9837..0c7b8469d 100644 --- a/langs/themes/en.json +++ b/langs/themes/en.json @@ -8,6 +8,10 @@ "description": "On this map, one can find and mark nearby defibrillators", "title": "Defibrillators" }, + "arcade": { + "description": "A map of arcades", + "title": "Arcades" + }, "architecture": { "description": "A map showing the architectural style of buildings", "title": "Buildings with an architectural style" From 5ee033a8c1e08f2c11bc36da8415e84118fc64bb Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 25 Aug 2025 14:24:23 +0200 Subject: [PATCH 36/92] Chore: reset filters --- android | 2 +- langs/layers/ca.json | 10 ---------- langs/layers/cs.json | 10 ---------- langs/layers/cy.json | 7 ------- langs/layers/de.json | 10 ---------- langs/layers/en.json | 19 +++++++++---------- langs/layers/it.json | 10 ---------- 7 files changed, 10 insertions(+), 58 deletions(-) diff --git a/android b/android index 5f0fb91b4..817e8198b 160000 --- a/android +++ b/android @@ -1 +1 @@ -Subproject commit 5f0fb91b4915d579722934752c19db4443c92d0f +Subproject commit 817e8198b5e4c30572d7d3f082d60fc10a7be21e diff --git a/langs/layers/ca.json b/langs/layers/ca.json index e4ac8aee9..e15f8fa0b 100644 --- a/langs/layers/ca.json +++ b/langs/layers/ca.json @@ -2278,16 +2278,6 @@ "campsite": { "description": "Càmpings", "filter": { - "0": { - "options": { - "0": { - "question": "Taxa" - }, - "1": { - "question": "Gratuït" - } - } - }, "1": { "options": { "0": { diff --git a/langs/layers/cs.json b/langs/layers/cs.json index 7b6cc195a..eab4f1eb5 100644 --- a/langs/layers/cs.json +++ b/langs/layers/cs.json @@ -2437,16 +2437,6 @@ "campsite": { "description": "Kempy", "filter": { - "0": { - "options": { - "0": { - "question": "Poplatek" - }, - "1": { - "question": "zdarma" - } - } - }, "1": { "options": { "0": { diff --git a/langs/layers/cy.json b/langs/layers/cy.json index 2f2908031..69d8a735f 100644 --- a/langs/layers/cy.json +++ b/langs/layers/cy.json @@ -225,13 +225,6 @@ }, "campsite": { "filter": { - "0": { - "options": { - "0": { - "question": "Ffi" - } - } - }, "1": { "options": { "7": { diff --git a/langs/layers/de.json b/langs/layers/de.json index 27c384de1..58e89547a 100644 --- a/langs/layers/de.json +++ b/langs/layers/de.json @@ -2230,16 +2230,6 @@ "campsite": { "description": "Zeltplätze", "filter": { - "0": { - "options": { - "0": { - "question": "Gebühr" - }, - "1": { - "question": "kostenlos" - } - } - }, "1": { "options": { "0": { diff --git a/langs/layers/en.json b/langs/layers/en.json index ee422ee16..76f94fdc5 100644 --- a/langs/layers/en.json +++ b/langs/layers/en.json @@ -2454,16 +2454,6 @@ "campsite": { "description": "Campsites", "filter": { - "0": { - "options": { - "0": { - "question": "Fee" - }, - "1": { - "question": "free of charge" - } - } - }, "1": { "options": { "0": { @@ -11263,6 +11253,15 @@ "second_hand": { "question": "Does this shop sell second-hand items?" }, + "self_checkout": { + "override": { + "+mappings": { + "0": { + "then": "This shop (probably) does not offer self-checkout" + } + } + } + }, "sells_new_bikes": { "mappings": { "0": { diff --git a/langs/layers/it.json b/langs/layers/it.json index 5a210ede3..65a7fd8d5 100644 --- a/langs/layers/it.json +++ b/langs/layers/it.json @@ -2423,16 +2423,6 @@ "campsite": { "description": "Campeggi", "filter": { - "0": { - "options": { - "0": { - "question": "A pagamento" - }, - "1": { - "question": "gratuito" - } - } - }, "1": { "options": { "0": { From 634d4a7186523cc78b8e288d92867c796183ce84 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 25 Aug 2025 23:43:03 +0200 Subject: [PATCH 37/92] Docs: add example images to element --- Docs/SpecialRenderings.md | 324 ++++++++++-------- ...al_preset_type_select_matching_presets.png | Bin 0 -> 108412 bytes .../Special_preset_type_select_preview.png | Bin 0 -> 112981 bytes src/UI/Popup/DataVisualisations.ts | 5 +- 4 files changed, 184 insertions(+), 145 deletions(-) create mode 100644 Docs/img/Special_preset_type_select_matching_presets.png create mode 100644 Docs/img/Special_preset_type_select_preview.png diff --git a/Docs/SpecialRenderings.md b/Docs/SpecialRenderings.md index cb00c41ad..c4882712f 100644 --- a/Docs/SpecialRenderings.md +++ b/Docs/SpecialRenderings.md @@ -14,8 +14,6 @@ General usage is `{func_name()}`, `{func_name(arg, someotherarg)}` or `{func_nam - [UI](#ui) + [braced](#braced) + [create_copy](#create_copy) - + [preset_description](#preset_description) - + [show_icons](#show_icons) + [title](#title) + [translated](#translated) - [data](#data) @@ -81,15 +79,13 @@ General usage is `{func_name()}`, `{func_name(arg, someotherarg)}` or `{func_nam - [tagrendering_manipulation](#tagrendering_manipulation) + [group](#group) + [multi](#multi) + + [open_in_iD](#open_in_id) + + [open_in_josm](#open_in_josm) + [steal](#steal) - - [ui](#ui) - + [preset_type_select](#preset_type_select) - [web_and_communication](#web_and_communication) + [fediverse_link](#fediverse_link) + [link](#link) + [mapillary_link](#mapillary_link) - + [open_in_iD](#open_in_id) - + [open_in_josm](#open_in_josm) + [send_email](#send_email) + [wikidata_label](#wikidata_label) + [wikipedia](#wikipedia) @@ -99,6 +95,8 @@ General usage is `{func_name()}`, `{func_name(arg, someotherarg)}` or `{func_nam + [histogram](#histogram) + [language_chooser](#language_chooser) + [multi_apply](#multi_apply) + + [preset_description](#preset_description) + + [preset_type_select](#preset_type_select) + [upload_to_osm](#upload_to_osm) # Using expanded syntax @@ -142,7 +140,8 @@ Show a literal text within braces -----|-----|----- | | text | _undefined_ | The value to show | -Defined in [/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L296](/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L296) +Defined +in [/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L295](/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L295) #### Example usage of braced @@ -152,42 +151,19 @@ Defined in [/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L296](/src/ Allow to create a copy of the current element -Defined in [/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L315](/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L315) +Defined +in [/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L314](/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L314) #### Example usage of create_copy `{create_copy()}` -### preset_description - -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 - -Defined in [/src/UI/Popup/DataVisualisations.ts#L215](/src/UI/Popup/DataVisualisations.ts#L215) - -#### Example usage of preset_description - -`{preset_description()}` - -### show_icons - -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 - -| name | default | description | ------|-----|----- | -| labels | _undefined_ | A ';'-separated list of labels and/or ids of tagRenderings | -| class | inline-flex mx-4 | CSS-classes of the container, space-separated | - -Defined in [/src/UI/Popup/DataVisualisations.ts#L307](/src/UI/Popup/DataVisualisations.ts#L307) - -#### Example usage of show_icons - -`{show_icons(,inline-flex mx-4)}` - ### title Shows the title of the popup. Useful for some cases, e.g. 'What is phone number of {title()}?' -Defined in [/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L281](/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L281) +Defined +in [/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L280](/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L280) #### Example usage of title @@ -201,7 +177,8 @@ If the given key can be interpreted as a JSON, only show the key containing the -----|-----|----- | | key | value | The attribute to interpret as json | -Defined in [/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L251](/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L251) +Defined +in [/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L250](/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L250) #### Example usage of translated @@ -215,7 +192,7 @@ Visualises data of a POI, sometimes with data updating capabilities Prints all key-value pairs of the object - used for debugging -Defined in [/src/UI/Popup/DataVisualisations.ts#L270](/src/UI/Popup/DataVisualisations.ts#L270) +Defined in [/src/UI/Popup/DataVisualisations.ts#L263](/src/UI/Popup/DataVisualisations.ts#L263) #### Example usage of all_tags @@ -229,7 +206,7 @@ Converts a short, canonical value into the long, translated text including the u -----|-----|----- | | key | _undefined_ | The key of the tag to give the canonical text for | -Defined in [/src/UI/Popup/DataVisualisations.ts#L163](/src/UI/Popup/DataVisualisations.ts#L163) +Defined in [/src/UI/Popup/DataVisualisations.ts#L155](/src/UI/Popup/DataVisualisations.ts#L155) #### Example usage of canonical @@ -244,7 +221,7 @@ Converts compass degrees (with 0° being north, 90° being east, ...) into a hum | key | _direction:centerpoint | The attribute containing the degrees | | offset | 0 | Offset value that is added to the actual value, e.g. `180` to indicate the opposite (backward) direction | -Defined in [/src/UI/Popup/DataVisualisations.ts#L47](/src/UI/Popup/DataVisualisations.ts#L47) +Defined in [/src/UI/Popup/DataVisualisations.ts#L39](/src/UI/Popup/DataVisualisations.ts#L39) #### Example usage of direction_absolute @@ -254,7 +231,7 @@ Defined in [/src/UI/Popup/DataVisualisations.ts#L47](/src/UI/Popup/DataVisualisa Gives a distance indicator and a compass pointing towards the location from your GPS-location. If clicked, centers the map on the object -Defined in [/src/UI/Popup/DataVisualisations.ts#L34](/src/UI/Popup/DataVisualisations.ts#L34) +Defined in [/src/UI/Popup/DataVisualisations.ts#L26](/src/UI/Popup/DataVisualisations.ts#L26) #### Example usage of direction_indicator @@ -270,7 +247,7 @@ A small element, showing if the POI is currently open and when the next change i | prefix | _empty string_ | Remove this string from the start of the value before parsing. __Note: use `&LPARENs` to indicate `(` if needed__ | | postfix | _empty string_ | Remove this string from the end of the value before parsing. __Note: use `&RPARENs` to indicate `)` if needed__ | -Defined in [/src/UI/Popup/DataVisualisations.ts#L126](/src/UI/Popup/DataVisualisations.ts#L126) +Defined in [/src/UI/Popup/DataVisualisations.ts#L118](/src/UI/Popup/DataVisualisations.ts#L118) #### Example usage of opening_hours_state @@ -286,7 +263,7 @@ Creates an opening-hours table. Usage: {opening_hours_table(opening_hours)} to c | prefix | _empty string_ | Remove this string from the start of the value before parsing. __Note: use `&LPARENs` to indicate `(` if needed__ | | postfix | _empty string_ | Remove this string from the end of the value before parsing. __Note: use `&RPARENs` to indicate `)` if needed__ | -Defined in [/src/UI/Popup/DataVisualisations.ts#L87](/src/UI/Popup/DataVisualisations.ts#L87) +Defined in [/src/UI/Popup/DataVisualisations.ts#L81](/src/UI/Popup/DataVisualisations.ts#L81) #### Example usage of opening_hours_table @@ -300,7 +277,7 @@ Creates a visualisation for 'points in time', e.g. collection times of a postbox -----|-----|----- | | key | _undefined_ | The key out of which the points_in_time will be parsed | -Defined in [/src/UI/Popup/DataVisualisations.ts#L281](/src/UI/Popup/DataVisualisations.ts#L281) +Defined in [/src/UI/Popup/DataVisualisations.ts#L274](/src/UI/Popup/DataVisualisations.ts#L274) #### Example usage of points_in_time @@ -310,7 +287,7 @@ Defined in [/src/UI/Popup/DataVisualisations.ts#L281](/src/UI/Popup/DataVisualis Show general statistics about all the elements currently in view. Intended to use on the `current_view`-layer. They will be split per layer -Defined in [/src/UI/Popup/DataVisualisations.ts#L203](/src/UI/Popup/DataVisualisations.ts#L203) +Defined in [/src/UI/Popup/DataVisualisations.ts#L195](/src/UI/Popup/DataVisualisations.ts#L195) #### Example usage of statistics @@ -354,7 +331,8 @@ Gives an interactive element which shows a tag comparison between the OSM-object | host | _undefined_ | The domain name(s) where data might be fetched from - this is needed to set the CSP. A domain must include 'https', e.g. 'https://example.com'. For multiple domains, separate them with ';'. If you don't know the possible domains, use '*'. | | readonly | _undefined_ | If 'yes', will not show 'apply'-buttons | -Defined in [/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts#L243](/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts#L243) +Defined +in [/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts#L243](/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts#L243) #### Example usage of compare_data @@ -414,7 +392,8 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be | icon | ./assets/svg/addSmall.svg | A nice icon to show in the button | | way_to_conflate | _undefined_ | The key, of which the corresponding value is the id of the OSM-way that must be conflated; typically a calculatedTag | -Defined in [/src/UI/Popup/ImportButtons/ConflateImportButtonViz.ts#L30](/src/UI/Popup/ImportButtons/ConflateImportButtonViz.ts#L30) +Defined +in [/src/UI/Popup/ImportButtons/ConflateImportButtonViz.ts#L30](/src/UI/Popup/ImportButtons/ConflateImportButtonViz.ts#L30) #### Example usage of conflate_button @@ -543,7 +522,8 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be | snap_onto_layers | _undefined_ | If no existing nearby point exists, but a line of a specified layer is closeby, snap to this layer instead | | snap_to_layer_max_distance | 0.1 | Distance to distort the geometry to snap to this layer | -Defined in [/src/UI/Popup/ImportButtons/WayImportButtonViz.ts#L22](/src/UI/Popup/ImportButtons/WayImportButtonViz.ts#L22) +Defined +in [/src/UI/Popup/ImportButtons/WayImportButtonViz.ts#L22](/src/UI/Popup/ImportButtons/WayImportButtonViz.ts#L22) #### Example usage of import_way_button @@ -561,7 +541,8 @@ Attempts to load (via a proxy) the specified website and parsed ld+json from the | mode | _undefined_ | If `display`, only show the data in tabular and readonly form, ignoring already existing tags. This is used to explicitly show all the tags. If unset or anything else, allow to apply/import on OSM | | collapsed | yes | If the containing accordion should be closed | -Defined in [/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts#L105](/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts#L105) +Defined +in [/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts#L105](/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts#L105) #### Example usage of linked_data_from_website @@ -580,7 +561,8 @@ Change the status of the given MapRoulette task | maproulette_id | mr_taskId | The property name containing the maproulette id | | ask_feedback | _empty string_ | If not an empty string, this will be used as question to ask some additional feedback. A text field will be added | -Defined in [/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts#L25](/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts#L25) +Defined +in [/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts#L25](/src/UI/SpecialVisualisations/DataImportSpecialVisualisations.ts#L25) #### Example usage of maproulette_set_status @@ -641,7 +623,7 @@ Note that these values can be prepare with javascript in the theme by using a [c | id_of_object_to_apply_this_one | _undefined_ | If specified, applies the the tags onto _another_ object. The id will be read from properties[id_of_object_to_apply_this_one] of the selected object. The tags are still calculated based on the tags of the _selected_ element | | maproulette_id | _undefined_ | If specified, this maproulette-challenge will be closed when the tags are applied. This should be the `id` of the individual task, _not_ the task_id (which corresponds with the challenge). | -Defined in [/src/UI/SpecialVisualisations/TagApplyViz.ts#L17](/src/UI/SpecialVisualisations/TagApplyViz.ts#L17) +Defined in [/src/UI/SpecialVisualisations/TagApplyViz.ts#L13](/src/UI/SpecialVisualisations/TagApplyViz.ts#L13) #### Example usage of tag_apply @@ -655,7 +637,8 @@ These special visualisations are (mostly) interactive components that most eleme An element which allows to add a new point on the 'last_click'-location. Only makes sense in the layer `last_click` -Defined in [/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L235](/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L235) +Defined +in [/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L234](/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L234) #### Example usage of add_new_point @@ -665,7 +648,8 @@ Defined in [/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L235](/src/ Adds a button which allows to delete the object at this location. The config will be read from the layer config -Defined in [/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L157](/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L157) +Defined +in [/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L157](/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L157) #### Example usage of delete_button @@ -680,7 +664,8 @@ Shows a 'nothing is currently known-message if there is at least one unanswered | text | _undefined_ | Text to show | | cssClasses | _undefined_ | Classes to apply onto the text | -Defined in [/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L207](/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L207) +Defined +in [/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L206](/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L206) #### Example usage of if_nothing_known @@ -696,7 +681,8 @@ A small map showing the selected feature. | idKey | id | The key of one or more properties of the feature, semi-colon separated. The corresponding value is interpreted as either the id or the a list of ID's. The features with these ID's will be shown on this minimap. | | class | h-40 rounded | CSS-classes (space-separated) that should be applied onto the container | -Defined in [/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L81](/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L81) +Defined +in [/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L81](/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L81) #### Example usage of minimap @@ -706,7 +692,8 @@ Defined in [/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L81](/src/U Adds a button which allows to move the object to another location. The config will be read from the layer config -Defined in [/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L137](/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L137) +Defined +in [/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L137](/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L137) #### Example usage of move_button @@ -721,7 +708,8 @@ Generates a QR-code to share the selected object | text | _undefined_ | Extra text on the side of the QR-code | | textClass | _undefined_ | CSS class of the the side text | -Defined in [/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L178](/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L178) +Defined +in [/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L178](/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L178) #### Example usage of qr_code @@ -737,7 +725,8 @@ The special element which shows the questions which are unknown. Added by defaul | blacklisted-labels | _undefined_ | One or more ';'-separated labels of questions which should _not_ be included. Note that the questionbox which is added by default will blacklist 'hidden'. If both a whitelist and a blacklist are given, will show questions having at least one label from the whitelist but none of the blacklist. | | show_all | _undefined_ | Either `no`, `yes` or `user-preference`. Indicates if all questions should be shown at once | -Defined in [/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L31](/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L31) +Defined +in [/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L31](/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L31) #### Example usage of questions @@ -762,7 +751,8 @@ Defined in [/src/UI/Popup/ShareLinkViz.ts#L6](/src/UI/Popup/ShareLinkViz.ts#L6) Adds a button which allows to split a way -Defined in [/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L123](/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L123) +Defined +in [/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L123](/src/UI/SpecialVisualisations/UISpecialVisualisations.ts#L123) #### Example usage of split_button @@ -776,7 +766,8 @@ Elements relating to marking an object as favourite (giving it a heart). Default A small button that allows a (logged in) contributor to mark a location as a favourite location, sized to fit a title-icon -Defined in [/src/UI/SpecialVisualisations/FavouriteVisualisations.ts#L19](/src/UI/SpecialVisualisations/FavouriteVisualisations.ts#L19) +Defined +in [/src/UI/SpecialVisualisations/FavouriteVisualisations.ts#L19](/src/UI/SpecialVisualisations/FavouriteVisualisations.ts#L19) #### Example usage of favourite_icon @@ -786,7 +777,8 @@ Defined in [/src/UI/SpecialVisualisations/FavouriteVisualisations.ts#L19](/src/U A button that allows a (logged in) contributor to mark a location as a favourite location -Defined in [/src/UI/SpecialVisualisations/FavouriteVisualisations.ts#L6](/src/UI/SpecialVisualisations/FavouriteVisualisations.ts#L6) +Defined +in [/src/UI/SpecialVisualisations/FavouriteVisualisations.ts#L6](/src/UI/SpecialVisualisations/FavouriteVisualisations.ts#L6) #### Example usage of favourite_status @@ -804,7 +796,8 @@ Creates an image carousel for the given sources. An attempt will be made to gues -----|-----|----- | | image_key | image;mapillary;image;wikidata;wikimedia_commons;image;panoramax;image;image | 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 | -Defined in [/src/UI/SpecialVisualisations/ImageVisualisations.ts#L48](/src/UI/SpecialVisualisations/ImageVisualisations.ts#L48) +Defined +in [/src/UI/SpecialVisualisations/ImageVisualisations.ts#L48](/src/UI/SpecialVisualisations/ImageVisualisations.ts#L48) #### Example usage of image_carousel @@ -820,7 +813,8 @@ Creates a button where a user can upload an image to panoramax | label | _undefined_ | The text to show on the button | | disable_blur | _undefined_ | If set to 'true' or 'yes', then face blurring will be disabled. To be used sparingly | -Defined in [/src/UI/SpecialVisualisations/ImageVisualisations.ts#L82](/src/UI/SpecialVisualisations/ImageVisualisations.ts#L82) +Defined +in [/src/UI/SpecialVisualisations/ImageVisualisations.ts#L82](/src/UI/SpecialVisualisations/ImageVisualisations.ts#L82) #### Example usage of image_upload @@ -835,7 +829,8 @@ A component showing nearby images loaded from various online services such as Ma | mode | closed | Either `open` or `closed`. If `open`, then the image carousel will always be shown | | readonly | _undefined_ | If 'readonly' or 'yes', will not show the 'link'-button | -Defined in [/src/UI/SpecialVisualisations/ImageVisualisations.ts#L12](/src/UI/SpecialVisualisations/ImageVisualisations.ts#L12) +Defined +in [/src/UI/SpecialVisualisations/ImageVisualisations.ts#L12](/src/UI/SpecialVisualisations/ImageVisualisations.ts#L12) #### Example usage of nearby_images @@ -853,7 +848,8 @@ Adds an image to a node -----|-----|----- | | Id-key | id | The property name where the ID of the note to close can be found | -Defined in [/src/UI/SpecialVisualisations/NoteVisualisations.ts#L115](/src/UI/SpecialVisualisations/NoteVisualisations.ts#L115) +Defined +in [/src/UI/SpecialVisualisations/NoteVisualisations.ts#L111](/src/UI/SpecialVisualisations/NoteVisualisations.ts#L111) #### Example usage of add_image_to_note @@ -867,7 +863,8 @@ A textfield to add a comment to a node (with the option to close the note). -----|-----|----- | | Id-key | id | The property name where the ID of the note to close can be found | -Defined in [/src/UI/SpecialVisualisations/NoteVisualisations.ts#L79](/src/UI/SpecialVisualisations/NoteVisualisations.ts#L79) +Defined +in [/src/UI/SpecialVisualisations/NoteVisualisations.ts#L75](/src/UI/SpecialVisualisations/NoteVisualisations.ts#L75) #### Example usage of add_note_comment @@ -886,7 +883,8 @@ Button to close a note. A predefined text can be defined to close the note with. | minZoom | _undefined_ | If set, only show the closenote button if zoomed in enough | | zoomButton | _undefined_ | Text to show if not zoomed in enough | -Defined in [/src/UI/SpecialVisualisations/NoteVisualisations.ts#L22](/src/UI/SpecialVisualisations/NoteVisualisations.ts#L22) +Defined +in [/src/UI/SpecialVisualisations/NoteVisualisations.ts#L18](/src/UI/SpecialVisualisations/NoteVisualisations.ts#L18) #### Example usage of close_note @@ -896,7 +894,8 @@ Defined in [/src/UI/SpecialVisualisations/NoteVisualisations.ts#L22](/src/UI/Spe 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 -Defined in [/src/UI/SpecialVisualisations/NoteVisualisations.ts#L98](/src/UI/SpecialVisualisations/NoteVisualisations.ts#L98) +Defined +in [/src/UI/SpecialVisualisations/NoteVisualisations.ts#L94](/src/UI/SpecialVisualisations/NoteVisualisations.ts#L94) #### Example usage of open_note @@ -911,7 +910,8 @@ Visualises the comments for notes | commentsKey | comments | The property name of the comments, which should be stringified json | | start | 0 | Drop the first 'start' comments | -Defined in [/src/UI/SpecialVisualisations/NoteVisualisations.ts#L136](/src/UI/SpecialVisualisations/NoteVisualisations.ts#L136) +Defined +in [/src/UI/SpecialVisualisations/NoteVisualisations.ts#L132](/src/UI/SpecialVisualisations/NoteVisualisations.ts#L132) #### Example usage of visualize_note_comments @@ -931,7 +931,8 @@ Invites the contributor to leave a review. Somewhat small UI-element until inter | fallback | _undefined_ | The identifier to use, if tags[subjectKey] as specified above is not available. This is effectively a fallback value | | question | _undefined_ | The question to ask during the review | -Defined in [/src/UI/SpecialVisualisations/ReviewSpecialVisualisations.ts#L22](/src/UI/SpecialVisualisations/ReviewSpecialVisualisations.ts#L22) +Defined +in [/src/UI/SpecialVisualisations/ReviewSpecialVisualisations.ts#L22](/src/UI/SpecialVisualisations/ReviewSpecialVisualisations.ts#L22) #### Example usage of create_review @@ -946,7 +947,8 @@ Adds an overview of the mangrove-reviews of this object. Mangrove.Reviews needs | subjectKey | name | The key to use to determine the subject. If specified, the subject will be tags[subjectKey] | | fallback | _undefined_ | The identifier to use, if tags[subjectKey] as specified above is not available. This is effectively a fallback value | -Defined in [/src/UI/SpecialVisualisations/ReviewSpecialVisualisations.ts#L88](/src/UI/SpecialVisualisations/ReviewSpecialVisualisations.ts#L88) +Defined +in [/src/UI/SpecialVisualisations/ReviewSpecialVisualisations.ts#L88](/src/UI/SpecialVisualisations/ReviewSpecialVisualisations.ts#L88) #### Example usage of list_reviews @@ -961,7 +963,8 @@ Shows stars which represent the average rating on mangrove. | subjectKey | name | The key to use to determine the subject. If the value is specified, the subject will be tags[subjectKey] and will use this to filter the reviews. | | fallback | _undefined_ | The identifier to use, if tags[subjectKey] as specified above is not available. This is effectively a fallback value | -Defined in [/src/UI/SpecialVisualisations/ReviewSpecialVisualisations.ts#L125](/src/UI/SpecialVisualisations/ReviewSpecialVisualisations.ts#L125) +Defined +in [/src/UI/SpecialVisualisations/ReviewSpecialVisualisations.ts#L125](/src/UI/SpecialVisualisations/ReviewSpecialVisualisations.ts#L125) #### Example usage of rating @@ -977,7 +980,8 @@ A pragmatic combination of `create_review` and `list_reviews` | fallback | _undefined_ | The identifier to use, if tags[subjectKey] as specified above is not available. This is effectively a fallback value | | question | _undefined_ | The question to ask in the review form. Optional | -Defined in [/src/UI/SpecialVisualisations/ReviewSpecialVisualisations.ts#L182](/src/UI/SpecialVisualisations/ReviewSpecialVisualisations.ts#L182) +Defined +in [/src/UI/SpecialVisualisations/ReviewSpecialVisualisations.ts#L182](/src/UI/SpecialVisualisations/ReviewSpecialVisualisations.ts#L182) #### Example usage of reviews @@ -995,7 +999,8 @@ A button which clears the locally downloaded data and the service worker. Login -----|-----|----- | | text | _undefined_ | The text to show on the button | -Defined in [/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L110](/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L110) +Defined +in [/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L110](/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L110) #### Example usage of clear_caches @@ -1005,7 +1010,8 @@ Defined in [/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L110](/src/U A button to remove the travelled track information from the device -Defined in [/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L215](/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L215) +Defined +in [/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L215](/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L215) #### Example usage of clear_location_history @@ -1015,7 +1021,8 @@ Defined in [/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L215](/src/U Shows which questions are disabled for every layer. Used in 'settings' -Defined in [/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L46](/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L46) +Defined +in [/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L46](/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L46) #### Example usage of disabled_questions @@ -1025,7 +1032,8 @@ Defined in [/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L46](/src/UI Shows the current tags of the GPS-representing object, used for debugging -Defined in [/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L69](/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L69) +Defined +in [/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L69](/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L69) #### Example usage of gps_all_tags @@ -1035,7 +1043,8 @@ Defined in [/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L69](/src/UI Shows the current tags of the GPS-representing object, used for debugging -Defined in [/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L58](/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L58) +Defined +in [/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L58](/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L58) #### Example usage of gyroscope_all_tags @@ -1049,7 +1058,8 @@ Only makes sense in the usersettings. Allows to import a mangrove public key and -----|-----|----- | | text | _undefined_ | The text that is shown on the button | -Defined in [/src/UI/SpecialVisualisations/ReviewSpecialVisualisations.ts#L162](/src/UI/SpecialVisualisations/ReviewSpecialVisualisations.ts#L162) +Defined +in [/src/UI/SpecialVisualisations/ReviewSpecialVisualisations.ts#L162](/src/UI/SpecialVisualisations/ReviewSpecialVisualisations.ts#L162) #### Example usage of import_mangrove_key @@ -1059,7 +1069,8 @@ Defined in [/src/UI/SpecialVisualisations/ReviewSpecialVisualisations.ts#L162](/ A component to set the language of the user interface -Defined in [/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L26](/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L26) +Defined +in [/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L26](/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L26) #### Example usage of language_picker @@ -1074,7 +1085,8 @@ Show a login button | force | _undefined_ | Always show this button, even if logged in | | message | _undefined_ | Message to display on the button | -Defined in [/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L131](/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L131) +Defined +in [/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L131](/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L131) #### Example usage of login_button @@ -1084,7 +1096,8 @@ Defined in [/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L131](/src/U Shows a button where the user can log out -Defined in [/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L192](/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L192) +Defined +in [/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L192](/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L192) #### Example usage of logout @@ -1094,7 +1107,8 @@ Defined in [/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L192](/src/U A module showing the pending changes, with the option to clear the pending changes -Defined in [/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L204](/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L204) +Defined +in [/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L204](/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L204) #### Example usage of pending_changes @@ -1109,7 +1123,8 @@ A QR-code which shares the current URL and adds the login token. Anyone with thi | text | _undefined_ | Extra text on the side of the QR-code | | textClass | _undefined_ | CSS class of the the side text | -Defined in [/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L161](/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L161) +Defined +in [/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L161](/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L161) #### Example usage of qr_login @@ -1119,7 +1134,8 @@ Defined in [/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L161](/src/U Shows the current state of storage -Defined in [/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L86](/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L86) +Defined +in [/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L86](/src/UI/SpecialVisualisations/SettingsVisualisations.ts#L86) #### Example usage of storage_all_tags @@ -1139,7 +1155,8 @@ A collapsable group (accordion) | labels | _undefined_ | A `;`-separated list of either identifiers or label names. All tagRenderings matching this value will be shown in the accordion | | blacklist | _undefined_ | A `;`-separated list of either identifiers or label names. Matching tagrenderings will _not_ be included, even if they are in `labels` | -Defined in [/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L176](/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L176) +Defined +in [/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L176](/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L176) #### Example usage of group @@ -1155,7 +1172,8 @@ Given an embedded tagRendering (read only) and a key, will read the keyname as a | tagrendering | _undefined_ | An entire tagRenderingConfig | | classes | _undefined_ | CSS-classes to apply on every individual item. Seperated by `space` | -Defined in [/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L96](/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L96) +Defined +in [/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L96](/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L96) #### Example usage of multi @@ -1173,6 +1191,28 @@ Defined in [/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisuali } ``` +### open_in_iD + +Opens the current view in the iD-editor + +Defined +in [/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L212](/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L212) + +#### Example usage of open_in_iD + +`{open_in_iD()}` + +### open_in_josm + +Opens the current view in the JOSM-editor + +Defined +in [/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L226](/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L226) + +#### Example usage of open_in_josm + +`{open_in_josm()}` + ### steal Shows a tagRendering from a different object as if this was the object itself @@ -1182,26 +1222,13 @@ Shows a tagRendering from a different object as if this was the object itself | featureId | _undefined_ | The key of the attribute which contains the id of the feature from which to use the tags | | tagRenderingId | _undefined_ | The layer-id and tagRenderingId to render. Can be multiple value if ';'-separated (in which case every value must also contain the layerId, e.g. `layerId.tagRendering0; layerId.tagRendering1`). Note: this can cause layer injection | -Defined in [/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L23](/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L23) +Defined +in [/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L23](/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L23) #### Example usage of steal `{steal(,)}` -## ui - -Elements to support the user interface, e.g. 'title', 'translated' - -### preset_type_select - -An editable tag rendering which allows to change the type - -Defined in [/src/UI/Popup/DataVisualisations.ts#L231](/src/UI/Popup/DataVisualisations.ts#L231) - -#### Example usage of preset_type_select - -`{preset_type_select()}` - ## web_and_communication Tools to show data from external websites, which link to external websites or which link to external profiles @@ -1214,7 +1241,8 @@ Converts a fediverse username or link into a clickable link -----|-----|----- | | key | _undefined_ | The attribute-name containing the link | -Defined in [/src/UI/SpecialVisualisations/WebAndCommunicationSpecialVisualisations.ts#L20](/src/UI/SpecialVisualisations/WebAndCommunicationSpecialVisualisations.ts#L20) +Defined +in [/src/UI/SpecialVisualisations/WebAndCommunicationSpecialVisualisations.ts#L16](/src/UI/SpecialVisualisations/WebAndCommunicationSpecialVisualisations.ts#L16) #### Example usage of fediverse_link @@ -1224,16 +1252,17 @@ Defined in [/src/UI/SpecialVisualisations/WebAndCommunicationSpecialVisualisatio Construct a link. By using the 'special' visualisation notation, translations should be easier -| name | default | description | ------|-----|----- | -| text | _undefined_ | Text to be shown | -| href | _undefined_ | The URL to link to. Note that this will be URI-encoded before and (as everything) supports substitutions of attributes | -| class | _undefined_ | CSS-classes to add to the element | -| download | _undefined_ | Expects a string which denotes the filename to download the contents of `href` into. If set, this link will act as a download-button. | -| arialabel | _undefined_ | If set, this text will be used as aria-label | -| icon | _undefined_ | If set, show this icon next to the link. You might want to combine this with `class: button` | +| name | default | description | +-----------|-------------|---------------------------------------------------------------------------------------------------------------------------------------| +| text | _undefined_ | Text to be shown | +| href | _undefined_ | The URL to link to. Note that this will be URI-encoded before and (as everything) supports substitutions of attributes | +| class | _undefined_ | CSS-classes to add to the element | +| download | _undefined_ | Expects a string which denotes the filename to download the contents of `href` into. If set, this link will act as a download-button. | +| arialabel | _undefined_ | If set, this text will be used as aria-label | +| icon | _undefined_ | If set, show this icon next to the link. You might want to combine this with `class: button` | -Defined in [/src/UI/SpecialVisualisations/WebAndCommunicationSpecialVisualisations.ts#L147](/src/UI/SpecialVisualisations/WebAndCommunicationSpecialVisualisations.ts#L147) +Defined +in [/src/UI/SpecialVisualisations/WebAndCommunicationSpecialVisualisations.ts#L143](/src/UI/SpecialVisualisations/WebAndCommunicationSpecialVisualisations.ts#L143) #### Example usage of link @@ -1253,26 +1282,6 @@ Defined in [/src/UI/Popup/MapillaryLinkVis.ts#L7](/src/UI/Popup/MapillaryLinkVis `{mapillary_link(18)}` -### open_in_iD - -Opens the current view in the iD-editor - -Defined in [/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L212](/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L212) - -#### Example usage of open_in_iD - -`{open_in_iD()}` - -### open_in_josm - -Opens the current view in the JOSM-editor - -Defined in [/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L226](/src/UI/SpecialVisualisations/TagrenderingManipulationSpecialVisualisations.ts#L226) - -#### Example usage of open_in_josm - -`{open_in_josm()}` - ### send_email Creates a `mailto`-link where some fields are already set and correctly escaped. The user will be promted to send the email @@ -1284,7 +1293,7 @@ Creates a `mailto`-link where some fields are already set and correctly escaped. | body | _undefined_ | The text in the email | | button_text | _undefined_ | The text shown on the button in the UI | -Defined in [/src/UI/SpecialVisualisations/WebAndCommunicationSpecialVisualisations.ts#L109](/src/UI/SpecialVisualisations/WebAndCommunicationSpecialVisualisations.ts#L109) +Defined in [/src/UI/SpecialVisualisations/WebAndCommunicationSpecialVisualisations.ts#L105](/src/UI/SpecialVisualisations/WebAndCommunicationSpecialVisualisations.ts#L105) #### Example usage of send_email @@ -1298,7 +1307,8 @@ Shows the label of the corresponding wikidata-item -----|-----|----- | | keyToShowWikidataFor | wikidata | Use the wikidata entry from this key to show the label | -Defined in [/src/UI/SpecialVisualisations/WebAndCommunicationSpecialVisualisations.ts#L68](/src/UI/SpecialVisualisations/WebAndCommunicationSpecialVisualisations.ts#L68) +Defined +in [/src/UI/SpecialVisualisations/WebAndCommunicationSpecialVisualisations.ts#L64](/src/UI/SpecialVisualisations/WebAndCommunicationSpecialVisualisations.ts#L64) #### Example usage of wikidata_label @@ -1312,7 +1322,8 @@ A box showing the corresponding wikipedia article(s) - based on the **wikidata** -----|-----|----- | | keyToShowWikipediaFor | wikidata;wikipedia | Use the wikidata entry from this key to show the wikipedia article for. Multiple keys can be given (separated by ';'), in which case the first matching value is used | -Defined in [/src/UI/SpecialVisualisations/WebAndCommunicationSpecialVisualisations.ts#L39](/src/UI/SpecialVisualisations/WebAndCommunicationSpecialVisualisations.ts#L39) +Defined +in [/src/UI/SpecialVisualisations/WebAndCommunicationSpecialVisualisations.ts#L35](/src/UI/SpecialVisualisations/WebAndCommunicationSpecialVisualisations.ts#L35) #### Example usage of wikipedia @@ -1326,7 +1337,7 @@ Various elements Exports the selected feature as GeoJson-file -Defined in [/src/UI/Popup/DataExportVisualisations.ts#L38](/src/UI/Popup/DataExportVisualisations.ts#L38) +Defined in [/src/UI/Popup/DataExportVisualisations.ts#L34](/src/UI/Popup/DataExportVisualisations.ts#L34) #### Example usage of export_as_geojson @@ -1336,7 +1347,7 @@ Defined in [/src/UI/Popup/DataExportVisualisations.ts#L38](/src/UI/Popup/DataExp Exports the selected feature as GPX-file -Defined in [/src/UI/Popup/DataExportVisualisations.ts#L12](/src/UI/Popup/DataExportVisualisations.ts#L12) +Defined in [/src/UI/Popup/DataExportVisualisations.ts#L8](/src/UI/Popup/DataExportVisualisations.ts#L8) #### Example usage of export_as_gpx @@ -1350,7 +1361,7 @@ Create a histogram for a list of given values, read from the properties. -----|-----|----- | | key | _undefined_ | The key to be read and to generate a histogram from | -Defined in [/src/UI/Popup/HistogramViz.ts#L11](/src/UI/Popup/HistogramViz.ts#L11) +Defined in [/src/UI/Popup/HistogramViz.ts#L7](/src/UI/Popup/HistogramViz.ts#L7) #### Example usage of histogram @@ -1360,14 +1371,14 @@ Defined in [/src/UI/Popup/HistogramViz.ts#L11](/src/UI/Popup/HistogramViz.ts#L11 The language element allows to show and pick all known (modern) languages. The key can be set -| name | default | description | ------|-----|----- | -| key | _undefined_ | 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 | -| question | _undefined_ | What to ask if no questions are known | -| render_list_item | {language()} | How a single language will be shown in the list of languages. Use `{language}` to indicate the language (which it must contain). | -| render_single_language | _undefined_ | What will be shown if the feature only supports a single language | -| render_all | {list()} | The full rendering. U0se `{list}` to show where the list of languages must come. Optional if mode=single | -| no_known_languages | _undefined_ | The text that is shown if no languages are known for this key. If this text is omitted, the languages will be prompted instead | +| name | default | description | +------------------------|--------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| key | _undefined_ | 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 | +| question | _undefined_ | What to ask if no questions are known | +| render_list_item | {language()} | How a single language will be shown in the list of languages. Use `{language}` to indicate the language (which it must contain). | +| render_single_language | _undefined_ | What will be shown if the feature only supports a single language | +| render_all | {list()} | The full rendering. U0se `{list}` to show where the list of languages must come. Optional if mode=single | +| no_known_languages | _undefined_ | The text that is shown if no languages are known for this key. If this text is omitted, the languages will be prompted instead | Defined in [/src/UI/Popup/LanguageElement/LanguageElement.ts#L5](/src/UI/Popup/LanguageElement/LanguageElement.ts#L5) @@ -1403,6 +1414,31 @@ Defined in [/src/UI/Popup/MultiApplyViz.ts#L7](/src/UI/Popup/MultiApplyViz.ts#L7 {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)} +### preset_description + +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 + +Defined in [/src/UI/Popup/DataVisualisations.ts#L207](/src/UI/Popup/DataVisualisations.ts#L207) + +#### Example usage of preset_description + +`{preset_description()}` + +### preset_type_select + +An editable tag rendering which allows to change the type. The options are the presets of the layer, effectively +allowing to change act as if the object was made with a different preset. For example + +How this element looks like (in question mode) for [ +`tourism_accomodation`](./Layers/tourism_accomodation.md): ![](./img/Special_preset_type_select_preview.png)The +presets ![](./img/Special_preset_type_select_matching_presets.png) + +Defined in [/src/UI/Popup/DataVisualisations.ts#L222](/src/UI/Popup/DataVisualisations.ts#L222) + +#### Example usage of preset_type_select + +`{preset_type_select()}` + ### upload_to_osm Uploads the GPS-history as GPX to OpenStreetMap.org; clears the history afterwards. The actual feature is ignored. diff --git a/Docs/img/Special_preset_type_select_matching_presets.png b/Docs/img/Special_preset_type_select_matching_presets.png new file mode 100644 index 0000000000000000000000000000000000000000..731e4bfbffb37f9748baab6692ea1b37c0a025ae GIT binary patch literal 108412 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

RDga=!<_<3^Qr87pH$3-kq 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 literal 0 HcmV?d00001 diff --git a/src/UI/Popup/DataVisualisations.ts b/src/UI/Popup/DataVisualisations.ts index cf1184f7e..dcbe8c80a 100644 --- a/src/UI/Popup/DataVisualisations.ts +++ b/src/UI/Popup/DataVisualisations.ts @@ -230,7 +230,10 @@ class PresetDescription extends SpecialVisualizationSvelte { class PresetTypeSelect extends SpecialVisualizationSvelte { funcName = "preset_type_select" - docs = "An editable tag rendering which allows to change the type" + docs = "An editable tag rendering which allows to change the type. The options are the presets of the layer, effectively allowing to change act as if the object was made with a different preset. " + + "For example\n\n" + + "How this element looks like (in question mode) for [`tourism_accomodation`](./Layers/tourism_accomodation.md): ![](./img/Special_preset_type_select_preview.png)" + + "The presets ![](./img/Special_preset_type_select_matching_presets.png)" args = [] group = "ui" From aed6defa168030fb7947271df068fe5feb0b0ba2 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 26 Aug 2025 00:43:08 +0200 Subject: [PATCH 38/92] Fix: fix #2509 --- src/Logic/Search/ThemeSearch.ts | 14 ++++++++++++-- src/UI/AllThemesGui.svelte | 20 +++++++------------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/Logic/Search/ThemeSearch.ts b/src/Logic/Search/ThemeSearch.ts index 96530d93d..060a41423 100644 --- a/src/Logic/Search/ThemeSearch.ts +++ b/src/Logic/Search/ThemeSearch.ts @@ -13,16 +13,22 @@ import { Lists } from "../../Utils/Lists" export class ThemeSearchIndex { private readonly themeIndex: Fuse private readonly layerIndex: Fuse<{ id: string; description }> + /** + * A 'search' will also search matching layers from the official themes. + * This might cause themes to show up which weren't passed in 'themesToSearch', so we create a whitelist + */ + private readonly themeWhitelist: Set constructor( language: string, - themesToSearch?: MinimalThemeInformation[], + themesToSearch: MinimalThemeInformation[], layersToIgnore: string[] = [] ) { - const themes = Utils.noNull(themesToSearch ?? ThemeSearch.officialThemes?.themes) + const themes = Utils.noNull(themesToSearch) if (!themes) { throw "No themes loaded. Did generate:layeroverview fail?" } + this.themeWhitelist = new Set(themes.map(th => th.id)) const fuseOptions: IFuseOptions = { ignoreLocation: true, threshold: 0.2, @@ -82,6 +88,10 @@ export class ThemeSearchIndex { const matchingThemes = ThemeSearch.layersToThemes.get(layer.item.id) const score = layer.score matchingThemes?.forEach((th) => { + if (!this.themeWhitelist.has(th.id)) { + // This theme was not in the 'themesToSearch' + return + } const previous = result.get(th.id) ?? 10000 result.set(th.id, Math.min(previous, score * 5)) }) diff --git a/src/UI/AllThemesGui.svelte b/src/UI/AllThemesGui.svelte index 5845c70fc..0a3cbf25d 100644 --- a/src/UI/AllThemesGui.svelte +++ b/src/UI/AllThemesGui.svelte @@ -61,7 +61,7 @@ let userLanguages = osmConnection.userDetails.map((ud) => ud?.languages ?? []) let search: UIEventSource = new UIEventSource("") - let searchStable = search.stabilized(100) + let searchStable: Store = search.stabilized(100) const officialThemes: MinimalThemeInformation[] = ThemeSearch.officialThemes.themes.filter( (th) => th.hideFromOverview === false @@ -83,13 +83,12 @@ ).mapD((stableIds) => Lists.noNullInplace(stableIds.map((id) => state.getUnofficialTheme(id)))) function filtered(themes: Store): Store { - const searchIndex = Locale.language.map( - (language) => { - return new ThemeSearchIndex(language, themes.data) - }, - [themes] + const searchIndex = themes.mapD( + (themes) => new ThemeSearchIndex(Locale.language.data, themes), + [Locale.language] ) + return searchStable.map( (searchTerm) => { if (!themes.data) { @@ -99,11 +98,9 @@ return themes.data } - const index = searchIndex.data - - return index.search(searchTerm) + return searchIndex.data?.search(searchTerm) }, - [searchIndex] + [searchIndex, themes] ) } @@ -134,9 +131,6 @@ { returnToIndex: new ImmutableStore(false) } ) - const topSPace = AndroidPolyfill.getInsetSizes().top - const bottom = AndroidPolyfill.getInsetSizes().bottom - /** * Opens the first search candidate */ From 376ed60e6e2fce4a3fa896273171ab5dd27a4303 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 26 Aug 2025 02:48:08 +0200 Subject: [PATCH 39/92] UX: small maps now follow the rotation of the main map, fix #2433, add compass indicators to most of the minimaps --- src/Logic/UIEventSource.ts | 23 ++++++++++--------- src/Models/MapProperties.ts | 3 +++ src/UI/BigComponents/CompassWidget.svelte | 5 ++-- .../NewPointLocationInput.svelte | 5 +++- src/UI/BigComponents/WaySplitMap.svelte | 6 ++++- .../InputElement/Helpers/DistanceInput.svelte | 11 ++++++++- .../InputElement/Helpers/LocationInput.svelte | 6 ++++- src/UI/Map/MapLibreAdaptor.ts | 23 ++++++++++++++----- src/UI/Map/MaplibreMap.svelte | 1 + src/UI/Map/OverlayMap.svelte | 6 ++--- src/UI/Map/RasterLayerPicker.svelte | 2 +- .../Popup/ImportButtons/WayImportFlow.svelte | 4 ++-- src/UI/Popup/MinimapViz.svelte | 2 ++ src/UI/Popup/MoveWizard.svelte | 5 ++-- src/UI/Popup/SplitRoadWizard.svelte | 2 +- 15 files changed, 71 insertions(+), 33 deletions(-) diff --git a/src/Logic/UIEventSource.ts b/src/Logic/UIEventSource.ts index 22e2431cc..c4af67e6e 100644 --- a/src/Logic/UIEventSource.ts +++ b/src/Logic/UIEventSource.ts @@ -87,6 +87,8 @@ export class Stores { }) return newStore } + + } export abstract class Store implements Readable { @@ -347,6 +349,16 @@ export abstract class Store implements Readable { } }) } + + /** + * Create a new UIEVentSource. Whenever 'this.data' changes, the returned UIEventSource will get this value as well. + * However, this value can be overridden without affecting source + */ + public followingClone(): UIEventSource { + const src = new UIEventSource(this.data) + this.addCallback((t) => src.setData(t)) + return src + } } export class ImmutableStore extends Store { @@ -814,17 +826,6 @@ export class UIEventSource extends Store implements Writable { (b) => JSON.stringify(b) ?? "" ) } - - /** - * Create a new UIEVentSource. Whenever 'source' changes, the returned UIEventSource will get this value as well. - * However, this value can be overriden without affecting source - */ - static feedFrom(store: Store): UIEventSource { - const src = new UIEventSource(store.data) - store.addCallback((t) => src.setData(t)) - return src - } - /** * Adds a callback * diff --git a/src/Models/MapProperties.ts b/src/Models/MapProperties.ts index b5a3f1974..e4e46ba58 100644 --- a/src/Models/MapProperties.ts +++ b/src/Models/MapProperties.ts @@ -18,6 +18,9 @@ export interface MapProperties { readonly maxbounds: UIEventSource readonly allowMoving: UIEventSource readonly allowRotating: UIEventSource + /** + * Current rotation of the map, ccw in degrees + */ readonly rotation: UIEventSource readonly pitch: UIEventSource readonly lastClickLocation: Store<{ diff --git a/src/UI/BigComponents/CompassWidget.svelte b/src/UI/BigComponents/CompassWidget.svelte index 8cec2bc39..dc7e7d745 100644 --- a/src/UI/BigComponents/CompassWidget.svelte +++ b/src/UI/BigComponents/CompassWidget.svelte @@ -22,7 +22,7 @@ let compassLoaded = Orientation.singleton.gotMeasurement let allowRotation = mapProperties.allowRotating - function clicked(e: Event) { + function clicked() { if (mapProperties.rotation.data === 0) { if (mapProperties.allowRotating.data && compassLoaded.data) { mapProperties.rotation.set(orientation.data) @@ -35,9 +35,8 @@ {#if $allowRotation || $gotNonZero}

-
+
Date: Tue, 26 Aug 2025 09:05:28 +0200 Subject: [PATCH 45/92] add hut.json --- assets/layers/hut/hut.json | 160 +++++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 assets/layers/hut/hut.json diff --git a/assets/layers/hut/hut.json b/assets/layers/hut/hut.json new file mode 100644 index 000000000..563a37332 --- /dev/null +++ b/assets/layers/hut/hut.json @@ -0,0 +1,160 @@ +{ + "id": "hut", + "name": { + "en": "Huts" + }, + "description": { + "en": "Layer showing basic huts, wilderness huts and alpine huts" + }, + "source": { + "osmTags": { + "or": [ + "tourism=wilderness_hut", + "tourism=alpine_hut", + { + "and": [ + "amenity=shelter", + "shelter_type=basic_hut" + ] + } + ] + } + }, + "minzoom": 10, + "title": { + "render": "Hut", + "mappings": [ + { + "if": "name~*", + "then": "{name}" + }, + { + "if": "tourism=wilderness_hut", + "then": "wilderness hut" + }, + { + "if": "tourism=alpine_hut", + "then": "alpine hut" + }, + { + "if": { + "and": [ + "amenity=shelter", + "shelter_type=basic_hut" + ] + }, + "then": "basic hut" + } + ] + }, + "pointRendering": [ + { + "location": [ + "point", + "centroid" + ], + "marker": [ + { + "icon": { + "render": "./assets/layers/shelter/shelter.svg", + "mappings": [ + { + "if": "tourism=wilderness_hut", + "then": "https://wiki.openstreetmap.org/w/images/8/8e/Wilderness_hut.svg" + }, + { + "if": "tourism=alpine_hut", + "then": "https://wiki.openstreetmap.org/w/images/f/f1/Alpinehut.svg" + }, + { + "if": { + "and": [ + "amenity=shelter", + "shelter_type=basic_hut" + ] + }, + "then": "./assets/layers/shelter/shelter.svg" + } + ] + } + } + ] + } + ], + "lineRendering": [], + "presets": [ + { + "tags": [ + "tourism=wilderness_hut" + ], + "title": { + "en": "wilderness hut" + }, + "description": { + "en": "An unserviced fully enclosed hut (with roof and walls) with beds or suitable sleeping areas and a fireplace or stove for heating and cooking." + } + }, + { + "tags": [ + "tourism=alpine_hut" + ], + "title": { + "en": "alpine hut" + }, + "description": { + "en": "A serviced remote building located in the mountains intended to provide board and lodging." + } + }, + { + "tags": [ + "amenity=shelter", + "shelter_type=basic_hut" + ], + "title": { + "en": "basic hut" + }, + "description": { + "en": "An unserviced fully enclosed hut (with roof and walls) with beds or suitable sleeping areas without a fireplace or stove." + } + } + ], + "tagRenderings": [ + "images", + "name", + "reservation", + "caravansites.caravansites-fee", + { + "id": "drinking_water", + "question": { + "en": "Is drinking water available here?" + }, + "mappings": [ + { + "if": "drinking_water=yes", + "then": { + "en": "Here is drinking water available." + } + }, + { + "if": "drinking_water=no", + "then": { + "en": "Here is no drinking water available." + } + } + ] + }, + "has_toilets", + "description", + { + "id": "preset_type", + "render": "{preset_type_select()}" + } + ], + "filter":[ + "free" + ], + "allowMove": { + "enableRelocation": false, + "enableImproveAccuracy": true + } +} \ No newline at end of file From 8f776bf0306338f0bac36db20006f7c26ac49f92 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 26 Aug 2025 18:00:46 +0200 Subject: [PATCH 46/92] Fix: fix tests --- src/Utils/Lists.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Utils/Lists.ts b/src/Utils/Lists.ts index 7340dbe68..3e0b71b57 100644 --- a/src/Utils/Lists.ts +++ b/src/Utils/Lists.ts @@ -98,7 +98,7 @@ export class Lists { /** * In the given list, all values which are lists will be merged with the values, e.g. * - * Utils.Flatten([ [1,2], 3, [4, [5 ,6]] ]) // => [1, 2, 3, 4, [5, 6]] + * Lists.flatten([ [1,2], 3, [4, [5 ,6]] ]) // => [1, 2, 3, 4, [5, 6]] */ public static flatten(list: (T | T[])[]): T[] { const result = [] @@ -155,9 +155,9 @@ export class Lists { /** * Finds all duplicates in a list of strings * - * Utils.Duplicates(["a", "b", "c"]) // => [] - * Utils.Duplicates(["a", "b","c","b"] // => ["b"] - * Utils.Duplicates(["a", "b","c","b","b"] // => ["b"] + * Lists.duplicates(["a", "b", "c"]) // => [] + * Lists.duplicates(["a", "b","c","b"] // => ["b"] + * Lists.duplicates(["a", "b","c","b","b"] // => ["b"] * */ public static duplicates(arr: string[]): string[] { From 32bc69bb32942dae5c859a4c1d223fc9b34105d3 Mon Sep 17 00:00:00 2001 From: Osmwithspace <> Date: Tue, 26 Aug 2025 22:54:04 +0200 Subject: [PATCH 47/92] replace remote icons --- assets/layers/hut/alpine_hut.svg | 15 +++++++++++++ assets/layers/hut/alpine_hut.svg.license | 2 ++ assets/layers/hut/hut.json | 4 ++-- assets/layers/hut/license_info.json | 22 ++++++++++++++++++++ assets/layers/hut/wilderness_hut.svg | 15 +++++++++++++ assets/layers/hut/wilderness_hut.svg.license | 2 ++ 6 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 assets/layers/hut/alpine_hut.svg create mode 100644 assets/layers/hut/alpine_hut.svg.license create mode 100644 assets/layers/hut/license_info.json create mode 100644 assets/layers/hut/wilderness_hut.svg create mode 100644 assets/layers/hut/wilderness_hut.svg.license diff --git a/assets/layers/hut/alpine_hut.svg b/assets/layers/hut/alpine_hut.svg new file mode 100644 index 000000000..e790edd62 --- /dev/null +++ b/assets/layers/hut/alpine_hut.svg @@ -0,0 +1,15 @@ + + + + + + image/svg+xml + + + + + + + + + \ No newline at end of file diff --git a/assets/layers/hut/alpine_hut.svg.license b/assets/layers/hut/alpine_hut.svg.license new file mode 100644 index 000000000..5e3616ed7 --- /dev/null +++ b/assets/layers/hut/alpine_hut.svg.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: Geozeisig +SPDX-License-Identifier: CC0-1.0 \ No newline at end of file diff --git a/assets/layers/hut/hut.json b/assets/layers/hut/hut.json index 563a37332..2aac48426 100644 --- a/assets/layers/hut/hut.json +++ b/assets/layers/hut/hut.json @@ -60,11 +60,11 @@ "mappings": [ { "if": "tourism=wilderness_hut", - "then": "https://wiki.openstreetmap.org/w/images/8/8e/Wilderness_hut.svg" + "then": "./assets/layers/hut/wilderness_hut.svg" }, { "if": "tourism=alpine_hut", - "then": "https://wiki.openstreetmap.org/w/images/f/f1/Alpinehut.svg" + "then": "./assets/layers/hut/alpine_hut.svg" }, { "if": { diff --git a/assets/layers/hut/license_info.json b/assets/layers/hut/license_info.json new file mode 100644 index 000000000..f8341f4f8 --- /dev/null +++ b/assets/layers/hut/license_info.json @@ -0,0 +1,22 @@ +[ + { + "path": "alpine_hut.svg", + "license": "CC0-1.0", + "authors": [ + "Geozeisig" + ], + "sources": [ + "https://wiki.openstreetmap.org/wiki/File:Alpinehut.svg" + ] + }, + { + "path": "wilderness_hut.svg", + "license": "CC0-1.0", + "authors": [ + "Geozeisig" + ], + "sources": [ + "https://wiki.openstreetmap.org/wiki/File:Wilderness_hut.svg" + ] + } +] \ No newline at end of file diff --git a/assets/layers/hut/wilderness_hut.svg b/assets/layers/hut/wilderness_hut.svg new file mode 100644 index 000000000..cbf146077 --- /dev/null +++ b/assets/layers/hut/wilderness_hut.svg @@ -0,0 +1,15 @@ + + + + + + image/svg+xml + + + + + + + + + \ No newline at end of file diff --git a/assets/layers/hut/wilderness_hut.svg.license b/assets/layers/hut/wilderness_hut.svg.license new file mode 100644 index 000000000..5e3616ed7 --- /dev/null +++ b/assets/layers/hut/wilderness_hut.svg.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: Geozeisig +SPDX-License-Identifier: CC0-1.0 \ No newline at end of file From 05631bb32db7ad31c4ddd46dd8448600999ee014 Mon Sep 17 00:00:00 2001 From: Osmwithspace <> Date: Tue, 26 Aug 2025 22:57:17 +0200 Subject: [PATCH 48/92] add hut layer in nature theme --- assets/themes/nature/nature.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/assets/themes/nature/nature.json b/assets/themes/nature/nature.json index 7158a1d5e..503d53129 100644 --- a/assets/themes/nature/nature.json +++ b/assets/themes/nature/nature.json @@ -60,7 +60,8 @@ "nature_reserve", { "builtin": [ - "shelter" + "shelter", + "hut" ], "override": { "minzoom": 11 From 94469f7123d618ce54bb6496e08134ae487bc321 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 26 Aug 2025 23:40:48 +0200 Subject: [PATCH 49/92] UX: improve 'offline' indicator, make translatable --- langs/en.json | 1 + src/UI/Image/UploadingImageCounter.svelte | 6 ++++-- src/UI/ThemeViewGUI.svelte | 10 +++++++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/langs/en.json b/langs/en.json index 0f25afbfe..c8f76a7ad 100644 --- a/langs/en.json +++ b/langs/en.json @@ -640,6 +640,7 @@ }, "noBlur": "Images will not be blurred. Do not photograph people", "offline": "You are currently offline. Uploading images be attempted when your internet is back", + "offlinePending": "{count} images are currently in the queue", "one": { "done": "Your image was successfully uploaded. Thank you!", "failed": "Sorry, we could not upload your image", diff --git a/src/UI/Image/UploadingImageCounter.svelte b/src/UI/Image/UploadingImageCounter.svelte index 4e214a4d2..64144ec38 100644 --- a/src/UI/Image/UploadingImageCounter.svelte +++ b/src/UI/Image/UploadingImageCounter.svelte @@ -94,8 +94,10 @@
{/if} {#if !$online && $pending > 0} -
- +
+ + +
{:else if $failed > dismissed} (dismissed = $failed)} {state} /> diff --git a/src/UI/ThemeViewGUI.svelte b/src/UI/ThemeViewGUI.svelte index 98d347fa6..ebaf594bb 100644 --- a/src/UI/ThemeViewGUI.svelte +++ b/src/UI/ThemeViewGUI.svelte @@ -56,6 +56,8 @@ import { AndroidPolyfill } from "../Logic/Web/AndroidPolyfill" import { IsOnline } from "../Logic/Web/IsOnline" import CompassWidget from "./BigComponents/CompassWidget.svelte" + import { WifiIcon } from "@babeard/svelte-heroicons/solid" + import Cross_bottom_right from "../assets/svg/Cross_bottom_right.svelte" export let state: WithSearchState new TitleHandler(state.selectedElement, state) @@ -438,7 +440,13 @@
Faking a user (Testmode)
{#if !$isOnline} -
Offline mode
+
+
+ + +
+ +
{:else if $apiState === "unknown"} {:else if $apiState !== "online"} From 94fbf1eccf99894eda4d5094eec744a931df4079 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Wed, 27 Aug 2025 00:05:14 +0200 Subject: [PATCH 50/92] Refactoring: move methods out of 'utils.ts' --- .../Actors/SelectedElementTagsUpdater.ts | 3 +- .../Actors/SaveFeatureSourceToLocalStorage.ts | 4 +- .../Sources/ChangeGeometryApplicator.ts | 4 +- .../Sources/OverpassFeatureSource.ts | 4 +- src/Logic/Search/FilterSearch.ts | 4 +- src/Logic/Search/LayerSearch.ts | 4 +- src/Logic/Search/LocalElementSearch.ts | 5 +- src/Logic/Search/SearchUtils.ts | 5 +- src/Logic/UIEventSource.ts | 3 +- .../ThemeConfig/Conversion/Validation.ts | 8 +- .../TagRendering/TagRenderingEditable.svelte | 3 +- src/UI/Studio/EditLayerState.ts | 10 +- src/Utils.ts | 99 ------------------- src/Utils/Lists.ts | 49 +++++++-- src/Utils/Objects.ts | 33 +++++++ src/Utils/Strings.ts | 38 +++++++ 16 files changed, 140 insertions(+), 136 deletions(-) create mode 100644 src/Utils/Objects.ts diff --git a/src/Logic/Actors/SelectedElementTagsUpdater.ts b/src/Logic/Actors/SelectedElementTagsUpdater.ts index 67a7c2b44..f34ec863e 100644 --- a/src/Logic/Actors/SelectedElementTagsUpdater.ts +++ b/src/Logic/Actors/SelectedElementTagsUpdater.ts @@ -10,6 +10,7 @@ import { Changes } from "../Osm/Changes" import ThemeConfig from "../../Models/ThemeConfig/ThemeConfig" import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore" import { WithChangesState } from "../../Models/ThemeViewState/WithChangesState" +import Objects from "../../Utils/Objects" export default class SelectedElementTagsUpdater { private static readonly metatags = new Set([ @@ -160,7 +161,7 @@ export default class SelectedElementTagsUpdater { const newGeometry = osmObject.asGeoJson()?.geometry const oldFeature = state.indexedFeatures.featuresById.data.get(id) const oldGeometry = oldFeature?.geometry - if (oldGeometry !== undefined && !Utils.SameObject(newGeometry, oldGeometry)) { + if (oldGeometry !== undefined && !Objects.sameObject(newGeometry, oldGeometry)) { console.log("Detected a difference in geometry for ", id) this.invalidateCache(s) oldFeature.geometry = newGeometry diff --git a/src/Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts b/src/Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts index c9cb4b04c..8f3d009f2 100644 --- a/src/Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts +++ b/src/Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts @@ -4,9 +4,9 @@ import TileLocalStorage from "./TileLocalStorage" import { GeoOperations } from "../../GeoOperations" import FeaturePropertiesStore from "./FeaturePropertiesStore" import { UIEventSource } from "../../UIEventSource" -import { Utils } from "../../../Utils" import { Tiles } from "../../../Models/TileRange" import { BBox } from "../../BBox" +import { Lists } from "../../../Utils/Lists" class SingleTileSaver { private readonly _storage: UIEventSource @@ -31,7 +31,7 @@ class SingleTileSaver { } public saveFeatures(features: Feature[]) { - if (Utils.sameList(features, this._storage.data)) { + if (Lists.sameList(features, this._storage.data)) { return } for (const feature of features) { diff --git a/src/Logic/FeatureSource/Sources/ChangeGeometryApplicator.ts b/src/Logic/FeatureSource/Sources/ChangeGeometryApplicator.ts index dee5f8994..90cc9ef1b 100644 --- a/src/Logic/FeatureSource/Sources/ChangeGeometryApplicator.ts +++ b/src/Logic/FeatureSource/Sources/ChangeGeometryApplicator.ts @@ -6,7 +6,7 @@ import { Stores, UIEventSource } from "../../UIEventSource" import { FeatureSource, IndexedFeatureSource } from "../FeatureSource" import { ChangeDescription, ChangeDescriptionTools } from "../../Osm/Actions/ChangeDescription" import { Feature } from "geojson" -import { Utils } from "../../../Utils" +import Objects from "../../../Utils/Objects" export default class ChangeGeometryApplicator implements FeatureSource { public readonly features: UIEventSource = new UIEventSource([]) @@ -69,7 +69,7 @@ export default class ChangeGeometryApplicator implements FeatureSource { // We only apply the last change as that one'll have the latest geometry const change = changesForFeature[changesForFeature.length - 1] copy.geometry = ChangeDescriptionTools.getGeojsonGeometry(change) - if (Utils.SameObject(copy.geometry, feature.geometry)) { + if (Objects.sameObject(copy.geometry, feature.geometry)) { // No actual changes: pass along the original newFeatures.push(feature) continue diff --git a/src/Logic/FeatureSource/Sources/OverpassFeatureSource.ts b/src/Logic/FeatureSource/Sources/OverpassFeatureSource.ts index 37be66206..7cbad0368 100644 --- a/src/Logic/FeatureSource/Sources/OverpassFeatureSource.ts +++ b/src/Logic/FeatureSource/Sources/OverpassFeatureSource.ts @@ -9,7 +9,7 @@ import { BBox } from "../../BBox" import { OsmFeature } from "../../../Models/OsmFeature" import { Lists } from "../../../Utils/Lists" -;("use strict") +("use strict") /** * A wrapper around the 'Overpass'-object. @@ -229,7 +229,7 @@ export default class OverpassFeatureSource im const requestedBounds = this.state.bounds.data if ( this._lastQueryBBox !== undefined && - Utils.sameList(this._layersToDownload.data, this._lastRequestedLayers) && + Lists.sameList(this._layersToDownload.data, this._lastRequestedLayers) && requestedBounds.isContainedIn(this._lastQueryBBox) ) { return undefined diff --git a/src/Logic/Search/FilterSearch.ts b/src/Logic/Search/FilterSearch.ts index 6dbf346f2..7cc80b653 100644 --- a/src/Logic/Search/FilterSearch.ts +++ b/src/Logic/Search/FilterSearch.ts @@ -32,7 +32,7 @@ export default class FilterSearch { .split(" ") .map((query) => { if (!Strings.isEmoji(query)) { - return Utils.simplifyStringForSearch(query) + return Strings.simplifyStringForSearch(query) } return query }) @@ -64,7 +64,7 @@ export default class FilterSearch { option.searchTerms?.["en"] ?? []), ].flatMap((term) => [term, ...(term?.split(" ") ?? [])]) - terms = terms.map((t) => Utils.simplifyStringForSearch(t)) + terms = terms.map((t) => Strings.simplifyStringForSearch(t)) terms.push(option.emoji) Lists.noNullInplace(terms) const distances = queries.flatMap((query) => diff --git a/src/Logic/Search/LayerSearch.ts b/src/Logic/Search/LayerSearch.ts index 3d23767c4..ebe644122 100644 --- a/src/Logic/Search/LayerSearch.ts +++ b/src/Logic/Search/LayerSearch.ts @@ -2,7 +2,7 @@ import SearchUtils from "./SearchUtils" import ThemeSearch from "./ThemeSearch" import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import ThemeConfig from "../../Models/ThemeConfig/ThemeConfig" -import { Utils } from "../../Utils" +import { Strings } from "../../Utils/Strings" export default class LayerSearch { private readonly _theme: ThemeConfig @@ -24,7 +24,7 @@ export default class LayerSearch { const queryParts = query .trim() .split(" ") - .map((q) => Utils.simplifyStringForSearch(q)) + .map((q) => Strings.simplifyStringForSearch(q)) for (const id in ThemeSearch.officialThemes.layers) { if (options?.whitelist && !options?.whitelist.has(id)) { continue diff --git a/src/Logic/Search/LocalElementSearch.ts b/src/Logic/Search/LocalElementSearch.ts index b34e34bf4..e4d697816 100644 --- a/src/Logic/Search/LocalElementSearch.ts +++ b/src/Logic/Search/LocalElementSearch.ts @@ -6,6 +6,7 @@ import { GeoOperations } from "../GeoOperations" import { ImmutableStore, Store, Stores } from "../UIEventSource" import OpenStreetMapIdSearch from "./OpenStreetMapIdSearch" import { Lists } from "../../Utils/Lists" +import { Strings } from "../../Utils/Strings" type IntermediateResult = { feature: Feature @@ -61,7 +62,7 @@ export default class LocalElementSearch implements GeocodingProvider { ...searchTerms .flatMap((entry) => entry.split(/ /)) .map((entry) => { - let simplified = Utils.simplifyStringForSearch(entry) + let simplified = Strings.simplifyStringForSearch(entry) if (matchStart) { simplified = simplified.slice(0, query.length) } @@ -103,7 +104,7 @@ export default class LocalElementSearch implements GeocodingProvider { const centerPoint: [number, number] = [center.lon, center.lat] const properties = this._state.perLayer const candidateId = OpenStreetMapIdSearch.extractId(query) - query = Utils.simplifyStringForSearch(query) + query = Strings.simplifyStringForSearch(query) const partials: Store[] = [] diff --git a/src/Logic/Search/SearchUtils.ts b/src/Logic/Search/SearchUtils.ts index a50ac5bf7..e5f993de2 100644 --- a/src/Logic/Search/SearchUtils.ts +++ b/src/Logic/Search/SearchUtils.ts @@ -2,6 +2,7 @@ import Locale from "../../UI/i18n/Locale" import { Utils } from "../../Utils" import ThemeSearch from "./ThemeSearch" import { Lists } from "../../Utils/Lists" +import { Strings } from "../../Utils/Strings" export default class SearchUtils { /** Applies special search terms, such as 'studio', 'osmcha', ... @@ -60,7 +61,7 @@ export default class SearchUtils { const queryParts = query .trim() .split(" ") - .map((q) => Utils.simplifyStringForSearch(q)) + .map((q) => Strings.simplifyStringForSearch(q)) let terms: string[] if (Array.isArray(keywords)) { terms = keywords @@ -74,7 +75,7 @@ export default class SearchUtils { const q = queryParts[i] let minDistance: number = 99 for (const term of termsAll) { - const d = Utils.levenshteinDistance(q, Utils.simplifyStringForSearch(term)) + const d = Utils.levenshteinDistance(q, Strings.simplifyStringForSearch(term)) if (d < minDistance) { minDistance = d } diff --git a/src/Logic/UIEventSource.ts b/src/Logic/UIEventSource.ts index c4af67e6e..3790be513 100644 --- a/src/Logic/UIEventSource.ts +++ b/src/Logic/UIEventSource.ts @@ -1,5 +1,6 @@ import { Utils } from "../Utils" import { Readable, Subscriber, Unsubscriber, Updater, Writable } from "svelte/store" +import { Lists } from "../Utils/Lists" /** * Various static utils @@ -66,7 +67,7 @@ export class Stores { stable.setData(undefined) return } - if (Utils.sameList(stable.data, list)) { + if (Lists.sameList(stable.data, list)) { return } stable.setData(list) diff --git a/src/Models/ThemeConfig/Conversion/Validation.ts b/src/Models/ThemeConfig/Conversion/Validation.ts index 4d4919442..c4383e497 100644 --- a/src/Models/ThemeConfig/Conversion/Validation.ts +++ b/src/Models/ThemeConfig/Conversion/Validation.ts @@ -25,6 +25,7 @@ import { eliCategory } from "../../RasterLayerProperties" import licenses from "../../../assets/generated/license_info.json" import { Strings } from "../../../Utils/Strings" import { Lists } from "../../../Utils/Lists" +import Objects from "../../../Utils/Objects" export class ValidateLanguageCompleteness extends DesugaringStep { private readonly _languages: string[] @@ -1085,11 +1086,8 @@ export class DetectDuplicatePresets extends DesugaringStep { const presetBTags = optimizedTags[j] const presetB = presets[j] if ( - Utils.SameObject(presetATags, presetBTags) && - Utils.sameList( - presetA.preciseInput.snapToLayers, - presetB.preciseInput.snapToLayers - ) + Objects.sameObject(presetATags, presetBTags) && + Lists.sameList(presetA.preciseInput.snapToLayers, presetB.preciseInput.snapToLayers) ) { context.err( `This theme has multiple presets with the same tags: ${presetATags.asHumanString( diff --git a/src/UI/Popup/TagRendering/TagRenderingEditable.svelte b/src/UI/Popup/TagRendering/TagRenderingEditable.svelte index c0b16be48..78f7c3006 100644 --- a/src/UI/Popup/TagRendering/TagRenderingEditable.svelte +++ b/src/UI/Popup/TagRendering/TagRenderingEditable.svelte @@ -12,6 +12,7 @@ import { Utils } from "../../../Utils" import { twMerge } from "tailwind-merge" import EditButton from "./EditButton.svelte" + import { Strings } from "../../../Utils/Strings" export let config: TagRenderingConfig export let tags: UIEventSource> @@ -83,7 +84,7 @@ onDestroy(highlightedRendering?.addCallbackAndRun(() => setHighlighting())) onDestroy(_htmlElement.addCallbackAndRun(() => setHighlighting())) } - let answerId = "answer-" + Utils.randomString(5) + let answerId = "answer-" + Strings.randomString(5) let debug = state?.featureSwitches?.featureSwitchIsDebugging ?? new ImmutableStore(false) let apiState: Store = state?.osmConnection?.apiIsOnline ?? new ImmutableStore("online") diff --git a/src/UI/Studio/EditLayerState.ts b/src/UI/Studio/EditLayerState.ts index f73137ab5..594776421 100644 --- a/src/UI/Studio/EditLayerState.ts +++ b/src/UI/Studio/EditLayerState.ts @@ -1,12 +1,7 @@ import { ConfigMeta } from "./configMeta" import { Store, UIEventSource } from "../../Logic/UIEventSource" import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson" -import { - Conversion, - ConversionMessage, - DesugaringContext, - Pipe, -} from "../../Models/ThemeConfig/Conversion/Conversion" +import { Conversion, ConversionMessage, DesugaringContext, Pipe } from "../../Models/ThemeConfig/Conversion/Conversion" import { PrepareLayer } from "../../Models/ThemeConfig/Conversion/PrepareLayer" import { PrevalidateTheme, ValidateLayer } from "../../Models/ThemeConfig/Conversion/Validation" import { AllSharedLayers } from "../../Customizations/AllSharedLayers" @@ -26,6 +21,7 @@ import { TagRenderingConfigJson } from "../../Models/ThemeConfig/Json/TagRenderi import { ValidateTheme } from "../../Models/ThemeConfig/Conversion/ValidateTheme" import * as questions from "../../../public/assets/generated/layers/questions.json" import { Lists } from "../../Utils/Lists" +import { Strings } from "../../Utils/Strings" export interface HighlightedTagRendering { path: ReadonlyArray @@ -431,7 +427,7 @@ export default class EditLayerState extends EditJsonState { } if (!tr["id"] && !tr["override"]) { const qtr = tr - let id = "" + i + "_" + Utils.randomString(5) + let id = "" + i + "_" + Strings.randomString(5) if (qtr?.freeform?.key) { id = qtr?.freeform?.key } else if (qtr.mappings?.[0]?.if) { diff --git a/src/Utils.ts b/src/Utils.ts index c4f203b6d..123818e2c 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -1378,66 +1378,6 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be } element.scrollIntoView({ behavior: "smooth", block: "nearest" }) } - - /** - * Returns true if the contents of `a` are the same (and in the same order) as `b`. - * Might have false negatives in some cases - * @param a - * @param b - */ - public static sameList(a: ReadonlyArray, b: ReadonlyArray) { - if (a == b) { - return true - } - if (a === undefined || a === null || b === undefined || b === null) { - return false - } - if (a.length !== b.length) { - return false - } - for (let i = 0; i < a.length; i++) { - const ai = a[i] - const bi = b[i] - if (ai == bi) { - continue - } - if (ai === bi) { - continue - } - return false - } - return true - } - - public static SameObject(a: T, b: T, ignoreKeys?: string[]): boolean { - if (a === b) { - return true - } - if (a === undefined || a === null || b === null || b === undefined) { - return false - } - if (typeof a === "object" && typeof b === "object") { - for (const aKey in a) { - if (!(aKey in b)) { - return false - } - } - - for (const bKey in b) { - if (!(bKey in a)) { - return false - } - } - for (const k in a) { - if (!Utils.SameObject(a[k], b[k])) { - return false - } - } - return true - } - return false - } - /** * * Utils.splitIntoSubstitutionParts("abc") // => [{message: "abc"}] @@ -1510,45 +1450,6 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be filename: path.substring(path.lastIndexOf("/") + 1), } } - - /** - * Removes accents from a string - * @param str - * @constructor - * - * Utils.RemoveDiacritics("bâtiments") // => "batiments" - * Utils.RemoveDiacritics(undefined) // => undefined - */ - public static RemoveDiacritics(str?: string): string { - // See #1729 - if (!str) { - return str - } - return str.normalize("NFD").replace(/\p{Diacritic}/gu, "") - } - - /** - * Simplifies a string to increase the chance of a match - * @param str - * Utils.simplifyStringForSearch("abc def; ghi 564") // => "abcdefghi564" - * Utils.simplifyStringForSearch("âbc déf; ghi 564") // => "abcdefghi564" - * Utils.simplifyStringForSearch(undefined) // => undefined - */ - public static simplifyStringForSearch(str: string): string { - return Utils.RemoveDiacritics(str) - ?.toLowerCase() - ?.replace(/[^a-z0-9]/g, "") - } - - public static randomString(length: number): string { - let result = "" - for (let i = 0; i < length; i++) { - const chr = Math.random().toString(36).substr(2, 3) - result += chr - } - return result - } - /** * Recursively rewrites all keys from `+key`, `key+` and `=key` into `key * diff --git a/src/Utils/Lists.ts b/src/Utils/Lists.ts index 3e0b71b57..7d06a395a 100644 --- a/src/Utils/Lists.ts +++ b/src/Utils/Lists.ts @@ -43,12 +43,15 @@ export class Lists { * Elements are returned in the same order as they appear in the lists. * Null/Undefined is returned as is. If an empty array is given, a new empty array will be returned */ - public static dedup(arr: NonNullable): NonNullable + public static dedup(arr: NonNullable>): NonNullable public static dedup(arr: undefined): undefined - public static dedup(arr: string[] | undefined): string[] | undefined - public static dedup(arr: string[]): string[] { - if (arr === undefined || arr === null) { - return arr + public static dedup(arr: ReadonlyArray | undefined): string[] | undefined + public static dedup(arr: ReadonlyArray): string[] { + if (arr === undefined) { + return undefined + } + if (arr === null) { + return null } const newArr = [] for (const string of arr) { @@ -60,8 +63,8 @@ export class Lists { } public static dedupT(arr: ReadonlyArray): T[] - public static dedupT(arr: null): null - public static dedupT(arr: undefined): undefined + public static dedupT(arr: null): null + public static dedupT(arr: undefined): undefined public static dedupT(arr: ReadonlyArray): T[] { if (arr === undefined) { return undefined @@ -160,7 +163,7 @@ export class Lists { * Lists.duplicates(["a", "b","c","b","b"] // => ["b"] * */ - public static duplicates(arr: string[]): string[] { + public static duplicates(arr: ReadonlyArray): string[] { if (arr === undefined) { return undefined } @@ -175,4 +178,34 @@ export class Lists { return Array.from(duplicates) } + /** + * Returns true if the contents of `a` are the same (and in the same order) as `b`. + * Might have false negatives in some cases + * @param a + * @param b + */ + public static sameList(a: ReadonlyArray, b: ReadonlyArray): boolean { + if (a == b) { + return true + } + if (a === undefined || a === null || b === undefined || b === null) { + return false + } + if (a.length !== b.length) { + return false + } + for (let i = 0; i < a.length; i++) { + const ai = a[i] + const bi = b[i] + if (ai == bi) { + continue + } + if (ai === bi) { + continue + } + return false + } + return true + } + } diff --git a/src/Utils/Objects.ts b/src/Utils/Objects.ts new file mode 100644 index 000000000..1d8b95c15 --- /dev/null +++ b/src/Utils/Objects.ts @@ -0,0 +1,33 @@ +/** + * Various object-related utils + */ +export default class Objects { + public static sameObject(a: T, b: T, ignoreKeys?: string[]): boolean { + if (a === b) { + return true + } + if (a === undefined || a === null || b === null || b === undefined) { + return false + } + if (typeof a === "object" && typeof b === "object") { + for (const aKey in a) { + if (!(aKey in b)) { + return false + } + } + + for (const bKey in b) { + if (!(bKey in a)) { + return false + } + } + for (const k in a) { + if (!Objects.sameObject(a[k], b[k])) { + return false + } + } + return true + } + return false + } +} diff --git a/src/Utils/Strings.ts b/src/Utils/Strings.ts index ae619a3e4..709014115 100644 --- a/src/Utils/Strings.ts +++ b/src/Utils/Strings.ts @@ -21,4 +21,42 @@ export class Strings { public static isEmojiFlag(string: string): boolean { return /[🇦-🇿]{2}/u.test(string) // flags, see https://stackoverflow.com/questions/53360006/detect-with-regex-if-emoji-is-country-flag } + + /** + * Removes accents from a string + * @param str + * @constructor + * + * Strings.removeDiacritics("bâtiments") // => "batiments" + * Strings.removeDiacritics(undefined) // => undefined + */ + public static removeDiacritics(str?: string): string { + // See #1729 + if (!str) { + return str + } + return str.normalize("NFD").replace(/\p{Diacritic}/gu, "") + } + + /** + * Simplifies a string to increase the chance of a match + * @param str + * Strings.simplifyStringForSearch("abc def; ghi 564") // => "abcdefghi564" + * Strings.simplifyStringForSearch("âbc déf; ghi 564") // => "abcdefghi564" + * Strings.simplifyStringForSearch(undefined) // => undefined + */ + public static simplifyStringForSearch(str: string): string { + return Strings.removeDiacritics(str) + ?.toLowerCase() + ?.replace(/[^a-z0-9]/g, "") + } + + public static randomString(length: number): string { + let result = "" + for (let i = 0; i < length; i++) { + const chr = Math.random().toString(36).substr(2, 3) + result += chr + } + return result + } } From 3d225655df43b64518e28a127111ee650d380b4e Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Wed, 27 Aug 2025 00:18:49 +0200 Subject: [PATCH 51/92] Refactoring(uieventsource): introduce 'once' --- src/Logic/Osm/OsmPreferences.ts | 3 +-- src/Logic/State/UserRelatedState.ts | 2 +- src/Logic/UIEventSource.ts | 9 +++++++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Logic/Osm/OsmPreferences.ts b/src/Logic/Osm/OsmPreferences.ts index db8dcfa27..e1dda7820 100644 --- a/src/Logic/Osm/OsmPreferences.ts +++ b/src/Logic/Osm/OsmPreferences.ts @@ -34,9 +34,8 @@ export class OsmPreferences { this.auth = auth this._fakeUser = fakeUser this.osmConnection = osmConnection - osmConnection.userDetails.addCallbackAndRunD(() => { + osmConnection.userDetails.once(() => { this.loadBulkPreferences() - return true }) } diff --git a/src/Logic/State/UserRelatedState.ts b/src/Logic/State/UserRelatedState.ts index fe78cab3d..b7d93bc58 100644 --- a/src/Logic/State/UserRelatedState.ts +++ b/src/Logic/State/UserRelatedState.ts @@ -389,7 +389,7 @@ export default class UserRelatedState { */ public addUnofficialTheme(themeInfo: MinimalThemeInformation) { const pref = this.osmConnection.getPreference("unofficial-theme-" + themeInfo.id) - this.osmConnection.isLoggedIn.when(() => pref.set(JSON.stringify(themeInfo))) + this.osmConnection.isLoggedIn.once(() => pref.set(JSON.stringify(themeInfo))) } public getUnofficialTheme(id: string): MinimalThemeInformation | undefined { diff --git a/src/Logic/UIEventSource.ts b/src/Logic/UIEventSource.ts index c4af67e6e..015715952 100644 --- a/src/Logic/UIEventSource.ts +++ b/src/Logic/UIEventSource.ts @@ -340,8 +340,13 @@ export abstract class Store implements Readable { public abstract destroy() - when(callback: () => void, condition?: (v: T) => boolean) { - condition ??= (v) => v === true + /** + * Execute `f` once, but only if the value within is true-ish (or the explicit 'condition' is met) + * @param callback + * @param condition + */ + public once(callback: () => void, condition?: (v: T) => boolean) { + condition ??= (v) => !!v this.addCallbackAndRunD((v) => { if (condition(v)) { callback() From dd0ed24f3b4425676d6f85a4467bb7c1662459fd Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Wed, 27 Aug 2025 00:19:13 +0200 Subject: [PATCH 52/92] Feature(offline): attempt to upload pictures when connection is restored --- src/Logic/ImageProviders/ImageUploadManager.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Logic/ImageProviders/ImageUploadManager.ts b/src/Logic/ImageProviders/ImageUploadManager.ts index d229998cf..e9aa4cd69 100644 --- a/src/Logic/ImageProviders/ImageUploadManager.ts +++ b/src/Logic/ImageProviders/ImageUploadManager.ts @@ -82,10 +82,17 @@ export class ImageUploadManager { this._changes = changes this._gps = gpsLocation this._reportError = reportError - Stores.chronic(5 * 60000).addCallback(() => { + Stores.chronic(5 * 60000).addCallback(async () => { // If images failed to upload: attempt to reupload - this.uploadQueue() + await this.uploadQueue() }) + + IsOnline.isOnline.addCallback(async (isOnline) => { + if (isOnline) { + await this.uploadQueue() + } + }, + ) } public async canBeUploaded(file: File): Promise { From a5bab8d819082dee354eed1fc7bcac998c4f49ab Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Wed, 27 Aug 2025 00:19:29 +0200 Subject: [PATCH 53/92] Feature(offline): UX: add icon --- src/UI/Image/UploadingImageCounter.svelte | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/UI/Image/UploadingImageCounter.svelte b/src/UI/Image/UploadingImageCounter.svelte index 2a332b964..9bf2c4b02 100644 --- a/src/UI/Image/UploadingImageCounter.svelte +++ b/src/UI/Image/UploadingImageCounter.svelte @@ -13,6 +13,8 @@ import Loading from "../Base/Loading.svelte" import UploadFailedMessage from "./UploadFailedMessage.svelte" import { IsOnline } from "../../Logic/Web/IsOnline" + import { WifiIcon } from "@babeard/svelte-heroicons/solid" + import Cross_bottom_right from "../../assets/svg/Cross_bottom_right.svelte" export let state: SpecialVisualizationState export let tags: Store = undefined @@ -98,9 +100,15 @@
{/if} {#if !$online && $pending > 0} -
+
+
+ + +
+
+
{:else if $failed > dismissed} Date: Wed, 27 Aug 2025 00:31:50 +0200 Subject: [PATCH 54/92] Feature(offline): reload data of current view when internet is restored, see #2111 --- src/Logic/FeatureSource/Sources/ThemeSource.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/Logic/FeatureSource/Sources/ThemeSource.ts b/src/Logic/FeatureSource/Sources/ThemeSource.ts index 29d38ee38..a9c4a91ed 100644 --- a/src/Logic/FeatureSource/Sources/ThemeSource.ts +++ b/src/Logic/FeatureSource/Sources/ThemeSource.ts @@ -14,6 +14,7 @@ import DynamicMvtileSource from "../TiledFeatureSource/DynamicMvtTileSource" import FeatureSourceMerger from "./FeatureSourceMerger" import { Feature, Geometry } from "geojson" import { OsmFeature } from "../../../Models/OsmFeature" +import { IsOnline } from "../../Web/IsOnline" /** * This source will fetch the needed data from various sources for the given layout. @@ -67,6 +68,18 @@ export default class ThemeSource core.featuresById.addCallbackAndRun((data) => featuresById.set(data)) return core }) + + IsOnline.isOnline.addCallback(async online => { + if (online) { + // Connectivity is restored - let us try to update the data + console.log("Internet got restored - starting to download all data") + isLoading.set(true) + await this.downloadAll() + isLoading.set(false) + } else { + isLoading.set(false) + } + }) } public async downloadAll() { From ea0c87b3ff220c3554289b7daf8f255ab01f1715 Mon Sep 17 00:00:00 2001 From: Osmwithspace <> Date: Wed, 27 Aug 2025 09:55:17 +0200 Subject: [PATCH 55/92] add shelter type question in hut layer --- assets/layers/hut/hut.json | 112 +++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/assets/layers/hut/hut.json b/assets/layers/hut/hut.json index 2aac48426..681d1d06d 100644 --- a/assets/layers/hut/hut.json +++ b/assets/layers/hut/hut.json @@ -148,6 +148,118 @@ { "id": "preset_type", "render": "{preset_type_select()}" + }, + { + "id": "shelter-type", + "question": { + "en": "What kind of shelter is this?", + "ca": "Quin tipus de refugi és aquest?", + "cs": "Co je to za přístřešek?", + "de": "Um welche Art von Unterstand handelt es sich?", + "es": "¿Qué tipo de refugio es este?", + "it": "Che tipo di riparo è questo?", + "nl": "Wat voor schuilplaats is dit?", + "uk": "Що це за притулок?" + }, + "render": { + "en": "Shelter type: {shelter_type}", + "ca": "Tipus de refugi: {shelter_type}", + "cs": "Typ přístřešku: {shelter_type}", + "de": "Art des Unterstands: {shelter_type}", + "es": "Tipo de refugio: {shelter_type}", + "it": "Tipo di riparo: {shelter_type}", + "uk": "Тип притулку: {shelter_type}" + }, + "freeform": { + "key": "shelter_type", + "type": "string" + }, + "condition": { + "and": [ + "amenity=shelter", + "shelter_type=basic_hut" + ] + }, + "mappings": [ + { + "if": "shelter_type=public_transport", + "then": { + "en": "This is a shelter at a public transport stop.", + "ca": "Es tracta d'un refugi en una parada de transport públic.", + "cs": "Jedná se o přístřešek u zastávky MHD.", + "de": "Das ist ein Unterstand an einer Haltestelle für öffentliche Verkehrsmittel.", + "es": "Este es un refugio en una parada de transporte público.", + "it": "Questo è un riparo presso una fermata del trasporto pubblico.", + "nl": "Dit is een schuilplaats bij een halte voor openbaar vervoer.", + "uk": "Це притулок на зупинці громадського транспорту." + } + }, + { + "if": "shelter_type=picnic_shelter", + "then": { + "en": "This is a shelter protecting from rain at a picnic site.", + "ca": "Es tracta d'un refugi que protegeix de la pluja en un lloc de pícnic.", + "cs": "Jedná se o přístřešek chránící před deštěm na piknikovém místě.", + "de": "Dies ist ein Unterstand zum Schutz vor Regen auf einem Picknickplatz.", + "es": "Este es un refugio que protege de la lluvia en un área de picnic.", + "it": "Questo è un riparo che protegge dalla pioggia in un'area picnic.", + "uk": "Це накриття, щ�� захищає від дощу на місці для пікніка." + } + }, + { + "if": "shelter_type=gazebo", + "then": { + "en": "This is a gazebo.", + "ca": "Això és una glorieta.", + "cs": "Toto je altán.", + "de": "Das ist ein offener Gartenpavillon.", + "es": "Este es un cenador.", + "it": "Questo è un gazebo.", + "uk": "Це альтанка." + } + }, + { + "if": "shelter_type=weather_shelter", + "then": { + "en": "This is a small shelter, primarily intended for short breaks. Usually found in the mountains or alongside roads.", + "ca": "Es tracta d'un petit refugi, principalment destinat a descansos curts. Normalment es troba a les muntanyes o al costat de les carreteres.", + "cs": "Jedná se o malý přístřešek, primárně určený pro krátké přestávky. Obvykle se vyskytuje v horách nebo podél silnic.", + "de": "Dies ist ein kleiner Unterstand, der vor allem für kurze Pausen gedacht ist. Normalerweise findet man ihn in Bergen oder an Straßen.", + "es": "Este es un refugio pequeño, principalmente destinado a descansos cortos. Normalmente se encuentra en las montañas o al lado de las carreteras.", + "it": "Questo è un piccolo riparo, principalmente destinato a brevi pause. Si trova solitamente in montagna o lungo le strade.", + "uk": "Це невеликий притулок, призначений насамперед для коротких перерв. Зазвичай знаходиться в горах або вздовж доріг." + } + }, + { + "if": "shelter_type=lean_to", + "then": { + "en": "This is a shed with 3 walls, primarily intended for camping.", + "ca": "Es tracta d'un cobert amb 3 parets, destinat principalment a l'acampada.", + "cs": "Jedná se o přístřešek se 3 stěnami, primárně určený pro kempování.", + "da": "Dette er et skur med 3 vægge, primært beregnet til camping.", + "de": "Es handelt sich um einen an 3 Seiten geschlossenen Unterstand, der in erster Linie zum Campen gedacht ist.", + "es": "Este es un cobertizo con 3 paredes, principalmente destinado para acampar.", + "it": "Questa è una tettoia con 3 pareti, principalmente destinata al campeggio.", + "uk": "Це сарай з 3-ма стінами, в першу чергу призначений для кемпінгу." + } + }, + { + "if": "shelter_type=pavilion", + "then": { + "en": "This is a pavilion", + "ca": "Aquest és un pavelló", + "cs": "Toto je pavilon", + "de": "Das ist ein Pavillon", + "es": "Este es un pabellón", + "it": "Questo è un padiglione", + "uk": "Це павільйон" + } + }, + { + "if": "shelter_type=basic_hut", + "then": "This is a basic hut, providing basic shelter and sleeping facilities." + } + ] } ], "filter":[ From 95898fdfcddbb0608270c5d38952d01520c03212 Mon Sep 17 00:00:00 2001 From: Osmwithspace <> Date: Wed, 27 Aug 2025 09:58:21 +0200 Subject: [PATCH 56/92] add website question --- assets/layers/hut/hut.json | 1 + 1 file changed, 1 insertion(+) diff --git a/assets/layers/hut/hut.json b/assets/layers/hut/hut.json index 681d1d06d..8f641e211 100644 --- a/assets/layers/hut/hut.json +++ b/assets/layers/hut/hut.json @@ -121,6 +121,7 @@ "tagRenderings": [ "images", "name", + "website", "reservation", "caravansites.caravansites-fee", { From 3ad699b077b0b04c80607222c3825a7445e67fe5 Mon Sep 17 00:00:00 2001 From: Osmwithspace <> Date: Wed, 27 Aug 2025 10:33:15 +0200 Subject: [PATCH 57/92] use builtin for contact and shelter type --- assets/layers/hut/hut.json | 131 ++++++------------------------------- 1 file changed, 21 insertions(+), 110 deletions(-) diff --git a/assets/layers/hut/hut.json b/assets/layers/hut/hut.json index 8f641e211..8ebb80836 100644 --- a/assets/layers/hut/hut.json +++ b/assets/layers/hut/hut.json @@ -121,7 +121,19 @@ "tagRenderings": [ "images", "name", - "website", + { + "builtin": "website", + "override": { + "condition": "tourism=wilderness_hut", + "id": "website-single" + } + }, + { + "builtin": "contact", + "override": { + "condition": "tourism=alpine_hut" + } + }, "reservation", "caravansites.caravansites-fee", { @@ -151,116 +163,15 @@ "render": "{preset_type_select()}" }, { - "id": "shelter-type", - "question": { - "en": "What kind of shelter is this?", - "ca": "Quin tipus de refugi és aquest?", - "cs": "Co je to za přístřešek?", - "de": "Um welche Art von Unterstand handelt es sich?", - "es": "¿Qué tipo de refugio es este?", - "it": "Che tipo di riparo è questo?", - "nl": "Wat voor schuilplaats is dit?", - "uk": "Що це за притулок?" - }, - "render": { - "en": "Shelter type: {shelter_type}", - "ca": "Tipus de refugi: {shelter_type}", - "cs": "Typ přístřešku: {shelter_type}", - "de": "Art des Unterstands: {shelter_type}", - "es": "Tipo de refugio: {shelter_type}", - "it": "Tipo di riparo: {shelter_type}", - "uk": "Тип притулку: {shelter_type}" - }, - "freeform": { - "key": "shelter_type", - "type": "string" - }, - "condition": { - "and": [ - "amenity=shelter", - "shelter_type=basic_hut" - ] - }, - "mappings": [ - { - "if": "shelter_type=public_transport", - "then": { - "en": "This is a shelter at a public transport stop.", - "ca": "Es tracta d'un refugi en una parada de transport públic.", - "cs": "Jedná se o přístřešek u zastávky MHD.", - "de": "Das ist ein Unterstand an einer Haltestelle für öffentliche Verkehrsmittel.", - "es": "Este es un refugio en una parada de transporte público.", - "it": "Questo è un riparo presso una fermata del trasporto pubblico.", - "nl": "Dit is een schuilplaats bij een halte voor openbaar vervoer.", - "uk": "Це притулок на зупинці громадського транспорту." - } - }, - { - "if": "shelter_type=picnic_shelter", - "then": { - "en": "This is a shelter protecting from rain at a picnic site.", - "ca": "Es tracta d'un refugi que protegeix de la pluja en un lloc de pícnic.", - "cs": "Jedná se o přístřešek chránící před deštěm na piknikovém místě.", - "de": "Dies ist ein Unterstand zum Schutz vor Regen auf einem Picknickplatz.", - "es": "Este es un refugio que protege de la lluvia en un área de picnic.", - "it": "Questo è un riparo che protegge dalla pioggia in un'area picnic.", - "uk": "Це накриття, щ�� захищає від дощу на місці для пікніка." - } - }, - { - "if": "shelter_type=gazebo", - "then": { - "en": "This is a gazebo.", - "ca": "Això és una glorieta.", - "cs": "Toto je altán.", - "de": "Das ist ein offener Gartenpavillon.", - "es": "Este es un cenador.", - "it": "Questo è un gazebo.", - "uk": "Це альтанка." - } - }, - { - "if": "shelter_type=weather_shelter", - "then": { - "en": "This is a small shelter, primarily intended for short breaks. Usually found in the mountains or alongside roads.", - "ca": "Es tracta d'un petit refugi, principalment destinat a descansos curts. Normalment es troba a les muntanyes o al costat de les carreteres.", - "cs": "Jedná se o malý přístřešek, primárně určený pro krátké přestávky. Obvykle se vyskytuje v horách nebo podél silnic.", - "de": "Dies ist ein kleiner Unterstand, der vor allem für kurze Pausen gedacht ist. Normalerweise findet man ihn in Bergen oder an Straßen.", - "es": "Este es un refugio pequeño, principalmente destinado a descansos cortos. Normalmente se encuentra en las montañas o al lado de las carreteras.", - "it": "Questo è un piccolo riparo, principalmente destinato a brevi pause. Si trova solitamente in montagna o lungo le strade.", - "uk": "Це невеликий притулок, призначений насамперед для коротких перерв. Зазвичай знаходиться в горах або вздовж доріг." - } - }, - { - "if": "shelter_type=lean_to", - "then": { - "en": "This is a shed with 3 walls, primarily intended for camping.", - "ca": "Es tracta d'un cobert amb 3 parets, destinat principalment a l'acampada.", - "cs": "Jedná se o přístřešek se 3 stěnami, primárně určený pro kempování.", - "da": "Dette er et skur med 3 vægge, primært beregnet til camping.", - "de": "Es handelt sich um einen an 3 Seiten geschlossenen Unterstand, der in erster Linie zum Campen gedacht ist.", - "es": "Este es un cobertizo con 3 paredes, principalmente destinado para acampar.", - "it": "Questa è una tettoia con 3 pareti, principalmente destinata al campeggio.", - "uk": "Це сарай з 3-ма стінами, в першу чергу призначений для кемпінгу." - } - }, - { - "if": "shelter_type=pavilion", - "then": { - "en": "This is a pavilion", - "ca": "Aquest és un pavelló", - "cs": "Toto je pavilon", - "de": "Das ist ein Pavillon", - "es": "Este es un pabellón", - "it": "Questo è un padiglione", - "uk": "Це павільйон" - } - }, - { - "if": "shelter_type=basic_hut", - "then": "This is a basic hut, providing basic shelter and sleeping facilities." + "builtin": "shelter.shelter-type", + "override": { + "condition": { + "and": [ + "amenity=shelter", + "shelter_type=basic_hut" + ] } - ] + } } ], "filter":[ From ba4d220b63fe707ad9e91f4b1c4980e2c930d443 Mon Sep 17 00:00:00 2001 From: Martin Bodin Date: Wed, 27 Aug 2025 11:34:50 +0200 Subject: [PATCH 58/92] Including feedbacks from @pietervdvn. --- assets/layers/surveillance_camera/surveillance_camera.json | 2 +- .../surveillance/{panorama.license => panorama.svg.license} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename assets/themes/surveillance/{panorama.license => panorama.svg.license} (100%) diff --git a/assets/layers/surveillance_camera/surveillance_camera.json b/assets/layers/surveillance_camera/surveillance_camera.json index 03008ae9b..1843c3977 100644 --- a/assets/layers/surveillance_camera/surveillance_camera.json +++ b/assets/layers/surveillance_camera/surveillance_camera.json @@ -344,7 +344,7 @@ ] }, { - "id": "Camera type: fixed; panning; dome; panorama", + "id": "Camera type: fixed; panning; dome", "question": { "en": "What kind of camera is this?", "ca": "Quin tipus de càmera és aquesta?", diff --git a/assets/themes/surveillance/panorama.license b/assets/themes/surveillance/panorama.svg.license similarity index 100% rename from assets/themes/surveillance/panorama.license rename to assets/themes/surveillance/panorama.svg.license From e9263eb342e7862c3de2a8f356dd8e29fc6ab179 Mon Sep 17 00:00:00 2001 From: Martin Bodin Date: Wed, 27 Aug 2025 11:36:33 +0200 Subject: [PATCH 59/92] I think that I forgot to add this information in the license file. --- assets/themes/surveillance/license_info.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/assets/themes/surveillance/license_info.json b/assets/themes/surveillance/license_info.json index 53a9f3c25..393433ece 100644 --- a/assets/themes/surveillance/license_info.json +++ b/assets/themes/surveillance/license_info.json @@ -23,6 +23,14 @@ ], "sources": [] }, + { + "path": "panorama.svg", + "license": "CC0-1.0", + "authors": [ + "Martin Bodin" + ], + "sources": [] + }, { "path": "logo.svg", "license": "CC0-1.0", @@ -31,4 +39,4 @@ ], "sources": [] } -] \ No newline at end of file +] From 5689376e1278057dd797e111709112431fe4eb1a Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 28 Aug 2025 01:16:30 +0200 Subject: [PATCH 60/92] Chore: reset translations --- langs/layers/ca.json | 2 +- langs/layers/cs.json | 2 +- langs/layers/da.json | 2 +- langs/layers/de.json | 3 +++ langs/layers/en.json | 34 ++++++++++++++++++++++++++++++++++ langs/layers/es.json | 3 +++ langs/layers/fr.json | 5 ++++- langs/layers/it.json | 2 +- 8 files changed, 48 insertions(+), 5 deletions(-) diff --git a/langs/layers/ca.json b/langs/layers/ca.json index e15f8fa0b..4d1c9f080 100644 --- a/langs/layers/ca.json +++ b/langs/layers/ca.json @@ -11556,7 +11556,7 @@ "2": { "then": "Una càmera panoràmica" }, - "3": { + "4": { "then": "Un timbre que es pot activar remotament en qualsevol moment o mitjançant la detecció de moviment. Aquests són típicament Smart, banderes connectades a Internet. Les marques típiques són Ring, Google Nest, Eufy, ..." } }, diff --git a/langs/layers/cs.json b/langs/layers/cs.json index 36a11b112..7f756f766 100644 --- a/langs/layers/cs.json +++ b/langs/layers/cs.json @@ -12500,7 +12500,7 @@ "2": { "then": "Otáčecí kamera" }, - "3": { + "4": { "then": "Domovní zvonek, který lze spouštět kdykoli vzdáleně nebo detekcí pohybu. Jsou to typicky chytré zvonky připojené k Internetu. Typické značky jsou Ring, Google Nest, Eufy…" } }, diff --git a/langs/layers/da.json b/langs/layers/da.json index d1a4d9c39..1133fb708 100644 --- a/langs/layers/da.json +++ b/langs/layers/da.json @@ -3152,7 +3152,7 @@ "2": { "then": "Et kamera, der panorerer" }, - "3": { + "4": { "then": "En dørklokke, som kan tændes på afstand når som helst eller ved bevægelsesregistrering. Disse er typisk intelligente internetforbundne dørklokker. Typiske mærker er Ring, Google Nest, Eufy, …" } } diff --git a/langs/layers/de.json b/langs/layers/de.json index 58e89547a..0dc01e01f 100644 --- a/langs/layers/de.json +++ b/langs/layers/de.json @@ -11546,6 +11546,9 @@ "then": "Eine bewegliche Kamera" }, "3": { + "then": "Eine 360°-Kamera" + }, + "4": { "then": "Eine Türklingel, die jederzeit oder per Bewegungserkennung ferngeschaltet werden kann. Dies sind typischerweise Smart, internetgebundene Türklingeln. Typische Marken sind Ring, Google Nest, Eufy, ..." } }, diff --git a/langs/layers/en.json b/langs/layers/en.json index 76f94fdc5..f617e8404 100644 --- a/langs/layers/en.json +++ b/langs/layers/en.json @@ -7016,6 +7016,37 @@ "render": "Hospital" } }, + "hut": { + "description": "Layer showing basic huts, wilderness huts and alpine huts", + "name": "Huts", + "presets": { + "0": { + "description": "An unserviced fully enclosed hut (with roof and walls) with beds or suitable sleeping areas and a fireplace or stove for heating and cooking.", + "title": "wilderness hut" + }, + "1": { + "description": "A serviced remote building located in the mountains intended to provide board and lodging.", + "title": "alpine hut" + }, + "2": { + "description": "An unserviced fully enclosed hut (with roof and walls) with beds or suitable sleeping areas without a fireplace or stove.", + "title": "basic hut" + } + }, + "tagRenderings": { + "drinking_water": { + "mappings": { + "0": { + "then": "Here is drinking water available." + }, + "1": { + "then": "Here is no drinking water available." + } + }, + "question": "Is drinking water available here?" + } + } + }, "hydrant": { "description": "Map layer to show fire hydrants.", "name": "Hydrants", @@ -12543,6 +12574,9 @@ "then": "A panning camera" }, "3": { + "then": "A 360° camera" + }, + "4": { "then": "A doorbell which might be turned on remotely at any time or by motion detection. These are typically Smart, internet-connected doorbells. Typical brands are Ring, Google Nest, Eufy, …" } }, diff --git a/langs/layers/es.json b/langs/layers/es.json index 0baced4d3..08e758918 100644 --- a/langs/layers/es.json +++ b/langs/layers/es.json @@ -10582,6 +10582,9 @@ }, "2": { "then": "Una cámara panorámica" + }, + "3": { + "then": "Una cámara de 360°" } }, "question": "¿Qué tipo de cámara es esta?" diff --git a/langs/layers/fr.json b/langs/layers/fr.json index dad2406a9..c6fbae3b2 100644 --- a/langs/layers/fr.json +++ b/langs/layers/fr.json @@ -6519,6 +6519,9 @@ }, "2": { "then": "Une caméra panoramique" + }, + "3": { + "then": "Une caméra 360°" } }, "question": "Quel genre de caméra est-ce ?" @@ -6543,7 +6546,7 @@ "then": "Une zone intérieure privée est surveillée, par exemple un magasin, un parking souterrain privé…" } }, - "question": "De quel genre de surveillance cette caméra est-elle ?" + "question": "De quel genre de surveillance cette caméra est-elle ?" }, "Surveillance:zone": { "mappings": { diff --git a/langs/layers/it.json b/langs/layers/it.json index 65a7fd8d5..5fefddef9 100644 --- a/langs/layers/it.json +++ b/langs/layers/it.json @@ -12109,7 +12109,7 @@ "2": { "then": "Una telecamera panoramica" }, - "3": { + "4": { "then": "Un campanello che potrebbe essere acceso da remoto in qualsiasi momento o tramite rilevamento del movimento. Questi sono tipicamente campanelli Smart, connessi a Internet. Marchi tipici sono Ring, Google Nest, Eufy, ..." } }, From db62329d396e067dcb2155a7deed2e466065e383 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 28 Aug 2025 03:12:10 +0200 Subject: [PATCH 61/92] Themes(surveillance): add some tweaks to the 'panorama'-camera --- .../surveillance_camera.json | 11 ++- .../mapcomplete-changes.json | 3 + assets/themes/surveillance/license_info.json | 18 ++--- assets/themes/surveillance/panorama.svg | 68 ++++++++++--------- .../themes/surveillance/panorama.svg.license | 2 +- 5 files changed, 57 insertions(+), 45 deletions(-) diff --git a/assets/layers/surveillance_camera/surveillance_camera.json b/assets/layers/surveillance_camera/surveillance_camera.json index 495b297ea..e19ab6d52 100644 --- a/assets/layers/surveillance_camera/surveillance_camera.json +++ b/assets/layers/surveillance_camera/surveillance_camera.json @@ -114,6 +114,10 @@ ] }, "then": "50,35,center" + }, + { + "if": "camera:type=panorama", + "then": "55,55,center" } ], "render": "35,35,center" @@ -396,7 +400,7 @@ }, "icon": "./assets/themes/surveillance/dome.svg" }, - { + { "if": "camera:type=panning", "then": { "en": "A panning camera", @@ -413,10 +417,11 @@ }, { "if": "camera:type=panorama", + "icon": "./assets/themes/surveillance/panorama.svg", "then": { "en": "A 360° camera", - "de": "Eine 360°-Kamera", - "es": "Una cámara de 360°", + "de": "Eine 360°-Kamera", + "es": "Una cámara de 360°", "fr": "Une caméra 360°" } }, 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/assets/themes/surveillance/license_info.json b/assets/themes/surveillance/license_info.json index 393433ece..4b40d545c 100644 --- a/assets/themes/surveillance/license_info.json +++ b/assets/themes/surveillance/license_info.json @@ -23,14 +23,6 @@ ], "sources": [] }, - { - "path": "panorama.svg", - "license": "CC0-1.0", - "authors": [ - "Martin Bodin" - ], - "sources": [] - }, { "path": "logo.svg", "license": "CC0-1.0", @@ -38,5 +30,13 @@ "Pieter Vander Vennet" ], "sources": [] + }, + { + "path": "panorama.svg", + "license": "CC0-1.0", + "authors": [ + "Martin Bodin" + ], + "sources": [] } -] +] \ No newline at end of file diff --git a/assets/themes/surveillance/panorama.svg b/assets/themes/surveillance/panorama.svg index 0a21207b0..6e135df24 100644 --- a/assets/themes/surveillance/panorama.svg +++ b/assets/themes/surveillance/panorama.svg @@ -1,19 +1,19 @@ + inkscape:version="1.3.2 (1:1.3.2+202311252150+091e20ef0f)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> @@ -28,7 +28,7 @@ + inkscape:current-layer="surface1" + inkscape:showpageshadow="2" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" /> + style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:4.36475;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1" + d="M 11.439583,18.537673 H 87.78125 c 12.54792,0.03646 12.93854,8.290452 0,8.132119 H 11.439583 c -11.3604163,0.03333 -11.219791,-8.256077 0,-8.132119 z" + id="path2" + transform="scale(3.75)" + sodipodi:nodetypes="ccccc" /> + transform="matrix(0.93922526,0,0,0.93922526,79.006064,2.8192093)"> + style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.536273" /> + style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.536273" /> + transform="matrix(0.69882528,0,0,0.93922526,220.19065,2.8192093)"> + style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.536273" /> + style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.536273" /> + transform="matrix(0.69882528,0,0,0.93922526,-6.6284342,2.8192093)"> + style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.536273" /> + style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.536273" /> diff --git a/assets/themes/surveillance/panorama.svg.license b/assets/themes/surveillance/panorama.svg.license index f725fff05..27a495574 100644 --- a/assets/themes/surveillance/panorama.svg.license +++ b/assets/themes/surveillance/panorama.svg.license @@ -1,2 +1,2 @@ SPDX-FileCopyrightText: Martin Bodin -SPDX-License-Identifier: CC0-1.0 +SPDX-License-Identifier: CC0-1.0 \ No newline at end of file From 0f2e8486cd723c68701a35f63437dcc804be4ea7 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Wed, 27 Aug 2025 02:23:06 +0200 Subject: [PATCH 62/92] UI: change tablist to reflect fixmycity-design, see #2107 --- public/css/index-tailwind-output.css | 40 ++++++++++++++++++++++++ src/UI/Base/TabbedGroup.svelte | 35 ++++++--------------- src/UI/Wikipedia/WikipediaPanel.svelte | 13 ++------ src/index.css | 42 ++++++++++++++++++++++++++ 4 files changed, 93 insertions(+), 37 deletions(-) diff --git a/public/css/index-tailwind-output.css b/public/css/index-tailwind-output.css index 34744bb80..eff0449cf 100644 --- a/public/css/index-tailwind-output.css +++ b/public/css/index-tailwind-output.css @@ -5444,6 +5444,46 @@ button.unstyled, .button-unstyled button { padding: 0; } +/****** Tablist elements *****/ + +.tablist { + margin: 0.25rem; + padding: 0.5rem; + border: 2px dashed var(--button-background-hover); + border-radius: 0.5rem; + display: flex; + justify-content: center; +} + +.tab { + border: unset; + border-radius: 0; + transition: all; + color: var(--foreground-color); + border-bottom: 2px solid var(--foreground-color); + font-weight: bold; + margin: 0.25rem; + padding: 0.25rem; + padding-left: 0.75rem; + padding-right: 0.75rem; +} + +.tab-selected { + opacity: 100%; + background: var(--interactive-background); +} + +/* Actually used, don't remove*/ + +.tab-unselected { + background: #00000000 !important; + opacity: 60%; +} + +.tab-unselected:hover { + background: var(--interactive-background); +} + /******* Other input elements ******/ .hover-alert:hover { diff --git a/src/UI/Base/TabbedGroup.svelte b/src/UI/Base/TabbedGroup.svelte index 586f7daf8..8c692740a 100644 --- a/src/UI/Base/TabbedGroup.svelte +++ b/src/UI/Base/TabbedGroup.svelte @@ -43,11 +43,11 @@ } }} > -
- +
+ {#if $$slots.title0} twJoin("tab", selected && "primary", !$condition0 && "hidden")} + class={({ selected }) => twJoin("tab", selected ? "tab-selected": "tab-unselected", !$condition0 && "hidden")} >
Tab 0 @@ -56,7 +56,7 @@ {/if} {#if $$slots.title1} twJoin("tab", selected && "primary", !$condition1 && "hidden")} + class={({ selected }) => twJoin("tab", selected ? "tab-selected": "tab-unselected", !$condition1 && "hidden")} >
@@ -65,7 +65,7 @@ {/if} {#if $$slots.title2} twJoin("tab", selected && "primary", !$condition2 && "hidden")} + class={({ selected }) => twJoin("tab", selected ? "tab-selected": "tab-unselected", !$condition2 && "hidden")} >
@@ -74,7 +74,7 @@ {/if} {#if $$slots.title3} twJoin("tab", selected && "primary", !$condition3 && "hidden")} + class={({ selected }) => twJoin("tab", selected ? "tab-selected": "tab-unselected", !$condition3 && "hidden")} >
@@ -83,7 +83,7 @@ {/if} {#if $$slots.title4} twJoin("tab", selected && "primary", !$condition4 && "hidden")} + class={({ selected }) => twJoin("tab", selected ? "tab-selected": "tab-unselected", !$condition4 && "hidden")} >
@@ -92,7 +92,7 @@ {/if} {#if $$slots.title5} twJoin("tab", selected && "primary", !$condition5 && "hidden")} + class={({ selected }) => twJoin("tab", selected ? "tab-selected": "tab-unselected", !$condition5 && "hidden")} >
@@ -101,7 +101,7 @@ {/if} {#if $$slots.title6} twJoin("tab", selected && "primary", !$condition6 && "hidden")} + class={({ selected }) => twJoin("tab", selected ? "tab-selected": "tab-unselected", !$condition6 && "hidden")} >
@@ -167,19 +167,6 @@ height: calc(100% - 2rem); } - :global(.tab) { - margin: 0.25rem; - padding: 0.25rem; - padding-left: 0.75rem; - padding-right: 0.75rem; - border-radius: 1rem; - } - - :global(.tab .flex) { - align-items: center; - gap: 0.25rem; - } - :global(.tab span|div) { align-items: center; gap: 0.25rem; @@ -190,8 +177,4 @@ fill: var(--interactive-contrast); } - :global(.tab-unselected) { - background-color: var(--background-color) !important; - color: var(--foreground-color) !important; - } diff --git a/src/UI/Wikipedia/WikipediaPanel.svelte b/src/UI/Wikipedia/WikipediaPanel.svelte index ac9e710d5..43db3d34a 100644 --- a/src/UI/Wikipedia/WikipediaPanel.svelte +++ b/src/UI/Wikipedia/WikipediaPanel.svelte @@ -31,9 +31,9 @@ {:else} - + {#each _wikipediaStores as store (store.tag)} - (selected ? "tab-selected" : "tab-unselected")}> + ("tab "+ (selected ? "tab-selected" : "tab-unselected"))}> {/each} @@ -51,14 +51,5 @@ diff --git a/src/index.css b/src/index.css index d1dc9a284..0fd70652d 100644 --- a/src/index.css +++ b/src/index.css @@ -293,6 +293,48 @@ button.unstyled, .button-unstyled button { padding: 0; } +/****** Tablist elements *****/ + +.tablist { + margin: 0.25rem; + padding: 0.5rem; + border: 2px dashed var(--button-background-hover); + border-radius: 0.5rem; + display: flex; + justify-content: center; + +} + +.tab { + border: unset; + border-radius: 0; + transition: all; + color: var(--foreground-color); + border-bottom: 2px solid var(--foreground-color); + font-weight: bold; + + margin: 0.25rem; + padding: 0.25rem; + padding-left: 0.75rem; + padding-right: 0.75rem; + +} + +.tab-selected { + opacity: 100%; + background: var(--interactive-background); +} + +/* Actually used, don't remove*/ +.tab-unselected { + background: #00000000 !important; + opacity: 60%; +} + +.tab-unselected:hover { + background: var(--interactive-background); +} + /******* Other input elements ******/ .hover-alert:hover { From 0a48765f0f443939af01f21912598b65422dfb0a Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Wed, 27 Aug 2025 02:32:23 +0200 Subject: [PATCH 63/92] UI(menu): add link to app download page in the menu drawer --- langs/en.json | 1 + src/UI/BigComponents/MenuDrawerIndex.svelte | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/langs/en.json b/langs/en.json index 19daa1127..897eb4bf6 100644 --- a/langs/en.json +++ b/langs/en.json @@ -319,6 +319,7 @@ "menu": { "aboutCurrentThemeTitle": "About this map", "aboutMapComplete": "About MapComplete", + "downloadApp": "Download the app for Android", "filter": "Filter data", "legal": "Legal notices", "moreUtilsTitle": "Discover more", diff --git a/src/UI/BigComponents/MenuDrawerIndex.svelte b/src/UI/BigComponents/MenuDrawerIndex.svelte index cac998937..84f136b9c 100644 --- a/src/UI/BigComponents/MenuDrawerIndex.svelte +++ b/src/UI/BigComponents/MenuDrawerIndex.svelte @@ -59,6 +59,9 @@ import OfflineManagement from "./OfflineManagement.svelte" import { GlobeEuropeAfrica } from "@babeard/svelte-heroicons/solid/GlobeEuropeAfrica" import { onDestroy } from "svelte" + import { DevicePhoneMobileIcon } from "@babeard/svelte-heroicons/solid" + import If from "../Base/If.svelte" + import IfNot from "../Base/IfNot.svelte" export let state: { favourites: FavouritesFeatureSource @@ -229,6 +232,13 @@ + + + + + + + From 8ae09e25de57b514c51a3fa3e42aa3f1e1bf85f7 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Wed, 27 Aug 2025 02:51:33 +0200 Subject: [PATCH 64/92] UI(Inspector): update tabgroups, fix import --- src/UI/History/AggregateView.svelte | 2 +- src/UI/InspectorGUI.svelte | 166 +++++++++++++++------------- 2 files changed, 89 insertions(+), 79 deletions(-) diff --git a/src/UI/History/AggregateView.svelte b/src/UI/History/AggregateView.svelte index 35ce509cd..130e7c2dd 100644 --- a/src/UI/History/AggregateView.svelte +++ b/src/UI/History/AggregateView.svelte @@ -12,9 +12,9 @@ import Translations from "../i18n/Translations" import type { TagRenderingConfigJson } from "../../Models/ThemeConfig/Json/TagRenderingConfigJson" import { Or } from "../../Logic/Tags/Or" - import { Utils } from "../../Utils" import ChartJs from "../Base/ChartJs.svelte" import { ChartJsUtils } from "../Base/ChartJsUtils" + import { Lists } from "../../Utils/Lists" export let onlyShowUsername: string[] export let features: Feature[] diff --git a/src/UI/InspectorGUI.svelte b/src/UI/InspectorGUI.svelte index 5afae05be..32f0c8f5d 100644 --- a/src/UI/InspectorGUI.svelte +++ b/src/UI/InspectorGUI.svelte @@ -33,6 +33,9 @@ import GeocodeResults from "./Search/GeocodeResults.svelte" import MagnifyingGlassCircle from "@babeard/svelte-heroicons/mini/MagnifyingGlassCircle" import type { GeocodeResult } from "../Logic/Search/GeocodingProvider" + import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@rgossiaux/svelte-headlessui" + import WikipediaTitle from "./Wikipedia/WikipediaTitle.svelte" + import WikipediaArticle from "./Wikipedia/WikipediaArticle.svelte" console.log("Loading inspector GUI") let username = QueryParameters.GetQueryParameter("user", undefined, "Inspect this user") @@ -49,7 +52,7 @@ new CoordinateSearch(), new OpenLocationCodeSearch(), new PhotonSearch(true, 2), - new PhotonSearch() + new PhotonSearch(), ) let showSearchDrawer = new UIEventSource(true) let searchIsFocussed = new UIEventSource(false) @@ -138,7 +141,7 @@ const overpass = new Overpass( Constants.defaultOverpassUrls[0], undefined, - user.split(";").map((user) => 'nw(user_touched:"' + user + '");') + user.split(";").map((user) => "nw(user_touched:\"" + user + "\");"), ) if (!maplibremap.bounds.data) { return @@ -161,8 +164,6 @@ return true }) - let mode: "map" | "table" | "aggregate" | "images" = "map" - let showPreviouslyVisited = new UIEventSource(true) const t = Translations.t.inspector @@ -207,89 +208,98 @@
-
- - - - -
+ + + ("tab "+ (selected ? "tab-selected" : "tab-unselected"))}> - {#if mode === "map"} - {#if $selectedElement !== undefined} - - + + ("tab "+ (selected ? "tab-selected" : "tab-unselected"))}> + + + + ("tab "+ (selected ? "tab-selected" : "tab-unselected"))}> + + + + ("tab "+ (selected ? "tab-selected" : "tab-unselected"))}> + + + + + + + + + {#if $selectedElement !== undefined} + + - {/if} - -
- -
- search()} - isFocused={searchIsFocussed} - value={searchvalue} - on:focus={() => state.searchState.showSearchDrawer.set(true)} - /> - {#if $searchSuggestions?.length > 0 || $searchIsFocussed} - search(event.detail)} /> + + + {/if} -
-
- {:else if mode === "table"} -
- {#each $featuresStore as f} - - {/each} -
- {:else if mode === "aggregate"} -
- -
- {:else if mode === "images"} -
- -
- {/if} + +
+ +
+ search()} + isFocused={searchIsFocussed} + value={searchvalue} + on:focus={() => state.searchState.showSearchDrawer.set(true)} + /> + {#if $searchSuggestions?.length > 0 || $searchIsFocussed} + search(event.detail)} /> + {/if} +
+
+ +
+ + + {#each $featuresStore as f} + + {/each} + + + + + + + + +
+
From 247507802c7c5229ba5541eee19b30908ae76976 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 28 Aug 2025 22:41:13 +0200 Subject: [PATCH 65/92] UI(Inspector): tweak UI --- public/css/index-tailwind-output.css | 1 + src/UI/InspectorGUI.svelte | 2 +- src/index.css | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/public/css/index-tailwind-output.css b/public/css/index-tailwind-output.css index eff0449cf..63aa24107 100644 --- a/public/css/index-tailwind-output.css +++ b/public/css/index-tailwind-output.css @@ -5453,6 +5453,7 @@ button.unstyled, .button-unstyled button { border-radius: 0.5rem; display: flex; justify-content: center; + flex-wrap: wrap; } .tab { diff --git a/src/UI/InspectorGUI.svelte b/src/UI/InspectorGUI.svelte index 32f0c8f5d..06c93d9bc 100644 --- a/src/UI/InspectorGUI.svelte +++ b/src/UI/InspectorGUI.svelte @@ -182,8 +182,8 @@
-

+

Date: Thu, 28 Aug 2025 22:41:31 +0200 Subject: [PATCH 66/92] Feature: add 'download app' item into the menu drawer (if not on Android) --- src/UI/BigComponents/MenuDrawerIndex.svelte | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/UI/BigComponents/MenuDrawerIndex.svelte b/src/UI/BigComponents/MenuDrawerIndex.svelte index 84f136b9c..463edc0fb 100644 --- a/src/UI/BigComponents/MenuDrawerIndex.svelte +++ b/src/UI/BigComponents/MenuDrawerIndex.svelte @@ -60,8 +60,6 @@ import { GlobeEuropeAfrica } from "@babeard/svelte-heroicons/solid/GlobeEuropeAfrica" import { onDestroy } from "svelte" import { DevicePhoneMobileIcon } from "@babeard/svelte-heroicons/solid" - import If from "../Base/If.svelte" - import IfNot from "../Base/IfNot.svelte" export let state: { favourites: FavouritesFeatureSource @@ -232,13 +230,12 @@ - - - - - - - + {#if !$isAndroid} + + + + + {/if} From 6cfc40009df3480a31b82a534b608e63fa472e47 Mon Sep 17 00:00:00 2001 From: Osmwithspace <> Date: Wed, 27 Aug 2025 15:52:33 +0200 Subject: [PATCH 67/92] remove duplication --- assets/layers/campsite/campsite.json | 1 - 1 file changed, 1 deletion(-) diff --git a/assets/layers/campsite/campsite.json b/assets/layers/campsite/campsite.json index 5e9024b87..d29e848c4 100644 --- a/assets/layers/campsite/campsite.json +++ b/assets/layers/campsite/campsite.json @@ -280,7 +280,6 @@ ] } }, - "caravansites.caravansites-toilets", "toilet_at_amenity_lib.all", "questions", "mastodon" From 3ecfbcc3071c3e4831bd7bf219a4f01892064ee8 Mon Sep 17 00:00:00 2001 From: Osmwithspace <> Date: Wed, 27 Aug 2025 15:52:55 +0200 Subject: [PATCH 68/92] use builtin question --- assets/layers/caravansites/caravansites.json | 80 +------------------- 1 file changed, 1 insertion(+), 79 deletions(-) diff --git a/assets/layers/caravansites/caravansites.json b/assets/layers/caravansites/caravansites.json index 373de01a3..4240ceb99 100644 --- a/assets/layers/caravansites/caravansites.json +++ b/assets/layers/caravansites/caravansites.json @@ -666,85 +666,7 @@ ] } }, - { - "id": "caravansites-toilets", - "question": { - "en": "Does this place have toilets?", - "ca": "Aquest lloc té lavabos?", - "cs": "Má toto místo toalety?", - "da": "Har dette sted toiletter?", - "de": "Verfügt dieser Ort über Toiletten?", - "es": "¿Este lugar tiene baños?", - "fr": "Y-a-t’il des toilettes sur le site ?", - "hu": "Van-e itt WC?", - "it": "Questo posto ha servizi igienici?", - "ja": "ここにトイレはありますか?", - "nb_NO": "Har dette stedet toaletter?", - "nl": "Heeft deze plaats toiletten?", - "pl": "Czy to miejsce ma toalety?", - "pt": "Este lugar tem casas de banho?", - "pt_BR": "Este lugar tem banheiros?", - "ru": "Здесь есть туалеты?", - "zh_Hant": "這個地方有廁所嗎?" - }, - "mappings": [ - { - "if": { - "and": [ - "toilets=yes" - ] - }, - "then": { - "en": "This place has toilets", - "ca": "Aquest lloc té lavabos", - "cs": "Toto místo má toalety", - "da": "Dette sted har toiletter", - "de": "Dieser Ort verfügt über Toiletten", - "es": "Este lugar tiene baños", - "fr": "Ce site a des toilettes", - "hu": "Itt van WC", - "id": "Tempat sini ada tandas", - "it": "Questo posto ha servizi igienici", - "ja": "ここにはトイレがある", - "nb_NO": "Dette stedet har toalettfasiliteter", - "nl": "Deze plaats heeft toiletten", - "pl": "To miejsce ma toalety", - "pt": "Este lugar tem casa de banho", - "pt_BR": "Este lugar tem banheiros", - "ru": "В этом месте есть туалеты", - "zh_Hant": "這個地方有廁所" - } - }, - { - "if": { - "and": [ - "toilets=no" - ] - }, - "then": { - "en": "This place does not have toilets", - "ca": "Aquest lloc no té lavabos", - "cs": "Toto místo nemá toalety", - "da": "Dette sted har ikke toiletter", - "de": "Dieser Ort verfügt nicht über Toiletten", - "es": "Este lugar no tiene baños", - "eu": "Toki honek ez dauka komunik", - "fr": "Ce site n’a pas de toilettes", - "hu": "Itt nincs WC", - "id": "Tempat sini tiada tandas", - "it": "Questo posto non ha servizi igienici", - "ja": "ここにはトイレがない", - "nb_NO": "Dette stedet har ikke toalettfasiliteter", - "nl": "Deze plaats heeft geen toiletten", - "pl": "To miejsce nie ma toalet", - "pt": "Este lugar não tem casas de banho", - "pt_BR": "Este lugar não tem banheiros", - "ru": "В этом месте нет туалетов", - "zh_Hant": "這個地方並沒有廁所" - } - } - ] - }, + "has_toilets", { "id": "caravansites-website", "question": { From 7f206da86cd3daed65ada504d978e57c1f640a4a Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 28 Aug 2025 22:48:22 +0200 Subject: [PATCH 69/92] Chore: reset translations --- langs/layers/ca.json | 11 ----------- langs/layers/cs.json | 11 ----------- langs/layers/da.json | 11 ----------- langs/layers/de.json | 11 ----------- langs/layers/en.json | 11 ----------- langs/layers/es.json | 11 ----------- langs/layers/eu.json | 7 ------- langs/layers/fr.json | 11 ----------- langs/layers/hu.json | 13 ------------- langs/layers/id.json | 10 ---------- langs/layers/it.json | 11 ----------- langs/layers/ja.json | 11 ----------- langs/layers/nb_NO.json | 11 ----------- langs/layers/nl.json | 11 ----------- langs/layers/pl.json | 11 ----------- langs/layers/pt.json | 11 ----------- langs/layers/pt_BR.json | 11 ----------- langs/layers/ru.json | 11 ----------- langs/layers/zh_Hant.json | 11 ----------- 19 files changed, 206 deletions(-) diff --git a/langs/layers/ca.json b/langs/layers/ca.json index 20e392c32..a68397a07 100644 --- a/langs/layers/ca.json +++ b/langs/layers/ca.json @@ -2468,17 +2468,6 @@ }, "question": "Aquest lloc té una estació d'abocament sanitari?" }, - "caravansites-toilets": { - "mappings": { - "0": { - "then": "Aquest lloc té lavabos" - }, - "1": { - "then": "Aquest lloc no té lavabos" - } - }, - "question": "Aquest lloc té lavabos?" - }, "caravansites-website": { "question": "Aquest lloc té un lloc web?", "render": "Lloc web oficial: {website}" diff --git a/langs/layers/cs.json b/langs/layers/cs.json index 26b07d97c..348bf81cb 100644 --- a/langs/layers/cs.json +++ b/langs/layers/cs.json @@ -2663,17 +2663,6 @@ }, "question": "Má toto místo sanitární skládku?" }, - "caravansites-toilets": { - "mappings": { - "0": { - "then": "Toto místo má toalety" - }, - "1": { - "then": "Toto místo nemá toalety" - } - }, - "question": "Má toto místo toalety?" - }, "caravansites-website": { "question": "Má toto místo webové stránky?", "render": "Oficiální webové stránky: {website}" diff --git a/langs/layers/da.json b/langs/layers/da.json index 1133fb708..19ad241fb 100644 --- a/langs/layers/da.json +++ b/langs/layers/da.json @@ -1561,17 +1561,6 @@ }, "question": "Har dette sted en sanitær tømningsstation?" }, - "caravansites-toilets": { - "mappings": { - "0": { - "then": "Dette sted har toiletter" - }, - "1": { - "then": "Dette sted har ikke toiletter" - } - }, - "question": "Har dette sted toiletter?" - }, "caravansites-website": { "question": "Har dette sted et websted?", "render": "Officiel hjemmeside: {website}" diff --git a/langs/layers/de.json b/langs/layers/de.json index 93e921a23..da3f91f1d 100644 --- a/langs/layers/de.json +++ b/langs/layers/de.json @@ -2439,17 +2439,6 @@ }, "question": "Hat dieser Ort eine sanitäre Entsorgungsstation?" }, - "caravansites-toilets": { - "mappings": { - "0": { - "then": "Dieser Ort verfügt über Toiletten" - }, - "1": { - "then": "Dieser Ort verfügt nicht über Toiletten" - } - }, - "question": "Verfügt dieser Ort über Toiletten?" - }, "caravansites-website": { "question": "Hat dieser Ort eine Webseite?", "render": "Offizielle Webseite: {website}" diff --git a/langs/layers/en.json b/langs/layers/en.json index 01939caf2..8fa7d11c6 100644 --- a/langs/layers/en.json +++ b/langs/layers/en.json @@ -2697,17 +2697,6 @@ }, "question": "Does this place have a sanitary dump station?" }, - "caravansites-toilets": { - "mappings": { - "0": { - "then": "This place has toilets" - }, - "1": { - "then": "This place does not have toilets" - } - }, - "question": "Does this place have toilets?" - }, "caravansites-website": { "question": "Does this place have a website?", "render": "Official website: {website}" diff --git a/langs/layers/es.json b/langs/layers/es.json index 5ff7d5e91..076643f52 100644 --- a/langs/layers/es.json +++ b/langs/layers/es.json @@ -2270,17 +2270,6 @@ }, "question": "¿Este lugar tiene un punto de vaciado de aguas grises?" }, - "caravansites-toilets": { - "mappings": { - "0": { - "then": "Este lugar tiene baños" - }, - "1": { - "then": "Este lugar no tiene baños" - } - }, - "question": "¿Este lugar tiene baños?" - }, "caravansites-website": { "question": "¿Este lugar tiene una página web?", "render": "Página web oficial: {website}" diff --git a/langs/layers/eu.json b/langs/layers/eu.json index 30ad60b23..8473c20b9 100644 --- a/langs/layers/eu.json +++ b/langs/layers/eu.json @@ -289,13 +289,6 @@ } } }, - "caravansites-toilets": { - "mappings": { - "1": { - "then": "Toki honek ez dauka komunik" - } - } - }, "caravansites-website": { "question": "Toki honek webgunerik ba al du?" } diff --git a/langs/layers/fr.json b/langs/layers/fr.json index bd576b835..cceb1374f 100644 --- a/langs/layers/fr.json +++ b/langs/layers/fr.json @@ -1873,17 +1873,6 @@ }, "question": "Ce site possède-t’il un lieu de vidange ?" }, - "caravansites-toilets": { - "mappings": { - "0": { - "then": "Ce site a des toilettes" - }, - "1": { - "then": "Ce site n’a pas de toilettes" - } - }, - "question": "Y-a-t’il des toilettes sur le site ?" - }, "caravansites-website": { "question": "Ce lieu a-t’il un site internet ?", "render": "Site officiel : {website}" diff --git a/langs/layers/hu.json b/langs/layers/hu.json index d1e1287f3..c95ddb68a 100644 --- a/langs/layers/hu.json +++ b/langs/layers/hu.json @@ -466,19 +466,6 @@ "description": "Új hivatalos lakóautóhely hozzáadása. Ez arra vannak kijelölve, hogy lakóautóval ott éjszakázzunk. Lehet, hogy úgy néz ki, mint egy igazi kemping, de az is lehet, hogy csak olyan, mint egy parkoló. Előfordulhat, hogy egyáltalán nem jelzik őket, hanem csak egy önkormányzati határozatban vannak kijelölve. A lakóautósoknak szánt olyan hagyományos parkolók, ahol nem várhatóan nem fognak éjszakázni, -nem minősül- lakóautóhelynek. ", "title": "lakóautós megállóhely" } - }, - "tagRenderings": { - "caravansites-toilets": { - "mappings": { - "0": { - "then": "Itt van WC" - }, - "1": { - "then": "Itt nincs WC" - } - }, - "question": "Van-e itt WC?" - } } }, "charging_station": { diff --git a/langs/layers/id.json b/langs/layers/id.json index cc5cd707c..beb701796 100644 --- a/langs/layers/id.json +++ b/langs/layers/id.json @@ -196,16 +196,6 @@ }, "question": "Apakah tempat ini memiliki tempat pembuangan sanitasi?" }, - "caravansites-toilets": { - "mappings": { - "0": { - "then": "Tempat sini ada tandas" - }, - "1": { - "then": "Tempat sini tiada tandas" - } - } - }, "caravansites-website": { "question": "Tempat sini terada situs web?", "render": "Situs resmi: {website}" diff --git a/langs/layers/it.json b/langs/layers/it.json index 25accfbc2..990c15157 100644 --- a/langs/layers/it.json +++ b/langs/layers/it.json @@ -2632,17 +2632,6 @@ }, "question": "Questo posto ha una stazione di scarico sanitario?" }, - "caravansites-toilets": { - "mappings": { - "0": { - "then": "Questo posto ha servizi igienici" - }, - "1": { - "then": "Questo posto non ha servizi igienici" - } - }, - "question": "Questo posto ha servizi igienici?" - }, "caravansites-website": { "question": "Questo posto ha un sito web?", "render": "Sito web ufficiale: {website}" diff --git a/langs/layers/ja.json b/langs/layers/ja.json index b9808a0b2..7ee7a672c 100644 --- a/langs/layers/ja.json +++ b/langs/layers/ja.json @@ -217,17 +217,6 @@ }, "question": "この場所に衛生的なゴミ捨て場はありますか?" }, - "caravansites-toilets": { - "mappings": { - "0": { - "then": "ここにはトイレがある" - }, - "1": { - "then": "ここにはトイレがない" - } - }, - "question": "ここにトイレはありますか?" - }, "caravansites-website": { "question": "ここにはウェブサイトがありますか?", "render": "公式Webサイト: {website}" diff --git a/langs/layers/nb_NO.json b/langs/layers/nb_NO.json index 2879f7599..e85806b21 100644 --- a/langs/layers/nb_NO.json +++ b/langs/layers/nb_NO.json @@ -443,17 +443,6 @@ "question": "Hva heter dette stedet?", "render": "Dette stedet heter {name}" }, - "caravansites-toilets": { - "mappings": { - "0": { - "then": "Dette stedet har toalettfasiliteter" - }, - "1": { - "then": "Dette stedet har ikke toalettfasiliteter" - } - }, - "question": "Har dette stedet toaletter?" - }, "caravansites-website": { "question": "Har dette stedet en nettside?", "render": "Offisiell nettside: {website}" diff --git a/langs/layers/nl.json b/langs/layers/nl.json index db5c3e361..865edb6e9 100644 --- a/langs/layers/nl.json +++ b/langs/layers/nl.json @@ -2546,17 +2546,6 @@ }, "question": "Heeft deze plaats een loosplaats?" }, - "caravansites-toilets": { - "mappings": { - "0": { - "then": "Deze plaats heeft toiletten" - }, - "1": { - "then": "Deze plaats heeft geen toiletten" - } - }, - "question": "Heeft deze plaats toiletten?" - }, "caravansites-website": { "question": "Heeft deze plaats een website?", "render": "Officiële website: : {website}" diff --git a/langs/layers/pl.json b/langs/layers/pl.json index decd96ce4..187e36d08 100644 --- a/langs/layers/pl.json +++ b/langs/layers/pl.json @@ -1143,17 +1143,6 @@ }, "question": "Czy w tym miejscu znajduje się stacja zrzutu ścieków sanitarnych?" }, - "caravansites-toilets": { - "mappings": { - "0": { - "then": "To miejsce ma toalety" - }, - "1": { - "then": "To miejsce nie ma toalet" - } - }, - "question": "Czy to miejsce ma toalety?" - }, "caravansites-website": { "question": "Czy to miejsce ma stronę internetową?", "render": "Official website: {website}" diff --git a/langs/layers/pt.json b/langs/layers/pt.json index 6e2b577c2..b1599bf54 100644 --- a/langs/layers/pt.json +++ b/langs/layers/pt.json @@ -1418,17 +1418,6 @@ }, "question": "Este local tem uma estação de aterro sanitário?" }, - "caravansites-toilets": { - "mappings": { - "0": { - "then": "Este lugar tem casa de banho" - }, - "1": { - "then": "Este lugar não tem casas de banho" - } - }, - "question": "Este lugar tem casas de banho?" - }, "caravansites-website": { "question": "Este lugar tem um website?", "render": "Site oficial: {website}" diff --git a/langs/layers/pt_BR.json b/langs/layers/pt_BR.json index 2834d9a0c..6da173f4d 100644 --- a/langs/layers/pt_BR.json +++ b/langs/layers/pt_BR.json @@ -1428,17 +1428,6 @@ }, "question": "Este local tem uma estação de aterro sanitário?" }, - "caravansites-toilets": { - "mappings": { - "0": { - "then": "Este lugar tem banheiros" - }, - "1": { - "then": "Este lugar não tem banheiros" - } - }, - "question": "Este lugar tem banheiros?" - }, "caravansites-website": { "question": "Este lugar tem um website?", "render": "Site oficial: {website}" diff --git a/langs/layers/ru.json b/langs/layers/ru.json index 4cda3d5e1..915e17453 100644 --- a/langs/layers/ru.json +++ b/langs/layers/ru.json @@ -710,17 +710,6 @@ }, "question": "В этом кемпинге есть место для слива отходов из туалетных резервуаров?" }, - "caravansites-toilets": { - "mappings": { - "0": { - "then": "В этом месте есть туалеты" - }, - "1": { - "then": "В этом месте нет туалетов" - } - }, - "question": "Здесь есть туалеты?" - }, "caravansites-website": { "question": "Есть ли у этого места веб-сайт?", "render": "Официальный сайт: {website}" diff --git a/langs/layers/zh_Hant.json b/langs/layers/zh_Hant.json index 44fd18acb..adf784206 100644 --- a/langs/layers/zh_Hant.json +++ b/langs/layers/zh_Hant.json @@ -594,17 +594,6 @@ }, "question": "這個地方有衛生設施嗎?" }, - "caravansites-toilets": { - "mappings": { - "0": { - "then": "這個地方有廁所" - }, - "1": { - "then": "這個地方並沒有廁所" - } - }, - "question": "這個地方有廁所嗎?" - }, "caravansites-website": { "question": "這個地方有網站嗎?", "render": "官方網站:{website}" From 3da62f8d708c73cd72868db0206ed90cea2b6b98 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 29 Aug 2025 02:10:49 +0200 Subject: [PATCH 70/92] Themes(hut): move 'hut' above shelter, steal shelter type question, also see #2515 --- assets/layers/hut/hut.json | 8 +++++++- assets/themes/nature/nature.json | 6 +++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/assets/layers/hut/hut.json b/assets/layers/hut/hut.json index 2aac48426..7620f1bfa 100644 --- a/assets/layers/hut/hut.json +++ b/assets/layers/hut/hut.json @@ -148,6 +148,12 @@ { "id": "preset_type", "render": "{preset_type_select()}" + }, + { + "builtin": "shelter.shelter-type", + "override": { + "condition": "amenity=shelter" + } } ], "filter":[ @@ -157,4 +163,4 @@ "enableRelocation": false, "enableImproveAccuracy": true } -} \ No newline at end of file +} diff --git a/assets/themes/nature/nature.json b/assets/themes/nature/nature.json index 0973a354d..a91c126a1 100644 --- a/assets/themes/nature/nature.json +++ b/assets/themes/nature/nature.json @@ -60,8 +60,8 @@ "nature_reserve", { "builtin": [ - "shelter", - "hut" + "hut", + "shelter" ], "override": { "minzoom": 11 @@ -97,4 +97,4 @@ "observation_tower", "viewpoint" ] -} \ No newline at end of file +} From 152d93bf4bdc73e4d331e2718181599ccf9a18b1 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 29 Aug 2025 02:11:42 +0200 Subject: [PATCH 71/92] Themes(preset_type_select): preset type select now removes keys set by other presets --- src/UI/Popup/DataVisualisations.ts | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/UI/Popup/DataVisualisations.ts b/src/UI/Popup/DataVisualisations.ts index dcbe8c80a..1cc80257e 100644 --- a/src/UI/Popup/DataVisualisations.ts +++ b/src/UI/Popup/DataVisualisations.ts @@ -30,6 +30,8 @@ import Tr from "../Base/Tr.svelte" import Combine from "../Base/Combine" import Marker from "../Map/Marker.svelte" import { twJoin } from "tailwind-merge" +import { Tag } from "../../Logic/Tags/Tag" +import { Lists } from "../../Utils/Lists" class DirectionIndicatorVis extends SpecialVisualizationSvelte { funcName = "direction_indicator" @@ -243,17 +245,24 @@ class PresetTypeSelect extends SpecialVisualizationSvelte { console.warn("Trying to use the _original_ layer") layer = state.theme.layers.find((l) => l.id === layer._basedOn) ?? layer } + + const allKeys = Lists.dedup(layer.presets.flatMap(preset => preset.tags.flatMap(tag => tag.usedKeys()))) + const question: QuestionableTagRenderingConfigJson = { id: layer.id + "-type", question: t.question.translations, - mappings: layer.presets.map((pr) => ({ - if: new And(pr.tags).asJson(), - icon: "auto", - then: (pr.description ? t.typeDescription : t.typeTitle).Subs({ - title: pr.title, - description: pr.description, - }).translations, - })), + mappings: layer.presets.map((pr) => { + const presetKeys = new Set(pr.tags.flatMap(t => t.key)) + const keysToRemove = allKeys.filter(k => !presetKeys.has(k)).map(k => new Tag(k, "")) + return ({ + if: new And([...pr.tags, ...keysToRemove]).asJson(), + icon: "auto", + then: (pr.description ? t.typeDescription : t.typeTitle).Subs({ + title: pr.title, + description: pr.description, + }).translations, + }) + }), } if (question.mappings.length === 0) { console.error("No mappings for preset_type_select, something went wrong") From 81f98e62ceec6313256a7864822225efb0b772d1 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 29 Aug 2025 02:11:58 +0200 Subject: [PATCH 72/92] Themes(preset_type_select): fix 'auto-icon' display --- src/Logic/Tags/SubstitutingTag.ts | 2 +- src/Logic/Tags/TagUtils.ts | 22 ++++++++++++++++++- .../TagRendering/TagRenderingMapping.svelte | 3 ++- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/Logic/Tags/SubstitutingTag.ts b/src/Logic/Tags/SubstitutingTag.ts index e44e28394..9930a1de1 100644 --- a/src/Logic/Tags/SubstitutingTag.ts +++ b/src/Logic/Tags/SubstitutingTag.ts @@ -120,7 +120,7 @@ export default class SubstitutingTag extends TagsFilter { } isNegative(): boolean { - return false + return this._value === "" } visit(f: (tagsFilter: TagsFilter) => void) { diff --git a/src/Logic/Tags/TagUtils.ts b/src/Logic/Tags/TagUtils.ts index c09a77617..e4aba8209 100644 --- a/src/Logic/Tags/TagUtils.ts +++ b/src/Logic/Tags/TagUtils.ts @@ -994,11 +994,31 @@ export class TagUtils { ].join("\n") } - static fromProperties(tags: Record): TagConfigJson | boolean { + public static fromProperties(tags: Record): TagConfigJson | boolean { const opt = new And(Object.keys(tags).map((k) => new Tag(k, tags[k]))).optimize() if (opt === true || opt === false) { return opt } return opt.asJson() } + + /** + * Returns a similarly structured tag, but all tags with an empty value are removed. + * Those are assumed to be all met (and thus true) + * + * new And([new Tag("a", "b"), new Tag("c", "")] // => new Tag("a","b") + * new And([new Tag("c", "")] // => true + */ + public static removeEmptyParts(tag: UploadableTag): UploadableTag | true { + if (tag["and"]) { + const tags = tag["and"] + const cleaned = tags.map(t => TagUtils.removeEmptyParts(t)) + const filtered = cleaned.filter(t => t !== true) + return new And(filtered) + } + if (tag.isNegative()) { + return true + } + return tag + } } diff --git a/src/UI/Popup/TagRendering/TagRenderingMapping.svelte b/src/UI/Popup/TagRendering/TagRenderingMapping.svelte index 44b99286f..f7e365f1c 100644 --- a/src/UI/Popup/TagRendering/TagRenderingMapping.svelte +++ b/src/UI/Popup/TagRendering/TagRenderingMapping.svelte @@ -44,8 +44,9 @@ } function getAutoIcon(mapping: { readonly if?: TagsFilter }): Readonly> { + const ifTags = TagUtils.removeEmptyParts(mapping.if) for (const preset of layer.presets) { - if (!new And(preset.tags).shadows(mapping.if)) { + if (!new And(preset.tags).shadows(ifTags)) { continue } From 2b8f32fe3ab219656f7414df58b9ced13991d291 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Fri, 29 Aug 2025 00:13:26 +0000 Subject: [PATCH 73/92] Make branches compatible --- assets/layers/hut/hut.json | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/assets/layers/hut/hut.json b/assets/layers/hut/hut.json index 8ebb80836..27951671e 100644 --- a/assets/layers/hut/hut.json +++ b/assets/layers/hut/hut.json @@ -165,12 +165,7 @@ { "builtin": "shelter.shelter-type", "override": { - "condition": { - "and": [ - "amenity=shelter", - "shelter_type=basic_hut" - ] - } + "condition": "amenity=shelter" } } ], From 34cc65b2406b7add9f0e44ca802e2effed8b12ba Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 1 Sep 2025 00:06:37 +0200 Subject: [PATCH 74/92] Refactoring: small tweaks to PointRenderingConfig.ts --- src/Models/ThemeConfig/PointRenderingConfig.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/Models/ThemeConfig/PointRenderingConfig.ts b/src/Models/ThemeConfig/PointRenderingConfig.ts index 7b63f7d5e..8229fd8e2 100644 --- a/src/Models/ThemeConfig/PointRenderingConfig.ts +++ b/src/Models/ThemeConfig/PointRenderingConfig.ts @@ -129,7 +129,8 @@ export default class PointRenderingConfig extends WithContextLoader { context + ".rotationAlignment" ) } - private static FromHtmlMulti( + + private static fromHtmlMulti( multiSpec: string, tags: Store> ): BaseUIElement { @@ -207,7 +208,7 @@ export default class PointRenderingConfig extends WithContextLoader { : undefined let badges = undefined if (options?.includeBadges ?? true) { - badges = this.GetBadges(tags, options?.metatags) + badges = this.getBadges(tags, options?.metatags) } const iconAndBadges = new Combine([icon, badges]).SetClass("block relative") @@ -235,7 +236,7 @@ export default class PointRenderingConfig extends WithContextLoader { } else if (label === undefined) { htmlEl = new Combine([iconAndBadges]) } else { - htmlEl = new Combine([iconAndBadges, label]).SetStyle("flex flex-col") + htmlEl = new Combine([iconAndBadges, label]) } if (css !== undefined) { @@ -251,7 +252,7 @@ export default class PointRenderingConfig extends WithContextLoader { } } - private GetBadges( + private getBadges( tags: Store>, metaTags?: Store> ): BaseUIElement { @@ -283,15 +284,14 @@ export default class PointRenderingConfig extends WithContextLoader { if (htmlDefs.startsWith("<") && htmlDefs.endsWith(">")) { // This is probably an HTML-element return new FixedUiElement(Utils.SubstituteKeys(htmlDefs, tagsData)) - .SetStyle("width: 1.5rem") - .SetClass("block") + .SetClass("block w-6") } if (!htmlDefs) { return undefined } - const badgeElement = PointRenderingConfig.FromHtmlMulti( + const badgeElement = PointRenderingConfig.fromHtmlMulti( htmlDefs, tags )?.SetClass("block relative") @@ -299,8 +299,7 @@ export default class PointRenderingConfig extends WithContextLoader { return undefined } return new Combine([badgeElement]) - .SetStyle("width: 1.5rem") - .SetClass("block") + .SetClass("block w-6") }) return new Combine(badgeElements).SetClass("inline-flex h-full") From 42f07bc1f3f64a63ce7dec0bf22a4a1e5bdbd42f Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 1 Sep 2025 00:58:11 +0200 Subject: [PATCH 75/92] Fix: fix tests --- src/Logic/Tags/TagUtils.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Logic/Tags/TagUtils.ts b/src/Logic/Tags/TagUtils.ts index e4aba8209..03f9a7f0a 100644 --- a/src/Logic/Tags/TagUtils.ts +++ b/src/Logic/Tags/TagUtils.ts @@ -1006,15 +1006,18 @@ export class TagUtils { * Returns a similarly structured tag, but all tags with an empty value are removed. * Those are assumed to be all met (and thus true) * - * new And([new Tag("a", "b"), new Tag("c", "")] // => new Tag("a","b") - * new And([new Tag("c", "")] // => true + * TagUtils.removeEmptyParts(new And([new Tag("a", "b"), new Tag("c", "")])) // => new Tag("a","b") + * TagUtils.removeEmptyParts(new And([new Tag("c", "")])) // => true */ public static removeEmptyParts(tag: UploadableTag): UploadableTag | true { if (tag["and"]) { const tags = tag["and"] const cleaned = tags.map(t => TagUtils.removeEmptyParts(t)) const filtered = cleaned.filter(t => t !== true) - return new And(filtered) + if (filtered.length === 0) { + return true + } + return new And(filtered).optimize() } if (tag.isNegative()) { return true From 2a2b8b77e8338a5a72b235d511efa4053d775041 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 1 Sep 2025 00:58:39 +0200 Subject: [PATCH 76/92] Refactoring: reduce dependency on 'VariableUIElement' by having a 'TrDyn'-svelte component instead --- src/Models/Denomination.ts | 2 +- .../ThemeConfig/Conversion/FixImages.ts | 5 +- src/Models/Unit.ts | 10 +-- src/UI/Base/TrDyn.svelte | 9 +++ src/UI/Popup/DataVisualisations.ts | 69 +++++++++---------- .../UISpecialVisualisations.ts | 39 +++++------ ...ebAndCommunicationSpecialVisualisations.ts | 39 +++++------ src/UI/SpecialVisualizations.ts | 15 ++-- 8 files changed, 90 insertions(+), 98 deletions(-) create mode 100644 src/UI/Base/TrDyn.svelte diff --git a/src/Models/Denomination.ts b/src/Models/Denomination.ts index 659cc9a11..487d36c7a 100644 --- a/src/Models/Denomination.ts +++ b/src/Models/Denomination.ts @@ -64,7 +64,7 @@ export class Denomination { throw `${context} uses the old 'default'-key. Use "useIfNoUnitGiven" or "useAsDefaultInput" instead` } - const humanTexts = Translations.T(json.human) + const humanTexts = typeof json.human === "string" ? Translations.T(json.human) : new Translation(json.human) humanTexts.OnEveryLanguage((text, language) => { if (text.indexOf("{quantity}") < 0) { throw `In denomination: a human text should contain {quantity} (at ${context}.human.${language}). The offending text is: ${text}` diff --git a/src/Models/ThemeConfig/Conversion/FixImages.ts b/src/Models/ThemeConfig/Conversion/FixImages.ts index 1eca0e2a9..eb680177e 100644 --- a/src/Models/ThemeConfig/Conversion/FixImages.ts +++ b/src/Models/ThemeConfig/Conversion/FixImages.ts @@ -190,10 +190,7 @@ export class ExtractImages extends Conversion< if (!allRenderedValuesAreImages && isImage) { // Extract images from the translations allFoundImages.push( - ...Translations.T( - img.leaf, - "extract_images from " + img.path.join(".") - ) + ...Translations.T(img.leaf) .ExtractImages(false) .map((path) => ({ path, diff --git a/src/Models/Unit.ts b/src/Models/Unit.ts index 9004dfc2f..a7e5428b7 100644 --- a/src/Models/Unit.ts +++ b/src/Models/Unit.ts @@ -1,7 +1,8 @@ -import BaseUIElement from "../UI/BaseUIElement" import { Denomination } from "./Denomination" import { Validator } from "../UI/InputElement/Validator" import FloatValidator from "../UI/InputElement/Validators/FloatValidator" +import { Translation } from "../UI/i18n/Translation" +import Translations from "../UI/i18n/Translations" export class Unit { public readonly appliesToKeys: Set @@ -109,7 +110,7 @@ export class Unit { return [undefined, undefined] } - asHumanLongValue(value: string | number, country: () => string): BaseUIElement | string { + asHumanLongValue(value: string | number, country: () => string): Translation { if (value === undefined) { return undefined } @@ -120,10 +121,10 @@ export class Unit { return human.Subs({ quantity: stripped + "/" }) } if (stripped === "1") { - return denom?.humanSingular ?? stripped + return Translations.T(denom?.humanSingular ?? stripped) } if (human === undefined) { - return stripped ?? value + return Translations.T(stripped ?? value) } return human.Subs({ quantity: stripped }) @@ -173,7 +174,6 @@ export class Unit { /** * Gets the value in the canonical denomination; * e.g. "1cm -> 0.01" as it is 0.01meter - * @param v */ public valueInCanonical(value: string, country: () => string): number { const denom = this.findDenomination(value, country) diff --git a/src/UI/Base/TrDyn.svelte b/src/UI/Base/TrDyn.svelte new file mode 100644 index 000000000..d84619ffc --- /dev/null +++ b/src/UI/Base/TrDyn.svelte @@ -0,0 +1,9 @@ + + + diff --git a/src/UI/Popup/DataVisualisations.ts b/src/UI/Popup/DataVisualisations.ts index 1cc80257e..4df4c45c7 100644 --- a/src/UI/Popup/DataVisualisations.ts +++ b/src/UI/Popup/DataVisualisations.ts @@ -2,7 +2,7 @@ import { SpecialVisualisationArg, SpecialVisualisationParams, SpecialVisualization, - SpecialVisualizationSvelte, + SpecialVisualizationSvelte } from "../SpecialVisualization" import { HistogramViz } from "./HistogramViz" import { Store } from "../../Logic/UIEventSource" @@ -32,6 +32,8 @@ import Marker from "../Map/Marker.svelte" import { twJoin } from "tailwind-merge" import { Tag } from "../../Logic/Tags/Tag" import { Lists } from "../../Utils/Lists" +import { Translation } from "../i18n/Translation" +import TrDyn from "../Base/TrDyn.svelte" class DirectionIndicatorVis extends SpecialVisualizationSvelte { funcName = "direction_indicator" @@ -46,7 +48,7 @@ class DirectionIndicatorVis extends SpecialVisualizationSvelte { } } -class DirectionAbsolute extends SpecialVisualization { +class DirectionAbsolute extends SpecialVisualizationSvelte { funcName = "direction_absolute" docs = "Converts compass degrees (with 0° being north, 90° being east, ...) into a human readable, translated direction such as 'north', 'northeast'" @@ -65,24 +67,20 @@ class DirectionAbsolute extends SpecialVisualization { ] group = "data" - constr({ tags, args }: SpecialVisualisationParams): BaseUIElement { + constr({ tags, args }: SpecialVisualisationParams): SvelteUIElement { const key = args[0] === "" ? "_direction:centerpoint" : args[0] const offset = args[1] === "" ? 0 : Number(args[1]) - return new VariableUiElement( - tags - .map((tags) => { - console.log("Direction value", tags[key], key) - return tags[key] - }) - .mapD((value) => { - const dir = GeoOperations.bearingToHuman( - GeoOperations.parseBearing(value) + offset - ) - console.log("Human dir", dir) - return Translations.t.general.visualFeedback.directionsAbsolute[dir] - }) - ) + const t: Store = tags + .map((tags) => tags[key]) + .mapD((value: string) => { + const dir = GeoOperations.bearingToHuman( + GeoOperations.parseBearing(value) + offset + ) + return Translations.t.general.visualFeedback.directionsAbsolute[dir] + }) + + return new SvelteUIElement(TrDyn, { t }) } } @@ -162,7 +160,7 @@ class OpeningHoursState extends SpecialVisualizationSvelte { } } -class Canonical extends SpecialVisualization { +class Canonical extends SpecialVisualizationSvelte { group = "data" funcName = "canonical" @@ -181,24 +179,23 @@ class Canonical extends SpecialVisualization { constr({ state, tags, args }: SpecialVisualisationParams) { const key = args[0] - return new VariableUiElement( - tags - .map((tags) => tags[key]) - .map((value) => { - if (value === undefined) { - return undefined - } - const allUnits: Unit[] = [].concat( - ...(state?.theme?.layers?.map((lyr) => lyr.units) ?? []) - ) - const unit = allUnits.filter((unit) => unit.isApplicableToKey(key))[0] - if (unit === undefined) { - return value - } - const getCountry = () => tags.data._country - return unit.asHumanLongValue(value, getCountry) - }) - ) + const t: Store = tags + .map((tags) => tags[key]) + .map((value: string) => { + if (value === undefined) { + return undefined + } + const allUnits: Unit[] = [].concat( + ...(state?.theme?.layers?.map((lyr) => lyr.units) ?? []) + ) + const unit = allUnits.filter((unit) => unit.isApplicableToKey(key))[0] + if (unit === undefined) { + return Translations.T(value) + } + const getCountry = () => tags.data._country + return unit.asHumanLongValue(value, getCountry) + }) + return new SvelteUIElement(TrDyn, { t }) } } diff --git a/src/UI/SpecialVisualisations/UISpecialVisualisations.ts b/src/UI/SpecialVisualisations/UISpecialVisualisations.ts index 0b7277d8d..40d9a5513 100644 --- a/src/UI/SpecialVisualisations/UISpecialVisualisations.ts +++ b/src/UI/SpecialVisualisations/UISpecialVisualisations.ts @@ -1,13 +1,6 @@ -import { - SpecialVisualisationParams, - SpecialVisualization, - SpecialVisualizationState, - SpecialVisualizationSvelte, -} from "../SpecialVisualization" +import { SpecialVisualisationParams, SpecialVisualization, SpecialVisualizationSvelte } from "../SpecialVisualization" import SvelteUIElement from "../Base/SvelteUIElement" -import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource" -import { Feature, GeoJSON } from "geojson" -import LayerConfig from "../../Models/ThemeConfig/LayerConfig" +import { ImmutableStore, Store } from "../../Logic/UIEventSource" import Questionbox from "../Popup/TagRendering/Questionbox.svelte" import MinimapViz from "../Popup/MinimapViz.svelte" import SplitRoadWizard from "../Popup/SplitRoadWizard.svelte" @@ -19,11 +12,11 @@ import { ShareLinkViz } from "../Popup/ShareLinkViz" import { GeoOperations } from "../../Logic/GeoOperations" import AddNewPoint from "../Popup/AddNewPoint/AddNewPoint.svelte" import BaseUIElement from "../BaseUIElement" -import { VariableUiElement } from "../Base/VariableUIElement" import { Translation } from "../i18n/Translation" import { FixedUiElement } from "../Base/FixedUiElement" import { default as FeatureTitle } from "../Popup/Title.svelte" import CreateCopy from "../Popup/AddNewPoint/CreateCopy.svelte" +import TrDyn from "../Base/TrDyn.svelte" /** * Thin wrapper around QuestionBox.svelte to include it into the special Visualisations @@ -105,7 +98,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, tags, args, feature, layer }: SpecialVisualisationParams): SvelteUIElement { + constr({ state, tags, args, feature }: SpecialVisualisationParams): SvelteUIElement { const minzoom = Number(args[0] ?? 18) const ids = args[1]?.split(";")?.map((s) => s.trim()) ?? ["id"] const clss = args[2] @@ -263,18 +256,18 @@ class Translated extends SpecialVisualization { ] constr({ tags, args }: SpecialVisualisationParams): BaseUIElement { - return new VariableUiElement( - 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") - } catch (e) { - console.error("Cannot create a translation for", v, "due to", e) - return JSON.stringify(v) - } - }) - ) + + const t: Store = tags.map((tags) => { + const v = tags[args[0] ?? "value"] + try { + const tr = typeof v === "string" ? JSON.parse(v) : v + return new Translation(tr) + } catch (e) { + console.error("Cannot create a translation for", v, "due to", e) + return new Translation({ "*": JSON.stringify(v) }) + } + }) + return new SvelteUIElement(TrDyn, { t }) } } diff --git a/src/UI/SpecialVisualisations/WebAndCommunicationSpecialVisualisations.ts b/src/UI/SpecialVisualisations/WebAndCommunicationSpecialVisualisations.ts index 48033f9de..8f88d3b2d 100644 --- a/src/UI/SpecialVisualisations/WebAndCommunicationSpecialVisualisations.ts +++ b/src/UI/SpecialVisualisations/WebAndCommunicationSpecialVisualisations.ts @@ -1,21 +1,18 @@ -import { - SpecialVisualisationParams, - SpecialVisualization, - SpecialVisualizationSvelte, -} from "../SpecialVisualization" +import { SpecialVisualisationParams, 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" import Wikipedia from "../../Logic/Web/Wikipedia" import WikipediaPanel from "../Wikipedia/WikipediaPanel.svelte" -import { VariableUiElement } from "../Base/VariableUIElement" import { Utils } from "../../Utils" import { Translation } from "../i18n/Translation" import { MapillaryLinkVis } from "../Popup/MapillaryLinkVis" import SendEmail from "../Popup/SendEmail.svelte" import DynLink from "../Base/DynLink.svelte" import { Lists } from "../../Utils/Lists" +import TrDyn from "../Base/TrDyn.svelte" +import Translations from "../i18n/Translations" class FediverseLinkVis extends SpecialVisualizationSvelte { funcName = "fediverse_link" @@ -65,7 +62,7 @@ class WikipediaVis extends SpecialVisualizationSvelte { } } -class WikidatalabelVis extends SpecialVisualization { +class WikidatalabelVis extends SpecialVisualizationSvelte { funcName = "wikidata_label" group = "web_and_communication" @@ -84,25 +81,25 @@ class WikidatalabelVis extends SpecialVisualization { "`{wikidata_label()}` is a basic example, `{wikipedia(name:etymology:wikidata)}` to show the label itself" constr({ tags, args }: SpecialVisualisationParams) { - const id = tags + const id: Store = tags .map((tags) => tags[args[0]]) .map((wikidata) => { const wikidataIds = Lists.noEmpty( wikidata?.split(";")?.map((wd) => wd.trim()) ?? [] ) - return wikidataIds?.[0] + return (wikidataIds?.[0]) }) - const entry = id.bind((id) => Wikidata.LoadWikidataEntry(id)) - - return new VariableUiElement( - entry.map((e) => { - if (e === undefined || e["success"] === undefined) { - return id.data - } - const response = e["success"] - return Translation.fromMap(response.labels) - }) - ) + const entry: Store<{ success: WikidataResponse } | { + error: any + }> = id.bind((id) => Wikidata.LoadWikidataEntry(id)) + const t: Store = entry.map((e) => { + if (e === undefined || e["success"] === undefined) { + return Translations.T(id.data) + } + const response = e["success"] + return Translation.fromMap(response.labels) + }) + return new SvelteUIElement(TrDyn, { t }) } } @@ -199,7 +196,7 @@ class LinkVis extends SpecialVisualizationSvelte { } } export class WebAndCommunicationSpecialVisualisations { - public static initList(): SpecialVisualization[] { + public static initList(): SpecialVisualizationSvelte[] { return [ new FediverseLinkVis(), new WikipediaVis(), diff --git a/src/UI/SpecialVisualizations.ts b/src/UI/SpecialVisualizations.ts index 5f44e7800..9f78a8710 100644 --- a/src/UI/SpecialVisualizations.ts +++ b/src/UI/SpecialVisualizations.ts @@ -1,8 +1,4 @@ -import { - RenderingSpecification, - SpecialVisualization, - SpecialVisualizationSvelte, -} from "./SpecialVisualization" +import { RenderingSpecification, SpecialVisualization, SpecialVisualizationSvelte } from "./SpecialVisualization" import { UploadToOsmViz } from "./Popup/UploadToOsmViz" import { MultiApplyViz } from "./Popup/MultiApplyViz" import AutoApplyButtonVis from "./Popup/AutoApplyButtonVis" @@ -15,8 +11,11 @@ import { UISpecialVisualisations } from "./SpecialVisualisations/UISpecialVisual import { SettingsVisualisations } from "./SpecialVisualisations/SettingsVisualisations" import { ReviewSpecialVisualisations } from "./SpecialVisualisations/ReviewSpecialVisualisations" import { DataImportSpecialVisualisations } from "./SpecialVisualisations/DataImportSpecialVisualisations" -import TagrenderingManipulationSpecialVisualisations from "./SpecialVisualisations/TagrenderingManipulationSpecialVisualisations" -import { WebAndCommunicationSpecialVisualisations } from "./SpecialVisualisations/WebAndCommunicationSpecialVisualisations" +import TagrenderingManipulationSpecialVisualisations + from "./SpecialVisualisations/TagrenderingManipulationSpecialVisualisations" +import { + WebAndCommunicationSpecialVisualisations +} from "./SpecialVisualisations/WebAndCommunicationSpecialVisualisations" import { DataVisualisations } from "./Popup/DataVisualisations" import { DataExportVisualisations } from "./Popup/DataExportVisualisations" import { Utils } from "../Utils" @@ -186,6 +185,7 @@ export default class SpecialVisualizations { ...SettingsVisualisations.initList(), ...DataImportSpecialVisualisations.initList(), ...DataExportVisualisations.initList(), + ...WebAndCommunicationSpecialVisualisations.initList(), new UploadToOsmViz(), new MultiApplyViz(), ] @@ -195,7 +195,6 @@ export default class SpecialVisualizations { ...UISpecialVisualisations.initList(), ...ReviewSpecialVisualisations.initList(), ...TagrenderingManipulationSpecialVisualisations.initList(), - ...WebAndCommunicationSpecialVisualisations.initList(), ...DataVisualisations.initList(), ...specialVisualizationsSv, ] From 5a0866ff08a341c9c82f44ae93e428b2ef6f4d7c Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 1 Sep 2025 01:04:15 +0200 Subject: [PATCH 77/92] Refactoring: move class to more appropriate file, remove obsolete file --- .../State/GeolocationControlState.ts} | 0 .../UserMapFeatureswitchState.ts | 3 +-- .../BigComponents/GeolocationIndicator.svelte | 2 +- src/UI/BigComponents/SimpleAddUI.ts | 21 ------------------- 4 files changed, 2 insertions(+), 24 deletions(-) rename src/{UI/BigComponents/GeolocationControl.ts => Logic/State/GeolocationControlState.ts} (100%) delete mode 100644 src/UI/BigComponents/SimpleAddUI.ts diff --git a/src/UI/BigComponents/GeolocationControl.ts b/src/Logic/State/GeolocationControlState.ts similarity index 100% rename from src/UI/BigComponents/GeolocationControl.ts rename to src/Logic/State/GeolocationControlState.ts diff --git a/src/Models/ThemeViewState/UserMapFeatureswitchState.ts b/src/Models/ThemeViewState/UserMapFeatureswitchState.ts index f6cfbc442..98f7f9ad2 100644 --- a/src/Models/ThemeViewState/UserMapFeatureswitchState.ts +++ b/src/Models/ThemeViewState/UserMapFeatureswitchState.ts @@ -18,7 +18,6 @@ import { FeatureSource, WritableFeatureSource } from "../../Logic/FeatureSource/ import FullNodeDatabaseSource from "../../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource" import { WithUserRelatedState } from "./WithUserRelatedState" import GeoLocationHandler from "../../Logic/Actors/GeoLocationHandler" -import { GeolocationControlState } from "../../UI/BigComponents/GeolocationControl" import ShowOverlayRasterLayer from "../../UI/Map/ShowOverlayRasterLayer" import { BBox } from "../../Logic/BBox" import ShowDataLayer from "../../UI/Map/ShowDataLayer" @@ -26,6 +25,7 @@ import { OfflineBasemapManager } from "../../Logic/OfflineBasemapManager" import { IsOnline } from "../../Logic/Web/IsOnline" import { Tiles } from "../TileRange" import { LocalStorageSource } from "../../Logic/Web/LocalStorageSource" +import { GeolocationControlState } from "../../Logic/State/GeolocationControlState" /** * The first core of the state management; everything related to: @@ -273,7 +273,6 @@ export class UserMapFeatureswitchState extends WithUserRelatedState { * * Note: this method is _incorrectly_ marked as not used */ - // @ts-ignore public showCurrentLocationOn(map: Store) { const id = "gps_location" const layer = this.theme.getLayer(id) diff --git a/src/UI/BigComponents/GeolocationIndicator.svelte b/src/UI/BigComponents/GeolocationIndicator.svelte index 69cacf5f7..bcd5ad55d 100644 --- a/src/UI/BigComponents/GeolocationIndicator.svelte +++ b/src/UI/BigComponents/GeolocationIndicator.svelte @@ -1,7 +1,7 @@ +
+ +
diff --git a/src/index.css b/src/index.css index 2de7f9750..1d9c0a82d 100644 --- a/src/index.css +++ b/src/index.css @@ -37,7 +37,7 @@ --interactive-background: #dddddd; --interactive-foreground: black; - --interactive-contrast: #cd1dcd; + --interactive-contrast: #C107C5; --interaction-border: #bfbfbf; @@ -228,8 +228,10 @@ button:hover:not(.disabled):not(.as-link):not(.primary), .button:hover:not(.disa } -button:focus, .button:focus { - border-color: var(--interactive-contrast); +:focus-visible { + outline: auto; + outline-color: var(--interactive-contrast); + outline-style: solid; } .focus { From 0d33e18a593dd6527465cd5e86b2506fceb4d1ed Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 2 Sep 2025 23:08:28 +0200 Subject: [PATCH 81/92] Feature: add multiTitle, better error handling, improve help text of script --- scripts/importCustomTheme.ts | 11 +++++++++-- .../Json/QuestionableTagRenderingConfigJson.ts | 6 ++++++ src/Models/ThemeConfig/Json/ThemeConfigJson.ts | 1 + src/Models/ThemeConfig/TagRenderingConfig.ts | 2 ++ src/UI/Popup/TagRendering/TagRenderingAnswer.svelte | 9 ++++++--- src/UI/Studio/StudioServer.ts | 4 +++- src/UI/StudioGUI.svelte | 3 ++- 7 files changed, 29 insertions(+), 7 deletions(-) diff --git a/scripts/importCustomTheme.ts b/scripts/importCustomTheme.ts index 70de201c0..2ee03fd66 100644 --- a/scripts/importCustomTheme.ts +++ b/scripts/importCustomTheme.ts @@ -14,13 +14,20 @@ import { GenerateLicenseInfo } from "./generateLicenseInfo" class ImportCustomTheme extends Script { constructor() { - super("Given the path of a custom layer, will load the layer into mapcomplete as official") + super(["Given the path of a custom layer, will load the layer into mapcomplete as official","", + "Usage:", + "vite-node scripts/importCustomTheme.ts "].join("\n")) } async main(args: string[]) { + if(args.length === 0){ + this.printHelp() + return + } const path = args[0] - const layerconfig = JSON.parse(readFileSync(path, "utf-8")) + const layerconfig = JSON.parse( + readFileSync(path, "utf-8")) const id = layerconfig.id const dirPath = "./assets/layers/" + id if (!existsSync(dirPath)) { diff --git a/src/Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson.ts b/src/Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson.ts index 34f4831f8..b0aa9cecc 100644 --- a/src/Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson.ts +++ b/src/Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson.ts @@ -216,6 +216,12 @@ export interface QuestionableTagRenderingConfigJson extends TagRenderingConfigJs */ multiAnswer?: boolean + /** + * question: If one or more answers match (in case of a multiAnswer), what title/intro should be added? + * ifunset: don't show a title + */ + multiTitle?: Translatable + /** * Allow freeform text input from the user */ diff --git a/src/Models/ThemeConfig/Json/ThemeConfigJson.ts b/src/Models/ThemeConfig/Json/ThemeConfigJson.ts index 4644b1a8f..4177ea817 100644 --- a/src/Models/ThemeConfig/Json/ThemeConfigJson.ts +++ b/src/Models/ThemeConfig/Json/ThemeConfigJson.ts @@ -45,6 +45,7 @@ export interface ThemeConfigJson { * question: What is the title of this theme? * * The human-readable title, as shown in the welcome message and the index page + * ifunset: reuse 'name' from the only layer * group: basic */ title: Translatable diff --git a/src/Models/ThemeConfig/TagRenderingConfig.ts b/src/Models/ThemeConfig/TagRenderingConfig.ts index 25da44ccf..befa3ff70 100644 --- a/src/Models/ThemeConfig/TagRenderingConfig.ts +++ b/src/Models/ThemeConfig/TagRenderingConfig.ts @@ -92,6 +92,7 @@ export default class TagRenderingConfig { } public readonly multiAnswer: boolean + public readonly multiTitle?: Translation public mappings: Mapping[] public readonly editButtonAriaLabel?: Translation @@ -164,6 +165,7 @@ export default class TagRenderingConfig { this.questionHintIsMd = json["questionHintIsMd"] ?? false this.alwaysForceSaveButton = json["#force-save-button"] === "yes" this.description = Translations.T(json.description, translationKey + ".description") + this.multiTitle = Translations.T(json.multiTitle, translationKey + ".multiTitle") this._definedIn = json._definedIn if (json.onSoftDelete && !Array.isArray(json.onSoftDelete)) { throw context + ".onSoftDelete Not an array: " + typeof json.onSoftDelete diff --git a/src/UI/Popup/TagRendering/TagRenderingAnswer.svelte b/src/UI/Popup/TagRendering/TagRenderingAnswer.svelte index 3c0342586..e6efe22d3 100644 --- a/src/UI/Popup/TagRendering/TagRenderingAnswer.svelte +++ b/src/UI/Popup/TagRendering/TagRenderingAnswer.svelte @@ -9,6 +9,7 @@ import { twMerge } from "tailwind-merge" import { onDestroy } from "svelte" import { Lists } from "../../../Utils/Lists" + import Tr from "../../Base/Tr.svelte" export let tags: UIEventSource | undefined> @@ -34,10 +35,12 @@ {#if config !== undefined && (config?.condition === undefined || config.condition.matchesProperties($tags))}
- {#if $showHome} + {#if $showHome || $isAndroid} {#if Utils.isIframe} From 90fdb22842a85e2a81f11983f404601657c38ed7 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Wed, 3 Sep 2025 01:09:50 +0200 Subject: [PATCH 85/92] Chore: update underlying android version --- android | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android b/android index 817e8198b..dc3f3f5ac 160000 --- a/android +++ b/android @@ -1 +1 @@ -Subproject commit 817e8198b5e4c30572d7d3f082d60fc10a7be21e +Subproject commit dc3f3f5ac3d4d42bed7aeb58ff5386a063dbac06 From 3f897bdff5a2220d9cdc6ab898f45b153cd23375 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Wed, 3 Sep 2025 01:10:56 +0200 Subject: [PATCH 86/92] UI: attempt to fix #2508 --- src/Logic/Web/AndroidPolyfill.ts | 6 +++++- src/UI/AllThemesGui.svelte | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Logic/Web/AndroidPolyfill.ts b/src/Logic/Web/AndroidPolyfill.ts index 57436c6a2..b92e07b2b 100644 --- a/src/Logic/Web/AndroidPolyfill.ts +++ b/src/Logic/Web/AndroidPolyfill.ts @@ -120,7 +120,7 @@ export class AndroidPolyfill { if (typeof v === "string") { v = JSON.parse(v) } - console.log("Got inset sizes:", result) + console.log("Got inset sizes:", JSON.stringify(result)) insets.bottom.set(v.bottom / window.devicePixelRatio) insets.top.set(v.top / window.devicePixelRatio) }) @@ -186,4 +186,8 @@ export class AndroidPolyfill { } ) } + + static exit() { + this.databridgePlugin.request({key: "exit"}) + } } diff --git a/src/UI/AllThemesGui.svelte b/src/UI/AllThemesGui.svelte index 483898bfa..61a0cdbdb 100644 --- a/src/UI/AllThemesGui.svelte +++ b/src/UI/AllThemesGui.svelte @@ -122,10 +122,16 @@ AndroidPolyfill.onBackButton( () => { + console.log("AllThemesGui received a backbutton from Android") + if(guistate.closeAll()){ + return true + } if (searchIsFocussed.data) { searchIsFocussed.set(false) return true } + // We'll probably want to exit the app + AndroidPolyfill.exit() return false }, { returnToIndex: new ImmutableStore(false) } From 5e6eb9ea95ba3cc1e673ee097a8e22518b7ab118 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Wed, 3 Sep 2025 01:12:37 +0200 Subject: [PATCH 87/92] Chore: bump android version code --- .forgejo/workflows/on_release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.forgejo/workflows/on_release.yml b/.forgejo/workflows/on_release.yml index 8feb81640..e87e9455e 100644 --- a/.forgejo/workflows/on_release.yml +++ b/.forgejo/workflows/on_release.yml @@ -53,7 +53,7 @@ jobs: cd android/app # We assign the version code simply based on the date new_version_code=$(( ( $(date +%s) - $(date -d "2025-07-01" +%s) ) / 86400 )) - new_version_code=$(( 1948 + $new_version_code )) # see https://source.mapcomplete.org/MapComplete/MapComplete/issues/2520 + new_version_code=$(( 1949 + $new_version_code )) # see https://source.mapcomplete.org/MapComplete/MapComplete/issues/2520 versionname="${{ github.ref_name }}" versionname="${versionname:1}" echo "Versioncode will be $new_version_code ; versionname is $versionname" From 946439f8c3e5d9b09267d49f4f3e58722616aa8c Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Wed, 3 Sep 2025 01:12:41 +0200 Subject: [PATCH 88/92] chore(release): 0.55.7 --- CHANGELOG.md | 2 ++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 853927f83..c04831189 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +### [0.55.7](https://source.mapcomplete.org/MapComplete/MapComplete/compare/v0.55.6...v0.55.7) (2025-09-02) + ### [0.55.6](https://source.mapcomplete.org/MapComplete/MapComplete/compare/v0.55.5...v0.55.6) (2025-09-02) diff --git a/package-lock.json b/package-lock.json index a92da2be8..c2bec2fca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "mapcomplete", - "version": "0.55.6", + "version": "0.55.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mapcomplete", - "version": "0.55.6", + "version": "0.55.7", "hasInstallScript": true, "license": "GPL-3.0-or-later", "dependencies": { diff --git a/package.json b/package.json index d1176dd28..03d99f05e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mapcomplete", - "version": "0.55.6", + "version": "0.55.7", "repository": "https://source.mapcomplete.org/MapComplete/MapComplete", "description": "A small website to edit OSM easily", "bugs": "hhttps://source.mapcomplete.org/MapComplete/MapComplete/issues", From 258cbe08022a2c5876849ce18c4054ffa653e8c0 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sat, 6 Sep 2025 22:12:16 +0200 Subject: [PATCH 89/92] Fix: force a space between value and denomination (e.g. `20 mph`), add integration test for this --- src/Logic/Tags/TagTypes.ts | 2 +- src/Logic/Tags/TagUtils.ts | 26 ++++++++------------ src/Models/ThemeConfig/TagRenderingConfig.ts | 23 +++++++++-------- test/integration/UnitHasSpace.spec.ts | 21 ++++++++++++++++ 4 files changed, 43 insertions(+), 29 deletions(-) create mode 100644 test/integration/UnitHasSpace.spec.ts diff --git a/src/Logic/Tags/TagTypes.ts b/src/Logic/Tags/TagTypes.ts index 9e19bc842..98730bb3b 100644 --- a/src/Logic/Tags/TagTypes.ts +++ b/src/Logic/Tags/TagTypes.ts @@ -13,7 +13,7 @@ type Brand = { [__is_optimized]: B } */ export type OptimizedTag = Brand -export type UploadableTag = Tag | SubstitutingTag | And +export type UploadableTag = Tag | SubstitutingTag | And /** * Not nested */ diff --git a/src/Logic/Tags/TagUtils.ts b/src/Logic/Tags/TagUtils.ts index 03f9a7f0a..55b142506 100644 --- a/src/Logic/Tags/TagUtils.ts +++ b/src/Logic/Tags/TagUtils.ts @@ -328,22 +328,6 @@ export class TagUtils { return keyValues } - /** - * Flattens an 'uploadableTag' and replaces all 'SubstitutingTags' into normal tags - */ - static FlattenAnd(tagFilters: UploadableTag, currentProperties: Record): Tag[] { - const tags: Tag[] = [] - tagFilters.visit((tf: UploadableTag) => { - if (tf instanceof Tag) { - tags.push(tf) - } - if (tf instanceof SubstitutingTag) { - tags.push(tf.asTag(currentProperties)) - } - }) - return tags - } - static optimzeJson(json: TagConfigJson): TagConfigJson | boolean { const optimized = TagUtils.Tag(json).optimize() if (optimized === true || optimized === false) { @@ -1024,4 +1008,14 @@ export class TagUtils { } return tag } + + static flattenAnd(tg: UploadableTag | UploadableTag[]): (SubstitutingTag | Tag)[] { + if (Array.isArray(tg)) { + return tg.flatMap(t => TagUtils.flattenAnd(t)) + } + if (tg["and"] || tg instanceof And) { + return tg["and"].flatMap(tg => TagUtils.flattenAnd(tg)) + } + return [tg] + } } diff --git a/src/Models/ThemeConfig/TagRenderingConfig.ts b/src/Models/ThemeConfig/TagRenderingConfig.ts index befa3ff70..29b342ed2 100644 --- a/src/Models/ThemeConfig/TagRenderingConfig.ts +++ b/src/Models/ThemeConfig/TagRenderingConfig.ts @@ -5,10 +5,7 @@ import { TagUtils } from "../../Logic/Tags/TagUtils" import { And } from "../../Logic/Tags/And" import { Utils } from "../../Utils" import { Tag } from "../../Logic/Tags/Tag" -import { - MappingConfigJson, - QuestionableTagRenderingConfigJson, -} from "./Json/QuestionableTagRenderingConfigJson" +import { MappingConfigJson, QuestionableTagRenderingConfigJson } from "./Json/QuestionableTagRenderingConfigJson" import Validators, { ValidatorType } from "../../UI/InputElement/Validators" import { TagRenderingConfigJson } from "./Json/TagRenderingConfigJson" import { RegexTag } from "../../Logic/Tags/RegexTag" @@ -23,6 +20,7 @@ import ComparingTag from "../../Logic/Tags/ComparingTag" import { Unit } from "../Unit" import { Lists } from "../../Utils/Lists" import { IsOnline } from "../../Logic/Web/IsOnline" +import SubstitutingTag from "../../Logic/Tags/SubstitutingTag" export interface Mapping { readonly if: UploadableTag @@ -803,7 +801,7 @@ export default class TagRenderingConfig { multiSelectedMapping: boolean[] | undefined, currentProperties: Record, unit?: Unit - ): UploadableTag[] { + ): (Tag | SubstitutingTag)[] { if (typeof freeformValue === "string") { freeformValue = freeformValue?.trim() } @@ -820,7 +818,8 @@ export default class TagRenderingConfig { valueNoUnit, () => currentProperties["_country"] ) - freeformValue = formatted + denom.canonical + // In general, we want a space between the amount and the unit + freeformValue = formatted + " " + denom.canonical } else { freeformValue = validator.reformat( freeformValue, @@ -865,7 +864,7 @@ export default class TagRenderingConfig { const freeformOnly = { [this.freeform.key]: freeformValue } const matchingMapping = this.mappings?.find((m) => m.if.matchesProperties(freeformOnly)) if (matchingMapping) { - return [matchingMapping.if, ...(matchingMapping.addExtraTags ?? [])] + return [...TagUtils.flattenAnd(matchingMapping.if), ...(matchingMapping.addExtraTags ?? [])] } // Either no mappings, or this is a radio-button selected freeform value const tag = [ @@ -877,7 +876,7 @@ export default class TagRenderingConfig { return undefined } - return tag + return TagUtils.flattenAnd(tag) } if (this.multiAnswer) { @@ -907,7 +906,7 @@ export default class TagRenderingConfig { ) { return undefined } - return and + return TagUtils.flattenAnd(and) } // Is at least one mapping shown in the answer? @@ -927,11 +926,11 @@ export default class TagRenderingConfig { if (useFreeform) { return [ new Tag(this.freeform.key, freeformValue), - ...(this.freeform.addExtraTags ?? []), + ...(TagUtils.flattenAnd(this.freeform.addExtraTags) ?? []) ] } else if (singleSelectedMapping !== undefined) { return [ - this.mappings[singleSelectedMapping].if, + ...TagUtils.flattenAnd(this.mappings[singleSelectedMapping].if), ...(this.mappings[singleSelectedMapping].addExtraTags ?? []), ] } else { @@ -1188,7 +1187,7 @@ export class TagRenderingConfigUtils { console.log("Could not download the NSI: ", extraMappingsErr["error"]) return config } - const extraMappings = extraMappingsErr?.success + const extraMappings = extraMappingsErr?.["success"] if(extraMappings === undefined){ if(!IsOnline.isOnline.data){ // The 'extraMappings' will still attempt to download the NSI - it might be in the service worker's cache diff --git a/test/integration/UnitHasSpace.spec.ts b/test/integration/UnitHasSpace.spec.ts new file mode 100644 index 000000000..91cb8b850 --- /dev/null +++ b/test/integration/UnitHasSpace.spec.ts @@ -0,0 +1,21 @@ +import { describe, it } from "vitest" +import LayerConfig from "../../src/Models/ThemeConfig/LayerConfig" +import maxspeed_layer from "../../public/assets/generated/layers/maxspeed.json" +import { LayerConfigJson } from "../../src/Models/ThemeConfig/Json/LayerConfigJson" +import { expect } from "chai" +import { Tag } from "../../src/Logic/Tags/Tag" + +describe("Integration_unit_has_space", () => { + it("maxspeed should produce '20 mph' and not '20mph'", () => { + const maxspeed = new LayerConfig( + maxspeed_layer, "integration_test" + ) + const maxspeedQuestion = maxspeed.tagRenderings.find(tr => tr.id === "maxspeed-maxspeed") + const unit = maxspeed.units.find(unit => unit.appliesToKeys.has("maxspeed")) + const spec = maxspeedQuestion.constructChangeSpecification("20", undefined, undefined, { + "_country": "gb" + }, unit).find(t => t["key"] === "maxspeed") + expect(spec.asJson()).to.eq(new Tag("maxspeed", "20 mph").asJson()) + }) + +}) From 0d24f231777689488975c8897fc38b0d33b8931c Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sat, 6 Sep 2025 22:12:36 +0200 Subject: [PATCH 90/92] Themes(toilet): add clarification on 'left' and 'right' --- assets/layers/grab_rail/grab_rail.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/assets/layers/grab_rail/grab_rail.json b/assets/layers/grab_rail/grab_rail.json index 5c7d212ed..da6181c8f 100644 --- a/assets/layers/grab_rail/grab_rail.json +++ b/assets/layers/grab_rail/grab_rail.json @@ -19,6 +19,9 @@ "it": "C'è un maniglione?", "nl": "Is er een handgreep of grijpbeugel?" }, + "questionHint": { + "en": "Left and right are interpreted as when sitting on the toilet" + }, "mappings": [ { "if": { @@ -164,6 +167,10 @@ "it": "Il maniglione {{TRANSL}} è pieghevole?", "nl": "Is de grijpbeugel aan de {{TRANSL}}kant opklapbaar?" }, + "questionHint": { + "en": "Left and right are as seen when sitting on the toilet", + "nl": "Links en rechts is wanneer men op de toilet zit" + }, "mappings": [ { "if": "grab_rail:foldable:LOCATION=yes", From 8d678008b76bd3ef48a7dc4b910d666e9ffd06ec Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sat, 6 Sep 2025 23:28:15 +0200 Subject: [PATCH 91/92] Scripts: add script that moves wikimedia-files from image to Wikimedia --- scripts/osm_cleanup/FixWikimediaInImageTag.ts | 95 +++++++++++++++++++ src/Logic/Osm/Overpass.ts | 12 +-- 2 files changed, 101 insertions(+), 6 deletions(-) create mode 100644 scripts/osm_cleanup/FixWikimediaInImageTag.ts diff --git a/scripts/osm_cleanup/FixWikimediaInImageTag.ts b/scripts/osm_cleanup/FixWikimediaInImageTag.ts new file mode 100644 index 000000000..1b93fc6c7 --- /dev/null +++ b/scripts/osm_cleanup/FixWikimediaInImageTag.ts @@ -0,0 +1,95 @@ +import Script from "../Script" +import { Overpass } from "../../src/Logic/Osm/Overpass" +import Constants from "../../src/Models/Constants" +import { BBox } from "../../src/Logic/BBox" +import { RegexTag } from "../../src/Logic/Tags/RegexTag" +import { Tag } from "../../src/Logic/Tags/Tag" + +import { Feature, FeatureCollection } from "geojson" +import { existsSync, readFileSync, writeFileSync } from "fs" +import { Changes } from "../../src/Logic/Osm/Changes" +import ChangeTagAction from "../../src/Logic/Osm/Actions/ChangeTagAction" +import { And } from "../../src/Logic/Tags/And" +import { Lists } from "../../src/Utils/Lists" + +export class FixWikimediaInImageTag extends Script { + + constructor() { + super("For the given bbox, queries all `image=http(s)://commons.wikimedia.org` tags and replaces it with `commons`-tagging") + } + + + private handleFeature(f: Feature): ChangeTagAction { + const p = f.properties + const existingCommons = p["wikimedia_commons"] + const img = p["image"] + const id = p.id + if (!img) { + return undefined + } + + const extractedCommons: string = img.match(/^https?:\/\/commons.wikimedia.org\/wiki\/(.*)$/)[1] + console.log("Feature " + p.id + ": " + img + ", extr " + extractedCommons + ", old: " + existingCommons) + + + if (existingCommons === extractedCommons) { + return new ChangeTagAction(id, + new Tag("image", ""), + p, + { + changeType: "cleanup", + theme: "/" + } + ) + } + if (existingCommons) { + return undefined + } + if (!extractedCommons.startsWith("File:")) { + return undefined + } + return new ChangeTagAction(id, + new And([new Tag("image", ""), new Tag("wikimedia_commons", decodeURIComponent(extractedCommons))]), + p, + { + changeType: "cleanup", + theme: "/" + } + ) + } + + private async fetchData(bbox: BBox): Promise { + const pth = "fix_wikimedia_" + bbox.toLngLatFlat().join("_") + ".geojson" + if (existsSync(pth)) { + return JSON.parse(readFileSync(pth, "utf-8")) + } + const overpass = new Overpass(Constants.defaultOverpassUrls[0], + new RegexTag("image", /https?:\/\/commons.wikimedia.org/) + ) + const [feats] = await overpass.queryGeoJson(bbox) + writeFileSync(pth, JSON.stringify(feats), "utf8") + return feats + } + + async main(args: string[]): Promise { + if (args.length < 1) { + this.printHelp() + //return + } + + const bbox = new BBox([3.632100582083325, + 51.11343904784337, 3.8584183481742116, + 50.99383861993195]) + const feats = await this.fetchData(bbox) + + const actions = Lists.noNull(feats.features.map(f => this.handleFeature(f))) + + const xml = await Changes.createChangesetXMLForJosm(actions) + const pth = "move_image_to_wikimedia_commons_" + bbox.toLngLatFlat().join("_") + ".osc" + writeFileSync(pth, xml, "utf-8") + console.log("Written xml to file://" + pth) + + } +} + +new FixWikimediaInImageTag().run() diff --git a/src/Logic/Osm/Overpass.ts b/src/Logic/Osm/Overpass.ts index 3c9edd61b..c242367fc 100644 --- a/src/Logic/Osm/Overpass.ts +++ b/src/Logic/Osm/Overpass.ts @@ -3,7 +3,7 @@ import { Utils } from "../../Utils" import { ImmutableStore, Store } from "../UIEventSource" import { BBox } from "../BBox" import osmtogeojson from "osmtogeojson" -import { Feature } from "geojson" +import { Feature, FeatureCollection } from "geojson" ("use strict") /** @@ -37,7 +37,7 @@ export class Overpass { this._includeMeta = includeMeta } - public async queryGeoJson(bounds: BBox): Promise<[{features: T[]}, Date]> { + public async queryGeoJson(bounds: BBox): Promise<[{ features: T[] } & FeatureCollection, Date]> { const bbox = "[bbox:" + bounds.getSouth() + @@ -49,16 +49,16 @@ export class Overpass { bounds.getEast() + "]" const query = this.buildScript(bbox) - return await this.ExecuteQuery(query) + return await this.executeQuery(query) } public buildUrl(query: string) { return `${this._interpreterUrl}?data=${encodeURIComponent(query)}` } - private async ExecuteQuery( + private async executeQuery( query: string - ): Promise<[{features: T[]}, Date]> { + ): Promise<[{ features: T[] } & FeatureCollection, Date]> { const json = await Utils.downloadJson<{ elements: [] remark @@ -73,7 +73,7 @@ export class Overpass { console.warn("No features for", this.buildUrl(query)) } - const geojson = <{features: T[]}> osmtogeojson(json) + const geojson = <{ features: T[] } & FeatureCollection>osmtogeojson(json) const osmTime = new Date(json.osm3s.timestamp_osm_base) return [geojson, osmTime] } From 35c42224660a429b10ab340fa933c44010ea9f61 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sat, 6 Sep 2025 23:34:12 +0200 Subject: [PATCH 92/92] Fix: also show wikimedia images if the 'http'-url was used --- src/Logic/ImageProviders/WikimediaImageProvider.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Logic/ImageProviders/WikimediaImageProvider.ts b/src/Logic/ImageProviders/WikimediaImageProvider.ts index ea5db41c7..00f5e6167 100644 --- a/src/Logic/ImageProviders/WikimediaImageProvider.ts +++ b/src/Logic/ImageProviders/WikimediaImageProvider.ts @@ -15,7 +15,9 @@ export class WikimediaImageProvider extends ImageProvider { "https://commons.wikimedia.org/wiki/", "https://upload.wikimedia.org", ] - public static readonly commonsPrefixes = [...WikimediaImageProvider.apiUrls, "File:"] + public static readonly commonsPrefixes = [...WikimediaImageProvider.apiUrls, + "http://commons.wikimedia.org/wiki/", + "http://upload.wikimedia.org", "File:"] private readonly commons_key = "wikimedia_commons" public readonly defaultKeyPrefixes = [this.commons_key, "image"] public readonly name = "Wikimedia"