Many UI improvements

This commit is contained in:
Pieter Vander Vennet 2024-06-18 03:33:11 +02:00
parent ef158ec914
commit 1098d71aa6
30 changed files with 5601 additions and 569 deletions

View file

@ -76,7 +76,7 @@
</div>
{:else}
<div
class="selected-element-view flex h-full w-full flex-col gap-y-2 overflow-y-auto p-1 px-4"
class="selected-element-view flex h-full w-full flex-col gap-y-1 overflow-y-auto p-1 px-4"
tabindex="-1"
>
{#each $knownTagRenderings as config (config.id)}

View file

@ -114,7 +114,7 @@
<Tr t={t.allIncluded.Subs({ source: sourceUrl })} />
</div>
{:else}
<div class="low-interaction border-interactive p-1">
<div class="low-interaction p-1">
{#if !readonly}
<Tr t={t.loadedFrom.Subs({ url: sourceUrl, source: sourceUrl })} />
{/if}

View file

@ -12,6 +12,7 @@
import type { OsmTags } from "../../Models/OsmFeature"
import Translations from "../i18n/Translations"
import Tr from "../Base/Tr.svelte"
import AccordionSingle from "../Flowbite/AccordionSingle.svelte"
export let externalData: Store<
| { success: { content: Record<string, string> } }
@ -25,6 +26,8 @@
export let feature: Feature
export let readonly = false
export let sourceUrl: Store<string>
export let collapsed : boolean
</script>
{#if !$sourceUrl}
@ -32,11 +35,12 @@
{:else if $externalData === undefined}
<Loading />
{:else if $externalData["error"] !== undefined}
<div class="alert flex">
<Tr t={Translations.t.general.error} />
{$externalData["error"]}
<div class="subtle italic low-interaction p-2 px-4 rounded">
<Tr t={Translations.t.external.error} />
</div>
{:else if $externalData["success"] !== undefined}
<AccordionSingle>
<span slot="header">Structured data from the website</span>
<ComparisonTable
externalProperties={$externalData["success"]}
{state}
@ -46,4 +50,5 @@
{readonly}
sourceUrl={$sourceUrl}
/>
</AccordionSingle>
{/if}

View file

@ -0,0 +1,17 @@
<script lang="ts">
import { Accordion, AccordionItem } from "flowbite-svelte"
export let expanded = false
</script>
<Accordion>
<AccordionItem open={expanded} paddingDefault="p-0" inactiveClass="text-black">
<span slot="header" class="text-base p-2 ">
<slot name="header" />
</span>
<div class="low-interaction p-2 rounded-b">
<slot />
</div>
</AccordionItem>
</Accordion>

View file

@ -30,7 +30,7 @@
lon,
lat,
allowSpherical: new UIEventSource<boolean>(false),
blacklist: AllImageProviders.LoadImagesFor(tags),
blacklist: AllImageProviders.LoadImagesFor(tags)
},
state.indexedFeatures
)
@ -39,24 +39,23 @@
let allDone = imagesProvider.allDone
</script>
<div class="interactive border-interactive rounded-2xl p-2">
<div class="flex justify-between">
<h4>
<Tr t={Translations.t.image.nearby.title} />
</h4>
<slot name="corner" />
</div>
{#if !$allDone}
<Loading />
{:else if $images.length === 0}
<Tr t={Translations.t.image.nearby.noNearbyImages} cls="alert" />
{:else}
<div class="flex w-full space-x-1 overflow-x-auto" style="scroll-snap-type: x proximity">
{#each $images as image (image.pictureUrl)}
<div class="flex justify-between">
<h4>
<Tr t={Translations.t.image.nearby.title} />
</h4>
<slot name="corner" />
</div>
{#if !$allDone}
<Loading />
{:else if $images.length === 0}
<Tr t={Translations.t.image.nearby.noNearbyImages} cls="alert" />
{:else}
<div class="flex w-full space-x-1 overflow-x-auto" style="scroll-snap-type: x proximity">
{#each $images as image (image.pictureUrl)}
<span class="w-fit shrink-0" style="scroll-snap-align: start">
<LinkableImage {tags} {image} {state} {feature} {layer} {linkable} />
</span>
{/each}
</div>
{/if}
</div>
{/each}
</div>
{/if}

View file

@ -11,6 +11,8 @@
import Camera_plus from "../../assets/svg/Camera_plus.svelte"
import LoginToggle from "../Base/LoginToggle.svelte"
import { ariaLabel } from "../../Utils/ariaLabel"
import { Accordion, AccordionItem } from "flowbite-svelte"
import AccordionSingle from "../Flowbite/AccordionSingle.svelte"
export let tags: UIEventSource<OsmTags>
export let state: SpecialVisualizationState
@ -25,31 +27,10 @@
let expanded = false
</script>
<div class="my-4">
{#if expanded}
<NearbyImages {tags} {state} {lon} {lat} {feature} {linkable} {layer}>
<button
slot="corner"
class="no-image-background h-6 w-6 cursor-pointer border-none p-0"
use:ariaLabel={t.close}
on:click={() => {
expanded = false
}}
>
<XCircleIcon />
</button>
</NearbyImages>
{:else}
<button
class="flex w-full items-center"
style="margin-left: 0; margin-right: 0"
on:click={() => {
expanded = true
}}
aria-expanded={expanded}
>
<Camera_plus class="mr-2 block h-8 w-8 p-1" />
<AccordionSingle>
<span slot="header" class="text-base p-2">
<Tr t={t.seeNearby} />
</button>
{/if}
</div>
</span>
<NearbyImages {tags} {state} {lon} {lat} {feature} {linkable} {layer} />
</AccordionSingle>

View file

@ -11,9 +11,9 @@
import Tr from "../Base/Tr.svelte"
import UploadingImageCounter from "./UploadingImageCounter.svelte"
import FileSelector from "../Base/FileSelector.svelte"
import Camera_plus from "../../assets/svg/Camera_plus.svelte"
import LoginButton from "../Base/LoginButton.svelte"
import { Translation } from "../i18n/Translation"
import Camera from "@babeard/svelte-heroicons/solid/Camera"
export let state: SpecialVisualizationState
@ -73,7 +73,7 @@
{#if image !== undefined}
<img src={image} aria-hidden="true" />
{:else}
<Camera_plus class="block h-12 w-12 p-1 text-4xl" aria-hidden="true" />
<Camera class="h-12 w-12 p-1" aria-hidden="true" />
{/if}
{#if labelText}
{labelText}

View file

@ -18,6 +18,9 @@
import Loading from "../../Base/Loading.svelte"
import { DeleteFlowState } from "./DeleteFlowState"
import { twJoin } from "tailwind-merge"
import AccordionSingle from "../../Flowbite/AccordionSingle.svelte"
import Trash from "@babeard/svelte-heroicons/mini/Trash"
import Invalid from "../../../assets/svg/Invalid.svelte"
export let state: SpecialVisualizationState
export let deleteConfig: DeleteConfig
@ -35,7 +38,7 @@
const canBeDeletedReason = deleteAbility.canBeDeletedReason
const hasSoftDeletion = deleteConfig.softDeletionTags !== undefined
let currentState: "start" | "confirm" | "applying" | "deleted" = "start"
let currentState: "confirm" | "applying" | "deleted" = "confirm"
$: {
deleteAbility.CheckDeleteability(true)
}
@ -63,7 +66,7 @@
deleteConfig.softDeletionTags,
{
theme: state?.layout?.id ?? "unknown",
specialMotivation: deleteReason,
specialMotivation: deleteReason
},
canBeDeleted.data
)
@ -71,7 +74,7 @@
// 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",
changeType: "special-delete"
})
}
@ -84,23 +87,32 @@
<LoginToggle ignoreLoading={true} {state}>
{#if $canBeDeleted === false && !hasSoftDeletion}
<div class="low-interaction flex flex-col">
<Tr t={$canBeDeletedReason} />
<Tr cls="subtle" t={t.useSomethingElse} />
<div class="low-interaction rounded text-sm flex p-2 italic subtle gap-x-1">
<div class="relative h-fit">
<Trash class="w-8 h-8 pb-1" />
<Invalid class="absolute bottom-0 right-0 w-5 h-5"/>
</div>
<div class="flex flex-col">
<Tr t={t.cannotBeDeleted} />
<Tr t={$canBeDeletedReason} />
<Tr t={t.useSomethingElse} />
</div>
</div>
{:else if currentState === "start"}
<button
class="w-full"
on:click={() => {
currentState = "confirm"
}}
>
{:else}
<AccordionSingle>
<span slot="header" class="flex">
<TrashIcon class="h-6 w-6" />
<Tr t={t.delete} />
</button>
{:else if currentState === "confirm"}
</span>
<span>
{#if currentState === "confirm"}
<TagRenderingQuestion
bind:selectedTags
clss=""
{tags}
config={deleteConfig.constructTagRendering()}
{state}
@ -123,11 +135,9 @@
/>
<Tr t={t.delete} />
</button>
<button slot="cancel" class="items-center" on:click={() => (currentState = "start")}>
<Tr t={t.cancel} />
</button>
<div slot="under-buttons">
<div slot="under-buttons" class="italic subtle">
{#if selectedTags !== undefined}
{#if canBeDeleted && isHardDelete}
<!-- This is a hard delete - explain that this is a hard delete...-->
@ -149,4 +159,9 @@
<Tr t={t.isDeleted} />
</div>
{/if}
</span>
</AccordionSingle>
{/if}
</LoginToggle>

View file

@ -26,7 +26,7 @@
<LoginToggle ignoreLoading={true} {state}>
{#if $isFavourite}
<div class="flex h-fit items-start">
<button on:click={() => markFavourite(false)}>
<button class="w-full" on:click={() => markFavourite(false)}>
<HeartSolidIcon class="mr-2 w-16 shrink-0" on:click={() => markFavourite(false)} />
<div class="flex flex-col items-start">
<Tr t={t.button.unmark} />
@ -36,7 +36,7 @@
</div>
<Tr cls="font-bold thanks m-2 p-2 block" t={t.button.isFavourite} />
{:else}
<button on:click={() => markFavourite(true)}>
<button class="w-full" on:click={() => markFavourite(true)}>
<HeartOutlineIcon class="mr-2 w-16 shrink-0" on:click={() => markFavourite(true)} />
<div class="flex w-full flex-col items-start">

View file

@ -19,16 +19,21 @@
import If from "../Base/If.svelte"
import Constants from "../../Models/Constants"
import LoginToggle from "../Base/LoginToggle.svelte"
import AccordionSingle from "../Flowbite/AccordionSingle.svelte"
import BackButton from "../Base/BackButton.svelte"
import ChevronLeft from "@babeard/svelte-heroicons/solid/ChevronLeft"
import ThemeViewState from "../../Models/ThemeViewState"
export let state: SpecialVisualizationState
export let state: ThemeViewState
export let layer: LayerConfig
export let featureToMove: Feature<Point>
let id: string = featureToMove.properties.id
let currentStep: "start" | "reason" | "pick_location" | "moved" = "start"
let currentStep: "reason" | "pick_location" | "moved" = "reason"
const t = Translations.t.move
const reason = new UIEventSource<MoveReason>(undefined)
let reason = new UIEventSource<MoveReason>(undefined)
let [lon, lat] = GeoOperations.centerpointCoordinates(featureToMove)
let newLocation = new UIEventSource<{ lon: number; lat: number }>(undefined)
@ -42,11 +47,14 @@
location: new UIEventSource({ lon, lat }),
minzoom: new UIEventSource($reason.minZoom),
rasterLayer: state.mapProperties.rasterLayer,
zoom: new UIEventSource($reason?.startZoom ?? 16),
zoom: new UIEventSource($reason?.startZoom ?? 16)
}
}
let moveWizardState = new MoveWizardState(id, layer.allowMove, state)
if(moveWizardState.reasons.length === 1){
reason.setData(moveWizardState.reasons[0])
}
let notAllowed = moveWizardState.moveDisallowedReason
let currentMapProperties: MapProperties = undefined
</script>
@ -61,109 +69,91 @@
<Tr t={$notAllowed} />
</div>
</div>
{:else if currentStep === "start"}
{#if moveWizardState.reasons.length === 1}
<button
class="w-full"
on:click={() => {
reason.setData(moveWizardState.reasons[0])
currentStep = "pick_location"
}}
>
<ToSvelte
construct={moveWizardState.reasons[0].icon.SetStyle("height: 1.5rem; width: 1.5rem;")}
/>
<Tr t={Translations.T(moveWizardState.reasons[0].invitingText)} />
</button>
{:else}
<button
class="w-full"
on:click={() => {
currentStep = "reason"
}}
>
<Move class="h-6 w-6" />
<Tr t={t.inviteToMove.generic} />
</button>
{/if}
{:else if currentStep === "reason"}
<div class="interactive border-interactive flex flex-col p-2">
<Tr cls="text-lg font-bold" t={t.whyMove} />
{#each moveWizardState.reasons as reasonSpec}
<button
on:click={() => {
reason.setData(reasonSpec)
currentStep = "pick_location"
}}
>
<ToSvelte construct={reasonSpec.icon.SetClass("w-16 h-16 pr-2")} />
<Tr t={Translations.T(reasonSpec.text)} />
</button>
{/each}
</div>
{:else if currentStep === "pick_location"}
<div class="border-interactive interactive flex flex-col p-2">
<Tr cls="text-lg font-bold" t={t.moveTitle} />
<div class="relative h-64 w-full">
<LocationInput
mapProperties={(currentMapProperties = initMapProperties())}
value={newLocation}
initialCoordinate={{ lon, lat }}
/>
<div class="absolute bottom-0 left-0">
<OpenBackgroundSelectorButton {state} />
</div>
</div>
{#if $reason.includeSearch}
<Geosearch bounds={currentMapProperties.bounds} clearAfterView={false} />
{/if}
<div class="flex flex-wrap">
<If
condition={currentMapProperties.zoom.mapD(
(zoom) => zoom >= Constants.minZoomLevelToAddNewPoint
)}
>
<button
class="primary w-full"
on:click={() => {
moveWizardState.moveFeature(newLocation.data, reason.data, featureToMove)
currentStep = "moved"
{:else}
<AccordionSingle>
<span slot="header" class="flex">
{#if moveWizardState.reasons.length === 1}
<ToSvelte
construct={moveWizardState.reasons[0].icon.SetStyle("height: 1.5rem; width: 1.5rem;")}
/>
<Tr t={Translations.T(moveWizardState.reasons[0].invitingText)} />
{:else}
<Move class="h-6 w-6" />
<Tr t={t.inviteToMove.generic} />
{/if}
</span>
<span class="flex flex-col p-2">
{#if currentStep === "reason" && moveWizardState.reasons.length > 1}
<Tr cls="text-lg font-bold" t={t.whyMove} />
{#each moveWizardState.reasons as reasonSpec}
<button
on:click={() => {
reason.setData(reasonSpec)
currentStep = "pick_location"
}}
>
<Tr t={t.confirmMove} />
</button>
<div slot="else" class="alert">
<Tr t={t.zoomInFurther} />
>
<ToSvelte construct={reasonSpec.icon.SetClass("w-16 h-16 pr-2")} />
<Tr t={Translations.T(reasonSpec.text)} />
</button>
{/each}
{:else if currentStep === "pick_location" || currentStep === "reason"}
<div class="relative h-64 w-full">
<LocationInput
mapProperties={(currentMapProperties = initMapProperties())}
value={newLocation}
initialCoordinate={{ lon, lat }}
/>
<div class="absolute bottom-0 left-0">
<OpenBackgroundSelectorButton {state} />
</div>
</div>
</If>
<button
class="w-full"
on:click={() => {
currentStep = "start"
}}
>
<XCircleIcon class="mr-2 h-6 w-6" />
<Tr t={t.cancel} />
</button>
{#if $reason.includeSearch}
<Geosearch bounds={currentMapProperties.bounds} clearAfterView={false} />
{/if}
<div class="flex flex-wrap">
<If
condition={currentMapProperties.zoom.mapD(
(zoom) => zoom >= Constants.minZoomLevelToAddNewPoint
)}
>
<button
class="primary w-full"
on:click={() => {
moveWizardState.moveFeature(newLocation.data, reason.data, featureToMove)
currentStep = "moved"
}}
>
<Tr t={t.confirmMove} />
</button>
<div slot="else" class="alert w-full">
<Tr t={t.zoomInFurther} />
</div>
</If>
{#if moveWizardState.reasons.length > 1}
<button class="w-full" on:click={() => {currentStep = "reason"}}>
<ChevronLeft class="w-6 h-6" />
<Tr t={t.cancel} />
</button>
{/if}
</div>
</div>
{:else if currentStep === "moved"}
<div class="flex flex-col">
<Tr cls="thanks" t={t.pointIsMoved} />
<button
on:click={() => {
currentStep = "reason"
}}
>
<Move class="h-6 w-6 pr-2" />
<Tr t={t.inviteToMoveAgain} />
</button>
</div>
{:else if currentStep === "moved"}
<div class="flex flex-col">
<Tr cls="thanks" t={t.pointIsMoved} />
<button
on:click={() => {
currentStep = "reason"
}}
>
<Move class="h-6 w-6 pr-2" />
<Tr t={t.inviteToMoveAgain} />
</button>
</div>
{/if}
</span>
</AccordionSingle>
{/if}
{/if}
</LoginToggle>

View file

@ -44,6 +44,8 @@
export let allowDeleteOfFreeform: boolean = false
export let clss = "interactive border-interactive"
let feedback: UIEventSource<Translation> = new UIEventSource<Translation>(undefined)
let unit: Unit = layer?.units?.find((unit) => unit.appliesToKeys.has(config.freeform?.key))
@ -308,9 +310,9 @@
</script>
{#if question !== undefined}
<div class="relative">
<div class={clss} >
<form
class="interactive border-interactive relative flex flex-col overflow-y-auto px-2"
class="relative flex flex-col overflow-y-auto px-2"
style="max-height: 75vh"
on:submit|preventDefault={() => {
/*onSave(); This submit is not needed and triggers to early, causing bugs: see #1808*/
@ -318,7 +320,7 @@
>
<fieldset>
<legend>
<div class="interactive sticky top-0 justify-between pt-1 font-bold" style="z-index: 11">
<div class="sticky top-0 justify-between pt-1 font-bold" style="z-index: 11">
<SpecialTranslation t={question} {tags} {state} {layer} feature={selectedElement} />
</div>
@ -477,7 +479,7 @@
</div>
{/if}
<div
class="interactive sticky bottom-0 flex flex-wrap-reverse items-stretch justify-end sm:flex-nowrap"
class="sticky bottom-0 flex flex-wrap-reverse items-stretch justify-end sm:flex-nowrap"
style="z-index: 11"
>
<!-- TagRenderingQuestion-buttons -->

View file

@ -22,6 +22,7 @@ function filterLangs(maindiv) {
continue
}
if (childLang.value === lang) {
child.style.display = ""
continue
}
child.parentElement.removeChild(child)

View file

@ -1776,6 +1776,11 @@ export default class SpecialVisualizations {
{
name: "mode",
doc: "If `display`, only show the data in tabular and readonly form, ignoring already existing tags. This is used to explicitly show all the tags. If unset or anything else, allow to apply/import on OSM"
},
{
name: "collapsed",
defaultValue: "yes",
doc: "If the containing accordion should be closed"
}
],
needsUrls: [Constants.linkedDataProxy, "http://www.schema.org"],
@ -1789,6 +1794,7 @@ export default class SpecialVisualizations {
const key = argument[0] ?? "website"
const useProxy = argument[1] !== "no"
const readonly = argument[3] === "readonly"
const isClosed = (arguments[4] ?? "yes") === "yes"
const url = tags
.mapD((tags) => {