mapcomplete/UI/BigComponents/PlantNetSpeciesSearch.ts

121 lines
No EOL
6 KiB
TypeScript

import {VariableUiElement} from "../Base/VariableUIElement";
import {Store, UIEventSource} from "../../Logic/UIEventSource";
import PlantNet from "../../Logic/Web/PlantNet";
import Loading from "../Base/Loading";
import Wikidata from "../../Logic/Web/Wikidata";
import WikidataPreviewBox from "../Wikipedia/WikidataPreviewBox";
import {Button} from "../Base/Button";
import Combine from "../Base/Combine";
import Title from "../Base/Title";
import WikipediaBox from "../Wikipedia/WikipediaBox";
import Translations from "../i18n/Translations";
import List from "../Base/List";
import Svg from "../../Svg";
export default class PlantNetSpeciesSearch extends VariableUiElement {
/***
* Given images, queries plantnet to search a species matching those images.
* A list of species will be presented to the user, after which they can confirm an item.
* The wikidata-url is returned in the callback when the user selects one
*/
constructor(images: Store<string[]>, onConfirm: (wikidataUrl: string) => Promise<void>) {
const t = Translations.t.plantDetection
super(
images
.bind(images => {
if (images.length === 0) {
return null
}
return UIEventSource.FromPromiseWithErr(PlantNet.query(images.slice(0,5)));
})
.map(result => {
if (images.data.length === 0) {
return new Combine([t.takeImages, t.howTo.intro, new List(
[
t.howTo.li0,
t.howTo.li1,
t.howTo.li2,
t.howTo.li3
]
)]).SetClass("flex flex-col")
}
if (result === undefined) {
return new Loading(t.querying.Subs(images.data))
}
if (result["error"] !== undefined) {
return t.error.Subs(<any>result).SetClass("alert")
}
console.log(result)
const success = result["success"]
const selectedSpecies = new UIEventSource<string>(undefined)
const speciesInformation = success.results
.filter(species => species.score >= 0.005)
.map(species => {
const wikidata = UIEventSource.FromPromise(Wikidata.Sparql<{ species }>(["?species", "?speciesLabel"],
["?species wdt:P846 \"" + species.gbif.id + "\""]));
const confirmButton = new Button(t.seeInfo, async() => {
await selectedSpecies.setData(wikidata.data[0].species?.value)
}).SetClass("btn")
const match = t.matchPercentage.Subs({match: Math.round(species.score * 100)}).SetClass("font-bold")
const extraItems = new Combine([match, confirmButton]).SetClass("flex flex-col")
return new WikidataPreviewBox(wikidata.map(wd => wd == undefined ? undefined : wd[0]?.species?.value),
{
whileLoading: new Loading(
t.loadingWikidata.Subs({species: species.species.scientificNameWithoutAuthor})),
extraItems: [new Combine([extraItems])],
imageStyle: "max-width: 8rem; width: unset; height: 8rem"
})
.SetClass("border-2 border-subtle rounded-xl block mb-2")
}
);
const plantOverview = new Combine([
new Title(t.overviewTitle),
t.overviewIntro,
t.overviewVerify.SetClass("font-bold"),
...speciesInformation]).SetClass("flex flex-col")
return new VariableUiElement(selectedSpecies.map(wikidataSpecies => {
if (wikidataSpecies === undefined) {
return plantOverview
}
const buttons = new Combine([
new Button(
new Combine([
Svg.back_svg().SetClass("w-6 mr-1 bg-white rounded-full p-1"),
t.back]).SetClass("flex"),
() => {
selectedSpecies.setData(undefined)
}).SetClass("btn btn-secondary"),
new Button(
new Combine([Svg.confirm_svg().SetClass("w-6 mr-1"), t.confirm]).SetClass("flex")
, () => {
onConfirm(wikidataSpecies)
}).SetClass("btn"),
]).SetClass("flex justify-between");
return new Combine([
new WikipediaBox([wikidataSpecies], {
firstParagraphOnly: false,
noImages: false,
addHeader: false
}).SetClass("h-96"),
buttons
]).SetClass("flex flex-col self-end")
}))
}
))
}
}