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", |       "id": "repeated", | ||||||
|       "labels": ["level"], |       "labels": [ | ||||||
|  |         "level" | ||||||
|  |       ], | ||||||
|       "condition": "repeat_on~*", |       "condition": "repeat_on~*", | ||||||
|       "render": { |       "render": { | ||||||
|         "en": "Multiple, identical objects can be found on floors {repeat_on}.", |         "en": "Multiple, identical objects can be found on floors {repeat_on}.", | ||||||
|  | @ -1667,7 +1669,9 @@ | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       "id": "single_level", |       "id": "single_level", | ||||||
|       "labels": ["level"], |       "labels": [ | ||||||
|  |         "level" | ||||||
|  |       ], | ||||||
|       "condition": "repeat_on=", |       "condition": "repeat_on=", | ||||||
|       "question": { |       "question": { | ||||||
|         "nl": "Op welke verdieping bevindt dit punt zich?", |         "nl": "Op welke verdieping bevindt dit punt zich?", | ||||||
|  |  | ||||||
							
								
								
									
										15
									
								
								package.json
									
										
									
									
									
								
							
							
						
						
									
										15
									
								
								package.json
									
										
									
									
									
								
							|  | @ -1,6 +1,6 @@ | ||||||
| { | { | ||||||
|   "name": "mapcomplete", |   "name": "mapcomplete", | ||||||
|   "version": "0.33.4", |   "version": "0.33.5", | ||||||
|   "repository": "https://github.com/pietervdvn/MapComplete", |   "repository": "https://github.com/pietervdvn/MapComplete", | ||||||
|   "description": "A small website to edit OSM easily", |   "description": "A small website to edit OSM easily", | ||||||
|   "bugs": "https://github.com/pietervdvn/MapComplete/issues", |   "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`" |       "Alternatively, you can override the `osm` credentials using the environment variables `VITE_OSM_OAUTH_CLIENT_ID` and `VITE_OSM_OAUTH_SECRET`" | ||||||
|     ], |     ], | ||||||
|     "oauth_credentials": { |     "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", |         "#": "This client-id is registered by 'MapComplete' on osm.org", | ||||||
|         "oauth_client_id": "K93H1d8ve7p-tVLE1ZwsQ4lAFLQk8INx5vfTLMu5DWk", |         "oauth_client_id": "K93H1d8ve7p-tVLE1ZwsQ4lAFLQk8INx5vfTLMu5DWk", | ||||||
|         "oauth_secret": "NBWGhWDrD3QDB35xtVuxv4aExnmIt4FA_WgeLtwxasg", |         "oauth_secret": "NBWGhWDrD3QDB35xtVuxv4aExnmIt4FA_WgeLtwxasg", | ||||||
|         "url": "https://www.openstreetmap.org" |         "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": { |     "api_keys": { | ||||||
|       "#": "Various API-keys for various services. Feel free to reuse those in another MapComplete-hosted version", |       "#": "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" | export NODE_OPTIONS="--max-old-space-size=8192" | ||||||
| 
 | 
 | ||||||
| # This script ends every line with '&&' to chain everything. A failure will thus stop the build | # 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:editor-layer-index && | ||||||
| # npm run generate && | npm run generate && | ||||||
| npm run generate:layouts | npm run generate:layouts | ||||||
| 
 | 
 | ||||||
| if [ $? -ne 0 ]; then | if [ $? -ne 0 ]; then | ||||||
|  | @ -38,7 +38,8 @@ then | ||||||
|     export ASSET_URL |     export ASSET_URL | ||||||
|     echo "$ASSET_URL" |     echo "$ASSET_URL" | ||||||
| else | else | ||||||
|   ASSET_URL="$BRANCH" |   # ASSET_URL="$BRANCH" | ||||||
|  |   ASSET_URL="./" | ||||||
|   export ASSET_URL |   export ASSET_URL | ||||||
|   echo "$ASSET_URL" |   echo "$ASSET_URL" | ||||||
| fi | fi | ||||||
|  |  | ||||||
|  | @ -8,6 +8,10 @@ import LayoutConfig from "../src/Models/ThemeConfig/LayoutConfig" | ||||||
| import xml2js from "xml2js" | import xml2js from "xml2js" | ||||||
| import ScriptUtils from "./ScriptUtils" | import ScriptUtils from "./ScriptUtils" | ||||||
| import { Utils } from "../src/Utils" | 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 sharp = require("sharp") | ||||||
| const template = readFileSync("theme.html", "utf8") | const template = readFileSync("theme.html", "utf8") | ||||||
|  | @ -195,29 +199,72 @@ function asLangSpan(t: Translation, tag = "span"): string { | ||||||
|         if (lang === "_context") { |         if (lang === "_context") { | ||||||
|             continue |             continue | ||||||
|         } |         } | ||||||
|         values.push(`<${tag} lang='${lang}'>${t.translations[lang]}</${tag}>`) |         values.push(`<${tag} lang="${lang}">${t.translations[lang]}</${tag}>`) | ||||||
|     } |     } | ||||||
|     return values.join("\n") |     return values.join("\n") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| let cspCached: string = undefined | let previousSrc: Set<string> = new Set<string>() | ||||||
| function generateCsp(): string { | function generateCsp(layout: LayoutConfig): string { | ||||||
|     if (cspCached !== undefined) { |     const apiUrls: string[] = [ | ||||||
|         return cspCached |         "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 = { |     const csp = { | ||||||
|         "default-src": "'self'", |         "default-src": "'self'", | ||||||
|         "script-src": "'self'", |         "script-src": "'self' https://gc.zgo.at/count.js", | ||||||
|         "img-src": "*", |         "img-src": "* data:", // maplibre depends on 'data:' to load
 | ||||||
|         "connect-src": "*", |         "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) |     const content = Object.keys(csp) | ||||||
|         .map((k) => k + ": " + csp[k]) |         .map((k) => k + " " + csp[k]) | ||||||
|         .join("; ") |         .join("; ") | ||||||
| 
 | 
 | ||||||
|     cspCached = `<meta http-equiv="Content-Security-Policy" content="${content}">` |     return [ | ||||||
|     return cspCached |         `<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) { | async function createLandingPage(layout: LayoutConfig, manifest, whiteIcons, alreadyWritten) { | ||||||
|  | @ -290,7 +337,7 @@ async function createLandingPage(layout: LayoutConfig, manifest, whiteIcons, alr | ||||||
|         ...apple_icons, |         ...apple_icons, | ||||||
|     ].join("\n") |     ].join("\n") | ||||||
| 
 | 
 | ||||||
|     const loadingText = Translations.t.general.loadingTheme.Subs({ theme: ogTitle }) |     const loadingText = Translations.t.general.loadingTheme.Subs({ theme: layout.title }) | ||||||
| 
 | 
 | ||||||
|     let output = template |     let output = template | ||||||
|         .replace("Loading MapComplete, hang on...", asLangSpan(loadingText, "h1")) |         .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) |             Translations.t.general.poweredByOsm.textFor(targetLanguage) | ||||||
|         ) |         ) | ||||||
|         .replace(/<!-- THEME-SPECIFIC -->.*<!-- THEME-SPECIFIC-END-->/s, themeSpecific) |         .replace(/<!-- THEME-SPECIFIC -->.*<!-- THEME-SPECIFIC-END-->/s, themeSpecific) | ||||||
|         .replace(/<!-- CSP -->/, generateCsp()) |         .replace(/<!-- CSP -->/, generateCsp(layout)) | ||||||
|         .replace( |         .replace( | ||||||
|             /<!-- DESCRIPTION START -->.*<!-- DESCRIPTION END -->/s, |             /<!-- DESCRIPTION START -->.*<!-- DESCRIPTION END -->/s, | ||||||
|             asLangSpan(layout.shortDescription) |             asLangSpan(layout.shortDescription) | ||||||
|  | @ -311,7 +358,7 @@ async function createLandingPage(layout: LayoutConfig, manifest, whiteIcons, alr | ||||||
| 
 | 
 | ||||||
|         .replace( |         .replace( | ||||||
|             '<script src="./src/index.ts" type="module"></script>', |             '<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 |     return output | ||||||
|  |  | ||||||
|  | @ -4,7 +4,6 @@ hosted.mapcomplete.org { | ||||||
| 	header { | 	header { | ||||||
| 		+Permissions-Policy "interest-cohort=()" | 		+Permissions-Policy "interest-cohort=()" | ||||||
|         +Report-To `\{"group":"csp-endpoint", "max_age": 86400,"endpoints": [\{"url": "https://report.mapcomplete.org/csp"}], "include_subdomains": true}` |         +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 | # unzip tiles.zip | ||||||
| 
 | 
 | ||||||
| MAPCOMPLETE_CONFIGURATION="config_hetzner" | 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 reset:layeroverview | ||||||
| npm run test | npm run test | ||||||
| cp config.json config.json.bu && |  | ||||||
| cp ./scripts/hetzner/config.json . && |  | ||||||
| npm run prepare-deploy && | npm run prepare-deploy && | ||||||
| mv config.json.bu config.json && | mv config.json.bu config.json && | ||||||
| zip dist.zip -r dist/* && | zip dist.zip -r dist/* && | ||||||
| scp -r dist.zip hetzner:/root/ && | scp -r dist.zip hetzner:/root/ && | ||||||
| scp ./scripts/hetzner/config/* hetzner:/root/ | echo "Upload completed, deploying config and booting" && | ||||||
| ssh hetzner -t "unzip dist.zip && rm dist.zip && rm -rf public/ && mv dist public && caddy stop && caddy start" | 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 | 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 = { |     private static providersByName = { | ||||||
|         imgur: Imgur.singleton, |         imgur: Imgur.singleton, | ||||||
|         mapillary: Mapillary.singleton, |         mapillary: Mapillary.singleton, | ||||||
|         wikidata: WikidataImageProvider.singleton, |         wikidata: WikidataImageProvider.singleton, | ||||||
|         wikimedia: WikimediaImageProvider.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< |     private static _cache: Map<string, UIEventSource<ProvidedImage[]>> = new Map< | ||||||
|         string, |         string, | ||||||
|         UIEventSource<ProvidedImage[]> |         UIEventSource<ProvidedImage[]> | ||||||
|     >() |     >() | ||||||
| 
 | 
 | ||||||
|  |     public static byName(name: string) { | ||||||
|  |         return AllImageProviders.providersByName[name.toLowerCase()] | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public static LoadImagesFor( |     public static LoadImagesFor( | ||||||
|         tags: Store<Record<string, string>>, |         tags: Store<Record<string, string>>, | ||||||
|         tagKey?: string[] |         tagKey?: string[] | ||||||
|  |  | ||||||
|  | @ -3,6 +3,10 @@ import ImageProvider, { ProvidedImage } from "./ImageProvider" | ||||||
| export default class GenericImageProvider extends ImageProvider { | export default class GenericImageProvider extends ImageProvider { | ||||||
|     public defaultKeyPrefixes: string[] = ["image"] |     public defaultKeyPrefixes: string[] = ["image"] | ||||||
| 
 | 
 | ||||||
|  |     public apiUrls(): string[] { | ||||||
|  |         return [] | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     private readonly _valuePrefixBlacklist: string[] |     private readonly _valuePrefixBlacklist: string[] | ||||||
| 
 | 
 | ||||||
|     public constructor(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 ExtractUrls(key: string, value: string): Promise<Promise<ProvidedImage>[]> | ||||||
| 
 | 
 | ||||||
|     public abstract DownloadAttribution(url: string): Promise<LicenseInfo> |     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 Constants from "../../Models/Constants"; | ||||||
| import { LicenseInfo } from "./LicenseInfo"; | import { LicenseInfo } from "./LicenseInfo"; | ||||||
| import { ImageUploader } from "./ImageUploader"; | 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 defaultValuePrefix = ["https://i.imgur.com"] | ||||||
|     public static readonly singleton = new Imgur() |     public static readonly singleton = new Imgur() | ||||||
|     public readonly defaultKeyPrefixes: string[] = ["image"] |     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() { |     private constructor() { | ||||||
|         super() |         super() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     apiUrls(): string[] { | ||||||
|  |         return [Imgur.apiUrl] | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Uploads an image, returns the URL where to find the image |      * Uploads an image, returns the URL where to find the image | ||||||
|      * @param title |      * @param title | ||||||
|  | @ -24,8 +31,8 @@ export class Imgur extends ImageProvider implements ImageUploader{ | ||||||
|         title: string, |         title: string, | ||||||
|         description: string, |         description: string, | ||||||
|         blob: File |         blob: File | ||||||
|     ): Promise<{ key: string, value: string }> { |     ): Promise<{ key: string; value: string }> { | ||||||
|         const apiUrl = "https://api.imgur.com/3/image" |         const apiUrl = Imgur.apiUrl | ||||||
|         const apiKey = Constants.ImgurApiKey |         const apiKey = Constants.ImgurApiKey | ||||||
| 
 | 
 | ||||||
|         const formData = new FormData() |         const formData = new FormData() | ||||||
|  | @ -33,7 +40,6 @@ export class Imgur extends ImageProvider implements ImageUploader{ | ||||||
|         formData.append("title", title) |         formData.append("title", title) | ||||||
|         formData.append("description", description) |         formData.append("description", description) | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|         const settings: RequestInit = { |         const settings: RequestInit = { | ||||||
|             method: "POST", |             method: "POST", | ||||||
|             body: formData, |             body: formData, | ||||||
|  |  | ||||||
|  | @ -17,6 +17,10 @@ export class Mapillary extends ImageProvider { | ||||||
|     ] |     ] | ||||||
|     defaultKeyPrefixes = ["mapillary", "image"] |     defaultKeyPrefixes = ["mapillary", "image"] | ||||||
| 
 | 
 | ||||||
|  |     apiUrls(): string[] { | ||||||
|  |         return ["https://mapillary.com", "https://www.mapillary.com", "https://graph.mapillary.com"] | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Indicates that this is the same URL |      * Indicates that this is the same URL | ||||||
|      * Ignores 'stp' parameter |      * Ignores 'stp' parameter | ||||||
|  |  | ||||||
|  | @ -5,6 +5,9 @@ import { WikimediaImageProvider } from "./WikimediaImageProvider" | ||||||
| import Wikidata from "../Web/Wikidata" | import Wikidata from "../Web/Wikidata" | ||||||
| 
 | 
 | ||||||
| export class WikidataImageProvider extends ImageProvider { | export class WikidataImageProvider extends ImageProvider { | ||||||
|  |     public apiUrls(): string[] { | ||||||
|  |         return Wikidata.neededUrls | ||||||
|  |     } | ||||||
|     public static readonly singleton = new WikidataImageProvider() |     public static readonly singleton = new WikidataImageProvider() | ||||||
|     public readonly defaultKeyPrefixes = ["wikidata"] |     public readonly defaultKeyPrefixes = ["wikidata"] | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -11,11 +11,11 @@ import Wikimedia from "../Web/Wikimedia" | ||||||
|  */ |  */ | ||||||
| export class WikimediaImageProvider extends ImageProvider { | export class WikimediaImageProvider extends ImageProvider { | ||||||
|     public static readonly singleton = new WikimediaImageProvider() |     public static readonly singleton = new WikimediaImageProvider() | ||||||
|     public static readonly commonsPrefixes = [ |     public static readonly apiUrls = [ | ||||||
|         "https://commons.wikimedia.org/wiki/", |         "https://commons.wikimedia.org/wiki/", | ||||||
|         "https://upload.wikimedia.org", |         "https://upload.wikimedia.org", | ||||||
|         "File:", |  | ||||||
|     ] |     ] | ||||||
|  |     public static readonly commonsPrefixes = [...WikimediaImageProvider.apiUrls, "File:"] | ||||||
|     private readonly commons_key = "wikimedia_commons" |     private readonly commons_key = "wikimedia_commons" | ||||||
|     public readonly defaultKeyPrefixes = [this.commons_key, "image"] |     public readonly defaultKeyPrefixes = [this.commons_key, "image"] | ||||||
| 
 | 
 | ||||||
|  | @ -66,6 +66,10 @@ export class WikimediaImageProvider extends ImageProvider { | ||||||
|         return value |         return value | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     apiUrls(): string[] { | ||||||
|  |         return WikimediaImageProvider.apiUrls | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     SourceIcon(backlink: string): BaseUIElement { |     SourceIcon(backlink: string): BaseUIElement { | ||||||
|         const img = Svg.wikimedia_commons_white_svg().SetStyle("width:2em;height: 2em") |         const img = Svg.wikimedia_commons_white_svg().SetStyle("width:2em;height: 2em") | ||||||
|         if (backlink === undefined) { |         if (backlink === undefined) { | ||||||
|  |  | ||||||
|  | @ -32,11 +32,12 @@ export default class Maproulette { | ||||||
|     private readonly apiKey: string |     private readonly apiKey: string | ||||||
| 
 | 
 | ||||||
|     public static singleton = new Maproulette() |     public static singleton = new Maproulette() | ||||||
|  |     public static readonly defaultEndpoint = "https://maproulette.org/api/v2" | ||||||
|     /** |     /** | ||||||
|      * Creates a new Maproulette instance |      * Creates a new Maproulette instance | ||||||
|      * @param endpoint The API endpoint to use |      * @param endpoint The API endpoint to use | ||||||
|      */ |      */ | ||||||
|     constructor(endpoint: string = "https://maproulette.org/api/v2") { |     constructor(endpoint: string = Maproulette.defaultEndpoint) { | ||||||
|         this.endpoint = endpoint |         this.endpoint = endpoint | ||||||
|         this.apiKey = Constants.MaprouletteApiKey |         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 { | export class OsmConnectionFeatureSwitches { | ||||||
|     public readonly featureSwitchFakeUser: UIEventSource<boolean> |     public readonly featureSwitchFakeUser: UIEventSource<boolean> | ||||||
|     public readonly featureSwitchApiURL: UIEventSource<string> |  | ||||||
| 
 | 
 | ||||||
|     constructor() { |     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( |         this.featureSwitchFakeUser = QueryParameters.GetBooleanQueryParameter( | ||||||
|             "fake-user", |             "fake-user", | ||||||
|  | @ -143,7 +137,6 @@ export default class FeatureSwitchState extends OsmConnectionFeatureSwitches { | ||||||
| 
 | 
 | ||||||
|         let testingDefaultValue = false |         let testingDefaultValue = false | ||||||
|         if ( |         if ( | ||||||
|             this.featureSwitchApiURL.data !== "osm-test" && |  | ||||||
|             !Utils.runningFromConsole && |             !Utils.runningFromConsole && | ||||||
|             (location.hostname === "localhost" || location.hostname === "127.0.0.1") |             (location.hostname === "localhost" || location.hostname === "127.0.0.1") | ||||||
|         ) { |         ) { | ||||||
|  |  | ||||||
|  | @ -1,9 +1,9 @@ | ||||||
| import { IndexedFeatureSource } from "../FeatureSource/FeatureSource" | import { IndexedFeatureSource } from "../FeatureSource/FeatureSource" | ||||||
| import { GeoOperations } from "../GeoOperations" | import { GeoOperations } from "../GeoOperations" | ||||||
| import { ImmutableStore, Store, Stores, UIEventSource } from "../UIEventSource" | import { ImmutableStore, Store, Stores, UIEventSource } from "../UIEventSource" | ||||||
| import { Mapillary } from "../ImageProviders/Mapillary" |  | ||||||
| import P4C from "pic4carto" | import P4C from "pic4carto" | ||||||
| import { Utils } from "../../Utils" | import { Utils } from "../../Utils" | ||||||
|  | 
 | ||||||
| export interface NearbyImageOptions { | export interface NearbyImageOptions { | ||||||
|     lon: number |     lon: number | ||||||
|     lat: 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 { | export default class NearbyImagesSearch { | ||||||
|     private static readonly services = [ |     public static readonly services = ["mapillary", "flickr", "kartaview", "wikicommons"] as const | ||||||
|         "mapillary", |     public static readonly apiUrls = ["https://api.flickr.com"] | ||||||
|         "flickr", |     private readonly individualStores: Store<{ images: P4CPicture[]; beforeFilter: number }>[] | ||||||
|         "openstreetcam", |  | ||||||
|         "wikicommons", |  | ||||||
|     ] as const |  | ||||||
| 
 |  | ||||||
|     private individualStores |  | ||||||
|     private readonly _store: UIEventSource<P4CPicture[]> = new UIEventSource<P4CPicture[]>([]) |     private readonly _store: UIEventSource<P4CPicture[]> = new UIEventSource<P4CPicture[]>([]) | ||||||
|     public readonly store: Store<P4CPicture[]> = this._store |     public readonly store: Store<P4CPicture[]> = this._store | ||||||
|     private readonly _options: NearbyImageOptions |     private readonly _options: NearbyImageOptions | ||||||
|  | @ -71,16 +66,16 @@ export default class NearbyImagesSearch { | ||||||
|         this.update() |         this.update() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private static buildPictureFetcher( |     private static async fetchImages( | ||||||
|         options: NearbyImageOptions, |         options: NearbyImageOptions, | ||||||
|         fetcher: "mapillary" | "flickr" | "openstreetcam" | "wikicommons" |         fetcher: P4CService | ||||||
|     ): Store<{ images: P4CPicture[]; beforeFilter: number }> { |     ): Promise<P4CPicture[]> { | ||||||
|         const picManager = new P4C.PicturesManager({ usefetchers: [fetcher] }) |         const picManager = new P4C.PicturesManager({ usefetchers: [fetcher] }) | ||||||
|         const searchRadius = options.searchRadius ?? 100 |  | ||||||
|         const maxAgeSeconds = (options.maxDaysOld ?? 3 * 365) * 24 * 60 * 60 * 1000 |         const maxAgeSeconds = (options.maxDaysOld ?? 3 * 365) * 24 * 60 * 60 * 1000 | ||||||
|  |         const searchRadius = options.searchRadius ?? 100 | ||||||
| 
 | 
 | ||||||
|         const p4cStore = Stores.FromPromise<P4CPicture[]>( |         try { | ||||||
|             picManager.startPicsRetrievalAround( |             const pics: P4CPicture[] = await picManager.startPicsRetrievalAround( | ||||||
|                 new P4C.LatLng(options.lat, options.lon), |                 new P4C.LatLng(options.lat, options.lon), | ||||||
|                 searchRadius, |                 searchRadius, | ||||||
|                 { |                 { | ||||||
|  | @ -88,7 +83,21 @@ export default class NearbyImagesSearch { | ||||||
|                     towardscenter: false, |                     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( |         return p4cStore.map( | ||||||
|             (images) => { |             (images) => { | ||||||
|                 if (images === undefined) { |                 if (images === undefined) { | ||||||
|  | @ -220,3 +229,5 @@ class ImagesInLoadedDataFetcher { | ||||||
|         return foundImages |         return foundImages | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | type P4CService = (typeof NearbyImagesSearch.services)[number] | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| import { Utils } from "../../Utils" | import { Utils } from "../../Utils" | ||||||
| 
 | 
 | ||||||
| export default class PlantNet { | export default class PlantNet { | ||||||
|     private static baseUrl = |     public static baseUrl = | ||||||
|         "https://my-api.plantnet.org/v2/identify/all?api-key=2b10AAsjzwzJvucA5Ncm5qxe" |         "https://my-api.plantnet.org/v2/identify/all?api-key=2b10AAsjzwzJvucA5Ncm5qxe" | ||||||
| 
 | 
 | ||||||
|     public static query(imageUrls: string[]): Promise<PlantNetResult> { |     public static query(imageUrls: string[]): Promise<PlantNetResult> { | ||||||
|  |  | ||||||
|  | @ -123,6 +123,11 @@ export interface WikidataAdvancedSearchoptions extends WikidataSearchoptions { | ||||||
|  * Utility functions around wikidata |  * Utility functions around wikidata | ||||||
|  */ |  */ | ||||||
| export default class 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 _identifierPrefixes = ["Q", "L"].map((str) => str.toLowerCase()) | ||||||
|     private static readonly _prefixesToRemove = [ |     private static readonly _prefixesToRemove = [ | ||||||
|         "https://www.wikidata.org/wiki/Lexeme:", |         "https://www.wikidata.org/wiki/Lexeme:", | ||||||
|  | @ -130,11 +135,11 @@ export default class Wikidata { | ||||||
|         "http://www.wikidata.org/entity/", |         "http://www.wikidata.org/entity/", | ||||||
|         "Lexeme:", |         "Lexeme:", | ||||||
|     ].map((str) => str.toLowerCase()) |     ].map((str) => str.toLowerCase()) | ||||||
| 
 |  | ||||||
|     private static readonly _storeCache = new Map< |     private static readonly _storeCache = new Map< | ||||||
|         string, |         string, | ||||||
|         Store<{ success: WikidataResponse } | { error: any }> |         Store<{ success: WikidataResponse } | { error: any }> | ||||||
|     >() |     >() | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Same as LoadWikidataEntry, but wrapped into a UIEventSource |      * Same as LoadWikidataEntry, but wrapped into a UIEventSource | ||||||
|      * @param value |      * @param value | ||||||
|  | @ -388,6 +393,7 @@ export default class Wikidata { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private static _cache = new Map<string, Promise<WikidataResponse>>() |     private static _cache = new Map<string, Promise<WikidataResponse>>() | ||||||
|  | 
 | ||||||
|     public static async LoadWikidataEntryAsync(value: string | number): Promise<WikidataResponse> { |     public static async LoadWikidataEntryAsync(value: string | number): Promise<WikidataResponse> { | ||||||
|         const key = "" + value |         const key = "" + value | ||||||
|         const cached = Wikidata._cache.get(key) |         const cached = Wikidata._cache.get(key) | ||||||
|  | @ -398,6 +404,7 @@ export default class Wikidata { | ||||||
|         Wikidata._cache.set(key, uncached) |         Wikidata._cache.set(key, uncached) | ||||||
|         return uncached |         return uncached | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Loads a wikidata page |      * Loads a wikidata page | ||||||
|      * @returns the entity of the given value |      * @returns the entity of the given value | ||||||
|  |  | ||||||
|  | @ -34,6 +34,8 @@ export default class Wikipedia { | ||||||
| 
 | 
 | ||||||
|     private static readonly idsToRemove = ["sjabloon_zie"] |     private static readonly idsToRemove = ["sjabloon_zie"] | ||||||
| 
 | 
 | ||||||
|  |     public static readonly neededUrls = ["*.wikipedia.org"] | ||||||
|  | 
 | ||||||
|     private static readonly _cache = new Map<string, Promise<string>>() |     private static readonly _cache = new Map<string, Promise<string>>() | ||||||
|     private static _fullDetailsCache = new Map<string, Store<FullWikipediaDetails>>() |     private static _fullDetailsCache = new Map<string, Store<FullWikipediaDetails>>() | ||||||
|     public readonly backend: string |     public readonly backend: string | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| import * as packagefile from "../../package.json" | import * as packagefile from "../../package.json" | ||||||
| import * as extraconfig from "../../config.json" | import * as extraconfig from "../../config.json" | ||||||
| import { Utils } from "../Utils" | import { Utils } from "../Utils" | ||||||
|  | import { AuthConfig } from "../Logic/Osm/AuthConfig" | ||||||
| 
 | 
 | ||||||
| export type PriviligedLayerType = (typeof Constants.priviliged_layers)[number] | 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 ImgurApiKey = Constants.config.api_keys.imgur | ||||||
|     public static readonly mapillary_client_token_v4 = Constants.config.api_keys.mapillary_v4 |     public static readonly mapillary_client_token_v4 = Constants.config.api_keys.mapillary_v4 | ||||||
|     public static defaultOverpassUrls = Constants.config.default_overpass_urls |     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 |      * 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", |                 "oauth_token", | ||||||
|                 undefined, |                 undefined, | ||||||
|                 "Used to complete the login" |                 "Used to complete the login" | ||||||
|             ), |             ) | ||||||
|             osmConfiguration: <"osm" | "osm-test">this.featureSwitches.featureSwitchApiURL.data, |  | ||||||
|         }) |         }) | ||||||
|         this.userRelatedState = new UserRelatedState( |         this.userRelatedState = new UserRelatedState( | ||||||
|             this.osmConnection, |             this.osmConnection, | ||||||
|  |  | ||||||
|  | @ -22,8 +22,7 @@ export default class AllThemesGui { | ||||||
|                     "oauth_token", |                     "oauth_token", | ||||||
|                     undefined, |                     undefined, | ||||||
|                     "Used to complete the login" |                     "Used to complete the login" | ||||||
|                 ), |                 ) | ||||||
|                 osmConfiguration: <"osm" | "osm-test">featureSwitches.featureSwitchApiURL.data, |  | ||||||
|             }) |             }) | ||||||
|             const state = new UserRelatedState(osmConnection) |             const state = new UserRelatedState(osmConnection) | ||||||
|             const intro = new Combine([ |             const intro = new Combine([ | ||||||
|  |  | ||||||
|  | @ -11,6 +11,7 @@ import { Utils } from "../../Utils" | ||||||
| import Constants from "../../Models/Constants" | import Constants from "../../Models/Constants" | ||||||
| 
 | 
 | ||||||
| export class OpenJosm extends Combine { | 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) { |     constructor(osmConnection: OsmConnection, bounds: Store<BBox>, iconStyle?: string) { | ||||||
|         const t = Translations.t.general.attribution |         const t = Translations.t.general.attribution | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -10,9 +10,11 @@ import Combine from "../Base/Combine" | ||||||
| import Title from "../Base/Title" | import Title from "../Base/Title" | ||||||
| import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" | import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" | ||||||
| import { UIEventSource } from "../../Logic/UIEventSource" | import { UIEventSource } from "../../Logic/UIEventSource" | ||||||
|  | import Constants from "../../Models/Constants" | ||||||
| 
 | 
 | ||||||
| export class AddNoteCommentViz implements SpecialVisualization { | export class AddNoteCommentViz implements SpecialVisualization { | ||||||
|     funcName = "add_note_comment" |     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)." |     docs = "A textfield to add a comment to a node (with the option to close the note)." | ||||||
|     args = [ |     args = [ | ||||||
|         { |         { | ||||||
|  |  | ||||||
|  | @ -9,7 +9,6 @@ import { Utils } from "../../Utils" | ||||||
| import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource" | import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource" | ||||||
| import { VariableUiElement } from "../Base/VariableUIElement" | import { VariableUiElement } from "../Base/VariableUIElement" | ||||||
| import Loading from "../Base/Loading" | import Loading from "../Base/Loading" | ||||||
| import { OsmConnection } from "../../Logic/Osm/OsmConnection" |  | ||||||
| import Translations from "../i18n/Translations" | import Translations from "../i18n/Translations" | ||||||
| import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" | import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" | ||||||
| import { Changes } from "../../Logic/Osm/Changes" | import { Changes } from "../../Logic/Osm/Changes" | ||||||
|  | @ -209,6 +208,8 @@ class ApplyButton extends UIElement { | ||||||
| export default class AutoApplyButton implements SpecialVisualization { | export default class AutoApplyButton implements SpecialVisualization { | ||||||
|     public readonly docs: BaseUIElement |     public readonly docs: BaseUIElement | ||||||
|     public readonly funcName: string = "auto_apply" |     public readonly funcName: string = "auto_apply" | ||||||
|  |     public readonly needsUrls = [] | ||||||
|  | 
 | ||||||
|     public readonly args: { |     public readonly args: { | ||||||
|         name: string |         name: string | ||||||
|         defaultValue?: string |         defaultValue?: string | ||||||
|  | @ -271,14 +272,7 @@ export default class AutoApplyButton implements SpecialVisualization { | ||||||
|         argument: string[] |         argument: string[] | ||||||
|     ): BaseUIElement { |     ): BaseUIElement { | ||||||
|         try { |         try { | ||||||
|             if ( |             if (!state.layout.official && !state.featureSwitchIsTesting.data) { | ||||||
|                 !state.layout.official && |  | ||||||
|                 !( |  | ||||||
|                     state.featureSwitchIsTesting.data || |  | ||||||
|                     state.osmConnection._oauth_config.url === |  | ||||||
|                         OsmConnection.oauth_configs["osm-test"].url |  | ||||||
|                 ) |  | ||||||
|             ) { |  | ||||||
|                 const t = Translations.t.general.add.import |                 const t = Translations.t.general.add.import | ||||||
|                 return new Combine([ |                 return new Combine([ | ||||||
|                     new FixedUiElement( |                     new FixedUiElement( | ||||||
|  |  | ||||||
|  | @ -8,9 +8,11 @@ import Toggle from "../Input/Toggle" | ||||||
| import { LoginToggle } from "./LoginButton" | import { LoginToggle } from "./LoginButton" | ||||||
| import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" | import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" | ||||||
| import { UIEventSource } from "../../Logic/UIEventSource" | import { UIEventSource } from "../../Logic/UIEventSource" | ||||||
|  | import Constants from "../../Models/Constants" | ||||||
| 
 | 
 | ||||||
| export class CloseNoteButton implements SpecialVisualization { | export class CloseNoteButton implements SpecialVisualization { | ||||||
|     public readonly funcName = "close_note" |     public readonly funcName = "close_note" | ||||||
|  |     public readonly needsUrls = [Constants.osmAuthConfig.url] | ||||||
|     public readonly docs = |     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." |         "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 = [ |     public readonly args = [ | ||||||
|  |  | ||||||
|  | @ -13,7 +13,7 @@ export class ExportAsGpxViz implements SpecialVisualization { | ||||||
|     funcName = "export_as_gpx" |     funcName = "export_as_gpx" | ||||||
|     docs = "Exports the selected feature as GPX-file" |     docs = "Exports the selected feature as GPX-file" | ||||||
|     args = [] |     args = [] | ||||||
| 
 |     needsUrls = [] | ||||||
|     constr( |     constr( | ||||||
|         state: SpecialVisualizationState, |         state: SpecialVisualizationState, | ||||||
|         tagSource: UIEventSource<Record<string, string>>, |         tagSource: UIEventSource<Record<string, string>>, | ||||||
|  |  | ||||||
|  | @ -2,10 +2,13 @@ import { Store, UIEventSource } from "../../Logic/UIEventSource" | ||||||
| import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" | import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" | ||||||
| import Histogram from "../BigComponents/Histogram" | import Histogram from "../BigComponents/Histogram" | ||||||
| import { Feature } from "geojson" | import { Feature } from "geojson" | ||||||
|  | import Constants from "../../Models/Constants" | ||||||
| 
 | 
 | ||||||
| export class HistogramViz implements SpecialVisualization { | export class HistogramViz implements SpecialVisualization { | ||||||
|     funcName = "histogram" |     funcName = "histogram" | ||||||
|     docs = "Create a histogram for a list of given values, read from the properties." |     docs = "Create a histogram for a list of given values, read from the properties." | ||||||
|  |     needsUrls = [] | ||||||
|  | 
 | ||||||
|     example = |     example = | ||||||
|         '`{histogram(\'some_key\')}` with properties being `{some_key: ["a","b","a","c"]} to create a histogram' |         '`{histogram(\'some_key\')}` with properties being `{some_key: ["a","b","a","c"]} to create a histogram' | ||||||
|     args = [ |     args = [ | ||||||
|  |  | ||||||
|  | @ -24,6 +24,7 @@ export interface ConflateFlowArguments extends ImportFlowArguments { | ||||||
| 
 | 
 | ||||||
| export default class ConflateImportButtonViz implements SpecialVisualization, AutoAction { | export default class ConflateImportButtonViz implements SpecialVisualization, AutoAction { | ||||||
|     supportsAutoAction: boolean = true |     supportsAutoAction: boolean = true | ||||||
|  |     needsUrls = [] | ||||||
|     public readonly funcName: string = "conflate_button" |     public readonly funcName: string = "conflate_button" | ||||||
|     public readonly args: { |     public readonly args: { | ||||||
|         name: string |         name: string | ||||||
|  |  | ||||||
|  | @ -194,10 +194,7 @@ export default abstract class ImportFlow<ArgT extends ImportFlowArguments> { | ||||||
|                     return { error: t.hasBeenImported } |                     return { error: t.hasBeenImported } | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 const usesTestUrl = |                 if (!state.layout.official && !isTesting) { | ||||||
|                     this.state.osmConnection._oauth_config.url === |  | ||||||
|                     OsmConnection.oauth_configs["osm-test"].url |  | ||||||
|                 if (!state.layout.official && !(isTesting || usesTestUrl)) { |  | ||||||
|                     // Unofficial theme - imports not allowed
 |                     // Unofficial theme - imports not allowed
 | ||||||
|                     return { |                     return { | ||||||
|                         error: t.officialThemesOnly, |                         error: t.officialThemesOnly, | ||||||
|  |  | ||||||
|  | @ -18,6 +18,7 @@ export class PointImportButtonViz implements SpecialVisualization { | ||||||
|     public readonly docs: string | BaseUIElement |     public readonly docs: string | BaseUIElement | ||||||
|     public readonly example?: string |     public readonly example?: string | ||||||
|     public readonly args: { name: string; defaultValue?: string; doc: string }[] |     public readonly args: { name: string; defaultValue?: string; doc: string }[] | ||||||
|  |     public needsUrls = [] | ||||||
| 
 | 
 | ||||||
|     constructor() { |     constructor() { | ||||||
|         this.funcName = "import_button" |         this.funcName = "import_button" | ||||||
|  |  | ||||||
|  | @ -20,6 +20,7 @@ import FullNodeDatabaseSource from "../../../Logic/FeatureSource/TiledFeatureSou | ||||||
|  */ |  */ | ||||||
| export default class WayImportButtonViz implements AutoAction, SpecialVisualization { | export default class WayImportButtonViz implements AutoAction, SpecialVisualization { | ||||||
|     public readonly funcName: string = "import_way_button" |     public readonly funcName: string = "import_way_button" | ||||||
|  |     needsUrls = [] | ||||||
|     public readonly docs: string = |     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'" + |         "This button will copy the data from an external dataset into OpenStreetMap, copying the geometry and adding it as a 'line'" + | ||||||
|         ImportFlowUtils.documentationGeneral |         ImportFlowUtils.documentationGeneral | ||||||
|  |  | ||||||
|  | @ -20,6 +20,7 @@ import { Feature } from "geojson" | ||||||
| 
 | 
 | ||||||
| export class LanguageElement implements SpecialVisualization { | export class LanguageElement implements SpecialVisualization { | ||||||
|     funcName: string = "language_chooser" |     funcName: string = "language_chooser" | ||||||
|  |     needsUrls = [] | ||||||
| 
 | 
 | ||||||
|     docs: string | BaseUIElement = |     docs: string | BaseUIElement = | ||||||
|         "The language element allows to show and pick all known (modern) languages. The key can be set" |         "The language element allows to show and pick all known (modern) languages. The key can be set" | ||||||
|  |  | ||||||
|  | @ -9,6 +9,8 @@ import MapillaryLink from "../BigComponents/MapillaryLink.svelte" | ||||||
| export class MapillaryLinkVis implements SpecialVisualization { | export class MapillaryLinkVis implements SpecialVisualization { | ||||||
|     funcName = "mapillary_link" |     funcName = "mapillary_link" | ||||||
|     docs = "Adds a button to open mapillary on the specified location" |     docs = "Adds a button to open mapillary on the specified location" | ||||||
|  |     needsUrls = [] | ||||||
|  | 
 | ||||||
|     args = [ |     args = [ | ||||||
|         { |         { | ||||||
|             name: "zoom", |             name: "zoom", | ||||||
|  |  | ||||||
|  | @ -13,6 +13,7 @@ import { BBox } from "../../Logic/BBox" | ||||||
| export class MinimapViz implements SpecialVisualization { | export class MinimapViz implements SpecialVisualization { | ||||||
|     funcName = "minimap" |     funcName = "minimap" | ||||||
|     docs = "A small map showing the selected feature." |     docs = "A small map showing the selected feature." | ||||||
|  |     needsUrls = [] | ||||||
|     args = [ |     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", |             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 { | export class MultiApplyViz implements SpecialVisualization { | ||||||
|     funcName = "multi_apply" |     funcName = "multi_apply" | ||||||
|  |     needsUrls = [] | ||||||
|     docs = |     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" |         "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 = [ |     args = [ | ||||||
|  |  | ||||||
|  | @ -8,9 +8,10 @@ import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders" | ||||||
| import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" | import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" | ||||||
| import SvelteUIElement from "../Base/SvelteUIElement" | import SvelteUIElement from "../Base/SvelteUIElement" | ||||||
| import PlantNet from "../PlantNet/PlantNet.svelte" | import PlantNet from "../PlantNet/PlantNet.svelte" | ||||||
| 
 | import { default as PlantNetCode } from "../../Logic/Web/PlantNet" | ||||||
| export class PlantNetDetectionViz implements SpecialVisualization { | export class PlantNetDetectionViz implements SpecialVisualization { | ||||||
|     funcName = "plantnet_detection" |     funcName = "plantnet_detection" | ||||||
|  |     needsUrls = [PlantNetCode.baseUrl] | ||||||
| 
 | 
 | ||||||
|     docs = |     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`). " |         "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 { | export default class QuestionViz implements SpecialVisualization { | ||||||
|     funcName = "questions" |     funcName = "questions" | ||||||
|  |     needsUrls = [] | ||||||
|  | 
 | ||||||
|     docs = |     docs = | ||||||
|         "The special element which shows the questions which are unkown. Added by default if not yet there" |         "The special element which shows the questions which are unkown. Added by default if not yet there" | ||||||
|     args = [ |     args = [ | ||||||
|  |  | ||||||
|  | @ -15,6 +15,7 @@ export class ShareLinkViz implements SpecialVisualization { | ||||||
|             doc: "The url to share (default: current URL)", |             doc: "The url to share (default: current URL)", | ||||||
|         }, |         }, | ||||||
|     ] |     ] | ||||||
|  |     needsUrls = [] | ||||||
| 
 | 
 | ||||||
|     public constr( |     public constr( | ||||||
|         state: SpecialVisualizationState, |         state: SpecialVisualizationState, | ||||||
|  |  | ||||||
|  | @ -21,6 +21,7 @@ import Maproulette from "../../Logic/Maproulette" | ||||||
| 
 | 
 | ||||||
| export default class TagApplyButton implements AutoAction, SpecialVisualization { | export default class TagApplyButton implements AutoAction, SpecialVisualization { | ||||||
|     public readonly funcName = "tag_apply" |     public readonly funcName = "tag_apply" | ||||||
|  |     needsUrls = [] | ||||||
|     public readonly docs = |     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" + |         "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 |         Utils.Special_visualizations_tagsToApplyHelpText | ||||||
|  |  | ||||||
|  | @ -2,6 +2,7 @@ import UploadTraceToOsmUI from "../BigComponents/UploadTraceToOsmUI" | ||||||
| import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" | import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" | ||||||
| import { UIEventSource } from "../../Logic/UIEventSource" | import { UIEventSource } from "../../Logic/UIEventSource" | ||||||
| import { GeoOperations } from "../../Logic/GeoOperations" | import { GeoOperations } from "../../Logic/GeoOperations" | ||||||
|  | import Constants from "../../Models/Constants" | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Wrapper  around 'UploadTraceToOsmUI' |  * Wrapper  around 'UploadTraceToOsmUI' | ||||||
|  | @ -11,6 +12,7 @@ export class UploadToOsmViz implements SpecialVisualization { | ||||||
|     docs = |     docs = | ||||||
|         "Uploads the GPS-history as GPX to OpenStreetMap.org; clears the history afterwards. The actual feature is ignored." |         "Uploads the GPS-history as GPX to OpenStreetMap.org; clears the history afterwards. The actual feature is ignored." | ||||||
|     args = [] |     args = [] | ||||||
|  |     needsUrls = [Constants.osmAuthConfig.url] | ||||||
| 
 | 
 | ||||||
|     constr( |     constr( | ||||||
|         state: SpecialVisualizationState, |         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 { Store, UIEventSource } from "../Logic/UIEventSource" | ||||||
| import BaseUIElement from "./BaseUIElement"; | import BaseUIElement from "./BaseUIElement" | ||||||
| import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"; | import LayoutConfig from "../Models/ThemeConfig/LayoutConfig" | ||||||
| import { IndexedFeatureSource, WritableFeatureSource } from "../Logic/FeatureSource/FeatureSource"; | import { IndexedFeatureSource, WritableFeatureSource } from "../Logic/FeatureSource/FeatureSource" | ||||||
| import { OsmConnection } from "../Logic/Osm/OsmConnection"; | import { OsmConnection } from "../Logic/Osm/OsmConnection" | ||||||
| import { Changes } from "../Logic/Osm/Changes"; | import { Changes } from "../Logic/Osm/Changes" | ||||||
| import { ExportableMap, MapProperties } from "../Models/MapProperties"; | import { ExportableMap, MapProperties } from "../Models/MapProperties" | ||||||
| import LayerState from "../Logic/State/LayerState"; | import LayerState from "../Logic/State/LayerState" | ||||||
| import { Feature, Geometry, Point } from "geojson"; | import { Feature, Geometry, Point } from "geojson" | ||||||
| import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource"; | import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource" | ||||||
| import { MangroveIdentity } from "../Logic/Web/MangroveReviews"; | import { MangroveIdentity } from "../Logic/Web/MangroveReviews" | ||||||
| import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore"; | import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore" | ||||||
| import LayerConfig from "../Models/ThemeConfig/LayerConfig"; | import LayerConfig from "../Models/ThemeConfig/LayerConfig" | ||||||
| import FeatureSwitchState from "../Logic/State/FeatureSwitchState"; | import FeatureSwitchState from "../Logic/State/FeatureSwitchState" | ||||||
| import { MenuState } from "../Models/MenuState"; | import { MenuState } from "../Models/MenuState" | ||||||
| import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader"; | import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader" | ||||||
| import { RasterLayerPolygon } from "../Models/RasterLayers"; | import { RasterLayerPolygon } from "../Models/RasterLayers" | ||||||
| import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager"; | import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager" | ||||||
| import { OsmTags } from "../Models/OsmFeature"; | import { OsmTags } from "../Models/OsmFeature" | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * The state needed to render a special Visualisation. |  * The state needed to render a special Visualisation. | ||||||
|  */ |  */ | ||||||
| export interface SpecialVisualizationState { | export interface SpecialVisualizationState { | ||||||
|   readonly guistate: MenuState; |     readonly guistate: MenuState | ||||||
|   readonly layout: LayoutConfig; |     readonly layout: LayoutConfig | ||||||
|   readonly featureSwitches: FeatureSwitchState; |     readonly featureSwitches: FeatureSwitchState | ||||||
| 
 | 
 | ||||||
|   readonly layerState: LayerState; |     readonly layerState: LayerState | ||||||
|   readonly featureProperties: { getStore(id: string): UIEventSource<Record<string, string>>, trackFeature?(feature: { properties: OsmTags }) }; |     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. |      * Some features will create a new element that should be displayed. | ||||||
|    * These can be injected by appending them to this featuresource (and pinging it) |      * These can be injected by appending them to this featuresource (and pinging it) | ||||||
|    */ |      */ | ||||||
|   readonly newFeatures: WritableFeatureSource; |     readonly newFeatures: WritableFeatureSource | ||||||
| 
 | 
 | ||||||
|   readonly historicalUserLocations: WritableFeatureSource<Feature<Point>>; |     readonly historicalUserLocations: WritableFeatureSource<Feature<Point>> | ||||||
| 
 | 
 | ||||||
|   readonly osmConnection: OsmConnection; |     readonly osmConnection: OsmConnection | ||||||
|   readonly featureSwitchUserbadge: Store<boolean>; |     readonly featureSwitchUserbadge: Store<boolean> | ||||||
|   readonly featureSwitchIsTesting: Store<boolean>; |     readonly featureSwitchIsTesting: Store<boolean> | ||||||
|   readonly changes: Changes; |     readonly changes: Changes | ||||||
|   readonly osmObjectDownloader: OsmObjectDownloader; |     readonly osmObjectDownloader: OsmObjectDownloader | ||||||
|   /** |     /** | ||||||
|    * State of the main map |      * State of the main map | ||||||
|    */ |      */ | ||||||
|   readonly mapProperties: MapProperties & ExportableMap; |     readonly mapProperties: MapProperties & ExportableMap | ||||||
| 
 | 
 | ||||||
|   readonly selectedElement: UIEventSource<Feature>; |     readonly selectedElement: UIEventSource<Feature> | ||||||
|   /** |     /** | ||||||
|    * Works together with 'selectedElement' to indicate what properties should be displayed |      * Works together with 'selectedElement' to indicate what properties should be displayed | ||||||
|    */ |      */ | ||||||
|   readonly selectedLayer: UIEventSource<LayerConfig>; |     readonly selectedLayer: UIEventSource<LayerConfig> | ||||||
|   readonly selectedElementAndLayer: Store<{ feature: Feature; layer: LayerConfig }>; |     readonly selectedElementAndLayer: Store<{ feature: Feature; layer: LayerConfig }> | ||||||
| 
 | 
 | ||||||
|   /** |     /** | ||||||
|    * If data is currently being fetched from external sources |      * If data is currently being fetched from external sources | ||||||
|    */ |      */ | ||||||
|   readonly dataIsLoading: Store<boolean>; |     readonly dataIsLoading: Store<boolean> | ||||||
|   /** |     /** | ||||||
|    * Only needed for 'ReplaceGeometryAction' |      * Only needed for 'ReplaceGeometryAction' | ||||||
|    */ |      */ | ||||||
|   readonly fullNodeDatabase?: FullNodeDatabaseSource; |     readonly fullNodeDatabase?: FullNodeDatabaseSource | ||||||
| 
 | 
 | ||||||
|   readonly perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer>; |     readonly perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer> | ||||||
|   readonly userRelatedState: { |     readonly userRelatedState: { | ||||||
|     readonly imageLicense: UIEventSource<string>; |         readonly imageLicense: UIEventSource<string> | ||||||
|     readonly showTags: UIEventSource<"no" | undefined | "always" | "yes" | "full"> |         readonly showTags: UIEventSource<"no" | undefined | "always" | "yes" | "full"> | ||||||
|     readonly mangroveIdentity: MangroveIdentity |         readonly mangroveIdentity: MangroveIdentity | ||||||
|     readonly showAllQuestionsAtOnce: UIEventSource<boolean> |         readonly showAllQuestionsAtOnce: UIEventSource<boolean> | ||||||
|     readonly preferencesAsTags: Store<Record<string, string>> |         readonly preferencesAsTags: Store<Record<string, string>> | ||||||
|     readonly language: UIEventSource<string> |         readonly language: UIEventSource<string> | ||||||
|   }; |     } | ||||||
|   readonly lastClickObject: WritableFeatureSource; |     readonly lastClickObject: WritableFeatureSource | ||||||
| 
 | 
 | ||||||
|   readonly availableLayers: Store<RasterLayerPolygon[]>; |     readonly availableLayers: Store<RasterLayerPolygon[]> | ||||||
| 
 | 
 | ||||||
|   readonly imageUploadManager: ImageUploadManager; |     readonly imageUploadManager: ImageUploadManager | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export interface SpecialVisualization { | export interface SpecialVisualization { | ||||||
|   readonly funcName: string; |     readonly funcName: string | ||||||
|   readonly docs: string | BaseUIElement; |     readonly docs: string | BaseUIElement | ||||||
|   readonly example?: string; |     readonly example?: string | ||||||
|  |     readonly needsUrls: string[] | ||||||
| 
 | 
 | ||||||
|   /** |     /** | ||||||
|    * Indicates that this special visualisation will make requests to the 'alLNodesDatabase' and that it thus should be included |      * Indicates that this special visualisation will make requests to the 'alLNodesDatabase' and that it thus should be included | ||||||
|    */ |      */ | ||||||
|   readonly needsNodeDatabase?: boolean; |     readonly needsNodeDatabase?: boolean | ||||||
|   readonly args: { |     readonly args: { | ||||||
|     name: string |         name: string | ||||||
|     defaultValue?: string |         defaultValue?: string | ||||||
|     doc: string |         doc: string | ||||||
|     required?: false | boolean |         required?: false | boolean | ||||||
|   }[]; |     }[] | ||||||
|   readonly getLayerDependencies?: (argument: string[]) => string[]; |     readonly getLayerDependencies?: (argument: string[]) => string[] | ||||||
| 
 | 
 | ||||||
|   structuredExamples?(): { feature: Feature<Geometry, Record<string, string>>; args: string[] }[]; |     structuredExamples?(): { feature: Feature<Geometry, Record<string, string>>; args: string[] }[] | ||||||
| 
 | 
 | ||||||
|   constr( |     constr( | ||||||
|     state: SpecialVisualizationState, |         state: SpecialVisualizationState, | ||||||
|     tagSource: UIEventSource<Record<string, string>>, |         tagSource: UIEventSource<Record<string, string>>, | ||||||
|     argument: string[], |         argument: string[], | ||||||
|     feature: Feature, |         feature: Feature, | ||||||
|     layer: LayerConfig |         layer: LayerConfig | ||||||
|   ): BaseUIElement; |     ): BaseUIElement | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export type RenderingSpecification = | export type RenderingSpecification = | ||||||
|   | string |     | string | ||||||
|   | { |     | { | ||||||
|   func: SpecialVisualization |           func: SpecialVisualization | ||||||
|   args: string[] |           args: string[] | ||||||
|   style: string |           style: string | ||||||
| } |       } | ||||||
|  |  | ||||||
|  | @ -1,71 +1,79 @@ | ||||||
| import Combine from "./Base/Combine"; | import Combine from "./Base/Combine" | ||||||
| import { FixedUiElement } from "./Base/FixedUiElement"; | import { FixedUiElement } from "./Base/FixedUiElement" | ||||||
| import BaseUIElement from "./BaseUIElement"; | import BaseUIElement from "./BaseUIElement" | ||||||
| import Title from "./Base/Title"; | import Title from "./Base/Title" | ||||||
| import Table from "./Base/Table"; | import Table from "./Base/Table" | ||||||
| import { RenderingSpecification, SpecialVisualization, SpecialVisualizationState } from "./SpecialVisualization"; | import { | ||||||
| import { HistogramViz } from "./Popup/HistogramViz"; |     RenderingSpecification, | ||||||
| import { MinimapViz } from "./Popup/MinimapViz"; |     SpecialVisualization, | ||||||
| import { ShareLinkViz } from "./Popup/ShareLinkViz"; |     SpecialVisualizationState, | ||||||
| import { UploadToOsmViz } from "./Popup/UploadToOsmViz"; | } from "./SpecialVisualization" | ||||||
| import { MultiApplyViz } from "./Popup/MultiApplyViz"; | import { HistogramViz } from "./Popup/HistogramViz" | ||||||
| import { AddNoteCommentViz } from "./Popup/AddNoteCommentViz"; | import { MinimapViz } from "./Popup/MinimapViz" | ||||||
| import { PlantNetDetectionViz } from "./Popup/PlantNetDetectionViz"; | import { ShareLinkViz } from "./Popup/ShareLinkViz" | ||||||
| import TagApplyButton from "./Popup/TagApplyButton"; | import { UploadToOsmViz } from "./Popup/UploadToOsmViz" | ||||||
| import { CloseNoteButton } from "./Popup/CloseNoteButton"; | import { MultiApplyViz } from "./Popup/MultiApplyViz" | ||||||
| import { MapillaryLinkVis } from "./Popup/MapillaryLinkVis"; | import { AddNoteCommentViz } from "./Popup/AddNoteCommentViz" | ||||||
| import { Store, Stores, UIEventSource } from "../Logic/UIEventSource"; | import { PlantNetDetectionViz } from "./Popup/PlantNetDetectionViz" | ||||||
| import AllTagsPanel from "./Popup/AllTagsPanel.svelte"; | import TagApplyButton from "./Popup/TagApplyButton" | ||||||
| import AllImageProviders from "../Logic/ImageProviders/AllImageProviders"; | import { CloseNoteButton } from "./Popup/CloseNoteButton" | ||||||
| import { ImageCarousel } from "./Image/ImageCarousel"; | import { MapillaryLinkVis } from "./Popup/MapillaryLinkVis" | ||||||
| import { VariableUiElement } from "./Base/VariableUIElement"; | import { Store, Stores, UIEventSource } from "../Logic/UIEventSource" | ||||||
| import { Utils } from "../Utils"; | import AllTagsPanel from "./Popup/AllTagsPanel.svelte" | ||||||
| import Wikidata, { WikidataResponse } from "../Logic/Web/Wikidata"; | import AllImageProviders from "../Logic/ImageProviders/AllImageProviders" | ||||||
| import { Translation } from "./i18n/Translation"; | import { ImageCarousel } from "./Image/ImageCarousel" | ||||||
| import Translations from "./i18n/Translations"; | import { VariableUiElement } from "./Base/VariableUIElement" | ||||||
| import ReviewForm from "./Reviews/ReviewForm"; | import { Utils } from "../Utils" | ||||||
| import ReviewElement from "./Reviews/ReviewElement"; | import Wikidata, { WikidataResponse } from "../Logic/Web/Wikidata" | ||||||
| import OpeningHoursVisualization from "./OpeningHours/OpeningHoursVisualization"; | import { Translation } from "./i18n/Translation" | ||||||
| import LiveQueryHandler from "../Logic/Web/LiveQueryHandler"; | import Translations from "./i18n/Translations" | ||||||
| import { SubtleButton } from "./Base/SubtleButton"; | import ReviewForm from "./Reviews/ReviewForm" | ||||||
| import Svg from "../Svg"; | import ReviewElement from "./Reviews/ReviewElement" | ||||||
| import NoteCommentElement from "./Popup/NoteCommentElement"; | import OpeningHoursVisualization from "./OpeningHours/OpeningHoursVisualization" | ||||||
| import { SubstitutedTranslation } from "./SubstitutedTranslation"; | import { SubtleButton } from "./Base/SubtleButton" | ||||||
| import List from "./Base/List"; | import Svg from "../Svg" | ||||||
| import StatisticsPanel from "./BigComponents/StatisticsPanel"; | import NoteCommentElement from "./Popup/NoteCommentElement" | ||||||
| import AutoApplyButton from "./Popup/AutoApplyButton"; | import { SubstitutedTranslation } from "./SubstitutedTranslation" | ||||||
| import { LanguageElement } from "./Popup/LanguageElement"; | import List from "./Base/List" | ||||||
| import FeatureReviews from "../Logic/Web/MangroveReviews"; | import StatisticsPanel from "./BigComponents/StatisticsPanel" | ||||||
| import Maproulette from "../Logic/Maproulette"; | import AutoApplyButton from "./Popup/AutoApplyButton" | ||||||
| import SvelteUIElement from "./Base/SvelteUIElement"; | import { LanguageElement } from "./Popup/LanguageElement" | ||||||
| import { BBoxFeatureSourceForLayer } from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource"; | import FeatureReviews from "../Logic/Web/MangroveReviews" | ||||||
| import QuestionViz from "./Popup/QuestionViz"; | import Maproulette from "../Logic/Maproulette" | ||||||
| import { Feature, Point } from "geojson"; | import SvelteUIElement from "./Base/SvelteUIElement" | ||||||
| import { GeoOperations } from "../Logic/GeoOperations"; | import { BBoxFeatureSourceForLayer } from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource" | ||||||
| import CreateNewNote from "./Popup/CreateNewNote.svelte"; | import QuestionViz from "./Popup/QuestionViz" | ||||||
| import AddNewPoint from "./Popup/AddNewPoint/AddNewPoint.svelte"; | import { Feature, Point } from "geojson" | ||||||
| import UserProfile from "./BigComponents/UserProfile.svelte"; | import { GeoOperations } from "../Logic/GeoOperations" | ||||||
| import LanguagePicker from "./LanguagePicker"; | import CreateNewNote from "./Popup/CreateNewNote.svelte" | ||||||
| import Link from "./Base/Link"; | import AddNewPoint from "./Popup/AddNewPoint/AddNewPoint.svelte" | ||||||
| import LayerConfig from "../Models/ThemeConfig/LayerConfig"; | import UserProfile from "./BigComponents/UserProfile.svelte" | ||||||
| import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig"; | import LanguagePicker from "./LanguagePicker" | ||||||
| import { OsmTags, WayId } from "../Models/OsmFeature"; | import Link from "./Base/Link" | ||||||
| import MoveWizard from "./Popup/MoveWizard"; | import LayerConfig from "../Models/ThemeConfig/LayerConfig" | ||||||
| import SplitRoadWizard from "./Popup/SplitRoadWizard"; | import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig" | ||||||
| import { ExportAsGpxViz } from "./Popup/ExportAsGpxViz"; | import { OsmTags, WayId } from "../Models/OsmFeature" | ||||||
| import WikipediaPanel from "./Wikipedia/WikipediaPanel.svelte"; | import MoveWizard from "./Popup/MoveWizard" | ||||||
| import TagRenderingEditable from "./Popup/TagRendering/TagRenderingEditable.svelte"; | import SplitRoadWizard from "./Popup/SplitRoadWizard" | ||||||
| import { PointImportButtonViz } from "./Popup/ImportButtons/PointImportButtonViz"; | import { ExportAsGpxViz } from "./Popup/ExportAsGpxViz" | ||||||
| import WayImportButtonViz from "./Popup/ImportButtons/WayImportButtonViz"; | import WikipediaPanel from "./Wikipedia/WikipediaPanel.svelte" | ||||||
| import ConflateImportButtonViz from "./Popup/ImportButtons/ConflateImportButtonViz"; | import TagRenderingEditable from "./Popup/TagRendering/TagRenderingEditable.svelte" | ||||||
| import DeleteWizard from "./Popup/DeleteFlow/DeleteWizard.svelte"; | import { PointImportButtonViz } from "./Popup/ImportButtons/PointImportButtonViz" | ||||||
| import { OpenJosm } from "./BigComponents/OpenJosm"; | import WayImportButtonViz from "./Popup/ImportButtons/WayImportButtonViz" | ||||||
| import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte"; | import ConflateImportButtonViz from "./Popup/ImportButtons/ConflateImportButtonViz" | ||||||
| import FediverseValidator from "./InputElement/Validators/FediverseValidator"; | import DeleteWizard from "./Popup/DeleteFlow/DeleteWizard.svelte" | ||||||
| import SendEmail from "./Popup/SendEmail.svelte"; | import { OpenJosm } from "./BigComponents/OpenJosm" | ||||||
| import NearbyImages from "./Popup/NearbyImages.svelte"; | import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte" | ||||||
| import NearbyImagesCollapsed from "./Popup/NearbyImagesCollapsed.svelte"; | import FediverseValidator from "./InputElement/Validators/FediverseValidator" | ||||||
| import UploadImage from "./Image/UploadImage.svelte"; | 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 NearbyImageVis implements SpecialVisualization { | ||||||
|     // Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
 |     // Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
 | ||||||
|  | @ -79,7 +87,7 @@ class NearbyImageVis implements SpecialVisualization { | ||||||
|     docs = |     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" |         "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" |     funcName = "nearby_images" | ||||||
| 
 |     needsUrls = NearbyImagesSearch.apiUrls | ||||||
|     constr( |     constr( | ||||||
|         state: SpecialVisualizationState, |         state: SpecialVisualizationState, | ||||||
|         tags: UIEventSource<Record<string, string>>, |         tags: UIEventSource<Record<string, string>>, | ||||||
|  | @ -117,6 +125,7 @@ class StealViz implements SpecialVisualization { | ||||||
|             required: true, |             required: true, | ||||||
|         }, |         }, | ||||||
|     ] |     ] | ||||||
|  |     needsUrls = [] | ||||||
| 
 | 
 | ||||||
|     constr(state: SpecialVisualizationState, featureTags, args) { |     constr(state: SpecialVisualizationState, featureTags, args) { | ||||||
|         const [featureIdKey, layerAndtagRenderingIds] = args |         const [featureIdKey, layerAndtagRenderingIds] = args | ||||||
|  | @ -264,7 +273,6 @@ export default class SpecialVisualizations { | ||||||
|                     SpecialVisualizations.specialVisualizations |                     SpecialVisualizations.specialVisualizations | ||||||
|                         .map((sp) => sp.funcName + "()") |                         .map((sp) => sp.funcName + "()") | ||||||
|                         .join(", ") |                         .join(", ") | ||||||
| 
 |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | @ -378,6 +386,7 @@ export default class SpecialVisualizations { | ||||||
|                 funcName: "add_new_point", |                 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`", |                 docs: "An element which allows to add a new point on the 'last_click'-location. Only makes sense in the layer `last_click`", | ||||||
|                 args: [], |                 args: [], | ||||||
|  |                 needsUrls: [], | ||||||
|                 constr(state: SpecialVisualizationState, _, __, feature): BaseUIElement { |                 constr(state: SpecialVisualizationState, _, __, feature): BaseUIElement { | ||||||
|                     let [lon, lat] = GeoOperations.centerpointCoordinates(feature) |                     let [lon, lat] = GeoOperations.centerpointCoordinates(feature) | ||||||
|                     return new SvelteUIElement(AddNewPoint, { |                     return new SvelteUIElement(AddNewPoint, { | ||||||
|  | @ -389,6 +398,7 @@ export default class SpecialVisualizations { | ||||||
|             { |             { | ||||||
|                 funcName: "user_profile", |                 funcName: "user_profile", | ||||||
|                 args: [], |                 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'", |                 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 { |                 constr(state: SpecialVisualizationState): BaseUIElement { | ||||||
|                     return new SvelteUIElement(UserProfile, { |                     return new SvelteUIElement(UserProfile, { | ||||||
|  | @ -399,6 +409,7 @@ export default class SpecialVisualizations { | ||||||
|             { |             { | ||||||
|                 funcName: "language_picker", |                 funcName: "language_picker", | ||||||
|                 args: [], |                 args: [], | ||||||
|  |                 needsUrls: [], | ||||||
|                 docs: "A component to set the language of the user interface", |                 docs: "A component to set the language of the user interface", | ||||||
|                 constr(state: SpecialVisualizationState): BaseUIElement { |                 constr(state: SpecialVisualizationState): BaseUIElement { | ||||||
|                     return new LanguagePicker( |                     return new LanguagePicker( | ||||||
|  | @ -410,6 +421,7 @@ export default class SpecialVisualizations { | ||||||
|             { |             { | ||||||
|                 funcName: "logout", |                 funcName: "logout", | ||||||
|                 args: [], |                 args: [], | ||||||
|  |                 needsUrls: [Constants.osmAuthConfig.url], | ||||||
|                 docs: "Shows a button where the user can log out", |                 docs: "Shows a button where the user can log out", | ||||||
|                 constr(state: SpecialVisualizationState): BaseUIElement { |                 constr(state: SpecialVisualizationState): BaseUIElement { | ||||||
|                     return new SubtleButton(Svg.logout_svg(), Translations.t.general.logout, { |                     return new SubtleButton(Svg.logout_svg(), Translations.t.general.logout, { | ||||||
|  | @ -426,6 +438,7 @@ export default class SpecialVisualizations { | ||||||
|                 funcName: "split_button", |                 funcName: "split_button", | ||||||
|                 docs: "Adds a button which allows to split a way", |                 docs: "Adds a button which allows to split a way", | ||||||
|                 args: [], |                 args: [], | ||||||
|  |                 needsUrls: [], | ||||||
|                 constr( |                 constr( | ||||||
|                     state: SpecialVisualizationState, |                     state: SpecialVisualizationState, | ||||||
|                     tagSource: UIEventSource<Record<string, string>> |                     tagSource: UIEventSource<Record<string, string>> | ||||||
|  | @ -446,6 +459,7 @@ export default class SpecialVisualizations { | ||||||
|                 funcName: "move_button", |                 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", |                 docs: "Adds a button which allows to move the object to another location. The config will be read from the layer config", | ||||||
|                 args: [], |                 args: [], | ||||||
|  |                 needsUrls: [], | ||||||
|                 constr( |                 constr( | ||||||
|                     state: SpecialVisualizationState, |                     state: SpecialVisualizationState, | ||||||
|                     tagSource: UIEventSource<Record<string, string>>, |                     tagSource: UIEventSource<Record<string, string>>, | ||||||
|  | @ -469,6 +483,7 @@ export default class SpecialVisualizations { | ||||||
|                 funcName: "delete_button", |                 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", |                 docs: "Adds a button which allows to delete the object at this location. The config will be read from the layer config", | ||||||
|                 args: [], |                 args: [], | ||||||
|  |                 needsUrls: [], | ||||||
|                 constr( |                 constr( | ||||||
|                     state: SpecialVisualizationState, |                     state: SpecialVisualizationState, | ||||||
|                     tagSource: UIEventSource<Record<string, string>>, |                     tagSource: UIEventSource<Record<string, string>>, | ||||||
|  | @ -493,6 +508,7 @@ export default class SpecialVisualizations { | ||||||
|             { |             { | ||||||
|                 funcName: "open_note", |                 funcName: "open_note", | ||||||
|                 args: [], |                 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", |                 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( |                 constr( | ||||||
|                     state: SpecialVisualizationState, |                     state: SpecialVisualizationState, | ||||||
|  | @ -525,6 +541,7 @@ export default class SpecialVisualizations { | ||||||
|                         defaultValue: "wikidata;wikipedia", |                         defaultValue: "wikidata;wikipedia", | ||||||
|                     }, |                     }, | ||||||
|                 ], |                 ], | ||||||
|  |                 needsUrls: [...Wikidata.neededUrls, ...Wikipedia.neededUrls], | ||||||
|                 example: |                 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", |                     "`{wikipedia()}` is a basic example, `{wikipedia(name:etymology:wikidata)}` to show the wikipedia page of whom the feature was named after. Also remember that these can be styled, e.g. `{wikipedia():max-height: 10rem}` to limit the height", | ||||||
|                 constr: (_, tagsSource, args) => { |                 constr: (_, tagsSource, args) => { | ||||||
|  | @ -548,6 +565,7 @@ export default class SpecialVisualizations { | ||||||
|                         defaultValue: "wikidata", |                         defaultValue: "wikidata", | ||||||
|                     }, |                     }, | ||||||
|                 ], |                 ], | ||||||
|  |                 needsUrls: Wikidata.neededUrls, | ||||||
|                 example: |                 example: | ||||||
|                     "`{wikidata_label()}` is a basic example, `{wikipedia(name:etymology:wikidata)}` to show the label itself", |                     "`{wikidata_label()}` is a basic example, `{wikipedia(name:etymology:wikidata)}` to show the label itself", | ||||||
|                 constr: (_, tagsSource, args) => |                 constr: (_, tagsSource, args) => | ||||||
|  | @ -577,6 +595,7 @@ export default class SpecialVisualizations { | ||||||
|                 funcName: "all_tags", |                 funcName: "all_tags", | ||||||
|                 docs: "Prints all key-value pairs of the object - used for debugging", |                 docs: "Prints all key-value pairs of the object - used for debugging", | ||||||
|                 args: [], |                 args: [], | ||||||
|  |                 needsUrls: [], | ||||||
|                 constr: (state, tags: UIEventSource<any>) => |                 constr: (state, tags: UIEventSource<any>) => | ||||||
|                     new SvelteUIElement(AllTagsPanel, { tags, state }), |                     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 ", |                         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) => { |                 constr: (state, tags, args) => { | ||||||
|                     let imagePrefixes: string[] = undefined |                     let imagePrefixes: string[] = undefined | ||||||
|                     if (args.length > 0) { |                     if (args.length > 0) { | ||||||
|  | @ -605,27 +625,32 @@ export default class SpecialVisualizations { | ||||||
|             { |             { | ||||||
|                 funcName: "image_upload", |                 funcName: "image_upload", | ||||||
|                 docs: "Creates a button where a user can upload an image to IMGUR", |                 docs: "Creates a button where a user can upload an image to IMGUR", | ||||||
|  |                 needsUrls: [Imgur.apiUrl], | ||||||
|                 args: [ |                 args: [ | ||||||
|                     { |                     { | ||||||
|                         name: "image-key", |                         name: "image-key", | ||||||
|                         doc: "Image tag to add the URL to (or image-tag:0, image-tag:1 when multiple images are added)", |                         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", |                         name: "label", | ||||||
|                         doc: "The text to show on the button", |                         doc: "The text to show on the button", | ||||||
|                         required: false |                         required: false, | ||||||
|                     }, |                     }, | ||||||
|                 ], |                 ], | ||||||
|                 constr: (state, tags, args) => { |                 constr: (state, tags, args) => { | ||||||
|                     return new SvelteUIElement(UploadImage, { |                     return new SvelteUIElement(UploadImage, { | ||||||
|                         state,tags, labelText: args[1], image: args[0] |                         state, | ||||||
|  |                         tags, | ||||||
|  |                         labelText: args[1], | ||||||
|  |                         image: args[0], | ||||||
|                     }) |                     }) | ||||||
|                 }, |                 }, | ||||||
|             }, |             }, | ||||||
|             { |             { | ||||||
|                 funcName: "reviews", |                 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", |                 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: |                 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", |                     "`{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: [ |                 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__", |                         doc: "Remove this string from the end of the value before parsing. __Note: use `&RPARENs` to indicate `)` if needed__", | ||||||
|                     }, |                     }, | ||||||
|                 ], |                 ], | ||||||
|  |                 needsUrls: [], | ||||||
|                 example: |                 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)}`", |                     "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) => { |                 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", |                 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. ", |                 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: |                 example: | ||||||
|                     "If the object has `length=42`, then `{canonical(length)}` will be shown as **42 meter** (in english), **42 metre** (in french), ...", |                     "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", |                 funcName: "export_as_geojson", | ||||||
|                 docs: "Exports the selected feature as GeoJson-file", |                 docs: "Exports the selected feature as GeoJson-file", | ||||||
|                 args: [], |                 args: [], | ||||||
|  |                 needsUrls: [], | ||||||
|                 constr: (state, tagSource, tagsSource, feature, layer) => { |                 constr: (state, tagSource, tagsSource, feature, layer) => { | ||||||
|                     const t = Translations.t.general.download |                     const t = Translations.t.general.download | ||||||
| 
 | 
 | ||||||
|  | @ -786,6 +784,7 @@ export default class SpecialVisualizations { | ||||||
|                 funcName: "open_in_iD", |                 funcName: "open_in_iD", | ||||||
|                 docs: "Opens the current view in the iD-editor", |                 docs: "Opens the current view in the iD-editor", | ||||||
|                 args: [], |                 args: [], | ||||||
|  |                 needsUrls: [], | ||||||
|                 constr: (state, feature) => { |                 constr: (state, feature) => { | ||||||
|                     return new SvelteUIElement(OpenIdEditor, { |                     return new SvelteUIElement(OpenIdEditor, { | ||||||
|                         mapProperties: state.mapProperties, |                         mapProperties: state.mapProperties, | ||||||
|  | @ -797,6 +796,8 @@ export default class SpecialVisualizations { | ||||||
|                 funcName: "open_in_josm", |                 funcName: "open_in_josm", | ||||||
|                 docs: "Opens the current view in the JOSM-editor", |                 docs: "Opens the current view in the JOSM-editor", | ||||||
|                 args: [], |                 args: [], | ||||||
|  |                 needsUrls: OpenJosm.needsUrls, | ||||||
|  | 
 | ||||||
|                 constr: (state) => { |                 constr: (state) => { | ||||||
|                     return new OpenJosm(state.osmConnection, state.mapProperties.bounds) |                     return new OpenJosm(state.osmConnection, state.mapProperties.bounds) | ||||||
|                 }, |                 }, | ||||||
|  | @ -805,6 +806,7 @@ export default class SpecialVisualizations { | ||||||
|                 funcName: "clear_location_history", |                 funcName: "clear_location_history", | ||||||
|                 docs: "A button to remove the travelled track information from the device", |                 docs: "A button to remove the travelled track information from the device", | ||||||
|                 args: [], |                 args: [], | ||||||
|  |                 needsUrls: [], | ||||||
|                 constr: (state) => { |                 constr: (state) => { | ||||||
|                     return new SubtleButton( |                     return new SubtleButton( | ||||||
|                         Svg.delete_icon_svg().SetStyle("height: 1.5rem"), |                         Svg.delete_icon_svg().SetStyle("height: 1.5rem"), | ||||||
|  | @ -830,6 +832,7 @@ export default class SpecialVisualizations { | ||||||
|                         defaultValue: "0", |                         defaultValue: "0", | ||||||
|                     }, |                     }, | ||||||
|                 ], |                 ], | ||||||
|  |                 needsUrls: [Constants.osmAuthConfig.url], | ||||||
|                 constr: (state, tags, args) => |                 constr: (state, tags, args) => | ||||||
|                     new VariableUiElement( |                     new VariableUiElement( | ||||||
|                         tags |                         tags | ||||||
|  | @ -858,16 +861,18 @@ export default class SpecialVisualizations { | ||||||
|                         defaultValue: "id", |                         defaultValue: "id", | ||||||
|                     }, |                     }, | ||||||
|                 ], |                 ], | ||||||
|  |                 needsUrls: [Imgur.apiUrl], | ||||||
|                 constr: (state, tags, args) => { |                 constr: (state, tags, args) => { | ||||||
|                     const id = tags.data[args[0] ?? "id"] |                     const id = tags.data[args[0] ?? "id"] | ||||||
|                     tags = state.featureProperties.getStore(id) |                     tags = state.featureProperties.getStore(id) | ||||||
|                     console.log("Id is", id) |                     console.log("Id is", id) | ||||||
|                     return new SvelteUIElement(UploadImage, {state, tags}) |                     return new SvelteUIElement(UploadImage, { state, tags }) | ||||||
|                     } |                 }, | ||||||
|             }, |             }, | ||||||
|             { |             { | ||||||
|                 funcName: "title", |                 funcName: "title", | ||||||
|                 args: [], |                 args: [], | ||||||
|  |                 needsUrls: [], | ||||||
|                 docs: "Shows the title of the popup. Useful for some cases, e.g. 'What is phone number of {title()}?'", |                 docs: "Shows the title of the popup. Useful for some cases, e.g. 'What is phone number of {title()}?'", | ||||||
|                 example: |                 example: | ||||||
|                     "`What is the phone number of {title()}`, which might automatically become `What is the phone number of XYZ`.", |                     "`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", |                 funcName: "maproulette_task", | ||||||
|                 args: [], |                 args: [], | ||||||
|  |                 needsUrls: [Maproulette.defaultEndpoint], | ||||||
|                 constr(state, tagSource) { |                 constr(state, tagSource) { | ||||||
|                     let parentId = tagSource.data.mr_challengeId |                     let parentId = tagSource.data.mr_challengeId | ||||||
|                     if (parentId === undefined) { |                     if (parentId === undefined) { | ||||||
|  | @ -931,6 +937,7 @@ export default class SpecialVisualizations { | ||||||
|             { |             { | ||||||
|                 funcName: "maproulette_set_status", |                 funcName: "maproulette_set_status", | ||||||
|                 docs: "Change the status of the given MapRoulette task", |                 docs: "Change the status of the given MapRoulette task", | ||||||
|  |                 needsUrls: [Maproulette.defaultEndpoint], | ||||||
|                 example: |                 example: | ||||||
|                     " The following example sets the status to '2' (false positive)\n" + |                     " The following example sets the status to '2' (false positive)\n" + | ||||||
|                     "\n" + |                     "\n" + | ||||||
|  | @ -1054,6 +1061,7 @@ export default class SpecialVisualizations { | ||||||
|                 funcName: "statistics", |                 funcName: "statistics", | ||||||
|                 docs: "Show general statistics about the elements currently in view. Intended to use on the `current_view`-layer", |                 docs: "Show general statistics about the elements currently in view. Intended to use on the `current_view`-layer", | ||||||
|                 args: [], |                 args: [], | ||||||
|  |                 needsUrls: [], | ||||||
|                 constr: (state) => { |                 constr: (state) => { | ||||||
|                     return new Combine( |                     return new Combine( | ||||||
|                         state.layout.layers |                         state.layout.layers | ||||||
|  | @ -1096,6 +1104,8 @@ export default class SpecialVisualizations { | ||||||
|                         required: true, |                         required: true, | ||||||
|                     }, |                     }, | ||||||
|                 ], |                 ], | ||||||
|  |                 needsUrls: [], | ||||||
|  | 
 | ||||||
|                 constr(__, tags, args) { |                 constr(__, tags, args) { | ||||||
|                     return new SvelteUIElement(SendEmail, { args, tags }) |                     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", |                         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( |                 constr( | ||||||
|                     state: SpecialVisualizationState, |                     state: SpecialVisualizationState, | ||||||
|                     tagSource: UIEventSource<Record<string, string>>, |                     tagSource: UIEventSource<Record<string, string>>, | ||||||
|  | @ -1144,6 +1155,7 @@ export default class SpecialVisualizations { | ||||||
|             { |             { | ||||||
|                 funcName: "multi", |                 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", |                 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: |                 example: | ||||||
|                     "```json\n" + |                     "```json\n" + | ||||||
|                     JSON.stringify( |                     JSON.stringify( | ||||||
|  | @ -1204,6 +1216,7 @@ export default class SpecialVisualizations { | ||||||
|                         required: true, |                         required: true, | ||||||
|                     }, |                     }, | ||||||
|                 ], |                 ], | ||||||
|  |                 needsUrls: [], | ||||||
|                 constr( |                 constr( | ||||||
|                     state: SpecialVisualizationState, |                     state: SpecialVisualizationState, | ||||||
|                     tagSource: UIEventSource<Record<string, string>>, |                     tagSource: UIEventSource<Record<string, string>>, | ||||||
|  |  | ||||||
|  | @ -37,6 +37,7 @@ export class SubstitutedTranslation extends VariableUiElement { | ||||||
|                 constr: typeof value === "function" ? value : () => value, |                 constr: typeof value === "function" ? value : () => value, | ||||||
|                 docs: "Dynamically injected input element", |                 docs: "Dynamically injected input element", | ||||||
|                 args: [], |                 args: [], | ||||||
|  |                 needsUrls: [], | ||||||
|                 example: "", |                 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 ThemeViewGUI from "./src/UI/ThemeViewGUI.svelte" | ||||||
| import LayoutConfig from "./src/Models/ThemeConfig/LayoutConfig"; | import LayoutConfig from "./src/Models/ThemeConfig/LayoutConfig"; | ||||||
| import MetaTagging from "./src/Logic/MetaTagging"; | import MetaTagging from "./src/Logic/MetaTagging"; | ||||||
|  | import { FixedUiElement } from "./src/UI/Base/FixedUiElement"; | ||||||
| 
 | 
 | ||||||
| function webgl_support() { | function webgl_support() { | ||||||
|     try { |     try { | ||||||
|  |  | ||||||
							
								
								
									
										57
									
								
								theme.html
									
										
									
									
									
								
							
							
						
						
									
										57
									
								
								theme.html
									
										
									
									
									
								
							|  | @ -4,7 +4,7 @@ | ||||||
| <head> | <head> | ||||||
|     <meta charset="UTF-8"> |     <meta charset="UTF-8"> | ||||||
|     <meta content="width=device-width, initial-scale=1.0, user-scalable=no" name="viewport"> |     <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/mobile.css" rel="stylesheet"/> | ||||||
|     <link href="./css/openinghourstable.css" rel="stylesheet"/> |     <link href="./css/openinghourstable.css" rel="stylesheet"/> | ||||||
|     <link href="./css/tagrendering.css" rel="stylesheet"/> |     <link href="./css/tagrendering.css" rel="stylesheet"/> | ||||||
|  | @ -65,57 +65,12 @@ | ||||||
|     </div> |     </div> | ||||||
| </div> | </div> | ||||||
| <div id="belowmap" class="absolute top-0 left-0 -z-10">Below</div> | <div id="belowmap" class="absolute top-0 left-0 -z-10">Below</div> | ||||||
|  | <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> |      | ||||||
| 
 |  | ||||||
|     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 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> | </body> | ||||||
| </html> | </html> | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue