forked from MapComplete/MapComplete
		
	Merge develop
This commit is contained in:
		
						commit
						477ef56e00
					
				
					 202 changed files with 5785 additions and 2386 deletions
				
			
		|  | @ -1,13 +1,13 @@ | |||
| import { UIEventSource } from "../UIEventSource"; | ||||
| import { LocalStorageSource } from "../Web/LocalStorageSource"; | ||||
| import { QueryParameters } from "../Web/QueryParameters"; | ||||
| import { UIEventSource } from "../UIEventSource" | ||||
| import { LocalStorageSource } from "../Web/LocalStorageSource" | ||||
| import { QueryParameters } from "../Web/QueryParameters" | ||||
| 
 | ||||
| export type GeolocationPermissionState = "prompt" | "requested" | "granted" | "denied" | ||||
| 
 | ||||
| export interface GeoLocationPointProperties extends GeolocationCoordinates { | ||||
|     id: "gps"; | ||||
|     "user:location": "yes"; | ||||
|     date: string; | ||||
|     id: "gps" | ||||
|     "user:location": "yes" | ||||
|     date: string | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  | @ -23,22 +23,22 @@ export class GeoLocationState { | |||
|      */ | ||||
|     public readonly permission: UIEventSource<GeolocationPermissionState> = 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 | ||||
|      */ | ||||
|     public readonly allowMoving: UIEventSource<boolean> = new UIEventSource<boolean>(true); | ||||
|     public readonly allowMoving: UIEventSource<boolean> = new UIEventSource<boolean>(true) | ||||
| 
 | ||||
|     /** | ||||
|      * The latest GeoLocationCoordinates, as given by the WebAPI | ||||
|      */ | ||||
|     public readonly currentGPSLocation: UIEventSource<GeolocationCoordinates | undefined> = | ||||
|         new UIEventSource<GeolocationCoordinates | undefined>(undefined); | ||||
|         new UIEventSource<GeolocationCoordinates | undefined>(undefined) | ||||
| 
 | ||||
|     /** | ||||
|      * A small flag on localstorage. If the user previously granted the geolocation, it will be set. | ||||
|  | @ -50,46 +50,46 @@ export class GeoLocationState { | |||
|      */ | ||||
|     private readonly _previousLocationGrant: UIEventSource<"true" | "false"> = <any>( | ||||
|         LocalStorageSource.Get("geolocation-permissions") | ||||
|     ); | ||||
|     ) | ||||
| 
 | ||||
|     /** | ||||
|      * Used to detect a permission retraction | ||||
|      */ | ||||
|     private readonly _grantedThisSession: UIEventSource<boolean> = new UIEventSource<boolean>(false); | ||||
|     private readonly _grantedThisSession: UIEventSource<boolean> = new UIEventSource<boolean>(false) | ||||
| 
 | ||||
|     constructor() { | ||||
|         const self = this; | ||||
|         const self = this | ||||
| 
 | ||||
|         this.permission.addCallbackAndRunD(async (state) => { | ||||
|             if (state === "granted") { | ||||
|                 self._previousLocationGrant.setData("true"); | ||||
|                 self._grantedThisSession.setData(true); | ||||
|                 self._previousLocationGrant.setData("true") | ||||
|                 self._grantedThisSession.setData(true) | ||||
|             } | ||||
|             if (state === "prompt" && self._grantedThisSession.data) { | ||||
|                 // This is _really_ weird: we had a grant earlier, but it's 'prompt' now?
 | ||||
|                 // This means that the rights have been revoked again!
 | ||||
|                 self._previousLocationGrant.setData("false"); | ||||
|                 self.permission.setData("denied"); | ||||
|                 self.currentGPSLocation.setData(undefined); | ||||
|                 console.warn("Detected a downgrade in permissions!"); | ||||
|                 self._previousLocationGrant.setData("false") | ||||
|                 self.permission.setData("denied") | ||||
|                 self.currentGPSLocation.setData(undefined) | ||||
|                 console.warn("Detected a downgrade in permissions!") | ||||
|             } | ||||
|             if (state === "denied") { | ||||
|                 self._previousLocationGrant.setData("false"); | ||||
|                 self._previousLocationGrant.setData("false") | ||||
|             } | ||||
|         }); | ||||
|         console.log("Previous location grant:", this._previousLocationGrant.data); | ||||
|         }) | ||||
|         console.log("Previous location grant:", this._previousLocationGrant.data) | ||||
|         if (this._previousLocationGrant.data === "true") { | ||||
|             // A previous visit successfully granted permission. Chance is high that we are allowed to use it again!
 | ||||
| 
 | ||||
|             // 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"); | ||||
|             console.log("Requesting access to GPS as this was previously granted"); | ||||
|             this._previousLocationGrant.setData("false") | ||||
|             console.log("Requesting access to GPS as this was previously granted") | ||||
|             const latLonGivenViaUrl = | ||||
|                 QueryParameters.wasInitialized("lat") || QueryParameters.wasInitialized("lon"); | ||||
|                 QueryParameters.wasInitialized("lat") || QueryParameters.wasInitialized("lon") | ||||
|             if (!latLonGivenViaUrl) { | ||||
|                 this.requestMoment.setData(new Date()); | ||||
|                 this.requestMoment.setData(new Date()) | ||||
|             } | ||||
|             this.requestPermission(); | ||||
|             this.requestPermission() | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -101,37 +101,36 @@ export class GeoLocationState { | |||
|     public requestPermission() { | ||||
|         if (typeof navigator === "undefined") { | ||||
|             // Not compatible with this browser
 | ||||
|             this.permission.setData("denied"); | ||||
|             return; | ||||
|             this.permission.setData("denied") | ||||
|             return | ||||
|         } | ||||
|         if (this.permission.data !== "prompt" && this.permission.data !== "requested") { | ||||
|             // If the user denies the first prompt, revokes the deny and then tries again, we have to run the flow as well
 | ||||
|             // Hence that we continue the flow if it is "requested"
 | ||||
|             return; | ||||
|             return | ||||
|         } | ||||
| 
 | ||||
|         this.permission.setData("requested"); | ||||
|         this.permission.setData("requested") | ||||
|         try { | ||||
|             navigator?.permissions | ||||
|                 ?.query({ name: "geolocation" }) | ||||
|                 .then((status) => { | ||||
|                     const self = this; | ||||
|                     if(status.state === "granted" || status.state === "denied"){ | ||||
|                     const self = this | ||||
|                     if (status.state === "granted" || status.state === "denied") { | ||||
|                         self.permission.setData(status.state) | ||||
|                         return | ||||
|                     } | ||||
|                     status.addEventListener("change", (e) => { | ||||
|                         self.permission.setData(status.state); | ||||
| 
 | ||||
|                     }); | ||||
|                         self.permission.setData(status.state) | ||||
|                     }) | ||||
|                     // The code above might have reset it to 'prompt', but we _did_ request permission!
 | ||||
|                     this.permission.setData("requested") | ||||
|                     // We _must_ call 'startWatching', as that is the actual trigger for the popup...
 | ||||
|                     self.startWatching(); | ||||
|                     self.startWatching() | ||||
|                 }) | ||||
|                 .catch((e) => console.error("Could not get geopermission", e)); | ||||
|                 .catch((e) => console.error("Could not get geopermission", e)) | ||||
|         } catch (e) { | ||||
|             console.error("Could not get permission:", e); | ||||
|             console.error("Could not get permission:", e) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -140,18 +139,18 @@ export class GeoLocationState { | |||
|      * @private | ||||
|      */ | ||||
|     private async startWatching() { | ||||
|         const self = this; | ||||
|         const self = this | ||||
|         navigator.geolocation.watchPosition( | ||||
|             function(position) { | ||||
|                 self.currentGPSLocation.setData(position.coords); | ||||
|                 self._previousLocationGrant.setData("true"); | ||||
|             function (position) { | ||||
|                 self.currentGPSLocation.setData(position.coords) | ||||
|                 self._previousLocationGrant.setData("true") | ||||
|             }, | ||||
|             function() { | ||||
|                 console.warn("Could not get location with navigator.geolocation"); | ||||
|             function () { | ||||
|                 console.warn("Could not get location with navigator.geolocation") | ||||
|             }, | ||||
|             { | ||||
|                 enableHighAccuracy: true | ||||
|                 enableHighAccuracy: true, | ||||
|             } | ||||
|         ); | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -1,23 +1,23 @@ | |||
| import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; | ||||
| import { OsmConnection } from "../Osm/OsmConnection"; | ||||
| import { MangroveIdentity } from "../Web/MangroveReviews"; | ||||
| import { Store, Stores, UIEventSource } from "../UIEventSource"; | ||||
| import StaticFeatureSource from "../FeatureSource/Sources/StaticFeatureSource"; | ||||
| import { FeatureSource } from "../FeatureSource/FeatureSource"; | ||||
| import { Feature } from "geojson"; | ||||
| import { Utils } from "../../Utils"; | ||||
| import translators from "../../assets/translators.json"; | ||||
| import codeContributors from "../../assets/contributors.json"; | ||||
| import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; | ||||
| import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson"; | ||||
| import usersettings from "../../../src/assets/generated/layers/usersettings.json"; | ||||
| import Locale from "../../UI/i18n/Locale"; | ||||
| import LinkToWeblate from "../../UI/Base/LinkToWeblate"; | ||||
| import FeatureSwitchState from "./FeatureSwitchState"; | ||||
| import Constants from "../../Models/Constants"; | ||||
| import { QueryParameters } from "../Web/QueryParameters"; | ||||
| import { ThemeMetaTagging } from "./UserSettingsMetaTagging"; | ||||
| import { MapProperties } from "../../Models/MapProperties"; | ||||
| import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" | ||||
| import { OsmConnection } from "../Osm/OsmConnection" | ||||
| import { MangroveIdentity } from "../Web/MangroveReviews" | ||||
| import { Store, Stores, UIEventSource } from "../UIEventSource" | ||||
| import StaticFeatureSource from "../FeatureSource/Sources/StaticFeatureSource" | ||||
| import { FeatureSource } from "../FeatureSource/FeatureSource" | ||||
| import { Feature } from "geojson" | ||||
| import { Utils } from "../../Utils" | ||||
| import translators from "../../assets/translators.json" | ||||
| import codeContributors from "../../assets/contributors.json" | ||||
| import LayerConfig from "../../Models/ThemeConfig/LayerConfig" | ||||
| import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson" | ||||
| import usersettings from "../../../src/assets/generated/layers/usersettings.json" | ||||
| import Locale from "../../UI/i18n/Locale" | ||||
| import LinkToWeblate from "../../UI/Base/LinkToWeblate" | ||||
| import FeatureSwitchState from "./FeatureSwitchState" | ||||
| import Constants from "../../Models/Constants" | ||||
| import { QueryParameters } from "../Web/QueryParameters" | ||||
| import { ThemeMetaTagging } from "./UserSettingsMetaTagging" | ||||
| import { MapProperties } from "../../Models/MapProperties" | ||||
| 
 | ||||
| /** | ||||
|  * The part of the state which keeps track of user-related stuff, e.g. the OSM-connection, | ||||
|  | @ -42,8 +42,10 @@ export default class UserRelatedState { | |||
|     public readonly fixateNorth: UIEventSource<undefined | "yes"> | ||||
|     public readonly homeLocation: FeatureSource | ||||
|     public readonly language: UIEventSource<string> | ||||
|     public readonly preferredBackgroundLayer: UIEventSource<string | "photo" | "map" | "osmbasedmap" | undefined> | ||||
|     public readonly imageLicense : UIEventSource<string> | ||||
|     public readonly preferredBackgroundLayer: UIEventSource< | ||||
|         string | "photo" | "map" | "osmbasedmap" | undefined | ||||
|     > | ||||
|     public readonly imageLicense: UIEventSource<string> | ||||
|     /** | ||||
|      * The number of seconds that the GPS-locations are stored in memory. | ||||
|      * Time in seconds | ||||
|  | @ -61,7 +63,7 @@ export default class UserRelatedState { | |||
|      * Note: these are linked via OsmConnection.preferences which exports all preferences as UIEventSource | ||||
|      */ | ||||
|     public readonly preferencesAsTags: UIEventSource<Record<string, string>> | ||||
|     private readonly _mapProperties: MapProperties; | ||||
|     private readonly _mapProperties: MapProperties | ||||
| 
 | ||||
|     constructor( | ||||
|         osmConnection: OsmConnection, | ||||
|  | @ -71,7 +73,7 @@ export default class UserRelatedState { | |||
|         mapProperties?: MapProperties | ||||
|     ) { | ||||
|         this.osmConnection = osmConnection | ||||
|         this._mapProperties = mapProperties; | ||||
|         this._mapProperties = mapProperties | ||||
|         { | ||||
|             const translationMode: UIEventSource<undefined | "true" | "false" | "mobile" | string> = | ||||
|                 this.osmConnection.GetPreference("translation-mode", "false") | ||||
|  | @ -104,12 +106,17 @@ export default class UserRelatedState { | |||
|         this.mangroveIdentity = new MangroveIdentity( | ||||
|             this.osmConnection.GetLongPreference("identity", "mangrove") | ||||
|         ) | ||||
|         this.preferredBackgroundLayer= this.osmConnection.GetPreference("preferred-background-layer", undefined, { | ||||
|             documentation: "The ID of a layer or layer category that MapComplete uses by default" | ||||
|         }) | ||||
|         this.preferredBackgroundLayer = this.osmConnection.GetPreference( | ||||
|             "preferred-background-layer", | ||||
|             undefined, | ||||
|             { | ||||
|                 documentation: | ||||
|                     "The ID of a layer or layer category that MapComplete uses by default", | ||||
|             } | ||||
|         ) | ||||
| 
 | ||||
|         this.imageLicense =  this.osmConnection.GetPreference("pictures-license", "CC0", { | ||||
|             documentation: "The license under which new images are uploaded" | ||||
|         this.imageLicense = this.osmConnection.GetPreference("pictures-license", "CC0", { | ||||
|             documentation: "The license under which new images are uploaded", | ||||
|         }) | ||||
|         this.installedUserThemes = this.InitInstalledUserThemes() | ||||
| 
 | ||||
|  | @ -277,7 +284,6 @@ export default class UserRelatedState { | |||
|             amendedPrefs.data["__url_parameter_initialized:" + key] = "yes" | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         const osmConnection = this.osmConnection | ||||
|         osmConnection.preferencesHandler.preferences.addCallback((newPrefs) => { | ||||
|             for (const k in newPrefs) { | ||||
|  | @ -405,13 +411,11 @@ export default class UserRelatedState { | |||
|             } | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         this._mapProperties?.rasterLayer?.addCallbackAndRun(l => { | ||||
|         this._mapProperties?.rasterLayer?.addCallbackAndRun((l) => { | ||||
|             amendedPrefs.data["__current_background"] = l?.properties?.id | ||||
|             amendedPrefs.ping() | ||||
|         }) | ||||
| 
 | ||||
| 
 | ||||
|         return amendedPrefs | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -1,14 +1,42 @@ | |||
| import { Utils } from "../../Utils" | ||||
| /** This code is autogenerated - do not edit. Edit ./assets/layers/usersettings/usersettings.json instead */ | ||||
| export class ThemeMetaTagging { | ||||
|    public static readonly themeName = "usersettings" | ||||
|     public static readonly themeName = "usersettings" | ||||
| 
 | ||||
|    public metaTaggging_for_usersettings(feat: {properties: Record<string, string>}) { | ||||
|       Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_md', () => feat.properties._description.match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/)?.at(1) )  | ||||
|       Utils.AddLazyProperty(feat.properties, '_d', () => feat.properties._description?.replace(/</g,'<')?.replace(/>/g,'>') ?? '' )  | ||||
|       Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_a', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.href.match(/mastodon|en.osm.town/) !== null)[0]?.href   }) (feat)  )  | ||||
|       Utils.AddLazyProperty(feat.properties, '_mastodon_link', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.getAttribute("rel")?.indexOf('me') >= 0)[0]?.href})(feat)  )  | ||||
|       Utils.AddLazyProperty(feat.properties, '_mastodon_candidate', () => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a )  | ||||
|       feat.properties['__current_backgroun'] = 'initial_value' | ||||
|    } | ||||
| } | ||||
|     public metaTaggging_for_usersettings(feat: { properties: Record<string, string> }) { | ||||
|         Utils.AddLazyProperty(feat.properties, "_mastodon_candidate_md", () => | ||||
|             feat.properties._description | ||||
|                 .match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/) | ||||
|                 ?.at(1) | ||||
|         ) | ||||
|         Utils.AddLazyProperty( | ||||
|             feat.properties, | ||||
|             "_d", | ||||
|             () => feat.properties._description?.replace(/</g, "<")?.replace(/>/g, ">") ?? "" | ||||
|         ) | ||||
|         Utils.AddLazyProperty(feat.properties, "_mastodon_candidate_a", () => | ||||
|             ((feat) => { | ||||
|                 const e = document.createElement("div") | ||||
|                 e.innerHTML = feat.properties._d | ||||
|                 return Array.from(e.getElementsByTagName("a")).filter( | ||||
|                     (a) => a.href.match(/mastodon|en.osm.town/) !== null | ||||
|                 )[0]?.href | ||||
|             })(feat) | ||||
|         ) | ||||
|         Utils.AddLazyProperty(feat.properties, "_mastodon_link", () => | ||||
|             ((feat) => { | ||||
|                 const e = document.createElement("div") | ||||
|                 e.innerHTML = feat.properties._d | ||||
|                 return Array.from(e.getElementsByTagName("a")).filter( | ||||
|                     (a) => a.getAttribute("rel")?.indexOf("me") >= 0 | ||||
|                 )[0]?.href | ||||
|             })(feat) | ||||
|         ) | ||||
|         Utils.AddLazyProperty( | ||||
|             feat.properties, | ||||
|             "_mastodon_candidate", | ||||
|             () => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a | ||||
|         ) | ||||
|         feat.properties["__current_backgroun"] = "initial_value" | ||||
|     } | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue