forked from MapComplete/MapComplete
		
	Refactoring: port PlantNet-detection to svelte, re-integrate wikipedia component
This commit is contained in:
		
							parent
							
								
									d1aa751e18
								
							
						
					
					
						commit
						5f04a69517
					
				
					 18 changed files with 297 additions and 210 deletions
				
			
		|  | @ -380,6 +380,7 @@ | |||
|                 "born": "Born: {value}", | ||||
|                 "died": "Died: {value}" | ||||
|             }, | ||||
|             "readMore": "Read the rest of the article", | ||||
|             "searchToShort": "Your search query is too short, enter a longer text", | ||||
|             "searchWikidata": "Search on Wikidata", | ||||
|             "wikipediaboxTitle": "Wikipedia" | ||||
|  | @ -498,7 +499,9 @@ | |||
|     }, | ||||
|     "plantDetection": { | ||||
|         "back": "Back to species overview", | ||||
|         "button": "Automatically detect the plant species using the AI of Plantnet.org", | ||||
|         "confirm": "Select species", | ||||
|         "done": "The species has been applied", | ||||
|         "error": "Something went wrong while detecting the tree species: {error}", | ||||
|         "howTo": { | ||||
|             "intro": "For optimal results,", | ||||
|  | @ -515,7 +518,8 @@ | |||
|         "poweredByPlantnet": "Powered by <a href='https://plantnet.org' target='_blank'>plantnet.org</a>", | ||||
|         "querying": "Querying plantnet.org with {length} images", | ||||
|         "seeInfo": "See more information about the species", | ||||
|         "takeImages": "Take images of the tree to automatically detect the tree type" | ||||
|         "takeImages": "Take images of the tree to automatically detect the tree type", | ||||
|         "tryAgain": "Select a different species" | ||||
|     }, | ||||
|     "privacy": { | ||||
|         "editing": "When you make a change to the map, this change is recorded on OpenStreetMap and is publicly available to anyone. A changeset made with MapComplete includes the following data: <ul><li>The changes you made</li><li>Your username</li><li>When this change is made</li><li>The theme you used while making the change</li><li>The language of the user interface</li><li>An indication of how close you were to changed objects. Other mappers can use this information to determine if a change was made based on survey or on remote research</li></ul> Please refer to <a href='https://wiki.osmfoundation.org/wiki/Privacy_Policy' target='_blank'>the privacy policy on OpenStreetMap.org</a> for detailed information. We'd like to remind you that you can use a fictional name when signing up.", | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| { | ||||
|   "name": "mapcomplete", | ||||
|   "version": "0.32.0", | ||||
|   "version": "0.33.0", | ||||
|   "repository": "https://github.com/pietervdvn/MapComplete", | ||||
|   "description": "A small website to edit OSM easily", | ||||
|   "bugs": "https://github.com/pietervdvn/MapComplete/issues", | ||||
|  |  | |||
|  | @ -1096,6 +1096,10 @@ video { | |||
|   height: 2.75rem; | ||||
| } | ||||
| 
 | ||||
| .h-10 { | ||||
|   height: 2.5rem; | ||||
| } | ||||
| 
 | ||||
| .h-48 { | ||||
|   height: 12rem; | ||||
| } | ||||
|  | @ -1104,10 +1108,6 @@ video { | |||
|   height: 10rem; | ||||
| } | ||||
| 
 | ||||
| .h-10 { | ||||
|   height: 2.5rem; | ||||
| } | ||||
| 
 | ||||
| .h-80 { | ||||
|   height: 20rem; | ||||
| } | ||||
|  | @ -1709,11 +1709,6 @@ video { | |||
|   padding-right: 0.5rem; | ||||
| } | ||||
| 
 | ||||
| .py-2 { | ||||
|   padding-top: 0.5rem; | ||||
|   padding-bottom: 0.5rem; | ||||
| } | ||||
| 
 | ||||
| .pl-1 { | ||||
|   padding-left: 0.25rem; | ||||
| } | ||||
|  | @ -2209,6 +2204,11 @@ input[type=text] { | |||
|   border-radius: 0.5rem; | ||||
| } | ||||
| 
 | ||||
| .border-region { | ||||
|   border: 2px dashed var(--interactive-background); | ||||
|   border-radius: 0.5rem; | ||||
| } | ||||
| 
 | ||||
| /******************* Styling of input elements **********************/ | ||||
| 
 | ||||
| /** | ||||
|  |  | |||
|  | @ -985,17 +985,7 @@ export default class PlantNet { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| export interface PlantNetResult { | ||||
|     query: { | ||||
|         project: string | ||||
|         images: string[] | ||||
|         organs: string[] | ||||
|         includeRelatedImages: boolean | ||||
|     } | ||||
|     language: string | ||||
|     preferedReferential: string | ||||
|     bestMatch: string | ||||
|     results: { | ||||
| export interface PlantNetSpeciesMatch { | ||||
|     score: number | ||||
|     gbif: { id: string /*Actually a number*/ } | ||||
|     species: { | ||||
|  | @ -1014,7 +1004,19 @@ export interface PlantNetResult { | |||
|         commonNames: string[] | ||||
|         scientificName: string | ||||
|     } | ||||
|     }[] | ||||
| } | ||||
| 
 | ||||
| export interface PlantNetResult { | ||||
|     query: { | ||||
|         project: string | ||||
|         images: string[] | ||||
|         organs: string[] | ||||
|         includeRelatedImages: boolean | ||||
|     } | ||||
|     language: string | ||||
|     preferedReferential: string | ||||
|     bestMatch: string | ||||
|     results: PlantNetSpeciesMatch[] | ||||
|     version: string | ||||
|     remainingIdentificationRequests: number | ||||
| } | ||||
|  |  | |||
|  | @ -10,12 +10,13 @@ | |||
| 
 | ||||
|   const dispatch = createEventDispatcher<{ click }>() | ||||
|   export let clss: string | undefined = undefined | ||||
|   export let imageClass: string | undefined = undefined | ||||
| </script> | ||||
| 
 | ||||
| <SubtleButton | ||||
|   on:click={() => dispatch("click")} | ||||
|   options={{ extraClasses: twMerge("flex items-center", clss) }} | ||||
| > | ||||
|   <ChevronLeftIcon class="h-12 w-12" slot="image" /> | ||||
|   <ChevronLeftIcon class={imageClass ?? "h-12 w-12"} slot="image" /> | ||||
|   <slot slot="message" /> | ||||
| </SubtleButton> | ||||
|  |  | |||
|  | @ -20,6 +20,6 @@ | |||
|   <slot name="image" slot="image" /> | ||||
|   <div class="flex w-full items-center justify-between" slot="message"> | ||||
|     <slot /> | ||||
|     <ChevronRightIcon class="h-12 w-12" /> | ||||
|     <ChevronRightIcon class="h-12 w-12 shrink-0" /> | ||||
|   </div> | ||||
| </SubtleButton> | ||||
|  |  | |||
|  | @ -1,127 +0,0 @@ | |||
| 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 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 | ||||
|                             } | ||||
|                             return 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") | ||||
|                         }) | ||||
|                     ) | ||||
|                 }) | ||||
|         ) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										123
									
								
								src/UI/PlantNet/PlantNet.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								src/UI/PlantNet/PlantNet.svelte
									
										
									
									
									
										Normal 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> | ||||
							
								
								
									
										37
									
								
								src/UI/PlantNet/PlantNetSpeciesList.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/UI/PlantNet/PlantNetSpeciesList.svelte
									
										
									
									
									
										Normal 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} | ||||
							
								
								
									
										56
									
								
								src/UI/PlantNet/SpeciesButton.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/UI/PlantNet/SpeciesButton.svelte
									
										
									
									
									
										Normal 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> | ||||
|  | @ -1,18 +1,14 @@ | |||
| import { Store, UIEventSource } from "../../Logic/UIEventSource" | ||||
| import Toggle from "../Input/Toggle" | ||||
| import Lazy from "../Base/Lazy" | ||||
| import { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider" | ||||
| import PlantNetSpeciesSearch from "../BigComponents/PlantNetSpeciesSearch" | ||||
| import Wikidata from "../../Logic/Web/Wikidata" | ||||
| import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction" | ||||
| import { And } from "../../Logic/Tags/And" | ||||
| import { Tag } from "../../Logic/Tags/Tag" | ||||
| import { SubtleButton } from "../Base/SubtleButton" | ||||
| import Combine from "../Base/Combine" | ||||
| import Svg from "../../Svg" | ||||
| import Translations from "../i18n/Translations" | ||||
| import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders" | ||||
| import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" | ||||
| import SvelteUIElement from "../Base/SvelteUIElement" | ||||
| import PlantNet from "../PlantNet/PlantNet.svelte" | ||||
| 
 | ||||
| export class PlantNetDetectionViz implements SpecialVisualization { | ||||
|     funcName = "plantnet_detection" | ||||
|  | @ -37,17 +33,13 @@ export class PlantNetDetectionViz implements SpecialVisualization { | |||
|             imagePrefixes = [].concat(...args.map((a) => a.split(","))) | ||||
|         } | ||||
| 
 | ||||
|         const detect = new UIEventSource(false) | ||||
|         const toggle = new Toggle( | ||||
|             new Lazy(() => { | ||||
|         const allProvidedImages: Store<ProvidedImage[]> = AllImageProviders.LoadImagesFor( | ||||
|             tags, | ||||
|             imagePrefixes | ||||
|         ) | ||||
|                 const allImages: Store<string[]> = allProvidedImages.map((pi) => | ||||
|                     pi.map((pi) => pi.url) | ||||
|                 ) | ||||
|                 return new PlantNetSpeciesSearch(allImages, async (selectedWikidata) => { | ||||
|         const imageUrls: Store<string[]> = allProvidedImages.map((pi) => pi.map((pi) => pi.url)) | ||||
| 
 | ||||
|         async function applySpecies(selectedWikidata) { | ||||
|             selectedWikidata = Wikidata.ExtractKey(selectedWikidata) | ||||
|             const change = new ChangeTagAction( | ||||
|                 tags.data.id, | ||||
|  | @ -62,20 +54,8 @@ export class PlantNetDetectionViz implements SpecialVisualization { | |||
|                 } | ||||
|             ) | ||||
|             await state.changes.applyAction(change) | ||||
|                 }) | ||||
|             }), | ||||
|             new SubtleButton(undefined, "Detect plant species with plantnet.org").onClick(() => | ||||
|                 detect.setData(true) | ||||
|             ), | ||||
|             detect | ||||
|         ) | ||||
|         } | ||||
| 
 | ||||
|         return new Combine([ | ||||
|             toggle, | ||||
|             new Combine([ | ||||
|                 Svg.plantnet_logo_svg().SetClass("w-10 h-10 p-1 mr-1 bg-white rounded-full"), | ||||
|                 Translations.t.plantDetection.poweredByPlantnet, | ||||
|             ]).SetClass("flex p-2 bg-gray-200 rounded-xl self-end"), | ||||
|         ]).SetClass("flex flex-col") | ||||
|         return new SvelteUIElement(PlantNet, { imageUrls, onConfirm: applySpecies }) | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -538,7 +538,7 @@ export default class SpecialVisualizations { | |||
|                     const keys = args[0].split(";").map((k) => k.trim()) | ||||
|                     const wikiIds: Store<string[]> = tagsSource.map((tags) => { | ||||
|                         const key = keys.find((k) => tags[k] !== undefined && tags[k] !== "") | ||||
|                         return tags[key]?.split(";")?.map((id) => id.trim()) | ||||
|                         return tags[key]?.split(";")?.map((id) => id.trim()) ?? [] | ||||
|                     }) | ||||
|                     return new SvelteUIElement(WikipediaPanel, { | ||||
|                         wikiIds, | ||||
|  |  | |||
|  | @ -29,6 +29,10 @@ | |||
|       areas, where some buttons might appear. | ||||
|     </p> | ||||
| 
 | ||||
|     <div class="border-interactive interactive"> | ||||
|       Highly interactive area (mostly: active question) | ||||
|     </div> | ||||
|      | ||||
|     <div class="flex"> | ||||
|       <button class="primary"> | ||||
|         <ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")} /> | ||||
|  |  | |||
|  | @ -126,7 +126,7 @@ export default class WikidataPreviewBox extends VariableUiElement { | |||
|             new Combine([ | ||||
|                 Translation.fromMap(wikidata.labels)?.SetClass("font-bold"), | ||||
|                 link, | ||||
|             ]).SetClass("flex justify-between"), | ||||
|             ]).SetClass("flex justify-between flex-wrap-reverse"), | ||||
|             Translation.fromMap(wikidata.descriptions), | ||||
|             WikidataPreviewBox.QuickFacts(wikidata, options), | ||||
|             ...(options?.extraItems ?? []), | ||||
|  |  | |||
|  | @ -131,7 +131,7 @@ Another example is to search for species and trees: | |||
|         const searchResult: Store<{ success?: WikidataResponse[]; error?: any }> = searchField | ||||
|             .GetValue() | ||||
|             .bind((searchText) => { | ||||
|                 if (searchText.length < 3) { | ||||
|                 if (searchText.length < 3 && !searchText.match(/[qQ][0-9]+/)) { | ||||
|                     return tooShort | ||||
|                 } | ||||
|                 const lang = Locale.language.data | ||||
|  |  | |||
|  | @ -11,7 +11,7 @@ | |||
|   import Translations from "../i18n/Translations"; | ||||
| 
 | ||||
|   /** | ||||
|    * Small helper | ||||
|    * Shows a wikipedia-article + wikidata preview for the given item | ||||
|    */ | ||||
|   export let wikipediaDetails: Store<FullWikipediaDetails>; | ||||
| </script> | ||||
|  | @ -23,9 +23,11 @@ | |||
|     <Tr t={Translations.t.general.wikipedia.fromWikipedia} /> | ||||
|   </a> | ||||
| {/if} | ||||
| 
 | ||||
| {#if $wikipediaDetails.wikidata} | ||||
|   <ToSvelte construct={WikidataPreviewBox.WikidataResponsePreview($wikipediaDetails.wikidata)} /> | ||||
| {/if} | ||||
| 
 | ||||
| {#if $wikipediaDetails.articleUrl} | ||||
| 
 | ||||
|   {#if $wikipediaDetails.firstParagraph === "" || $wikipediaDetails.firstParagraph === undefined} | ||||
|  | @ -42,7 +44,7 @@ | |||
|             style={(open ? "transform: rotate(90deg); " : "") + | ||||
|               "  transition: all .25s linear; width: 1.5rem; height: 1.5rem"} | ||||
|           /> | ||||
|           Read the rest of the article | ||||
|           <Tr t={Translations.t.general.wikipedia.readMore}/> | ||||
|         </span> | ||||
|       </DisclosureButton> | ||||
|       <DisclosurePanel> | ||||
|  |  | |||
|  | @ -16,7 +16,7 @@ | |||
|    */ | ||||
|   export let wikiIds: Store<string[]>; | ||||
|   let wikipediaStores: Store<Store<FullWikipediaDetails>[]> = Locale.language.bind((language) => | ||||
|     wikiIds.map((wikiIds) => wikiIds.map((id) => Wikipedia.fetchArticleAndWikidata(id, language))) | ||||
|     wikiIds?.map((wikiIds) => wikiIds?.map((id) => Wikipedia.fetchArticleAndWikidata(id, language))) | ||||
|   ); | ||||
|   let _wikipediaStores; | ||||
|   onDestroy( | ||||
|  |  | |||
|  | @ -154,6 +154,11 @@ input[type=text] { | |||
|     border-radius: 0.5rem; | ||||
| } | ||||
| 
 | ||||
| .border-region { | ||||
|     border: 2px dashed var(--interactive-background); | ||||
|     border-radius: 0.5rem; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /******************* Styling of input elements **********************/ | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue