forked from MapComplete/MapComplete
		
	Refactoring: make needed URLs explicit
This commit is contained in:
		
							parent
							
								
									7852829f1b
								
							
						
					
					
						commit
						4852888b41
					
				
					 51 changed files with 978 additions and 871 deletions
				
			
		|  | @ -1658,7 +1658,9 @@ | |||
|     }, | ||||
|     { | ||||
|       "id": "repeated", | ||||
|       "labels": ["level"], | ||||
|       "labels": [ | ||||
|         "level" | ||||
|       ], | ||||
|       "condition": "repeat_on~*", | ||||
|       "render": { | ||||
|         "en": "Multiple, identical objects can be found on floors {repeat_on}.", | ||||
|  | @ -1667,7 +1669,9 @@ | |||
|     }, | ||||
|     { | ||||
|       "id": "single_level", | ||||
|       "labels": ["level"], | ||||
|       "labels": [ | ||||
|         "level" | ||||
|       ], | ||||
|       "condition": "repeat_on=", | ||||
|       "question": { | ||||
|         "nl": "Op welke verdieping bevindt dit punt zich?", | ||||
|  |  | |||
							
								
								
									
										15
									
								
								package.json
									
										
									
									
									
								
							
							
						
						
									
										15
									
								
								package.json
									
										
									
									
									
								
							|  | @ -1,6 +1,6 @@ | |||
| { | ||||
|   "name": "mapcomplete", | ||||
|   "version": "0.33.4", | ||||
|   "version": "0.33.5", | ||||
|   "repository": "https://github.com/pietervdvn/MapComplete", | ||||
|   "description": "A small website to edit OSM easily", | ||||
|   "bugs": "https://github.com/pietervdvn/MapComplete/issues", | ||||
|  | @ -18,23 +18,10 @@ | |||
|       "Alternatively, you can override the `osm` credentials using the environment variables `VITE_OSM_OAUTH_CLIENT_ID` and `VITE_OSM_OAUTH_SECRET`" | ||||
|     ], | ||||
|     "oauth_credentials": { | ||||
|       "osm_pietervdvn": { | ||||
|         "#": "This client_id is registered by 'Pieter Vander Vennet' on OSM.org", | ||||
|         "oauth_client_id": "sa1ngLJBJ8McmzHElN8NYtIDm5TZTYEYhq3-0snO4Qc", | ||||
|         "oauth_secret": "XU_cD5Mvw9VKk9T0t_gO8V7cbRC4Hmw2Tb4Rv0Zmz-U", | ||||
|         "url": "https://www.openstreetmap.org" | ||||
|       }, | ||||
|       "osm": { | ||||
|         "#": "This client-id is registered by 'MapComplete' on osm.org", | ||||
|         "oauth_client_id": "K93H1d8ve7p-tVLE1ZwsQ4lAFLQk8INx5vfTLMu5DWk", | ||||
|         "oauth_secret": "NBWGhWDrD3QDB35xtVuxv4aExnmIt4FA_WgeLtwxasg", | ||||
|         "url": "https://www.openstreetmap.org" | ||||
|       }, | ||||
|       "osm-test": { | ||||
|         "oauth_client_id": "HwUn6GPxGm1m9WwMarxTglhy6dBTM4YkaV1I9h6pDGU", | ||||
|         "oauth_secret": "luFZtPJg7j96K6WM6RpcZ_3M-r6muuDq6fG1ygk0I_4", | ||||
|         "url": "https://master.apis.dev.openstreetmap.org" | ||||
|       } | ||||
|     }, | ||||
|     "api_keys": { | ||||
|       "#": "Various API-keys for various services. Feel free to reuse those in another MapComplete-hosted version", | ||||
|  |  | |||
|  | @ -12,8 +12,8 @@ mkdir dist/assets 2> /dev/null | |||
| export NODE_OPTIONS="--max-old-space-size=8192" | ||||
| 
 | ||||
| # This script ends every line with '&&' to chain everything. A failure will thus stop the build | ||||
| # npm run generate:editor-layer-index && | ||||
| # npm run generate && | ||||
| npm run generate:editor-layer-index && | ||||
| npm run generate && | ||||
| npm run generate:layouts | ||||
| 
 | ||||
| if [ $? -ne 0 ]; then | ||||
|  | @ -38,7 +38,8 @@ then | |||
|     export ASSET_URL | ||||
|     echo "$ASSET_URL" | ||||
| else | ||||
|   ASSET_URL="$BRANCH" | ||||
|   # ASSET_URL="$BRANCH" | ||||
|   ASSET_URL="./" | ||||
|   export ASSET_URL | ||||
|   echo "$ASSET_URL" | ||||
| fi | ||||
|  |  | |||
|  | @ -8,6 +8,10 @@ import LayoutConfig from "../src/Models/ThemeConfig/LayoutConfig" | |||
| import xml2js from "xml2js" | ||||
| import ScriptUtils from "./ScriptUtils" | ||||
| import { Utils } from "../src/Utils" | ||||
| import SpecialVisualizations from "../src/UI/SpecialVisualizations" | ||||
| import Constants from "../src/Models/Constants" | ||||
| import { AvailableRasterLayers, RasterLayerPolygon } from "../src/Models/RasterLayers" | ||||
| import { ImmutableStore } from "../src/Logic/UIEventSource" | ||||
| 
 | ||||
| const sharp = require("sharp") | ||||
| const template = readFileSync("theme.html", "utf8") | ||||
|  | @ -195,29 +199,72 @@ function asLangSpan(t: Translation, tag = "span"): string { | |||
|         if (lang === "_context") { | ||||
|             continue | ||||
|         } | ||||
|         values.push(`<${tag} lang='${lang}'>${t.translations[lang]}</${tag}>`) | ||||
|         values.push(`<${tag} lang="${lang}">${t.translations[lang]}</${tag}>`) | ||||
|     } | ||||
|     return values.join("\n") | ||||
| } | ||||
| 
 | ||||
| let cspCached: string = undefined | ||||
| function generateCsp(): string { | ||||
|     if (cspCached !== undefined) { | ||||
|         return cspCached | ||||
| let previousSrc: Set<string> = new Set<string>() | ||||
| function generateCsp(layout: LayoutConfig): string { | ||||
|     const apiUrls: string[] = [ | ||||
|         "self", | ||||
|         ...Constants.defaultOverpassUrls, | ||||
|         Constants.countryCoderEndpoint, | ||||
|         "https://api.openstreetmap.org", | ||||
|         "https://pietervdvn.goatcounter.com", | ||||
|     ].concat(...SpecialVisualizations.specialVisualizations.map((sv) => sv.needsUrls)) | ||||
| 
 | ||||
|     const geojsonSources: string[] = layout.layers.map((l) => l.source?.geojsonSource) | ||||
|     const hosts = new Set<string>() | ||||
|     const eliLayers: RasterLayerPolygon[] = AvailableRasterLayers.layersAvailableAt( | ||||
|         new ImmutableStore({ lon: 0, lat: 0 }) | ||||
|     ).data | ||||
|     const vectorLayers = eliLayers.filter((l) => l.properties.type === "vector") | ||||
|     const vectorSources = vectorLayers.map((l) => l.properties.url) | ||||
|     apiUrls.push(...vectorSources) | ||||
|     for (const connectSource of apiUrls.concat(geojsonSources)) { | ||||
|         if (!connectSource) { | ||||
|             continue | ||||
|         } | ||||
|         try { | ||||
|             const url = new URL(connectSource) | ||||
|             hosts.add("https://" + url.host) | ||||
|         } catch (e) { | ||||
|             hosts.add(connectSource) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     const connectSrc = Array.from(hosts).sort() | ||||
| 
 | ||||
|     const newSrcs = connectSrc.filter((newItem) => !previousSrc.has(newItem)) | ||||
| 
 | ||||
|     console.log( | ||||
|         "Got", | ||||
|         hosts.size, | ||||
|         "connect-src items for theme", | ||||
|         layout.id, | ||||
|         "(extra sources: ", | ||||
|         newSrcs.join(" ") + ")" | ||||
|     ) | ||||
|     previousSrc = hosts | ||||
| 
 | ||||
|     const csp = { | ||||
|         "default-src": "'self'", | ||||
|         "script-src": "'self'", | ||||
|         "img-src": "*", | ||||
|         "connect-src": "*", | ||||
|         "script-src": "'self' https://gc.zgo.at/count.js", | ||||
|         "img-src": "* data:", // maplibre depends on 'data:' to load
 | ||||
|         "connect-src": connectSrc.join(" "), | ||||
|         "report-to": "https://report.mapcomplete.org/csp", | ||||
|         "worker-src": "'self' blob:", // Vite somehow loads the worker via a 'blob'
 | ||||
|         "style-src": "'self' 'unsafe-inline'", // unsafe-inline is needed to change the default background pin colours
 | ||||
|     } | ||||
|     const content = Object.keys(csp) | ||||
|         .map((k) => k + ": " + csp[k]) | ||||
|         .map((k) => k + " " + csp[k]) | ||||
|         .join("; ") | ||||
| 
 | ||||
|     cspCached = `<meta http-equiv="Content-Security-Policy" content="${content}">` | ||||
|     return cspCached | ||||
|     return [ | ||||
|         `<meta http-equiv ="Report-To" content='{"group":"csp-endpoint", "max_age": 86400,"endpoints": [\{"url": "https://report.mapcomplete.org/csp"}], "include_subdomains": true}'>`, | ||||
|         `<meta http-equiv="Content-Security-Policy" content="${content}">`, | ||||
|     ].join("\n") | ||||
| } | ||||
| 
 | ||||
| async function createLandingPage(layout: LayoutConfig, manifest, whiteIcons, alreadyWritten) { | ||||
|  | @ -290,7 +337,7 @@ async function createLandingPage(layout: LayoutConfig, manifest, whiteIcons, alr | |||
|         ...apple_icons, | ||||
|     ].join("\n") | ||||
| 
 | ||||
|     const loadingText = Translations.t.general.loadingTheme.Subs({ theme: ogTitle }) | ||||
|     const loadingText = Translations.t.general.loadingTheme.Subs({ theme: layout.title }) | ||||
| 
 | ||||
|     let output = template | ||||
|         .replace("Loading MapComplete, hang on...", asLangSpan(loadingText, "h1")) | ||||
|  | @ -299,7 +346,7 @@ async function createLandingPage(layout: LayoutConfig, manifest, whiteIcons, alr | |||
|             Translations.t.general.poweredByOsm.textFor(targetLanguage) | ||||
|         ) | ||||
|         .replace(/<!-- THEME-SPECIFIC -->.*<!-- THEME-SPECIFIC-END-->/s, themeSpecific) | ||||
|         .replace(/<!-- CSP -->/, generateCsp()) | ||||
|         .replace(/<!-- CSP -->/, generateCsp(layout)) | ||||
|         .replace( | ||||
|             /<!-- DESCRIPTION START -->.*<!-- DESCRIPTION END -->/s, | ||||
|             asLangSpan(layout.shortDescription) | ||||
|  | @ -311,7 +358,7 @@ async function createLandingPage(layout: LayoutConfig, manifest, whiteIcons, alr | |||
| 
 | ||||
|         .replace( | ||||
|             '<script src="./src/index.ts" type="module"></script>', | ||||
|             `<script type="module" src='./index_${layout.id}.ts'></script>` | ||||
|             `<script type="module" src="./index_${layout.id}.ts"></script>` | ||||
|         ) | ||||
| 
 | ||||
|     return output | ||||
|  |  | |||
|  | @ -4,7 +4,6 @@ hosted.mapcomplete.org { | |||
| 	header { | ||||
| 		+Permissions-Policy "interest-cohort=()" | ||||
|         +Report-To `\{"group":"csp-endpoint", "max_age": 86400,"endpoints": [\{"url": "https://report.mapcomplete.org/csp"}], "include_subdomains": true}` | ||||
| 		+Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self' https://gc.zgo.at ; img-src * ; report-uri https://report.mapcomplete.org/csp ; report-to csp-endpoint ;" | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -10,14 +10,15 @@ | |||
| # unzip tiles.zip | ||||
| 
 | ||||
| MAPCOMPLETE_CONFIGURATION="config_hetzner" | ||||
| cp config.json config.json.bu && | ||||
| cp ./scripts/hetzner/config.json . && # Copy the config _before_ building, as the config might contain some needed URLs | ||||
| npm run reset:layeroverview | ||||
| npm run test | ||||
| cp config.json config.json.bu && | ||||
| cp ./scripts/hetzner/config.json . && | ||||
| npm run prepare-deploy && | ||||
| mv config.json.bu config.json && | ||||
| zip dist.zip -r dist/* && | ||||
| scp -r dist.zip hetzner:/root/ && | ||||
| scp ./scripts/hetzner/config/* hetzner:/root/ | ||||
| ssh hetzner -t "unzip dist.zip && rm dist.zip && rm -rf public/ && mv dist public && caddy stop && caddy start" | ||||
| echo "Upload completed, deploying config and booting" && | ||||
| rsync -rzh --progress dist.zip hetzner:/root/ && | ||||
| ssh hetzner -t "unzip dist.zip && rm dist.zip && rm -rf public/ && mv dist public && caddy stop && caddy start" && | ||||
| rm dist.zip | ||||
|  |  | |||
							
								
								
									
										13
									
								
								src/InstallServiceWorker.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/InstallServiceWorker.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,13 @@ | |||
| export {} | ||||
| window.addEventListener("load", async () => { | ||||
|     if (!("serviceWorker" in navigator)) { | ||||
|         console.log("Service workers are not supported") | ||||
|         return | ||||
|     } | ||||
|     try { | ||||
|         await navigator.serviceWorker.register("/service-worker.js") | ||||
|         console.log("Service worker registration successful") | ||||
|     } catch (err) { | ||||
|         console.error("Service worker registration failed", err) | ||||
|     } | ||||
| }) | ||||
|  | @ -23,27 +23,27 @@ export default class AllImageProviders { | |||
|             ) | ||||
|         ), | ||||
|     ] | ||||
| 
 | ||||
|     public static apiUrls: string[] = [].concat( | ||||
|         ...AllImageProviders.ImageAttributionSource.map((src) => src.apiUrls()) | ||||
|     ) | ||||
|     public static defaultKeys = [].concat( | ||||
|         AllImageProviders.ImageAttributionSource.map((provider) => provider.defaultKeyPrefixes) | ||||
|     ) | ||||
|     private static providersByName = { | ||||
|         imgur: Imgur.singleton, | ||||
|         mapillary: Mapillary.singleton, | ||||
|         wikidata: WikidataImageProvider.singleton, | ||||
|         wikimedia: WikimediaImageProvider.singleton, | ||||
|     } | ||||
| 
 | ||||
|     public static byName(name: string) { | ||||
|         return AllImageProviders.providersByName[name.toLowerCase()] | ||||
|     } | ||||
| 
 | ||||
|     public static defaultKeys = [].concat( | ||||
|         AllImageProviders.ImageAttributionSource.map((provider) => provider.defaultKeyPrefixes) | ||||
|     ) | ||||
| 
 | ||||
|     private static _cache: Map<string, UIEventSource<ProvidedImage[]>> = new Map< | ||||
|         string, | ||||
|         UIEventSource<ProvidedImage[]> | ||||
|     >() | ||||
| 
 | ||||
|     public static byName(name: string) { | ||||
|         return AllImageProviders.providersByName[name.toLowerCase()] | ||||
|     } | ||||
| 
 | ||||
|     public static LoadImagesFor( | ||||
|         tags: Store<Record<string, string>>, | ||||
|         tagKey?: string[] | ||||
|  |  | |||
|  | @ -3,6 +3,10 @@ import ImageProvider, { ProvidedImage } from "./ImageProvider" | |||
| export default class GenericImageProvider extends ImageProvider { | ||||
|     public defaultKeyPrefixes: string[] = ["image"] | ||||
| 
 | ||||
|     public apiUrls(): string[] { | ||||
|         return [] | ||||
|     } | ||||
| 
 | ||||
|     private readonly _valuePrefixBlacklist: string[] | ||||
| 
 | ||||
|     public constructor(valuePrefixBlacklist: string[]) { | ||||
|  |  | |||
|  | @ -65,4 +65,6 @@ export default abstract class ImageProvider { | |||
|     public abstract ExtractUrls(key: string, value: string): Promise<Promise<ProvidedImage>[]> | ||||
| 
 | ||||
|     public abstract DownloadAttribution(url: string): Promise<LicenseInfo> | ||||
| 
 | ||||
|     public abstract apiUrls(): string[] | ||||
| } | ||||
|  |  | |||
|  | @ -4,16 +4,23 @@ import { Utils } from "../../Utils"; | |||
| import Constants from "../../Models/Constants"; | ||||
| import { LicenseInfo } from "./LicenseInfo"; | ||||
| import { ImageUploader } from "./ImageUploader"; | ||||
| import Img from "../../UI/Base/Img"; | ||||
| 
 | ||||
| export class Imgur extends ImageProvider implements ImageUploader{ | ||||
| export class Imgur extends ImageProvider implements ImageUploader { | ||||
|     public static readonly defaultValuePrefix = ["https://i.imgur.com"] | ||||
|     public static readonly singleton = new Imgur() | ||||
|     public readonly defaultKeyPrefixes: string[] = ["image"] | ||||
|     public readonly  maxFileSizeInMegabytes = 10 | ||||
|     public readonly maxFileSizeInMegabytes = 10 | ||||
|     public static readonly apiUrl = "https://api.imgur.com/3/image" | ||||
| 
 | ||||
|     private constructor() { | ||||
|         super() | ||||
|     } | ||||
| 
 | ||||
|     apiUrls(): string[] { | ||||
|         return [Imgur.apiUrl] | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Uploads an image, returns the URL where to find the image | ||||
|      * @param title | ||||
|  | @ -24,8 +31,8 @@ export class Imgur extends ImageProvider implements ImageUploader{ | |||
|         title: string, | ||||
|         description: string, | ||||
|         blob: File | ||||
|     ): Promise<{ key: string, value: string }> { | ||||
|         const apiUrl = "https://api.imgur.com/3/image" | ||||
|     ): Promise<{ key: string; value: string }> { | ||||
|         const apiUrl = Imgur.apiUrl | ||||
|         const apiKey = Constants.ImgurApiKey | ||||
| 
 | ||||
|         const formData = new FormData() | ||||
|  | @ -33,7 +40,6 @@ export class Imgur extends ImageProvider implements ImageUploader{ | |||
|         formData.append("title", title) | ||||
|         formData.append("description", description) | ||||
| 
 | ||||
| 
 | ||||
|         const settings: RequestInit = { | ||||
|             method: "POST", | ||||
|             body: formData, | ||||
|  |  | |||
|  | @ -17,6 +17,10 @@ export class Mapillary extends ImageProvider { | |||
|     ] | ||||
|     defaultKeyPrefixes = ["mapillary", "image"] | ||||
| 
 | ||||
|     apiUrls(): string[] { | ||||
|         return ["https://mapillary.com", "https://www.mapillary.com", "https://graph.mapillary.com"] | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Indicates that this is the same URL | ||||
|      * Ignores 'stp' parameter | ||||
|  |  | |||
|  | @ -5,6 +5,9 @@ import { WikimediaImageProvider } from "./WikimediaImageProvider" | |||
| import Wikidata from "../Web/Wikidata" | ||||
| 
 | ||||
| export class WikidataImageProvider extends ImageProvider { | ||||
|     public apiUrls(): string[] { | ||||
|         return Wikidata.neededUrls | ||||
|     } | ||||
|     public static readonly singleton = new WikidataImageProvider() | ||||
|     public readonly defaultKeyPrefixes = ["wikidata"] | ||||
| 
 | ||||
|  |  | |||
|  | @ -11,11 +11,11 @@ import Wikimedia from "../Web/Wikimedia" | |||
|  */ | ||||
| export class WikimediaImageProvider extends ImageProvider { | ||||
|     public static readonly singleton = new WikimediaImageProvider() | ||||
|     public static readonly commonsPrefixes = [ | ||||
|     public static readonly apiUrls = [ | ||||
|         "https://commons.wikimedia.org/wiki/", | ||||
|         "https://upload.wikimedia.org", | ||||
|         "File:", | ||||
|     ] | ||||
|     public static readonly commonsPrefixes = [...WikimediaImageProvider.apiUrls, "File:"] | ||||
|     private readonly commons_key = "wikimedia_commons" | ||||
|     public readonly defaultKeyPrefixes = [this.commons_key, "image"] | ||||
| 
 | ||||
|  | @ -66,6 +66,10 @@ export class WikimediaImageProvider extends ImageProvider { | |||
|         return value | ||||
|     } | ||||
| 
 | ||||
|     apiUrls(): string[] { | ||||
|         return WikimediaImageProvider.apiUrls | ||||
|     } | ||||
| 
 | ||||
|     SourceIcon(backlink: string): BaseUIElement { | ||||
|         const img = Svg.wikimedia_commons_white_svg().SetStyle("width:2em;height: 2em") | ||||
|         if (backlink === undefined) { | ||||
|  |  | |||
|  | @ -32,11 +32,12 @@ export default class Maproulette { | |||
|     private readonly apiKey: string | ||||
| 
 | ||||
|     public static singleton = new Maproulette() | ||||
|     public static readonly defaultEndpoint = "https://maproulette.org/api/v2" | ||||
|     /** | ||||
|      * Creates a new Maproulette instance | ||||
|      * @param endpoint The API endpoint to use | ||||
|      */ | ||||
|     constructor(endpoint: string = "https://maproulette.org/api/v2") { | ||||
|     constructor(endpoint: string = Maproulette.defaultEndpoint) { | ||||
|         this.endpoint = endpoint | ||||
|         this.apiKey = Constants.MaprouletteApiKey | ||||
|     } | ||||
|  |  | |||
							
								
								
									
										6
									
								
								src/Logic/Osm/AuthConfig.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/Logic/Osm/AuthConfig.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,6 @@ | |||
| export interface AuthConfig { | ||||
|     "#"?: string // optional comment
 | ||||
|     oauth_client_id: string | ||||
|     oauth_secret: string | ||||
|     url: string | ||||
| } | ||||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -28,14 +28,8 @@ class FeatureSwitchUtils { | |||
| 
 | ||||
| export class OsmConnectionFeatureSwitches { | ||||
|     public readonly featureSwitchFakeUser: UIEventSource<boolean> | ||||
|     public readonly featureSwitchApiURL: UIEventSource<string> | ||||
| 
 | ||||
|     constructor() { | ||||
|         this.featureSwitchApiURL = QueryParameters.GetQueryParameter( | ||||
|             "backend", | ||||
|             "osm", | ||||
|             "The OSM backend to use - can be used to redirect mapcomplete to the testing backend when using 'osm-test'" | ||||
|         ) | ||||
| 
 | ||||
|         this.featureSwitchFakeUser = QueryParameters.GetBooleanQueryParameter( | ||||
|             "fake-user", | ||||
|  | @ -143,7 +137,6 @@ export default class FeatureSwitchState extends OsmConnectionFeatureSwitches { | |||
| 
 | ||||
|         let testingDefaultValue = false | ||||
|         if ( | ||||
|             this.featureSwitchApiURL.data !== "osm-test" && | ||||
|             !Utils.runningFromConsole && | ||||
|             (location.hostname === "localhost" || location.hostname === "127.0.0.1") | ||||
|         ) { | ||||
|  |  | |||
|  | @ -1,9 +1,9 @@ | |||
| import { IndexedFeatureSource } from "../FeatureSource/FeatureSource" | ||||
| import { GeoOperations } from "../GeoOperations" | ||||
| import { ImmutableStore, Store, Stores, UIEventSource } from "../UIEventSource" | ||||
| import { Mapillary } from "../ImageProviders/Mapillary" | ||||
| import P4C from "pic4carto" | ||||
| import { Utils } from "../../Utils" | ||||
| 
 | ||||
| export interface NearbyImageOptions { | ||||
|     lon: number | ||||
|     lat: number | ||||
|  | @ -35,17 +35,12 @@ export interface P4CPicture { | |||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Uses Pic4wCarto to fetch nearby images from various providers | ||||
|  * Uses Pic4Carto to fetch nearby images from various providers | ||||
|  */ | ||||
| export default class NearbyImagesSearch { | ||||
|     private static readonly services = [ | ||||
|         "mapillary", | ||||
|         "flickr", | ||||
|         "openstreetcam", | ||||
|         "wikicommons", | ||||
|     ] as const | ||||
| 
 | ||||
|     private individualStores | ||||
|     public static readonly services = ["mapillary", "flickr", "kartaview", "wikicommons"] as const | ||||
|     public static readonly apiUrls = ["https://api.flickr.com"] | ||||
|     private readonly individualStores: Store<{ images: P4CPicture[]; beforeFilter: number }>[] | ||||
|     private readonly _store: UIEventSource<P4CPicture[]> = new UIEventSource<P4CPicture[]>([]) | ||||
|     public readonly store: Store<P4CPicture[]> = this._store | ||||
|     private readonly _options: NearbyImageOptions | ||||
|  | @ -71,16 +66,16 @@ export default class NearbyImagesSearch { | |||
|         this.update() | ||||
|     } | ||||
| 
 | ||||
|     private static buildPictureFetcher( | ||||
|     private static async fetchImages( | ||||
|         options: NearbyImageOptions, | ||||
|         fetcher: "mapillary" | "flickr" | "openstreetcam" | "wikicommons" | ||||
|     ): Store<{ images: P4CPicture[]; beforeFilter: number }> { | ||||
|         fetcher: P4CService | ||||
|     ): Promise<P4CPicture[]> { | ||||
|         const picManager = new P4C.PicturesManager({ usefetchers: [fetcher] }) | ||||
|         const searchRadius = options.searchRadius ?? 100 | ||||
|         const maxAgeSeconds = (options.maxDaysOld ?? 3 * 365) * 24 * 60 * 60 * 1000 | ||||
|         const searchRadius = options.searchRadius ?? 100 | ||||
| 
 | ||||
|         const p4cStore = Stores.FromPromise<P4CPicture[]>( | ||||
|             picManager.startPicsRetrievalAround( | ||||
|         try { | ||||
|             const pics: P4CPicture[] = await picManager.startPicsRetrievalAround( | ||||
|                 new P4C.LatLng(options.lat, options.lon), | ||||
|                 searchRadius, | ||||
|                 { | ||||
|  | @ -88,7 +83,21 @@ export default class NearbyImagesSearch { | |||
|                     towardscenter: false, | ||||
|                 } | ||||
|             ) | ||||
|             return pics | ||||
|         } catch (e) { | ||||
|             console.error("Could not fetch images from service", fetcher, e) | ||||
|             return [] | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private static buildPictureFetcher( | ||||
|         options: NearbyImageOptions, | ||||
|         fetcher: P4CService | ||||
|     ): Store<{ images: P4CPicture[]; beforeFilter: number }> { | ||||
|         const p4cStore = Stores.FromPromise<P4CPicture[]>( | ||||
|             NearbyImagesSearch.fetchImages(options, fetcher) | ||||
|         ) | ||||
|         const searchRadius = options.searchRadius ?? 100 | ||||
|         return p4cStore.map( | ||||
|             (images) => { | ||||
|                 if (images === undefined) { | ||||
|  | @ -220,3 +229,5 @@ class ImagesInLoadedDataFetcher { | |||
|         return foundImages | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| type P4CService = (typeof NearbyImagesSearch.services)[number] | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| import { Utils } from "../../Utils" | ||||
| 
 | ||||
| export default class PlantNet { | ||||
|     private static baseUrl = | ||||
|     public static baseUrl = | ||||
|         "https://my-api.plantnet.org/v2/identify/all?api-key=2b10AAsjzwzJvucA5Ncm5qxe" | ||||
| 
 | ||||
|     public static query(imageUrls: string[]): Promise<PlantNetResult> { | ||||
|  |  | |||
|  | @ -123,6 +123,11 @@ export interface WikidataAdvancedSearchoptions extends WikidataSearchoptions { | |||
|  * Utility functions around wikidata | ||||
|  */ | ||||
| export default class Wikidata { | ||||
|     public static readonly neededUrls = [ | ||||
|         "https://www.wikidata.org/", | ||||
|         "https://wikidata.org/", | ||||
|         "https://query.wikidata.org", | ||||
|     ] | ||||
|     private static readonly _identifierPrefixes = ["Q", "L"].map((str) => str.toLowerCase()) | ||||
|     private static readonly _prefixesToRemove = [ | ||||
|         "https://www.wikidata.org/wiki/Lexeme:", | ||||
|  | @ -130,11 +135,11 @@ export default class Wikidata { | |||
|         "http://www.wikidata.org/entity/", | ||||
|         "Lexeme:", | ||||
|     ].map((str) => str.toLowerCase()) | ||||
| 
 | ||||
|     private static readonly _storeCache = new Map< | ||||
|         string, | ||||
|         Store<{ success: WikidataResponse } | { error: any }> | ||||
|     >() | ||||
| 
 | ||||
|     /** | ||||
|      * Same as LoadWikidataEntry, but wrapped into a UIEventSource | ||||
|      * @param value | ||||
|  | @ -388,6 +393,7 @@ export default class Wikidata { | |||
|     } | ||||
| 
 | ||||
|     private static _cache = new Map<string, Promise<WikidataResponse>>() | ||||
| 
 | ||||
|     public static async LoadWikidataEntryAsync(value: string | number): Promise<WikidataResponse> { | ||||
|         const key = "" + value | ||||
|         const cached = Wikidata._cache.get(key) | ||||
|  | @ -398,6 +404,7 @@ export default class Wikidata { | |||
|         Wikidata._cache.set(key, uncached) | ||||
|         return uncached | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Loads a wikidata page | ||||
|      * @returns the entity of the given value | ||||
|  |  | |||
|  | @ -34,6 +34,8 @@ export default class Wikipedia { | |||
| 
 | ||||
|     private static readonly idsToRemove = ["sjabloon_zie"] | ||||
| 
 | ||||
|     public static readonly neededUrls = ["*.wikipedia.org"] | ||||
| 
 | ||||
|     private static readonly _cache = new Map<string, Promise<string>>() | ||||
|     private static _fullDetailsCache = new Map<string, Store<FullWikipediaDetails>>() | ||||
|     public readonly backend: string | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| import * as packagefile from "../../package.json" | ||||
| import * as extraconfig from "../../config.json" | ||||
| import { Utils } from "../Utils" | ||||
| import { AuthConfig } from "../Logic/Osm/AuthConfig" | ||||
| 
 | ||||
| export type PriviligedLayerType = (typeof Constants.priviliged_layers)[number] | ||||
| 
 | ||||
|  | @ -104,7 +105,8 @@ export default class Constants { | |||
|     public static ImgurApiKey = Constants.config.api_keys.imgur | ||||
|     public static readonly mapillary_client_token_v4 = Constants.config.api_keys.mapillary_v4 | ||||
|     public static defaultOverpassUrls = Constants.config.default_overpass_urls | ||||
|     static countryCoderEndpoint: string = Constants.config.country_coder_host | ||||
|     public static countryCoderEndpoint: string = Constants.config.country_coder_host | ||||
|     public static osmAuthConfig: AuthConfig = Constants.config.oauth_credentials | ||||
| 
 | ||||
|     /** | ||||
|      * These are the values that are allowed to use as 'backdrop' icon for a map pin | ||||
|  |  | |||
|  | @ -140,8 +140,7 @@ export default class ThemeViewState implements SpecialVisualizationState { | |||
|                 "oauth_token", | ||||
|                 undefined, | ||||
|                 "Used to complete the login" | ||||
|             ), | ||||
|             osmConfiguration: <"osm" | "osm-test">this.featureSwitches.featureSwitchApiURL.data, | ||||
|             ) | ||||
|         }) | ||||
|         this.userRelatedState = new UserRelatedState( | ||||
|             this.osmConnection, | ||||
|  |  | |||
|  | @ -22,8 +22,7 @@ export default class AllThemesGui { | |||
|                     "oauth_token", | ||||
|                     undefined, | ||||
|                     "Used to complete the login" | ||||
|                 ), | ||||
|                 osmConfiguration: <"osm" | "osm-test">featureSwitches.featureSwitchApiURL.data, | ||||
|                 ) | ||||
|             }) | ||||
|             const state = new UserRelatedState(osmConnection) | ||||
|             const intro = new Combine([ | ||||
|  |  | |||
|  | @ -11,6 +11,7 @@ import { Utils } from "../../Utils" | |||
| import Constants from "../../Models/Constants" | ||||
| 
 | ||||
| export class OpenJosm extends Combine { | ||||
|     public static readonly needsUrls = ["http://127.0.0.1:8111/load_and_zoom"] | ||||
|     constructor(osmConnection: OsmConnection, bounds: Store<BBox>, iconStyle?: string) { | ||||
|         const t = Translations.t.general.attribution | ||||
| 
 | ||||
|  |  | |||
|  | @ -10,9 +10,11 @@ import Combine from "../Base/Combine" | |||
| import Title from "../Base/Title" | ||||
| import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" | ||||
| import { UIEventSource } from "../../Logic/UIEventSource" | ||||
| import Constants from "../../Models/Constants" | ||||
| 
 | ||||
| export class AddNoteCommentViz implements SpecialVisualization { | ||||
|     funcName = "add_note_comment" | ||||
|     needsUrls = [Constants.osmAuthConfig.url] | ||||
|     docs = "A textfield to add a comment to a node (with the option to close the note)." | ||||
|     args = [ | ||||
|         { | ||||
|  |  | |||
|  | @ -9,7 +9,6 @@ import { Utils } from "../../Utils" | |||
| import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource" | ||||
| import { VariableUiElement } from "../Base/VariableUIElement" | ||||
| import Loading from "../Base/Loading" | ||||
| import { OsmConnection } from "../../Logic/Osm/OsmConnection" | ||||
| import Translations from "../i18n/Translations" | ||||
| import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" | ||||
| import { Changes } from "../../Logic/Osm/Changes" | ||||
|  | @ -209,6 +208,8 @@ class ApplyButton extends UIElement { | |||
| export default class AutoApplyButton implements SpecialVisualization { | ||||
|     public readonly docs: BaseUIElement | ||||
|     public readonly funcName: string = "auto_apply" | ||||
|     public readonly needsUrls = [] | ||||
| 
 | ||||
|     public readonly args: { | ||||
|         name: string | ||||
|         defaultValue?: string | ||||
|  | @ -271,14 +272,7 @@ export default class AutoApplyButton implements SpecialVisualization { | |||
|         argument: string[] | ||||
|     ): BaseUIElement { | ||||
|         try { | ||||
|             if ( | ||||
|                 !state.layout.official && | ||||
|                 !( | ||||
|                     state.featureSwitchIsTesting.data || | ||||
|                     state.osmConnection._oauth_config.url === | ||||
|                         OsmConnection.oauth_configs["osm-test"].url | ||||
|                 ) | ||||
|             ) { | ||||
|             if (!state.layout.official && !state.featureSwitchIsTesting.data) { | ||||
|                 const t = Translations.t.general.add.import | ||||
|                 return new Combine([ | ||||
|                     new FixedUiElement( | ||||
|  |  | |||
|  | @ -8,9 +8,11 @@ import Toggle from "../Input/Toggle" | |||
| import { LoginToggle } from "./LoginButton" | ||||
| import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" | ||||
| import { UIEventSource } from "../../Logic/UIEventSource" | ||||
| import Constants from "../../Models/Constants" | ||||
| 
 | ||||
| export class CloseNoteButton implements SpecialVisualization { | ||||
|     public readonly funcName = "close_note" | ||||
|     public readonly needsUrls = [Constants.osmAuthConfig.url] | ||||
|     public readonly docs = | ||||
|         "Button to close a note. A predifined text can be defined to close the note with. If the note is already closed, will show a small text." | ||||
|     public readonly args = [ | ||||
|  |  | |||
|  | @ -13,7 +13,7 @@ export class ExportAsGpxViz implements SpecialVisualization { | |||
|     funcName = "export_as_gpx" | ||||
|     docs = "Exports the selected feature as GPX-file" | ||||
|     args = [] | ||||
| 
 | ||||
|     needsUrls = [] | ||||
|     constr( | ||||
|         state: SpecialVisualizationState, | ||||
|         tagSource: UIEventSource<Record<string, string>>, | ||||
|  |  | |||
|  | @ -2,10 +2,13 @@ import { Store, UIEventSource } from "../../Logic/UIEventSource" | |||
| import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" | ||||
| import Histogram from "../BigComponents/Histogram" | ||||
| import { Feature } from "geojson" | ||||
| import Constants from "../../Models/Constants" | ||||
| 
 | ||||
| export class HistogramViz implements SpecialVisualization { | ||||
|     funcName = "histogram" | ||||
|     docs = "Create a histogram for a list of given values, read from the properties." | ||||
|     needsUrls = [] | ||||
| 
 | ||||
|     example = | ||||
|         '`{histogram(\'some_key\')}` with properties being `{some_key: ["a","b","a","c"]} to create a histogram' | ||||
|     args = [ | ||||
|  |  | |||
|  | @ -24,6 +24,7 @@ export interface ConflateFlowArguments extends ImportFlowArguments { | |||
| 
 | ||||
| export default class ConflateImportButtonViz implements SpecialVisualization, AutoAction { | ||||
|     supportsAutoAction: boolean = true | ||||
|     needsUrls = [] | ||||
|     public readonly funcName: string = "conflate_button" | ||||
|     public readonly args: { | ||||
|         name: string | ||||
|  |  | |||
|  | @ -194,10 +194,7 @@ export default abstract class ImportFlow<ArgT extends ImportFlowArguments> { | |||
|                     return { error: t.hasBeenImported } | ||||
|                 } | ||||
| 
 | ||||
|                 const usesTestUrl = | ||||
|                     this.state.osmConnection._oauth_config.url === | ||||
|                     OsmConnection.oauth_configs["osm-test"].url | ||||
|                 if (!state.layout.official && !(isTesting || usesTestUrl)) { | ||||
|                 if (!state.layout.official && !isTesting) { | ||||
|                     // Unofficial theme - imports not allowed
 | ||||
|                     return { | ||||
|                         error: t.officialThemesOnly, | ||||
|  |  | |||
|  | @ -18,6 +18,7 @@ export class PointImportButtonViz implements SpecialVisualization { | |||
|     public readonly docs: string | BaseUIElement | ||||
|     public readonly example?: string | ||||
|     public readonly args: { name: string; defaultValue?: string; doc: string }[] | ||||
|     public needsUrls = [] | ||||
| 
 | ||||
|     constructor() { | ||||
|         this.funcName = "import_button" | ||||
|  |  | |||
|  | @ -20,6 +20,7 @@ import FullNodeDatabaseSource from "../../../Logic/FeatureSource/TiledFeatureSou | |||
|  */ | ||||
| export default class WayImportButtonViz implements AutoAction, SpecialVisualization { | ||||
|     public readonly funcName: string = "import_way_button" | ||||
|     needsUrls = [] | ||||
|     public readonly docs: string = | ||||
|         "This button will copy the data from an external dataset into OpenStreetMap, copying the geometry and adding it as a 'line'" + | ||||
|         ImportFlowUtils.documentationGeneral | ||||
|  |  | |||
|  | @ -20,6 +20,7 @@ import { Feature } from "geojson" | |||
| 
 | ||||
| export class LanguageElement implements SpecialVisualization { | ||||
|     funcName: string = "language_chooser" | ||||
|     needsUrls = [] | ||||
| 
 | ||||
|     docs: string | BaseUIElement = | ||||
|         "The language element allows to show and pick all known (modern) languages. The key can be set" | ||||
|  |  | |||
|  | @ -9,6 +9,8 @@ import MapillaryLink from "../BigComponents/MapillaryLink.svelte" | |||
| export class MapillaryLinkVis implements SpecialVisualization { | ||||
|     funcName = "mapillary_link" | ||||
|     docs = "Adds a button to open mapillary on the specified location" | ||||
|     needsUrls = [] | ||||
| 
 | ||||
|     args = [ | ||||
|         { | ||||
|             name: "zoom", | ||||
|  |  | |||
|  | @ -13,6 +13,7 @@ import { BBox } from "../../Logic/BBox" | |||
| export class MinimapViz implements SpecialVisualization { | ||||
|     funcName = "minimap" | ||||
|     docs = "A small map showing the selected feature." | ||||
|     needsUrls = [] | ||||
|     args = [ | ||||
|         { | ||||
|             doc: "The (maximum) zoomlevel: the target zoomlevel after fitting the entire feature. The minimap will fit the entire feature, then zoom out to this zoom level. The higher, the more zoomed in with 1 being the entire world and 19 being really close", | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisua | |||
| 
 | ||||
| export class MultiApplyViz implements SpecialVisualization { | ||||
|     funcName = "multi_apply" | ||||
|     needsUrls = [] | ||||
|     docs = | ||||
|         "A button to apply the tagging of this object onto a list of other features. This is an advanced feature for which you'll need calculatedTags" | ||||
|     args = [ | ||||
|  |  | |||
|  | @ -8,9 +8,10 @@ import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders" | |||
| import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" | ||||
| import SvelteUIElement from "../Base/SvelteUIElement" | ||||
| import PlantNet from "../PlantNet/PlantNet.svelte" | ||||
| 
 | ||||
| import { default as PlantNetCode } from "../../Logic/Web/PlantNet" | ||||
| export class PlantNetDetectionViz implements SpecialVisualization { | ||||
|     funcName = "plantnet_detection" | ||||
|     needsUrls = [PlantNetCode.baseUrl] | ||||
| 
 | ||||
|     docs = | ||||
|         "Sends the images linked to the current object to plantnet.org and asks it what plant species is shown on it. The user can then select the correct species; the corresponding wikidata-identifier will then be added to the object (together with `source:species:wikidata=plantnet.org AI`). " | ||||
|  |  | |||
|  | @ -11,6 +11,8 @@ import LayerConfig from "../../Models/ThemeConfig/LayerConfig" | |||
|  */ | ||||
| export default class QuestionViz implements SpecialVisualization { | ||||
|     funcName = "questions" | ||||
|     needsUrls = [] | ||||
| 
 | ||||
|     docs = | ||||
|         "The special element which shows the questions which are unkown. Added by default if not yet there" | ||||
|     args = [ | ||||
|  |  | |||
|  | @ -15,6 +15,7 @@ export class ShareLinkViz implements SpecialVisualization { | |||
|             doc: "The url to share (default: current URL)", | ||||
|         }, | ||||
|     ] | ||||
|     needsUrls = [] | ||||
| 
 | ||||
|     public constr( | ||||
|         state: SpecialVisualizationState, | ||||
|  |  | |||
|  | @ -21,6 +21,7 @@ import Maproulette from "../../Logic/Maproulette" | |||
| 
 | ||||
| export default class TagApplyButton implements AutoAction, SpecialVisualization { | ||||
|     public readonly funcName = "tag_apply" | ||||
|     needsUrls = [] | ||||
|     public readonly docs = | ||||
|         "Shows a big button; clicking this button will apply certain tags onto the feature.\n\nThe first argument takes a specification of which tags to add.\n" + | ||||
|         Utils.Special_visualizations_tagsToApplyHelpText | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ import UploadTraceToOsmUI from "../BigComponents/UploadTraceToOsmUI" | |||
| import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" | ||||
| import { UIEventSource } from "../../Logic/UIEventSource" | ||||
| import { GeoOperations } from "../../Logic/GeoOperations" | ||||
| import Constants from "../../Models/Constants" | ||||
| 
 | ||||
| /** | ||||
|  * Wrapper  around 'UploadTraceToOsmUI' | ||||
|  | @ -11,6 +12,7 @@ export class UploadToOsmViz implements SpecialVisualization { | |||
|     docs = | ||||
|         "Uploads the GPS-history as GPX to OpenStreetMap.org; clears the history afterwards. The actual feature is ignored." | ||||
|     args = [] | ||||
|     needsUrls = [Constants.osmAuthConfig.url] | ||||
| 
 | ||||
|     constr( | ||||
|         state: SpecialVisualizationState, | ||||
|  |  | |||
							
								
								
									
										32
									
								
								src/UI/RemoveOtherLanguages.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/UI/RemoveOtherLanguages.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,32 @@ | |||
| export {} | ||||
| let lang = ( | ||||
|     (navigator.languages && navigator.languages[0]) || | ||||
|     navigator.language || | ||||
|     navigator["userLanguage"] || | ||||
|     "en" | ||||
| ).substr(0, 2) | ||||
| 
 | ||||
| function filterLangs(maindiv: HTMLElement) { | ||||
|     let foundLangs = 0 | ||||
|     for (const child of Array.from(maindiv.children)) { | ||||
|         if (child.attributes.getNamedItem("lang")?.value === lang) { | ||||
|             foundLangs++ | ||||
|         } | ||||
|     } | ||||
|     if (foundLangs === 0) { | ||||
|         lang = "en" | ||||
|     } | ||||
|     for (const child of Array.from(maindiv.children)) { | ||||
|         const childLang = child.attributes.getNamedItem("lang") | ||||
|         if (childLang === undefined) { | ||||
|             continue | ||||
|         } | ||||
|         if (childLang.value === lang) { | ||||
|             continue | ||||
|         } | ||||
|         child.parentElement.removeChild(child) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| filterLangs(document.getElementById("descriptions-while-loading")) | ||||
| filterLangs(document.getElementById("default-title")) | ||||
|  | @ -1,118 +1,122 @@ | |||
| import { Store, UIEventSource } from "../Logic/UIEventSource"; | ||||
| import BaseUIElement from "./BaseUIElement"; | ||||
| import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"; | ||||
| import { IndexedFeatureSource, WritableFeatureSource } from "../Logic/FeatureSource/FeatureSource"; | ||||
| import { OsmConnection } from "../Logic/Osm/OsmConnection"; | ||||
| import { Changes } from "../Logic/Osm/Changes"; | ||||
| import { ExportableMap, MapProperties } from "../Models/MapProperties"; | ||||
| import LayerState from "../Logic/State/LayerState"; | ||||
| import { Feature, Geometry, Point } from "geojson"; | ||||
| import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource"; | ||||
| import { MangroveIdentity } from "../Logic/Web/MangroveReviews"; | ||||
| import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore"; | ||||
| import LayerConfig from "../Models/ThemeConfig/LayerConfig"; | ||||
| import FeatureSwitchState from "../Logic/State/FeatureSwitchState"; | ||||
| import { MenuState } from "../Models/MenuState"; | ||||
| import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader"; | ||||
| import { RasterLayerPolygon } from "../Models/RasterLayers"; | ||||
| import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager"; | ||||
| import { OsmTags } from "../Models/OsmFeature"; | ||||
| import { Store, UIEventSource } from "../Logic/UIEventSource" | ||||
| import BaseUIElement from "./BaseUIElement" | ||||
| import LayoutConfig from "../Models/ThemeConfig/LayoutConfig" | ||||
| import { IndexedFeatureSource, WritableFeatureSource } from "../Logic/FeatureSource/FeatureSource" | ||||
| import { OsmConnection } from "../Logic/Osm/OsmConnection" | ||||
| import { Changes } from "../Logic/Osm/Changes" | ||||
| import { ExportableMap, MapProperties } from "../Models/MapProperties" | ||||
| import LayerState from "../Logic/State/LayerState" | ||||
| import { Feature, Geometry, Point } from "geojson" | ||||
| import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource" | ||||
| import { MangroveIdentity } from "../Logic/Web/MangroveReviews" | ||||
| import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore" | ||||
| import LayerConfig from "../Models/ThemeConfig/LayerConfig" | ||||
| import FeatureSwitchState from "../Logic/State/FeatureSwitchState" | ||||
| import { MenuState } from "../Models/MenuState" | ||||
| import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader" | ||||
| import { RasterLayerPolygon } from "../Models/RasterLayers" | ||||
| import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager" | ||||
| import { OsmTags } from "../Models/OsmFeature" | ||||
| 
 | ||||
| /** | ||||
|  * The state needed to render a special Visualisation. | ||||
|  */ | ||||
| export interface SpecialVisualizationState { | ||||
|   readonly guistate: MenuState; | ||||
|   readonly layout: LayoutConfig; | ||||
|   readonly featureSwitches: FeatureSwitchState; | ||||
|     readonly guistate: MenuState | ||||
|     readonly layout: LayoutConfig | ||||
|     readonly featureSwitches: FeatureSwitchState | ||||
| 
 | ||||
|   readonly layerState: LayerState; | ||||
|   readonly featureProperties: { getStore(id: string): UIEventSource<Record<string, string>>, trackFeature?(feature: { properties: OsmTags }) }; | ||||
|     readonly layerState: LayerState | ||||
|     readonly featureProperties: { | ||||
|         getStore(id: string): UIEventSource<Record<string, string>> | ||||
|         trackFeature?(feature: { properties: OsmTags }) | ||||
|     } | ||||
| 
 | ||||
|   readonly indexedFeatures: IndexedFeatureSource; | ||||
|     readonly indexedFeatures: IndexedFeatureSource | ||||
| 
 | ||||
|   /** | ||||
|    * Some features will create a new element that should be displayed. | ||||
|    * These can be injected by appending them to this featuresource (and pinging it) | ||||
|    */ | ||||
|   readonly newFeatures: WritableFeatureSource; | ||||
|     /** | ||||
|      * Some features will create a new element that should be displayed. | ||||
|      * These can be injected by appending them to this featuresource (and pinging it) | ||||
|      */ | ||||
|     readonly newFeatures: WritableFeatureSource | ||||
| 
 | ||||
|   readonly historicalUserLocations: WritableFeatureSource<Feature<Point>>; | ||||
|     readonly historicalUserLocations: WritableFeatureSource<Feature<Point>> | ||||
| 
 | ||||
|   readonly osmConnection: OsmConnection; | ||||
|   readonly featureSwitchUserbadge: Store<boolean>; | ||||
|   readonly featureSwitchIsTesting: Store<boolean>; | ||||
|   readonly changes: Changes; | ||||
|   readonly osmObjectDownloader: OsmObjectDownloader; | ||||
|   /** | ||||
|    * State of the main map | ||||
|    */ | ||||
|   readonly mapProperties: MapProperties & ExportableMap; | ||||
|     readonly osmConnection: OsmConnection | ||||
|     readonly featureSwitchUserbadge: Store<boolean> | ||||
|     readonly featureSwitchIsTesting: Store<boolean> | ||||
|     readonly changes: Changes | ||||
|     readonly osmObjectDownloader: OsmObjectDownloader | ||||
|     /** | ||||
|      * State of the main map | ||||
|      */ | ||||
|     readonly mapProperties: MapProperties & ExportableMap | ||||
| 
 | ||||
|   readonly selectedElement: UIEventSource<Feature>; | ||||
|   /** | ||||
|    * Works together with 'selectedElement' to indicate what properties should be displayed | ||||
|    */ | ||||
|   readonly selectedLayer: UIEventSource<LayerConfig>; | ||||
|   readonly selectedElementAndLayer: Store<{ feature: Feature; layer: LayerConfig }>; | ||||
|     readonly selectedElement: UIEventSource<Feature> | ||||
|     /** | ||||
|      * Works together with 'selectedElement' to indicate what properties should be displayed | ||||
|      */ | ||||
|     readonly selectedLayer: UIEventSource<LayerConfig> | ||||
|     readonly selectedElementAndLayer: Store<{ feature: Feature; layer: LayerConfig }> | ||||
| 
 | ||||
|   /** | ||||
|    * If data is currently being fetched from external sources | ||||
|    */ | ||||
|   readonly dataIsLoading: Store<boolean>; | ||||
|   /** | ||||
|    * Only needed for 'ReplaceGeometryAction' | ||||
|    */ | ||||
|   readonly fullNodeDatabase?: FullNodeDatabaseSource; | ||||
|     /** | ||||
|      * If data is currently being fetched from external sources | ||||
|      */ | ||||
|     readonly dataIsLoading: Store<boolean> | ||||
|     /** | ||||
|      * Only needed for 'ReplaceGeometryAction' | ||||
|      */ | ||||
|     readonly fullNodeDatabase?: FullNodeDatabaseSource | ||||
| 
 | ||||
|   readonly perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer>; | ||||
|   readonly userRelatedState: { | ||||
|     readonly imageLicense: UIEventSource<string>; | ||||
|     readonly showTags: UIEventSource<"no" | undefined | "always" | "yes" | "full"> | ||||
|     readonly mangroveIdentity: MangroveIdentity | ||||
|     readonly showAllQuestionsAtOnce: UIEventSource<boolean> | ||||
|     readonly preferencesAsTags: Store<Record<string, string>> | ||||
|     readonly language: UIEventSource<string> | ||||
|   }; | ||||
|   readonly lastClickObject: WritableFeatureSource; | ||||
|     readonly perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer> | ||||
|     readonly userRelatedState: { | ||||
|         readonly imageLicense: UIEventSource<string> | ||||
|         readonly showTags: UIEventSource<"no" | undefined | "always" | "yes" | "full"> | ||||
|         readonly mangroveIdentity: MangroveIdentity | ||||
|         readonly showAllQuestionsAtOnce: UIEventSource<boolean> | ||||
|         readonly preferencesAsTags: Store<Record<string, string>> | ||||
|         readonly language: UIEventSource<string> | ||||
|     } | ||||
|     readonly lastClickObject: WritableFeatureSource | ||||
| 
 | ||||
|   readonly availableLayers: Store<RasterLayerPolygon[]>; | ||||
|     readonly availableLayers: Store<RasterLayerPolygon[]> | ||||
| 
 | ||||
|   readonly imageUploadManager: ImageUploadManager; | ||||
|     readonly imageUploadManager: ImageUploadManager | ||||
| } | ||||
| 
 | ||||
| export interface SpecialVisualization { | ||||
|   readonly funcName: string; | ||||
|   readonly docs: string | BaseUIElement; | ||||
|   readonly example?: string; | ||||
|     readonly funcName: string | ||||
|     readonly docs: string | BaseUIElement | ||||
|     readonly example?: string | ||||
|     readonly needsUrls: string[] | ||||
| 
 | ||||
|   /** | ||||
|    * Indicates that this special visualisation will make requests to the 'alLNodesDatabase' and that it thus should be included | ||||
|    */ | ||||
|   readonly needsNodeDatabase?: boolean; | ||||
|   readonly args: { | ||||
|     name: string | ||||
|     defaultValue?: string | ||||
|     doc: string | ||||
|     required?: false | boolean | ||||
|   }[]; | ||||
|   readonly getLayerDependencies?: (argument: string[]) => string[]; | ||||
|     /** | ||||
|      * Indicates that this special visualisation will make requests to the 'alLNodesDatabase' and that it thus should be included | ||||
|      */ | ||||
|     readonly needsNodeDatabase?: boolean | ||||
|     readonly args: { | ||||
|         name: string | ||||
|         defaultValue?: string | ||||
|         doc: string | ||||
|         required?: false | boolean | ||||
|     }[] | ||||
|     readonly getLayerDependencies?: (argument: string[]) => string[] | ||||
| 
 | ||||
|   structuredExamples?(): { feature: Feature<Geometry, Record<string, string>>; args: string[] }[]; | ||||
|     structuredExamples?(): { feature: Feature<Geometry, Record<string, string>>; args: string[] }[] | ||||
| 
 | ||||
|   constr( | ||||
|     state: SpecialVisualizationState, | ||||
|     tagSource: UIEventSource<Record<string, string>>, | ||||
|     argument: string[], | ||||
|     feature: Feature, | ||||
|     layer: LayerConfig | ||||
|   ): BaseUIElement; | ||||
|     constr( | ||||
|         state: SpecialVisualizationState, | ||||
|         tagSource: UIEventSource<Record<string, string>>, | ||||
|         argument: string[], | ||||
|         feature: Feature, | ||||
|         layer: LayerConfig | ||||
|     ): BaseUIElement | ||||
| } | ||||
| 
 | ||||
| export type RenderingSpecification = | ||||
|   | string | ||||
|   | { | ||||
|   func: SpecialVisualization | ||||
|   args: string[] | ||||
|   style: string | ||||
| } | ||||
|     | string | ||||
|     | { | ||||
|           func: SpecialVisualization | ||||
|           args: string[] | ||||
|           style: string | ||||
|       } | ||||
|  |  | |||
|  | @ -1,71 +1,79 @@ | |||
| import Combine from "./Base/Combine"; | ||||
| import { FixedUiElement } from "./Base/FixedUiElement"; | ||||
| import BaseUIElement from "./BaseUIElement"; | ||||
| import Title from "./Base/Title"; | ||||
| import Table from "./Base/Table"; | ||||
| import { RenderingSpecification, SpecialVisualization, SpecialVisualizationState } from "./SpecialVisualization"; | ||||
| import { HistogramViz } from "./Popup/HistogramViz"; | ||||
| import { MinimapViz } from "./Popup/MinimapViz"; | ||||
| import { ShareLinkViz } from "./Popup/ShareLinkViz"; | ||||
| import { UploadToOsmViz } from "./Popup/UploadToOsmViz"; | ||||
| import { MultiApplyViz } from "./Popup/MultiApplyViz"; | ||||
| import { AddNoteCommentViz } from "./Popup/AddNoteCommentViz"; | ||||
| import { PlantNetDetectionViz } from "./Popup/PlantNetDetectionViz"; | ||||
| import TagApplyButton from "./Popup/TagApplyButton"; | ||||
| import { CloseNoteButton } from "./Popup/CloseNoteButton"; | ||||
| import { MapillaryLinkVis } from "./Popup/MapillaryLinkVis"; | ||||
| import { Store, Stores, UIEventSource } from "../Logic/UIEventSource"; | ||||
| import AllTagsPanel from "./Popup/AllTagsPanel.svelte"; | ||||
| import AllImageProviders from "../Logic/ImageProviders/AllImageProviders"; | ||||
| import { ImageCarousel } from "./Image/ImageCarousel"; | ||||
| import { VariableUiElement } from "./Base/VariableUIElement"; | ||||
| import { Utils } from "../Utils"; | ||||
| import Wikidata, { WikidataResponse } from "../Logic/Web/Wikidata"; | ||||
| import { Translation } from "./i18n/Translation"; | ||||
| import Translations from "./i18n/Translations"; | ||||
| import ReviewForm from "./Reviews/ReviewForm"; | ||||
| import ReviewElement from "./Reviews/ReviewElement"; | ||||
| import OpeningHoursVisualization from "./OpeningHours/OpeningHoursVisualization"; | ||||
| import LiveQueryHandler from "../Logic/Web/LiveQueryHandler"; | ||||
| import { SubtleButton } from "./Base/SubtleButton"; | ||||
| import Svg from "../Svg"; | ||||
| import NoteCommentElement from "./Popup/NoteCommentElement"; | ||||
| import { SubstitutedTranslation } from "./SubstitutedTranslation"; | ||||
| import List from "./Base/List"; | ||||
| import StatisticsPanel from "./BigComponents/StatisticsPanel"; | ||||
| import AutoApplyButton from "./Popup/AutoApplyButton"; | ||||
| import { LanguageElement } from "./Popup/LanguageElement"; | ||||
| import FeatureReviews from "../Logic/Web/MangroveReviews"; | ||||
| import Maproulette from "../Logic/Maproulette"; | ||||
| import SvelteUIElement from "./Base/SvelteUIElement"; | ||||
| import { BBoxFeatureSourceForLayer } from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource"; | ||||
| import QuestionViz from "./Popup/QuestionViz"; | ||||
| import { Feature, Point } from "geojson"; | ||||
| import { GeoOperations } from "../Logic/GeoOperations"; | ||||
| import CreateNewNote from "./Popup/CreateNewNote.svelte"; | ||||
| import AddNewPoint from "./Popup/AddNewPoint/AddNewPoint.svelte"; | ||||
| import UserProfile from "./BigComponents/UserProfile.svelte"; | ||||
| import LanguagePicker from "./LanguagePicker"; | ||||
| import Link from "./Base/Link"; | ||||
| import LayerConfig from "../Models/ThemeConfig/LayerConfig"; | ||||
| import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig"; | ||||
| import { OsmTags, WayId } from "../Models/OsmFeature"; | ||||
| import MoveWizard from "./Popup/MoveWizard"; | ||||
| import SplitRoadWizard from "./Popup/SplitRoadWizard"; | ||||
| import { ExportAsGpxViz } from "./Popup/ExportAsGpxViz"; | ||||
| import WikipediaPanel from "./Wikipedia/WikipediaPanel.svelte"; | ||||
| import TagRenderingEditable from "./Popup/TagRendering/TagRenderingEditable.svelte"; | ||||
| import { PointImportButtonViz } from "./Popup/ImportButtons/PointImportButtonViz"; | ||||
| import WayImportButtonViz from "./Popup/ImportButtons/WayImportButtonViz"; | ||||
| import ConflateImportButtonViz from "./Popup/ImportButtons/ConflateImportButtonViz"; | ||||
| import DeleteWizard from "./Popup/DeleteFlow/DeleteWizard.svelte"; | ||||
| import { OpenJosm } from "./BigComponents/OpenJosm"; | ||||
| import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte"; | ||||
| import FediverseValidator from "./InputElement/Validators/FediverseValidator"; | ||||
| import SendEmail from "./Popup/SendEmail.svelte"; | ||||
| import NearbyImages from "./Popup/NearbyImages.svelte"; | ||||
| import NearbyImagesCollapsed from "./Popup/NearbyImagesCollapsed.svelte"; | ||||
| import UploadImage from "./Image/UploadImage.svelte"; | ||||
| import Combine from "./Base/Combine" | ||||
| import { FixedUiElement } from "./Base/FixedUiElement" | ||||
| import BaseUIElement from "./BaseUIElement" | ||||
| import Title from "./Base/Title" | ||||
| import Table from "./Base/Table" | ||||
| import { | ||||
|     RenderingSpecification, | ||||
|     SpecialVisualization, | ||||
|     SpecialVisualizationState, | ||||
| } from "./SpecialVisualization" | ||||
| import { HistogramViz } from "./Popup/HistogramViz" | ||||
| import { MinimapViz } from "./Popup/MinimapViz" | ||||
| import { ShareLinkViz } from "./Popup/ShareLinkViz" | ||||
| import { UploadToOsmViz } from "./Popup/UploadToOsmViz" | ||||
| import { MultiApplyViz } from "./Popup/MultiApplyViz" | ||||
| import { AddNoteCommentViz } from "./Popup/AddNoteCommentViz" | ||||
| import { PlantNetDetectionViz } from "./Popup/PlantNetDetectionViz" | ||||
| import TagApplyButton from "./Popup/TagApplyButton" | ||||
| import { CloseNoteButton } from "./Popup/CloseNoteButton" | ||||
| import { MapillaryLinkVis } from "./Popup/MapillaryLinkVis" | ||||
| import { Store, Stores, UIEventSource } from "../Logic/UIEventSource" | ||||
| import AllTagsPanel from "./Popup/AllTagsPanel.svelte" | ||||
| import AllImageProviders from "../Logic/ImageProviders/AllImageProviders" | ||||
| import { ImageCarousel } from "./Image/ImageCarousel" | ||||
| import { VariableUiElement } from "./Base/VariableUIElement" | ||||
| import { Utils } from "../Utils" | ||||
| import Wikidata, { WikidataResponse } from "../Logic/Web/Wikidata" | ||||
| import { Translation } from "./i18n/Translation" | ||||
| import Translations from "./i18n/Translations" | ||||
| import ReviewForm from "./Reviews/ReviewForm" | ||||
| import ReviewElement from "./Reviews/ReviewElement" | ||||
| import OpeningHoursVisualization from "./OpeningHours/OpeningHoursVisualization" | ||||
| import { SubtleButton } from "./Base/SubtleButton" | ||||
| import Svg from "../Svg" | ||||
| import NoteCommentElement from "./Popup/NoteCommentElement" | ||||
| import { SubstitutedTranslation } from "./SubstitutedTranslation" | ||||
| import List from "./Base/List" | ||||
| import StatisticsPanel from "./BigComponents/StatisticsPanel" | ||||
| import AutoApplyButton from "./Popup/AutoApplyButton" | ||||
| import { LanguageElement } from "./Popup/LanguageElement" | ||||
| import FeatureReviews from "../Logic/Web/MangroveReviews" | ||||
| import Maproulette from "../Logic/Maproulette" | ||||
| import SvelteUIElement from "./Base/SvelteUIElement" | ||||
| import { BBoxFeatureSourceForLayer } from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource" | ||||
| import QuestionViz from "./Popup/QuestionViz" | ||||
| import { Feature, Point } from "geojson" | ||||
| import { GeoOperations } from "../Logic/GeoOperations" | ||||
| import CreateNewNote from "./Popup/CreateNewNote.svelte" | ||||
| import AddNewPoint from "./Popup/AddNewPoint/AddNewPoint.svelte" | ||||
| import UserProfile from "./BigComponents/UserProfile.svelte" | ||||
| import LanguagePicker from "./LanguagePicker" | ||||
| import Link from "./Base/Link" | ||||
| import LayerConfig from "../Models/ThemeConfig/LayerConfig" | ||||
| import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig" | ||||
| import { OsmTags, WayId } from "../Models/OsmFeature" | ||||
| import MoveWizard from "./Popup/MoveWizard" | ||||
| import SplitRoadWizard from "./Popup/SplitRoadWizard" | ||||
| import { ExportAsGpxViz } from "./Popup/ExportAsGpxViz" | ||||
| import WikipediaPanel from "./Wikipedia/WikipediaPanel.svelte" | ||||
| import TagRenderingEditable from "./Popup/TagRendering/TagRenderingEditable.svelte" | ||||
| import { PointImportButtonViz } from "./Popup/ImportButtons/PointImportButtonViz" | ||||
| import WayImportButtonViz from "./Popup/ImportButtons/WayImportButtonViz" | ||||
| import ConflateImportButtonViz from "./Popup/ImportButtons/ConflateImportButtonViz" | ||||
| import DeleteWizard from "./Popup/DeleteFlow/DeleteWizard.svelte" | ||||
| import { OpenJosm } from "./BigComponents/OpenJosm" | ||||
| import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte" | ||||
| import FediverseValidator from "./InputElement/Validators/FediverseValidator" | ||||
| import SendEmail from "./Popup/SendEmail.svelte" | ||||
| import NearbyImages from "./Popup/NearbyImages.svelte" | ||||
| import NearbyImagesCollapsed from "./Popup/NearbyImagesCollapsed.svelte" | ||||
| import UploadImage from "./Image/UploadImage.svelte" | ||||
| import { Imgur } from "../Logic/ImageProviders/Imgur" | ||||
| import Constants from "../Models/Constants" | ||||
| import { MangroveReviews } from "mangrove-reviews-typescript" | ||||
| import Wikipedia from "../Logic/Web/Wikipedia" | ||||
| import NearbyImagesSearch from "../Logic/Web/NearbyImagesSearch" | ||||
| 
 | ||||
| class NearbyImageVis implements SpecialVisualization { | ||||
|     // Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
 | ||||
|  | @ -79,7 +87,7 @@ class NearbyImageVis implements SpecialVisualization { | |||
|     docs = | ||||
|         "A component showing nearby images loaded from various online services such as Mapillary. In edit mode and when used on a feature, the user can select an image to add to the feature" | ||||
|     funcName = "nearby_images" | ||||
| 
 | ||||
|     needsUrls = NearbyImagesSearch.apiUrls | ||||
|     constr( | ||||
|         state: SpecialVisualizationState, | ||||
|         tags: UIEventSource<Record<string, string>>, | ||||
|  | @ -117,6 +125,7 @@ class StealViz implements SpecialVisualization { | |||
|             required: true, | ||||
|         }, | ||||
|     ] | ||||
|     needsUrls = [] | ||||
| 
 | ||||
|     constr(state: SpecialVisualizationState, featureTags, args) { | ||||
|         const [featureIdKey, layerAndtagRenderingIds] = args | ||||
|  | @ -264,7 +273,6 @@ export default class SpecialVisualizations { | |||
|                     SpecialVisualizations.specialVisualizations | ||||
|                         .map((sp) => sp.funcName + "()") | ||||
|                         .join(", ") | ||||
| 
 | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|  | @ -378,6 +386,7 @@ export default class SpecialVisualizations { | |||
|                 funcName: "add_new_point", | ||||
|                 docs: "An element which allows to add a new point on the 'last_click'-location. Only makes sense in the layer `last_click`", | ||||
|                 args: [], | ||||
|                 needsUrls: [], | ||||
|                 constr(state: SpecialVisualizationState, _, __, feature): BaseUIElement { | ||||
|                     let [lon, lat] = GeoOperations.centerpointCoordinates(feature) | ||||
|                     return new SvelteUIElement(AddNewPoint, { | ||||
|  | @ -389,6 +398,7 @@ export default class SpecialVisualizations { | |||
|             { | ||||
|                 funcName: "user_profile", | ||||
|                 args: [], | ||||
|                 needsUrls: [], | ||||
|                 docs: "A component showing information about the currently logged in user (username, profile description, profile picture + link to edit them). Mostly meant to be used in the 'user-settings'", | ||||
|                 constr(state: SpecialVisualizationState): BaseUIElement { | ||||
|                     return new SvelteUIElement(UserProfile, { | ||||
|  | @ -399,6 +409,7 @@ export default class SpecialVisualizations { | |||
|             { | ||||
|                 funcName: "language_picker", | ||||
|                 args: [], | ||||
|                 needsUrls: [], | ||||
|                 docs: "A component to set the language of the user interface", | ||||
|                 constr(state: SpecialVisualizationState): BaseUIElement { | ||||
|                     return new LanguagePicker( | ||||
|  | @ -410,6 +421,7 @@ export default class SpecialVisualizations { | |||
|             { | ||||
|                 funcName: "logout", | ||||
|                 args: [], | ||||
|                 needsUrls: [Constants.osmAuthConfig.url], | ||||
|                 docs: "Shows a button where the user can log out", | ||||
|                 constr(state: SpecialVisualizationState): BaseUIElement { | ||||
|                     return new SubtleButton(Svg.logout_svg(), Translations.t.general.logout, { | ||||
|  | @ -426,6 +438,7 @@ export default class SpecialVisualizations { | |||
|                 funcName: "split_button", | ||||
|                 docs: "Adds a button which allows to split a way", | ||||
|                 args: [], | ||||
|                 needsUrls: [], | ||||
|                 constr( | ||||
|                     state: SpecialVisualizationState, | ||||
|                     tagSource: UIEventSource<Record<string, string>> | ||||
|  | @ -446,6 +459,7 @@ export default class SpecialVisualizations { | |||
|                 funcName: "move_button", | ||||
|                 docs: "Adds a button which allows to move the object to another location. The config will be read from the layer config", | ||||
|                 args: [], | ||||
|                 needsUrls: [], | ||||
|                 constr( | ||||
|                     state: SpecialVisualizationState, | ||||
|                     tagSource: UIEventSource<Record<string, string>>, | ||||
|  | @ -469,6 +483,7 @@ export default class SpecialVisualizations { | |||
|                 funcName: "delete_button", | ||||
|                 docs: "Adds a button which allows to delete the object at this location. The config will be read from the layer config", | ||||
|                 args: [], | ||||
|                 needsUrls: [], | ||||
|                 constr( | ||||
|                     state: SpecialVisualizationState, | ||||
|                     tagSource: UIEventSource<Record<string, string>>, | ||||
|  | @ -493,6 +508,7 @@ export default class SpecialVisualizations { | |||
|             { | ||||
|                 funcName: "open_note", | ||||
|                 args: [], | ||||
|                 needsUrls: [Constants.osmAuthConfig.url], | ||||
|                 docs: "Creates a new map note on the given location. This options is placed in the 'last_click'-popup automatically if the 'notes'-layer is enabled", | ||||
|                 constr( | ||||
|                     state: SpecialVisualizationState, | ||||
|  | @ -525,6 +541,7 @@ export default class SpecialVisualizations { | |||
|                         defaultValue: "wikidata;wikipedia", | ||||
|                     }, | ||||
|                 ], | ||||
|                 needsUrls: [...Wikidata.neededUrls, ...Wikipedia.neededUrls], | ||||
|                 example: | ||||
|                     "`{wikipedia()}` is a basic example, `{wikipedia(name:etymology:wikidata)}` to show the wikipedia page of whom the feature was named after. Also remember that these can be styled, e.g. `{wikipedia():max-height: 10rem}` to limit the height", | ||||
|                 constr: (_, tagsSource, args) => { | ||||
|  | @ -548,6 +565,7 @@ export default class SpecialVisualizations { | |||
|                         defaultValue: "wikidata", | ||||
|                     }, | ||||
|                 ], | ||||
|                 needsUrls: Wikidata.neededUrls, | ||||
|                 example: | ||||
|                     "`{wikidata_label()}` is a basic example, `{wikipedia(name:etymology:wikidata)}` to show the label itself", | ||||
|                 constr: (_, tagsSource, args) => | ||||
|  | @ -577,6 +595,7 @@ export default class SpecialVisualizations { | |||
|                 funcName: "all_tags", | ||||
|                 docs: "Prints all key-value pairs of the object - used for debugging", | ||||
|                 args: [], | ||||
|                 needsUrls: [], | ||||
|                 constr: (state, tags: UIEventSource<any>) => | ||||
|                     new SvelteUIElement(AllTagsPanel, { tags, state }), | ||||
|             }, | ||||
|  | @ -590,6 +609,7 @@ export default class SpecialVisualizations { | |||
|                         doc: "The keys given to the images, e.g. if <span class='literal-code'>image</span> is given, the first picture URL will be added as <span class='literal-code'>image</span>, the second as <span class='literal-code'>image:0</span>, the third as <span class='literal-code'>image:1</span>, etc... Multiple values are allowed if ';'-separated ", | ||||
|                     }, | ||||
|                 ], | ||||
|                 needsUrls: AllImageProviders.apiUrls, | ||||
|                 constr: (state, tags, args) => { | ||||
|                     let imagePrefixes: string[] = undefined | ||||
|                     if (args.length > 0) { | ||||
|  | @ -605,27 +625,32 @@ export default class SpecialVisualizations { | |||
|             { | ||||
|                 funcName: "image_upload", | ||||
|                 docs: "Creates a button where a user can upload an image to IMGUR", | ||||
|                 needsUrls: [Imgur.apiUrl], | ||||
|                 args: [ | ||||
|                     { | ||||
|                         name: "image-key", | ||||
|                         doc: "Image tag to add the URL to (or image-tag:0, image-tag:1 when multiple images are added)", | ||||
|                         required: false | ||||
|                         required: false, | ||||
|                     }, | ||||
|                     { | ||||
|                         name: "label", | ||||
|                         doc: "The text to show on the button", | ||||
|                         required: false | ||||
|                         required: false, | ||||
|                     }, | ||||
|                 ], | ||||
|                 constr: (state, tags, args) => { | ||||
|                     return new SvelteUIElement(UploadImage, { | ||||
|                         state,tags, labelText: args[1], image: args[0] | ||||
|                         state, | ||||
|                         tags, | ||||
|                         labelText: args[1], | ||||
|                         image: args[0], | ||||
|                     }) | ||||
|                 }, | ||||
|             }, | ||||
|             { | ||||
|                 funcName: "reviews", | ||||
|                 docs: "Adds an overview of the mangrove-reviews of this object. Mangrove.Reviews needs - in order to identify the reviewed object - a coordinate and a name. By default, the name of the object is given, but this can be overwritten", | ||||
|                 needsUrls: [MangroveReviews.ORIGINAL_API], | ||||
|                 example: | ||||
|                     "`{reviews()}` for a vanilla review, `{reviews(name, play_forest)}` to review a play forest. If a name is known, the name will be used as identifier, otherwise 'play_forest' is used", | ||||
|                 args: [ | ||||
|  | @ -676,6 +701,7 @@ export default class SpecialVisualizations { | |||
|                         doc: "Remove this string from the end of the value before parsing. __Note: use `&RPARENs` to indicate `)` if needed__", | ||||
|                     }, | ||||
|                 ], | ||||
|                 needsUrls: [], | ||||
|                 example: | ||||
|                     "A normal opening hours table can be invoked with `{opening_hours_table()}`. A table for e.g. conditional access with opening hours can be `{opening_hours_table(access:conditional, no @ &LPARENS, &RPARENS)}`", | ||||
|                 constr: (state, tagSource: UIEventSource<any>, args) => { | ||||
|  | @ -688,38 +714,9 @@ export default class SpecialVisualizations { | |||
|                     ) | ||||
|                 }, | ||||
|             }, | ||||
|             { | ||||
|                 funcName: "live", | ||||
|                 docs: "Downloads a JSON from the given URL, e.g. '{live(example.org/data.json, shorthand:x.y.z, other:a.b.c, shorthand)}' will download the given file, will create an object {shorthand: json[x][y][z], other: json[a][b][c] out of it and will return 'other' or 'json[a][b][c]. This is made to use in combination with tags, e.g. {live({url}, {url:format}, needed_value)}", | ||||
|                 example: | ||||
|                     "{live({url},{url:format},hour)} {live(https://data.mobility.brussels/bike/api/counts/?request=live&featureID=CB2105,hour:data.hour_cnt;day:data.day_cnt;year:data.year_cnt,hour)}", | ||||
|                 args: [ | ||||
|                     { | ||||
|                         name: "Url", | ||||
|                         doc: "The URL to load", | ||||
|                         required: true, | ||||
|                     }, | ||||
|                     { | ||||
|                         name: "Shorthands", | ||||
|                         doc: "A list of shorthands, of the format 'shorthandname:path.path.path'. separated by ;", | ||||
|                     }, | ||||
|                     { | ||||
|                         name: "path", | ||||
|                         doc: "The path (or shorthand) that should be returned", | ||||
|                     }, | ||||
|                 ], | ||||
|                 constr: (_, tagSource: UIEventSource<any>, args) => { | ||||
|                     const url = args[0] | ||||
|                     const shorthands = args[1] | ||||
|                     const neededValue = args[2] | ||||
|                     const source = LiveQueryHandler.FetchLiveData(url, shorthands.split(";")) | ||||
|                     return new VariableUiElement( | ||||
|                         source.map((data) => data[neededValue] ?? "Loading...") | ||||
|                     ) | ||||
|                 }, | ||||
|             }, | ||||
|             { | ||||
|                 funcName: "canonical", | ||||
|                 needsUrls: [], | ||||
|                 docs: "Converts a short, canonical value into the long, translated text including the unit. This only works if a `unit` is defined for the corresponding value. The unit specification will be included in the text. ", | ||||
|                 example: | ||||
|                     "If the object has `length=42`, then `{canonical(length)}` will be shown as **42 meter** (in english), **42 metre** (in french), ...", | ||||
|  | @ -757,6 +754,7 @@ export default class SpecialVisualizations { | |||
|                 funcName: "export_as_geojson", | ||||
|                 docs: "Exports the selected feature as GeoJson-file", | ||||
|                 args: [], | ||||
|                 needsUrls: [], | ||||
|                 constr: (state, tagSource, tagsSource, feature, layer) => { | ||||
|                     const t = Translations.t.general.download | ||||
| 
 | ||||
|  | @ -786,6 +784,7 @@ export default class SpecialVisualizations { | |||
|                 funcName: "open_in_iD", | ||||
|                 docs: "Opens the current view in the iD-editor", | ||||
|                 args: [], | ||||
|                 needsUrls: [], | ||||
|                 constr: (state, feature) => { | ||||
|                     return new SvelteUIElement(OpenIdEditor, { | ||||
|                         mapProperties: state.mapProperties, | ||||
|  | @ -797,6 +796,8 @@ export default class SpecialVisualizations { | |||
|                 funcName: "open_in_josm", | ||||
|                 docs: "Opens the current view in the JOSM-editor", | ||||
|                 args: [], | ||||
|                 needsUrls: OpenJosm.needsUrls, | ||||
| 
 | ||||
|                 constr: (state) => { | ||||
|                     return new OpenJosm(state.osmConnection, state.mapProperties.bounds) | ||||
|                 }, | ||||
|  | @ -805,6 +806,7 @@ export default class SpecialVisualizations { | |||
|                 funcName: "clear_location_history", | ||||
|                 docs: "A button to remove the travelled track information from the device", | ||||
|                 args: [], | ||||
|                 needsUrls: [], | ||||
|                 constr: (state) => { | ||||
|                     return new SubtleButton( | ||||
|                         Svg.delete_icon_svg().SetStyle("height: 1.5rem"), | ||||
|  | @ -830,6 +832,7 @@ export default class SpecialVisualizations { | |||
|                         defaultValue: "0", | ||||
|                     }, | ||||
|                 ], | ||||
|                 needsUrls: [Constants.osmAuthConfig.url], | ||||
|                 constr: (state, tags, args) => | ||||
|                     new VariableUiElement( | ||||
|                         tags | ||||
|  | @ -858,16 +861,18 @@ export default class SpecialVisualizations { | |||
|                         defaultValue: "id", | ||||
|                     }, | ||||
|                 ], | ||||
|                 needsUrls: [Imgur.apiUrl], | ||||
|                 constr: (state, tags, args) => { | ||||
|                     const id = tags.data[args[0] ?? "id"] | ||||
|                     tags = state.featureProperties.getStore(id) | ||||
|                     console.log("Id is", id) | ||||
|                     return new SvelteUIElement(UploadImage, {state, tags}) | ||||
|                     } | ||||
|                     return new SvelteUIElement(UploadImage, { state, tags }) | ||||
|                 }, | ||||
|             }, | ||||
|             { | ||||
|                 funcName: "title", | ||||
|                 args: [], | ||||
|                 needsUrls: [], | ||||
|                 docs: "Shows the title of the popup. Useful for some cases, e.g. 'What is phone number of {title()}?'", | ||||
|                 example: | ||||
|                     "`What is the phone number of {title()}`, which might automatically become `What is the phone number of XYZ`.", | ||||
|  | @ -888,6 +893,7 @@ export default class SpecialVisualizations { | |||
|             { | ||||
|                 funcName: "maproulette_task", | ||||
|                 args: [], | ||||
|                 needsUrls: [Maproulette.defaultEndpoint], | ||||
|                 constr(state, tagSource) { | ||||
|                     let parentId = tagSource.data.mr_challengeId | ||||
|                     if (parentId === undefined) { | ||||
|  | @ -931,6 +937,7 @@ export default class SpecialVisualizations { | |||
|             { | ||||
|                 funcName: "maproulette_set_status", | ||||
|                 docs: "Change the status of the given MapRoulette task", | ||||
|                 needsUrls: [Maproulette.defaultEndpoint], | ||||
|                 example: | ||||
|                     " The following example sets the status to '2' (false positive)\n" + | ||||
|                     "\n" + | ||||
|  | @ -1054,6 +1061,7 @@ export default class SpecialVisualizations { | |||
|                 funcName: "statistics", | ||||
|                 docs: "Show general statistics about the elements currently in view. Intended to use on the `current_view`-layer", | ||||
|                 args: [], | ||||
|                 needsUrls: [], | ||||
|                 constr: (state) => { | ||||
|                     return new Combine( | ||||
|                         state.layout.layers | ||||
|  | @ -1096,6 +1104,8 @@ export default class SpecialVisualizations { | |||
|                         required: true, | ||||
|                     }, | ||||
|                 ], | ||||
|                 needsUrls: [], | ||||
| 
 | ||||
|                 constr(__, tags, args) { | ||||
|                     return new SvelteUIElement(SendEmail, { args, tags }) | ||||
|                 }, | ||||
|  | @ -1123,6 +1133,7 @@ export default class SpecialVisualizations { | |||
|                         doc: "If set, this link will act as a download-button. The contents of `href` will be offered for download; this parameter will act as the proposed filename", | ||||
|                     }, | ||||
|                 ], | ||||
|                 needsUrls: [], | ||||
|                 constr( | ||||
|                     state: SpecialVisualizationState, | ||||
|                     tagSource: UIEventSource<Record<string, string>>, | ||||
|  | @ -1144,6 +1155,7 @@ export default class SpecialVisualizations { | |||
|             { | ||||
|                 funcName: "multi", | ||||
|                 docs: "Given an embedded tagRendering (read only) and a key, will read the keyname as a JSON-list. Every element of this list will be considered as tags and rendered with the tagRendering", | ||||
|                 needsUrls: [], | ||||
|                 example: | ||||
|                     "```json\n" + | ||||
|                     JSON.stringify( | ||||
|  | @ -1204,6 +1216,7 @@ export default class SpecialVisualizations { | |||
|                         required: true, | ||||
|                     }, | ||||
|                 ], | ||||
|                 needsUrls: [], | ||||
|                 constr( | ||||
|                     state: SpecialVisualizationState, | ||||
|                     tagSource: UIEventSource<Record<string, string>>, | ||||
|  |  | |||
|  | @ -37,6 +37,7 @@ export class SubstitutedTranslation extends VariableUiElement { | |||
|                 constr: typeof value === "function" ? value : () => value, | ||||
|                 docs: "Dynamically injected input element", | ||||
|                 args: [], | ||||
|                 needsUrls: [], | ||||
|                 example: "", | ||||
|             }) | ||||
|         }) | ||||
|  |  | |||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							|  | @ -3,6 +3,7 @@ import SvelteUIElement from "./src/UI/Base/SvelteUIElement" | |||
| import ThemeViewGUI from "./src/UI/ThemeViewGUI.svelte" | ||||
| import LayoutConfig from "./src/Models/ThemeConfig/LayoutConfig"; | ||||
| import MetaTagging from "./src/Logic/MetaTagging"; | ||||
| import { FixedUiElement } from "./src/UI/Base/FixedUiElement"; | ||||
| 
 | ||||
| function webgl_support() { | ||||
|     try { | ||||
|  |  | |||
							
								
								
									
										55
									
								
								theme.html
									
										
									
									
									
								
							
							
						
						
									
										55
									
								
								theme.html
									
										
									
									
									
								
							|  | @ -4,7 +4,7 @@ | |||
| <head> | ||||
|     <meta charset="UTF-8"> | ||||
|     <meta content="width=device-width, initial-scale=1.0, user-scalable=no" name="viewport"> | ||||
|     <!-- CSP // disabled --> | ||||
|     <!-- CSP --> | ||||
|     <link href="./css/mobile.css" rel="stylesheet"/> | ||||
|     <link href="./css/openinghourstable.css" rel="stylesheet"/> | ||||
|     <link href="./css/tagrendering.css" rel="stylesheet"/> | ||||
|  | @ -65,57 +65,12 @@ | |||
|     </div> | ||||
| </div> | ||||
| <div id="belowmap" class="absolute top-0 left-0 -z-10">Below</div> | ||||
| 
 | ||||
| <script> | ||||
| 
 | ||||
|     let lang = ((navigator.languages && navigator.languages[0]) || navigator.language || navigator.userLanguage || 'en').substr(0, 2); | ||||
| 
 | ||||
|     function filterLangs(maindiv) { | ||||
|         let foundLangs = 0 | ||||
|         for (const child of Array.from(maindiv.children)) { | ||||
|             if (child.attributes.lang?.value === lang) { | ||||
|                 foundLangs++ | ||||
|             } | ||||
|         } | ||||
|         if (foundLangs === 0) { | ||||
|             lang = "en" | ||||
|         } | ||||
|         for (const child of Array.from(maindiv.children)) { | ||||
|             const childLang = child.attributes.lang | ||||
|             if (childLang === undefined) { | ||||
|                 continue | ||||
|             } | ||||
|             if (childLang.value === lang) { | ||||
|                 continue | ||||
|             } | ||||
|             child.parentElement.removeChild(child) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     filterLangs(document.getElementById("descriptions-while-loading")) | ||||
|     filterLangs(document.getElementById("default-title")) | ||||
| </script> | ||||
| <script async src="./src/UI/RemoveOtherLanguages.ts" type="module"></script> | ||||
| <script async src="./src/InstallServiceWorker.ts" type="module"></script> | ||||
| <script defer src="./src/index.ts" type="module"></script> | ||||
| <script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="https://gc.zgo.at/count.js" crossorigin="anonymous" integrity="sha384-gtO6vSydQeOAGGK19NHrlVLNtaDSJjN4aGMWschK+dwAZOdPQWbjXgL+FM5XsgFJ"></script> | ||||
| 
 | ||||
|      | ||||
| <script src="./src/index.ts" type="module"></script> | ||||
| <script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="//gc.zgo.at/count.js" crossorigin="anonymous" integrity="sha384-gtO6vSydQeOAGGK19NHrlVLNtaDSJjN4aGMWschK+dwAZOdPQWbjXgL+FM5XsgFJ"></script> | ||||
| 
 | ||||
| <script> | ||||
|     window.addEventListener('load', () => { | ||||
|         if ('serviceWorker' in navigator) { | ||||
|             // register service worker | ||||
|             navigator.serviceWorker.register('/service-worker.js').then( | ||||
|                 () => { | ||||
|                     console.log('Service worker registration successful'); | ||||
|                 }, | ||||
|                 err => { | ||||
|                     console.error('Service worker registration failed', err) | ||||
|                 }); | ||||
|         } else { | ||||
|             console.log("Service workers are not supported") | ||||
|         } | ||||
|     }); | ||||
| </script> | ||||
| 
 | ||||
| </body> | ||||
| </html> | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue