UX: improve 'plantnet detection' UI, fix #1989 (technically, the text was there all along)

This commit is contained in:
Pieter Vander Vennet 2025-05-06 13:57:29 +02:00
parent 01d924374e
commit 6c730f5581
2 changed files with 73 additions and 54 deletions

View file

@ -734,7 +734,8 @@
"li1": "take a picture which shows a single leaf", "li1": "take a picture which shows a single leaf",
"li2": "take a picture which shows the bark", "li2": "take a picture which shows the bark",
"li3": "take a picture of the flowers", "li3": "take a picture of the flowers",
"li4": "take a picture of the fruits" "li4": "take a picture of the fruits",
"title": "What pictures to take for automatic detection?"
}, },
"loadingWikidata": "Loading information about {species}…", "loadingWikidata": "Loading information about {species}…",
"matchPercentage": "{match}% match", "matchPercentage": "{match}% match",

View file

@ -11,6 +11,7 @@
import WikipediaPanel from "../Wikipedia/WikipediaPanel.svelte" import WikipediaPanel from "../Wikipedia/WikipediaPanel.svelte"
import Plantnet_logo from "../../assets/svg/Plantnet_logo.svelte" import Plantnet_logo from "../../assets/svg/Plantnet_logo.svelte"
import ArrowPath from "@babeard/svelte-heroicons/mini/ArrowPath" import ArrowPath from "@babeard/svelte-heroicons/mini/ArrowPath"
import AccordionSingle from "../Flowbite/AccordionSingle.svelte"
/** /**
* The main entry point for the plantnet wizard * The main entry point for the plantnet wizard
@ -23,7 +24,6 @@
*/ */
export let imageUrls: Store<string[]> export let imageUrls: Store<string[]>
export let onConfirm: (wikidataId: string) => void export let onConfirm: (wikidataId: string) => void
let collapsedMode = true
let options: UIEventSource<PlantNetSpeciesMatch[]> = new UIEventSource<PlantNetSpeciesMatch[]>( let options: UIEventSource<PlantNetSpeciesMatch[]> = new UIEventSource<PlantNetSpeciesMatch[]>(
undefined undefined
) )
@ -35,6 +35,8 @@
*/ */
let selectedOption: string let selectedOption: string
let running = false
let expandExplanation = false
let done = false let done = false
function speciesSelected(species: string) { function speciesSelected(species: string) {
@ -44,69 +46,52 @@
async function detectSpecies() { async function detectSpecies() {
error = undefined error = undefined
collapsedMode = false running = true
try { try {
const result: PlantNetResult | "no_plant_detected" = await PlantNet.query(imageUrls.data.slice(0, 5)) const result: PlantNetResult | "no_plant_detected" = await PlantNet.query(imageUrls.data.slice(0, 5))
if (result === "no_plant_detected") { if (result === "no_plant_detected") {
error = "no_plant_detected" error = "no_plant_detected"
expandExplanation = true
return return
} }
options.set(result.results.filter((r) => r.score > 0.005).slice(0, 8)) const filteredResults = result.results.filter((r) => r.score > 0.005).slice(0, 8)
expandExplanation = filteredResults.length == 0
options.set(filteredResults)
} catch (e) { } catch (e) {
console.error("Caught", e) console.error("Caught", e)
error = e error = e
running = false
} }
} }
</script> </script>
<div class="flex flex-col"> <div class="flex flex-col mb-4">
{#if collapsedMode}
<button class="w-full" on:click={detectSpecies}> <!-- Error message -->
<Tr t={t.button} /> {#if error === "no_plant_detected"}
</button>
{:else if error === "no_plant_detected"}
<Tr cls="alert" t={t.noPlantDetected} /> <Tr cls="alert" t={t.noPlantDetected} />
<button on:click={() => detectSpecies()}>
<ArrowPath class="h-6 w-6" />
<Tr t={Translations.t.general.retry} />
</button>
{:else if error !== undefined} {:else if error !== undefined}
<Tr cls="alert" t={t.error.Subs({ error })} /> <Tr cls="alert" t={t.error.Subs({ error })} />
<button on:click={() => detectSpecies()}> {/if}
<ArrowPath class="h-6 w-6" />
<Tr t={Translations.t.general.retry} />
<!-- Button. If 'done==false', the button should be shown, otherwise <PlantnetSpeciesList> will handle the flow -->
{#if !done && !running && !selectedOption}
<button class="w-full flex" on:click={detectSpecies}>
<Plantnet_logo class="mr-2 shrink-0 h-8 w-8 rounded-full bg-white p-1" />
<span class="flex flex-col items-center">
<Tr t={t.button} />
{#if error !== undefined}
<div class="flex subtle mt-2">
<ArrowPath class="h-6 w-6" />
<Tr t={Translations.t.general.retry} />
</div>
{/if}
</span>
</button> </button>
{:else if $imageUrls.length === 0} {:else if !selectedOption}
<!-- No urls are available, show the explanation instead-->
<div class=" border-region relative mb-1 p-2">
<XCircleIcon
class="absolute right-0 top-0 m-4 h-8 w-8 cursor-pointer"
on:click={() => {
collapsedMode = true
}}
/>
<Tr t={t.takeImages} />
<Tr t={t.howTo.intro} />
<ul>
<li>
<Tr t={t.howTo.li0} />
</li>
<li>
<Tr t={t.howTo.li1} />
</li>
<li>
<Tr t={t.howTo.li2} />
</li>
<li>
<Tr t={t.howTo.li3} />
</li>
<li>
<Tr t={t.howTo.li4} />
</li>
</ul>
</div>
{:else if selectedOption === undefined}
<PlantNetSpeciesList <PlantNetSpeciesList
{options} {options}
numberOfImages={$imageUrls.length} numberOfImages={$imageUrls.length}
@ -115,9 +100,7 @@
<XCircleIcon <XCircleIcon
slot="upper-right" slot="upper-right"
class="m-4 h-8 w-8 cursor-pointer" class="m-4 h-8 w-8 cursor-pointer"
on:click={() => { on:click={() => {running = false}}
collapsedMode = true
}}
/> />
</PlantNetSpeciesList> </PlantNetSpeciesList>
{:else if !done} {:else if !done}
@ -158,8 +141,43 @@
<Tr t={t.tryAgain} /> <Tr t={t.tryAgain} />
</BackButton> </BackButton>
{/if} {/if}
<div class="low-interaction flex self-end rounded-xl p-2">
<Plantnet_logo class="mr-1 h-8 w-8 rounded-full bg-white p-1" /> <!-- Explanation -->
<Tr t={t.poweredByPlantnet} /> <div class="my-1 low-interaction">
<AccordionSingle noBorder expanded={expandExplanation}>
<div class="subtle flex justify-start text-sm p-1" slot="header">
<Tr t={t.howTo.title} />
</div>
<div class="p-4 border-low-interaction border-l border-r border-b flex flex-col">
<Tr t={t.takeImages} />
<Tr t={t.howTo.intro} />
<ul>
<li>
<Tr t={t.howTo.li0} />
</li>
<li>
<Tr t={t.howTo.li1} />
</li>
<li>
<Tr t={t.howTo.li2} />
</li>
<li>
<Tr t={t.howTo.li3} />
</li>
<li>
<Tr t={t.howTo.li4} />
</li>
</ul>
<!-- Attribution -->
<div class="low-interaction flex items-center self-end rounded-xl p-2 px-3 mt-4 w-fit">
<Plantnet_logo class="mr-2 h-8 w-8 rounded-full bg-white p-1" />
<Tr t={t.poweredByPlantnet} />
</div>
</div>
</AccordionSingle>
</div> </div>
</div> </div>