Refactoring: port PlantNet-detection to svelte, re-integrate wikipedia component

This commit is contained in:
Pieter Vander Vennet 2023-09-20 01:47:32 +02:00
parent d1aa751e18
commit 5f04a69517
18 changed files with 297 additions and 210 deletions

View file

@ -0,0 +1,123 @@
<script lang="ts">
import Translations from "../i18n/Translations";
import Tr from "../Base/Tr.svelte";
import PlantNetSpeciesList from "./PlantNetSpeciesList.svelte";
import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource";
import type { PlantNetSpeciesMatch } from "../../Logic/Web/PlantNet";
import PlantNet from "../../Logic/Web/PlantNet";
import { XCircleIcon } from "@babeard/svelte-heroicons/solid";
import BackButton from "../Base/BackButton.svelte";
import NextButton from "../Base/NextButton.svelte";
import WikipediaPanel from "../Wikipedia/WikipediaPanel.svelte";
import { createEventDispatcher } from "svelte";
import ToSvelte from "../Base/ToSvelte.svelte";
import Svg from "../../Svg";
/**
* The main entry point for the plantnet wizard
*/
const t = Translations.t.plantDetection;
/**
* All the URLs pointing to images of the selected feature.
* We need to feed them into Plantnet when applicable
*/
export let imageUrls: Store<string[]>;
export let onConfirm: (wikidataId: string) => void;
const dispatch = createEventDispatcher<{ selected: string }>();
let collapsedMode = true;
let options: UIEventSource<PlantNetSpeciesMatch[]> = new UIEventSource<PlantNetSpeciesMatch[]>(undefined);
let error: string = undefined;
/**
* The Wikidata-id of the species to apply
*/
let selectedOption: string;
let done = false;
function speciesSelected(species: PlantNetSpeciesMatch) {
console.log("Selected:", species);
selectedOption = species;
}
async function detectSpecies() {
collapsedMode = false;
try {
const result = await PlantNet.query(imageUrls.data.slice(0, 5));
options.set(result.results.filter(r => r.score > 0.005).slice(0, 8));
} catch (e) {
error = e;
}
}
</script>
<div class="flex flex-col">
{#if collapsedMode}
<button class="w-full" on:click={detectSpecies}>
<Tr t={t.button} />
</button>
{:else if $error !== undefined}
<Tr cls="alert" t={t.error.Subs({error})} />
{:else if $imageUrls.length === 0}
<!-- No urls are available, show the explanation instead-->
<div class=" border-region p-2 mb-1 relative">
<XCircleIcon class="absolute top-0 right-0 w-8 h-8 m-4 cursor-pointer"
on:click={() => {collapsedMode = true}}></XCircleIcon>
<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>
</ul>
</div>
{:else if selectedOption === undefined}
<PlantNetSpeciesList {options} numberOfImages={$imageUrls.length}
on:selected={(species) => speciesSelected(species.detail)}>
<XCircleIcon slot="upper-right" class="w-8 h-8 m-4 cursor-pointer"
on:click={() => {collapsedMode = true}}></XCircleIcon>
</PlantNetSpeciesList>
{:else if !done}
<div class="flex flex-col border-interactive">
<div class="m-2">
<WikipediaPanel wikiIds={new ImmutableStore([selectedOption])} />
</div>
<div class="flex justify-between">
<BackButton on:click={() => {selectedOption = undefined}}>
<Tr t={t.back} />
</BackButton>
<NextButton clss="primary shrink-0" on:click={() => { done = true; onConfirm(selectedOption); }} >
<Tr t={t.confirm} />
</NextButton>
</div>
</div>
{:else}
<!-- done ! -->
<Tr t={t.done} cls="thanks w-full" />
<BackButton imageClass="w-6 h-6 shrink-0" clss="p-1 m-0" on:click={() => {done = false; selectedOption = undefined}}>
<Tr t={t.tryAgain} />
</BackButton>
{/if}
<div class="flex p-2 bg-gray-200 rounded-xl self-end">
<ToSvelte construct={Svg.plantnet_logo_svg().SetClass("w-10 h-10 pr-1 mr-1 bg-white rounded-full")} />
<Tr t={t.poweredByPlantnet} />
</div>
</div>

View file

@ -0,0 +1,37 @@
<script lang="ts">/**
* Show the list of options to choose from
*/
import type { PlantNetSpeciesMatch } from "../../Logic/Web/PlantNet";
import { Store } from "../../Logic/UIEventSource";
import Translations from "../i18n/Translations";
import Tr from "../Base/Tr.svelte";
import Loading from "../Base/Loading.svelte";
import SpeciesButton from "./SpeciesButton.svelte";
const t = Translations.t.plantDetection;
export let options: Store<PlantNetSpeciesMatch[]>;
export let numberOfImages: number;
</script>
{#if $options === undefined}
<Loading>
<Tr t={t.querying.Subs({length: numberOfImages})} />
</Loading>
{:else}
<div class="low-interaction border-interactive flex p-2 flex-col relative">
<div class="absolute top-0 right-0" >
<slot name="upper-right"/>
</div>
<h3>
<Tr t={t.overviewTitle} />
</h3>
<Tr t={t.overviewIntro} />
<Tr cls="font-bold" t={t.overviewVerify} />
{#each $options as species}
<SpeciesButton {species} on:selected/>
{/each}
</div>
{/if}

View file

@ -0,0 +1,56 @@
<script lang="ts">/**
* A button to select a single species
*/
import { createEventDispatcher } from "svelte";
import type { PlantNetSpeciesMatch } from "../../Logic/Web/PlantNet";
import { UIEventSource } from "../../Logic/UIEventSource";
import Wikidata from "../../Logic/Web/Wikidata";
import NextButton from "../Base/NextButton.svelte";
import Loading from "../Base/Loading.svelte";
import WikidataPreviewBox from "../Wikipedia/WikidataPreviewBox";
import Tr from "../Base/Tr.svelte";
import Translations from "../i18n/Translations";
import ToSvelte from "../Base/ToSvelte.svelte";
export let species: PlantNetSpeciesMatch;
let wikidata = UIEventSource.FromPromise(
Wikidata.Sparql<{ species }>(
["?species", "?speciesLabel"],
["?species wdt:P846 \"" + species.gbif.id + "\""]
)
);
const dispatch = createEventDispatcher<{ selected: string /* wikidata-id*/ }>();
const t = Translations.t.plantDetection;
/**
* PlantNet give us a GBIF-id, but we want the Wikidata-id instead.
* We look this up in wikidata
*/
const wikidataId: Store<string> = UIEventSource.FromPromise(
Wikidata.Sparql<{ species }>(
["?species", "?speciesLabel"],
["?species wdt:P846 \"" + species.gbif.id + "\""]
)
).mapD(wd => wd[0]?.species?.value);
</script>
<NextButton on:click={() =>{
console.log("Dispatching: ", $wikidataId)
return dispatch("selected", $wikidataId); }}>
{#if $wikidata === undefined}
<Loading>
<Tr t={ t.loadingWikidata.Subs({
species: species.species.scientificNameWithoutAuthor,
})} />
</Loading>
{:else}
<ToSvelte construct={() => new WikidataPreviewBox(wikidataId,
{ imageStyle: "max-width: 8rem; width: unset; height: 8rem",
extraItems: [t.matchPercentage
.Subs({ match: Math.round(species.score * 100) })
.SetClass("thanks w-fit self-center")]
}).SetClass("w-full")}></ToSvelte>
{/if}
</NextButton>