forked from MapComplete/MapComplete
		
	UX: add proper error message when loading an image fails + add detection for strict tracking protection to show an error message for that; fix #2408
This commit is contained in:
		
							parent
							
								
									6f66556cf3
								
							
						
					
					
						commit
						45c0f1a8d6
					
				
					 6 changed files with 77 additions and 13 deletions
				
			
		|  | @ -561,6 +561,8 @@ | ||||||
|         "addPicture": "Take a picture", |         "addPicture": "Take a picture", | ||||||
|         "doDelete": "Remove image", |         "doDelete": "Remove image", | ||||||
|         "isDeleted": "Deleted", |         "isDeleted": "Deleted", | ||||||
|  |         "loadingFailed": "Loading this image failed", | ||||||
|  |         "mapillaryTrackingProtection": "Strict tracking protection blocks loading images from Mapillary, as Mapillary is owned by Facebook/Meta. Disable strict tracking protection if you want to see this image.", | ||||||
|         "nearby": { |         "nearby": { | ||||||
|             "close": "Collapse panel with nearby images", |             "close": "Collapse panel with nearby images", | ||||||
|             "failed": "Fetching images from {service} failed", |             "failed": "Fetching images from {service} failed", | ||||||
|  | @ -590,8 +592,11 @@ | ||||||
|         }, |         }, | ||||||
|         "pleaseLogin": "Please log in to add a picture", |         "pleaseLogin": "Please log in to add a picture", | ||||||
|         "processing": "The server is processing your image", |         "processing": "The server is processing your image", | ||||||
|  |         "reported": "This image is reported and might contain harmful content", | ||||||
|         "respectPrivacy": "Do not upload from Google Maps, Google Streetview or other copyrighted sources.", |         "respectPrivacy": "Do not upload from Google Maps, Google Streetview or other copyrighted sources.", | ||||||
|         "selectFile": "Select a picture from your device", |         "selectFile": "Select a picture from your device", | ||||||
|  |         "showAnyway": "Show picture anyway", | ||||||
|  |         "strictProtectionDetected": "Strict tracking protection (or another content blocker) was detected and might have blocked access to this image.", | ||||||
|         "toBig": "Your image is too large as it is {actual_size}. Please use images of at most {max_size}", |         "toBig": "Your image is too large as it is {actual_size}. Please use images of at most {max_size}", | ||||||
|         "unlink": { |         "unlink": { | ||||||
|             "button": "Unlink picture", |             "button": "Unlink picture", | ||||||
|  |  | ||||||
|  | @ -5513,7 +5513,7 @@ h2.group { | ||||||
|   border-radius: 1em; |   border-radius: 1em; | ||||||
|   margin: 0.25em; |   margin: 0.25em; | ||||||
|   text-align: center; |   text-align: center; | ||||||
|   padding: 0.15em 0.3em; |   padding: 0.15em 0.7em; | ||||||
|   border: 2px dotted #ff9143; |   border: 2px dotted #ff9143; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -6,6 +6,7 @@ import Constants from "../../Models/Constants" | ||||||
| import SvelteUIElement from "../../UI/Base/SvelteUIElement" | import SvelteUIElement from "../../UI/Base/SvelteUIElement" | ||||||
| import MapillaryIcon from "./MapillaryIcon.svelte" | import MapillaryIcon from "./MapillaryIcon.svelte" | ||||||
| import { Feature, Point } from "geojson" | import { Feature, Point } from "geojson" | ||||||
|  | import { Store, UIEventSource } from "../UIEventSource" | ||||||
| 
 | 
 | ||||||
| export class Mapillary extends ImageProvider { | export class Mapillary extends ImageProvider { | ||||||
|     public static readonly singleton = new Mapillary() |     public static readonly singleton = new Mapillary() | ||||||
|  | @ -257,4 +258,34 @@ export class Mapillary extends ImageProvider { | ||||||
|         } |         } | ||||||
|         return Mapillary.createLink(location, 17, image.id) |         return Mapillary.createLink(location, 17, image.id) | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Returns true if we are in firefox strict mode (or if we are offline) | ||||||
|  |      * @private | ||||||
|  |      */ | ||||||
|  |     private static async checkStrictMode(): Promise<boolean> { | ||||||
|  |         try { | ||||||
|  |             const result = await fetch("https://scontent-bru2-1.xx.fbcdn.net/m1/v/t6/Xn8-ISUUYQyBD9FyACzPFRGZnBJRqIFmnQ_yd7FU6vxFYwD21fvAcZwDQoMzsScxcQyCWeBviKpWO4nX8yf--neJDvVjC4JlQtfBYb6TrpXQTniyafSFeZeePT_NVx3H6gMjceEvXHyvBqOOcCB_xQ?stp=c2048.2048.2000.988a_s1000x1000&_nc_gid=E2oHnrAtHutVvjaIm9qDLg&_nc_oc=AdkcScR9HuKt1X_K5-GrUeR5Paj8d7MsNFFYEBSmgc0IiBey_wS3RiNJpflWIKaQzNE&ccb=10-5&oh=00_AfNJ1Ki1IeGdUMxdFUc3ZX9VYIVFxVfXZ9MUATU3vj_RJw&oe=686AF002&_nc_sid=201bca") | ||||||
|  |             console.log("Not blocked, got a forbidden", result.status) | ||||||
|  |             return false | ||||||
|  |         } catch (e) { | ||||||
|  |             console.log("Mapillary blocked - probably a scriptblocker") | ||||||
|  |             return true | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private static _isInStrictMode: UIEventSource<boolean> = undefined | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Creates a store which contains true if strict tracking protection is probably enabled. | ||||||
|  |      * This will attempt to fetch a bit of content from mapillary - as that is probably the main culprit | ||||||
|  |      * Loads lazy, so will only attempt to fetch the _first time_ this method is called | ||||||
|  |      */ | ||||||
|  |     public static isInStrictMode(): Store<boolean> { | ||||||
|  |         if (this._isInStrictMode === undefined) { | ||||||
|  |             this._isInStrictMode = UIEventSource.FromPromise(this.checkStrictMode()) | ||||||
|  |         } | ||||||
|  |         return this._isInStrictMode | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <div class="relative h-80 w-60"> | <div class="relative h-80 w-60"> | ||||||
|   <div class="h-full w-full animate-pulse bg-gray-400" /> |   <div class="h-full w-full animate-pulse interactive" /> | ||||||
|   <div class="absolute top-0 flex h-full w-full items-center justify-center"> |   <div class="absolute top-0 flex h-full w-full items-center justify-center"> | ||||||
|     <Loading /> |     <Loading /> | ||||||
|   </div> |   </div> | ||||||
|  |  | ||||||
|  | @ -23,6 +23,7 @@ | ||||||
|   import ThemeViewState from "../../Models/ThemeViewState" |   import ThemeViewState from "../../Models/ThemeViewState" | ||||||
|   import Panorama360 from "../../assets/svg/Panorama360.svelte" |   import Panorama360 from "../../assets/svg/Panorama360.svelte" | ||||||
|   import { ExternalLinkIcon } from "@rgossiaux/svelte-heroicons/solid" |   import { ExternalLinkIcon } from "@rgossiaux/svelte-heroicons/solid" | ||||||
|  |   import { ExclamationTriangle as TriangleOutline } from "@babeard/svelte-heroicons/outline/ExclamationTriangle" | ||||||
| 
 | 
 | ||||||
|   export let image: Partial<ProvidedImage> & { id: string; url: string } |   export let image: Partial<ProvidedImage> & { id: string; url: string } | ||||||
|   let fallbackImage: string = undefined |   let fallbackImage: string = undefined | ||||||
|  | @ -41,21 +42,32 @@ | ||||||
|     | Store<Feature<Geometry, HotspotProperties>[]> = [] |     | Store<Feature<Geometry, HotspotProperties>[]> = [] | ||||||
| 
 | 
 | ||||||
|   let loaded = false |   let loaded = false | ||||||
|  |   let error = false | ||||||
|  |   let ignoreHidden = false | ||||||
|  |   let isInStrictMode = new UIEventSource(false) | ||||||
|  | 
 | ||||||
|  |   function onError() { | ||||||
|  |     error = true | ||||||
|  |     Mapillary.isInStrictMode().addCallbackAndRunD(isStrict => { | ||||||
|  |       isInStrictMode.set(isStrict) | ||||||
|  |       return true // unregister | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|   let visitUrl = image.provider?.visitUrl(image) |   let visitUrl = image.provider?.visitUrl(image) | ||||||
|   let showBigPreview = new UIEventSource(false) |   let showBigPreview = new UIEventSource(false) | ||||||
|   onDestroy( |   onDestroy( | ||||||
|     showBigPreview.addCallbackAndRun((shown) => { |     showBigPreview.addCallbackAndRun((shown) => { | ||||||
|       state?.guistate?.setPreviewedImage(shown ? image : undefined) |       state?.guistate?.setPreviewedImage(shown ? image : undefined) | ||||||
|     }) |     }), | ||||||
|   ) |   ) | ||||||
|   if (previewedImage) { |   if (previewedImage) { | ||||||
|     onDestroy( |     onDestroy( | ||||||
|       previewedImage.addCallbackAndRun((previewedImage) => { |       previewedImage.addCallbackAndRun((previewedImage) => { | ||||||
|         showBigPreview.set( |         showBigPreview.set( | ||||||
|           previewedImage !== undefined && |           previewedImage !== undefined && | ||||||
|             (previewedImage?.id ?? previewedImage?.url) === (image.id ?? image.url) |           (previewedImage?.id ?? previewedImage?.url) === (image.id ?? image.url), | ||||||
|         ) |         ) | ||||||
|       }) |       }), | ||||||
|     ) |     ) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | @ -98,14 +110,26 @@ | ||||||
|     /> |     /> | ||||||
|   </div> |   </div> | ||||||
| </Popup> | </Popup> | ||||||
| 
 | {#if error} | ||||||
| {#if image.status !== undefined && image.status !== "ready" && image.status !== "hidden"} |   <div class="h-80 w-60 interactive flex flex-col justify-center items-center p-4 text-center"> | ||||||
|  |     <div class="alert flex items-center"> | ||||||
|  |       <TriangleOutline class="shrink-0 h-8 w-8" /> | ||||||
|  |       <Tr t={Translations.t.image.loadingFailed}/> | ||||||
|  |     </div> | ||||||
|  |     {#if image.provider.name.toLowerCase() === "mapillary" && $isInStrictMode} | ||||||
|  |       <Tr t={Translations.t.image.mapillaryTrackingProtection}/> | ||||||
|  |     {:else if $isInStrictMode} | ||||||
|  |       <Tr t={Translations.t.image.strictProtectionDetected}/> | ||||||
|  |       <div class="subtle text-sm mt-8">{image.url}</div> | ||||||
|  |     {/if} | ||||||
|  |   </div> | ||||||
|  | {:else if image.status !== undefined && image.status !== "ready" && image.status !== "hidden"} | ||||||
|   <div class="flex h-full flex-col justify-center p-4"> |   <div class="flex h-full flex-col justify-center p-4"> | ||||||
|     <Loading> |     <Loading> | ||||||
|       <Tr t={Translations.t.image.processing} /> |       <Tr t={Translations.t.image.processing} /> | ||||||
|     </Loading> |     </Loading> | ||||||
|   </div> |   </div> | ||||||
| {:else if image.status !== "hidden"} | {:else if image.status !== "hidden" || ignoreHidden} | ||||||
|   <div class="relative shrink-0"> |   <div class="relative shrink-0"> | ||||||
|     <div |     <div | ||||||
|       class={"relative w-fit"} |       class={"relative w-fit"} | ||||||
|  | @ -136,9 +160,7 @@ | ||||||
|           previewedImage?.set(image) |           previewedImage?.set(image) | ||||||
|         }} |         }} | ||||||
|         on:error={() => { |         on:error={() => { | ||||||
|           if (fallbackImage) { |           onError() | ||||||
|             imgEl.src = fallbackImage |  | ||||||
|           } |  | ||||||
|         }} |         }} | ||||||
|         src={image.url} |         src={image.url} | ||||||
|       /> |       /> | ||||||
|  | @ -169,5 +191,11 @@ | ||||||
|     </div> |     </div> | ||||||
|   </div> |   </div> | ||||||
| {:else if image.status === "hidden"} | {:else if image.status === "hidden"} | ||||||
|   <div class="subtle">This image has been reported</div> |   <div class="h-80 w-60 flex flex-col justify-center items-center break-words p-4 text-center"> | ||||||
|  |     <TriangleOutline class="w-8 h-8 subtle" /> | ||||||
|  |     <Tr t={Translations.t.image.reported} /> | ||||||
|  |     <button class="text-sm" on:click={() => ignoreHidden = true}> | ||||||
|  |       <Tr t={Translations.t.image.showAnyway} /> | ||||||
|  |     </button> | ||||||
|  |   </div> | ||||||
| {/if} | {/if} | ||||||
|  |  | ||||||
|  | @ -400,7 +400,7 @@ h2.group { | ||||||
|     border-radius: 1em; |     border-radius: 1em; | ||||||
|     margin: 0.25em; |     margin: 0.25em; | ||||||
|     text-align: center; |     text-align: center; | ||||||
|     padding: 0.15em 0.3em; |     padding: 0.15em 0.7em; | ||||||
|     border: 2px dotted #ff9143; |     border: 2px dotted #ff9143; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue