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

@ -380,6 +380,7 @@
"born": "Born: {value}", "born": "Born: {value}",
"died": "Died: {value}" "died": "Died: {value}"
}, },
"readMore": "Read the rest of the article",
"searchToShort": "Your search query is too short, enter a longer text", "searchToShort": "Your search query is too short, enter a longer text",
"searchWikidata": "Search on Wikidata", "searchWikidata": "Search on Wikidata",
"wikipediaboxTitle": "Wikipedia" "wikipediaboxTitle": "Wikipedia"
@ -498,7 +499,9 @@
}, },
"plantDetection": { "plantDetection": {
"back": "Back to species overview", "back": "Back to species overview",
"button": "Automatically detect the plant species using the AI of Plantnet.org",
"confirm": "Select species", "confirm": "Select species",
"done": "The species has been applied",
"error": "Something went wrong while detecting the tree species: {error}", "error": "Something went wrong while detecting the tree species: {error}",
"howTo": { "howTo": {
"intro": "For optimal results,", "intro": "For optimal results,",
@ -515,7 +518,8 @@
"poweredByPlantnet": "Powered by <a href='https://plantnet.org' target='_blank'>plantnet.org</a>", "poweredByPlantnet": "Powered by <a href='https://plantnet.org' target='_blank'>plantnet.org</a>",
"querying": "Querying plantnet.org with {length} images", "querying": "Querying plantnet.org with {length} images",
"seeInfo": "See more information about the species", "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": { "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.",

View file

@ -1,6 +1,6 @@
{ {
"name": "mapcomplete", "name": "mapcomplete",
"version": "0.32.0", "version": "0.33.0",
"repository": "https://github.com/pietervdvn/MapComplete", "repository": "https://github.com/pietervdvn/MapComplete",
"description": "A small website to edit OSM easily", "description": "A small website to edit OSM easily",
"bugs": "https://github.com/pietervdvn/MapComplete/issues", "bugs": "https://github.com/pietervdvn/MapComplete/issues",

View file

@ -1096,6 +1096,10 @@ video {
height: 2.75rem; height: 2.75rem;
} }
.h-10 {
height: 2.5rem;
}
.h-48 { .h-48 {
height: 12rem; height: 12rem;
} }
@ -1104,10 +1108,6 @@ video {
height: 10rem; height: 10rem;
} }
.h-10 {
height: 2.5rem;
}
.h-80 { .h-80 {
height: 20rem; height: 20rem;
} }
@ -1709,11 +1709,6 @@ video {
padding-right: 0.5rem; padding-right: 0.5rem;
} }
.py-2 {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}
.pl-1 { .pl-1 {
padding-left: 0.25rem; padding-left: 0.25rem;
} }
@ -2209,6 +2204,11 @@ input[type=text] {
border-radius: 0.5rem; border-radius: 0.5rem;
} }
.border-region {
border: 2px dashed var(--interactive-background);
border-radius: 0.5rem;
}
/******************* Styling of input elements **********************/ /******************* Styling of input elements **********************/
/** /**

View file

@ -985,6 +985,27 @@ export default class PlantNet {
} }
} }
export interface PlantNetSpeciesMatch {
score: number
gbif: { id: string /*Actually a number*/ }
species: {
scientificNameWithoutAuthor: string
scientificNameAuthorship: string
genus: {
scientificNameWithoutAuthor: string
scientificNameAuthorship: string
scientificName: string
}
family: {
scientificNameWithoutAuthor: string
scientificNameAuthorship: string
scientificName: string
}
commonNames: string[]
scientificName: string
}
}
export interface PlantNetResult { export interface PlantNetResult {
query: { query: {
project: string project: string
@ -995,26 +1016,7 @@ export interface PlantNetResult {
language: string language: string
preferedReferential: string preferedReferential: string
bestMatch: string bestMatch: string
results: { results: PlantNetSpeciesMatch[]
score: number
gbif: { id: string /*Actually a number*/ }
species: {
scientificNameWithoutAuthor: string
scientificNameAuthorship: string
genus: {
scientificNameWithoutAuthor: string
scientificNameAuthorship: string
scientificName: string
}
family: {
scientificNameWithoutAuthor: string
scientificNameAuthorship: string
scientificName: string
}
commonNames: string[]
scientificName: string
}
}[]
version: string version: string
remainingIdentificationRequests: number remainingIdentificationRequests: number
} }

View file

@ -10,12 +10,13 @@
const dispatch = createEventDispatcher<{ click }>() const dispatch = createEventDispatcher<{ click }>()
export let clss: string | undefined = undefined export let clss: string | undefined = undefined
export let imageClass: string | undefined = undefined
</script> </script>
<SubtleButton <SubtleButton
on:click={() => dispatch("click")} on:click={() => dispatch("click")}
options={{ extraClasses: twMerge("flex items-center", clss) }} 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" /> <slot slot="message" />
</SubtleButton> </SubtleButton>

View file

@ -20,6 +20,6 @@
<slot name="image" slot="image" /> <slot name="image" slot="image" />
<div class="flex w-full items-center justify-between" slot="message"> <div class="flex w-full items-center justify-between" slot="message">
<slot /> <slot />
<ChevronRightIcon class="h-12 w-12" /> <ChevronRightIcon class="h-12 w-12 shrink-0" />
</div> </div>
</SubtleButton> </SubtleButton>

View file

@ -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")
})
)
})
)
}
}

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>

View file

@ -1,18 +1,14 @@
import { Store, UIEventSource } from "../../Logic/UIEventSource" import { Store, UIEventSource } from "../../Logic/UIEventSource"
import Toggle from "../Input/Toggle"
import Lazy from "../Base/Lazy"
import { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider" import { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
import PlantNetSpeciesSearch from "../BigComponents/PlantNetSpeciesSearch" import PlantNetSpeciesSearch from "../BigComponents/PlantNetSpeciesSearch"
import Wikidata from "../../Logic/Web/Wikidata" import Wikidata from "../../Logic/Web/Wikidata"
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction" import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"
import { And } from "../../Logic/Tags/And" import { And } from "../../Logic/Tags/And"
import { Tag } from "../../Logic/Tags/Tag" 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 AllImageProviders from "../../Logic/ImageProviders/AllImageProviders"
import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization"
import SvelteUIElement from "../Base/SvelteUIElement"
import PlantNet from "../PlantNet/PlantNet.svelte"
export class PlantNetDetectionViz implements SpecialVisualization { export class PlantNetDetectionViz implements SpecialVisualization {
funcName = "plantnet_detection" funcName = "plantnet_detection"
@ -37,45 +33,29 @@ export class PlantNetDetectionViz implements SpecialVisualization {
imagePrefixes = [].concat(...args.map((a) => a.split(","))) imagePrefixes = [].concat(...args.map((a) => a.split(",")))
} }
const detect = new UIEventSource(false) const allProvidedImages: Store<ProvidedImage[]> = AllImageProviders.LoadImagesFor(
const toggle = new Toggle( tags,
new Lazy(() => { imagePrefixes
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.layout.id,
changeType: "plantnet-ai-detection",
}
)
await state.changes.applyAction(change)
})
}),
new SubtleButton(undefined, "Detect plant species with plantnet.org").onClick(() =>
detect.setData(true)
),
detect
) )
const imageUrls: Store<string[]> = allProvidedImages.map((pi) => pi.map((pi) => pi.url))
return new Combine([ async function applySpecies(selectedWikidata) {
toggle, selectedWikidata = Wikidata.ExtractKey(selectedWikidata)
new Combine([ const change = new ChangeTagAction(
Svg.plantnet_logo_svg().SetClass("w-10 h-10 p-1 mr-1 bg-white rounded-full"), tags.data.id,
Translations.t.plantDetection.poweredByPlantnet, new And([
]).SetClass("flex p-2 bg-gray-200 rounded-xl self-end"), new Tag("species:wikidata", selectedWikidata),
]).SetClass("flex flex-col") new Tag("source:species:wikidata", "PlantNet.org AI"),
]),
tags.data,
{
theme: state.layout.id,
changeType: "plantnet-ai-detection",
}
)
await state.changes.applyAction(change)
}
return new SvelteUIElement(PlantNet, { imageUrls, onConfirm: applySpecies })
} }
} }

View file

@ -538,7 +538,7 @@ export default class SpecialVisualizations {
const keys = args[0].split(";").map((k) => k.trim()) const keys = args[0].split(";").map((k) => k.trim())
const wikiIds: Store<string[]> = tagsSource.map((tags) => { const wikiIds: Store<string[]> = tagsSource.map((tags) => {
const key = keys.find((k) => tags[k] !== undefined && tags[k] !== "") 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, { return new SvelteUIElement(WikipediaPanel, {
wikiIds, wikiIds,

View file

@ -29,6 +29,10 @@
areas, where some buttons might appear. areas, where some buttons might appear.
</p> </p>
<div class="border-interactive interactive">
Highly interactive area (mostly: active question)
</div>
<div class="flex"> <div class="flex">
<button class="primary"> <button class="primary">
<ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")} /> <ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")} />

View file

@ -126,7 +126,7 @@ export default class WikidataPreviewBox extends VariableUiElement {
new Combine([ new Combine([
Translation.fromMap(wikidata.labels)?.SetClass("font-bold"), Translation.fromMap(wikidata.labels)?.SetClass("font-bold"),
link, link,
]).SetClass("flex justify-between"), ]).SetClass("flex justify-between flex-wrap-reverse"),
Translation.fromMap(wikidata.descriptions), Translation.fromMap(wikidata.descriptions),
WikidataPreviewBox.QuickFacts(wikidata, options), WikidataPreviewBox.QuickFacts(wikidata, options),
...(options?.extraItems ?? []), ...(options?.extraItems ?? []),

View file

@ -131,7 +131,7 @@ Another example is to search for species and trees:
const searchResult: Store<{ success?: WikidataResponse[]; error?: any }> = searchField const searchResult: Store<{ success?: WikidataResponse[]; error?: any }> = searchField
.GetValue() .GetValue()
.bind((searchText) => { .bind((searchText) => {
if (searchText.length < 3) { if (searchText.length < 3 && !searchText.match(/[qQ][0-9]+/)) {
return tooShort return tooShort
} }
const lang = Locale.language.data const lang = Locale.language.data

View file

@ -11,7 +11,7 @@
import Translations from "../i18n/Translations"; import Translations from "../i18n/Translations";
/** /**
* Small helper * Shows a wikipedia-article + wikidata preview for the given item
*/ */
export let wikipediaDetails: Store<FullWikipediaDetails>; export let wikipediaDetails: Store<FullWikipediaDetails>;
</script> </script>
@ -23,9 +23,11 @@
<Tr t={Translations.t.general.wikipedia.fromWikipedia} /> <Tr t={Translations.t.general.wikipedia.fromWikipedia} />
</a> </a>
{/if} {/if}
{#if $wikipediaDetails.wikidata} {#if $wikipediaDetails.wikidata}
<ToSvelte construct={WikidataPreviewBox.WikidataResponsePreview($wikipediaDetails.wikidata)} /> <ToSvelte construct={WikidataPreviewBox.WikidataResponsePreview($wikipediaDetails.wikidata)} />
{/if} {/if}
{#if $wikipediaDetails.articleUrl} {#if $wikipediaDetails.articleUrl}
{#if $wikipediaDetails.firstParagraph === "" || $wikipediaDetails.firstParagraph === undefined} {#if $wikipediaDetails.firstParagraph === "" || $wikipediaDetails.firstParagraph === undefined}
@ -42,7 +44,7 @@
style={(open ? "transform: rotate(90deg); " : "") + style={(open ? "transform: rotate(90deg); " : "") +
" transition: all .25s linear; width: 1.5rem; height: 1.5rem"} " transition: all .25s linear; width: 1.5rem; height: 1.5rem"}
/> />
Read the rest of the article <Tr t={Translations.t.general.wikipedia.readMore}/>
</span> </span>
</DisclosureButton> </DisclosureButton>
<DisclosurePanel> <DisclosurePanel>

View file

@ -16,7 +16,7 @@
*/ */
export let wikiIds: Store<string[]>; export let wikiIds: Store<string[]>;
let wikipediaStores: Store<Store<FullWikipediaDetails>[]> = Locale.language.bind((language) => 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; let _wikipediaStores;
onDestroy( onDestroy(

View file

@ -154,6 +154,11 @@ input[type=text] {
border-radius: 0.5rem; border-radius: 0.5rem;
} }
.border-region {
border: 2px dashed var(--interactive-background);
border-radius: 0.5rem;
}
/******************* Styling of input elements **********************/ /******************* Styling of input elements **********************/