forked from MapComplete/MapComplete
		
	Merge branch 'develop'
This commit is contained in:
		
						commit
						2f45be975e
					
				
					 77 changed files with 668 additions and 486 deletions
				
			
		
							
								
								
									
										2
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							| 
						 | 
					@ -36,3 +36,5 @@ service-worker.js
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Built Visual Studio Code Extensions
 | 
					# Built Visual Studio Code Extensions
 | 
				
			||||||
*.vsix
 | 
					*.vsix
 | 
				
			||||||
 | 
					public/*.webmanifest
 | 
				
			||||||
 | 
					public/assets/generated/
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,4 @@
 | 
				
			||||||
import * as known_themes from "../assets/generated/known_layers_and_themes.json"
 | 
					import known_themes from "../assets/generated/known_layers_and_themes.json"
 | 
				
			||||||
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"
 | 
					import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"
 | 
				
			||||||
import LayerConfig from "../Models/ThemeConfig/LayerConfig"
 | 
					import LayerConfig from "../Models/ThemeConfig/LayerConfig"
 | 
				
			||||||
import BaseUIElement from "../UI/BaseUIElement"
 | 
					import BaseUIElement from "../UI/BaseUIElement"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
import * as questions from "../assets/tagRenderings/questions.json"
 | 
					import questions from "../assets/tagRenderings/questions.json"
 | 
				
			||||||
import * as icons from "../assets/tagRenderings/icons.json"
 | 
					import icons from "../assets/tagRenderings/icons.json"
 | 
				
			||||||
import { Utils } from "../Utils"
 | 
					import { Utils } from "../Utils"
 | 
				
			||||||
import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig"
 | 
					import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig"
 | 
				
			||||||
import { TagRenderingConfigJson } from "../Models/ThemeConfig/Json/TagRenderingConfigJson"
 | 
					import { TagRenderingConfigJson } from "../Models/ThemeConfig/Json/TagRenderingConfigJson"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -45,13 +45,10 @@ export default class GeoLocationHandler {
 | 
				
			||||||
                (new Date().getTime() - geolocationState.requestMoment.data?.getTime() ?? 0) / 1000
 | 
					                (new Date().getTime() - geolocationState.requestMoment.data?.getTime() ?? 0) / 1000
 | 
				
			||||||
            if (!this.mapHasMoved.data) {
 | 
					            if (!this.mapHasMoved.data) {
 | 
				
			||||||
                // The map hasn't moved yet; we received our first coordinates, so let's move there!
 | 
					                // The map hasn't moved yet; we received our first coordinates, so let's move there!
 | 
				
			||||||
                console.log(
 | 
					                self.MoveMapToCurrentLocation()
 | 
				
			||||||
                    "Moving the map to an initial location; time since last request is",
 | 
					            }
 | 
				
			||||||
                    timeSinceLastRequest
 | 
					            if (timeSinceLastRequest < Constants.zoomToLocationTimeout) {
 | 
				
			||||||
                )
 | 
					                self.MoveMapToCurrentLocation()
 | 
				
			||||||
                if (timeSinceLastRequest < Constants.zoomToLocationTimeout) {
 | 
					 | 
				
			||||||
                    self.MoveMapToCurrentLocation()
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (this.geolocationState.isLocked.data) {
 | 
					            if (this.geolocationState.isLocked.data) {
 | 
				
			||||||
| 
						 | 
					@ -109,11 +106,12 @@ export default class GeoLocationHandler {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        mapLocation.setData({
 | 
					        mapLocation.setData({
 | 
				
			||||||
            zoom: mapLocation.data.zoom,
 | 
					            zoom: Math.max(mapLocation.data.zoom, 16),
 | 
				
			||||||
            lon: newLocation.longitude,
 | 
					            lon: newLocation.longitude,
 | 
				
			||||||
            lat: newLocation.latitude,
 | 
					            lat: newLocation.latitude,
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
        this.mapHasMoved.setData(true)
 | 
					        this.mapHasMoved.setData(true)
 | 
				
			||||||
 | 
					        this.geolocationState.requestMoment.setData(undefined)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private CopyGeolocationIntoMapstate() {
 | 
					    private CopyGeolocationIntoMapstate() {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -90,12 +90,12 @@ export default class SelectedFeatureHandler {
 | 
				
			||||||
            if (feature === undefined) {
 | 
					            if (feature === undefined) {
 | 
				
			||||||
                return
 | 
					                return
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            const currentlySeleced = state.selectedElement.data
 | 
					            const currentlySelected = state.selectedElement.data
 | 
				
			||||||
            if (currentlySeleced === undefined) {
 | 
					            if (currentlySelected === undefined) {
 | 
				
			||||||
                state.selectedElement.setData(feature)
 | 
					                state.selectedElement.setData(feature)
 | 
				
			||||||
                return
 | 
					                return
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            if (currentlySeleced.properties?.id === feature.properties.id) {
 | 
					            if (currentlySelected.properties?.id === feature.properties.id) {
 | 
				
			||||||
                // We already have the right feature
 | 
					                // We already have the right feature
 | 
				
			||||||
                return
 | 
					                return
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,9 +12,9 @@ import LZString from "lz-string"
 | 
				
			||||||
import { FixLegacyTheme } from "../Models/ThemeConfig/Conversion/LegacyJsonConvert"
 | 
					import { FixLegacyTheme } from "../Models/ThemeConfig/Conversion/LegacyJsonConvert"
 | 
				
			||||||
import { LayerConfigJson } from "../Models/ThemeConfig/Json/LayerConfigJson"
 | 
					import { LayerConfigJson } from "../Models/ThemeConfig/Json/LayerConfigJson"
 | 
				
			||||||
import SharedTagRenderings from "../Customizations/SharedTagRenderings"
 | 
					import SharedTagRenderings from "../Customizations/SharedTagRenderings"
 | 
				
			||||||
import * as known_layers from "../assets/generated/known_layers.json"
 | 
					import known_layers from "../assets/generated/known_layers.json"
 | 
				
			||||||
import { PrepareTheme } from "../Models/ThemeConfig/Conversion/PrepareTheme"
 | 
					import { PrepareTheme } from "../Models/ThemeConfig/Conversion/PrepareTheme"
 | 
				
			||||||
import * as licenses from "../assets/generated/license_info.json"
 | 
					import licenses from "../assets/generated/license_info.json"
 | 
				
			||||||
import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig"
 | 
					import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig"
 | 
				
			||||||
import { FixImages } from "../Models/ThemeConfig/Conversion/FixImages"
 | 
					import { FixImages } from "../Models/ThemeConfig/Conversion/FixImages"
 | 
				
			||||||
import Svg from "../Svg"
 | 
					import Svg from "../Svg"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -234,7 +234,6 @@ export default class MetaTagging {
 | 
				
			||||||
                for (const f of functions) {
 | 
					                for (const f of functions) {
 | 
				
			||||||
                    f(feature)
 | 
					                    f(feature)
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                state?.allElements?.getEventSourceById(feature.properties.id)?.ping()
 | 
					 | 
				
			||||||
            } catch (e) {
 | 
					            } catch (e) {
 | 
				
			||||||
                console.error("Invalid syntax in calculated tags or some other error: ", e)
 | 
					                console.error("Invalid syntax in calculated tags or some other error: ", e)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
import { Utils } from "../../Utils"
 | 
					import { Utils } from "../../Utils"
 | 
				
			||||||
import * as polygon_features from "../../assets/polygon-features.json"
 | 
					import polygon_features from "../../assets/polygon-features.json"
 | 
				
			||||||
import { Store, UIEventSource } from "../UIEventSource"
 | 
					import { Store, UIEventSource } from "../UIEventSource"
 | 
				
			||||||
import { BBox } from "../BBox"
 | 
					import { BBox } from "../BBox"
 | 
				
			||||||
import OsmToGeoJson from "osmtogeojson"
 | 
					import OsmToGeoJson from "osmtogeojson"
 | 
				
			||||||
| 
						 | 
					@ -290,7 +290,7 @@ export abstract class OsmObject {
 | 
				
			||||||
        { values: Set<string>; blacklist: boolean }
 | 
					        { values: Set<string>; blacklist: boolean }
 | 
				
			||||||
    > {
 | 
					    > {
 | 
				
			||||||
        const result = new Map<string, { values: Set<string>; blacklist: boolean }>()
 | 
					        const result = new Map<string, { values: Set<string>; blacklist: boolean }>()
 | 
				
			||||||
        for (const polygonFeature of polygon_features["default"] ?? polygon_features) {
 | 
					        for (const polygonFeature of polygon_features) {
 | 
				
			||||||
            const key = polygonFeature.key
 | 
					            const key = polygonFeature.key
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (polygonFeature.polygon === "all") {
 | 
					            if (polygonFeature.polygon === "all") {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -33,6 +33,10 @@ export class SimpleMetaTagger {
 | 
				
			||||||
        docs: {
 | 
					        docs: {
 | 
				
			||||||
            keys: string[]
 | 
					            keys: string[]
 | 
				
			||||||
            doc: string
 | 
					            doc: string
 | 
				
			||||||
 | 
					            /**
 | 
				
			||||||
 | 
					             * Set this flag if the data is volatile or date-based.
 | 
				
			||||||
 | 
					             * It'll _won't_ be cached in this case
 | 
				
			||||||
 | 
					             */
 | 
				
			||||||
            includesDates?: boolean
 | 
					            includesDates?: boolean
 | 
				
			||||||
            isLazy?: boolean
 | 
					            isLazy?: boolean
 | 
				
			||||||
            cleanupRetagger?: boolean
 | 
					            cleanupRetagger?: boolean
 | 
				
			||||||
| 
						 | 
					@ -492,6 +496,7 @@ export default class SimpleMetaTaggers {
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            keys: ["_referencing_ways"],
 | 
					            keys: ["_referencing_ways"],
 | 
				
			||||||
            isLazy: true,
 | 
					            isLazy: true,
 | 
				
			||||||
 | 
					            includesDates: true,
 | 
				
			||||||
            doc: "_referencing_ways contains - for a node - which ways use this this node as point in their geometry. ",
 | 
					            doc: "_referencing_ways contains - for a node - which ways use this this node as point in their geometry. ",
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        (feature, _, __, state) => {
 | 
					        (feature, _, __, state) => {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,6 @@
 | 
				
			||||||
import { UIEventSource } from "../UIEventSource"
 | 
					import { UIEventSource } from "../UIEventSource"
 | 
				
			||||||
import { LocalStorageSource } from "../Web/LocalStorageSource"
 | 
					import { LocalStorageSource } from "../Web/LocalStorageSource"
 | 
				
			||||||
 | 
					import { QueryParameters } from "../Web/QueryParameters"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type GeolocationState = "prompt" | "requested" | "granted" | "denied"
 | 
					type GeolocationState = "prompt" | "requested" | "granted" | "denied"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,8 +12,6 @@ export interface GeoLocationPointProperties extends GeolocationCoordinates {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * An abstract representation of the current state of the geolocation.
 | 
					 * An abstract representation of the current state of the geolocation.
 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export class GeoLocationState {
 | 
					export class GeoLocationState {
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
| 
						 | 
					@ -21,10 +20,12 @@ export class GeoLocationState {
 | 
				
			||||||
     * 'requested' means the user tapped the 'locate me' button at least once
 | 
					     * 'requested' means the user tapped the 'locate me' button at least once
 | 
				
			||||||
     * 'granted' means that it is granted
 | 
					     * 'granted' means that it is granted
 | 
				
			||||||
     * 'denied' means that we don't have access
 | 
					     * 'denied' means that we don't have access
 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public readonly permission: UIEventSource<GeolocationState> = new UIEventSource("prompt")
 | 
					    public readonly permission: UIEventSource<GeolocationState> = new UIEventSource("prompt")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Important to determine e.g. if we move automatically on fix or not
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
    public readonly requestMoment: UIEventSource<Date | undefined> = new UIEventSource(undefined)
 | 
					    public readonly requestMoment: UIEventSource<Date | undefined> = new UIEventSource(undefined)
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * If true: the map will center (and re-center) to this location
 | 
					     * If true: the map will center (and re-center) to this location
 | 
				
			||||||
| 
						 | 
					@ -79,6 +80,11 @@ export class GeoLocationState {
 | 
				
			||||||
            // We set the flag to false again. If the user only wanted to share their location once, we are not gonna keep bothering them
 | 
					            // We set the flag to false again. If the user only wanted to share their location once, we are not gonna keep bothering them
 | 
				
			||||||
            this._previousLocationGrant.setData("false")
 | 
					            this._previousLocationGrant.setData("false")
 | 
				
			||||||
            console.log("Requesting access to GPS as this was previously granted")
 | 
					            console.log("Requesting access to GPS as this was previously granted")
 | 
				
			||||||
 | 
					            const latLonGivenViaUrl =
 | 
				
			||||||
 | 
					                QueryParameters.wasInitialized("lat") || QueryParameters.wasInitialized("lon")
 | 
				
			||||||
 | 
					            if (!latLonGivenViaUrl) {
 | 
				
			||||||
 | 
					                this.requestMoment.setData(new Date())
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            this.requestPermission()
 | 
					            this.requestPermission()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        window["geolocation_state"] = this
 | 
					        window["geolocation_state"] = this
 | 
				
			||||||
| 
						 | 
					@ -120,7 +126,7 @@ export class GeoLocationState {
 | 
				
			||||||
            // Hence that we continue the flow if it is "requested"
 | 
					            // Hence that we continue the flow if it is "requested"
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        this.requestMoment.setData(new Date())
 | 
					
 | 
				
			||||||
        this.permission.setData("requested")
 | 
					        this.permission.setData("requested")
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            navigator?.permissions
 | 
					            navigator?.permissions
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
 | 
					import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
 | 
				
			||||||
import { OsmConnection } from "../Osm/OsmConnection"
 | 
					import { OsmConnection } from "../Osm/OsmConnection"
 | 
				
			||||||
import { MangroveIdentity } from "../Web/MangroveReviews"
 | 
					import { MangroveIdentity } from "../Web/MangroveReviews"
 | 
				
			||||||
import { Store } from "../UIEventSource"
 | 
					import { Store, UIEventSource } from "../UIEventSource"
 | 
				
			||||||
import { QueryParameters } from "../Web/QueryParameters"
 | 
					import { QueryParameters } from "../Web/QueryParameters"
 | 
				
			||||||
import Locale from "../../UI/i18n/Locale"
 | 
					import Locale from "../../UI/i18n/Locale"
 | 
				
			||||||
import ElementsState from "./ElementsState"
 | 
					import ElementsState from "./ElementsState"
 | 
				
			||||||
| 
						 | 
					@ -9,7 +9,6 @@ import SelectedElementTagsUpdater from "../Actors/SelectedElementTagsUpdater"
 | 
				
			||||||
import { Changes } from "../Osm/Changes"
 | 
					import { Changes } from "../Osm/Changes"
 | 
				
			||||||
import ChangeToElementsActor from "../Actors/ChangeToElementsActor"
 | 
					import ChangeToElementsActor from "../Actors/ChangeToElementsActor"
 | 
				
			||||||
import PendingChangesUploader from "../Actors/PendingChangesUploader"
 | 
					import PendingChangesUploader from "../Actors/PendingChangesUploader"
 | 
				
			||||||
import * as translators from "../../assets/translators.json"
 | 
					 | 
				
			||||||
import Maproulette from "../Maproulette"
 | 
					import Maproulette from "../Maproulette"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
| 
						 | 
					@ -53,29 +52,25 @@ export default class UserRelatedState extends ElementsState {
 | 
				
			||||||
            osmConfiguration: <"osm" | "osm-test">this.featureSwitchApiURL.data,
 | 
					            osmConfiguration: <"osm" | "osm-test">this.featureSwitchApiURL.data,
 | 
				
			||||||
            attemptLogin: options?.attemptLogin,
 | 
					            attemptLogin: options?.attemptLogin,
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
        const translationMode = this.osmConnection.GetPreference("translation-mode").sync(
 | 
					        {
 | 
				
			||||||
            (str) => (str === undefined ? undefined : str === "true"),
 | 
					            const translationMode: UIEventSource<undefined | "true" | "false" | "mobile" | string> =
 | 
				
			||||||
            [],
 | 
					                this.osmConnection.GetPreference("translation-mode")
 | 
				
			||||||
            (b) => (b === undefined ? undefined : b + "")
 | 
					            translationMode.addCallbackAndRunD((mode) => {
 | 
				
			||||||
        )
 | 
					                mode = mode.toLowerCase()
 | 
				
			||||||
 | 
					                if (mode === "true" || mode === "yes") {
 | 
				
			||||||
        translationMode.syncWith(Locale.showLinkToWeblate)
 | 
					                    Locale.showLinkOnMobile.setData(false)
 | 
				
			||||||
 | 
					                    Locale.showLinkToWeblate.setData(true)
 | 
				
			||||||
        this.isTranslator = this.osmConnection.userDetails.map((ud) => {
 | 
					                } else if (mode === "false" || mode === "no") {
 | 
				
			||||||
            if (!ud.loggedIn) {
 | 
					                    Locale.showLinkToWeblate.setData(false)
 | 
				
			||||||
                return false
 | 
					                } else if (mode === "mobile") {
 | 
				
			||||||
            }
 | 
					                    Locale.showLinkOnMobile.setData(true)
 | 
				
			||||||
            const name = ud.name.toLowerCase().replace(/\s+/g, "")
 | 
					                    Locale.showLinkToWeblate.setData(true)
 | 
				
			||||||
            return translators.contributors.some(
 | 
					                } else {
 | 
				
			||||||
                (c) => c.contributor.toLowerCase().replace(/\s+/g, "") === name
 | 
					                    Locale.showLinkOnMobile.setData(false)
 | 
				
			||||||
            )
 | 
					                    Locale.showLinkToWeblate.setData(false)
 | 
				
			||||||
        })
 | 
					                }
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
        this.isTranslator.addCallbackAndRunD((ud) => {
 | 
					        }
 | 
				
			||||||
            if (ud) {
 | 
					 | 
				
			||||||
                Locale.showLinkToWeblate.setData(true)
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.changes = new Changes(this, layoutToUse?.isLeftRightSensitive() ?? false)
 | 
					        this.changes = new Changes(this, layoutToUse?.isLeftRightSensitive() ?? false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -117,41 +112,6 @@ export default class UserRelatedState extends ElementsState {
 | 
				
			||||||
        this.installedUserThemes = this.InitInstalledUserThemes()
 | 
					        this.installedUserThemes = this.InitInstalledUserThemes()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private InitializeLanguage() {
 | 
					 | 
				
			||||||
        const layoutToUse = this.layoutToUse
 | 
					 | 
				
			||||||
        Locale.language.syncWith(this.osmConnection.GetPreference("language"))
 | 
					 | 
				
			||||||
        Locale.language.addCallback((currentLanguage) => {
 | 
					 | 
				
			||||||
            if (layoutToUse === undefined) {
 | 
					 | 
				
			||||||
                return
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            if (Locale.showLinkToWeblate.data) {
 | 
					 | 
				
			||||||
                return true // Disable auto switching as we are in translators mode
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            if (this.layoutToUse.language.indexOf(currentLanguage) < 0) {
 | 
					 | 
				
			||||||
                console.log(
 | 
					 | 
				
			||||||
                    "Resetting language to",
 | 
					 | 
				
			||||||
                    layoutToUse.language[0],
 | 
					 | 
				
			||||||
                    "as",
 | 
					 | 
				
			||||||
                    currentLanguage,
 | 
					 | 
				
			||||||
                    " is unsupported"
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                // The current language is not supported -> switch to a supported one
 | 
					 | 
				
			||||||
                Locale.language.setData(layoutToUse.language[0])
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
        Locale.language.ping()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private InitInstalledUserThemes(): Store<string[]> {
 | 
					 | 
				
			||||||
        const prefix = "mapcomplete-unofficial-theme-"
 | 
					 | 
				
			||||||
        const postfix = "-combined-length"
 | 
					 | 
				
			||||||
        return this.osmConnection.preferencesHandler.preferences.map((prefs) =>
 | 
					 | 
				
			||||||
            Object.keys(prefs)
 | 
					 | 
				
			||||||
                .filter((k) => k.startsWith(prefix) && k.endsWith(postfix))
 | 
					 | 
				
			||||||
                .map((k) => k.substring(prefix.length, k.length - postfix.length))
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public GetUnofficialTheme(id: string):
 | 
					    public GetUnofficialTheme(id: string):
 | 
				
			||||||
        | {
 | 
					        | {
 | 
				
			||||||
              id: string
 | 
					              id: string
 | 
				
			||||||
| 
						 | 
					@ -193,4 +153,39 @@ export default class UserRelatedState extends ElementsState {
 | 
				
			||||||
            return undefined
 | 
					            return undefined
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private InitializeLanguage() {
 | 
				
			||||||
 | 
					        const layoutToUse = this.layoutToUse
 | 
				
			||||||
 | 
					        Locale.language.syncWith(this.osmConnection.GetPreference("language"))
 | 
				
			||||||
 | 
					        Locale.language.addCallback((currentLanguage) => {
 | 
				
			||||||
 | 
					            if (layoutToUse === undefined) {
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (Locale.showLinkToWeblate.data) {
 | 
				
			||||||
 | 
					                return true // Disable auto switching as we are in translators mode
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (this.layoutToUse.language.indexOf(currentLanguage) < 0) {
 | 
				
			||||||
 | 
					                console.log(
 | 
				
			||||||
 | 
					                    "Resetting language to",
 | 
				
			||||||
 | 
					                    layoutToUse.language[0],
 | 
				
			||||||
 | 
					                    "as",
 | 
				
			||||||
 | 
					                    currentLanguage,
 | 
				
			||||||
 | 
					                    " is unsupported"
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                // The current language is not supported -> switch to a supported one
 | 
				
			||||||
 | 
					                Locale.language.setData(layoutToUse.language[0])
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        Locale.language.ping()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private InitInstalledUserThemes(): Store<string[]> {
 | 
				
			||||||
 | 
					        const prefix = "mapcomplete-unofficial-theme-"
 | 
				
			||||||
 | 
					        const postfix = "-combined-length"
 | 
				
			||||||
 | 
					        return this.osmConnection.preferencesHandler.preferences.map((prefs) =>
 | 
				
			||||||
 | 
					            Object.keys(prefs)
 | 
				
			||||||
 | 
					                .filter((k) => k.startsWith(prefix) && k.endsWith(postfix))
 | 
				
			||||||
 | 
					                .map((k) => k.substring(prefix.length, k.length - postfix.length))
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,13 +7,13 @@ import { RegexTag } from "./RegexTag"
 | 
				
			||||||
import SubstitutingTag from "./SubstitutingTag"
 | 
					import SubstitutingTag from "./SubstitutingTag"
 | 
				
			||||||
import { Or } from "./Or"
 | 
					import { Or } from "./Or"
 | 
				
			||||||
import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson"
 | 
					import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson"
 | 
				
			||||||
import * as key_counts from "../../assets/key_totals.json"
 | 
					import key_counts from "../../assets/key_totals.json"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Tags = Record<string, string>
 | 
					type Tags = Record<string, string>
 | 
				
			||||||
export type UploadableTag = Tag | SubstitutingTag | And
 | 
					export type UploadableTag = Tag | SubstitutingTag | And
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class TagUtils {
 | 
					export class TagUtils {
 | 
				
			||||||
    private static keyCounts: { keys: any; tags: any } = key_counts["default"] ?? key_counts
 | 
					    private static keyCounts: { keys: any; tags: any } = key_counts
 | 
				
			||||||
    private static comparators: [string, (a: number, b: number) => boolean][] = [
 | 
					    private static comparators: [string, (a: number, b: number) => boolean][] = [
 | 
				
			||||||
        ["<=", (a, b) => a <= b],
 | 
					        ["<=", (a, b) => a <= b],
 | 
				
			||||||
        [">=", (a, b) => a >= b],
 | 
					        [">=", (a, b) => a >= b],
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -106,7 +106,7 @@ export default class Constants {
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * In seconds
 | 
					     * In seconds
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    static zoomToLocationTimeout = 60
 | 
					    static zoomToLocationTimeout = 15
 | 
				
			||||||
    static countryCoderEndpoint: string =
 | 
					    static countryCoderEndpoint: string =
 | 
				
			||||||
        "https://raw.githubusercontent.com/pietervdvn/MapComplete-data/main/latlon2country"
 | 
					        "https://raw.githubusercontent.com/pietervdvn/MapComplete-data/main/latlon2country"
 | 
				
			||||||
    public static readonly OsmPreferenceKeyPicturesLicense = "pictures-license"
 | 
					    public static readonly OsmPreferenceKeyPicturesLicense = "pictures-license"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,7 +31,7 @@ export class Denomination {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.alternativeDenominations = json.alternativeDenomination?.map((v) => v.trim()) ?? []
 | 
					        this.alternativeDenominations = json.alternativeDenomination?.map((v) => v.trim()) ?? []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (json["default"] !== undefined) {
 | 
					        if (json["default" /* @code-quality: ignore*/] !== undefined) {
 | 
				
			||||||
            throw `${context} uses the old 'default'-key. Use "useIfNoUnitGiven" or "useAsDefaultInput" instead`
 | 
					            throw `${context} uses the old 'default'-key. Use "useIfNoUnitGiven" or "useAsDefaultInput" instead`
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        this.useIfNoUnitGiven = json.useIfNoUnitGiven
 | 
					        this.useIfNoUnitGiven = json.useIfNoUnitGiven
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,21 +1,20 @@
 | 
				
			||||||
import { Conversion, DesugaringStep } from "./Conversion"
 | 
					import { Conversion, DesugaringStep } from "./Conversion"
 | 
				
			||||||
import { LayoutConfigJson } from "../Json/LayoutConfigJson"
 | 
					import { LayoutConfigJson } from "../Json/LayoutConfigJson"
 | 
				
			||||||
import { Utils } from "../../../Utils"
 | 
					import { Utils } from "../../../Utils"
 | 
				
			||||||
import * as metapaths from "../../../assets/layoutconfigmeta.json"
 | 
					import metapaths from "../../../assets/layoutconfigmeta.json"
 | 
				
			||||||
import * as tagrenderingmetapaths from "../../../assets/questionabletagrenderingconfigmeta.json"
 | 
					import tagrenderingmetapaths from "../../../assets/questionabletagrenderingconfigmeta.json"
 | 
				
			||||||
import Translations from "../../../UI/i18n/Translations"
 | 
					import Translations from "../../../UI/i18n/Translations"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class ExtractImages extends Conversion<LayoutConfigJson, string[]> {
 | 
					export class ExtractImages extends Conversion<LayoutConfigJson, string[]> {
 | 
				
			||||||
    private _isOfficial: boolean
 | 
					    private _isOfficial: boolean
 | 
				
			||||||
    private _sharedTagRenderings: Map<string, any>
 | 
					    private _sharedTagRenderings: Map<string, any>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static readonly layoutMetaPaths = (metapaths["default"] ?? metapaths).filter(
 | 
					    private static readonly layoutMetaPaths = metapaths.filter(
 | 
				
			||||||
        (mp) =>
 | 
					        (mp) =>
 | 
				
			||||||
            ExtractImages.mightBeTagRendering(mp) ||
 | 
					            ExtractImages.mightBeTagRendering(<any>mp) ||
 | 
				
			||||||
            (mp.typeHint !== undefined && (mp.typeHint === "image" || mp.typeHint === "icon"))
 | 
					            (mp.typeHint !== undefined && (mp.typeHint === "image" || mp.typeHint === "icon"))
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    private static readonly tagRenderingMetaPaths =
 | 
					    private static readonly tagRenderingMetaPaths = tagrenderingmetapaths
 | 
				
			||||||
        tagrenderingmetapaths["default"] ?? tagrenderingmetapaths
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(isOfficial: boolean, sharedTagRenderings: Map<string, any>) {
 | 
					    constructor(isOfficial: boolean, sharedTagRenderings: Map<string, any>) {
 | 
				
			||||||
        super("Extract all images from a layoutConfig using the meta paths.", [], "ExctractImages")
 | 
					        super("Extract all images from a layoutConfig using the meta paths.", [], "ExctractImages")
 | 
				
			||||||
| 
						 | 
					@ -23,14 +22,16 @@ export class ExtractImages extends Conversion<LayoutConfigJson, string[]> {
 | 
				
			||||||
        this._sharedTagRenderings = sharedTagRenderings
 | 
					        this._sharedTagRenderings = sharedTagRenderings
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static mightBeTagRendering(metapath: { type: string | string[] }): boolean {
 | 
					    public static mightBeTagRendering(metapath: { type?: string | string[] }): boolean {
 | 
				
			||||||
        if (!Array.isArray(metapath.type)) {
 | 
					        if (!Array.isArray(metapath.type)) {
 | 
				
			||||||
            return false
 | 
					            return false
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return metapath.type.some(
 | 
					        return (
 | 
				
			||||||
            (t) =>
 | 
					            metapath.type?.some(
 | 
				
			||||||
                t["$ref"] == "#/definitions/TagRenderingConfigJson" ||
 | 
					                (t) =>
 | 
				
			||||||
                t["$ref"] == "#/definitions/QuestionableTagRenderingConfigJson"
 | 
					                    t["$ref"] == "#/definitions/TagRenderingConfigJson" ||
 | 
				
			||||||
 | 
					                    t["$ref"] == "#/definitions/QuestionableTagRenderingConfigJson"
 | 
				
			||||||
 | 
					            ) ?? false
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -83,7 +84,7 @@ export class ExtractImages extends Conversion<LayoutConfigJson, string[]> {
 | 
				
			||||||
        const errors = []
 | 
					        const errors = []
 | 
				
			||||||
        const warnings = []
 | 
					        const warnings = []
 | 
				
			||||||
        for (const metapath of ExtractImages.layoutMetaPaths) {
 | 
					        for (const metapath of ExtractImages.layoutMetaPaths) {
 | 
				
			||||||
            const mightBeTr = ExtractImages.mightBeTagRendering(metapath)
 | 
					            const mightBeTr = ExtractImages.mightBeTagRendering(<any>metapath)
 | 
				
			||||||
            const allRenderedValuesAreImages =
 | 
					            const allRenderedValuesAreImages =
 | 
				
			||||||
                metapath.typeHint === "icon" || metapath.typeHint === "image"
 | 
					                metapath.typeHint === "icon" || metapath.typeHint === "image"
 | 
				
			||||||
            const found = Utils.CollectPath(metapath.path, json)
 | 
					            const found = Utils.CollectPath(metapath.path, json)
 | 
				
			||||||
| 
						 | 
					@ -271,14 +272,11 @@ export class FixImages extends DesugaringStep<LayoutConfigJson> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        json = Utils.Clone(json)
 | 
					        json = Utils.Clone(json)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let paths = metapaths["default"] ?? metapaths
 | 
					        for (const metapath of metapaths) {
 | 
				
			||||||
        let trpaths = tagrenderingmetapaths["default"] ?? tagrenderingmetapaths
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for (const metapath of paths) {
 | 
					 | 
				
			||||||
            if (metapath.typeHint !== "image" && metapath.typeHint !== "icon") {
 | 
					            if (metapath.typeHint !== "image" && metapath.typeHint !== "icon") {
 | 
				
			||||||
                continue
 | 
					                continue
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            const mightBeTr = ExtractImages.mightBeTagRendering(metapath)
 | 
					            const mightBeTr = ExtractImages.mightBeTagRendering(<any>metapath)
 | 
				
			||||||
            Utils.WalkPath(metapath.path, json, (leaf, path) => {
 | 
					            Utils.WalkPath(metapath.path, json, (leaf, path) => {
 | 
				
			||||||
                if (typeof leaf === "string") {
 | 
					                if (typeof leaf === "string") {
 | 
				
			||||||
                    return replaceString(leaf)
 | 
					                    return replaceString(leaf)
 | 
				
			||||||
| 
						 | 
					@ -287,7 +285,7 @@ export class FixImages extends DesugaringStep<LayoutConfigJson> {
 | 
				
			||||||
                if (mightBeTr) {
 | 
					                if (mightBeTr) {
 | 
				
			||||||
                    // We might have reached a tagRenderingConfig containing icons
 | 
					                    // We might have reached a tagRenderingConfig containing icons
 | 
				
			||||||
                    // lets walk every rendered value and fix the images in there
 | 
					                    // lets walk every rendered value and fix the images in there
 | 
				
			||||||
                    for (const trpath of trpaths) {
 | 
					                    for (const trpath of tagrenderingmetapaths) {
 | 
				
			||||||
                        if (trpath.typeHint !== "rendered") {
 | 
					                        if (trpath.typeHint !== "rendered") {
 | 
				
			||||||
                            continue
 | 
					                            continue
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,10 +16,10 @@ import RewritableConfigJson from "../Json/RewritableConfigJson"
 | 
				
			||||||
import SpecialVisualizations from "../../../UI/SpecialVisualizations"
 | 
					import SpecialVisualizations from "../../../UI/SpecialVisualizations"
 | 
				
			||||||
import Translations from "../../../UI/i18n/Translations"
 | 
					import Translations from "../../../UI/i18n/Translations"
 | 
				
			||||||
import { Translation } from "../../../UI/i18n/Translation"
 | 
					import { Translation } from "../../../UI/i18n/Translation"
 | 
				
			||||||
import * as tagrenderingconfigmeta from "../../../assets/tagrenderingconfigmeta.json"
 | 
					import tagrenderingconfigmeta from "../../../assets/tagrenderingconfigmeta.json"
 | 
				
			||||||
import { AddContextToTranslations } from "./AddContextToTranslations"
 | 
					import { AddContextToTranslations } from "./AddContextToTranslations"
 | 
				
			||||||
import FilterConfigJson from "../Json/FilterConfigJson"
 | 
					import FilterConfigJson from "../Json/FilterConfigJson"
 | 
				
			||||||
import * as predifined_filters from "../../../assets/layers/filters/filters.json"
 | 
					import predifined_filters from "../../../assets/layers/filters/filters.json"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ExpandFilter extends DesugaringStep<LayerConfigJson> {
 | 
					class ExpandFilter extends DesugaringStep<LayerConfigJson> {
 | 
				
			||||||
    private static load_filters(): Map<string, FilterConfigJson> {
 | 
					    private static load_filters(): Map<string, FilterConfigJson> {
 | 
				
			||||||
| 
						 | 
					@ -730,8 +730,7 @@ export class RewriteSpecial extends DesugaringStep<TagRenderingConfigJson> {
 | 
				
			||||||
    } {
 | 
					    } {
 | 
				
			||||||
        const errors = []
 | 
					        const errors = []
 | 
				
			||||||
        json = Utils.Clone(json)
 | 
					        json = Utils.Clone(json)
 | 
				
			||||||
        const paths: { path: string[]; type?: any; typeHint?: string }[] =
 | 
					        const paths: { path: string[]; type?: any; typeHint?: string }[] = tagrenderingconfigmeta
 | 
				
			||||||
            tagrenderingconfigmeta["default"] ?? tagrenderingconfigmeta
 | 
					 | 
				
			||||||
        for (const path of paths) {
 | 
					        for (const path of paths) {
 | 
				
			||||||
            if (path.typeHint !== "rendered") {
 | 
					            if (path.typeHint !== "rendered") {
 | 
				
			||||||
                continue
 | 
					                continue
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,11 +9,9 @@ import LayoutConfig from "../LayoutConfig"
 | 
				
			||||||
import { TagRenderingConfigJson } from "../Json/TagRenderingConfigJson"
 | 
					import { TagRenderingConfigJson } from "../Json/TagRenderingConfigJson"
 | 
				
			||||||
import { TagUtils } from "../../../Logic/Tags/TagUtils"
 | 
					import { TagUtils } from "../../../Logic/Tags/TagUtils"
 | 
				
			||||||
import { ExtractImages } from "./FixImages"
 | 
					import { ExtractImages } from "./FixImages"
 | 
				
			||||||
import ScriptUtils from "../../../scripts/ScriptUtils"
 | 
					 | 
				
			||||||
import { And } from "../../../Logic/Tags/And"
 | 
					import { And } from "../../../Logic/Tags/And"
 | 
				
			||||||
import Translations from "../../../UI/i18n/Translations"
 | 
					import Translations from "../../../UI/i18n/Translations"
 | 
				
			||||||
import Svg from "../../../Svg"
 | 
					import Svg from "../../../Svg"
 | 
				
			||||||
import { QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson"
 | 
					 | 
				
			||||||
import FilterConfigJson from "../Json/FilterConfigJson"
 | 
					import FilterConfigJson from "../Json/FilterConfigJson"
 | 
				
			||||||
import DeleteConfig from "../DeleteConfig"
 | 
					import DeleteConfig from "../DeleteConfig"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -365,7 +363,7 @@ export class PrevalidateTheme extends Fuse<LayoutConfigJson> {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class DetectShadowedMappings extends DesugaringStep<QuestionableTagRenderingConfigJson> {
 | 
					export class DetectShadowedMappings extends DesugaringStep<TagRenderingConfigJson> {
 | 
				
			||||||
    private readonly _calculatedTagNames: string[]
 | 
					    private readonly _calculatedTagNames: string[]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(layerConfig?: LayerConfigJson) {
 | 
					    constructor(layerConfig?: LayerConfigJson) {
 | 
				
			||||||
| 
						 | 
					@ -425,9 +423,9 @@ export class DetectShadowedMappings extends DesugaringStep<QuestionableTagRender
 | 
				
			||||||
     * r.errors[0].indexOf("The mapping key=value&x=y is fully matched by a previous mapping (namely 0)") >= 0 // => true
 | 
					     * r.errors[0].indexOf("The mapping key=value&x=y is fully matched by a previous mapping (namely 0)") >= 0 // => true
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    convert(
 | 
					    convert(
 | 
				
			||||||
        json: QuestionableTagRenderingConfigJson,
 | 
					        json: TagRenderingConfigJson,
 | 
				
			||||||
        context: string
 | 
					        context: string
 | 
				
			||||||
    ): { result: QuestionableTagRenderingConfigJson; errors?: string[]; warnings?: string[] } {
 | 
					    ): { result: TagRenderingConfigJson; errors?: string[]; warnings?: string[] } {
 | 
				
			||||||
        const errors = []
 | 
					        const errors = []
 | 
				
			||||||
        const warnings = []
 | 
					        const warnings = []
 | 
				
			||||||
        if (json.mappings === undefined || json.mappings.length === 0) {
 | 
					        if (json.mappings === undefined || json.mappings.length === 0) {
 | 
				
			||||||
| 
						 | 
					@ -441,12 +439,9 @@ export class DetectShadowedMappings extends DesugaringStep<QuestionableTagRender
 | 
				
			||||||
        const parsedConditions = json.mappings.map((m, i) => {
 | 
					        const parsedConditions = json.mappings.map((m, i) => {
 | 
				
			||||||
            const ctx = `${context}.mappings[${i}]`
 | 
					            const ctx = `${context}.mappings[${i}]`
 | 
				
			||||||
            const ifTags = TagUtils.Tag(m.if, ctx)
 | 
					            const ifTags = TagUtils.Tag(m.if, ctx)
 | 
				
			||||||
            if (
 | 
					            const hideInAnswer = m["hideInAnswer"]
 | 
				
			||||||
                m.hideInAnswer !== undefined &&
 | 
					            if (hideInAnswer !== undefined && hideInAnswer !== false && hideInAnswer !== true) {
 | 
				
			||||||
                m.hideInAnswer !== false &&
 | 
					                let conditionTags = TagUtils.Tag(hideInAnswer)
 | 
				
			||||||
                m.hideInAnswer !== true
 | 
					 | 
				
			||||||
            ) {
 | 
					 | 
				
			||||||
                let conditionTags = TagUtils.Tag(m.hideInAnswer)
 | 
					 | 
				
			||||||
                // Merge the condition too!
 | 
					                // Merge the condition too!
 | 
				
			||||||
                return new And([conditionTags, ifTags])
 | 
					                return new And([conditionTags, ifTags])
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
| 
						 | 
					@ -467,8 +462,8 @@ export class DetectShadowedMappings extends DesugaringStep<QuestionableTagRender
 | 
				
			||||||
                const doesMatch = parsedConditions[j].matchesProperties(properties)
 | 
					                const doesMatch = parsedConditions[j].matchesProperties(properties)
 | 
				
			||||||
                if (
 | 
					                if (
 | 
				
			||||||
                    doesMatch &&
 | 
					                    doesMatch &&
 | 
				
			||||||
                    json.mappings[j].hideInAnswer === true &&
 | 
					                    json.mappings[j]["hideInAnswer"] === true &&
 | 
				
			||||||
                    json.mappings[i].hideInAnswer !== true
 | 
					                    json.mappings[i]["hideInAnswer"] !== true
 | 
				
			||||||
                ) {
 | 
					                ) {
 | 
				
			||||||
                    warnings.push(
 | 
					                    warnings.push(
 | 
				
			||||||
                        `At ${context}: Mapping ${i} is shadowed by mapping ${j}. However, mapping ${j} has 'hideInAnswer' set, which will result in a different rendering in question-mode.`
 | 
					                        `At ${context}: Mapping ${i} is shadowed by mapping ${j}. However, mapping ${j} has 'hideInAnswer' set, which will result in a different rendering in question-mode.`
 | 
				
			||||||
| 
						 | 
					@ -623,7 +618,8 @@ export class ValidateTagRenderings extends Fuse<TagRenderingConfigJson> {
 | 
				
			||||||
        super(
 | 
					        super(
 | 
				
			||||||
            "Various validation on tagRenderingConfigs",
 | 
					            "Various validation on tagRenderingConfigs",
 | 
				
			||||||
            new DetectShadowedMappings(layerConfig),
 | 
					            new DetectShadowedMappings(layerConfig),
 | 
				
			||||||
            new DetectMappingsWithImages(doesImageExist)
 | 
					            new DetectMappingsWithImages(doesImageExist),
 | 
				
			||||||
 | 
					            new MiscTagRenderingChecks()
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -26,7 +26,7 @@ export interface MappingConfigJson {
 | 
				
			||||||
              /**
 | 
					              /**
 | 
				
			||||||
               * Size of the image
 | 
					               * Size of the image
 | 
				
			||||||
               */
 | 
					               */
 | 
				
			||||||
              class: "small" | "medium" | "large" | string
 | 
					              class?: "small" | "medium" | "large" | string
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -125,7 +125,7 @@ export interface TagRenderingConfigJson {
 | 
				
			||||||
                   * A hint to mapcomplete on how to render this icon within the mapping.
 | 
					                   * A hint to mapcomplete on how to render this icon within the mapping.
 | 
				
			||||||
                   * This is translated to 'mapping-icon-<classtype>', so defining your own in combination with a custom CSS is possible (but discouraged)
 | 
					                   * This is translated to 'mapping-icon-<classtype>', so defining your own in combination with a custom CSS is possible (but discouraged)
 | 
				
			||||||
                   */
 | 
					                   */
 | 
				
			||||||
                  class: "small" | "medium" | "large" | string
 | 
					                  class?: "small" | "medium" | "large" | string
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
    }[]
 | 
					    }[]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,7 +7,7 @@ import TilesourceConfig from "./TilesourceConfig"
 | 
				
			||||||
import { ExtractImages } from "./Conversion/FixImages"
 | 
					import { ExtractImages } from "./Conversion/FixImages"
 | 
				
			||||||
import ExtraLinkConfig from "./ExtraLinkConfig"
 | 
					import ExtraLinkConfig from "./ExtraLinkConfig"
 | 
				
			||||||
import { Utils } from "../../Utils"
 | 
					import { Utils } from "../../Utils"
 | 
				
			||||||
import * as used_languages from "../../assets/generated/used_languages.json"
 | 
					import used_languages from "../../assets/generated/used_languages.json"
 | 
				
			||||||
export default class LayoutConfig {
 | 
					export default class LayoutConfig {
 | 
				
			||||||
    public static readonly defaultSocialImage = "assets/SocialImage.png"
 | 
					    public static readonly defaultSocialImage = "assets/SocialImage.png"
 | 
				
			||||||
    public readonly id: string
 | 
					    public readonly id: string
 | 
				
			||||||
| 
						 | 
					@ -237,13 +237,11 @@ export default class LayoutConfig {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public missingTranslations(): {
 | 
					    public missingTranslations(): {
 | 
				
			||||||
        completeness: Map<string, number>
 | 
					 | 
				
			||||||
        untranslated: Map<string, string[]>
 | 
					        untranslated: Map<string, string[]>
 | 
				
			||||||
        total: number
 | 
					        total: number
 | 
				
			||||||
    } {
 | 
					    } {
 | 
				
			||||||
        const layout = this
 | 
					        const layout = this
 | 
				
			||||||
        let total = 0
 | 
					        let total = 0
 | 
				
			||||||
        const completeness = new Map<string, number>()
 | 
					 | 
				
			||||||
        const untranslated = new Map<string, string[]>()
 | 
					        const untranslated = new Map<string, string[]>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Utils.WalkObject(
 | 
					        Utils.WalkObject(
 | 
				
			||||||
| 
						 | 
					@ -264,13 +262,21 @@ export default class LayoutConfig {
 | 
				
			||||||
                    if (trans["*"] !== undefined) {
 | 
					                    if (trans["*"] !== undefined) {
 | 
				
			||||||
                        return
 | 
					                        return
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					                    if (translation.context.indexOf(":") < 0) {
 | 
				
			||||||
 | 
					                        return
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
                    if (trans[ln] === undefined) {
 | 
					                    if (trans[ln] === undefined) {
 | 
				
			||||||
                        if (!untranslated.has(ln)) {
 | 
					                        if (!untranslated.has(ln)) {
 | 
				
			||||||
                            untranslated.set(ln, [])
 | 
					                            untranslated.set(ln, [])
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        untranslated.get(ln).push(translation.context)
 | 
					                        untranslated
 | 
				
			||||||
                    } else {
 | 
					                            .get(ln)
 | 
				
			||||||
                        completeness.set(ln, 1 + (completeness.get(ln) ?? 0))
 | 
					                            .push(
 | 
				
			||||||
 | 
					                                translation.context.replace(
 | 
				
			||||||
 | 
					                                    /^note_import_[a-zA-Z0-9_]*/,
 | 
				
			||||||
 | 
					                                    "note_import"
 | 
				
			||||||
 | 
					                                )
 | 
				
			||||||
 | 
					                            )
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                })
 | 
					                })
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
| 
						 | 
					@ -282,7 +288,7 @@ export default class LayoutConfig {
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return { completeness, untranslated, total }
 | 
					        return { untranslated, total }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    public getMatchingLayer(tags: any): LayerConfig | undefined {
 | 
					    public getMatchingLayer(tags: any): LayerConfig | undefined {
 | 
				
			||||||
        if (tags === undefined) {
 | 
					        if (tags === undefined) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -27,7 +27,7 @@ import { QueryParameters } from "../Logic/Web/QueryParameters"
 | 
				
			||||||
import { SubstitutedTranslation } from "./SubstitutedTranslation"
 | 
					import { SubstitutedTranslation } from "./SubstitutedTranslation"
 | 
				
			||||||
import { AutoAction } from "./Popup/AutoApplyButton"
 | 
					import { AutoAction } from "./Popup/AutoApplyButton"
 | 
				
			||||||
import DynamicGeoJsonTileSource from "../Logic/FeatureSource/TiledFeatureSource/DynamicGeoJsonTileSource"
 | 
					import DynamicGeoJsonTileSource from "../Logic/FeatureSource/TiledFeatureSource/DynamicGeoJsonTileSource"
 | 
				
			||||||
import * as themeOverview from "../assets/generated/theme_overview.json"
 | 
					import themeOverview from "../assets/generated/theme_overview.json"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AutomationPanel extends Combine {
 | 
					class AutomationPanel extends Combine {
 | 
				
			||||||
    private static readonly openChangeset = new UIEventSource<number>(undefined)
 | 
					    private static readonly openChangeset = new UIEventSource<number>(undefined)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,6 +2,7 @@ import { VariableUiElement } from "./VariableUIElement"
 | 
				
			||||||
import Locale from "../i18n/Locale"
 | 
					import Locale from "../i18n/Locale"
 | 
				
			||||||
import Link from "./Link"
 | 
					import Link from "./Link"
 | 
				
			||||||
import Svg from "../../Svg"
 | 
					import Svg from "../../Svg"
 | 
				
			||||||
 | 
					import show = Mocha.reporters.Base.cursor.show
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * The little 'translate'-icon next to every icon + some static helper functions
 | 
					 * The little 'translate'-icon next to every icon + some static helper functions
 | 
				
			||||||
| 
						 | 
					@ -21,7 +22,7 @@ export default class LinkToWeblate extends VariableUiElement {
 | 
				
			||||||
                        return undefined
 | 
					                        return undefined
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    const icon = Svg.translate_svg().SetClass(
 | 
					                    const icon = Svg.translate_svg().SetClass(
 | 
				
			||||||
                        "rounded-full border border-gray-400 inline-block w-4 h-4 m-1 weblate-link self-center"
 | 
					                        "rounded-full inline-block w-3 h-3 ml-1 weblate-link self-center"
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
                    if (availableTranslations[ln] === undefined) {
 | 
					                    if (availableTranslations[ln] === undefined) {
 | 
				
			||||||
                        icon.SetClass("bg-red-400")
 | 
					                        icon.SetClass("bg-red-400")
 | 
				
			||||||
| 
						 | 
					@ -31,7 +32,15 @@ export default class LinkToWeblate extends VariableUiElement {
 | 
				
			||||||
                [Locale.showLinkToWeblate]
 | 
					                [Locale.showLinkToWeblate]
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        this.SetClass("enable-links hidden-on-mobile")
 | 
					        this.SetClass("enable-links")
 | 
				
			||||||
 | 
					        const self = this
 | 
				
			||||||
 | 
					        Locale.showLinkOnMobile.addCallbackAndRunD((showOnMobile) => {
 | 
				
			||||||
 | 
					            if (showOnMobile) {
 | 
				
			||||||
 | 
					                self.RemoveClass("hidden-on-mobile")
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                self.SetClass("hidden-on-mobile")
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -143,7 +143,7 @@ export default class ScrollableFullScreen {
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const contentWrapper = new Combine([content]).SetClass(
 | 
					        const contentWrapper = new Combine([content]).SetClass(
 | 
				
			||||||
            "block p-2 md:pt-4 w-full h-full overflow-y-auto desktop:max-h-65vh"
 | 
					            "block p-2 md:pt-4 w-full h-full overflow-y-auto"
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this._resetScrollSignal.addCallback((_) => {
 | 
					        this._resetScrollSignal.addCallback((_) => {
 | 
				
			||||||
| 
						 | 
					@ -159,7 +159,7 @@ export default class ScrollableFullScreen {
 | 
				
			||||||
                // We add an ornament which takes around 5em. This is in order to make sure the Web UI doesn't hide
 | 
					                // We add an ornament which takes around 5em. This is in order to make sure the Web UI doesn't hide
 | 
				
			||||||
            ]).SetClass("flex flex-col h-full relative bg-white"),
 | 
					            ]).SetClass("flex flex-col h-full relative bg-white"),
 | 
				
			||||||
        ]).SetClass(
 | 
					        ]).SetClass(
 | 
				
			||||||
            "fixed top-0 left-0 right-0 h-screen w-screen desktop:max-h-65vh md:w-auto md:relative z-above-controls md:rounded-xl overflow-hidden"
 | 
					            "fixed top-0 left-0 right-0 h-screen w-screen md:w-auto md:relative z-above-controls md:rounded-xl overflow-hidden"
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,9 +9,10 @@ import { SubtleButton } from "../Base/SubtleButton"
 | 
				
			||||||
import Svg from "../../Svg"
 | 
					import Svg from "../../Svg"
 | 
				
			||||||
import { Utils } from "../../Utils"
 | 
					import { Utils } from "../../Utils"
 | 
				
			||||||
import { MapillaryLink } from "./MapillaryLink"
 | 
					import { MapillaryLink } from "./MapillaryLink"
 | 
				
			||||||
import TranslatorsPanel from "./TranslatorsPanel"
 | 
					 | 
				
			||||||
import { OpenIdEditor, OpenJosm } from "./CopyrightPanel"
 | 
					import { OpenIdEditor, OpenJosm } from "./CopyrightPanel"
 | 
				
			||||||
import Toggle from "../Input/Toggle"
 | 
					import Toggle from "../Input/Toggle"
 | 
				
			||||||
 | 
					import ScrollableFullScreen from "../Base/ScrollableFullScreen"
 | 
				
			||||||
 | 
					import { DefaultGuiState } from "../DefaultGuiState"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class BackToThemeOverview extends Toggle {
 | 
					export class BackToThemeOverview extends Toggle {
 | 
				
			||||||
    constructor(
 | 
					    constructor(
 | 
				
			||||||
| 
						 | 
					@ -77,7 +78,14 @@ export class ActionButtons extends Combine {
 | 
				
			||||||
            new OpenIdEditor(state, iconStyle),
 | 
					            new OpenIdEditor(state, iconStyle),
 | 
				
			||||||
            new MapillaryLink(state, iconStyle),
 | 
					            new MapillaryLink(state, iconStyle),
 | 
				
			||||||
            new OpenJosm(state, iconStyle).SetClass("hidden-on-mobile"),
 | 
					            new OpenJosm(state, iconStyle).SetClass("hidden-on-mobile"),
 | 
				
			||||||
            new TranslatorsPanel(state, iconStyle),
 | 
					            new SubtleButton(
 | 
				
			||||||
 | 
					                Svg.translate_ui().SetStyle(iconStyle),
 | 
				
			||||||
 | 
					                Translations.t.translations.activateButton
 | 
				
			||||||
 | 
					            ).onClick(() => {
 | 
				
			||||||
 | 
					                ScrollableFullScreen.collapse()
 | 
				
			||||||
 | 
					                DefaultGuiState.state.userInfoIsOpened.setData(true)
 | 
				
			||||||
 | 
					                DefaultGuiState.state.userInfoFocusedQuestion.setData("translation-mode")
 | 
				
			||||||
 | 
					            }),
 | 
				
			||||||
        ])
 | 
					        ])
 | 
				
			||||||
        this.SetClass("block w-full link-no-underline")
 | 
					        this.SetClass("block w-full link-no-underline")
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,9 +1,9 @@
 | 
				
			||||||
import Combine from "../Base/Combine"
 | 
					import Combine from "../Base/Combine"
 | 
				
			||||||
import * as welcome_messages from "../../assets/welcome_message.json"
 | 
					import welcome_messages from "../../assets/welcome_message.json"
 | 
				
			||||||
import BaseUIElement from "../BaseUIElement"
 | 
					import BaseUIElement from "../BaseUIElement"
 | 
				
			||||||
import { FixedUiElement } from "../Base/FixedUiElement"
 | 
					import { FixedUiElement } from "../Base/FixedUiElement"
 | 
				
			||||||
import MoreScreen from "./MoreScreen"
 | 
					import MoreScreen from "./MoreScreen"
 | 
				
			||||||
import * as themeOverview from "../../assets/generated/theme_overview.json"
 | 
					import themeOverview from "../../assets/generated/theme_overview.json"
 | 
				
			||||||
import Translations from "../i18n/Translations"
 | 
					import Translations from "../i18n/Translations"
 | 
				
			||||||
import Title from "../Base/Title"
 | 
					import Title from "../Base/Title"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -43,7 +43,7 @@ export default class FeaturedMessage extends Combine {
 | 
				
			||||||
        }[] = []
 | 
					        }[] = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const themesById = new Map<string, { id: string; title: any; shortDescription: any }>()
 | 
					        const themesById = new Map<string, { id: string; title: any; shortDescription: any }>()
 | 
				
			||||||
        for (const theme of themeOverview["default"]) {
 | 
					        for (const theme of themeOverview) {
 | 
				
			||||||
            themesById.set(theme.id, theme)
 | 
					            themesById.set(theme.id, theme)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -88,9 +88,7 @@ export default class FeaturedMessage extends Combine {
 | 
				
			||||||
        const msg = new FixedUiElement(welcome_message.message).SetClass("link-underline font-lg")
 | 
					        const msg = new FixedUiElement(welcome_message.message).SetClass("link-underline font-lg")
 | 
				
			||||||
        els.push(new Combine([title, msg]).SetClass("m-4"))
 | 
					        els.push(new Combine([title, msg]).SetClass("m-4"))
 | 
				
			||||||
        if (welcome_message.featured_theme !== undefined) {
 | 
					        if (welcome_message.featured_theme !== undefined) {
 | 
				
			||||||
            const theme = themeOverview["default"].filter(
 | 
					            const theme = themeOverview.filter((th) => th.id === welcome_message.featured_theme)[0]
 | 
				
			||||||
                (th) => th.id === welcome_message.featured_theme
 | 
					 | 
				
			||||||
            )[0]
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            els.push(
 | 
					            els.push(
 | 
				
			||||||
                MoreScreen.createLinkButton({}, theme)
 | 
					                MoreScreen.createLinkButton({}, theme)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,6 +6,7 @@ import { BBox } from "../../Logic/BBox"
 | 
				
			||||||
import Loc from "../../Models/Loc"
 | 
					import Loc from "../../Models/Loc"
 | 
				
			||||||
import Hotkeys from "../Base/Hotkeys"
 | 
					import Hotkeys from "../Base/Hotkeys"
 | 
				
			||||||
import Translations from "../i18n/Translations"
 | 
					import Translations from "../i18n/Translations"
 | 
				
			||||||
 | 
					import Constants from "../../Models/Constants"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Displays an icon depending on the state of the geolocation.
 | 
					 * Displays an icon depending on the state of the geolocation.
 | 
				
			||||||
| 
						 | 
					@ -20,6 +21,9 @@ export class GeolocationControl extends VariableUiElement {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        const lastClick = new UIEventSource<Date>(undefined)
 | 
					        const lastClick = new UIEventSource<Date>(undefined)
 | 
				
			||||||
 | 
					        lastClick.addCallbackD((date) => {
 | 
				
			||||||
 | 
					            geolocationHandler.geolocationState.requestMoment.setData(date)
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
        const lastClickWithinThreeSecs = lastClick.map((lastClick) => {
 | 
					        const lastClickWithinThreeSecs = lastClick.map((lastClick) => {
 | 
				
			||||||
            if (lastClick === undefined) {
 | 
					            if (lastClick === undefined) {
 | 
				
			||||||
                return false
 | 
					                return false
 | 
				
			||||||
| 
						 | 
					@ -27,6 +31,16 @@ export class GeolocationControl extends VariableUiElement {
 | 
				
			||||||
            const timeDiff = (new Date().getTime() - lastClick.getTime()) / 1000
 | 
					            const timeDiff = (new Date().getTime() - lastClick.getTime()) / 1000
 | 
				
			||||||
            return timeDiff <= 3
 | 
					            return timeDiff <= 3
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
 | 
					        const lastRequestWithinTimeout = geolocationHandler.geolocationState.requestMoment.map(
 | 
				
			||||||
 | 
					            (date) => {
 | 
				
			||||||
 | 
					                if (date === undefined) {
 | 
				
			||||||
 | 
					                    return false
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                const timeDiff = (new Date().getTime() - date.getTime()) / 1000
 | 
				
			||||||
 | 
					                console.log("Timediff", timeDiff)
 | 
				
			||||||
 | 
					                return timeDiff <= Constants.zoomToLocationTimeout
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        const geolocationState = geolocationHandler?.geolocationState
 | 
					        const geolocationState = geolocationHandler?.geolocationState
 | 
				
			||||||
        super(
 | 
					        super(
 | 
				
			||||||
            geolocationState?.permission?.map(
 | 
					            geolocationState?.permission?.map(
 | 
				
			||||||
| 
						 | 
					@ -43,9 +57,10 @@ export class GeolocationControl extends VariableUiElement {
 | 
				
			||||||
                            return Svg.location_empty_svg()
 | 
					                            return Svg.location_empty_svg()
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        // Position not yet found, but permission is either requested or granted: we spin to indicate activity
 | 
					                        // Position not yet found, but permission is either requested or granted: we spin to indicate activity
 | 
				
			||||||
                        const icon = !geolocationHandler.mapHasMoved.data
 | 
					                        const icon =
 | 
				
			||||||
                            ? Svg.location_svg()
 | 
					                            !geolocationHandler.mapHasMoved.data || lastRequestWithinTimeout.data
 | 
				
			||||||
                            : Svg.location_empty_svg()
 | 
					                                ? Svg.location_svg()
 | 
				
			||||||
 | 
					                                : Svg.location_empty_svg()
 | 
				
			||||||
                        return icon
 | 
					                        return icon
 | 
				
			||||||
                            .SetClass("cursor-wait")
 | 
					                            .SetClass("cursor-wait")
 | 
				
			||||||
                            .SetStyle("animation: spin 4s linear infinite;")
 | 
					                            .SetStyle("animation: spin 4s linear infinite;")
 | 
				
			||||||
| 
						 | 
					@ -65,6 +80,7 @@ export class GeolocationControl extends VariableUiElement {
 | 
				
			||||||
                    geolocationState.isLocked,
 | 
					                    geolocationState.isLocked,
 | 
				
			||||||
                    geolocationHandler.mapHasMoved,
 | 
					                    geolocationHandler.mapHasMoved,
 | 
				
			||||||
                    lastClickWithinThreeSecs,
 | 
					                    lastClickWithinThreeSecs,
 | 
				
			||||||
 | 
					                    lastRequestWithinTimeout,
 | 
				
			||||||
                ]
 | 
					                ]
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
| 
						 | 
					@ -74,6 +90,8 @@ export class GeolocationControl extends VariableUiElement {
 | 
				
			||||||
                geolocationState.permission.data !== "granted" &&
 | 
					                geolocationState.permission.data !== "granted" &&
 | 
				
			||||||
                geolocationState.currentGPSLocation.data === undefined
 | 
					                geolocationState.currentGPSLocation.data === undefined
 | 
				
			||||||
            ) {
 | 
					            ) {
 | 
				
			||||||
 | 
					                lastClick.setData(new Date())
 | 
				
			||||||
 | 
					                geolocationState.requestMoment.setData(new Date())
 | 
				
			||||||
                await geolocationState.requestPermission()
 | 
					                await geolocationState.requestPermission()
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -85,6 +103,7 @@ export class GeolocationControl extends VariableUiElement {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (geolocationState.currentGPSLocation.data === undefined) {
 | 
					            if (geolocationState.currentGPSLocation.data === undefined) {
 | 
				
			||||||
                // No location is known yet, not much we can do
 | 
					                // No location is known yet, not much we can do
 | 
				
			||||||
 | 
					                lastClick.setData(new Date())
 | 
				
			||||||
                return
 | 
					                return
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -126,5 +145,12 @@ export class GeolocationControl extends VariableUiElement {
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }, 500)
 | 
					            }, 500)
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
 | 
					        geolocationHandler.geolocationState.requestMoment.addCallbackAndRunD((_) => {
 | 
				
			||||||
 | 
					            window.setTimeout(() => {
 | 
				
			||||||
 | 
					                if (lastRequestWithinTimeout.data) {
 | 
				
			||||||
 | 
					                    geolocationHandler.geolocationState.requestMoment.ping()
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }, 500)
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,7 @@ import Svg from "../../Svg"
 | 
				
			||||||
import Combine from "../Base/Combine"
 | 
					import Combine from "../Base/Combine"
 | 
				
			||||||
import { SubtleButton } from "../Base/SubtleButton"
 | 
					import { SubtleButton } from "../Base/SubtleButton"
 | 
				
			||||||
import Translations from "../i18n/Translations"
 | 
					import Translations from "../i18n/Translations"
 | 
				
			||||||
import * as personal from "../../assets/themes/personal/personal.json"
 | 
					import personal from "../../assets/themes/personal/personal.json"
 | 
				
			||||||
import Constants from "../../Models/Constants"
 | 
					import Constants from "../../Models/Constants"
 | 
				
			||||||
import BaseUIElement from "../BaseUIElement"
 | 
					import BaseUIElement from "../BaseUIElement"
 | 
				
			||||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
 | 
					import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,7 @@ import UserRelatedState from "../../Logic/State/UserRelatedState"
 | 
				
			||||||
import Toggle from "../Input/Toggle"
 | 
					import Toggle from "../Input/Toggle"
 | 
				
			||||||
import { Utils } from "../../Utils"
 | 
					import { Utils } from "../../Utils"
 | 
				
			||||||
import Title from "../Base/Title"
 | 
					import Title from "../Base/Title"
 | 
				
			||||||
import * as themeOverview from "../../assets/generated/theme_overview.json"
 | 
					import themeOverview from "../../assets/generated/theme_overview.json"
 | 
				
			||||||
import { Translation } from "../i18n/Translation"
 | 
					import { Translation } from "../i18n/Translation"
 | 
				
			||||||
import { TextField } from "../Input/TextField"
 | 
					import { TextField } from "../Input/TextField"
 | 
				
			||||||
import FilteredCombine from "../Base/FilteredCombine"
 | 
					import FilteredCombine from "../Base/FilteredCombine"
 | 
				
			||||||
| 
						 | 
					@ -30,7 +30,7 @@ export default class MoreScreen extends Combine {
 | 
				
			||||||
        mustHaveLanguage?: boolean
 | 
					        mustHaveLanguage?: boolean
 | 
				
			||||||
        hideFromOverview: boolean
 | 
					        hideFromOverview: boolean
 | 
				
			||||||
        keywors?: any[]
 | 
					        keywors?: any[]
 | 
				
			||||||
    }[] = themeOverview["default"]
 | 
					    }[] = themeOverview
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(
 | 
					    constructor(
 | 
				
			||||||
        state: UserRelatedState & {
 | 
					        state: UserRelatedState & {
 | 
				
			||||||
| 
						 | 
					@ -287,7 +287,7 @@ export default class MoreScreen extends Combine {
 | 
				
			||||||
    ): BaseUIElement {
 | 
					    ): BaseUIElement {
 | 
				
			||||||
        const t = Translations.t.general.morescreen
 | 
					        const t = Translations.t.general.morescreen
 | 
				
			||||||
        const prefix = "mapcomplete-hidden-theme-"
 | 
					        const prefix = "mapcomplete-hidden-theme-"
 | 
				
			||||||
        const hiddenThemes = themeOverview["default"].filter((layout) => layout.hideFromOverview)
 | 
					        const hiddenThemes = themeOverview.filter((layout) => layout.hideFromOverview)
 | 
				
			||||||
        const hiddenTotal = hiddenThemes.length
 | 
					        const hiddenTotal = hiddenThemes.length
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return new Toggle(
 | 
					        return new Toggle(
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,148 +0,0 @@
 | 
				
			||||||
import Toggle from "../Input/Toggle"
 | 
					 | 
				
			||||||
import Lazy from "../Base/Lazy"
 | 
					 | 
				
			||||||
import { Utils } from "../../Utils"
 | 
					 | 
				
			||||||
import Translations from "../i18n/Translations"
 | 
					 | 
				
			||||||
import Combine from "../Base/Combine"
 | 
					 | 
				
			||||||
import Locale from "../i18n/Locale"
 | 
					 | 
				
			||||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
 | 
					 | 
				
			||||||
import { Translation } from "../i18n/Translation"
 | 
					 | 
				
			||||||
import { VariableUiElement } from "../Base/VariableUIElement"
 | 
					 | 
				
			||||||
import Link from "../Base/Link"
 | 
					 | 
				
			||||||
import LinkToWeblate from "../Base/LinkToWeblate"
 | 
					 | 
				
			||||||
import Toggleable from "../Base/Toggleable"
 | 
					 | 
				
			||||||
import Title from "../Base/Title"
 | 
					 | 
				
			||||||
import { Store } from "../../Logic/UIEventSource"
 | 
					 | 
				
			||||||
import { SubtleButton } from "../Base/SubtleButton"
 | 
					 | 
				
			||||||
import Svg from "../../Svg"
 | 
					 | 
				
			||||||
import * as native_languages from "../../assets/language_native.json"
 | 
					 | 
				
			||||||
import BaseUIElement from "../BaseUIElement"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class TranslatorsPanelContent extends Combine {
 | 
					 | 
				
			||||||
    constructor(layout: LayoutConfig, isTranslator: Store<boolean>) {
 | 
					 | 
				
			||||||
        const t = Translations.t.translations
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const { completeness, untranslated, total } = layout.missingTranslations()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const seed = t.completeness
 | 
					 | 
				
			||||||
        for (const ln of Array.from(completeness.keys())) {
 | 
					 | 
				
			||||||
            if (ln === "*") {
 | 
					 | 
				
			||||||
                continue
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            if (seed.translations[ln] === undefined) {
 | 
					 | 
				
			||||||
                seed.translations[ln] = seed.translations["en"]
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const completenessTr = {}
 | 
					 | 
				
			||||||
        const completenessPercentage = {}
 | 
					 | 
				
			||||||
        seed.SupportedLanguages().forEach((ln) => {
 | 
					 | 
				
			||||||
            completenessTr[ln] = "" + (completeness.get(ln) ?? 0)
 | 
					 | 
				
			||||||
            completenessPercentage[ln] =
 | 
					 | 
				
			||||||
                "" + Math.round((100 * (completeness.get(ln) ?? 0)) / total)
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        function missingTranslationsFor(language: string): BaseUIElement[] {
 | 
					 | 
				
			||||||
            // e.g. "themes:<themename>.layers.0.tagRenderings..., or "layers:<layername>.description
 | 
					 | 
				
			||||||
            const missingKeys = Utils.NoNull(untranslated.get(language) ?? [])
 | 
					 | 
				
			||||||
                .filter((ctx) => ctx.indexOf(":") >= 0)
 | 
					 | 
				
			||||||
                .map((ctx) => ctx.replace(/note_import_[a-zA-Z0-9_]*/, "note_import"))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            const hasMissingTheme = missingKeys.some((k) => k.startsWith("themes:"))
 | 
					 | 
				
			||||||
            const missingLayers = Utils.Dedup(
 | 
					 | 
				
			||||||
                missingKeys
 | 
					 | 
				
			||||||
                    .filter((k) => k.startsWith("layers:"))
 | 
					 | 
				
			||||||
                    .map((k) => k.slice("layers:".length).split(".")[0])
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            console.log(
 | 
					 | 
				
			||||||
                "Getting untranslated string for",
 | 
					 | 
				
			||||||
                language,
 | 
					 | 
				
			||||||
                "raw:",
 | 
					 | 
				
			||||||
                missingKeys,
 | 
					 | 
				
			||||||
                "hasMissingTheme:",
 | 
					 | 
				
			||||||
                hasMissingTheme,
 | 
					 | 
				
			||||||
                "missingLayers:",
 | 
					 | 
				
			||||||
                missingLayers
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            return Utils.NoNull([
 | 
					 | 
				
			||||||
                hasMissingTheme
 | 
					 | 
				
			||||||
                    ? new Link(
 | 
					 | 
				
			||||||
                          "themes:" + layout.id + ".* (zen mode)",
 | 
					 | 
				
			||||||
                          LinkToWeblate.hrefToWeblateZen(language, "themes", layout.id),
 | 
					 | 
				
			||||||
                          true
 | 
					 | 
				
			||||||
                      )
 | 
					 | 
				
			||||||
                    : undefined,
 | 
					 | 
				
			||||||
                ...missingLayers.map(
 | 
					 | 
				
			||||||
                    (id) =>
 | 
					 | 
				
			||||||
                        new Link(
 | 
					 | 
				
			||||||
                            "layer:" + id + ".* (zen mode)",
 | 
					 | 
				
			||||||
                            LinkToWeblate.hrefToWeblateZen(language, "layers", id),
 | 
					 | 
				
			||||||
                            true
 | 
					 | 
				
			||||||
                        )
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
                ...missingKeys.map(
 | 
					 | 
				
			||||||
                    (context) =>
 | 
					 | 
				
			||||||
                        new Link(context, LinkToWeblate.hrefToWeblate(language, context), true)
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
            ])
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // "translationCompleteness": "Translations for {theme} in {language} are at {percentage}: {translated} out of {total}",
 | 
					 | 
				
			||||||
        const translated = seed.Subs({
 | 
					 | 
				
			||||||
            total,
 | 
					 | 
				
			||||||
            theme: layout.title,
 | 
					 | 
				
			||||||
            percentage: new Translation(completenessPercentage),
 | 
					 | 
				
			||||||
            translated: new Translation(completenessTr),
 | 
					 | 
				
			||||||
            language: seed.OnEveryLanguage((_, lng) => native_languages[lng] ?? lng),
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        super([
 | 
					 | 
				
			||||||
            new Title(Translations.t.translations.activateButton),
 | 
					 | 
				
			||||||
            new Toggle(t.isTranslator.SetClass("thanks block"), undefined, isTranslator),
 | 
					 | 
				
			||||||
            t.help,
 | 
					 | 
				
			||||||
            translated,
 | 
					 | 
				
			||||||
            /*Disable button:*/
 | 
					 | 
				
			||||||
            new SubtleButton(undefined, t.deactivate).onClick(() => {
 | 
					 | 
				
			||||||
                Locale.showLinkToWeblate.setData(false)
 | 
					 | 
				
			||||||
            }),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            new VariableUiElement(
 | 
					 | 
				
			||||||
                Locale.language.map((ln) => {
 | 
					 | 
				
			||||||
                    const missing = missingTranslationsFor(ln)
 | 
					 | 
				
			||||||
                    if (missing.length === 0) {
 | 
					 | 
				
			||||||
                        return undefined
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    let title = Translations.t.translations.allMissing
 | 
					 | 
				
			||||||
                    if (untranslated.get(ln) !== undefined) {
 | 
					 | 
				
			||||||
                        title = Translations.t.translations.missing.Subs({
 | 
					 | 
				
			||||||
                            count: untranslated.get(ln).length,
 | 
					 | 
				
			||||||
                        })
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    return new Toggleable(
 | 
					 | 
				
			||||||
                        new Title(title),
 | 
					 | 
				
			||||||
                        new Combine(missing).SetClass("flex flex-col")
 | 
					 | 
				
			||||||
                    )
 | 
					 | 
				
			||||||
                })
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
        ])
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default class TranslatorsPanel extends Toggle {
 | 
					 | 
				
			||||||
    constructor(
 | 
					 | 
				
			||||||
        state: { layoutToUse: LayoutConfig; isTranslator: Store<boolean> },
 | 
					 | 
				
			||||||
        iconStyle?: string
 | 
					 | 
				
			||||||
    ) {
 | 
					 | 
				
			||||||
        const t = Translations.t.translations
 | 
					 | 
				
			||||||
        super(
 | 
					 | 
				
			||||||
            new Lazy(
 | 
					 | 
				
			||||||
                () => new TranslatorsPanelContent(state.layoutToUse, state.isTranslator)
 | 
					 | 
				
			||||||
            ).SetClass("flex flex-col"),
 | 
					 | 
				
			||||||
            new SubtleButton(Svg.translate_ui().SetStyle(iconStyle), t.activateButton).onClick(() =>
 | 
					 | 
				
			||||||
                Locale.showLinkToWeblate.setData(true)
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
            Locale.showLinkToWeblate
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        this.SetClass("hidden-on-mobile")
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -19,11 +19,14 @@ import EditableTagRendering from "../Popup/EditableTagRendering"
 | 
				
			||||||
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
 | 
					import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
 | 
				
			||||||
import { SaveButton } from "../Popup/SaveButton"
 | 
					import { SaveButton } from "../Popup/SaveButton"
 | 
				
			||||||
import { TagUtils } from "../../Logic/Tags/TagUtils"
 | 
					import { TagUtils } from "../../Logic/Tags/TagUtils"
 | 
				
			||||||
import * as usersettings from "../../assets/generated/layers/usersettings.json"
 | 
					import usersettings from "../../assets/generated/layers/usersettings.json"
 | 
				
			||||||
import { LoginToggle } from "../Popup/LoginButton"
 | 
					import { LoginToggle } from "../Popup/LoginButton"
 | 
				
			||||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
 | 
					import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
 | 
				
			||||||
import * as translators from "../../assets/translators.json"
 | 
					import translators from "../../assets/translators.json"
 | 
				
			||||||
import * as codeContributors from "../../assets/contributors.json"
 | 
					import codeContributors from "../../assets/contributors.json"
 | 
				
			||||||
 | 
					import Locale from "../i18n/Locale"
 | 
				
			||||||
 | 
					import { Utils } from "../../Utils"
 | 
				
			||||||
 | 
					import LinkToWeblate from "../Base/LinkToWeblate"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class ImportViewerLinks extends VariableUiElement {
 | 
					export class ImportViewerLinks extends VariableUiElement {
 | 
				
			||||||
    constructor(osmConnection: OsmConnection) {
 | 
					    constructor(osmConnection: OsmConnection) {
 | 
				
			||||||
| 
						 | 
					@ -53,7 +56,7 @@ class SingleUserSettingsPanel extends EditableTagRendering {
 | 
				
			||||||
        userInfoFocusedQuestion?: UIEventSource<string>
 | 
					        userInfoFocusedQuestion?: UIEventSource<string>
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        const editMode = new UIEventSource(false)
 | 
					        const editMode = new UIEventSource(false)
 | 
				
			||||||
        // Isolate the preferences. THey'll be updated explicitely later on anyway
 | 
					        // Isolate the preferences. They'll be updated explicitely later on anyway
 | 
				
			||||||
        super(
 | 
					        super(
 | 
				
			||||||
            amendedPrefs,
 | 
					            amendedPrefs,
 | 
				
			||||||
            config,
 | 
					            config,
 | 
				
			||||||
| 
						 | 
					@ -68,6 +71,9 @@ class SingleUserSettingsPanel extends EditableTagRendering {
 | 
				
			||||||
                            TagUtils.FlattenAnd(store.data, amendedPrefs.data)
 | 
					                            TagUtils.FlattenAnd(store.data, amendedPrefs.data)
 | 
				
			||||||
                        ).asChange(amendedPrefs.data)
 | 
					                        ).asChange(amendedPrefs.data)
 | 
				
			||||||
                        for (const kv of selection) {
 | 
					                        for (const kv of selection) {
 | 
				
			||||||
 | 
					                            if (kv.k.startsWith("_")) {
 | 
				
			||||||
 | 
					                                continue
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
                            osmConnection.GetPreference(kv.k, "", "").setData(kv.v)
 | 
					                            osmConnection.GetPreference(kv.k, "", "").setData(kv.v)
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -104,13 +110,59 @@ class UserInformationMainPanel extends VariableUiElement {
 | 
				
			||||||
        const settings = new UIEventSource<Record<string, BaseUIElement>>({})
 | 
					        const settings = new UIEventSource<Record<string, BaseUIElement>>({})
 | 
				
			||||||
        const usersettingsConfig = new LayerConfig(usersettings, "userinformationpanel")
 | 
					        const usersettingsConfig = new LayerConfig(usersettings, "userinformationpanel")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const amendedPrefs = new UIEventSource<any>({})
 | 
					        const amendedPrefs = new UIEventSource<any>({ _theme: layout?.id })
 | 
				
			||||||
        osmConnection.preferencesHandler.preferences.addCallback((newPrefs) => {
 | 
					        osmConnection.preferencesHandler.preferences.addCallback((newPrefs) => {
 | 
				
			||||||
            for (const k in newPrefs) {
 | 
					            for (const k in newPrefs) {
 | 
				
			||||||
                amendedPrefs.data[k] = newPrefs[k]
 | 
					                amendedPrefs.data[k] = newPrefs[k]
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            amendedPrefs.ping()
 | 
					            amendedPrefs.ping()
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
 | 
					        const translationMode = osmConnection.GetPreference("translation-mode")
 | 
				
			||||||
 | 
					        Locale.language.mapD(
 | 
				
			||||||
 | 
					            (language) => {
 | 
				
			||||||
 | 
					                amendedPrefs.data["_language"] = language
 | 
				
			||||||
 | 
					                const trmode = translationMode.data
 | 
				
			||||||
 | 
					                if (trmode === "true" || trmode === "mobile") {
 | 
				
			||||||
 | 
					                    const missing = layout.missingTranslations()
 | 
				
			||||||
 | 
					                    const total = missing.total
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    const untranslated = missing.untranslated.get(language) ?? []
 | 
				
			||||||
 | 
					                    const hasMissingTheme = untranslated.some((k) => k.startsWith("themes:"))
 | 
				
			||||||
 | 
					                    const missingLayers = Utils.Dedup(
 | 
				
			||||||
 | 
					                        untranslated
 | 
				
			||||||
 | 
					                            .filter((k) => k.startsWith("layers:"))
 | 
				
			||||||
 | 
					                            .map((k) => k.slice("layers:".length).split(".")[0])
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    const zenLinks: { link: string; id: string }[] = Utils.NoNull([
 | 
				
			||||||
 | 
					                        hasMissingTheme
 | 
				
			||||||
 | 
					                            ? {
 | 
				
			||||||
 | 
					                                  id: "theme:" + layout.id,
 | 
				
			||||||
 | 
					                                  link: LinkToWeblate.hrefToWeblateZen(
 | 
				
			||||||
 | 
					                                      language,
 | 
				
			||||||
 | 
					                                      "themes",
 | 
				
			||||||
 | 
					                                      layout.id
 | 
				
			||||||
 | 
					                                  ),
 | 
				
			||||||
 | 
					                              }
 | 
				
			||||||
 | 
					                            : undefined,
 | 
				
			||||||
 | 
					                        ...missingLayers.map((id) => ({
 | 
				
			||||||
 | 
					                            id: "layer:" + id,
 | 
				
			||||||
 | 
					                            link: LinkToWeblate.hrefToWeblateZen(language, "layers", id),
 | 
				
			||||||
 | 
					                        })),
 | 
				
			||||||
 | 
					                    ])
 | 
				
			||||||
 | 
					                    const untranslated_count = untranslated.length
 | 
				
			||||||
 | 
					                    amendedPrefs.data["_translation_total"] = "" + total
 | 
				
			||||||
 | 
					                    amendedPrefs.data["_translation_translated_count"] =
 | 
				
			||||||
 | 
					                        "" + (total - untranslated_count)
 | 
				
			||||||
 | 
					                    amendedPrefs.data["_translation_percentage"] =
 | 
				
			||||||
 | 
					                        "" + Math.floor((100 * (total - untranslated_count)) / total)
 | 
				
			||||||
 | 
					                    console.log("Setting zenLinks", zenLinks)
 | 
				
			||||||
 | 
					                    amendedPrefs.data["_translation_links"] = JSON.stringify(zenLinks)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                amendedPrefs.ping()
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            [translationMode]
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        osmConnection.userDetails.addCallback((userDetails) => {
 | 
					        osmConnection.userDetails.addCallback((userDetails) => {
 | 
				
			||||||
            for (const k in userDetails) {
 | 
					            for (const k in userDetails) {
 | 
				
			||||||
                amendedPrefs.data["_" + k] = "" + userDetails[k]
 | 
					                amendedPrefs.data["_" + k] = "" + userDetails[k]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,7 +5,7 @@ import { Utils } from "../Utils"
 | 
				
			||||||
import Combine from "./Base/Combine"
 | 
					import Combine from "./Base/Combine"
 | 
				
			||||||
import ShowDataLayer from "./ShowDataLayer/ShowDataLayer"
 | 
					import ShowDataLayer from "./ShowDataLayer/ShowDataLayer"
 | 
				
			||||||
import LayerConfig from "../Models/ThemeConfig/LayerConfig"
 | 
					import LayerConfig from "../Models/ThemeConfig/LayerConfig"
 | 
				
			||||||
import * as home_location_json from "../assets/layers/home_location/home_location.json"
 | 
					import home_location_json from "../assets/layers/home_location/home_location.json"
 | 
				
			||||||
import State from "../State"
 | 
					import State from "../State"
 | 
				
			||||||
import Title from "./Base/Title"
 | 
					import Title from "./Base/Title"
 | 
				
			||||||
import { MinimapObj } from "./Base/Minimap"
 | 
					import { MinimapObj } from "./Base/Minimap"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,7 +18,7 @@ import SimpleAddUI from "./BigComponents/SimpleAddUI"
 | 
				
			||||||
import StrayClickHandler from "../Logic/Actors/StrayClickHandler"
 | 
					import StrayClickHandler from "../Logic/Actors/StrayClickHandler"
 | 
				
			||||||
import { DefaultGuiState } from "./DefaultGuiState"
 | 
					import { DefaultGuiState } from "./DefaultGuiState"
 | 
				
			||||||
import LayerConfig from "../Models/ThemeConfig/LayerConfig"
 | 
					import LayerConfig from "../Models/ThemeConfig/LayerConfig"
 | 
				
			||||||
import * as home_location_json from "../assets/layers/home_location/home_location.json"
 | 
					import home_location_json from "../assets/layers/home_location/home_location.json"
 | 
				
			||||||
import NewNoteUi from "./Popup/NewNoteUi"
 | 
					import NewNoteUi from "./Popup/NewNoteUi"
 | 
				
			||||||
import Combine from "./Base/Combine"
 | 
					import Combine from "./Base/Combine"
 | 
				
			||||||
import AddNewMarker from "./BigComponents/AddNewMarker"
 | 
					import AddNewMarker from "./BigComponents/AddNewMarker"
 | 
				
			||||||
| 
						 | 
					@ -33,7 +33,6 @@ import GeoLocationHandler from "../Logic/Actors/GeoLocationHandler"
 | 
				
			||||||
import { GeoLocationState } from "../Logic/State/GeoLocationState"
 | 
					import { GeoLocationState } from "../Logic/State/GeoLocationState"
 | 
				
			||||||
import Hotkeys from "./Base/Hotkeys"
 | 
					import Hotkeys from "./Base/Hotkeys"
 | 
				
			||||||
import AvailableBaseLayers from "../Logic/Actors/AvailableBaseLayers"
 | 
					import AvailableBaseLayers from "../Logic/Actors/AvailableBaseLayers"
 | 
				
			||||||
import Lazy from "./Base/Lazy"
 | 
					 | 
				
			||||||
import CopyrightPanel from "./BigComponents/CopyrightPanel"
 | 
					import CopyrightPanel from "./BigComponents/CopyrightPanel"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,12 +13,12 @@ import Minimap from "../Base/Minimap"
 | 
				
			||||||
import ShowDataLayer from "../ShowDataLayer/ShowDataLayer"
 | 
					import ShowDataLayer from "../ShowDataLayer/ShowDataLayer"
 | 
				
			||||||
import FeatureInfoBox from "../Popup/FeatureInfoBox"
 | 
					import FeatureInfoBox from "../Popup/FeatureInfoBox"
 | 
				
			||||||
import { ImportUtils } from "./ImportUtils"
 | 
					import { ImportUtils } from "./ImportUtils"
 | 
				
			||||||
import * as import_candidate from "../../assets/layers/import_candidate/import_candidate.json"
 | 
					import import_candidate from "../../assets/layers/import_candidate/import_candidate.json"
 | 
				
			||||||
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"
 | 
					import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"
 | 
				
			||||||
import Title from "../Base/Title"
 | 
					import Title from "../Base/Title"
 | 
				
			||||||
import Loading from "../Base/Loading"
 | 
					import Loading from "../Base/Loading"
 | 
				
			||||||
import { VariableUiElement } from "../Base/VariableUIElement"
 | 
					import { VariableUiElement } from "../Base/VariableUIElement"
 | 
				
			||||||
import * as known_layers from "../../assets/generated/known_layers.json"
 | 
					import known_layers from "../../assets/generated/known_layers.json"
 | 
				
			||||||
import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson"
 | 
					import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson"
 | 
				
			||||||
import Translations from "../i18n/Translations"
 | 
					import Translations from "../i18n/Translations"
 | 
				
			||||||
import { Feature } from "geojson"
 | 
					import { Feature } from "geojson"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,12 +22,12 @@ import ShowDataLayer from "../ShowDataLayer/ShowDataLayer"
 | 
				
			||||||
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"
 | 
					import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"
 | 
				
			||||||
import ValidatedTextField from "../Input/ValidatedTextField"
 | 
					import ValidatedTextField from "../Input/ValidatedTextField"
 | 
				
			||||||
import { LocalStorageSource } from "../../Logic/Web/LocalStorageSource"
 | 
					import { LocalStorageSource } from "../../Logic/Web/LocalStorageSource"
 | 
				
			||||||
import * as import_candidate from "../../assets/layers/import_candidate/import_candidate.json"
 | 
					import import_candidate from "../../assets/layers/import_candidate/import_candidate.json"
 | 
				
			||||||
import { GeoOperations } from "../../Logic/GeoOperations"
 | 
					import { GeoOperations } from "../../Logic/GeoOperations"
 | 
				
			||||||
import FeatureInfoBox from "../Popup/FeatureInfoBox"
 | 
					import FeatureInfoBox from "../Popup/FeatureInfoBox"
 | 
				
			||||||
import { ImportUtils } from "./ImportUtils"
 | 
					import { ImportUtils } from "./ImportUtils"
 | 
				
			||||||
import Translations from "../i18n/Translations"
 | 
					import Translations from "../i18n/Translations"
 | 
				
			||||||
import * as currentview from "../../assets/layers/current_view/current_view.json"
 | 
					import currentview from "../../assets/layers/current_view/current_view.json"
 | 
				
			||||||
import { CheckBox } from "../Input/Checkboxes"
 | 
					import { CheckBox } from "../Input/Checkboxes"
 | 
				
			||||||
import BackgroundMapSwitch from "../BigComponents/BackgroundMapSwitch"
 | 
					import BackgroundMapSwitch from "../BigComponents/BackgroundMapSwitch"
 | 
				
			||||||
import { Feature, FeatureCollection, Point } from "geojson"
 | 
					import { Feature, FeatureCollection, Point } from "geojson"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,7 @@ import { FixedUiElement } from "../Base/FixedUiElement"
 | 
				
			||||||
import ShowDataLayer from "../ShowDataLayer/ShowDataLayer"
 | 
					import ShowDataLayer from "../ShowDataLayer/ShowDataLayer"
 | 
				
			||||||
import BaseUIElement from "../BaseUIElement"
 | 
					import BaseUIElement from "../BaseUIElement"
 | 
				
			||||||
import Toggle from "./Toggle"
 | 
					import Toggle from "./Toggle"
 | 
				
			||||||
import * as matchpoint from "../../assets/layers/matchpoint/matchpoint.json"
 | 
					import matchpoint from "../../assets/layers/matchpoint/matchpoint.json"
 | 
				
			||||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
 | 
					import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
 | 
				
			||||||
import FilteredLayer from "../../Models/FilteredLayer"
 | 
					import FilteredLayer from "../../Models/FilteredLayer"
 | 
				
			||||||
import { ElementStorage } from "../../Logic/ElementStorage"
 | 
					import { ElementStorage } from "../../Logic/ElementStorage"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -61,17 +61,6 @@ export class TextField extends InputElement<string> {
 | 
				
			||||||
        return this._isValid(t)
 | 
					        return this._isValid(t)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static test() {
 | 
					 | 
				
			||||||
        const placeholder = new UIEventSource<string>("placeholder")
 | 
					 | 
				
			||||||
        const tf = new TextField({
 | 
					 | 
				
			||||||
            placeholder,
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
        const html = <HTMLInputElement>tf.InnerConstructElement().children[0]
 | 
					 | 
				
			||||||
        html.placeholder // => 'placeholder'
 | 
					 | 
				
			||||||
        placeholder.setData("another piece of text")
 | 
					 | 
				
			||||||
        html.placeholder // => "another piece of text"
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * // should update placeholders dynamically
 | 
					     * // should update placeholders dynamically
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -52,10 +52,6 @@ export class TextFieldDef {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protectedisValid(s: string, _: (() => string) | undefined): boolean {
 | 
					 | 
				
			||||||
        return true
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public getFeedback(s: string): Translation {
 | 
					    public getFeedback(s: string): Translation {
 | 
				
			||||||
        const tr = Translations.t.validation[this.name]
 | 
					        const tr = Translations.t.validation[this.name]
 | 
				
			||||||
        if (tr !== undefined) {
 | 
					        if (tr !== undefined) {
 | 
				
			||||||
| 
						 | 
					@ -82,6 +78,9 @@ export class TextFieldDef {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        options["textArea"] = this.name === "text"
 | 
					        options["textArea"] = this.name === "text"
 | 
				
			||||||
 | 
					        if (this.name === "text") {
 | 
				
			||||||
 | 
					            options["htmlType"] = "area"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const self = this
 | 
					        const self = this
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -589,7 +588,7 @@ class StringTextField extends TextFieldDef {
 | 
				
			||||||
class TextTextField extends TextFieldDef {
 | 
					class TextTextField extends TextFieldDef {
 | 
				
			||||||
    declare inputmode: "text"
 | 
					    declare inputmode: "text"
 | 
				
			||||||
    constructor() {
 | 
					    constructor() {
 | 
				
			||||||
        super("text", "A longer piece of text")
 | 
					        super("text", "A longer piece of text. Uses an textArea instead of a textField")
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,10 +1,10 @@
 | 
				
			||||||
import { DropDown } from "./Input/DropDown"
 | 
					import { DropDown } from "./Input/DropDown"
 | 
				
			||||||
import Locale from "./i18n/Locale"
 | 
					import Locale from "./i18n/Locale"
 | 
				
			||||||
import BaseUIElement from "./BaseUIElement"
 | 
					import BaseUIElement from "./BaseUIElement"
 | 
				
			||||||
import * as native from "../assets/language_native.json"
 | 
					import native from "../assets/language_native.json"
 | 
				
			||||||
import * as language_translations from "../assets/language_translations.json"
 | 
					import language_translations from "../assets/language_translations.json"
 | 
				
			||||||
import { Translation } from "./i18n/Translation"
 | 
					import { Translation } from "./i18n/Translation"
 | 
				
			||||||
import * as used_languages from "../assets/generated/used_languages.json"
 | 
					import used_languages from "../assets/generated/used_languages.json"
 | 
				
			||||||
import Lazy from "./Base/Lazy"
 | 
					import Lazy from "./Base/Lazy"
 | 
				
			||||||
import Toggle from "./Input/Toggle"
 | 
					import Toggle from "./Input/Toggle"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -35,9 +35,8 @@ export default class LanguagePicker extends Toggle {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static hybrid(lang: string): Translation {
 | 
					    private static hybrid(lang: string): Translation {
 | 
				
			||||||
        const nativeText = native[lang] ?? lang
 | 
					        const nativeText = native[lang] ?? lang
 | 
				
			||||||
        const allTranslations = language_translations["default"] ?? language_translations
 | 
					 | 
				
			||||||
        const translation = {}
 | 
					        const translation = {}
 | 
				
			||||||
        const trans = allTranslations[lang]
 | 
					        const trans = language_translations[lang]
 | 
				
			||||||
        if (trans === undefined) {
 | 
					        if (trans === undefined) {
 | 
				
			||||||
            return new Translation({ "*": nativeText })
 | 
					            return new Translation({ "*": nativeText })
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					@ -45,7 +44,7 @@ export default class LanguagePicker extends Toggle {
 | 
				
			||||||
            if (key.startsWith("_")) {
 | 
					            if (key.startsWith("_")) {
 | 
				
			||||||
                continue
 | 
					                continue
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            const translationInKey = allTranslations[lang][key]
 | 
					            const translationInKey = language_translations[lang][key]
 | 
				
			||||||
            if (nativeText.toLowerCase() === translationInKey.toLowerCase()) {
 | 
					            if (nativeText.toLowerCase() === translationInKey.toLowerCase()) {
 | 
				
			||||||
                translation[key] = nativeText
 | 
					                translation[key] = nativeText
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
import { SearchablePillsSelector } from "../Input/SearchableMappingsSelector"
 | 
					import { SearchablePillsSelector } from "../Input/SearchableMappingsSelector"
 | 
				
			||||||
import { Store } from "../../Logic/UIEventSource"
 | 
					import { Store } from "../../Logic/UIEventSource"
 | 
				
			||||||
import BaseUIElement from "../BaseUIElement"
 | 
					import BaseUIElement from "../BaseUIElement"
 | 
				
			||||||
import * as all_languages from "../../assets/language_translations.json"
 | 
					import all_languages from "../../assets/language_translations.json"
 | 
				
			||||||
import { Translation } from "../i18n/Translation"
 | 
					import { Translation } from "../i18n/Translation"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class AllLanguagesSelector extends SearchablePillsSelector<string> {
 | 
					export class AllLanguagesSelector extends SearchablePillsSelector<string> {
 | 
				
			||||||
| 
						 | 
					@ -18,7 +18,7 @@ export class AllLanguagesSelector extends SearchablePillsSelector<string> {
 | 
				
			||||||
            hasPriority?: Store<boolean>
 | 
					            hasPriority?: Store<boolean>
 | 
				
			||||||
        }[] = []
 | 
					        }[] = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const langs = options?.supportedLanguages ?? all_languages["default"] ?? all_languages
 | 
					        const langs = options?.supportedLanguages ?? all_languages
 | 
				
			||||||
        for (const ln in langs) {
 | 
					        for (const ln in langs) {
 | 
				
			||||||
            let languageInfo: Record<string, string> & { _meta?: { countries: string[] } } =
 | 
					            let languageInfo: Record<string, string> & { _meta?: { countries: string[] } } =
 | 
				
			||||||
                all_languages[ln]
 | 
					                all_languages[ln]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -35,7 +35,7 @@ import CreateMultiPolygonWithPointReuseAction from "../../Logic/Osm/Actions/Crea
 | 
				
			||||||
import { Tag } from "../../Logic/Tags/Tag"
 | 
					import { Tag } from "../../Logic/Tags/Tag"
 | 
				
			||||||
import TagApplyButton from "./TagApplyButton"
 | 
					import TagApplyButton from "./TagApplyButton"
 | 
				
			||||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
 | 
					import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
 | 
				
			||||||
import * as conflation_json from "../../assets/layers/conflation/conflation.json"
 | 
					import conflation_json from "../../assets/layers/conflation/conflation.json"
 | 
				
			||||||
import { GeoOperations } from "../../Logic/GeoOperations"
 | 
					import { GeoOperations } from "../../Logic/GeoOperations"
 | 
				
			||||||
import { LoginToggle } from "./LoginButton"
 | 
					import { LoginToggle } from "./LoginButton"
 | 
				
			||||||
import { AutoAction } from "./AutoApplyButton"
 | 
					import { AutoAction } from "./AutoApplyButton"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,7 +4,7 @@ import { UIEventSource } from "../../Logic/UIEventSource"
 | 
				
			||||||
import FeaturePipelineState from "../../Logic/State/FeaturePipelineState"
 | 
					import FeaturePipelineState from "../../Logic/State/FeaturePipelineState"
 | 
				
			||||||
import { VariableUiElement } from "../Base/VariableUIElement"
 | 
					import { VariableUiElement } from "../Base/VariableUIElement"
 | 
				
			||||||
import { OsmTags } from "../../Models/OsmFeature"
 | 
					import { OsmTags } from "../../Models/OsmFeature"
 | 
				
			||||||
import * as all_languages from "../../assets/language_translations.json"
 | 
					import all_languages from "../../assets/language_translations.json"
 | 
				
			||||||
import { Translation } from "../i18n/Translation"
 | 
					import { Translation } from "../i18n/Translation"
 | 
				
			||||||
import Combine from "../Base/Combine"
 | 
					import Combine from "../Base/Combine"
 | 
				
			||||||
import Title from "../Base/Title"
 | 
					import Title from "../Base/Title"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,6 +11,7 @@ import Toggle from "../Input/Toggle"
 | 
				
			||||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
 | 
					import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
 | 
				
			||||||
import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline"
 | 
					import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline"
 | 
				
			||||||
import FilteredLayer from "../../Models/FilteredLayer"
 | 
					import FilteredLayer from "../../Models/FilteredLayer"
 | 
				
			||||||
 | 
					import Hash from "../../Logic/Web/Hash"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class NewNoteUi extends Toggle {
 | 
					export default class NewNoteUi extends Toggle {
 | 
				
			||||||
    constructor(
 | 
					    constructor(
 | 
				
			||||||
| 
						 | 
					@ -33,7 +34,7 @@ export default class NewNoteUi extends Toggle {
 | 
				
			||||||
        text.SetClass("border rounded-sm border-grey-500")
 | 
					        text.SetClass("border rounded-sm border-grey-500")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const postNote = new SubtleButton(Svg.addSmall_svg().SetClass("max-h-7"), t.createNote)
 | 
					        const postNote = new SubtleButton(Svg.addSmall_svg().SetClass("max-h-7"), t.createNote)
 | 
				
			||||||
        postNote.onClick(async () => {
 | 
					        postNote.OnClickWithLoading(t.creating, async () => {
 | 
				
			||||||
            let txt = text.GetValue().data
 | 
					            let txt = text.GetValue().data
 | 
				
			||||||
            if (txt === undefined || txt === "") {
 | 
					            if (txt === undefined || txt === "") {
 | 
				
			||||||
                return
 | 
					                return
 | 
				
			||||||
| 
						 | 
					@ -63,6 +64,7 @@ export default class NewNoteUi extends Toggle {
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            state?.featurePipeline?.InjectNewPoint(feature)
 | 
					            state?.featurePipeline?.InjectNewPoint(feature)
 | 
				
			||||||
            state.selectedElement?.setData(feature)
 | 
					            state.selectedElement?.setData(feature)
 | 
				
			||||||
 | 
					            Hash.hash.setData(feature.properties.id)
 | 
				
			||||||
            text.GetValue().setData("")
 | 
					            text.GetValue().setData("")
 | 
				
			||||||
            isCreated.setData(true)
 | 
					            isCreated.setData(true)
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
| 
						 | 
					@ -73,12 +75,12 @@ export default class NewNoteUi extends Toggle {
 | 
				
			||||||
            new Combine([
 | 
					            new Combine([
 | 
				
			||||||
                new Toggle(
 | 
					                new Toggle(
 | 
				
			||||||
                    undefined,
 | 
					                    undefined,
 | 
				
			||||||
                    t.warnAnonymous.SetClass("alert"),
 | 
					                    t.warnAnonymous.SetClass("block alert"),
 | 
				
			||||||
                    state?.osmConnection?.isLoggedIn
 | 
					                    state?.osmConnection?.isLoggedIn
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
                new Toggle(
 | 
					                new Toggle(
 | 
				
			||||||
                    postNote,
 | 
					                    postNote,
 | 
				
			||||||
                    t.textNeeded.SetClass("alert"),
 | 
					                    t.textNeeded.SetClass("block alert"),
 | 
				
			||||||
                    text.GetValue().map((txt) => txt?.length > 3)
 | 
					                    text.GetValue().map((txt) => txt?.length > 3)
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
            ]).SetClass("flex justify-end items-center"),
 | 
					            ]).SetClass("flex justify-end items-center"),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,7 @@ import Loc from "../../Models/Loc"
 | 
				
			||||||
import Minimap from "../Base/Minimap"
 | 
					import Minimap from "../Base/Minimap"
 | 
				
			||||||
import ShowDataLayer from "../ShowDataLayer/ShowDataLayer"
 | 
					import ShowDataLayer from "../ShowDataLayer/ShowDataLayer"
 | 
				
			||||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
 | 
					import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
 | 
				
			||||||
import * as left_right_style_json from "../../assets/layers/left_right_style/left_right_style.json"
 | 
					import left_right_style_json from "../../assets/layers/left_right_style/left_right_style.json"
 | 
				
			||||||
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"
 | 
					import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"
 | 
				
			||||||
import { SpecialVisualization } from "../SpecialVisualization"
 | 
					import { SpecialVisualization } from "../SpecialVisualization"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,7 +15,7 @@ import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeature
 | 
				
			||||||
import ShowDataMultiLayer from "../ShowDataLayer/ShowDataMultiLayer"
 | 
					import ShowDataMultiLayer from "../ShowDataLayer/ShowDataMultiLayer"
 | 
				
			||||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
 | 
					import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
 | 
				
			||||||
import { BBox } from "../../Logic/BBox"
 | 
					import { BBox } from "../../Logic/BBox"
 | 
				
			||||||
import * as split_point from "../../assets/layers/split_point/split_point.json"
 | 
					import split_point from "../../assets/layers/split_point/split_point.json"
 | 
				
			||||||
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
 | 
					import { OsmConnection } from "../../Logic/Osm/OsmConnection"
 | 
				
			||||||
import { Changes } from "../../Logic/Osm/Changes"
 | 
					import { Changes } from "../../Logic/Osm/Changes"
 | 
				
			||||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
 | 
					import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -136,7 +136,7 @@ export default class ShowDataLayerImplementation {
 | 
				
			||||||
        if (this._leafletMap.data === undefined) {
 | 
					        if (this._leafletMap.data === undefined) {
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        const v = this.leafletLayersPerId.get(selected.properties.id + selected.geometry.type)
 | 
					        const v = this.leafletLayersPerId.get(selected.properties.id)
 | 
				
			||||||
        if (v === undefined) {
 | 
					        if (v === undefined) {
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					@ -335,7 +335,20 @@ export default class ShowDataLayerImplementation {
 | 
				
			||||||
            icon: L.divIcon(style),
 | 
					            icon: L.divIcon(style),
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Creates a function which, for the given feature, will open the featureInfoBox (and lazyly create it)
 | 
				
			||||||
 | 
					     * This function is cached
 | 
				
			||||||
 | 
					     * @param feature
 | 
				
			||||||
 | 
					     * @param key
 | 
				
			||||||
 | 
					     * @param layer
 | 
				
			||||||
 | 
					     * @private
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
    private createActivateFunction(feature, key: string, layer: LayerConfig): (event) => void {
 | 
					    private createActivateFunction(feature, key: string, layer: LayerConfig): (event) => void {
 | 
				
			||||||
 | 
					        if (this.leafletLayersPerId.has(key)) {
 | 
				
			||||||
 | 
					            return this.leafletLayersPerId.get(key).activateFunc
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let infobox: ScrollableFullScreen = undefined
 | 
					        let infobox: ScrollableFullScreen = undefined
 | 
				
			||||||
        const self = this
 | 
					        const self = this
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -373,12 +386,7 @@ export default class ShowDataLayerImplementation {
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        const key = feature.properties.id
 | 
					        const key = feature.properties.id
 | 
				
			||||||
        let activate: (event) => void
 | 
					        const activate = this.createActivateFunction(feature, key, layer)
 | 
				
			||||||
        if (this.leafletLayersPerId.has(key)) {
 | 
					 | 
				
			||||||
            activate = this.leafletLayersPerId.get(key).activateFunc
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            activate = this.createActivateFunction(feature, key, layer)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // We also have to open on rightclick, doubleclick, ... as users sometimes do this. See #1219
 | 
					        // We also have to open on rightclick, doubleclick, ... as users sometimes do this. See #1219
 | 
				
			||||||
        leafletLayer.on({
 | 
					        leafletLayer.on({
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,7 +5,7 @@ import ShowDataLayer from "./ShowDataLayer"
 | 
				
			||||||
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"
 | 
					import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"
 | 
				
			||||||
import { GeoOperations } from "../../Logic/GeoOperations"
 | 
					import { GeoOperations } from "../../Logic/GeoOperations"
 | 
				
			||||||
import { Tiles } from "../../Models/TileRange"
 | 
					import { Tiles } from "../../Models/TileRange"
 | 
				
			||||||
import * as clusterstyle from "../../assets/layers/cluster_style/cluster_style.json"
 | 
					import clusterstyle from "../../assets/layers/cluster_style/cluster_style.json"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class ShowTileInfo {
 | 
					export default class ShowTileInfo {
 | 
				
			||||||
    public static readonly styling = new LayerConfig(clusterstyle, "ShowTileInfo", true)
 | 
					    public static readonly styling = new LayerConfig(clusterstyle, "ShowTileInfo", true)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -612,8 +612,8 @@ export default class SpecialVisualizations {
 | 
				
			||||||
                                special: {
 | 
					                                special: {
 | 
				
			||||||
                                    type: "multi",
 | 
					                                    type: "multi",
 | 
				
			||||||
                                    key: "_doors_from_building_properties",
 | 
					                                    key: "_doors_from_building_properties",
 | 
				
			||||||
                                    tagRendering: {
 | 
					                                    tagrendering: {
 | 
				
			||||||
                                        render: "The building containing this feature has a <a href='#{id}'>door</a> of width {entrance:width}",
 | 
					                                        en: "The building containing this feature has a <a href='#{id}'>door</a> of width {entrance:width}",
 | 
				
			||||||
                                    },
 | 
					                                    },
 | 
				
			||||||
                                },
 | 
					                                },
 | 
				
			||||||
                            },
 | 
					                            },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,6 +5,10 @@ import { QueryParameters } from "../../Logic/Web/QueryParameters"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class Locale {
 | 
					export default class Locale {
 | 
				
			||||||
    public static showLinkToWeblate: UIEventSource<boolean> = new UIEventSource<boolean>(false)
 | 
					    public static showLinkToWeblate: UIEventSource<boolean> = new UIEventSource<boolean>(false)
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Indicates that -if showLinkToWeblate is true- a link on mobile mode is shown as well
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static showLinkOnMobile: UIEventSource<boolean> = new UIEventSource<boolean>(false)
 | 
				
			||||||
    public static language: UIEventSource<string> = Locale.setup()
 | 
					    public static language: UIEventSource<string> = Locale.setup()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static setup() {
 | 
					    private static setup() {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
import { FixedUiElement } from "../Base/FixedUiElement"
 | 
					import { FixedUiElement } from "../Base/FixedUiElement"
 | 
				
			||||||
import { Translation, TypedTranslation } from "./Translation"
 | 
					import { Translation, TypedTranslation } from "./Translation"
 | 
				
			||||||
import BaseUIElement from "../BaseUIElement"
 | 
					import BaseUIElement from "../BaseUIElement"
 | 
				
			||||||
import * as known_languages from "../../assets/generated/used_languages.json"
 | 
					import known_languages from "../../assets/generated/used_languages.json"
 | 
				
			||||||
import CompiledTranslations from "../../assets/generated/CompiledTranslations"
 | 
					import CompiledTranslations from "../../assets/generated/CompiledTranslations"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class Translations {
 | 
					export default class Translations {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										2
									
								
								Utils.ts
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								Utils.ts
									
										
									
									
									
								
							| 
						 | 
					@ -1,4 +1,4 @@
 | 
				
			||||||
import * as colors from "./assets/colors.json"
 | 
					import colors from "./assets/colors.json"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class Utils {
 | 
					export class Utils {
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,4 @@
 | 
				
			||||||
import * as used_languages from "../assets/generated/used_languages.json"
 | 
					import used_languages from "../assets/generated/used_languages.json"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class LanguageUtils {
 | 
					export default class LanguageUtils {
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -192,7 +192,7 @@
 | 
				
			||||||
            "fr": "Pilier.",
 | 
					            "fr": "Pilier.",
 | 
				
			||||||
            "de": "Überflurhydrant.",
 | 
					            "de": "Überflurhydrant.",
 | 
				
			||||||
            "it": "Soprasuolo.",
 | 
					            "it": "Soprasuolo.",
 | 
				
			||||||
            "nl": "Pillaar type.",
 | 
					            "nl": "Bovengrondse brandkraan.",
 | 
				
			||||||
            "es": "De pilar.",
 | 
					            "es": "De pilar.",
 | 
				
			||||||
            "ca": "De pilar."
 | 
					            "ca": "De pilar."
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
| 
						 | 
					@ -257,7 +257,7 @@
 | 
				
			||||||
            "fr": "Enterré.",
 | 
					            "fr": "Enterré.",
 | 
				
			||||||
            "de": "Unterflurhydrant.",
 | 
					            "de": "Unterflurhydrant.",
 | 
				
			||||||
            "it": "Sottosuolo.",
 | 
					            "it": "Sottosuolo.",
 | 
				
			||||||
            "nl": "Ondergronds type.",
 | 
					            "nl": "Ondergrondse brandkraan.",
 | 
				
			||||||
            "ca": "Subterrani.",
 | 
					            "ca": "Subterrani.",
 | 
				
			||||||
            "es": "Bajo tierra."
 | 
					            "es": "Bajo tierra."
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
| 
						 | 
					@ -361,6 +361,29 @@
 | 
				
			||||||
        "nl": "Pijpdiameter:{canonical(fire_hydrant:diameter)}"
 | 
					        "nl": "Pijpdiameter:{canonical(fire_hydrant:diameter)}"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "id": "hydrant-number-of-couplings",
 | 
				
			||||||
 | 
					      "question": {
 | 
				
			||||||
 | 
					        "en": "How many couplings does this fire hydrant have?",
 | 
				
			||||||
 | 
					        "de": "Wie viele Kupplungen hat dieser Hydrant?",
 | 
				
			||||||
 | 
					        "nl": "Hoe veel koppelingen bezit deze brandkraan?",
 | 
				
			||||||
 | 
					        "ca": "Quants acoblaments té aquest hidrant?"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "freeform": {
 | 
				
			||||||
 | 
					        "key": "couplings",
 | 
				
			||||||
 | 
					        "placeholder": {
 | 
				
			||||||
 | 
					          "en": "Number of couplings",
 | 
				
			||||||
 | 
					          "de": "Anzahl der Kupplungen",
 | 
				
			||||||
 | 
					          "nl": "Aantal koppelingen"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "type": "int"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "render": {
 | 
				
			||||||
 | 
					        "en": "Number of couplings: {couplings}",
 | 
				
			||||||
 | 
					        "de": "Anzahl der Kupplungen: {couplings}",
 | 
				
			||||||
 | 
					        "nl": "Aantal koppelingen: {couplings}"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      "id": "hydrant-couplings",
 | 
					      "id": "hydrant-couplings",
 | 
				
			||||||
      "question": {
 | 
					      "question": {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -62,6 +62,118 @@
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      ]
 | 
					      ]
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "id": "translations-title",
 | 
				
			||||||
 | 
					      "group": "translations",
 | 
				
			||||||
 | 
					      "render": "<h3>Translating MapComplete</h3>"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "group": "translations",
 | 
				
			||||||
 | 
					      "id": "translation-mode",
 | 
				
			||||||
 | 
					      "question": {
 | 
				
			||||||
 | 
					        "en": "Do you want to help translating MapComplete?"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "mappings": [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "if": "mapcomplete-translation-mode=false",
 | 
				
			||||||
 | 
					          "then": {
 | 
				
			||||||
 | 
					            "en": "Don't show a button to quickly change translations"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "if": "mapcomplete-translation-mode=true",
 | 
				
			||||||
 | 
					          "then": {
 | 
				
			||||||
 | 
					            "en": "Show a button to quickly open translations when using MapComplete on a big screen"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "if": "mapcomplete-translation-mode=mobile",
 | 
				
			||||||
 | 
					          "then": {
 | 
				
			||||||
 | 
					            "en": "Always show the translation buttons, including on mobile"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ]
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "group": "translations",
 | 
				
			||||||
 | 
					      "id": "translation-help",
 | 
				
			||||||
 | 
					      "mappings": [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "if": {
 | 
				
			||||||
 | 
					            "or": [
 | 
				
			||||||
 | 
					              "mapcomplete-translation-mode=yes",
 | 
				
			||||||
 | 
					              "mapcomplete-translation-mode=true",
 | 
				
			||||||
 | 
					              "mapcomplete-translation-mode=mobile"
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "then": {
 | 
				
			||||||
 | 
					            "ca": "Fes clic a la icona 'tradueix' al costat d'una cadena per introduir o actualitzar un fragment de text. Necessites un compte de Weblate per a això. Crea'n un amb el teu nom d'usuari OSM per desbloquejar automàticament el mode de traducció.",
 | 
				
			||||||
 | 
					            "da": "Klik på 'oversæt'-ikonet ved siden af en streng for at indtaste eller opdatere et stykke tekst. Du skal have en Weblate-konto for at kunne gøre dette. Opret en med dit OSM-brugernavn for automatisk at låse oversættelsestilstanden op.",
 | 
				
			||||||
 | 
					            "de": "Klicken Sie auf das Übersetzungssymbol neben einer Zeichenfolge, um den Übersetzungstext einzugeben oder zu aktualisieren. Dazu benötigen Sie ein Weblate-Konto. Erstellen Sie eines mit Ihrem OSM-Benutzernamen, um den Übersetzungsmodus automatisch freizuschalten.",
 | 
				
			||||||
 | 
					            "en": "Click the 'translate'-icon next to a string to enter or update a piece of text. You need a Weblate-account for this. Create one with your OSM-username to automatically unlock translation mode.",
 | 
				
			||||||
 | 
					            "es": "Haz clic en el icono 'traducir' al lado de una cadena para introducir o actualizar un texto. Necesitas una cuenta de Weblate para esto. Crea una con tu usuario de OSM para desbloquear el modo de traducción automáticamente.",
 | 
				
			||||||
 | 
					            "fr": "Cliquez sur l'icône \"traduire\" à côté d'une chaîne de caractères pour saisir ou mettre à jour la chaine de texte. Vous aurez besoin d'un compte Weblate pour cela. Créez-en un avec votre nom d'utilisateur OSM pour déverrouiller automatiquement le mode traduction.",
 | 
				
			||||||
 | 
					            "nl": "Klik op het 'vertaal'-icoontje die naast een stukje tekst staat om deze tekst te vertalen of aan te passen. Hiervoor heb je een (gratis) Weblate-account nodig. Indien je jouw account maakt met dezelfde naam als je OSM-gebruikersnaam, dan zullen de vertaalknoppen automatisch verschijnen.",
 | 
				
			||||||
 | 
					            "zh_Hant": "點字串旁邊的 'translate'-icon 來輸入或是更新一段文字。你需要 Weblate 帳號。用你 OSM 帳號名稱來創建帳號,並且自動解鎖翻譯模式。"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "icon": "translate"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ]
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "group": "translations",
 | 
				
			||||||
 | 
					      "id": "translation-completeness",
 | 
				
			||||||
 | 
					      "render": {
 | 
				
			||||||
 | 
					        "ca": "Les traduccions de {_theme} en {_language} tenen un {_translation_percentage}%: {_translation_translated_count} cadenes de {_translation_total} estan traduïdes",
 | 
				
			||||||
 | 
					        "da": "Oversættelser for {_theme} i {_language} er på {_translation_percentage}%: {_translation_translated_count} strenge ud af {_translation_total} er oversat",
 | 
				
			||||||
 | 
					        "de": "Die Übersetzung für {_theme} in {_language} ist zu {_translation_percentage}% vollständig: {_translation_translated_count} Zeichenfolgen von {_translation_total} sind übersetzt",
 | 
				
			||||||
 | 
					        "en": "Translations for {_theme} in {_language} are at {_translation_percentage}%: {_translation_translated_count} strings out of {_translation_total} are translated",
 | 
				
			||||||
 | 
					        "es": "Las traducciones para {_theme} en {_language} están al {_translation_percentage}%: {_translation_translated_count} cadenas de {_translation_total} están traducidas",
 | 
				
			||||||
 | 
					        "id": "Terjemahan untuk {_theme} dalam {_language} masih {_translation_percentage}%: {_translation_translated_count} string dari {_translation_total} diterjemahkan",
 | 
				
			||||||
 | 
					        "nb_NO": "Oversettelsen for {_theme} i {_language} har {_translation_percentage}% dekning: {_translation_translated_count} strenger av {_translation_total} har blitt oversatt",
 | 
				
			||||||
 | 
					        "nl": "Vertalingen voor {_theme} in {_language} zijn momenteel op {_translation_percentage}%: van {_translation_total} teksten zijn er reeds {_translation_translated_count} vertaald",
 | 
				
			||||||
 | 
					        "zh_Hant": "{_theme} 的 {_language} 翻譯目前是 {_translation_percentage}%:{_translation_total} 中的 {_translation_translated_count} 已經翻譯了"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "condition": {
 | 
				
			||||||
 | 
					        "or": [
 | 
				
			||||||
 | 
					          "mapcomplete-translation-mode=yes",
 | 
				
			||||||
 | 
					          "mapcomplete-translation-mode=true",
 | 
				
			||||||
 | 
					          "mapcomplete-translation-mode=mobile"
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "mappings": [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "if": "_translation_percentage=100",
 | 
				
			||||||
 | 
					          "icon": "confirm",
 | 
				
			||||||
 | 
					          "then": {
 | 
				
			||||||
 | 
					            "en": "Completely translated",
 | 
				
			||||||
 | 
					            "nl": "Volledig vertaald"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ]
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "id": "translation-links",
 | 
				
			||||||
 | 
					      "group": "translations",
 | 
				
			||||||
 | 
					      "condition": {
 | 
				
			||||||
 | 
					        "and": [
 | 
				
			||||||
 | 
					          "_translation_links~*",
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "or": [
 | 
				
			||||||
 | 
					              "mapcomplete-translation-mode=true",
 | 
				
			||||||
 | 
					              "mapcomplete-translation-mode=mobile"
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "render": {
 | 
				
			||||||
 | 
					        "special": {
 | 
				
			||||||
 | 
					          "type": "multi",
 | 
				
			||||||
 | 
					          "key": "_translation_links",
 | 
				
			||||||
 | 
					          "tagrendering": "<a href='{link}' target='_blank'>Translate entries of {id}</a>"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      "id": "verified-mastodon",
 | 
					      "id": "verified-mastodon",
 | 
				
			||||||
      "mappings": [
 | 
					      "mappings": [
 | 
				
			||||||
| 
						 | 
					@ -85,6 +197,18 @@
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      ]
 | 
					      ]
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "id": "cscount-thanks",
 | 
				
			||||||
 | 
					      "mappings": [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "if": "_csCount>0",
 | 
				
			||||||
 | 
					          "then": {
 | 
				
			||||||
 | 
					            "en": "You have made changes on {_csCount} different occasions! That is awesome!"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "icon": "party"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ]
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      "id": "translation-thanks",
 | 
					      "id": "translation-thanks",
 | 
				
			||||||
      "mappings": [
 | 
					      "mappings": [
 | 
				
			||||||
| 
						 | 
					@ -110,13 +234,41 @@
 | 
				
			||||||
            "de": "Sie haben Code zu MapComplete mit {_code_contributions} Commits beigetragen! Das ist großartig!",
 | 
					            "de": "Sie haben Code zu MapComplete mit {_code_contributions} Commits beigetragen! Das ist großartig!",
 | 
				
			||||||
            "nl": "Je hebt mee geprogrammeerd aan MapComplete met {_code_contributions} commits! Das supercool van je! Bedankt hiervoor!"
 | 
					            "nl": "Je hebt mee geprogrammeerd aan MapComplete met {_code_contributions} commits! Das supercool van je! Bedankt hiervoor!"
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
          "icon": "party"
 | 
					          "icon": "party",
 | 
				
			||||||
 | 
					          "hideInAnswer": true
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ]
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "id": "show_debug",
 | 
				
			||||||
 | 
					      "question": {
 | 
				
			||||||
 | 
					        "en": "Show user settings debug info?"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "mappings": [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "if": "mapcomplete-show_debug=yes",
 | 
				
			||||||
 | 
					          "then": {
 | 
				
			||||||
 | 
					            "en": "Show debug info"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "if": "mapcomplete-show_debug=no",
 | 
				
			||||||
 | 
					          "then": {
 | 
				
			||||||
 | 
					            "en": "Don't show debug info"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "if": "mapcomplete-show_debug=",
 | 
				
			||||||
 | 
					          "then": {
 | 
				
			||||||
 | 
					            "en": "Don't show debug info"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "hideInAnswer": true
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      ]
 | 
					      ]
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      "id": "debug",
 | 
					      "id": "debug",
 | 
				
			||||||
      "condition": "_name=Pieter Vander Vennet",
 | 
					      "condition": "mapcomplete-show_debug=yes",
 | 
				
			||||||
      "render": "{all_tags()}"
 | 
					      "render": "{all_tags()}"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,6 +31,65 @@
 | 
				
			||||||
    "physiotherapist",
 | 
					    "physiotherapist",
 | 
				
			||||||
    "dentist",
 | 
					    "dentist",
 | 
				
			||||||
    "hospital",
 | 
					    "hospital",
 | 
				
			||||||
    "pharmacy"
 | 
					    "pharmacy",
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "builtin": "shops",
 | 
				
			||||||
 | 
					      "override": {
 | 
				
			||||||
 | 
					        "id": "medical-shops",
 | 
				
			||||||
 | 
					        "minzoom": 13,
 | 
				
			||||||
 | 
					        "=filter": [
 | 
				
			||||||
 | 
					          "open_now",
 | 
				
			||||||
 | 
					          "accepts_cash",
 | 
				
			||||||
 | 
					          "accepts_cards"
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "=presets": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "title": {
 | 
				
			||||||
 | 
					              "en": "a medical supply shop"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "tags": [
 | 
				
			||||||
 | 
					              "shop=medical_supply"
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "title": {
 | 
				
			||||||
 | 
					              "en": "a hearing aids shop"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "tags": [
 | 
				
			||||||
 | 
					              "shop=hearing_aids"
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "title": {
 | 
				
			||||||
 | 
					              "en": "an optician"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "tags": [
 | 
				
			||||||
 | 
					              "shop=optician"
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "source": {
 | 
				
			||||||
 | 
					          "osmTags": {
 | 
				
			||||||
 | 
					            "and+": [
 | 
				
			||||||
 | 
					              {
 | 
				
			||||||
 | 
					                "or": [
 | 
				
			||||||
 | 
					                  "shop=medical_supply",
 | 
				
			||||||
 | 
					                  "shop=hearing_aids",
 | 
				
			||||||
 | 
					                  "shop=optician"
 | 
				
			||||||
 | 
					                ]
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "builtin": "shops",
 | 
				
			||||||
 | 
					      "=presets": [],
 | 
				
			||||||
 | 
					      "=name": null,
 | 
				
			||||||
 | 
					      "override": {
 | 
				
			||||||
 | 
					        "minzoom": 19
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  ]
 | 
					  ]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -102,7 +102,7 @@
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          "id": "uk_addresses_embedding_outline",
 | 
					          "id": "uk_addresses_embedding_outline",
 | 
				
			||||||
          "render": "<b>Warning: </b>This point lies within a building or area for which we already have an address. You should only add this address if it is different. <br>The number and street name we have for the <a href='#{_embedding_object:id}' target='blank'>existing address</a> is <b>{_embedding_object:addr:housenumber} {_embedding_object:addr:street}</b>",
 | 
					          "render": "<b>Warning: </b>This point lies within a building or area for which we already have an address. You should only add this address if it is different. <br>The number and street name we have for the <a href='#{_embedding_object:id}'>existing address</a> is <b>{_embedding_object:addr:housenumber} {_embedding_object:addr:street}</b>",
 | 
				
			||||||
          "mappings": [
 | 
					          "mappings": [
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
              "if": "_embedding_object:id=true",
 | 
					              "if": "_embedding_object:id=true",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -862,6 +862,10 @@ video {
 | 
				
			||||||
  margin-bottom: 6rem;
 | 
					  margin-bottom: 6rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.ml-1 {
 | 
				
			||||||
 | 
					  margin-left: 0.25rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.mb-2 {
 | 
					.mb-2 {
 | 
				
			||||||
  margin-bottom: 0.5rem;
 | 
					  margin-bottom: 0.5rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -910,10 +914,6 @@ video {
 | 
				
			||||||
  margin-bottom: 2rem;
 | 
					  margin-bottom: 2rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.ml-1 {
 | 
					 | 
				
			||||||
  margin-left: 0.25rem;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.mb-1 {
 | 
					.mb-1 {
 | 
				
			||||||
  margin-bottom: 0.25rem;
 | 
					  margin-bottom: 0.25rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1027,6 +1027,10 @@ video {
 | 
				
			||||||
  height: 50%;
 | 
					  height: 50%;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.h-3 {
 | 
				
			||||||
 | 
					  height: 0.75rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.h-screen {
 | 
					.h-screen {
 | 
				
			||||||
  height: 100vh;
 | 
					  height: 100vh;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1047,10 +1051,6 @@ video {
 | 
				
			||||||
  height: 0px;
 | 
					  height: 0px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.h-3 {
 | 
					 | 
				
			||||||
  height: 0.75rem;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.h-48 {
 | 
					.h-48 {
 | 
				
			||||||
  height: 12rem;
 | 
					  height: 12rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1115,6 +1115,10 @@ video {
 | 
				
			||||||
  width: 0px;
 | 
					  width: 0px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.w-3 {
 | 
				
			||||||
 | 
					  width: 0.75rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.w-screen {
 | 
					.w-screen {
 | 
				
			||||||
  width: 100vw;
 | 
					  width: 100vw;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1422,11 +1426,6 @@ video {
 | 
				
			||||||
  border-color: rgb(0 0 0 / var(--tw-border-opacity));
 | 
					  border-color: rgb(0 0 0 / var(--tw-border-opacity));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.border-gray-400 {
 | 
					 | 
				
			||||||
  --tw-border-opacity: 1;
 | 
					 | 
				
			||||||
  border-color: rgb(156 163 175 / var(--tw-border-opacity));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.border-gray-300 {
 | 
					.border-gray-300 {
 | 
				
			||||||
  --tw-border-opacity: 1;
 | 
					  --tw-border-opacity: 1;
 | 
				
			||||||
  border-color: rgb(209 213 219 / var(--tw-border-opacity));
 | 
					  border-color: rgb(209 213 219 / var(--tw-border-opacity));
 | 
				
			||||||
| 
						 | 
					@ -1437,6 +1436,11 @@ video {
 | 
				
			||||||
  border-color: rgb(252 165 165 / var(--tw-border-opacity));
 | 
					  border-color: rgb(252 165 165 / var(--tw-border-opacity));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.border-gray-400 {
 | 
				
			||||||
 | 
					  --tw-border-opacity: 1;
 | 
				
			||||||
 | 
					  border-color: rgb(156 163 175 / var(--tw-border-opacity));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.border-blue-500 {
 | 
					.border-blue-500 {
 | 
				
			||||||
  --tw-border-opacity: 1;
 | 
					  --tw-border-opacity: 1;
 | 
				
			||||||
  border-color: rgb(59 130 246 / var(--tw-border-opacity));
 | 
					  border-color: rgb(59 130 246 / var(--tw-border-opacity));
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										2
									
								
								index.ts
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								index.ts
									
										
									
									
									
								
							| 
						 | 
					@ -60,7 +60,7 @@ new Combine([
 | 
				
			||||||
        .SetClass("link-underline small")
 | 
					        .SetClass("link-underline small")
 | 
				
			||||||
        .onClick(() => {
 | 
					        .onClick(() => {
 | 
				
			||||||
            localStorage.clear()
 | 
					            localStorage.clear()
 | 
				
			||||||
            window.location.reload(true)
 | 
					            window.location.reload()
 | 
				
			||||||
        }),
 | 
					        }),
 | 
				
			||||||
]).AttachTo("centermessage") // Add an initialization and reset button if something goes wrong
 | 
					]).AttachTo("centermessage") // Add an initialization and reset button if something goes wrong
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -33,9 +33,9 @@ ShowOverlayLayerImplementation.Implement();
 | 
				
			||||||
// Miscelleanous
 | 
					// Miscelleanous
 | 
				
			||||||
Utils.DisableLongPresses()
 | 
					Utils.DisableLongPresses()
 | 
				
			||||||
if(new URLSearchParams(window.location.search).get("test") === "true"){
 | 
					if(new URLSearchParams(window.location.search).get("test") === "true"){
 | 
				
			||||||
    console.log(themeConfig["default"])
 | 
					    console.log(themeConfig)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
const layoutToUse = new LayoutConfig(themeConfig["default"])
 | 
					const layoutToUse = new LayoutConfig(themeConfig)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Workaround/legacy to keep the old paramters working as I renamed some of them
 | 
					// Workaround/legacy to keep the old paramters working as I renamed some of them
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -586,10 +586,6 @@
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "translations": {
 | 
					    "translations": {
 | 
				
			||||||
        "activateButton": "Ajudar a traduir MapComplete",
 | 
					        "activateButton": "Ajudar a traduir MapComplete",
 | 
				
			||||||
        "completeness": "Les traduccions de {theme} en {language} tenen un {percentage}%: {translated} cadenes de {total} estan traduïdes",
 | 
					 | 
				
			||||||
        "deactivate": "Deshabilitar els botons de traducció",
 | 
					 | 
				
			||||||
        "help": "Fes clic a la icona 'tradueix' al costat d'una cadena per introduir o actualitzar un fragment de text. Necessites un compte de Weblate per a això. Crea'n un amb el teu nom d'usuari OSM per desbloquejar automàticament el mode de traducció.",
 | 
					 | 
				
			||||||
        "isTranslator": "El mode de traducció està actiu, ja que el vostre nom d'usuari coincideix amb el nom d'un traductor anterior",
 | 
					 | 
				
			||||||
        "missing": "{count} cadenes sense traduir",
 | 
					        "missing": "{count} cadenes sense traduir",
 | 
				
			||||||
        "notImmediate": "Les traduccions no s'actualitzen directament. Això sol trigar uns quants dies"
 | 
					        "notImmediate": "Les traduccions no s'actualitzen directament. Això sol trigar uns quants dies"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -803,10 +803,6 @@
 | 
				
			||||||
    "translations": {
 | 
					    "translations": {
 | 
				
			||||||
        "activateButton": "Hjælp med at oversætte MapComplete",
 | 
					        "activateButton": "Hjælp med at oversætte MapComplete",
 | 
				
			||||||
        "allMissing": "Ingen oversættelser endnu",
 | 
					        "allMissing": "Ingen oversættelser endnu",
 | 
				
			||||||
        "completeness": "Oversættelser for {theme} i {language} er på {percentage}%: {translated} strenge ud af {total} er oversat",
 | 
					 | 
				
			||||||
        "deactivate": "Slå oversættelsesknapper fra",
 | 
					 | 
				
			||||||
        "help": "Klik på 'oversæt'-ikonet ved siden af en streng for at indtaste eller opdatere et stykke tekst. Du skal have en Weblate-konto for at kunne gøre dette. Opret en med dit OSM-brugernavn for automatisk at låse oversættelsestilstanden op.",
 | 
					 | 
				
			||||||
        "isTranslator": "Oversættelsestilstanden er slået til, da dit brugernavn svarer til navnet på en tidligere oversætter",
 | 
					 | 
				
			||||||
        "missing": "{count} uoversatte strenge",
 | 
					        "missing": "{count} uoversatte strenge",
 | 
				
			||||||
        "notImmediate": "Oversættelser opdateres ikke direkte. Det tager typisk et par dage"
 | 
					        "notImmediate": "Oversættelser opdateres ikke direkte. Det tager typisk et par dage"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -928,10 +928,6 @@
 | 
				
			||||||
    "translations": {
 | 
					    "translations": {
 | 
				
			||||||
        "activateButton": "MapComplete übersetzen",
 | 
					        "activateButton": "MapComplete übersetzen",
 | 
				
			||||||
        "allMissing": "Noch keine Übersetzungen",
 | 
					        "allMissing": "Noch keine Übersetzungen",
 | 
				
			||||||
        "completeness": "Die Übersetzung für {theme} in {language} ist zu {percentage}% vollständig: {translated} Zeichenfolgen von {total} sind übersetzt",
 | 
					 | 
				
			||||||
        "deactivate": "Übersetzungssymbol ausblenden",
 | 
					 | 
				
			||||||
        "help": "Klicken Sie auf das Übersetzungssymbol neben einer Zeichenfolge, um den Übersetzungstext einzugeben oder zu aktualisieren. Dazu benötigen Sie ein Weblate-Konto. Erstellen Sie eines mit Ihrem OSM-Benutzernamen, um den Übersetzungsmodus automatisch freizuschalten.",
 | 
					 | 
				
			||||||
        "isTranslator": "Der Übersetzungsmodus ist aktiv, da Ihr Benutzername mit dem Namen eines früheren Übersetzers übereinstimmt",
 | 
					 | 
				
			||||||
        "missing": "{count} nicht übersetzte Zeichenfolgen",
 | 
					        "missing": "{count} nicht übersetzte Zeichenfolgen",
 | 
				
			||||||
        "notImmediate": "Die Übersetzung wird nicht direkt aktualisiert. Dies dauert in der Regel ein paar Tage"
 | 
					        "notImmediate": "Die Übersetzung wird nicht direkt aktualisiert. Dies dauert in der Regel ein paar Tage"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -746,6 +746,7 @@
 | 
				
			||||||
        "createNote": "Create a new note",
 | 
					        "createNote": "Create a new note",
 | 
				
			||||||
        "createNoteIntro": "Is something wrong or missing on the map? Create a note here. These will be checked by volunteers.",
 | 
					        "createNoteIntro": "Is something wrong or missing on the map? Create a note here. These will be checked by volunteers.",
 | 
				
			||||||
        "createNoteTitle": "Create a new note here",
 | 
					        "createNoteTitle": "Create a new note here",
 | 
				
			||||||
 | 
					        "creating": "Creating note...",
 | 
				
			||||||
        "disableAllNoteFilters": "Disable all filters",
 | 
					        "disableAllNoteFilters": "Disable all filters",
 | 
				
			||||||
        "isClosed": "This note is resolved",
 | 
					        "isClosed": "This note is resolved",
 | 
				
			||||||
        "isCreated": "Your note has been created!",
 | 
					        "isCreated": "Your note has been created!",
 | 
				
			||||||
| 
						 | 
					@ -930,10 +931,6 @@
 | 
				
			||||||
    "translations": {
 | 
					    "translations": {
 | 
				
			||||||
        "activateButton": "Help translate MapComplete",
 | 
					        "activateButton": "Help translate MapComplete",
 | 
				
			||||||
        "allMissing": "No translations yet",
 | 
					        "allMissing": "No translations yet",
 | 
				
			||||||
        "completeness": "Translations for {theme} in {language} are at {percentage}%: {translated} strings out of {total} are translated",
 | 
					 | 
				
			||||||
        "deactivate": "Disable translation buttons",
 | 
					 | 
				
			||||||
        "help": "Click the 'translate'-icon next to a string to enter or update a piece of text. You need a Weblate-account for this. Create one with your OSM-username to automatically unlock translation mode.",
 | 
					 | 
				
			||||||
        "isTranslator": "Translation mode is active as your username matches the name of a previous translator",
 | 
					 | 
				
			||||||
        "missing": "{count} untranslated strings",
 | 
					        "missing": "{count} untranslated strings",
 | 
				
			||||||
        "notImmediate": "Translations are not updated directly. This typically takes a few days"
 | 
					        "notImmediate": "Translations are not updated directly. This typically takes a few days"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -708,10 +708,6 @@
 | 
				
			||||||
    "translations": {
 | 
					    "translations": {
 | 
				
			||||||
        "activateButton": "Ayuda a traducir MapComplete",
 | 
					        "activateButton": "Ayuda a traducir MapComplete",
 | 
				
			||||||
        "allMissing": "Aún sin traducciónes",
 | 
					        "allMissing": "Aún sin traducciónes",
 | 
				
			||||||
        "completeness": "Las traducciones para {theme} en {language} están al {percentage}%: {translated} cadenas de {total} están traducidas",
 | 
					 | 
				
			||||||
        "deactivate": "Deshabilitar los botones de traducción",
 | 
					 | 
				
			||||||
        "help": "Haz clic en el icono 'traducir' al lado de una cadena para introducir o actualizar un texto. Necesitas una cuenta de Weblate para esto. Crea una con tu usuario de OSM para desbloquear el modo de traducción automáticamente.",
 | 
					 | 
				
			||||||
        "isTranslator": "El modo de traducción está activo si tu nombre de usuario coincide con el nombre de un traductor anterior",
 | 
					 | 
				
			||||||
        "missing": "{count} cadenas sin traducir",
 | 
					        "missing": "{count} cadenas sin traducir",
 | 
				
			||||||
        "notImmediate": "Las traducciones no se actualizan directamente. Habitualmente esto lleva unos días"
 | 
					        "notImmediate": "Las traducciones no se actualizan directamente. Habitualmente esto lleva unos días"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -473,9 +473,6 @@
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "translations": {
 | 
					    "translations": {
 | 
				
			||||||
        "activateButton": "Aidez à traduire MapComplete",
 | 
					        "activateButton": "Aidez à traduire MapComplete",
 | 
				
			||||||
        "deactivate": "Désactiver les boutons de traduction",
 | 
					 | 
				
			||||||
        "help": "Cliquez sur l'icône \"traduire\" à côté d'une chaîne de caractères pour saisir ou mettre à jour la chaine de texte. Vous aurez besoin d'un compte Weblate pour cela. Créez-en un avec votre nom d'utilisateur OSM pour déverrouiller automatiquement le mode traduction.",
 | 
					 | 
				
			||||||
        "isTranslator": "Mode traduction activé, votre pseudo correspond à celui d’une personne de l’équipe de traduction",
 | 
					 | 
				
			||||||
        "missing": "{count} segments non traduits"
 | 
					        "missing": "{count} segments non traduits"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "userinfo": {
 | 
					    "userinfo": {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -265,8 +265,6 @@
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "translations": {
 | 
					    "translations": {
 | 
				
			||||||
        "allMissing": "Belum ada terjemahan",
 | 
					        "allMissing": "Belum ada terjemahan",
 | 
				
			||||||
        "completeness": "Terjemahan untuk {theme} dalam {language} masih {percentage}%: {translated} string dari {total} diterjemahkan",
 | 
					 | 
				
			||||||
        "isTranslator": "Mode terjemahan aktif karena nama pengguna Anda cocok dengan nama penerjemah sebelumnya",
 | 
					 | 
				
			||||||
        "notImmediate": "Terjemahan tidak diperbarui secara langsung. Biasanya memakan waktu beberapa hari"
 | 
					        "notImmediate": "Terjemahan tidak diperbarui secara langsung. Biasanya memakan waktu beberapa hari"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "validation": {
 | 
					    "validation": {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -689,9 +689,6 @@
 | 
				
			||||||
    "translations": {
 | 
					    "translations": {
 | 
				
			||||||
        "activateButton": "Bistå oversettelsen av MapComplete",
 | 
					        "activateButton": "Bistå oversettelsen av MapComplete",
 | 
				
			||||||
        "allMissing": "Ingen oversettelser enda",
 | 
					        "allMissing": "Ingen oversettelser enda",
 | 
				
			||||||
        "completeness": "Oversettelsen for {theme} i {language} har {percentage}% dekning: {translated} strenger av {total} har blitt oversatt",
 | 
					 | 
				
			||||||
        "deactivate": "Skru av oversettelsesknapper",
 | 
					 | 
				
			||||||
        "isTranslator": "Oversettelsesmodus er aktivt siden brukernavnet ditt samsvarer med navnet på forrige oversetter",
 | 
					 | 
				
			||||||
        "missing": "{count} uoversatte strenger",
 | 
					        "missing": "{count} uoversatte strenger",
 | 
				
			||||||
        "notImmediate": "Oversettelser oppdateres ikke direkte. Dette tar typisk et par dager."
 | 
					        "notImmediate": "Oversettelser oppdateres ikke direkte. Dette tar typisk et par dager."
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -928,10 +928,6 @@
 | 
				
			||||||
    "translations": {
 | 
					    "translations": {
 | 
				
			||||||
        "activateButton": "Help met het vertalen van MapComplete",
 | 
					        "activateButton": "Help met het vertalen van MapComplete",
 | 
				
			||||||
        "allMissing": "Nog geen vertalingen",
 | 
					        "allMissing": "Nog geen vertalingen",
 | 
				
			||||||
        "completeness": "Vertalingen voor {theme} in {language} zijn momenteel op {percentage}%: van {total} teksten zijn er reeds {translated} vertaald",
 | 
					 | 
				
			||||||
        "deactivate": "Verberg de vertaalknoppen",
 | 
					 | 
				
			||||||
        "help": "Klik op het 'vertaal'-icoontje die naast een stukje tekst staat om deze tekst te vertalen of aan te passen. Hiervoor heb je een (gratis) Weblate-account nodig. Indien je jouw account maakt met dezelfde naam als je OSM-gebruikersnaam, dan zullen de vertaalknoppen automatisch verschijnen.",
 | 
					 | 
				
			||||||
        "isTranslator": "Vertaalmode is actief: je gebruikersnaam is dezelfde als van een vertaler. We gaan er dus vanuit dat jij die vertaler bent",
 | 
					 | 
				
			||||||
        "missing": "{count} niet-vertaalde teksten",
 | 
					        "missing": "{count} niet-vertaalde teksten",
 | 
				
			||||||
        "notImmediate": "Vertalingen worden niet onmiddelijk geupdate. Dit duurt gemiddeld enkele dagen"
 | 
					        "notImmediate": "Vertalingen worden niet onmiddelijk geupdate. Dit duurt gemiddeld enkele dagen"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -189,8 +189,6 @@
 | 
				
			||||||
    "translations": {
 | 
					    "translations": {
 | 
				
			||||||
        "activateButton": "Pomóż przetłumaczyć MapComplete",
 | 
					        "activateButton": "Pomóż przetłumaczyć MapComplete",
 | 
				
			||||||
        "allMissing": "Brak tłumaczeń",
 | 
					        "allMissing": "Brak tłumaczeń",
 | 
				
			||||||
        "deactivate": "Wyłącz przyciski tłumaczenia",
 | 
					 | 
				
			||||||
        "isTranslator": "Tryb tłumaczenia jest aktywny, ponieważ Twoja nazwa użytkownika odpowiada nazwisku poprzedniego tłumacza",
 | 
					 | 
				
			||||||
        "notImmediate": "Tłumaczenia nie są aktualizowane bezpośrednio. Zwykle trwa to kilka dni"
 | 
					        "notImmediate": "Tłumaczenia nie są aktualizowane bezpośrednio. Zwykle trwa to kilka dni"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "userinfo": {
 | 
					    "userinfo": {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -720,10 +720,6 @@
 | 
				
			||||||
    "translations": {
 | 
					    "translations": {
 | 
				
			||||||
        "activateButton": "協助翻譯 MapComplete",
 | 
					        "activateButton": "協助翻譯 MapComplete",
 | 
				
			||||||
        "allMissing": "還沒有翻譯",
 | 
					        "allMissing": "還沒有翻譯",
 | 
				
			||||||
        "completeness": "{theme} 的 {language} 翻譯目前是 {percentage}%:{total} 中的 {translated} 已經翻譯了",
 | 
					 | 
				
			||||||
        "deactivate": "關閉翻譯按鈕",
 | 
					 | 
				
			||||||
        "help": "點字串旁邊的 'translate'-icon 來輸入或是更新一段文字。你需要 Weblate 帳號。用你 OSM 帳號名稱來創建帳號,並且自動解鎖翻譯模式。",
 | 
					 | 
				
			||||||
        "isTranslator": "翻譯模式已經啟用,你的名字符合前一位翻譯者的名字",
 | 
					 | 
				
			||||||
        "missing": "{count} 未翻譯字串",
 | 
					        "missing": "{count} 未翻譯字串",
 | 
				
			||||||
        "notImmediate": "翻譯不會直接更新,通常會需要幾天時間"
 | 
					        "notImmediate": "翻譯不會直接更新,通常會需要幾天時間"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,3 @@
 | 
				
			||||||
import * as languages from "../assets/generated/used_languages.json"
 | 
					 | 
				
			||||||
import { readFileSync, writeFileSync } from "fs"
 | 
					import { readFileSync, writeFileSync } from "fs"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -78,13 +78,13 @@ function geoJsonName(targetDir: string, x: number, y: number, z: number): string
 | 
				
			||||||
    return targetDir + "_" + z + "_" + x + "_" + y + ".geojson"
 | 
					    return targetDir + "_" + z + "_" + x + "_" + y + ".geojson"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Downloads the given feature and saves them to disk
 | 
					/// Downloads the given tilerange from overpass and saves them to disk
 | 
				
			||||||
async function downloadRaw(
 | 
					async function downloadRaw(
 | 
				
			||||||
    targetdir: string,
 | 
					    targetdir: string,
 | 
				
			||||||
    r: TileRange,
 | 
					    r: TileRange,
 | 
				
			||||||
    theme: LayoutConfig,
 | 
					    theme: LayoutConfig,
 | 
				
			||||||
    relationTracker: RelationsTracker
 | 
					    relationTracker: RelationsTracker
 | 
				
			||||||
) /* : {failed: number, skipped :number} */ {
 | 
					): Promise<{ failed: number; skipped: number }> {
 | 
				
			||||||
    let downloaded = 0
 | 
					    let downloaded = 0
 | 
				
			||||||
    let failed = 0
 | 
					    let failed = 0
 | 
				
			||||||
    let skipped = 0
 | 
					    let skipped = 0
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,14 +15,15 @@ import List from "../UI/Base/List"
 | 
				
			||||||
import SharedTagRenderings from "../Customizations/SharedTagRenderings"
 | 
					import SharedTagRenderings from "../Customizations/SharedTagRenderings"
 | 
				
			||||||
import { writeFile } from "fs"
 | 
					import { writeFile } from "fs"
 | 
				
			||||||
import Translations from "../UI/i18n/Translations"
 | 
					import Translations from "../UI/i18n/Translations"
 | 
				
			||||||
import * as themeOverview from "../assets/generated/theme_overview.json"
 | 
					import themeOverview from "../assets/generated/theme_overview.json"
 | 
				
			||||||
import DefaultGUI from "../UI/DefaultGUI"
 | 
					import DefaultGUI from "../UI/DefaultGUI"
 | 
				
			||||||
import FeaturePipelineState from "../Logic/State/FeaturePipelineState"
 | 
					import FeaturePipelineState from "../Logic/State/FeaturePipelineState"
 | 
				
			||||||
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"
 | 
					import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"
 | 
				
			||||||
import * as bookcases from "../assets/generated/themes/bookcases.json"
 | 
					import bookcases from "../assets/generated/themes/bookcases.json"
 | 
				
			||||||
import { DefaultGuiState } from "../UI/DefaultGuiState"
 | 
					import { DefaultGuiState } from "../UI/DefaultGuiState"
 | 
				
			||||||
import * as fakedom from "fake-dom"
 | 
					import fakedom from "fake-dom"
 | 
				
			||||||
import Hotkeys from "../UI/Base/Hotkeys"
 | 
					import Hotkeys from "../UI/Base/Hotkeys"
 | 
				
			||||||
 | 
					import { QueryParameters } from "../Logic/Web/QueryParameters"
 | 
				
			||||||
function WriteFile(
 | 
					function WriteFile(
 | 
				
			||||||
    filename,
 | 
					    filename,
 | 
				
			||||||
    html: BaseUIElement,
 | 
					    html: BaseUIElement,
 | 
				
			||||||
| 
						 | 
					@ -103,7 +104,7 @@ function generateWikipage() {
 | 
				
			||||||
        "! Name, link !! Genre !! Covered region !! Language !! Description !! Free materials !! Image\n" +
 | 
					        "! Name, link !! Genre !! Covered region !! Language !! Description !! Free materials !! Image\n" +
 | 
				
			||||||
        "|-"
 | 
					        "|-"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (const layout of themeOverview["default"] ?? themeOverview) {
 | 
					    for (const layout of themeOverview) {
 | 
				
			||||||
        if (layout.hideFromOverview) {
 | 
					        if (layout.hideFromOverview) {
 | 
				
			||||||
            continue
 | 
					            continue
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					@ -225,6 +226,12 @@ WriteFile("./Docs/URL_Parameters.md", QueryParameterDocumentation.GenerateQueryP
 | 
				
			||||||
if (fakedom === undefined || window === undefined) {
 | 
					if (fakedom === undefined || window === undefined) {
 | 
				
			||||||
    throw "FakeDom not initialized"
 | 
					    throw "FakeDom not initialized"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					QueryParameters.GetQueryParameter(
 | 
				
			||||||
 | 
					    "mode",
 | 
				
			||||||
 | 
					    "map",
 | 
				
			||||||
 | 
					    "The mode the application starts in, e.g. 'map', 'dashboard' or 'statistics'"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
new DefaultGUI(
 | 
					new DefaultGUI(
 | 
				
			||||||
    new FeaturePipelineState(new LayoutConfig(<any>bookcases)),
 | 
					    new FeaturePipelineState(new LayoutConfig(<any>bookcases)),
 | 
				
			||||||
    new DefaultGuiState()
 | 
					    new DefaultGuiState()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
import ScriptUtils from "./ScriptUtils"
 | 
					import ScriptUtils from "./ScriptUtils"
 | 
				
			||||||
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "fs"
 | 
					import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "fs"
 | 
				
			||||||
import * as licenses from "../assets/generated/license_info.json"
 | 
					import licenses from "../assets/generated/license_info.json"
 | 
				
			||||||
import { LayoutConfigJson } from "../Models/ThemeConfig/Json/LayoutConfigJson"
 | 
					import { LayoutConfigJson } from "../Models/ThemeConfig/Json/LayoutConfigJson"
 | 
				
			||||||
import { LayerConfigJson } from "../Models/ThemeConfig/Json/LayerConfigJson"
 | 
					import { LayerConfigJson } from "../Models/ThemeConfig/Json/LayerConfigJson"
 | 
				
			||||||
import Constants from "../Models/Constants"
 | 
					import Constants from "../Models/Constants"
 | 
				
			||||||
| 
						 | 
					@ -14,8 +14,8 @@ import {
 | 
				
			||||||
} from "../Models/ThemeConfig/Conversion/Validation"
 | 
					} from "../Models/ThemeConfig/Conversion/Validation"
 | 
				
			||||||
import { Translation } from "../UI/i18n/Translation"
 | 
					import { Translation } from "../UI/i18n/Translation"
 | 
				
			||||||
import { TagRenderingConfigJson } from "../Models/ThemeConfig/Json/TagRenderingConfigJson"
 | 
					import { TagRenderingConfigJson } from "../Models/ThemeConfig/Json/TagRenderingConfigJson"
 | 
				
			||||||
import * as questions from "../assets/tagRenderings/questions.json"
 | 
					import questions from "../assets/tagRenderings/questions.json"
 | 
				
			||||||
import * as icons from "../assets/tagRenderings/icons.json"
 | 
					import icons from "../assets/tagRenderings/icons.json"
 | 
				
			||||||
import PointRenderingConfigJson from "../Models/ThemeConfig/Json/PointRenderingConfigJson"
 | 
					import PointRenderingConfigJson from "../Models/ThemeConfig/Json/PointRenderingConfigJson"
 | 
				
			||||||
import { PrepareLayer } from "../Models/ThemeConfig/Conversion/PrepareLayer"
 | 
					import { PrepareLayer } from "../Models/ThemeConfig/Conversion/PrepareLayer"
 | 
				
			||||||
import { PrepareTheme } from "../Models/ThemeConfig/Conversion/PrepareTheme"
 | 
					import { PrepareTheme } from "../Models/ThemeConfig/Conversion/PrepareTheme"
 | 
				
			||||||
| 
						 | 
					@ -155,7 +155,7 @@ class LayerOverviewUtils {
 | 
				
			||||||
        const dict = new Map<string, TagRenderingConfigJson>()
 | 
					        const dict = new Map<string, TagRenderingConfigJson>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const validator = new ValidateTagRenderings(undefined, doesImageExist)
 | 
					        const validator = new ValidateTagRenderings(undefined, doesImageExist)
 | 
				
			||||||
        for (const key in questions["default"]) {
 | 
					        for (const key in questions) {
 | 
				
			||||||
            if (key === "id") {
 | 
					            if (key === "id") {
 | 
				
			||||||
                continue
 | 
					                continue
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
| 
						 | 
					@ -168,7 +168,7 @@ class LayerOverviewUtils {
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            dict.set(key, config)
 | 
					            dict.set(key, config)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        for (const key in icons["default"]) {
 | 
					        for (const key in icons) {
 | 
				
			||||||
            if (key === "id") {
 | 
					            if (key === "id") {
 | 
				
			||||||
                continue
 | 
					                continue
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,7 @@ import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFile, writeFi
 | 
				
			||||||
import Locale from "../UI/i18n/Locale"
 | 
					import Locale from "../UI/i18n/Locale"
 | 
				
			||||||
import Translations from "../UI/i18n/Translations"
 | 
					import Translations from "../UI/i18n/Translations"
 | 
				
			||||||
import { Translation } from "../UI/i18n/Translation"
 | 
					import { Translation } from "../UI/i18n/Translation"
 | 
				
			||||||
import * as all_known_layouts from "../assets/generated/known_layers_and_themes.json"
 | 
					import all_known_layouts from "../assets/generated/known_layers_and_themes.json"
 | 
				
			||||||
import { LayoutConfigJson } from "../Models/ThemeConfig/Json/LayoutConfigJson"
 | 
					import { LayoutConfigJson } from "../Models/ThemeConfig/Json/LayoutConfigJson"
 | 
				
			||||||
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"
 | 
					import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"
 | 
				
			||||||
import xml2js from "xml2js"
 | 
					import xml2js from "xml2js"
 | 
				
			||||||
| 
						 | 
					@ -23,7 +23,7 @@ async function createIcon(iconPath: string, size: number, alreadyWritten: string
 | 
				
			||||||
        name = name.substr(2)
 | 
					        name = name.substr(2)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const newname = `assets/generated/images/${name.replace(/\//g, "_")}${size}.png`
 | 
					    const newname = `public/assets/generated/images/${name.replace(/\//g, "_")}${size}.png`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (alreadyWritten.indexOf(newname) >= 0) {
 | 
					    if (alreadyWritten.indexOf(newname) >= 0) {
 | 
				
			||||||
        return newname
 | 
					        return newname
 | 
				
			||||||
| 
						 | 
					@ -60,7 +60,7 @@ async function createSocialImage(layout: LayoutConfig, template: "" | "Wide"): P
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        return undefined
 | 
					        return undefined
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    const path = `./assets/generated/images/social_image_${layout.id}_${template}.svg`
 | 
					    const path = `./public/assets/generated/images/social_image_${layout.id}_${template}.svg`
 | 
				
			||||||
    if (existsSync(path)) {
 | 
					    if (existsSync(path)) {
 | 
				
			||||||
        return path
 | 
					        return path
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -121,7 +121,7 @@ async function createManifest(
 | 
				
			||||||
        // This is an svg. Lets create the needed pngs and do some checkes!
 | 
					        // This is an svg. Lets create the needed pngs and do some checkes!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const whiteBackgroundPath =
 | 
					        const whiteBackgroundPath =
 | 
				
			||||||
            "./assets/generated/images/theme_" + layout.id + "_white_background.svg"
 | 
					            "./public/assets/generated/images/theme_" + layout.id + "_white_background.svg"
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            const svg = await ScriptUtils.ReadSvg(icon)
 | 
					            const svg = await ScriptUtils.ReadSvg(icon)
 | 
				
			||||||
            const width: string = svg.$.width
 | 
					            const width: string = svg.$.width
 | 
				
			||||||
| 
						 | 
					@ -136,7 +136,7 @@ async function createManifest(
 | 
				
			||||||
        let path = layout.icon
 | 
					        let path = layout.icon
 | 
				
			||||||
        if (layout.icon.startsWith("<")) {
 | 
					        if (layout.icon.startsWith("<")) {
 | 
				
			||||||
            // THis is already the svg
 | 
					            // THis is already the svg
 | 
				
			||||||
            path = "./assets/generated/images/" + layout.id + "_logo.svg"
 | 
					            path = "./public/assets/generated/images/" + layout.id + "_logo.svg"
 | 
				
			||||||
            writeFileSync(path, layout.icon)
 | 
					            writeFileSync(path, layout.icon)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -235,7 +235,7 @@ async function createLandingPage(layout: LayoutConfig, manifest, whiteIcons, alr
 | 
				
			||||||
    let icon = layout.icon
 | 
					    let icon = layout.icon
 | 
				
			||||||
    if (icon.startsWith("<?xml") || icon.startsWith("<svg")) {
 | 
					    if (icon.startsWith("<?xml") || icon.startsWith("<svg")) {
 | 
				
			||||||
        // This already is an svg
 | 
					        // This already is an svg
 | 
				
			||||||
        icon = `./assets/generated/images/${layout.id}_icon.svg`
 | 
					        icon = `./public/assets/generated/images/${layout.id}_icon.svg`
 | 
				
			||||||
        writeFileSync(icon, layout.icon)
 | 
					        writeFileSync(icon, layout.icon)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -295,7 +295,7 @@ async function createIndexFor(theme: LayoutConfig) {
 | 
				
			||||||
    const filename = "index_" + theme.id + ".ts"
 | 
					    const filename = "index_" + theme.id + ".ts"
 | 
				
			||||||
    writeFileSync(
 | 
					    writeFileSync(
 | 
				
			||||||
        filename,
 | 
					        filename,
 | 
				
			||||||
        `import * as themeConfig from "./assets/generated/themes/${theme.id}.json"\n`
 | 
					        `import themeConfig from "./assets/generated/themes/${theme.id}.json"\n`
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    appendFileSync(filename, codeTemplate)
 | 
					    appendFileSync(filename, codeTemplate)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -311,7 +311,9 @@ async function main(): Promise<void> {
 | 
				
			||||||
    createDir("./assets/generated")
 | 
					    createDir("./assets/generated")
 | 
				
			||||||
    createDir("./assets/generated/layers")
 | 
					    createDir("./assets/generated/layers")
 | 
				
			||||||
    createDir("./assets/generated/themes")
 | 
					    createDir("./assets/generated/themes")
 | 
				
			||||||
    createDir("./assets/generated/images")
 | 
					    createDir("./public/assets/")
 | 
				
			||||||
 | 
					    createDir("./public/assets/generated")
 | 
				
			||||||
 | 
					    createDir("./public/assets/generated/images")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const blacklist = [
 | 
					    const blacklist = [
 | 
				
			||||||
        "",
 | 
					        "",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,4 @@
 | 
				
			||||||
import * as known_layers from "../assets/generated/known_layers.json"
 | 
					import known_layers from "../assets/generated/known_layers.json"
 | 
				
			||||||
import { LayerConfigJson } from "../Models/ThemeConfig/Json/LayerConfigJson"
 | 
					import { LayerConfigJson } from "../Models/ThemeConfig/Json/LayerConfigJson"
 | 
				
			||||||
import { TagUtils } from "../Logic/Tags/TagUtils"
 | 
					import { TagUtils } from "../Logic/Tags/TagUtils"
 | 
				
			||||||
import { Utils } from "../Utils"
 | 
					import { Utils } from "../Utils"
 | 
				
			||||||
| 
						 | 
					@ -10,7 +10,7 @@ import Constants from "../Models/Constants"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function main(includeTags = true) {
 | 
					async function main(includeTags = true) {
 | 
				
			||||||
    ScriptUtils.fixUtils()
 | 
					    ScriptUtils.fixUtils()
 | 
				
			||||||
    const layers: LayerConfigJson[] = (known_layers["default"] ?? known_layers).layers
 | 
					    const layers = <LayerConfigJson[]>known_layers.layers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const keysAndTags = new Map<string, Set<string>>()
 | 
					    const keysAndTags = new Map<string, Set<string>>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,7 +13,7 @@ async function main(args: string[]) {
 | 
				
			||||||
    console.log("Removing translation string ", path, "from the general translations")
 | 
					    console.log("Removing translation string ", path, "from the general translations")
 | 
				
			||||||
    const files = ScriptUtils.readDirRecSync("./langs", 1).filter((f) => f.endsWith(".json"))
 | 
					    const files = ScriptUtils.readDirRecSync("./langs", 1).filter((f) => f.endsWith(".json"))
 | 
				
			||||||
    for (const file of files) {
 | 
					    for (const file of files) {
 | 
				
			||||||
        const json = JSON.parse(fs.readFileSync(file, "UTF-8"))
 | 
					        const json = JSON.parse(fs.readFileSync(file, { encoding: "utf-8" }))
 | 
				
			||||||
        Utils.WalkPath(path, json, (_) => undefined)
 | 
					        Utils.WalkPath(path, json, (_) => undefined)
 | 
				
			||||||
        fs.writeFileSync(file, JSON.stringify(json, null, "    ") + "\n")
 | 
					        fs.writeFileSync(file, JSON.stringify(json, null, "    ") + "\n")
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,7 @@
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
import ScriptUtils from "../ScriptUtils"
 | 
					import ScriptUtils from "../ScriptUtils"
 | 
				
			||||||
import { existsSync, readFileSync, writeFileSync } from "fs"
 | 
					import { existsSync, readFileSync, writeFileSync } from "fs"
 | 
				
			||||||
import * as known_languages from "../../assets/language_native.json"
 | 
					import known_languages from "../../assets/language_native.json"
 | 
				
			||||||
import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson"
 | 
					import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson"
 | 
				
			||||||
import { MappingConfigJson } from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
 | 
					import { MappingConfigJson } from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
 | 
				
			||||||
import SmallLicense from "../../Models/smallLicense"
 | 
					import SmallLicense from "../../Models/smallLicense"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,62 +7,86 @@ import { exec } from "child_process"
 | 
				
			||||||
 * @param reason
 | 
					 * @param reason
 | 
				
			||||||
 * @private
 | 
					 * @private
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
function detectInCode(forbidden: string, reason: string) {
 | 
					function detectInCode(forbidden: string, reason: string): (done: () => void) => void {
 | 
				
			||||||
    const excludedDirs = [
 | 
					    return (done: () => void) => {
 | 
				
			||||||
        ".git",
 | 
					        const excludedDirs = [
 | 
				
			||||||
        "node_modules",
 | 
					            ".git",
 | 
				
			||||||
        "dist",
 | 
					            "node_modules",
 | 
				
			||||||
        ".cache",
 | 
					            "dist",
 | 
				
			||||||
        ".parcel-cache",
 | 
					            ".cache",
 | 
				
			||||||
        "assets",
 | 
					            ".parcel-cache",
 | 
				
			||||||
        "vendor",
 | 
					            "assets",
 | 
				
			||||||
        ".idea/",
 | 
					            "vendor",
 | 
				
			||||||
    ]
 | 
					            ".idea/",
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    exec(
 | 
					        exec(
 | 
				
			||||||
        'grep -n "' +
 | 
					            'grep -n "' +
 | 
				
			||||||
            forbidden +
 | 
					                forbidden +
 | 
				
			||||||
            '" -r . ' +
 | 
					                '" -r . ' +
 | 
				
			||||||
            excludedDirs.map((d) => "--exclude-dir=" + d).join(" "),
 | 
					                excludedDirs.map((d) => "--exclude-dir=" + d).join(" "),
 | 
				
			||||||
        (error, stdout, stderr) => {
 | 
					            (error, stdout, stderr) => {
 | 
				
			||||||
            if (error?.message?.startsWith("Command failed: grep")) {
 | 
					                if (error?.message?.startsWith("Command failed: grep")) {
 | 
				
			||||||
                console.warn("Command failed!")
 | 
					                    console.warn("Command failed!", error)
 | 
				
			||||||
                return
 | 
					                    return
 | 
				
			||||||
            }
 | 
					                }
 | 
				
			||||||
            if (error !== null) {
 | 
					                if (error !== null) {
 | 
				
			||||||
                throw error
 | 
					                    throw error
 | 
				
			||||||
            }
 | 
					                }
 | 
				
			||||||
            if (stderr !== "") {
 | 
					                if (stderr !== "") {
 | 
				
			||||||
                throw stderr
 | 
					                    throw stderr
 | 
				
			||||||
            }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            const found = stdout
 | 
					                const found = stdout
 | 
				
			||||||
                .split("\n")
 | 
					                    .split("\n")
 | 
				
			||||||
                .filter((s) => s !== "")
 | 
					                    .filter((s) => s !== "")
 | 
				
			||||||
                .filter((s) => !s.startsWith("./test/"))
 | 
					                    .filter((s) => !s.startsWith("./test/"))
 | 
				
			||||||
            if (found.length > 0) {
 | 
					                if (found.length > 0) {
 | 
				
			||||||
                throw `Found a '${forbidden}' at \n    ${found.join("\n     ")}.\n ${reason}`
 | 
					                    const msg = `Found a '${forbidden}' at \n    ${found.join(
 | 
				
			||||||
 | 
					                        "\n     "
 | 
				
			||||||
 | 
					                    )}.\n ${reason}`
 | 
				
			||||||
 | 
					                    console.error(msg)
 | 
				
			||||||
 | 
					                    console.error(found.length, "issues found")
 | 
				
			||||||
 | 
					                    throw msg
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                done()
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        )
 | 
				
			||||||
    )
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe("Code quality", () => {
 | 
					describe("Code quality", () => {
 | 
				
			||||||
    it("should not contain reverse", () => {
 | 
					    it(
 | 
				
			||||||
 | 
					        "should not contain reverse",
 | 
				
			||||||
        detectInCode(
 | 
					        detectInCode(
 | 
				
			||||||
            "reverse()",
 | 
					            "reverse()",
 | 
				
			||||||
            "Reverse is stateful and changes the source list. This often causes subtle bugs"
 | 
					            "Reverse is stateful and changes the source list. This often causes subtle bugs"
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    })
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("should not contain 'constructor.name'", () => {
 | 
					    it(
 | 
				
			||||||
 | 
					        "should not contain 'constructor.name'",
 | 
				
			||||||
        detectInCode("constructor\\.name", "This is not allowed, as minification does erase names.")
 | 
					        detectInCode("constructor\\.name", "This is not allowed, as minification does erase names.")
 | 
				
			||||||
    })
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("should not contain 'innerText'", () => {
 | 
					    it(
 | 
				
			||||||
 | 
					        "should not contain 'innerText'",
 | 
				
			||||||
        detectInCode(
 | 
					        detectInCode(
 | 
				
			||||||
            "innerText",
 | 
					            "innerText",
 | 
				
			||||||
            "innerText is not allowed as it is not testable with fakeDom. Use 'textContent' instead."
 | 
					            "innerText is not allowed as it is not testable with fakeDom. Use 'textContent' instead."
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    })
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it(
 | 
				
			||||||
 | 
					        "should not contain 'import * as name from \"xyz.json\"'",
 | 
				
			||||||
 | 
					        detectInCode(
 | 
				
			||||||
 | 
					            'import \\* as [a-zA-Z0-9_]\\+ from \\"[.-_/a-zA-Z0-9]\\+\\.json\\"',
 | 
				
			||||||
 | 
					            "With vite, json files have a default export. Use import name from file.json instead"
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it(
 | 
				
			||||||
 | 
					        "should not contain '[\"default\"]'",
 | 
				
			||||||
 | 
					        detectInCode('\\[\\"default\\"\\]', "Possible leftover of faulty default import")
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue