From 611dabd5cb7cfe5467a393b7aec7380d2ab4e0f4 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 16 Jun 2025 14:54:53 +0200 Subject: [PATCH] Attempted fix of #2430 --- src/Logic/ImageProviders/ImageProvider.ts | 5 +- src/Logic/Web/NearbyImagesSearch.ts | 4 +- src/UI/Comparison/ComparisonTable.svelte | 4 +- src/UI/Image/AttributedImage.svelte | 48 ++++++------ src/UI/Image/AttributedImageDotMenu.svelte | 33 +++++++++ src/UI/Image/ImageOperations.svelte | 60 +++------------ src/UI/Image/ImagePreview.svelte | 21 ++---- src/UI/Image/LinkableImage.svelte | 85 ++++++++++++++-------- src/UI/Image/NearbyImages.svelte | 16 ++-- src/UI/Image/photoSphereViewerWrapper.ts | 14 +++- 10 files changed, 156 insertions(+), 134 deletions(-) create mode 100644 src/UI/Image/AttributedImageDotMenu.svelte diff --git a/src/Logic/ImageProviders/ImageProvider.ts b/src/Logic/ImageProviders/ImageProvider.ts index d3d14b705e..66a7b833f1 100644 --- a/src/Logic/ImageProviders/ImageProvider.ts +++ b/src/Logic/ImageProviders/ImageProvider.ts @@ -3,6 +3,7 @@ import BaseUIElement from "../../UI/BaseUIElement" import { LicenseInfo } from "./LicenseInfo" import { Utils } from "../../Utils" import { Feature, Point } from "geojson" +import { P4CPicture } from "../Web/NearbyImagesSearch" export interface ProvidedImage { url: string @@ -36,7 +37,8 @@ export interface PanoramaView { */ northOffset?: number pitchOffset?: number - provider: ImageProvider | string + provider: ImageProvider | string, + p4c: P4CPicture } /** @@ -60,6 +62,7 @@ export interface HotspotProperties { pitch: number | "auto" gotoPanorama: Feature + originalP4C: P4CPicture } export default abstract class ImageProvider { diff --git a/src/Logic/Web/NearbyImagesSearch.ts b/src/Logic/Web/NearbyImagesSearch.ts index 89d5f749cc..3b077a2f2a 100644 --- a/src/Logic/Web/NearbyImagesSearch.ts +++ b/src/Logic/Web/NearbyImagesSearch.ts @@ -80,7 +80,7 @@ class NearbyImageUtils { } class P4CImageFetcher implements ImageFetcher { - public static readonly services = ["flickr", "kartaview", "wikicommons"] as const + public static readonly services = ["flickr", "kartaview", "wikicommons", "mapillary"] as const public static readonly apiUrls = ["https://api.flickr.com"] private _options: { maxDaysOld: number; searchRadius: number } public readonly name: P4CService @@ -374,7 +374,7 @@ export class CombinedFetcher { start_captured_at: maxage, panoramas: "no", }), - // new P4CImageFetcher("mapillary"), + new P4CImageFetcher("mapillary"), new P4CImageFetcher("wikicommons"), ].map((f) => new CachedFetcher(f)) } diff --git a/src/UI/Comparison/ComparisonTable.svelte b/src/UI/Comparison/ComparisonTable.svelte index dc5c3b3f1d..5523b57765 100644 --- a/src/UI/Comparison/ComparisonTable.svelte +++ b/src/UI/Comparison/ComparisonTable.svelte @@ -146,14 +146,14 @@ diff --git a/src/UI/Image/AttributedImage.svelte b/src/UI/Image/AttributedImage.svelte index 14764591cc..0f82e38749 100644 --- a/src/UI/Image/AttributedImage.svelte +++ b/src/UI/Image/AttributedImage.svelte @@ -3,11 +3,10 @@ * Shows an image with attribution */ import ImageAttribution from "./ImageAttribution.svelte" - import { Store } from "../../Logic/UIEventSource" + import { Store, UIEventSource } from "../../Logic/UIEventSource" import type { HotspotProperties, ProvidedImage } from "../../Logic/ImageProviders/ImageProvider" import { Mapillary } from "../../Logic/ImageProviders/Mapillary" - import { UIEventSource } from "../../Logic/UIEventSource" import { MagnifyingGlassPlusIcon } from "@babeard/svelte-heroicons/outline" import { CloseButton } from "flowbite-svelte" import ImageOperations from "./ImageOperations.svelte" @@ -17,12 +16,11 @@ import Loading from "../Base/Loading.svelte" import Translations from "../i18n/Translations" import Tr from "../Base/Tr.svelte" - import DotMenu from "../Base/DotMenu.svelte" import LoadingPlaceholder from "../Base/LoadingPlaceholder.svelte" import { MenuState } from "../../Models/MenuState" import ThemeViewState from "../../Models/ThemeViewState" import Panorama360 from "../../assets/svg/Panorama360.svelte" - import { ExternalLinkIcon } from "@rgossiaux/svelte-heroicons/solid" + import AttributedImageDotMenu from "./AttributedImageDotMenu.svelte" export let image: Partial & { id: string; url: string } let fallbackImage: string = undefined @@ -41,7 +39,6 @@ | Store[]> = [] let loaded = false - let visitUrl = image.provider?.visitUrl(image) let showBigPreview = new UIEventSource(false) onDestroy( showBigPreview.addCallbackAndRun((shown) => { @@ -53,7 +50,7 @@ previewedImage.addCallbackAndRun((previewedImage) => { showBigPreview.set( previewedImage !== undefined && - (previewedImage?.id ?? previewedImage?.url) === (image.id ?? image.url) + (previewedImage?.id ?? previewedImage?.url) === (image.id ?? image.url) ) }) ) @@ -71,24 +68,37 @@ type: "Feature", properties: { id: image.id, - rotation: image.rotation, + rotation: image.rotation }, geometry: { type: "Point", - coordinates: [image.lon, image.lat], - }, + coordinates: [image.lon, image.lat] + } } state?.geocodedImages.set([f]) } -
- +
+ - +
+
+ +
+ + +
+ + + +
+
highlight()} on:mouseleave={() => highlight(false)} > - {#if $$slots["dot-menu-actions"] || visitUrl !== undefined} - - - {#if visitUrl !== undefined} - - - - - {/if} - - {/if} + + + {#if !loaded} {/if} diff --git a/src/UI/Image/AttributedImageDotMenu.svelte b/src/UI/Image/AttributedImageDotMenu.svelte new file mode 100644 index 0000000000..821b36a936 --- /dev/null +++ b/src/UI/Image/AttributedImageDotMenu.svelte @@ -0,0 +1,33 @@ + + + + {#if visitUrl !== undefined} + + + + + {/if} + + + + + + diff --git a/src/UI/Image/ImageOperations.svelte b/src/UI/Image/ImageOperations.svelte index 46b8d2dd36..e383249c1f 100644 --- a/src/UI/Image/ImageOperations.svelte +++ b/src/UI/Image/ImageOperations.svelte @@ -3,66 +3,26 @@ * The 'imageOperations' previews an image and offers some extra tools (e.g. download) */ - import ImageProvider from "../../Logic/ImageProviders/ImageProvider" - import type { HotspotProperties } from "../../Logic/ImageProviders/ImageProvider" - import type { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider" - import ImageAttribution from "./ImageAttribution.svelte" + import type { HotspotProperties, ProvidedImage } from "../../Logic/ImageProviders/ImageProvider" import ImagePreview from "./ImagePreview.svelte" - import { DownloadIcon, ExternalLinkIcon } from "@rgossiaux/svelte-heroicons/solid" - import { twMerge } from "tailwind-merge" - import { UIEventSource } from "../../Logic/UIEventSource" + import { Store, UIEventSource } from "../../Logic/UIEventSource" import Loading from "../Base/Loading.svelte" - import Tr from "../Base/Tr.svelte" - import Translations from "../i18n/Translations" - import DotMenu from "../Base/DotMenu.svelte" import type { Feature, Geometry } from "geojson" - import { Store } from "../../Logic/UIEventSource" export let image: Partial & { id: string; url: string } - export let clss: string = undefined export let nearbyFeatures: | Feature[] | Store[]> = [] - let visitUrl = image.provider?.visitUrl(image) let isLoaded = new UIEventSource(false) + -
-
- {#if !$isLoaded} -
- -
- {/if} - -
- - {#if $$slots["dot-menu-actions"]} - - - - - {#if visitUrl !== undefined} - - - - - {/if} - - {/if} -
-
- +
+ {#if !$isLoaded} +
+
- - -
+ {/if} +
+ diff --git a/src/UI/Image/ImagePreview.svelte b/src/UI/Image/ImagePreview.svelte index e9cdc0029c..0a67f29175 100644 --- a/src/UI/Image/ImagePreview.svelte +++ b/src/UI/Image/ImagePreview.svelte @@ -4,13 +4,12 @@ */ import panzoom from "panzoom" import type { HotspotProperties, PanoramaView, ProvidedImage } from "../../Logic/ImageProviders/ImageProvider" - import ImageProvider from "../../Logic/ImageProviders/ImageProvider" import { Store, UIEventSource } from "../../Logic/UIEventSource" import Zoomcontrol from "../Zoomcontrol" - import { onDestroy } from "svelte" + import { createEventDispatcher, onDestroy } from "svelte" import { PhotoSphereViewerWrapper } from "./photoSphereViewerWrapper" import type { Feature, Geometry, Point } from "geojson" - import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders" + import type { P4CPicture } from "../../Logic/Web/NearbyImagesSearch" export let nearbyFeatures: | Feature[] @@ -21,6 +20,8 @@ let viewerEl: HTMLElement export let isLoaded: UIEventSource = undefined + let dispatch = createEventDispatcher<{ imagechange: P4CPicture }>() + onDestroy(Zoomcontrol.createLock()) let destroyed = false @@ -29,20 +30,14 @@ }) async function initPhotosphere() { const imageInfo: Feature = await image.provider.getPanoramaInfo(image) - if (imageInfo === undefined) { - console.error("Image info is apparently undefined for", image) + if (imageInfo?.properties?.url === undefined) { + console.error("Image info (or url) is apparently undefined for", image) return } const viewer = new PhotoSphereViewerWrapper(viewerEl, imageInfo) viewer.imageInfo.addCallbackAndRunD(panoramaInfo => { - let provider: ImageProvider - if (typeof panoramaInfo.properties.provider === "string") { - provider = AllImageProviders.byName(panoramaInfo.properties.provider) - } else { - provider = panoramaInfo.properties.provider - } - console.log(">>> Got:", panoramaInfo, "by", provider.name) - //actuallyDisplayed.set(image.properties.imageMeta) + console.log("Sending imagechange with", panoramaInfo.properties.p4c, "from", imageInfo) + dispatch("imagechange", panoramaInfo.properties.p4c) }) if (Array.isArray(nearbyFeatures)) { viewer.setNearbyFeatures(nearbyFeatures) diff --git a/src/UI/Image/LinkableImage.svelte b/src/UI/Image/LinkableImage.svelte index 2df9132f5b..9dc38b2617 100644 --- a/src/UI/Image/LinkableImage.svelte +++ b/src/UI/Image/LinkableImage.svelte @@ -22,15 +22,23 @@ export let tags: UIEventSource export let state: ThemeViewState - export let image: P4CPicture + export let image: UIEventSource export let feature: Feature export let layer: LayerConfig export let highlighted: UIEventSource = undefined export let nearbyFeatures: Feature[] | Store[]> = [] export let linkable = true - let targetValue = Object.values(image.osmTags)[0] - let isLinked = new UIEventSource(Object.values(tags.data).some((v) => targetValue === v)) + let targetValue: Store = image.mapD(image => { + console.log("Calculating new targetValue", image.osmTags) + return Object.values(image.osmTags)[0] + }) + targetValue.addCallbackAndRunD(tv => console.log("New target value is", tv)) + let isLinked = new UIEventSource(undefined) + targetValue.mapD(targetValue => { + Object.values(tags.data).some((v) => targetValue === v) + }, [tags]) + isLinked.addCallbackAndRun((linked) => { if (linked) { MenuState.previewedImage.set(undefined) @@ -38,84 +46,96 @@ }) const t = Translations.t.image.nearby - let date: Date - if (image.date) { + + let providedImage: Store = image.mapD(image => { + let date: Date try { date = new Date(image.date) } catch (e) { console.warn("Could not parse image date", image.date, "for", image.detailsUrl) } - } + const license: LicenseInfo = { + artist: image.author, + license: image.license, + informationLocation: image.detailsUrl ?? image.pictureUrl ?? image.thumbUrl, + date + } - let license: LicenseInfo = { - artist: image.author, - license: image.license, - informationLocation: image.detailsUrl ?? image.pictureUrl ?? image.thumbUrl, - date, - } + return { + url: image.thumbUrl ?? image.pictureUrl, + url_hd: image.pictureUrl, + key: undefined, + provider: AllImageProviders.byName(image.provider), + date, + id: Object.values(image.osmTags)[0], + isSpherical: image.details.isSpherical, + license - let providedImage: ProvidedImage = { - url: image.thumbUrl ?? image.pictureUrl, - url_hd: image.pictureUrl, - key: undefined, - provider: AllImageProviders.byName(image.provider), - date, - id: Object.values(image.osmTags)[0], - isSpherical: image.details.isSpherical, - license, - } + } + }) async function applyLink(isLinked: boolean) { - console.log("Applying linked image", isLinked, targetValue) const currentTags = tags.data - const key = Object.keys(image.osmTags)[0] - const url = targetValue + const key = Object.keys(image.data.osmTags)[0] + const url = image.data.osmTags[key] + console.log("Applying linked image", isLinked, url) if (isLinked) { const action = new LinkImageAction(currentTags.id, key, url, tags, { theme: tags.data._orig_theme ?? state.theme.id, - changeType: "link-image", + changeType: "link-image" }) await state.changes.applyAction(action) } else { for (const k in currentTags) { - const v = currentTags[k] + const v: string = currentTags[k] if (v === url) { const action = new ChangeTagAction(currentTags.id, new Tag(k, ""), currentTags, { theme: tags.data._orig_theme ?? state.theme.id, - changeType: "remove-image", + changeType: "remove-image" }) - state.changes.applyAction(action) + await state.changes.applyAction(action) } } } } + function onImageChanged(p4c: P4CPicture) { + console.log("Viewer changed panorama, now got", p4c) + if (p4c) { + image.set(p4c) + } else { + console.trace("Got a null!") + } + } + isLinked.addCallback((isLinked) => applyLink(isLinked)) let element: HTMLDivElement if (highlighted) { onDestroy( highlighted.addCallbackD((highlightedUrl) => { - if (highlightedUrl === image.pictureUrl) { + if (highlightedUrl === image.data.pictureUrl) { Utils.scrollIntoView(element) } }) ) } +
onImageChanged(ev.detail) } > @@ -123,6 +143,7 @@ {/if} diff --git a/src/UI/Image/NearbyImages.svelte b/src/UI/Image/NearbyImages.svelte index 7c1cb52dfa..5cafb4277f 100644 --- a/src/UI/Image/NearbyImages.svelte +++ b/src/UI/Image/NearbyImages.svelte @@ -7,7 +7,7 @@ import type { SpecialVisualizationState } from "../SpecialVisualization" import type { P4CPicture } from "../../Logic/Web/NearbyImagesSearch" import LinkableImage from "./LinkableImage.svelte" - import type { Feature, Geometry, Point } from "geojson" + import type { Feature, Point } from "geojson" import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import Loading from "../Base/Loading.svelte" import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders" @@ -53,9 +53,9 @@ ) // Panorama-views get a geojson feature to browse around - let asFeatures = result.map((p4cs) => + let asFeatures = result.map((p4cs: P4CPicture[]) => p4cs.map( - (p4c) => + (p4c: P4CPicture) => >{ type: "Feature", geometry: { @@ -68,7 +68,8 @@ northOffset: p4c.direction, rotation: p4c.direction, spherical: p4c.details.isSpherical ? "yes" : "no", - provider: p4c.provider + provider: p4c.provider, + p4c }, } ) @@ -150,7 +151,7 @@ }, }) - let nearbyFeatures: Store[]> = asFeatures.map( + let nearbyFeatures: Store[]> = asFeatures.map( (nearbyPoints) => { return [ { @@ -166,11 +167,12 @@ .map((f) => ({ ...f, properties: { - name: "Nearby panorama", + name: "Nearby panorama by " + f.properties.p4c.author, pitch: "auto", type: "scene", gotoPanorama: f, focus: false, + originalP4C: f.properties.p4c }, })), ] @@ -216,7 +218,7 @@ > , nearbyFeatures?: Feature[] ) { - this._imageInfo.set(imageInfo) + console.log(">>>", imageInfo.properties.url) + this.viewer = pannellum.viewer(container, { default: { firstScene: imageInfo.properties.url, @@ -35,7 +36,7 @@ export class PhotoSphereViewerWrapper { panorama: imageInfo.properties.url, autoLoad: true, hotSpots: [], - sceneFadeDuration: 250, + // sceneFadeDuration: 250, compass: true, showControls: false, northOffset: imageInfo.properties.northOffset, @@ -43,7 +44,7 @@ export class PhotoSphereViewerWrapper { }, }, }) - + this._imageInfo.set(imageInfo) this.setNearbyFeatures(nearbyFeatures) } @@ -80,17 +81,22 @@ export class PhotoSphereViewerWrapper { // Already the current scene return } + if (!imageInfo?.properties?.url) { + return // Not a panorama + } + console.log(">>><<<", imageInfo.properties.url) this.clearHotspots() this.viewer.addScene(imageInfo.properties.url, { panorama: imageInfo.properties.url, northOffset: imageInfo.properties.northOffset, type: "equirectangular", showControls: false, + compass: true }) this.viewer.loadScene(imageInfo.properties.url, 0, imageInfo.properties.northOffset) - this.setNearbyFeatures(this.nearbyFeatures) this._imageInfo.set(imageInfo) + this.setNearbyFeatures(this.nearbyFeatures) // Depends on 'imageInfo', must be set _after_ }