UX: move some items behind login-toggles, add a 'login to add pictures' button again (fix #1698)

This commit is contained in:
Pieter Vander Vennet 2023-12-04 15:02:42 +01:00
parent 5168b42c8f
commit 4e1384c2df
4 changed files with 182 additions and 174 deletions

View file

@ -1,50 +1,53 @@
<script lang="ts"> <script lang="ts">
/** /**
* Shows an 'upload'-button which will start the upload for this feature * Shows an 'upload'-button which will start the upload for this feature
*/ */
import type { SpecialVisualizationState } from "../SpecialVisualization" import type { SpecialVisualizationState } from "../SpecialVisualization"
import { ImmutableStore, Store } from "../../Logic/UIEventSource" import { ImmutableStore, Store } from "../../Logic/UIEventSource"
import type { OsmTags } from "../../Models/OsmFeature" import type { OsmTags } from "../../Models/OsmFeature"
import LoginToggle from "../Base/LoginToggle.svelte" import LoginToggle from "../Base/LoginToggle.svelte"
import Translations from "../i18n/Translations" import Translations from "../i18n/Translations"
import Tr from "../Base/Tr.svelte" import Tr from "../Base/Tr.svelte"
import UploadingImageCounter from "./UploadingImageCounter.svelte" import UploadingImageCounter from "./UploadingImageCounter.svelte"
import FileSelector from "../Base/FileSelector.svelte" import FileSelector from "../Base/FileSelector.svelte"
import Camera_plus from "../../assets/svg/Camera_plus.svelte" import Camera_plus from "../../assets/svg/Camera_plus.svelte"
import LoginButton from "../Base/LoginButton.svelte"
export let state: SpecialVisualizationState export let state: SpecialVisualizationState
export let tags: Store<OsmTags> export let tags: Store<OsmTags>
export let targetKey: string = undefined export let targetKey: string = undefined
/** /**
* Image to show in the button * Image to show in the button
* NOT the image to upload! * NOT the image to upload!
*/ */
export let image: string = undefined export let image: string = undefined
if (image === "") { if (image === "") {
image = undefined image = undefined
} }
export let labelText: string = undefined export let labelText: string = undefined
const t = Translations.t.image const t = Translations.t.image
let licenseStore = state?.userRelatedState?.imageLicense ?? new ImmutableStore("CC0") let licenseStore = state?.userRelatedState?.imageLicense ?? new ImmutableStore("CC0")
function handleFiles(files: FileList) { function handleFiles(files: FileList) {
for (let i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
const file = files.item(i) const file = files.item(i)
console.log("Got file", file.name) console.log("Got file", file.name)
try { try {
state?.imageUploadManager.uploadImageAndApply(file, tags, targetKey) state?.imageUploadManager.uploadImageAndApply(file, tags, targetKey)
} catch (e) { } catch (e) {
alert(e) alert(e)
} }
}
} }
}
</script> </script>
<LoginToggle {state}> <LoginToggle {state}>
<Tr slot="not-logged-in" t={t.pleaseLogin} /> <LoginButton slot="not-logged-in" clss="small w-full">
<Tr t={Translations.t.image.pleaseLogin} />
</LoginButton>
<div class="flex flex-col"> <div class="flex flex-col">
<UploadingImageCounter {state} {tags} /> <UploadingImageCounter {state} {tags} />
<FileSelector <FileSelector

View file

@ -1,95 +1,95 @@
<script lang="ts"> <script lang="ts">
import LoginToggle from "../../Base/LoginToggle.svelte" import LoginToggle from "../../Base/LoginToggle.svelte"
import type { SpecialVisualizationState } from "../../SpecialVisualization" import type { SpecialVisualizationState } from "../../SpecialVisualization"
import Translations from "../../i18n/Translations" import Translations from "../../i18n/Translations"
import Tr from "../../Base/Tr.svelte" import Tr from "../../Base/Tr.svelte"
import { TrashIcon } from "@babeard/svelte-heroicons/mini" import { TrashIcon } from "@babeard/svelte-heroicons/mini"
import type { OsmId, OsmTags } from "../../../Models/OsmFeature" import type { OsmId, OsmTags } from "../../../Models/OsmFeature"
import DeleteConfig from "../../../Models/ThemeConfig/DeleteConfig" import DeleteConfig from "../../../Models/ThemeConfig/DeleteConfig"
import TagRenderingQuestion from "../TagRendering/TagRenderingQuestion.svelte" import TagRenderingQuestion from "../TagRendering/TagRenderingQuestion.svelte"
import type { Feature } from "geojson" import type { Feature } from "geojson"
import { UIEventSource } from "../../../Logic/UIEventSource" import { UIEventSource } from "../../../Logic/UIEventSource"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
import { TagsFilter } from "../../../Logic/Tags/TagsFilter" import { TagsFilter } from "../../../Logic/Tags/TagsFilter"
import { XCircleIcon } from "@rgossiaux/svelte-heroicons/solid" import { XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
import { TagUtils } from "../../../Logic/Tags/TagUtils" import { TagUtils } from "../../../Logic/Tags/TagUtils"
import OsmChangeAction from "../../../Logic/Osm/Actions/OsmChangeAction" import OsmChangeAction from "../../../Logic/Osm/Actions/OsmChangeAction"
import DeleteAction from "../../../Logic/Osm/Actions/DeleteAction" import DeleteAction from "../../../Logic/Osm/Actions/DeleteAction"
import ChangeTagAction from "../../../Logic/Osm/Actions/ChangeTagAction" import ChangeTagAction from "../../../Logic/Osm/Actions/ChangeTagAction"
import Loading from "../../Base/Loading.svelte" import Loading from "../../Base/Loading.svelte"
import { DeleteFlowState } from "./DeleteFlowState" import { DeleteFlowState } from "./DeleteFlowState"
import { twJoin } from "tailwind-merge" import { twJoin } from "tailwind-merge"
export let state: SpecialVisualizationState export let state: SpecialVisualizationState
export let deleteConfig: DeleteConfig export let deleteConfig: DeleteConfig
export let tags: UIEventSource<OsmTags> export let tags: UIEventSource<OsmTags>
let featureId: OsmId = <OsmId>tags.data.id let featureId: OsmId = <OsmId>tags.data.id
export let feature: Feature export let feature: Feature
export let layer: LayerConfig export let layer: LayerConfig
const deleteAbility = new DeleteFlowState(featureId, state, deleteConfig.neededChangesets) const deleteAbility = new DeleteFlowState(featureId, state, deleteConfig.neededChangesets)
const canBeDeleted: UIEventSource<boolean | undefined> = deleteAbility.canBeDeleted const canBeDeleted: UIEventSource<boolean | undefined> = deleteAbility.canBeDeleted
const canBeDeletedReason = deleteAbility.canBeDeletedReason const canBeDeletedReason = deleteAbility.canBeDeletedReason
const hasSoftDeletion = deleteConfig.softDeletionTags !== undefined const hasSoftDeletion = deleteConfig.softDeletionTags !== undefined
let currentState: "start" | "confirm" | "applying" | "deleted" = "start" let currentState: "start" | "confirm" | "applying" | "deleted" = "start"
$: { $: {
deleteAbility.CheckDeleteability(true) deleteAbility.CheckDeleteability(true)
}
const t = Translations.t.delete
let selectedTags: TagsFilter
let changedProperties = undefined
$: changedProperties = TagUtils.changeAsProperties(selectedTags?.asChange(tags?.data ?? {}) ?? [])
let isHardDelete = undefined
$: isHardDelete = changedProperties[DeleteConfig.deleteReasonKey] !== undefined
async function onDelete() {
if (selectedTags === undefined) {
return
}
currentState = "applying"
let actionToTake: OsmChangeAction
const changedProperties = TagUtils.changeAsProperties(selectedTags.asChange(tags?.data ?? {}))
const deleteReason = changedProperties[DeleteConfig.deleteReasonKey]
if (deleteReason) {
// This is a proper, hard deletion
actionToTake = new DeleteAction(
featureId,
deleteConfig.softDeletionTags,
{
theme: state?.layout?.id ?? "unknown",
specialMotivation: deleteReason,
},
canBeDeleted.data
)
} else {
// no _delete_reason is given, which implies that this is _not_ a deletion but merely a retagging via a nonDeleteMapping
actionToTake = new ChangeTagAction(featureId, selectedTags, tags.data, {
theme: state?.layout?.id ?? "unkown",
changeType: "special-delete",
})
} }
await state.changes?.applyAction(actionToTake) const t = Translations.t.delete
tags.data["_deleted"] = "yes"
tags.ping() let selectedTags: TagsFilter
currentState = "deleted" let changedProperties = undefined
} $: changedProperties = TagUtils.changeAsProperties(selectedTags?.asChange(tags?.data ?? {}) ?? [])
let isHardDelete = undefined
$: isHardDelete = changedProperties[DeleteConfig.deleteReasonKey] !== undefined
async function onDelete() {
if (selectedTags === undefined) {
return
}
currentState = "applying"
let actionToTake: OsmChangeAction
const changedProperties = TagUtils.changeAsProperties(selectedTags.asChange(tags?.data ?? {}))
const deleteReason = changedProperties[DeleteConfig.deleteReasonKey]
if (deleteReason) {
// This is a proper, hard deletion
actionToTake = new DeleteAction(
featureId,
deleteConfig.softDeletionTags,
{
theme: state?.layout?.id ?? "unknown",
specialMotivation: deleteReason,
},
canBeDeleted.data,
)
} else {
// no _delete_reason is given, which implies that this is _not_ a deletion but merely a retagging via a nonDeleteMapping
actionToTake = new ChangeTagAction(featureId, selectedTags, tags.data, {
theme: state?.layout?.id ?? "unkown",
changeType: "special-delete",
})
}
await state.changes?.applyAction(actionToTake)
tags.data["_deleted"] = "yes"
tags.ping()
currentState = "deleted"
}
</script> </script>
<LoginToggle ignoreLoading={true} {state}>
{#if $canBeDeleted === false && !hasSoftDeletion} {#if $canBeDeleted === false && !hasSoftDeletion}
<div class="low-interaction flex flex-col"> <div class="low-interaction flex flex-col">
<Tr t={$canBeDeletedReason} /> <Tr t={$canBeDeletedReason} />
<Tr cls="subtle" t={t.useSomethingElse} /> <Tr cls="subtle" t={t.useSomethingElse} />
</div> </div>
{:else} {:else}
<LoginToggle ignoreLoading={true} {state}>
{#if currentState === "start"} {#if currentState === "start"}
<button <button
class="flex items-center" class="flex items-center"
@ -158,5 +158,5 @@
<Tr t={t.isDeleted} /> <Tr t={t.isDeleted} />
</div> </div>
{/if} {/if}
</LoginToggle> {/if}
{/if} </LoginToggle>

View file

@ -1,61 +1,63 @@
<script lang="ts"> <script lang="ts">
/** /**
* Show nearby images which can be clicked * Show nearby images which can be clicked
*/ */
import type { OsmTags } from "../../Models/OsmFeature" import type { OsmTags } from "../../Models/OsmFeature"
import { Store, UIEventSource } from "../../Logic/UIEventSource" import { Store, UIEventSource } from "../../Logic/UIEventSource"
import type { SpecialVisualizationState } from "../SpecialVisualization" import type { SpecialVisualizationState } from "../SpecialVisualization"
import type { P4CPicture } from "../../Logic/Web/NearbyImagesSearch" import type { P4CPicture } from "../../Logic/Web/NearbyImagesSearch"
import NearbyImagesSearch from "../../Logic/Web/NearbyImagesSearch" import NearbyImagesSearch from "../../Logic/Web/NearbyImagesSearch"
import LinkableImage from "./LinkableImage.svelte" import LinkableImage from "./LinkableImage.svelte"
import type { Feature } from "geojson" import type { Feature } from "geojson"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import Loading from "../Base/Loading.svelte" import Loading from "../Base/Loading.svelte"
import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders" import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders"
import Tr from "../Base/Tr.svelte" import Tr from "../Base/Tr.svelte"
import Translations from "../i18n/Translations" import Translations from "../i18n/Translations"
import LoginToggle from "../Base/LoginToggle.svelte"
export let tags: Store<OsmTags> export let tags: Store<OsmTags>
export let state: SpecialVisualizationState export let state: SpecialVisualizationState
export let lon: number export let lon: number
export let lat: number export let lat: number
export let feature: Feature export let feature: Feature
export let linkable: boolean = true export let linkable: boolean = true
export let layer: LayerConfig export let layer: LayerConfig
let imagesProvider = new NearbyImagesSearch( let imagesProvider = new NearbyImagesSearch(
{ {
lon, lon,
lat, lat,
allowSpherical: new UIEventSource<boolean>(false), allowSpherical: new UIEventSource<boolean>(false),
blacklist: AllImageProviders.LoadImagesFor(tags), blacklist: AllImageProviders.LoadImagesFor(tags),
}, },
state.indexedFeatures state.indexedFeatures,
) )
let images: Store<P4CPicture[]> = imagesProvider.store.map((images) => images.slice(0, 20)) let images: Store<P4CPicture[]> = imagesProvider.store.map((images) => images.slice(0, 20))
let allDone = imagesProvider.allDone let allDone = imagesProvider.allDone
</script> </script>
<LoginToggle {state}>
<div class="interactive border-interactive rounded-2xl p-2"> <div class="interactive border-interactive rounded-2xl p-2">
<div class="flex justify-between"> <div class="flex justify-between">
<h4> <h4>
<Tr t={Translations.t.image.nearby.title} /> <Tr t={Translations.t.image.nearby.title} />
</h4> </h4>
<slot name="corner" /> <slot name="corner" />
</div> </div>
{#if !$allDone} {#if !$allDone}
<Loading /> <Loading />
{:else if $images.length === 0} {:else if $images.length === 0}
<Tr t={Translations.t.image.nearby.noNearbyImages} cls="alert"/> <Tr t={Translations.t.image.nearby.noNearbyImages} cls="alert" />
{: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-1 overflow-x-auto" style="scroll-snap-type: x proximity">
{#each $images as image (image.pictureUrl)} {#each $images 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} {lon} {lat} {feature} {layer} {linkable} /> <LinkableImage {tags} {image} {state} {lon} {lat} {feature} {layer} {linkable} />
</span> </span>
{/each} {/each}
</div> </div>
{/if} {/if}
</div> </div>
</LoginToggle>

View file

@ -12,6 +12,7 @@
import { XCircleIcon } from "@babeard/svelte-heroicons/solid" import { XCircleIcon } from "@babeard/svelte-heroicons/solid"
import exp from "constants" import exp from "constants"
import Camera_plus from "../../assets/svg/Camera_plus.svelte" import Camera_plus from "../../assets/svg/Camera_plus.svelte"
import LoginToggle from "../Base/LoginToggle.svelte"
export let tags: Store<OsmTags> export let tags: Store<OsmTags>
export let state: SpecialVisualizationState export let state: SpecialVisualizationState
@ -25,7 +26,8 @@
let expanded = false let expanded = false
</script> </script>
<LoginToggle {state}>
{#if expanded} {#if expanded}
<NearbyImages {tags} {state} {lon} {lat} {feature} {linkable}> <NearbyImages {tags} {state} {lon} {lat} {feature} {linkable}>
<XCircleIcon <XCircleIcon
@ -47,3 +49,4 @@
<Tr t={t.seeNearby} /> <Tr t={t.seeNearby} />
</button> </button>
{/if} {/if}
</LoginToggle>