UX: improve attribution display, add magnifying class

This commit is contained in:
Pieter Vander Vennet 2024-08-28 15:07:18 +02:00
parent ac632206e9
commit 8465b59c7f
7 changed files with 97 additions and 64 deletions

View file

@ -193,6 +193,7 @@
}, },
"josmNotOpened": "JOSM could not be reached. Make sure it is opened and remote control is enabled", "josmNotOpened": "JOSM could not be reached. Make sure it is opened and remote control is enabled",
"josmOpened": "JOSM is opened", "josmOpened": "JOSM is opened",
"madeBy": "Mady by <b>{author}</b>",
"mapContributionsBy": "The current visible data has edits made by {contributors}", "mapContributionsBy": "The current visible data has edits made by {contributors}",
"mapContributionsByAndHidden": "The current visible data has edits made by {contributors} and {hiddenCount} more contributors", "mapContributionsByAndHidden": "The current visible data has edits made by {contributors} and {hiddenCount} more contributors",
"mapDataByOsm": "Map data: OpenStreetMap", "mapDataByOsm": "Map data: OpenStreetMap",

View file

@ -2073,6 +2073,12 @@ video {
margin-bottom: calc(0px * var(--tw-space-y-reverse)); margin-bottom: calc(0px * var(--tw-space-y-reverse));
} }
.space-x-4 > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0;
margin-right: calc(1rem * var(--tw-space-x-reverse));
margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
}
.space-x-2 > :not([hidden]) ~ :not([hidden]) { .space-x-2 > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0; --tw-space-x-reverse: 0;
margin-right: calc(0.5rem * var(--tw-space-x-reverse)); margin-right: calc(0.5rem * var(--tw-space-x-reverse));
@ -2097,12 +2103,6 @@ video {
margin-left: calc(-1px * calc(1 - var(--tw-space-x-reverse))); margin-left: calc(-1px * calc(1 - var(--tw-space-x-reverse)));
} }
.space-x-4 > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0;
margin-right: calc(1rem * var(--tw-space-x-reverse));
margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
}
.space-y-1 > :not([hidden]) ~ :not([hidden]) { .space-y-1 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0; --tw-space-y-reverse: 0;
margin-top: calc(0.25rem * calc(1 - var(--tw-space-y-reverse))); margin-top: calc(0.25rem * calc(1 - var(--tw-space-y-reverse)));
@ -2431,6 +2431,10 @@ video {
border-top-right-radius: 0.25rem; border-top-right-radius: 0.25rem;
} }
.rounded-bl-full {
border-bottom-left-radius: 9999px;
}
.rounded-tl { .rounded-tl {
border-top-left-radius: 0.25rem; border-top-left-radius: 0.25rem;
} }
@ -3454,6 +3458,14 @@ video {
padding-top: 0px; padding-top: 0px;
} }
.pl-3 {
padding-left: 0.75rem;
}
.pb-3 {
padding-bottom: 0.75rem;
}
.pl-4 { .pl-4 {
padding-left: 1rem; padding-left: 1rem;
} }
@ -3462,14 +3474,6 @@ video {
padding-right: 1rem; padding-right: 1rem;
} }
.pl-3 {
padding-left: 0.75rem;
}
.pr-3 {
padding-right: 0.75rem;
}
.pl-1 { .pl-1 {
padding-left: 0.25rem; padding-left: 0.25rem;
} }

View file

@ -6,6 +6,7 @@
import type { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider" import type { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
import { Mapillary } from "../../Logic/ImageProviders/Mapillary" import { Mapillary } from "../../Logic/ImageProviders/Mapillary"
import { UIEventSource } from "../../Logic/UIEventSource" import { UIEventSource } from "../../Logic/UIEventSource"
import { MagnifyingGlassPlusIcon } from "@babeard/svelte-heroicons/outline"
export let image: Partial<ProvidedImage> export let image: Partial<ProvidedImage>
let fallbackImage: string = undefined let fallbackImage: string = undefined
@ -16,25 +17,35 @@
let imgEl: HTMLImageElement let imgEl: HTMLImageElement
export let imgClass: string = undefined export let imgClass: string = undefined
export let previewedImage: UIEventSource<ProvidedImage> = undefined export let previewedImage: UIEventSource<ProvidedImage> = undefined
export let attributionFormat: "minimal" | "medium" | "large" = "medium"
let canZoom = previewedImage !== undefined // We check if there is a SOURCE, not if there is data in it!
</script> </script>
<div class="relative shrink-0"> <div class="relative shrink-0">
<img <div class="relative w-fit">
bind:this={imgEl} <img
class={imgClass ?? ""} bind:this={imgEl}
class:cursor-pointer={previewedImage !== undefined} class={imgClass ?? ""}
on:click={() => { class:cursor-zoom-in={previewedImage !== undefined}
on:click={() => {
previewedImage?.setData(image) previewedImage?.setData(image)
}} }}
on:error={() => { on:error={() => {
if (fallbackImage) { if (fallbackImage) {
imgEl.src = fallbackImage imgEl.src = fallbackImage
} }
}} }}
src={image.url} src={image.url}
/> />
{#if canZoom}
<div class="absolute right-0 top-0 bg-black-transparent rounded-bl-full">
<MagnifyingGlassPlusIcon class="w-8 h-8 pl-3 pb-3 cursor-zoom-in" color="white" />
</div>
{/if}
</div>
<div class="absolute bottom-0 left-0"> <div class="absolute bottom-0 left-0">
<ImageAttribution {image} /> <ImageAttribution {image} {attributionFormat} />
</div> </div>
</div> </div>

View file

@ -4,11 +4,15 @@
import { Store, UIEventSource } from "../../Logic/UIEventSource" import { Store, UIEventSource } from "../../Logic/UIEventSource"
import ToSvelte from "../Base/ToSvelte.svelte" import ToSvelte from "../Base/ToSvelte.svelte"
import { EyeIcon } from "@rgossiaux/svelte-heroicons/solid" import { EyeIcon } from "@rgossiaux/svelte-heroicons/solid"
import Tr from "../Base/Tr.svelte"
import Translations from "../i18n/Translations"
/** /**
* A small element showing the attribution of a single image * A small element showing the attribution of a single image
*/ */
export let image: Partial<ProvidedImage> & { id: string; url: string } export let image: Partial<ProvidedImage> & { id: string; url: string }
export let attributionFormat: "minimal" | "medium" | "large" = "medium"
let license: Store<LicenseInfo> = UIEventSource.FromPromise( let license: Store<LicenseInfo> = UIEventSource.FromPromise(
image.provider?.DownloadAttribution(image) image.provider?.DownloadAttribution(image)
) )
@ -16,50 +20,59 @@
</script> </script>
{#if $license !== undefined} {#if $license !== undefined}
<div class="no-images flex items-center rounded-lg bg-black p-0.5 pl-3 pr-3 text-sm text-white"> <div class="no-images flex items-center rounded-lg bg-black-transparent p-0.5 px-3 text-sm text-white">
{#if icon !== undefined} {#if icon !== undefined}
<div class="mr-2 h-6 w-6"> <div class="mr-2 h-6 w-6">
<ToSvelte construct={icon} /> <ToSvelte construct={icon} />
</div> </div>
{/if} {/if}
<div class="flex flex-col"> <div class="flex gap-x-2" class:flex-col={attributionFormat !== "minimal"}>
{#if $license.title} {#if attributionFormat !== "minimal" }
{#if $license.informationLocation} {#if $license.title}
<a href={$license.informationLocation.href} target="_blank" rel="noopener nofollower"> {#if $license.informationLocation}
{$license.title} <a href={$license.informationLocation.href} target="_blank" rel="noopener nofollower">
</a> {$license.title}
{:else} </a>
$license.title {:else}
$license.title
{/if}
{/if} {/if}
{/if} {/if}
{#if $license.artist} {#if $license.artist}
<div class="font-bold"> {#if attributionFormat === "large"}
{@html $license.artist} <Tr t={Translations.t.general.attribution.madeBy.Subs({author: $license.artist})} />
</div> {:else}
<div class="font-bold">
{@html $license.artist}
</div>
{/if}
{/if} {/if}
<div class="flex w-full justify-between gap-x-1">
{#if $license.license !== undefined || $license.licenseShortName !== undefined}
<div>
{$license?.license ?? $license?.licenseShortName}
</div>
{/if}
{#if $license.views}
<div class="flex justify-around self-center">
<EyeIcon class="h-4 w-4 pr-1" />
{$license.views}
</div>
{/if}
</div>
{#if $license.date} {#if $license.date}
<div> <div>
{$license.date.toLocaleDateString()} {$license.date.toLocaleDateString()}
</div> </div>
{/if} {/if}
{#if attributionFormat !== "minimal"}
<div class="flex w-full justify-between gap-x-1">
{#if ($license.license !== undefined || $license.licenseShortName !== undefined)}
<div>
{$license?.license ?? $license?.licenseShortName}
</div>
{/if}
{#if $license.views}
<div class="flex justify-around self-center text-xs">
<EyeIcon class="h-4 w-4 pr-1" />
{$license.views}
</div>
{/if}
</div>
{/if}
</div> </div>
</div> </div>
{/if} {/if}

View file

@ -38,9 +38,9 @@
class="pointer-events-none absolute bottom-0 left-0 flex w-full flex-wrap items-end justify-between" class="pointer-events-none absolute bottom-0 left-0 flex w-full flex-wrap items-end justify-between"
> >
<div <div
class="pointer-events-auto m-1 w-fit opacity-50 transition-colors duration-200 hover:opacity-100" class="pointer-events-auto m-1 w-fit transition-colors duration-200"
> >
<ImageAttribution {image} /> <ImageAttribution {image} attributionFormat="large"/>
</div> </div>
<button <button

View file

@ -14,6 +14,8 @@
import AttributedImage from "./AttributedImage.svelte" import AttributedImage from "./AttributedImage.svelte"
import SpecialTranslation from "../Popup/TagRendering/SpecialTranslation.svelte" import SpecialTranslation from "../Popup/TagRendering/SpecialTranslation.svelte"
import LoginToggle from "../Base/LoginToggle.svelte" import LoginToggle from "../Base/LoginToggle.svelte"
import ImagePreview from "./ImagePreview.svelte"
import FloatOver from "../Base/FloatOver.svelte"
export let tags: UIEventSource<OsmTags> export let tags: UIEventSource<OsmTags>
export let state: SpecialVisualizationState export let state: SpecialVisualizationState
@ -31,7 +33,7 @@
key: undefined, key: undefined,
provider: AllImageProviders.byName(image.provider), provider: AllImageProviders.byName(image.provider),
date: new Date(image.date), date: new Date(image.date),
id: Object.values(image.osmTags)[0], id: Object.values(image.osmTags)[0]
} }
async function applyLink(isLinked: boolean) { async function applyLink(isLinked: boolean) {
@ -42,7 +44,7 @@
if (isLinked) { if (isLinked) {
const action = new LinkImageAction(currentTags.id, key, url, tags, { const action = new LinkImageAction(currentTags.id, key, url, tags, {
theme: tags.data._orig_theme ?? state.layout.id, theme: tags.data._orig_theme ?? state.layout.id,
changeType: "link-image", changeType: "link-image"
}) })
await state.changes.applyAction(action) await state.changes.applyAction(action)
} else { } else {
@ -51,24 +53,26 @@
if (v === url) { if (v === url) {
const action = new ChangeTagAction(currentTags.id, new Tag(k, ""), currentTags, { const action = new ChangeTagAction(currentTags.id, new Tag(k, ""), currentTags, {
theme: tags.data._orig_theme ?? state.layout.id, theme: tags.data._orig_theme ?? state.layout.id,
changeType: "remove-image", changeType: "remove-image"
}) })
state.changes.applyAction(action) state.changes.applyAction(action)
} }
} }
} }
} }
isLinked.addCallback((isLinked) => applyLink(isLinked)) isLinked.addCallback((isLinked) => applyLink(isLinked))
</script> </script>
<div class="flex w-fit shrink-0 flex-col"> <div class="flex w-fit shrink-0 flex-col rounded-lg overflow-hidden" class:border-interactive={$isLinked}
<div class="cursor-zoom-in" on:click={() => state.previewedImage.setData(providedImage)}> style="border-width: 2px">
<AttributedImage <AttributedImage
image={providedImage} image={providedImage}
imgClass="max-h-64 w-auto" imgClass="max-h-64 w-auto"
previewedImage={state.previewedImage} previewedImage={state.previewedImage}
/> attributionFormat="minimal"
</div> />
<LoginToggle {state} silentFail={true}> <LoginToggle {state} silentFail={true}>
{#if linkable} {#if linkable}
<label> <label>

View file

@ -60,7 +60,7 @@
<Tr t={Translations.t.image.nearby.noNearbyImages} cls="alert" /> <Tr t={Translations.t.image.nearby.noNearbyImages} cls="alert" />
{/if} {/if}
{:else} {:else}
<div class="flex w-full space-x-1 overflow-x-auto" style="scroll-snap-type: x proximity"> <div class="flex w-full space-x-4 overflow-x-auto" style="scroll-snap-type: x proximity">
{#each $result as image (image.pictureUrl)} {#each $result as image (image.pictureUrl)}
<span class="w-fit shrink-0" style="scroll-snap-align: start"> <span class="w-fit shrink-0" style="scroll-snap-align: start">
<LinkableImage {tags} {image} {state} {feature} {layer} {linkable} /> <LinkableImage {tags} {image} {state} {feature} {layer} {linkable} />