diff --git a/src/Logic/ImageProviders/GenericImageProvider.ts b/src/Logic/ImageProviders/GenericImageProvider.ts index ee6060eee0..46156d826d 100644 --- a/src/Logic/ImageProviders/GenericImageProvider.ts +++ b/src/Logic/ImageProviders/GenericImageProvider.ts @@ -34,6 +34,9 @@ export default class GenericImageProvider extends ImageProvider { provider: this, id: value, isSpherical: undefined, + originalAttribute: { + key, value + } }, ] } diff --git a/src/Logic/ImageProviders/ImageProvider.ts b/src/Logic/ImageProviders/ImageProvider.ts index d3d14b705e..f6ce79043d 100644 --- a/src/Logic/ImageProviders/ImageProvider.ts +++ b/src/Logic/ImageProviders/ImageProvider.ts @@ -26,6 +26,7 @@ export interface ProvidedImage { host?: string isSpherical: boolean license?: LicenseInfo + originalAttribute?: {key: string, value: string} } export interface PanoramaView { diff --git a/src/Logic/ImageProviders/Imgur.ts b/src/Logic/ImageProviders/Imgur.ts index f7239b2cc3..7ec61812f2 100644 --- a/src/Logic/ImageProviders/Imgur.ts +++ b/src/Logic/ImageProviders/Imgur.ts @@ -33,6 +33,7 @@ export class Imgur extends ImageProvider { provider: this, id: value, isSpherical: false, + originalAttribute: {key, value} }, ] } diff --git a/src/Logic/ImageProviders/Mapillary.ts b/src/Logic/ImageProviders/Mapillary.ts index 17141f63a3..1a0d644097 100644 --- a/src/Logic/ImageProviders/Mapillary.ts +++ b/src/Logic/ImageProviders/Mapillary.ts @@ -170,8 +170,7 @@ export class Mapillary extends ImageProvider { properties: { url: response.thumb_2048_url, northOffset: response.computed_compass_angle, - provider: this, - imageMeta: image + provider: this }, } } @@ -246,6 +245,7 @@ export class Mapillary extends ImageProvider { response.camera_type === "spherical" || response.camera_type === "equirectangular", lat: geometry.coordinates[1], lon: geometry.coordinates[0], + originalAttribute: {key, value} } } diff --git a/src/Logic/ImageProviders/Panoramax.ts b/src/Logic/ImageProviders/Panoramax.ts index 8227c78d02..dc4b4bf436 100644 --- a/src/Logic/ImageProviders/Panoramax.ts +++ b/src/Logic/ImageProviders/Panoramax.ts @@ -174,6 +174,7 @@ export default class PanoramaxImageProvider extends ImageProvider { } const providedImage = await this.getInfo(value) providedImage.alt_id = alt_id + providedImage.originalAttribute = {key, value} return [providedImage] } diff --git a/src/Logic/ImageProviders/WikidataImageProvider.ts b/src/Logic/ImageProviders/WikidataImageProvider.ts index 87f1a0cb7e..baecf8c15c 100644 --- a/src/Logic/ImageProviders/WikidataImageProvider.ts +++ b/src/Logic/ImageProviders/WikidataImageProvider.ts @@ -61,7 +61,11 @@ export class WikidataImageProvider extends ImageProvider { allImages.push(promises) } const resolved = await Promise.all(Utils.NoNull(allImages)) - return [].concat(...resolved) + const flattened = resolved.flatMap( x => x) + if(flattened.length === 1){ + flattened[0].originalAttribute = {key, value} + } + return flattened } public DownloadAttribution(): Promise { diff --git a/src/Logic/ImageProviders/WikimediaImageProvider.ts b/src/Logic/ImageProviders/WikimediaImageProvider.ts index 5d247f1ff0..0c2759f8c8 100644 --- a/src/Logic/ImageProviders/WikimediaImageProvider.ts +++ b/src/Logic/ImageProviders/WikimediaImageProvider.ts @@ -145,14 +145,14 @@ export class WikimediaImageProvider extends ImageProvider { .map((image) => this.UrlForImage(image)) } if (value.startsWith("File:")) { - return [this.UrlForImage(value)] + return [this.UrlForImage(value, key, value)] } if (value.startsWith("http")) { // Probably an error return undefined } // We do a last effort and assume this is a file - return [this.UrlForImage("File:" + value)] + return [this.UrlForImage("File:" + value, key, value)] } public async DownloadAttribution(img: { id: string }): Promise { @@ -211,9 +211,9 @@ export class WikimediaImageProvider extends ImageProvider { return licenseInfo } - private UrlForImage(image: string): ProvidedImage { + private UrlForImage(image: string, key?: string, value?: string): ProvidedImage { image = "File:" + WikimediaImageProvider.makeCanonical(image) - return { + const providedImage: ProvidedImage = { url: WikimediaImageProvider.PrepareUrl(image), url_hd: WikimediaImageProvider.PrepareUrl(image, true), key: undefined, @@ -221,6 +221,10 @@ export class WikimediaImageProvider extends ImageProvider { id: image, isSpherical: false, } + if(key && value){ + providedImage.originalAttribute = {key, value} + } + return providedImage } getPanoramaInfo(): Promise> | undefined { diff --git a/src/Logic/State/UserRelatedState.ts b/src/Logic/State/UserRelatedState.ts index 1508bea92d..80b88fd58f 100644 --- a/src/Logic/State/UserRelatedState.ts +++ b/src/Logic/State/UserRelatedState.ts @@ -200,7 +200,6 @@ export default class UserRelatedState { public static readonly usersettingsConfig = UserRelatedState.initUserSettingsState() public static readonly availableUserSettingsIds: string[] = UserRelatedState.usersettingsConfig?.tagRenderings?.map((tr) => tr.id) ?? [] - public static readonly SHOW_TAGS_VALUES = ["always", "yes", "full"] as const /** The user credentials */ @@ -212,6 +211,7 @@ export default class UserRelatedState { public readonly installedUserThemes: Store public readonly showAllQuestionsAtOnce: UIEventSource public readonly showTags: UIEventSource<"no" | undefined | "always" | "yes" | "full"> + public readonly showTagsB: Store public readonly showCrosshair: UIEventSource<"yes" | "always" | "no" | undefined> public readonly translationMode: UIEventSource<"false" | "true" | "mobile" | undefined | string> @@ -269,6 +269,20 @@ export default class UserRelatedState { ) this.language = this.osmConnection.getPreference("language") this.showTags = this.osmConnection.getPreference("show_tags") + this.showTagsB = this.showTags.map(showTags => { + if (showTags === "always" || showTags === "full") { + return true + } + if (showTags === "no") { + return false + } + const userdetails = this.osmConnection.userDetails.data + if (!userdetails) { + return false + } + const csCount = userdetails.csCount + return csCount >= Constants.userJourney.tagsVisibleAt + }, [this.osmConnection.userDetails]) this.showCrosshair = this.osmConnection.getPreference("show_crosshair") this.fixateNorth = this.osmConnection.getPreference("fixate-north") this.morePrivacy = this.osmConnection.getPreference("more_privacy", { defaultValue: "no" }) diff --git a/src/UI/Base/LoginToggle.svelte b/src/UI/Base/LoginToggle.svelte index 661e8403c0..e3f751bbd7 100644 --- a/src/UI/Base/LoginToggle.svelte +++ b/src/UI/Base/LoginToggle.svelte @@ -22,7 +22,8 @@ */ export let silentFail: boolean = false /** - * If set and the OSM-api fails, do _not_ show any error messages nor the successful state, just hide + * If set and the OSM-api fails, do _not_ show any error messages nor the successful state, just hide. + * Will still show the "not-logged-in"-slot */ export let hiddenFail: boolean = false let loadingStatus = state?.osmConnection?.loadingStatus ?? new ImmutableStore("logged-in") diff --git a/src/UI/BigComponents/Filterview.svelte b/src/UI/BigComponents/Filterview.svelte index ba5c699662..46fe637c2c 100644 --- a/src/UI/BigComponents/Filterview.svelte +++ b/src/UI/BigComponents/Filterview.svelte @@ -24,14 +24,7 @@ let isDisplayed: UIEventSource = filteredLayer.isDisplayed let isDebugging = state?.featureSwitches?.featureSwitchIsDebugging ?? new ImmutableStore(false) - let showTags = state?.userRelatedState?.showTags?.map( - (s) => - (s === "yes" && - state?.userRelatedState?.osmConnection?.userDetails?.data?.csCount >= - Constants.userJourney.tagsVisibleAt) || - s === "always" || - s === "full" - ) + let showTags = state?.userRelatedState?.showTagsB /** * Gets a UIEventSource as boolean for the given option, to be used with a checkbox diff --git a/src/UI/Image/AttributedImage.svelte b/src/UI/Image/AttributedImage.svelte index 03be2c9c8d..0a6fb10a00 100644 --- a/src/UI/Image/AttributedImage.svelte +++ b/src/UI/Image/AttributedImage.svelte @@ -24,6 +24,7 @@ import Panorama360 from "../../assets/svg/Panorama360.svelte" import { ExternalLinkIcon } from "@rgossiaux/svelte-heroicons/solid" import { ExclamationTriangle as TriangleOutline } from "@babeard/svelte-heroicons/outline/ExclamationTriangle" + import LoginToggle from "../Base/LoginToggle.svelte" export let image: Partial & { id: string; url: string } let fallbackImage: string = undefined @@ -43,16 +44,39 @@ let loaded = false let error = false + let notFound = false let ignoreHidden = false let isInStrictMode = new UIEventSource(false) - function onError() { - error = true + async function detectErrorReason() { + try { + + const response = await fetch( + image.url, + { + headers: { + "Accept": "image/avif,image/webp,*/*", + }, + }, + ) + if (response.status === 404) { + notFound = true + } + } catch + (e) { + console.log("Could not load image while trying to remediate", e) + } + } + + async function onError() { Mapillary.isInStrictMode().addCallbackAndRunD(isStrict => { isInStrictMode.set(isStrict) return true // unregister }) + await detectErrorReason() + error = true } + let visitUrl = image.provider?.visitUrl(image) let showBigPreview = new UIEventSource(false) onDestroy( @@ -112,15 +136,25 @@ {#if error}
-
- - -
- {#if image.provider.name.toLowerCase() === "mapillary" && $isInStrictMode} - - {:else if $isInStrictMode} - -
{image.url}
+ {#if notFound} +
+ + Not found +
+ This image is probably incorrect or deleted. + + {:else} +
+ + +
+ {#if image.provider.name.toLowerCase() === "mapillary" && $isInStrictMode} + + {:else if $isInStrictMode} + + {image.provider.name} +
{image.url}
+ {/if} {/if}
{:else if image.status !== undefined && image.status !== "ready" && image.status !== "hidden"} diff --git a/src/UI/Image/DeletableImage.svelte b/src/UI/Image/DeletableImage.svelte index ce719de3b9..e2b0f114fa 100644 --- a/src/UI/Image/DeletableImage.svelte +++ b/src/UI/Image/DeletableImage.svelte @@ -39,6 +39,8 @@ let reportFreeText = new UIEventSource(undefined) let reported = new UIEventSource(false) + let canBeUnlinked = image.originalAttribute !== undefined + async function requestDeletion() { if (reportReason.data === "other" && !reportFreeText.data) { return @@ -63,31 +65,20 @@ } async function unlink() { - console.log("Unlinking image", image.key, image.id) - if (image.id.length < 10) { - console.error("Suspicious value, not deleting ", image.id) - return - } - // The "key" is the provider key, but not necessarely the actual key that should be reset - // We iterate over all tags. *Every* tag for which the value contains the id will be deleted - const tgs = tags.data - for (const key in tgs) { - if (typeof tgs[key] !== "string" || tgs[key].indexOf(image.id) < 0) { - continue - } - - await state?.changes?.applyAction( - new ChangeTagAction(tgs.id, new Tag(key, ""), tgs, { - changeType: "delete-image", - theme: state.theme.id, - }) - ) - } + const {key} = image.originalAttribute + await state?.changes?.applyAction( + new ChangeTagAction(tags.data.id, new Tag(key, ""), tags.data, { + changeType: "delete-image", + theme: state.theme.id, + }) + ) } const t = Translations.t.image.panoramax const tu = Translations.t.image.unlink const placeholder = t.placeholder.current + + let showTags = state.userRelatedState?.showTagsB @@ -169,10 +160,24 @@ - + {#if canBeUnlinked} + + {/if} + + + {#if canBeUnlinked} + + {#if $showTags} +
+ {image.originalAttribute.key}={image.originalAttribute.value} +
+ {/if} + {/if}
diff --git a/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte b/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte index f34f190fae..354898e8d3 100644 --- a/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte +++ b/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte @@ -16,9 +16,7 @@ import SubtleButton from "../../Base/SubtleButton.svelte" import TagRenderingMappingInput from "./TagRenderingMappingInput.svelte" import { Translation } from "../../i18n/Translation" - import Constants from "../../../Models/Constants" import { Unit } from "../../../Models/Unit" - import UserRelatedState from "../../../Logic/State/UserRelatedState" import { twJoin } from "tailwind-merge" import { TagUtils } from "../../../Logic/Tags/TagUtils" @@ -31,8 +29,8 @@ import { get } from "svelte/store" import Markdown from "../../Base/Markdown.svelte" import { Utils } from "../../../Utils" - import { TagTypes } from "../../../Logic/Tags/TagTypes" import type { UploadableTag } from "../../../Logic/Tags/TagTypes" + import { TagTypes } from "../../../Logic/Tags/TagTypes" import Popup from "../../Base/Popup.svelte" import If from "../../Base/If.svelte" @@ -315,8 +313,7 @@ let featureSwitchIsTesting = state?.featureSwitchIsTesting ?? new ImmutableStore(false) let featureSwitchIsDebugging = state?.featureSwitches?.featureSwitchIsDebugging ?? new ImmutableStore(false) - let showTags = state?.userRelatedState?.showTags ?? new ImmutableStore(undefined) - let numberOfCs = state?.osmConnection?.userDetails?.data?.csCount ?? 0 + let showTags : Store = state?.userRelatedState?.showTagsB ?? new ImmutableStore(false) let question = config.question let hideMappingsUnlessSearchedFor = config.mappings.length > 8 && config.mappings.some((m) => m.priorityIf !== undefined) @@ -324,14 +321,6 @@ $: hideMappingsUnlessSearchedFor = config.mappings.length > 8 && config.mappings.some((m) => m.priorityIf !== undefined) - if (state?.osmConnection) { - onDestroy( - state.osmConnection?.userDetails?.addCallbackAndRun((ud) => { - numberOfCs = ud?.csCount - }) - ) - } - function clearAnswer() { const tagsToSet: UploadableTag[] = onMarkUnknown.data const change = new ChangeTagAction(tags.data.id, new And(tagsToSet), tags.data, { @@ -577,9 +566,7 @@ v === "yes" || v === "full" || v === "always" - )} + condition={state.userRelatedState?.showTagsB} >
@@ -639,7 +626,7 @@
- {#if UserRelatedState.SHOW_TAGS_VALUES.indexOf($showTags) >= 0 || ($showTags === "" && numberOfCs >= Constants.userJourney.tagsVisibleAt) || $featureSwitchIsTesting || $featureSwitchIsDebugging} + {#if $showTags || $featureSwitchIsTesting || $featureSwitchIsDebugging}