forked from MapComplete/MapComplete
		
	First working plantnet UI
This commit is contained in:
		
							parent
							
								
									a8959fc934
								
							
						
					
					
						commit
						06f8cf7006
					
				
					 9 changed files with 216 additions and 39 deletions
				
			
		|  | @ -2,15 +2,17 @@ import OsmChangeAction from "./OsmChangeAction"; | ||||||
| import {Changes} from "../Changes"; | import {Changes} from "../Changes"; | ||||||
| import {ChangeDescription} from "./ChangeDescription"; | import {ChangeDescription} from "./ChangeDescription"; | ||||||
| import {TagsFilter} from "../../Tags/TagsFilter"; | import {TagsFilter} from "../../Tags/TagsFilter"; | ||||||
|  | import {OsmTags} from "../../../Models/OsmFeature"; | ||||||
| 
 | 
 | ||||||
| export default class ChangeTagAction extends OsmChangeAction { | export default class ChangeTagAction extends OsmChangeAction { | ||||||
|     private readonly _elementId: string; |     private readonly _elementId: string; | ||||||
|     private readonly _tagsFilter: TagsFilter; |     private readonly _tagsFilter: TagsFilter; | ||||||
|     private readonly _currentTags: any; |     private readonly _currentTags: Record<string, string> | OsmTags; | ||||||
|     private readonly _meta: { theme: string, changeType: string }; |     private readonly _meta: { theme: string, changeType: string }; | ||||||
| 
 | 
 | ||||||
|     constructor(elementId: string,  |     constructor(elementId: string,  | ||||||
|                 tagsFilter: TagsFilter, currentTags: any, meta: { |                 tagsFilter: TagsFilter,  | ||||||
|  |                 currentTags: Record<string, string>, meta: { | ||||||
|         theme: string, |         theme: string, | ||||||
|         changeType: "answer" | "soft-delete" | "add-image" | string |         changeType: "answer" | "soft-delete" | "add-image" | string | ||||||
|     }) { |     }) { | ||||||
|  |  | ||||||
|  | @ -3,12 +3,11 @@ import BaseUIElement from "../BaseUIElement"; | ||||||
| 
 | 
 | ||||||
| export class Button extends BaseUIElement { | export class Button extends BaseUIElement { | ||||||
|     private _text: BaseUIElement; |     private _text: BaseUIElement; | ||||||
|     private _onclick: () => void; |  | ||||||
| 
 | 
 | ||||||
|     constructor(text: string | BaseUIElement, onclick: (() => void)) { |     constructor(text: string | BaseUIElement, onclick: (() => void | Promise<void>)) { | ||||||
|         super(); |         super(); | ||||||
|         this._text = Translations.W(text); |         this._text = Translations.W(text); | ||||||
|         this._onclick = onclick; |         this.onClick(onclick) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     protected InnerConstructElement(): HTMLElement { |     protected InnerConstructElement(): HTMLElement { | ||||||
|  | @ -20,7 +19,6 @@ export class Button extends BaseUIElement { | ||||||
|         const button = document.createElement("button") |         const button = document.createElement("button") | ||||||
|         button.type = "button" |         button.type = "button" | ||||||
|         button.appendChild(el) |         button.appendChild(el) | ||||||
|         button.onclick = this._onclick |  | ||||||
|         form.appendChild(button) |         form.appendChild(button) | ||||||
|         return form; |         return form; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -1,5 +1,3 @@ | ||||||
| import { Utils } from "../Utils"; |  | ||||||
| 
 |  | ||||||
| /** | /** | ||||||
|  * A thin wrapper around a html element, which allows to generate a HTML-element. |  * A thin wrapper around a html element, which allows to generate a HTML-element. | ||||||
|  * |  * | ||||||
|  | @ -11,7 +9,7 @@ export default abstract class BaseUIElement { | ||||||
|     protected isDestroyed = false; |     protected isDestroyed = false; | ||||||
|     private readonly clss: Set<string> = new Set<string>(); |     private readonly clss: Set<string> = new Set<string>(); | ||||||
|     private style: string; |     private style: string; | ||||||
|     private _onClick: () => void; |     private _onClick: () => void | Promise<void>; | ||||||
| 
 | 
 | ||||||
|     public onClick(f: (() => void)) { |     public onClick(f: (() => void)) { | ||||||
|         this._onClick = f; |         this._onClick = f; | ||||||
|  | @ -127,12 +125,15 @@ export default abstract class BaseUIElement { | ||||||
| 
 | 
 | ||||||
|             if (this._onClick !== undefined) { |             if (this._onClick !== undefined) { | ||||||
|                 const self = this; |                 const self = this; | ||||||
|                 el.onclick = (e) => { |                 el.onclick = async (e) => { | ||||||
|                     // @ts-ignore
 |                     // @ts-ignore
 | ||||||
|                     if (e.consumed) { |                     if (e.consumed) { | ||||||
|                         return; |                         return; | ||||||
|                     } |                     } | ||||||
|                     self._onClick(); |                     const v = self._onClick(); | ||||||
|  |                     if(typeof v === "object"){ | ||||||
|  |                         await v | ||||||
|  |                     } | ||||||
|                     // @ts-ignore
 |                     // @ts-ignore
 | ||||||
|                     e.consumed = true; |                     e.consumed = true; | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
							
								
								
									
										103
									
								
								UI/BigComponents/PlantNetSpeciesSearch.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								UI/BigComponents/PlantNetSpeciesSearch.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,103 @@ | ||||||
|  | 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"; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 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 new UIEventSource({success: PlantNet.exampleResultPrunus}) /*/ UIEventSource.FromPromiseWithErr(PlantNet.query(images.slice(0,5))); //*/ | ||||||
|  |                 }) | ||||||
|  |                 .map(result => { | ||||||
|  |                         if (result === undefined) { | ||||||
|  |                             return new Loading(t.querying.Subs(images.data)) | ||||||
|  |                         } | ||||||
|  |                         if (result === null) { | ||||||
|  |                             return t.takeImages | ||||||
|  |                         } | ||||||
|  |                         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("Confirm", () => { | ||||||
|  |                                     onConfirm(wikidataSpecies) | ||||||
|  |                                 }).SetClass("btn"), | ||||||
|  |                                 new Button("Back to plant overview", () => { | ||||||
|  |                                     selectedSpecies.setData(undefined) | ||||||
|  |                                 }).SetClass("btn btn-secondary") | ||||||
|  |                             ]).SetClass("flex self-end"); | ||||||
|  | 
 | ||||||
|  |                             return new Combine([ | ||||||
|  |                                 new WikipediaBox([wikidataSpecies], { | ||||||
|  |                                     firstParagraphOnly: false, | ||||||
|  |                                     noImages: false, | ||||||
|  |                                     addHeader: false | ||||||
|  |                                 }).SetClass("h-96"), | ||||||
|  |                                 buttons | ||||||
|  |                             ]).SetClass("flex flex-col self-end") | ||||||
|  |                         })) | ||||||
|  | 
 | ||||||
|  |                     } | ||||||
|  |                 )) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -61,6 +61,8 @@ import StatisticsPanel from "./BigComponents/StatisticsPanel"; | ||||||
| import {OsmFeature} from "../Models/OsmFeature"; | import {OsmFeature} from "../Models/OsmFeature"; | ||||||
| import EditableTagRendering from "./Popup/EditableTagRendering"; | import EditableTagRendering from "./Popup/EditableTagRendering"; | ||||||
| import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig"; | import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig"; | ||||||
|  | import {ProvidedImage} from "../Logic/ImageProviders/ImageProvider"; | ||||||
|  | import PlantNetSpeciesSearch from "./BigComponents/PlantNetSpeciesSearch"; | ||||||
| 
 | 
 | ||||||
| export interface SpecialVisualization { | export interface SpecialVisualization { | ||||||
|     funcName: string, |     funcName: string, | ||||||
|  | @ -196,7 +198,7 @@ class NearbyImageVis implements SpecialVisualization { | ||||||
|                     new ChangeTagAction( |                     new ChangeTagAction( | ||||||
|                         id, |                         id, | ||||||
|                         new And(tags), |                         new And(tags), | ||||||
|                         tagSource, |                         tagSource.data, | ||||||
|                         { |                         { | ||||||
|                             theme: state?.layoutToUse.id, |                             theme: state?.layoutToUse.id, | ||||||
|                             changeType: "link-image" |                             changeType: "link-image" | ||||||
|  | @ -1299,6 +1301,46 @@ export default class SpecialVisualizations { | ||||||
|                         const [layerId, __] = tagRenderingId.split(".") |                         const [layerId, __] = tagRenderingId.split(".") | ||||||
|                         return [layerId] |                         return [layerId] | ||||||
|                     } |                     } | ||||||
|  |                 }, | ||||||
|  |                 { | ||||||
|  |                     funcName: "plantnet_detection", | ||||||
|  | 
 | ||||||
|  |                     docs: "Sends the images linked to the current object to plantnet.org and asks it what plant species is shown on it. The user can then select the correct species; the corresponding wikidata-identifier will then be added to the object (together with `source:species:wikidata=plantnet.org AI`). ", | ||||||
|  |                     args: [{ | ||||||
|  |                         name: "image_key", | ||||||
|  |                         defaultValue: AllImageProviders.defaultKeys.join(","), | ||||||
|  |                         doc: "The keys given to the images, e.g. if <span class='literal-code'>image</span> is given, the first picture URL will be added as <span class='literal-code'>image</span>, the second as <span class='literal-code'>image:0</span>, the third as <span class='literal-code'>image:1</span>, etc... Multiple values are allowed if ';'-separated " | ||||||
|  |                     }], | ||||||
|  |                     constr: (state, tags, args) => { | ||||||
|  |                         let imagePrefixes: string[] = undefined; | ||||||
|  |                         if (args.length > 0) { | ||||||
|  |                             imagePrefixes = [].concat(...args.map(a => a.split(","))); | ||||||
|  |                         } | ||||||
|  | 
 | ||||||
|  |                         const detect = new UIEventSource(false) | ||||||
|  |                         return 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 => { | ||||||
|  |                                     selectedWikidata = Wikidata.ExtractKey(selectedWikidata) | ||||||
|  |                                     const change = new ChangeTagAction(tags.data.id, | ||||||
|  |                                         new And([new Tag("species:wikidata", selectedWikidata), | ||||||
|  |                                         new Tag("source:species:wikidata","PlantNet.org AI") | ||||||
|  |                                         ]), | ||||||
|  |                                         tags.data, | ||||||
|  |                                         { | ||||||
|  |                                             theme: state.layoutToUse.id, | ||||||
|  |                                             changeType: "plantnet-ai-detection" | ||||||
|  |                                         } | ||||||
|  |                                         ) | ||||||
|  |                                    await state.changes.applyAction(change) | ||||||
|  |                                 }) | ||||||
|  |                             }), | ||||||
|  |                             new SubtleButton(undefined, "Detect plant species with plantnet.org").onClick(() => detect.setData(true)), | ||||||
|  |                             detect | ||||||
|  |                         ) | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|             ] |             ] | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -57,7 +57,11 @@ export default class WikidataPreviewBox extends VariableUiElement { | ||||||
|         } |         } | ||||||
|     ] |     ] | ||||||
| 
 | 
 | ||||||
|     constructor(wikidataId: Store<string>, options?: {noImages?: boolean, whileLoading?: BaseUIElement | string, extraItems?: (BaseUIElement | string)[]}) { |     constructor(wikidataId: Store<string>, options?: { | ||||||
|  |         noImages?: boolean,  | ||||||
|  |         imageStyle?: string, | ||||||
|  |         whileLoading?: BaseUIElement | string,  | ||||||
|  |         extraItems?: (BaseUIElement | string)[]}) { | ||||||
|         let inited = false; |         let inited = false; | ||||||
|         const wikidata = wikidataId |         const wikidata = wikidataId | ||||||
|             .stabilized(250) |             .stabilized(250) | ||||||
|  | @ -87,7 +91,10 @@ export default class WikidataPreviewBox extends VariableUiElement { | ||||||
| 
 | 
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static WikidataResponsePreview(wikidata: WikidataResponse, options?: {noImages?: boolean, extraItems?: (BaseUIElement | string)[]}): BaseUIElement { |     public static WikidataResponsePreview(wikidata: WikidataResponse, options?: { | ||||||
|  |         noImages?: boolean,  | ||||||
|  |         imageStyle?: string, | ||||||
|  |         extraItems?: (BaseUIElement | string)[]}): BaseUIElement { | ||||||
|         let link = new Link( |         let link = new Link( | ||||||
|             new Combine([ |             new Combine([ | ||||||
|                 wikidata.id, |                 wikidata.id, | ||||||
|  | @ -111,7 +118,7 @@ export default class WikidataPreviewBox extends VariableUiElement { | ||||||
|         } |         } | ||||||
|         if (imageUrl && !options?.noImages) { |         if (imageUrl && !options?.noImages) { | ||||||
|             imageUrl = WikimediaImageProvider.singleton.PrepUrl(imageUrl).url |             imageUrl = WikimediaImageProvider.singleton.PrepUrl(imageUrl).url | ||||||
|             info = new Combine([new Img(imageUrl).SetStyle("max-width: 5rem; width: unset; height: 4rem").SetClass("rounded-xl mr-2"), |             info = new Combine([new Img(imageUrl).SetStyle(options?.imageStyle ?? "max-width: 5rem; width: unset; height: 4rem").SetClass("rounded-xl mr-2"), | ||||||
|                 info.SetClass("w-full")]).SetClass("flex") |                 info.SetClass("w-full")]).SetClass("flex") | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -104,6 +104,11 @@ | ||||||
|         } |         } | ||||||
|       ] |       ] | ||||||
|     }, |     }, | ||||||
|  |     { | ||||||
|  |       "id": "plantnet", | ||||||
|  |       "render": "{plantnet_detection()}", | ||||||
|  |       "condition": "species:wikidata=" | ||||||
|  |     }, | ||||||
|     { |     { | ||||||
|       "id": "tree-species-wikidata", |       "id": "tree-species-wikidata", | ||||||
|       "question": { |       "question": { | ||||||
|  |  | ||||||
|  | @ -858,6 +858,10 @@ video { | ||||||
|   margin-bottom: 0.75rem; |   margin-bottom: 0.75rem; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .mb-2 { | ||||||
|  |   margin-bottom: 0.5rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .ml-3 { | .ml-3 { | ||||||
|   margin-left: 0.75rem; |   margin-left: 0.75rem; | ||||||
| } | } | ||||||
|  | @ -866,14 +870,6 @@ video { | ||||||
|   margin-bottom: 1rem; |   margin-bottom: 1rem; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .mt-8 { |  | ||||||
|   margin-top: 2rem; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .mt-4 { |  | ||||||
|   margin-top: 1rem; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .mt-2 { | .mt-2 { | ||||||
|   margin-top: 0.5rem; |   margin-top: 0.5rem; | ||||||
| } | } | ||||||
|  | @ -886,6 +882,10 @@ video { | ||||||
|   margin-right: 2rem; |   margin-right: 2rem; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .mt-4 { | ||||||
|  |   margin-top: 1rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .mt-6 { | .mt-6 { | ||||||
|   margin-top: 1.5rem; |   margin-top: 1.5rem; | ||||||
| } | } | ||||||
|  | @ -910,10 +910,6 @@ video { | ||||||
|   margin-right: 1rem; |   margin-right: 1rem; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .mb-2 { |  | ||||||
|   margin-bottom: 0.5rem; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .ml-2 { | .ml-2 { | ||||||
|   margin-left: 0.5rem; |   margin-left: 0.5rem; | ||||||
| } | } | ||||||
|  | @ -934,6 +930,10 @@ video { | ||||||
|   margin-top: 0px; |   margin-top: 0px; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .mt-8 { | ||||||
|  |   margin-top: 2rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .mb-8 { | .mb-8 { | ||||||
|   margin-bottom: 2rem; |   margin-bottom: 2rem; | ||||||
| } | } | ||||||
|  | @ -1054,6 +1054,10 @@ video { | ||||||
|   height: 6rem; |   height: 6rem; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .h-96 { | ||||||
|  |   height: 24rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .h-64 { | .h-64 { | ||||||
|   height: 16rem; |   height: 16rem; | ||||||
| } | } | ||||||
|  | @ -1162,6 +1166,10 @@ video { | ||||||
|   width: 2rem; |   width: 2rem; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .w-1\/3 { | ||||||
|  |   width: 33.333333%; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .w-4 { | .w-4 { | ||||||
|   width: 1rem; |   width: 1rem; | ||||||
| } | } | ||||||
|  | @ -1407,6 +1415,10 @@ video { | ||||||
|   border-radius: 9999px; |   border-radius: 9999px; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .rounded-xl { | ||||||
|  |   border-radius: 0.75rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .rounded-3xl { | .rounded-3xl { | ||||||
|   border-radius: 1.5rem; |   border-radius: 1.5rem; | ||||||
| } | } | ||||||
|  | @ -1423,10 +1435,6 @@ video { | ||||||
|   border-radius: 0.5rem; |   border-radius: 0.5rem; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .rounded-xl { |  | ||||||
|   border-radius: 0.75rem; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .rounded-sm { | .rounded-sm { | ||||||
|   border-radius: 0.125rem; |   border-radius: 0.125rem; | ||||||
| } | } | ||||||
|  | @ -1436,14 +1444,14 @@ video { | ||||||
|   border-bottom-left-radius: 0.25rem; |   border-bottom-left-radius: 0.25rem; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .border { |  | ||||||
|   border-width: 1px; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .border-2 { | .border-2 { | ||||||
|   border-width: 2px; |   border-width: 2px; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .border { | ||||||
|  |   border-width: 1px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .border-4 { | .border-4 { | ||||||
|   border-width: 4px; |   border-width: 4px; | ||||||
| } | } | ||||||
|  | @ -2866,10 +2874,6 @@ input { | ||||||
|     width: 75%; |     width: 75%; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   .lg\:w-1\/3 { |  | ||||||
|     width: 33.333333%; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   .lg\:w-1\/4 { |   .lg\:w-1\/4 { | ||||||
|     width: 25%; |     width: 25%; | ||||||
|   } |   } | ||||||
|  | @ -2878,6 +2882,10 @@ input { | ||||||
|     width: 16.666667%; |     width: 16.666667%; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   .lg\:w-1\/3 { | ||||||
|  |     width: 33.333333%; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   .lg\:grid-cols-3 { |   .lg\:grid-cols-3 { | ||||||
|     grid-template-columns: repeat(3, minmax(0, 1fr)); |     grid-template-columns: repeat(3, minmax(0, 1fr)); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -685,6 +685,17 @@ | ||||||
|         "typeText": "Type some text to add a comment", |         "typeText": "Type some text to add a comment", | ||||||
|         "warnAnonymous": "You are not logged in. We won't be able to contact you to resolve your issue." |         "warnAnonymous": "You are not logged in. We won't be able to contact you to resolve your issue." | ||||||
|     }, |     }, | ||||||
|  |     "plantDetection": { | ||||||
|  |         "error": "Something went wrong while detecting the tree species: {error}", | ||||||
|  |         "loadingWikidata": "Loading information about {species}", | ||||||
|  |         "matchPercentage": "{match}% match", | ||||||
|  |         "overviewIntro": "The AI on <a href='https://plantnet.org/' target='_blank'>PlantNet.org</a> thinks the images show the species below.", | ||||||
|  |         "overviewTitle": "Automatically detected species", | ||||||
|  |         "overviewVerify": "Please verify that correct species and link it to the tree", | ||||||
|  |         "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" | ||||||
|  |     }, | ||||||
|     "privacy": { |     "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.", |         "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.", | ||||||
|         "editingTitle": "When making changes", |         "editingTitle": "When making changes", | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue