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