forked from MapComplete/MapComplete
Merge develop
This commit is contained in:
commit
d1e7eba2db
19 changed files with 554 additions and 448 deletions
|
@ -1,6 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { ImmutableStore, Store } from "../../Logic/UIEventSource"
|
||||
import Icon from "../Map/Icon.svelte"
|
||||
import { Utils } from "../../Utils"
|
||||
|
||||
export let text: Store<string>
|
||||
export let href: Store<string>
|
||||
|
@ -13,7 +14,7 @@
|
|||
</script>
|
||||
|
||||
<a
|
||||
href={$href}
|
||||
href={Utils.prepareHref($href)}
|
||||
aria-label={$ariaLabel}
|
||||
title={$ariaLabel}
|
||||
target={$newTab ? "_blank" : undefined}
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
<script lang="ts">
|
||||
import { Utils } from "../../Utils"
|
||||
|
||||
export let text: string
|
||||
export let href: string
|
||||
|
||||
|
||||
export let classnames: string = undefined
|
||||
export let download: string = undefined
|
||||
export let ariaLabel: string = undefined
|
||||
|
@ -9,7 +13,7 @@
|
|||
</script>
|
||||
|
||||
<a
|
||||
{href}
|
||||
href={Utils.prepareHref(href)}
|
||||
aria-label={ariaLabel}
|
||||
title={ariaLabel}
|
||||
target={newTab ? "_blank" : undefined}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
import AccordionSingle from "../Flowbite/AccordionSingle.svelte"
|
||||
import GlobeAlt from "@babeard/svelte-heroicons/mini/GlobeAlt"
|
||||
import { ComparisonState } from "./ComparisonState"
|
||||
import LoginToggle from "../Base/LoginToggle.svelte"
|
||||
|
||||
export let externalData: Store<
|
||||
| { success: { content: Record<string, string> } }
|
||||
|
@ -45,35 +46,38 @@
|
|||
let enableLogin = state.featureSwitches.featureSwitchEnableLogin
|
||||
</script>
|
||||
|
||||
{#if !$sourceUrl || !$enableLogin}
|
||||
<!-- empty block -->
|
||||
{:else if $externalData === undefined}
|
||||
<Loading />
|
||||
{:else if $externalData["error"] !== undefined}
|
||||
<div class="subtle low-interaction rounded p-2 px-4 italic">
|
||||
<Tr t={Translations.t.external.error} />
|
||||
</div>
|
||||
{:else if $propertyKeysExternal.length === 0 && $knownImages.size + $unknownImages.length === 0}
|
||||
<Tr cls="subtle" t={t.noDataLoaded} />
|
||||
{:else if !$hasDifferencesAtStart}
|
||||
<LoginToggle {state} silentFail>
|
||||
|
||||
{#if !$sourceUrl || !$enableLogin}
|
||||
<!-- empty block -->
|
||||
{:else if $externalData === undefined}
|
||||
<Loading />
|
||||
{:else if $externalData["error"] !== undefined}
|
||||
<div class="subtle low-interaction rounded p-2 px-4 italic">
|
||||
<Tr t={Translations.t.external.error} />
|
||||
</div>
|
||||
{:else if $propertyKeysExternal.length === 0 && $knownImages.size + $unknownImages.length === 0}
|
||||
<Tr cls="subtle" t={t.noDataLoaded} />
|
||||
{:else if !$hasDifferencesAtStart}
|
||||
<span class="subtle text-sm">
|
||||
<Tr t={t.allIncluded.Subs({ source: $sourceUrl })} />
|
||||
</span>
|
||||
{:else if $comparisonState !== undefined}
|
||||
<AccordionSingle expanded={!collapsed}>
|
||||
{:else if $comparisonState !== undefined}
|
||||
<AccordionSingle expanded={!collapsed}>
|
||||
<span slot="header" class="flex">
|
||||
<GlobeAlt class="h-6 w-6" />
|
||||
<Tr t={Translations.t.external.title} />
|
||||
</span>
|
||||
<ComparisonTable
|
||||
externalProperties={$externalData["success"]}
|
||||
{state}
|
||||
{feature}
|
||||
{layer}
|
||||
{tags}
|
||||
{readonly}
|
||||
sourceUrl={$sourceUrl}
|
||||
comparisonState={$comparisonState}
|
||||
/>
|
||||
</AccordionSingle>
|
||||
{/if}
|
||||
<ComparisonTable
|
||||
externalProperties={$externalData["success"]}
|
||||
{state}
|
||||
{feature}
|
||||
{layer}
|
||||
{tags}
|
||||
{readonly}
|
||||
sourceUrl={$sourceUrl}
|
||||
comparisonState={$comparisonState}
|
||||
/>
|
||||
</AccordionSingle>
|
||||
{/if}
|
||||
</LoginToggle>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
import type { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
|
||||
import { Mapillary } from "../../Logic/ImageProviders/Mapillary"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { MagnifyingGlassPlusIcon } from "@babeard/svelte-heroicons/outline"
|
||||
|
||||
export let image: Partial<ProvidedImage>
|
||||
let fallbackImage: string = undefined
|
||||
|
@ -16,25 +17,37 @@
|
|||
let imgEl: HTMLImageElement
|
||||
export let imgClass: string = 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!
|
||||
let loaded = false
|
||||
</script>
|
||||
|
||||
<div class="relative shrink-0">
|
||||
<img
|
||||
bind:this={imgEl}
|
||||
class={imgClass ?? ""}
|
||||
class:cursor-pointer={previewedImage !== undefined}
|
||||
on:click={() => {
|
||||
<div class="relative w-fit">
|
||||
<img
|
||||
bind:this={imgEl}
|
||||
on:load={() => loaded = true}
|
||||
class={imgClass ?? ""}
|
||||
class:cursor-zoom-in={previewedImage !== undefined}
|
||||
on:click={() => {
|
||||
previewedImage?.setData(image)
|
||||
}}
|
||||
on:error={() => {
|
||||
on:error={() => {
|
||||
if (fallbackImage) {
|
||||
imgEl.src = fallbackImage
|
||||
}
|
||||
}}
|
||||
src={image.url}
|
||||
/>
|
||||
src={image.url}
|
||||
/>
|
||||
|
||||
{#if canZoom && loaded}
|
||||
<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">
|
||||
<ImageAttribution {image} />
|
||||
<ImageAttribution {image} {attributionFormat} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -4,11 +4,15 @@
|
|||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import ToSvelte from "../Base/ToSvelte.svelte"
|
||||
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
|
||||
*/
|
||||
export let image: Partial<ProvidedImage> & { id: string; url: string }
|
||||
export let attributionFormat: "minimal" | "medium" | "large" = "medium"
|
||||
|
||||
let license: Store<LicenseInfo> = UIEventSource.FromPromise(
|
||||
image.provider?.DownloadAttribution(image)
|
||||
)
|
||||
|
@ -16,50 +20,59 @@
|
|||
</script>
|
||||
|
||||
{#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}
|
||||
<div class="mr-2 h-6 w-6">
|
||||
<ToSvelte construct={icon} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="flex flex-col">
|
||||
{#if $license.title}
|
||||
{#if $license.informationLocation}
|
||||
<a href={$license.informationLocation.href} target="_blank" rel="noopener nofollower">
|
||||
{$license.title}
|
||||
</a>
|
||||
{:else}
|
||||
$license.title
|
||||
<div class="flex gap-x-2" class:flex-col={attributionFormat !== "minimal"}>
|
||||
{#if attributionFormat !== "minimal" }
|
||||
{#if $license.title}
|
||||
{#if $license.informationLocation}
|
||||
<a href={$license.informationLocation.href} target="_blank" rel="noopener nofollower">
|
||||
{$license.title}
|
||||
</a>
|
||||
{:else}
|
||||
$license.title
|
||||
{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if $license.artist}
|
||||
<div class="font-bold">
|
||||
{@html $license.artist}
|
||||
</div>
|
||||
{#if attributionFormat === "large"}
|
||||
<Tr t={Translations.t.general.attribution.madeBy.Subs({author: $license.artist})} />
|
||||
{:else}
|
||||
<div class="font-bold">
|
||||
{@html $license.artist}
|
||||
</div>
|
||||
{/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}
|
||||
<div>
|
||||
{$license.date.toLocaleDateString()}
|
||||
</div>
|
||||
{/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>
|
||||
{/if}
|
||||
|
|
|
@ -38,9 +38,9 @@
|
|||
class="pointer-events-none absolute bottom-0 left-0 flex w-full flex-wrap items-end justify-between"
|
||||
>
|
||||
<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>
|
||||
|
||||
<button
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
import AttributedImage from "./AttributedImage.svelte"
|
||||
import SpecialTranslation from "../Popup/TagRendering/SpecialTranslation.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 state: SpecialVisualizationState
|
||||
|
@ -31,7 +33,7 @@
|
|||
key: undefined,
|
||||
provider: AllImageProviders.byName(image.provider),
|
||||
date: new Date(image.date),
|
||||
id: Object.values(image.osmTags)[0],
|
||||
id: Object.values(image.osmTags)[0]
|
||||
}
|
||||
|
||||
async function applyLink(isLinked: boolean) {
|
||||
|
@ -42,7 +44,7 @@
|
|||
if (isLinked) {
|
||||
const action = new LinkImageAction(currentTags.id, key, url, tags, {
|
||||
theme: tags.data._orig_theme ?? state.layout.id,
|
||||
changeType: "link-image",
|
||||
changeType: "link-image"
|
||||
})
|
||||
await state.changes.applyAction(action)
|
||||
} else {
|
||||
|
@ -51,24 +53,26 @@
|
|||
if (v === url) {
|
||||
const action = new ChangeTagAction(currentTags.id, new Tag(k, ""), currentTags, {
|
||||
theme: tags.data._orig_theme ?? state.layout.id,
|
||||
changeType: "remove-image",
|
||||
changeType: "remove-image"
|
||||
})
|
||||
state.changes.applyAction(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isLinked.addCallback((isLinked) => applyLink(isLinked))
|
||||
|
||||
</script>
|
||||
|
||||
<div class="flex w-fit shrink-0 flex-col">
|
||||
<div class="cursor-zoom-in" on:click={() => state.previewedImage.setData(providedImage)}>
|
||||
<AttributedImage
|
||||
image={providedImage}
|
||||
imgClass="max-h-64 w-auto"
|
||||
previewedImage={state.previewedImage}
|
||||
/>
|
||||
</div>
|
||||
<div class="flex w-fit shrink-0 flex-col rounded-lg overflow-hidden" class:border-interactive={$isLinked}
|
||||
style="border-width: 2px">
|
||||
<AttributedImage
|
||||
image={providedImage}
|
||||
imgClass="max-h-64 w-auto"
|
||||
previewedImage={state.previewedImage}
|
||||
attributionFormat="minimal"
|
||||
/>
|
||||
<LoginToggle {state} silentFail={true}>
|
||||
{#if linkable}
|
||||
<label>
|
||||
|
|
|
@ -60,7 +60,7 @@
|
|||
<Tr t={Translations.t.image.nearby.noNearbyImages} cls="alert" />
|
||||
{/if}
|
||||
{: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)}
|
||||
<span class="w-fit shrink-0" style="scroll-snap-align: start">
|
||||
<LinkableImage {tags} {image} {state} {feature} {layer} {linkable} />
|
||||
|
|
|
@ -352,6 +352,7 @@
|
|||
{/if}
|
||||
</legend>
|
||||
|
||||
<!-- Search menu -->
|
||||
{#if config.mappings?.length >= 8 || hideMappingsUnlessSearchedFor}
|
||||
<div class="sticky flex w-full" aria-hidden="true">
|
||||
<Search class="h-6 w-6" />
|
||||
|
@ -369,6 +370,7 @@
|
|||
{/if}
|
||||
{/if}
|
||||
|
||||
<!-- Actual options-->
|
||||
{#if config?.freeform?.key && !(config?.mappings?.filter((m) => m.hideInAnswer != true)?.length > 0)}
|
||||
<!-- There are no options to choose from, simply show the input element: fill out the text field -->
|
||||
<FreeformInput
|
||||
|
@ -384,7 +386,7 @@
|
|||
/>
|
||||
{:else if config.mappings !== undefined && !config.multiAnswer}
|
||||
<!-- Simple radiobuttons as mapping -->
|
||||
<div class="flex flex-col">
|
||||
<div class="flex flex-col no-bold">
|
||||
{#each config.mappings as mapping, i (mapping.then)}
|
||||
<!-- Even though we have a list of 'mappings' already, we still iterate over the list as to keep the original indices-->
|
||||
<TagRenderingMappingInput
|
||||
|
@ -432,7 +434,7 @@
|
|||
</div>
|
||||
{:else if config.mappings !== undefined && config.multiAnswer}
|
||||
<!-- Multiple answers can be chosen: checkboxes -->
|
||||
<div class="flex flex-col">
|
||||
<div class="flex flex-col no-bold">
|
||||
{#each config.mappings as mapping, i (mapping.then)}
|
||||
<TagRenderingMappingInput
|
||||
{mapping}
|
||||
|
@ -475,6 +477,8 @@
|
|||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Save and cancel buttons, in a logintoggle -->
|
||||
<LoginToggle {state}>
|
||||
<Loading slot="loading" />
|
||||
<SubtleButton slot="not-logged-in" on:click={() => state?.osmConnection?.AttemptLogin()}>
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -3,7 +3,6 @@ import { Translation, TypedTranslation } from "./Translation"
|
|||
import BaseUIElement from "../BaseUIElement"
|
||||
import CompiledTranslations from "../../assets/generated/CompiledTranslations"
|
||||
import LanguageUtils from "../../Utils/LanguageUtils"
|
||||
import { ClickableToggle } from "../Input/Toggle"
|
||||
import { Store } from "../../Logic/UIEventSource"
|
||||
import Locale from "./Locale"
|
||||
import { Utils } from "../../Utils"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue