From 45c0f1a8d68673b6cef8b4dc40a398826d46672c Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sat, 7 Jun 2025 01:56:15 +0200 Subject: [PATCH] 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 --- langs/en.json | 5 +++ public/css/index-tailwind-output.css | 2 +- src/Logic/ImageProviders/Mapillary.ts | 31 +++++++++++++++++ src/UI/Base/LoadingPlaceholder.svelte | 2 +- src/UI/Image/AttributedImage.svelte | 48 +++++++++++++++++++++------ src/index.css | 2 +- 6 files changed, 77 insertions(+), 13 deletions(-) diff --git a/langs/en.json b/langs/en.json index 3258dd2ea..3a016d903 100644 --- a/langs/en.json +++ b/langs/en.json @@ -561,6 +561,8 @@ "addPicture": "Take a picture", "doDelete": "Remove image", "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": { "close": "Collapse panel with nearby images", "failed": "Fetching images from {service} failed", @@ -590,8 +592,11 @@ }, "pleaseLogin": "Please log in to add a picture", "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.", "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}", "unlink": { "button": "Unlink picture", diff --git a/public/css/index-tailwind-output.css b/public/css/index-tailwind-output.css index 0c72f3f4d..f0eababf8 100644 --- a/public/css/index-tailwind-output.css +++ b/public/css/index-tailwind-output.css @@ -5513,7 +5513,7 @@ h2.group { border-radius: 1em; margin: 0.25em; text-align: center; - padding: 0.15em 0.3em; + padding: 0.15em 0.7em; border: 2px dotted #ff9143; } diff --git a/src/Logic/ImageProviders/Mapillary.ts b/src/Logic/ImageProviders/Mapillary.ts index 44b95ec55..17141f63a 100644 --- a/src/Logic/ImageProviders/Mapillary.ts +++ b/src/Logic/ImageProviders/Mapillary.ts @@ -6,6 +6,7 @@ import Constants from "../../Models/Constants" import SvelteUIElement from "../../UI/Base/SvelteUIElement" import MapillaryIcon from "./MapillaryIcon.svelte" import { Feature, Point } from "geojson" +import { Store, UIEventSource } from "../UIEventSource" export class Mapillary extends ImageProvider { public static readonly singleton = new Mapillary() @@ -257,4 +258,34 @@ export class Mapillary extends ImageProvider { } 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 { + 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 = 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 { + if (this._isInStrictMode === undefined) { + this._isInStrictMode = UIEventSource.FromPromise(this.checkStrictMode()) + } + return this._isInStrictMode + } } diff --git a/src/UI/Base/LoadingPlaceholder.svelte b/src/UI/Base/LoadingPlaceholder.svelte index c5fabb3a9..c2a97d8b3 100644 --- a/src/UI/Base/LoadingPlaceholder.svelte +++ b/src/UI/Base/LoadingPlaceholder.svelte @@ -3,7 +3,7 @@
-
+
diff --git a/src/UI/Image/AttributedImage.svelte b/src/UI/Image/AttributedImage.svelte index 14764591c..03be2c9c8 100644 --- a/src/UI/Image/AttributedImage.svelte +++ b/src/UI/Image/AttributedImage.svelte @@ -23,6 +23,7 @@ import ThemeViewState from "../../Models/ThemeViewState" import Panorama360 from "../../assets/svg/Panorama360.svelte" import { ExternalLinkIcon } from "@rgossiaux/svelte-heroicons/solid" + import { ExclamationTriangle as TriangleOutline } from "@babeard/svelte-heroicons/outline/ExclamationTriangle" export let image: Partial & { id: string; url: string } let fallbackImage: string = undefined @@ -41,21 +42,32 @@ | Store[]> = [] 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 showBigPreview = new UIEventSource(false) onDestroy( showBigPreview.addCallbackAndRun((shown) => { state?.guistate?.setPreviewedImage(shown ? image : undefined) - }) + }), ) if (previewedImage) { onDestroy( previewedImage.addCallbackAndRun((previewedImage) => { showBigPreview.set( previewedImage !== undefined && - (previewedImage?.id ?? previewedImage?.url) === (image.id ?? image.url) + (previewedImage?.id ?? previewedImage?.url) === (image.id ?? image.url), ) - }) + }), ) } @@ -98,14 +110,26 @@ />
- -{#if image.status !== undefined && image.status !== "ready" && image.status !== "hidden"} +{#if error} +
+
+ + +
+ {#if image.provider.name.toLowerCase() === "mapillary" && $isInStrictMode} + + {:else if $isInStrictMode} + +
{image.url}
+ {/if} +
+{:else if image.status !== undefined && image.status !== "ready" && image.status !== "hidden"}
-{:else if image.status !== "hidden"} +{:else if image.status !== "hidden" || ignoreHidden}
{ - if (fallbackImage) { - imgEl.src = fallbackImage - } + onError() }} src={image.url} /> @@ -169,5 +191,11 @@
{:else if image.status === "hidden"} -
This image has been reported
+
+ + + +
{/if} diff --git a/src/index.css b/src/index.css index 6e45bfe99..043c0f974 100644 --- a/src/index.css +++ b/src/index.css @@ -400,7 +400,7 @@ h2.group { border-radius: 1em; margin: 0.25em; text-align: center; - padding: 0.15em 0.3em; + padding: 0.15em 0.7em; border: 2px dotted #ff9143; }