MapComplete/src/UI/InputElement/Helpers/WikidataInput.svelte

147 lines
4.5 KiB
Svelte

<script lang="ts">
/**
* Allows to search through wikidata and to select one value
*/
import Translations from "../../i18n/Translations"
import Tr from "../../Base/Tr.svelte"
import { ImmutableStore, Store, Stores, UIEventSource } from "../../../Logic/UIEventSource"
import Wikidata, { WikidataResponse } from "../../../Logic/Web/Wikidata"
import Locale from "../../i18n/Locale"
import SearchField from "../../BigComponents/SearchField.svelte"
import Loading from "../../Base/Loading.svelte"
import Wikidatapreview from "../../Wikipedia/Wikidatapreview.svelte"
import { Utils } from "../../../Utils"
import WikidataValidator from "../Validators/WikidataValidator"
const t = Translations.t.general.wikipedia
export let searchValue = new UIEventSource(undefined)
export let placeholder = t.searchWikidata
export let allowMultiple = false
export let notInstanceOf: number[] = []
export let instanceOf: number[] = []
export let value: UIEventSource<string>
let selectedWikidataSingle: WikidataResponse = undefined
let selectedMany: Record<string, boolean> = {}
let previouslySeen = new Map<string, WikidataResponse>()
$: {
if (selectedWikidataSingle) {
value.setData(selectedWikidataSingle.id)
}
}
$: {
const v = []
for (const id in selectedMany) {
if (selectedMany[id]) {
v.push(id)
}
}
value.setData(v.join(";"))
}
let tooShort = new ImmutableStore<{ success: WikidataResponse[] }>({ success: undefined })
let searchResult: Store<{ success?: WikidataResponse[]; error?: any }> = searchValue.bind(
(searchText) => {
if (searchText.length < 3 && !searchText.match(/[qQ][0-9]+/)) {
return tooShort
}
const lang = Locale.language.data
const key = lang + ":" + searchText
let promise = WikidataValidator._searchCache.get(key)
if (promise === undefined) {
promise = Wikidata.searchAndFetch(searchText, {
lang,
maxCount: 5,
notInstanceOf,
instanceOf,
})
WikidataValidator._searchCache.set(key, promise)
}
return Stores.FromPromiseWithErr(promise)
}
)
let selectedWithoutSearch: Store<WikidataResponse[]> = searchResult.map((sr) => {
for (const wikidataItem of sr?.success ?? []) {
previouslySeen.set(wikidataItem.id, wikidataItem)
}
let knownIds: Set<string> = new Set(sr?.success?.map((item) => item.id))
const seen = [selectedWikidataSingle]
for (const id in selectedMany) {
if (selectedMany[id]) {
const item = previouslySeen.get(id)
seen.push(item)
}
}
return Utils.NoNull(seen).filter((i) => !knownIds.has(i.id))
})
</script>
<h3>
<Tr t={Translations.t.general.wikipedia.searchWikidata} />
</h3>
<form>
<SearchField {searchValue} placeholderText={placeholder} />
{#if $searchValue.trim().length === 0}
<Tr cls="w-full flex justify-center p-4" t={t.doSearch} />
{:else if $searchValue.trim().length < 3}
<Tr t={t.searchToShort} />
{:else if $searchResult === undefined}
<div class="flex w-full justify-center p-4">
<Loading>
<Tr t={Translations.t.general.loading} />
</Loading>
</div>
{:else if $searchResult.error !== undefined}
<div class="flex w-full justify-center p-4">
<Tr cls="alert" t={t.failed} />
</div>
{:else if $searchResult.success}
{#if $searchResult.success.length === 0}
<Tr cls="w-full flex justify-center p-4" t={t.noResults.Subs({ search: $searchValue })} />
{:else}
{#each $searchResult.success as wikidata}
<label class="low-interaction m-4 flex items-center rounded-xl p-2">
{#if allowMultiple}
<input type="checkbox" bind:checked={selectedMany[wikidata.id]} />
{:else}
<input
type="radio"
name="selectedWikidata"
value={wikidata}
bind:group={selectedWikidataSingle}
/>
{/if}
<Wikidatapreview {wikidata} />
</label>
{/each}
{/if}
{/if}
{#each $selectedWithoutSearch as wikidata}
<label class="low-interaction m-4 flex items-center rounded-xl p-2">
{#if allowMultiple}
<input type="checkbox" bind:checked={selectedMany[wikidata.id]} />
{:else}
<input
type="radio"
name="selectedWikidata"
value={wikidata}
bind:group={selectedWikidataSingle}
/>
{/if}
<Wikidatapreview {wikidata} />
</label>
{/each}
</form>