forked from MapComplete/MapComplete
This commit is contained in:
parent
3a2addbddc
commit
d5430891bf
22 changed files with 580 additions and 507 deletions
|
@ -124,6 +124,7 @@
|
||||||
"helperArgs": [
|
"helperArgs": [
|
||||||
"name",
|
"name",
|
||||||
{
|
{
|
||||||
|
"multiple": "yes",
|
||||||
"notInstanceOf": [
|
"notInstanceOf": [
|
||||||
"Q79007",
|
"Q79007",
|
||||||
"Q22698"
|
"Q22698"
|
||||||
|
|
|
@ -78,7 +78,14 @@ export class WikimediaImageProvider extends ImageProvider {
|
||||||
return new SvelteUIElement(Wikimedia_commons_white).SetStyle("width:2em;height: 2em")
|
return new SvelteUIElement(Wikimedia_commons_white).SetStyle("width:2em;height: 2em")
|
||||||
}
|
}
|
||||||
|
|
||||||
public PrepUrl(value: string): ProvidedImage {
|
public PrepUrl(value: NonNullable<string>): ProvidedImage
|
||||||
|
public PrepUrl(value: undefined): undefined
|
||||||
|
|
||||||
|
public PrepUrl(value: string): ProvidedImage
|
||||||
|
public PrepUrl(value: string | undefined): ProvidedImage | undefined{
|
||||||
|
if(value === undefined){
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
value = WikimediaImageProvider.removeCommonsPrefix(value)
|
value = WikimediaImageProvider.removeCommonsPrefix(value)
|
||||||
|
|
||||||
if (value.startsWith("File:")) {
|
if (value.startsWith("File:")) {
|
||||||
|
|
|
@ -119,6 +119,8 @@ export interface WikidataAdvancedSearchoptions extends WikidataSearchoptions {
|
||||||
notInstanceOf?: number[]
|
notInstanceOf?: number[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SparqlResult {results: { bindings: {item, label, description, num}[] }}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility functions around wikidata
|
* Utility functions around wikidata
|
||||||
*/
|
*/
|
||||||
|
@ -202,7 +204,7 @@ export default class Wikidata {
|
||||||
} ORDER BY ASC(?num) LIMIT ${options?.maxCount ?? 20}`
|
} ORDER BY ASC(?num) LIMIT ${options?.maxCount ?? 20}`
|
||||||
const url = wds.sparqlQuery(sparql)
|
const url = wds.sparqlQuery(sparql)
|
||||||
|
|
||||||
const result = await Utils.downloadJson(url)
|
const result = await Utils.downloadJson<SparqlResult>(url)
|
||||||
/*The full uri of the wikidata-item*/
|
/*The full uri of the wikidata-item*/
|
||||||
|
|
||||||
return result.results.bindings.map(({ item, label, description, num }) => ({
|
return result.results.bindings.map(({ item, label, description, num }) => ({
|
||||||
|
@ -389,7 +391,7 @@ export default class Wikidata {
|
||||||
' SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }\n' +
|
' SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }\n' +
|
||||||
"}"
|
"}"
|
||||||
const url = wds.sparqlQuery(query)
|
const url = wds.sparqlQuery(query)
|
||||||
const result = await Utils.downloadJsonCached(url, 24 * 60 * 60 * 1000)
|
const result = await Utils.downloadJsonCached<SparqlResult>(url, 24 * 60 * 60 * 1000)
|
||||||
return result.results.bindings
|
return result.results.bindings
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -420,7 +422,7 @@ export default class Wikidata {
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = "https://www.wikidata.org/wiki/Special:EntityData/" + id + ".json"
|
const url = "https://www.wikidata.org/wiki/Special:EntityData/" + id + ".json"
|
||||||
const entities = (await Utils.downloadJsonCached(url, 10000)).entities
|
const entities = (await Utils.downloadJsonCached<{entities}>(url, 10000)).entities
|
||||||
const firstKey = <string>Array.from(Object.keys(entities))[0] // Roundabout way to fetch the entity; it might have been a redirect
|
const firstKey = <string>Array.from(Object.keys(entities))[0] // Roundabout way to fetch the entity; it might have been a redirect
|
||||||
const response = entities[firstKey]
|
const response = entities[firstKey]
|
||||||
|
|
||||||
|
|
|
@ -215,7 +215,7 @@ export default class Wikipedia {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async GetArticleUncachedAsync(pageName: string): Promise<string> {
|
private async GetArticleUncachedAsync(pageName: string): Promise<string> {
|
||||||
const response = await Utils.downloadJson(this.getDataUrl(pageName))
|
const response = await Utils.downloadJson<any>(this.getDataUrl(pageName))
|
||||||
if (response?.parse?.text === undefined) {
|
if (response?.parse?.text === undefined) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
|
@ -289,6 +289,11 @@ export interface QuestionableTagRenderingConfigJson extends TagRenderingConfigJs
|
||||||
* group: expert
|
* group: expert
|
||||||
*/
|
*/
|
||||||
postfixDistinguished?: string
|
postfixDistinguished?: string
|
||||||
|
/**
|
||||||
|
* Extra arguments to configure the input element
|
||||||
|
* group: hidden
|
||||||
|
*/
|
||||||
|
helperArgs: any
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -69,6 +69,7 @@ export default class TagRenderingConfig {
|
||||||
readonly inline: boolean
|
readonly inline: boolean
|
||||||
readonly default?: string
|
readonly default?: string
|
||||||
readonly postfixDistinguished?: string
|
readonly postfixDistinguished?: string
|
||||||
|
readonly args?: any
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly multiAnswer: boolean
|
public readonly multiAnswer: boolean
|
||||||
|
@ -203,6 +204,7 @@ export default class TagRenderingConfig {
|
||||||
inline: json.freeform.inline ?? false,
|
inline: json.freeform.inline ?? false,
|
||||||
default: json.freeform.default,
|
default: json.freeform.default,
|
||||||
postfixDistinguished: json.freeform.postfixDistinguished?.trim(),
|
postfixDistinguished: json.freeform.postfixDistinguished?.trim(),
|
||||||
|
args: json.freeform.helperArgs
|
||||||
}
|
}
|
||||||
if (json.freeform["extraTags"] !== undefined) {
|
if (json.freeform["extraTags"] !== undefined) {
|
||||||
throw `Freeform.extraTags is defined. This should probably be 'freeform.addExtraTag' (at ${context})`
|
throw `Freeform.extraTags is defined. This should probably be 'freeform.addExtraTag' (at ${context})`
|
||||||
|
|
|
@ -26,7 +26,7 @@ export default class SvelteUIElement<
|
||||||
|
|
||||||
constructor(svelteElement, props?: Props, events?: Events, slots?: Slots) {
|
constructor(svelteElement, props?: Props, events?: Events, slots?: Slots) {
|
||||||
super()
|
super()
|
||||||
this._svelteComponent = svelteElement
|
this._svelteComponent = <any> svelteElement
|
||||||
this._props = props ?? <Props>{}
|
this._props = props ?? <Props>{}
|
||||||
this._events = events
|
this._events = events
|
||||||
this._slots = slots
|
this._slots = slots
|
||||||
|
|
59
src/UI/BigComponents/SearchField.svelte
Normal file
59
src/UI/BigComponents/SearchField.svelte
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||||
|
import Translations from "../i18n/Translations"
|
||||||
|
import Loading from "../Base/Loading.svelte"
|
||||||
|
import Hotkeys from "../Base/Hotkeys"
|
||||||
|
import { createEventDispatcher, onDestroy } from "svelte"
|
||||||
|
import { placeholder } from "../../Utils/placeholder"
|
||||||
|
import { SearchIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||||
|
import { ariaLabel } from "../../Utils/ariaLabel"
|
||||||
|
import { Translation } from "../i18n/Translation"
|
||||||
|
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher<{search: string}>()
|
||||||
|
|
||||||
|
export let searchValue: UIEventSource<string>
|
||||||
|
export let placeholderText: Translation = Translations.t.general.search.search
|
||||||
|
export let feedback = new UIEventSource<string>(undefined)
|
||||||
|
|
||||||
|
|
||||||
|
let isRunning: boolean = false
|
||||||
|
|
||||||
|
let inputElement: HTMLInputElement
|
||||||
|
|
||||||
|
function _performSearch(){
|
||||||
|
dispatch("search", searchValue.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="normal-background flex justify-between rounded-full">
|
||||||
|
<form class="flex w-full flex-wrap" on:submit|preventDefault={() => {}}>
|
||||||
|
{#if isRunning}
|
||||||
|
<Loading>{Translations.t.general.search.searching}</Loading>
|
||||||
|
{:else}
|
||||||
|
<div class="flex w-full border border-gray-300 rounded-full">
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="search"
|
||||||
|
class="w-full outline-none mx-2"
|
||||||
|
bind:this={inputElement}
|
||||||
|
on:keypress={(keypr) => {
|
||||||
|
feedback.set(undefined)
|
||||||
|
return keypr.key === "Enter" ? _performSearch() : undefined
|
||||||
|
}}
|
||||||
|
bind:value={$searchValue}
|
||||||
|
use:placeholder={placeholderText}
|
||||||
|
use:ariaLabel={Translations.t.general.search.search}
|
||||||
|
/>
|
||||||
|
<SearchIcon aria-hidden="true" class="h-6 w-6 self-end" on:click={event => _performSearch()} />
|
||||||
|
</div>
|
||||||
|
{#if $feedback !== undefined}
|
||||||
|
<!-- The feedback is _always_ shown for screenreaders and to make sure that the searchfield can still be selected by tabbing-->
|
||||||
|
<div class="alert" role="alert" aria-live="assertive">
|
||||||
|
{$feedback}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
</form>
|
||||||
|
</div>
|
145
src/UI/InputElement/Helpers/WikidataInput.svelte
Normal file
145
src/UI/InputElement/Helpers/WikidataInput.svelte
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
<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("Tom boonen")
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$:{
|
||||||
|
console.log(selectedMany)
|
||||||
|
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 ?? []) {
|
||||||
|
console.log("Saving", wikidataItem.id)
|
||||||
|
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}></SearchField>
|
||||||
|
|
||||||
|
{#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="w-full flex justify-center p-4">
|
||||||
|
<Loading>
|
||||||
|
<Tr t={Translations.t.general.loading} />
|
||||||
|
</Loading>
|
||||||
|
</div>
|
||||||
|
{:else if $searchResult.error !== undefined}
|
||||||
|
<div class="w-full flex 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 p-2 rounded-xl flex items-center">
|
||||||
|
{#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 p-2 rounded-xl flex items-center">
|
||||||
|
{#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>
|
||||||
|
|
|
@ -19,6 +19,8 @@
|
||||||
import OpeningHoursInput from "./Helpers/OpeningHoursInput.svelte"
|
import OpeningHoursInput from "./Helpers/OpeningHoursInput.svelte"
|
||||||
import SlopeInput from "./Helpers/SlopeInput.svelte"
|
import SlopeInput from "./Helpers/SlopeInput.svelte"
|
||||||
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
||||||
|
import WikidataInput from "./Helpers/WikidataInput.svelte"
|
||||||
|
import WikidataInputHelper from "./WikidataInputHelper.svelte"
|
||||||
|
|
||||||
export let type: ValidatorType
|
export let type: ValidatorType
|
||||||
export let value: UIEventSource<string | object>
|
export let value: UIEventSource<string | object>
|
||||||
|
@ -26,17 +28,13 @@
|
||||||
export let feature: Feature
|
export let feature: Feature
|
||||||
export let args: (string | number | boolean)[] = undefined
|
export let args: (string | number | boolean)[] = undefined
|
||||||
export let state: SpecialVisualizationState
|
export let state: SpecialVisualizationState
|
||||||
export let helperArgs: (string | number | boolean)[]
|
|
||||||
export let key: string
|
|
||||||
export let extraTags: UIEventSource<Record<string, string>>
|
|
||||||
|
|
||||||
let properties = { feature, args: args ?? [] }
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if type === "translation"}
|
{#if type === "translation"}
|
||||||
<TranslationInput {value} on:submit {args} />
|
<TranslationInput {value} on:submit {args} />
|
||||||
{:else if type === "direction"}
|
{:else if type === "direction"}
|
||||||
<DirectionInput {value} mapProperties={InputHelpers.constructMapProperties(properties)} />
|
<DirectionInput {value} mapProperties={InputHelpers.constructMapProperties( { feature, args: args ?? [] })} />
|
||||||
{:else if type === "date"}
|
{:else if type === "date"}
|
||||||
<DateInput {value} />
|
<DateInput {value} />
|
||||||
{:else if type === "color"}
|
{:else if type === "color"}
|
||||||
|
@ -52,5 +50,5 @@
|
||||||
{:else if type === "slope"}
|
{:else if type === "slope"}
|
||||||
<SlopeInput {value} {feature} {state} />
|
<SlopeInput {value} {feature} {state} />
|
||||||
{:else if type === "wikidata"}
|
{:else if type === "wikidata"}
|
||||||
<ToSvelte construct={() => InputHelpers.constructWikidataHelper(value, properties)} />
|
<WikidataInputHelper {value} {feature} {state} {args}/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||||
|
|
||||||
import { MapProperties } from "../../Models/MapProperties"
|
import { MapProperties } from "../../Models/MapProperties"
|
||||||
import WikidataSearchBox from "../Wikipedia/WikidataSearchBox"
|
|
||||||
import Wikidata from "../../Logic/Web/Wikidata"
|
|
||||||
import { Utils } from "../../Utils"
|
|
||||||
import Locale from "../i18n/Locale"
|
|
||||||
import { Feature } from "geojson"
|
import { Feature } from "geojson"
|
||||||
import { GeoOperations } from "../../Logic/GeoOperations"
|
import { GeoOperations } from "../../Logic/GeoOperations"
|
||||||
|
|
||||||
|
@ -68,67 +64,5 @@ export default class InputHelpers {
|
||||||
return mapProperties
|
return mapProperties
|
||||||
}
|
}
|
||||||
|
|
||||||
public static constructWikidataHelper(
|
|
||||||
value: UIEventSource<string>,
|
|
||||||
props: InputHelperProperties
|
|
||||||
) {
|
|
||||||
const inputHelperOptions = props
|
|
||||||
const args = inputHelperOptions.args ?? []
|
|
||||||
const searchKey: string = <string>args[0] ?? "name"
|
|
||||||
|
|
||||||
const searchFor: string =
|
|
||||||
searchKey
|
|
||||||
.split(";")
|
|
||||||
.map((k) => inputHelperOptions.feature?.properties[k]?.toLowerCase())
|
|
||||||
.find((foundValue) => !!foundValue) ?? ""
|
|
||||||
|
|
||||||
let searchForValue: UIEventSource<string> = new UIEventSource(searchFor)
|
|
||||||
const options: any = args[1]
|
|
||||||
if (searchFor !== undefined && options !== undefined) {
|
|
||||||
const prefixes = <string[] | Record<string, string[]>>options["removePrefixes"] ?? []
|
|
||||||
const postfixes = <string[] | Record<string, string[]>>options["removePostfixes"] ?? []
|
|
||||||
const defaultValueCandidate = Locale.language.map((lg) => {
|
|
||||||
const prefixesUnrwapped: RegExp[] = (
|
|
||||||
Array.isArray(prefixes) ? prefixes : prefixes[lg] ?? []
|
|
||||||
).map((s) => new RegExp("^" + s, "i"))
|
|
||||||
const postfixesUnwrapped: RegExp[] = (
|
|
||||||
Array.isArray(postfixes) ? postfixes : postfixes[lg] ?? []
|
|
||||||
).map((s) => new RegExp(s + "$", "i"))
|
|
||||||
let clipped = searchFor
|
|
||||||
|
|
||||||
for (const postfix of postfixesUnwrapped) {
|
|
||||||
const match = searchFor.match(postfix)
|
|
||||||
if (match !== null) {
|
|
||||||
clipped = searchFor.substring(0, searchFor.length - match[0].length)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const prefix of prefixesUnrwapped) {
|
|
||||||
const match = searchFor.match(prefix)
|
|
||||||
if (match !== null) {
|
|
||||||
clipped = searchFor.substring(match[0].length)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return clipped
|
|
||||||
})
|
|
||||||
|
|
||||||
defaultValueCandidate.addCallbackAndRun((clipped) => searchForValue.setData(clipped))
|
|
||||||
}
|
|
||||||
|
|
||||||
let instanceOf: number[] = Utils.NoNull(
|
|
||||||
(options?.instanceOf ?? []).map((i) => Wikidata.QIdToNumber(i))
|
|
||||||
)
|
|
||||||
let notInstanceOf: number[] = Utils.NoNull(
|
|
||||||
(options?.notInstanceOf ?? []).map((i) => Wikidata.QIdToNumber(i))
|
|
||||||
)
|
|
||||||
|
|
||||||
return new WikidataSearchBox({
|
|
||||||
value,
|
|
||||||
searchText: searchForValue,
|
|
||||||
instanceOf,
|
|
||||||
notInstanceOf,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,101 @@
|
||||||
import Combine from "../../Base/Combine"
|
import Combine from "../../Base/Combine"
|
||||||
import Wikidata from "../../../Logic/Web/Wikidata"
|
import Wikidata, { WikidataResponse } from "../../../Logic/Web/Wikidata"
|
||||||
import WikidataSearchBox from "../../Wikipedia/WikidataSearchBox"
|
import WikidataSearchBox from "../../Wikipedia/WikidataSearchBox"
|
||||||
import { Validator } from "../Validator"
|
import { Validator } from "../Validator"
|
||||||
import { Translation } from "../../i18n/Translation"
|
import { Translation } from "../../i18n/Translation"
|
||||||
import Translations from "../../i18n/Translations"
|
import Translations from "../../i18n/Translations"
|
||||||
|
import Title from "../../Base/Title"
|
||||||
|
import Table from "../../Base/Table"
|
||||||
|
|
||||||
export default class WikidataValidator extends Validator {
|
export default class WikidataValidator extends Validator {
|
||||||
|
public static readonly _searchCache = new Map<string, Promise<WikidataResponse[]>>()
|
||||||
|
|
||||||
|
public static docs = new Combine([
|
||||||
|
new Title("Helper arguments"),
|
||||||
|
new Table(
|
||||||
|
["name", "doc"],
|
||||||
|
[
|
||||||
|
[
|
||||||
|
"key",
|
||||||
|
"the value of this tag will initialize search (default: name). This can be a ';'-separated list in which case every key will be inspected. The non-null value will be used as search",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"options",
|
||||||
|
new Combine([
|
||||||
|
"A JSON-object of type `{ removePrefixes: string[], removePostfixes: string[] }`.",
|
||||||
|
new Table(
|
||||||
|
["subarg", "doc"],
|
||||||
|
[
|
||||||
|
[
|
||||||
|
"removePrefixes",
|
||||||
|
"remove these snippets of text from the start of the passed string to search. This is either a list OR a hash of languages to a list. The individual strings are interpreted as case ignoring regexes",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"removePostfixes",
|
||||||
|
"remove these snippets of text from the end of the passed string to search. This is either a list OR a hash of languages to a list. The individual strings are interpreted as case ignoring regexes.",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"instanceOf",
|
||||||
|
"A list of Q-identifier which indicates that the search results _must_ be an entity of this type, e.g. [`Q5`](https://www.wikidata.org/wiki/Q5) for humans",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"notInstanceof",
|
||||||
|
"A list of Q-identifiers which indicates that the search results _must not_ be an entity of this type, e.g. [`Q79007`](https://www.wikidata.org/wiki/Q79007) to filter away all streets from the search results",
|
||||||
|
],
|
||||||
|
["multiple",
|
||||||
|
"If 'yes' or 'true', will allow to select multiple values at once"]
|
||||||
|
]
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
]
|
||||||
|
),
|
||||||
|
new Title("Example usage"),
|
||||||
|
`The following is the 'freeform'-part of a layer config which will trigger a search for the wikidata item corresponding with the name of the selected feature. It will also remove '-street', '-square', ... if found at the end of the name
|
||||||
|
|
||||||
|
\`\`\`json
|
||||||
|
"freeform": {
|
||||||
|
"key": "name:etymology:wikidata",
|
||||||
|
"type": "wikidata",
|
||||||
|
"helperArgs": [
|
||||||
|
"name",
|
||||||
|
{
|
||||||
|
"removePostfixes": {"en": [
|
||||||
|
"street",
|
||||||
|
"boulevard",
|
||||||
|
"path",
|
||||||
|
"square",
|
||||||
|
"plaza",
|
||||||
|
],
|
||||||
|
"nl": ["straat","plein","pad","weg",laan"],
|
||||||
|
"fr":["route (de|de la|de l'| de le)"]
|
||||||
|
},
|
||||||
|
|
||||||
|
"#": "Remove streets and parks from the search results:"
|
||||||
|
"notInstanceOf": ["Q79007","Q22698"]
|
||||||
|
}
|
||||||
|
|
||||||
|
]
|
||||||
|
}
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
Another example is to search for species and trees:
|
||||||
|
|
||||||
|
\`\`\`json
|
||||||
|
"freeform": {
|
||||||
|
"key": "species:wikidata",
|
||||||
|
"type": "wikidata",
|
||||||
|
"helperArgs": [
|
||||||
|
"species",
|
||||||
|
{
|
||||||
|
"instanceOf": [10884, 16521]
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
\`\`\`
|
||||||
|
`,
|
||||||
|
])
|
||||||
constructor() {
|
constructor() {
|
||||||
super("wikidata", new Combine(["A wikidata identifier, e.g. Q42.", WikidataSearchBox.docs]))
|
super("wikidata", new Combine(["A wikidata identifier, e.g. Q42.", WikidataValidator.docs]))
|
||||||
}
|
}
|
||||||
|
|
||||||
public isValid(str): boolean {
|
public isValid(str): boolean {
|
||||||
|
@ -44,4 +132,48 @@ export default class WikidataValidator extends Validator {
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param searchTerm
|
||||||
|
* @param postfixesToRemove
|
||||||
|
* @param prefixesToRemove
|
||||||
|
* @param language
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* WikidataValidator.removePostAndPrefixes("Elf-Julistraat", [], ["straat", "laan"], "nl") // => "Elf-Juli"
|
||||||
|
* WikidataValidator.removePostAndPrefixes("Elf-Julistraat", [], {"nl":["straat", "laan"], "en": ["street"]}, "nl") // => "Elf-Juli"
|
||||||
|
* WikidataValidator.removePostAndPrefixes("Elf-Julistraat", [], {"nl":["straat", "laan"], "en": ["street"]}, "en") // => "Elf-Julistraat"
|
||||||
|
*/
|
||||||
|
public static removePostAndPrefixes(searchTerm: string, prefixesToRemove: string[] | Record<string, string[]>, postfixesToRemove: string[] | Record<string, string[]>, language: string): string {
|
||||||
|
const prefixes = prefixesToRemove
|
||||||
|
const postfixes = postfixesToRemove
|
||||||
|
const prefixesUnwrapped: RegExp[] = (
|
||||||
|
Array.isArray(prefixes) ? prefixes : prefixes[language] ?? []
|
||||||
|
).map((s) => new RegExp("^" + s, "i"))
|
||||||
|
|
||||||
|
const postfixesUnwrapped: RegExp[] = (
|
||||||
|
Array.isArray(postfixes) ? postfixes : postfixes[language] ?? []
|
||||||
|
).map((s) => new RegExp(s + "$", "i"))
|
||||||
|
|
||||||
|
|
||||||
|
let clipped = searchTerm.trim()
|
||||||
|
|
||||||
|
for (const postfix of postfixesUnwrapped) {
|
||||||
|
const match = searchTerm.trim().match(postfix)
|
||||||
|
if (match !== null) {
|
||||||
|
clipped = searchTerm.trim().substring(0, searchTerm.trim().length - match[0].length)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const prefix of prefixesUnwrapped) {
|
||||||
|
const match = searchTerm.trim().match(prefix)
|
||||||
|
if (match !== null) {
|
||||||
|
clipped = searchTerm.trim().substring(match[0].length)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return clipped
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
58
src/UI/InputElement/WikidataInputHelper.svelte
Normal file
58
src/UI/InputElement/WikidataInputHelper.svelte
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
<script lang="ts">/**
|
||||||
|
*
|
||||||
|
Wrapper around 'WikidataInput.svelte' which handles the arguments
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||||
|
import Locale from "../i18n/Locale"
|
||||||
|
import { Utils } from "../../Utils"
|
||||||
|
import Wikidata from "../../Logic/Web/Wikidata"
|
||||||
|
import WikidataInput from "./Helpers/WikidataInput.svelte"
|
||||||
|
import type { Feature } from "geojson"
|
||||||
|
import { onDestroy } from "svelte"
|
||||||
|
import WikidataValidator from "./Validators/WikidataValidator"
|
||||||
|
|
||||||
|
export let args: (string | number | boolean)[] = []
|
||||||
|
export let feature: Feature
|
||||||
|
|
||||||
|
export let value: UIEventSource<string>
|
||||||
|
|
||||||
|
let searchKey: string = <string>args[0] ?? "name"
|
||||||
|
|
||||||
|
let searchFor: string =
|
||||||
|
searchKey
|
||||||
|
.split(";")
|
||||||
|
.map((k) => feature?.properties[k]?.toLowerCase())
|
||||||
|
.find((foundValue) => !!foundValue) ?? ""
|
||||||
|
|
||||||
|
const options: any = args[1]
|
||||||
|
console.log(">>>", args)
|
||||||
|
|
||||||
|
let searchForValue: UIEventSource<string> = new UIEventSource(searchFor)
|
||||||
|
|
||||||
|
onDestroy(
|
||||||
|
Locale.language.addCallbackAndRunD(lg => {
|
||||||
|
console.log(options)
|
||||||
|
if (searchFor !== undefined && options !== undefined) {
|
||||||
|
const post = options["removePostfixes"] ?? []
|
||||||
|
const pre = options["removePrefixes"] ?? []
|
||||||
|
const clipped = WikidataValidator.removePostAndPrefixes(searchFor, pre, post, lg)
|
||||||
|
console.log("Got clipped value:", clipped, post, pre)
|
||||||
|
searchForValue.setData(clipped)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
let instanceOf: number[] = Utils.NoNull(
|
||||||
|
(options?.instanceOf ?? []).map((i) => Wikidata.QIdToNumber(i))
|
||||||
|
)
|
||||||
|
let notInstanceOf: number[] = Utils.NoNull(
|
||||||
|
(options?.notInstanceOf ?? []).map((i) => Wikidata.QIdToNumber(i))
|
||||||
|
)
|
||||||
|
|
||||||
|
let allowMultipleArg = options?.["multiple"]
|
||||||
|
let allowMultiple = allowMultipleArg === "yes" || (""+allowMultipleArg) === "true"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<WikidataInput searchValue={searchForValue} {value} {instanceOf} {notInstanceOf} {allowMultiple}/>
|
|
@ -4,14 +4,13 @@
|
||||||
*/
|
*/
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
import type { PlantNetSpeciesMatch } from "../../Logic/Web/PlantNet"
|
import type { PlantNetSpeciesMatch } from "../../Logic/Web/PlantNet"
|
||||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||||
import Wikidata from "../../Logic/Web/Wikidata"
|
import Wikidata from "../../Logic/Web/Wikidata"
|
||||||
import NextButton from "../Base/NextButton.svelte"
|
import NextButton from "../Base/NextButton.svelte"
|
||||||
import Loading from "../Base/Loading.svelte"
|
import Loading from "../Base/Loading.svelte"
|
||||||
import WikidataPreviewBox from "../Wikipedia/WikidataPreviewBox"
|
|
||||||
import Tr from "../Base/Tr.svelte"
|
import Tr from "../Base/Tr.svelte"
|
||||||
import Translations from "../i18n/Translations"
|
import Translations from "../i18n/Translations"
|
||||||
import ToSvelte from "../Base/ToSvelte.svelte"
|
import WikidatapreviewWithLoading from "../Wikipedia/WikidatapreviewWithLoading.svelte"
|
||||||
|
|
||||||
export let species: PlantNetSpeciesMatch
|
export let species: PlantNetSpeciesMatch
|
||||||
let wikidata = UIEventSource.FromPromise(
|
let wikidata = UIEventSource.FromPromise(
|
||||||
|
@ -46,16 +45,12 @@
|
||||||
/>
|
/>
|
||||||
</Loading>
|
</Loading>
|
||||||
{:else}
|
{:else}
|
||||||
<ToSvelte
|
|
||||||
construct={() =>
|
<WikidatapreviewWithLoading wikidataId={wikidataId} imageStyle="max-width: 8rem; width: unset; height: 8rem">
|
||||||
new WikidataPreviewBox(wikidataId, {
|
<div slot="extra">
|
||||||
imageStyle: "max-width: 8rem; width: unset; height: 8rem",
|
<Tr cls="thanks w-fit self-center" t={ t.matchPercentage
|
||||||
extraItems: [
|
.Subs({ match: Math.round(species.score * 100) })}/>
|
||||||
t.matchPercentage
|
</div>
|
||||||
.Subs({ match: Math.round(species.score * 100) })
|
</WikidatapreviewWithLoading>
|
||||||
.SetClass("thanks w-fit self-center"),
|
|
||||||
],
|
|
||||||
}).SetClass("w-full")}
|
|
||||||
/>
|
|
||||||
{/if}
|
{/if}
|
||||||
</NextButton>
|
</NextButton>
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
export let unvalidatedText: UIEventSource<string> = new UIEventSource<string>(value.data)
|
export let unvalidatedText: UIEventSource<string> = new UIEventSource<string>(value.data)
|
||||||
export let config: TagRenderingConfig
|
export let config: TagRenderingConfig
|
||||||
export let tags: UIEventSource<Record<string, string>>
|
export let tags: UIEventSource<Record<string, string>>
|
||||||
export let extraTags: UIEventSource<Record<string, string>>
|
|
||||||
|
|
||||||
export let feature: Feature = undefined
|
export let feature: Feature = undefined
|
||||||
export let state: SpecialVisualizationState
|
export let state: SpecialVisualizationState
|
||||||
|
@ -28,8 +27,6 @@
|
||||||
inline = false
|
inline = false
|
||||||
inline = config.freeform?.inline
|
inline = config.freeform?.inline
|
||||||
}
|
}
|
||||||
let helperArgs = config.freeform?.helperArgs
|
|
||||||
let key = config.freeform?.key
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{ selected }>()
|
const dispatch = createEventDispatcher<{ selected }>()
|
||||||
export let feedback: UIEventSource<Translation>
|
export let feedback: UIEventSource<Translation>
|
||||||
|
@ -73,14 +70,11 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<InputHelper
|
<InputHelper
|
||||||
args={config.freeform.helperArgs}
|
args={config.freeform.args}
|
||||||
{feature}
|
{feature}
|
||||||
type={config.freeform.type}
|
type={config.freeform.type}
|
||||||
{value}
|
{value}
|
||||||
{state}
|
{state}
|
||||||
{helperArgs}
|
|
||||||
{key}
|
|
||||||
{extraTags}
|
|
||||||
on:submit
|
on:submit
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,34 +1,21 @@
|
||||||
import { VariableUiElement } from "../Base/VariableUIElement"
|
import { TypedTranslation } from "../i18n/Translation"
|
||||||
import { Store } from "../../Logic/UIEventSource"
|
|
||||||
import Wikidata, { WikidataResponse } from "../../Logic/Web/Wikidata"
|
|
||||||
import { Translation, TypedTranslation } from "../i18n/Translation"
|
|
||||||
import { FixedUiElement } from "../Base/FixedUiElement"
|
|
||||||
import Loading from "../Base/Loading"
|
|
||||||
import Translations from "../i18n/Translations"
|
import Translations from "../i18n/Translations"
|
||||||
import Combine from "../Base/Combine"
|
|
||||||
import Img from "../Base/Img"
|
|
||||||
import { WikimediaImageProvider } from "../../Logic/ImageProviders/WikimediaImageProvider"
|
|
||||||
import Link from "../Base/Link"
|
|
||||||
import BaseUIElement from "../BaseUIElement"
|
|
||||||
import { Utils } from "../../Utils"
|
|
||||||
import SvelteUIElement from "../Base/SvelteUIElement"
|
|
||||||
import { default as Wikidata_icon } from "../../assets/svg/Wikidata.svelte"
|
|
||||||
import Gender_male from "../../assets/svg/Gender_male.svelte"
|
import Gender_male from "../../assets/svg/Gender_male.svelte"
|
||||||
import Gender_female from "../../assets/svg/Gender_female.svelte"
|
import Gender_female from "../../assets/svg/Gender_female.svelte"
|
||||||
import Gender_inter from "../../assets/svg/Gender_inter.svelte"
|
import Gender_inter from "../../assets/svg/Gender_inter.svelte"
|
||||||
import Gender_trans from "../../assets/svg/Gender_trans.svelte"
|
import Gender_trans from "../../assets/svg/Gender_trans.svelte"
|
||||||
import Gender_queer from "../../assets/svg/Gender_queer.svelte"
|
import Gender_queer from "../../assets/svg/Gender_queer.svelte"
|
||||||
|
|
||||||
export default class WikidataPreviewBox extends VariableUiElement {
|
|
||||||
|
export default class WikidataPreviewBox {
|
||||||
private static isHuman = [{ p: 31 /*is a*/, q: 5 /* human */ }]
|
private static isHuman = [{ p: 31 /*is a*/, q: 5 /* human */ }]
|
||||||
// @ts-ignore
|
public static extraProperties: {
|
||||||
private static extraProperties: {
|
|
||||||
requires?: { p: number; q?: number }[]
|
requires?: { p: number; q?: number }[]
|
||||||
property: string
|
property: string
|
||||||
|
textMode?: Map<string, string>
|
||||||
display:
|
display:
|
||||||
| TypedTranslation<{ value }>
|
| TypedTranslation<{ value }>
|
||||||
| Map<string, string | (() => BaseUIElement) /*If translation: Subs({value: * }) */>
|
| Map<string, any>,
|
||||||
textMode?: Map<string, string>
|
|
||||||
}[] = [
|
}[] = [
|
||||||
{
|
{
|
||||||
requires: WikidataPreviewBox.isHuman,
|
requires: WikidataPreviewBox.isHuman,
|
||||||
|
@ -36,34 +23,28 @@ export default class WikidataPreviewBox extends VariableUiElement {
|
||||||
display: new Map([
|
display: new Map([
|
||||||
[
|
[
|
||||||
"Q6581097",
|
"Q6581097",
|
||||||
() => new SvelteUIElement(Gender_male).SetStyle("width: 1rem; height: auto"),
|
Gender_male
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"Q6581072",
|
"Q6581072",
|
||||||
() => new SvelteUIElement(Gender_female).SetStyle("width: 1rem; height: auto"),
|
Gender_female
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"Q1097630",
|
"Q1097630",
|
||||||
() => new SvelteUIElement(Gender_inter).SetStyle("width: 1rem; height: auto"),
|
Gender_inter
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"Q1052281",
|
"Q1052281",
|
||||||
() =>
|
Gender_trans /*'transwomen'*/
|
||||||
new SvelteUIElement(Gender_trans).SetStyle(
|
|
||||||
"width: 1rem; height: auto"
|
|
||||||
) /*'transwomen'*/,
|
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"Q2449503",
|
"Q2449503",
|
||||||
() =>
|
Gender_trans /*'transmen'*/
|
||||||
new SvelteUIElement(Gender_trans).SetStyle(
|
|
||||||
"width: 1rem; height: auto"
|
|
||||||
) /*'transmen'*/,
|
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"Q48270",
|
"Q48270",
|
||||||
() => new SvelteUIElement(Gender_queer).SetStyle("width: 1rem; height: auto"),
|
Gender_queer
|
||||||
],
|
]
|
||||||
]),
|
]),
|
||||||
textMode: new Map([
|
textMode: new Map([
|
||||||
["Q6581097", "♂️"],
|
["Q6581097", "♂️"],
|
||||||
|
@ -71,158 +52,19 @@ export default class WikidataPreviewBox extends VariableUiElement {
|
||||||
["Q1097630", "⚥️"],
|
["Q1097630", "⚥️"],
|
||||||
["Q1052281", "🏳️⚧️" /*'transwomen'*/],
|
["Q1052281", "🏳️⚧️" /*'transwomen'*/],
|
||||||
["Q2449503", "🏳️⚧️" /*'transmen'*/],
|
["Q2449503", "🏳️⚧️" /*'transmen'*/],
|
||||||
["Q48270", "🏳️🌈 ⚧"],
|
["Q48270", "🏳️🌈 ⚧"]
|
||||||
]),
|
])
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
property: "P569",
|
property: "P569",
|
||||||
requires: WikidataPreviewBox.isHuman,
|
requires: WikidataPreviewBox.isHuman,
|
||||||
display: Translations.t.general.wikipedia.previewbox.born,
|
display: Translations.t.general.wikipedia.previewbox.born
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
property: "P570",
|
property: "P570",
|
||||||
requires: WikidataPreviewBox.isHuman,
|
requires: WikidataPreviewBox.isHuman,
|
||||||
display: Translations.t.general.wikipedia.previewbox.died,
|
display: Translations.t.general.wikipedia.previewbox.died
|
||||||
},
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
constructor(
|
|
||||||
wikidataId: Store<string>,
|
|
||||||
options?: {
|
|
||||||
noImages?: boolean
|
|
||||||
imageStyle?: string
|
|
||||||
whileLoading?: BaseUIElement | string
|
|
||||||
extraItems?: (BaseUIElement | string)[]
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
let inited = false
|
|
||||||
const wikidata = wikidataId.stabilized(250).bind((id) => {
|
|
||||||
if (id === undefined || id === "" || id === "Q") {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
inited = true
|
|
||||||
return Wikidata.LoadWikidataEntry(id)
|
|
||||||
})
|
|
||||||
|
|
||||||
super(
|
|
||||||
wikidata.map((maybeWikidata) => {
|
|
||||||
if (maybeWikidata === null || !inited) {
|
|
||||||
return options?.whileLoading
|
|
||||||
}
|
|
||||||
|
|
||||||
if (maybeWikidata === undefined) {
|
|
||||||
return new Loading(Translations.t.general.loading)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (maybeWikidata["error"] !== undefined) {
|
|
||||||
return new FixedUiElement(maybeWikidata["error"]).SetClass("alert")
|
|
||||||
}
|
|
||||||
const wikidata = <WikidataResponse>maybeWikidata["success"]
|
|
||||||
console.log(">>>> got wikidata", wikidata)
|
|
||||||
return WikidataPreviewBox.WikidataResponsePreview(wikidata, options)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
public static WikidataResponsePreview(
|
|
||||||
wikidata: WikidataResponse,
|
|
||||||
options?: {
|
|
||||||
noImages?: boolean
|
|
||||||
imageStyle?: string
|
|
||||||
extraItems?: (BaseUIElement | string)[]
|
|
||||||
}
|
|
||||||
): BaseUIElement {
|
|
||||||
console.log(">>> constructing wikidata preview box", wikidata.labels)
|
|
||||||
|
|
||||||
const link = new Link(
|
|
||||||
new Combine([
|
|
||||||
wikidata.id,
|
|
||||||
options?.noImages
|
|
||||||
? wikidata.id
|
|
||||||
: new SvelteUIElement(Wikidata_icon)
|
|
||||||
.SetStyle("width: 2.5rem")
|
|
||||||
.SetClass("block"),
|
|
||||||
]).SetClass("flex"),
|
|
||||||
Wikidata.IdToArticle(wikidata.id),
|
|
||||||
true
|
|
||||||
)?.SetClass("must-link")
|
|
||||||
let info = new Combine([
|
|
||||||
new Combine([
|
|
||||||
Translation.fromMap(wikidata.labels)?.SetClass("font-bold"),
|
|
||||||
link,
|
|
||||||
]).SetClass("flex justify-between flex-wrap-reverse"),
|
|
||||||
Translation.fromMap(wikidata.descriptions, true),
|
|
||||||
WikidataPreviewBox.QuickFacts(wikidata, options),
|
|
||||||
...(options?.extraItems ?? []),
|
|
||||||
]).SetClass("flex flex-col link-underline")
|
|
||||||
|
|
||||||
let imageUrl = undefined
|
|
||||||
if (wikidata.claims.get("P18")?.size > 0) {
|
|
||||||
imageUrl = Array.from(wikidata.claims.get("P18"))[0]
|
|
||||||
}
|
|
||||||
if (imageUrl && !options?.noImages) {
|
|
||||||
imageUrl = WikimediaImageProvider.singleton.PrepUrl(imageUrl).url
|
|
||||||
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("p-2 w-full")
|
|
||||||
|
|
||||||
return info
|
|
||||||
}
|
|
||||||
|
|
||||||
public static QuickFacts(
|
|
||||||
wikidata: WikidataResponse,
|
|
||||||
options?: { noImages?: boolean }
|
|
||||||
): BaseUIElement {
|
|
||||||
const els: BaseUIElement[] = []
|
|
||||||
for (const extraProperty of WikidataPreviewBox.extraProperties) {
|
|
||||||
let hasAllRequirements = true
|
|
||||||
for (const requirement of extraProperty.requires) {
|
|
||||||
if (!wikidata.claims?.has("P" + requirement.p)) {
|
|
||||||
hasAllRequirements = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if (!wikidata.claims?.get("P" + requirement.p).has("Q" + requirement.q)) {
|
|
||||||
hasAllRequirements = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!hasAllRequirements) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
const key = extraProperty.property
|
|
||||||
const display =
|
|
||||||
(options?.noImages ? extraProperty.textMode : extraProperty.display) ??
|
|
||||||
extraProperty.display
|
|
||||||
if (wikidata.claims?.get(key) === undefined) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
const value: string[] = Array.from(wikidata.claims.get(key))
|
|
||||||
|
|
||||||
if (display instanceof Translation) {
|
|
||||||
els.push(display.Subs({ value: value.join(", ") }).SetClass("m-2"))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
const constructors = Utils.NoNull(value.map((property) => display.get(property)))
|
|
||||||
const elems = constructors.map((v) => {
|
|
||||||
if (typeof v === "string") {
|
|
||||||
return new FixedUiElement(v)
|
|
||||||
} else {
|
|
||||||
return v()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
els.push(new Combine(elems).SetClass("flex m-2"))
|
|
||||||
}
|
|
||||||
if (els.length === 0) {
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Combine(els).SetClass("flex")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
49
src/UI/Wikipedia/WikidataQuickfacts.svelte
Normal file
49
src/UI/Wikipedia/WikidataQuickfacts.svelte
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
<script lang="ts">
|
||||||
|
|
||||||
|
import { Translation } from "../i18n/Translation"
|
||||||
|
import WikidataPreviewBox from "./WikidataPreviewBox"
|
||||||
|
import { WikidataResponse } from "../../Logic/Web/Wikidata"
|
||||||
|
import Tr from "../Base/Tr.svelte"
|
||||||
|
|
||||||
|
export let wikidata: WikidataResponse
|
||||||
|
|
||||||
|
let propertiesToRender = WikidataPreviewBox.extraProperties.filter(property => {
|
||||||
|
for (const requirement of property.requires) {
|
||||||
|
if (!wikidata.claims?.has("P" + requirement.p)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (!wikidata.claims?.get("P" + requirement.p).has("Q" + requirement.q)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = property.property
|
||||||
|
if (wikidata.claims?.get(key) === undefined) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function getProperty(property: {property: string}){
|
||||||
|
const key = property.property
|
||||||
|
const value = Array.from(wikidata.claims?.get(key)).join(", ")
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if propertiesToRender.length > 0}
|
||||||
|
<div class="flex justify-start items-center">
|
||||||
|
{#each propertiesToRender as property}
|
||||||
|
{#if typeof property.display === "string" }
|
||||||
|
{property.display}
|
||||||
|
{:else if property.display instanceof Translation}
|
||||||
|
<Tr cls="m-2 shrink-0"
|
||||||
|
t={property.display.Subs({value: getProperty(property)}) } />
|
||||||
|
{:else}
|
||||||
|
<svelte:component this={property.display.get(getProperty(property))} class="h-6 w-fit m-1"/>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
|
@ -1,223 +0,0 @@
|
||||||
import Combine from "../Base/Combine"
|
|
||||||
import { InputElement } from "../Input/InputElement"
|
|
||||||
import { TextField } from "../Input/TextField"
|
|
||||||
import Translations from "../i18n/Translations"
|
|
||||||
import { ImmutableStore, Store, Stores, UIEventSource } from "../../Logic/UIEventSource"
|
|
||||||
import Wikidata, { WikidataResponse } from "../../Logic/Web/Wikidata"
|
|
||||||
import Locale from "../i18n/Locale"
|
|
||||||
import { VariableUiElement } from "../Base/VariableUIElement"
|
|
||||||
import WikidataPreviewBox from "./WikidataPreviewBox"
|
|
||||||
import Title from "../Base/Title"
|
|
||||||
import Svg from "../../Svg"
|
|
||||||
import Loading from "../Base/Loading"
|
|
||||||
import Table from "../Base/Table"
|
|
||||||
import SvelteUIElement from "../Base/SvelteUIElement"
|
|
||||||
import Search from "../../assets/svg/Search.svelte"
|
|
||||||
|
|
||||||
export default class WikidataSearchBox extends InputElement<string> {
|
|
||||||
public static docs = new Combine([
|
|
||||||
new Title("Helper arguments"),
|
|
||||||
new Table(
|
|
||||||
["name", "doc"],
|
|
||||||
[
|
|
||||||
[
|
|
||||||
"key",
|
|
||||||
"the value of this tag will initialize search (default: name). This can be a ';'-separated list in which case every key will be inspected. The non-null value will be used as search",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"options",
|
|
||||||
new Combine([
|
|
||||||
"A JSON-object of type `{ removePrefixes: string[], removePostfixes: string[] }`.",
|
|
||||||
new Table(
|
|
||||||
["subarg", "doc"],
|
|
||||||
[
|
|
||||||
[
|
|
||||||
"removePrefixes",
|
|
||||||
"remove these snippets of text from the start of the passed string to search. This is either a list OR a hash of languages to a list. The individual strings are interpreted as case ignoring regexes",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"removePostfixes",
|
|
||||||
"remove these snippets of text from the end of the passed string to search. This is either a list OR a hash of languages to a list. The individual strings are interpreted as case ignoring regexes.",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"instanceOf",
|
|
||||||
"A list of Q-identifier which indicates that the search results _must_ be an entity of this type, e.g. [`Q5`](https://www.wikidata.org/wiki/Q5) for humans",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"notInstanceof",
|
|
||||||
"A list of Q-identifiers which indicates that the search results _must not_ be an entity of this type, e.g. [`Q79007`](https://www.wikidata.org/wiki/Q79007) to filter away all streets from the search results",
|
|
||||||
],
|
|
||||||
]
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
],
|
|
||||||
]
|
|
||||||
),
|
|
||||||
new Title("Example usage"),
|
|
||||||
`The following is the 'freeform'-part of a layer config which will trigger a search for the wikidata item corresponding with the name of the selected feature. It will also remove '-street', '-square', ... if found at the end of the name
|
|
||||||
|
|
||||||
\`\`\`json
|
|
||||||
"freeform": {
|
|
||||||
"key": "name:etymology:wikidata",
|
|
||||||
"type": "wikidata",
|
|
||||||
"helperArgs": [
|
|
||||||
"name",
|
|
||||||
{
|
|
||||||
"removePostfixes": {"en": [
|
|
||||||
"street",
|
|
||||||
"boulevard",
|
|
||||||
"path",
|
|
||||||
"square",
|
|
||||||
"plaza",
|
|
||||||
],
|
|
||||||
"nl": ["straat","plein","pad","weg",laan"],
|
|
||||||
"fr":["route (de|de la|de l'| de le)"]
|
|
||||||
},
|
|
||||||
|
|
||||||
"#": "Remove streets and parks from the search results:"
|
|
||||||
"notInstanceOf": ["Q79007","Q22698"]
|
|
||||||
}
|
|
||||||
|
|
||||||
]
|
|
||||||
}
|
|
||||||
\`\`\`
|
|
||||||
|
|
||||||
Another example is to search for species and trees:
|
|
||||||
|
|
||||||
\`\`\`json
|
|
||||||
"freeform": {
|
|
||||||
"key": "species:wikidata",
|
|
||||||
"type": "wikidata",
|
|
||||||
"helperArgs": [
|
|
||||||
"species",
|
|
||||||
{
|
|
||||||
"instanceOf": [10884, 16521]
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
\`\`\`
|
|
||||||
`,
|
|
||||||
])
|
|
||||||
private static readonly _searchCache = new Map<string, Promise<WikidataResponse[]>>()
|
|
||||||
private readonly wikidataId: UIEventSource<string>
|
|
||||||
private readonly searchText: UIEventSource<string>
|
|
||||||
private readonly instanceOf?: number[]
|
|
||||||
private readonly notInstanceOf?: number[]
|
|
||||||
|
|
||||||
constructor(options?: {
|
|
||||||
searchText?: UIEventSource<string>
|
|
||||||
value?: UIEventSource<string>
|
|
||||||
notInstanceOf?: number[]
|
|
||||||
instanceOf?: number[]
|
|
||||||
}) {
|
|
||||||
super()
|
|
||||||
this.searchText = options?.searchText
|
|
||||||
this.wikidataId = options?.value ?? new UIEventSource<string>(undefined)
|
|
||||||
this.instanceOf = options?.instanceOf
|
|
||||||
this.notInstanceOf = options?.notInstanceOf
|
|
||||||
}
|
|
||||||
|
|
||||||
GetValue(): UIEventSource<string> {
|
|
||||||
return this.wikidataId
|
|
||||||
}
|
|
||||||
|
|
||||||
IsValid(t: string): boolean {
|
|
||||||
return t.startsWith("Q") && !isNaN(Number(t.substring(1)))
|
|
||||||
}
|
|
||||||
|
|
||||||
protected InnerConstructElement(): HTMLElement {
|
|
||||||
const searchField = new TextField({
|
|
||||||
placeholder: Translations.t.general.wikipedia.searchWikidata,
|
|
||||||
value: this.searchText,
|
|
||||||
inputStyle: "width: calc(100% - 0.5rem); border: 1px solid black",
|
|
||||||
})
|
|
||||||
const selectedWikidataId = this.wikidataId
|
|
||||||
|
|
||||||
const tooShort = new ImmutableStore<{ success: WikidataResponse[] }>({ success: undefined })
|
|
||||||
const searchResult: Store<{ success?: WikidataResponse[]; error?: any }> = searchField
|
|
||||||
.GetValue()
|
|
||||||
.bind((searchText) => {
|
|
||||||
if (searchText.length < 3 && !searchText.match(/[qQ][0-9]+/)) {
|
|
||||||
return tooShort
|
|
||||||
}
|
|
||||||
const lang = Locale.language.data
|
|
||||||
const key = lang + ":" + searchText
|
|
||||||
let promise = WikidataSearchBox._searchCache.get(key)
|
|
||||||
if (promise === undefined) {
|
|
||||||
promise = Wikidata.searchAndFetch(searchText, {
|
|
||||||
lang,
|
|
||||||
maxCount: 5,
|
|
||||||
notInstanceOf: this.notInstanceOf,
|
|
||||||
instanceOf: this.instanceOf,
|
|
||||||
})
|
|
||||||
WikidataSearchBox._searchCache.set(key, promise)
|
|
||||||
}
|
|
||||||
return Stores.FromPromiseWithErr(promise)
|
|
||||||
})
|
|
||||||
|
|
||||||
const previews = new VariableUiElement(
|
|
||||||
searchResult.map(
|
|
||||||
(searchResultsOrFail) => {
|
|
||||||
if (searchField.GetValue().data.length === 0) {
|
|
||||||
return Translations.t.general.wikipedia.doSearch
|
|
||||||
}
|
|
||||||
|
|
||||||
if (searchField.GetValue().data.length < 3) {
|
|
||||||
return Translations.t.general.wikipedia.searchToShort
|
|
||||||
}
|
|
||||||
|
|
||||||
if (searchResultsOrFail === undefined) {
|
|
||||||
return new Loading(Translations.t.general.loading)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (searchResultsOrFail.error !== undefined) {
|
|
||||||
return new Combine([
|
|
||||||
Translations.t.general.wikipedia.failed.Clone().SetClass("alert"),
|
|
||||||
searchResultsOrFail.error,
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
const searchResults = searchResultsOrFail.success
|
|
||||||
if (searchResults.length === 0) {
|
|
||||||
return Translations.t.general.wikipedia.noResults.Subs({
|
|
||||||
search: searchField.GetValue().data ?? "",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Combine(
|
|
||||||
searchResults.map((wikidataresponse) => {
|
|
||||||
const el = WikidataPreviewBox.WikidataResponsePreview(
|
|
||||||
wikidataresponse
|
|
||||||
).SetClass(
|
|
||||||
"rounded-xl p-1 sm:p-2 md:p-3 m-px border-2 sm:border-4 transition-colors"
|
|
||||||
)
|
|
||||||
el.onClick(() => {
|
|
||||||
selectedWikidataId.setData(wikidataresponse.id)
|
|
||||||
})
|
|
||||||
selectedWikidataId.addCallbackAndRunD((selected) => {
|
|
||||||
if (selected === wikidataresponse.id) {
|
|
||||||
el.SetClass("subtle-background border-attention")
|
|
||||||
} else {
|
|
||||||
el.RemoveClass("subtle-background")
|
|
||||||
el.RemoveClass("border-attention")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return el
|
|
||||||
})
|
|
||||||
).SetClass("flex flex-col")
|
|
||||||
},
|
|
||||||
[searchField.GetValue()]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return new Combine([
|
|
||||||
new Title(Translations.t.general.wikipedia.searchWikidata, 3).SetClass("m-2"),
|
|
||||||
new Combine([
|
|
||||||
new SvelteUIElement(Search).SetClass("w-6"),
|
|
||||||
searchField.SetClass("m-2 w-full"),
|
|
||||||
]).SetClass("flex"),
|
|
||||||
previews,
|
|
||||||
])
|
|
||||||
.SetClass("flex flex-col border-2 border-black rounded-xl m-2 p-2")
|
|
||||||
.ConstructElement()
|
|
||||||
}
|
|
||||||
}
|
|
42
src/UI/Wikipedia/Wikidatapreview.svelte
Normal file
42
src/UI/Wikipedia/Wikidatapreview.svelte
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
<script lang="ts">
|
||||||
|
|
||||||
|
import Wikidata, { WikidataResponse } from "../../Logic/Web/Wikidata"
|
||||||
|
import { Translation } from "../i18n/Translation"
|
||||||
|
import { WikimediaImageProvider } from "../../Logic/ImageProviders/WikimediaImageProvider"
|
||||||
|
import { default as Wikidata_icon } from "../../assets/svg/Wikidata.svelte"
|
||||||
|
import WikidataQuickfacts from "./WikidataQuickfacts.svelte"
|
||||||
|
import Tr from "../Base/Tr.svelte"
|
||||||
|
|
||||||
|
export let wikidata: WikidataResponse
|
||||||
|
export let imageStyle: string = "max-width: 5rem; width: unset; height: 4rem"
|
||||||
|
|
||||||
|
let imageProperty: string | undefined = Array.from(wikidata?.claims?.get("P18") ?? [])[0]
|
||||||
|
let imageUrl = WikimediaImageProvider.singleton.PrepUrl(imageProperty)?.url
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex w-full p-2">
|
||||||
|
|
||||||
|
{#if imageUrl}
|
||||||
|
<img src={imageUrl} style={imageStyle} class="mr-2" />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class="flex flex-col w-full">
|
||||||
|
|
||||||
|
<div class="flex w-full justify-between">
|
||||||
|
<Tr cls="font-bold" t={ Translation.fromMap(wikidata.labels) } />
|
||||||
|
<a href={Wikidata.IdToArticle(wikidata.id)} target="_blank" class="flex must-link items-center">
|
||||||
|
<Wikidata_icon class="w-10" /> {wikidata.id}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<Tr t={ Translation.fromMap(wikidata.descriptions, true)} />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="flex">
|
||||||
|
<WikidataQuickfacts {wikidata} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<slot name="extra"></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
35
src/UI/Wikipedia/WikidatapreviewWithLoading.svelte
Normal file
35
src/UI/Wikipedia/WikidatapreviewWithLoading.svelte
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import Wikidata, { WikidataResponse } from "../../Logic/Web/Wikidata"
|
||||||
|
import Translations from "../i18n/Translations"
|
||||||
|
import { FixedUiElement } from "../Base/FixedUiElement"
|
||||||
|
import { Store } from "../../Logic/UIEventSource"
|
||||||
|
import Wikidatapreview from "./Wikidatapreview.svelte"
|
||||||
|
import Tr from "../Base/Tr.svelte"
|
||||||
|
import Loading from "../Base/Loading.svelte"
|
||||||
|
|
||||||
|
export let wikidataId: Store<string>
|
||||||
|
export let imageStyle: string = undefined
|
||||||
|
|
||||||
|
let wikidata: Store<{ success: WikidataResponse } | { error: any }> = wikidataId.stabilized(100).bind((id) => {
|
||||||
|
if (id === undefined || id === "" || id === "Q") {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return Wikidata.LoadWikidataEntry(id)
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if $wikidata === undefined}
|
||||||
|
<Loading>
|
||||||
|
<Tr t={Translations.t.general.loading} />
|
||||||
|
</Loading>
|
||||||
|
{:else if $wikidata["error"]}
|
||||||
|
<div class="alert">
|
||||||
|
{$wikidata["error"]}
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
|
||||||
|
<Wikidatapreview {imageStyle} wikidata={$wikidata["success"]}>
|
||||||
|
<slot name="extra" slot="extra" />
|
||||||
|
</Wikidatapreview>
|
||||||
|
{/if}
|
|
@ -10,6 +10,7 @@
|
||||||
import Tr from "../Base/Tr.svelte"
|
import Tr from "../Base/Tr.svelte"
|
||||||
import Translations from "../i18n/Translations"
|
import Translations from "../i18n/Translations"
|
||||||
import Wikipedia from "../../assets/svg/Wikipedia.svelte"
|
import Wikipedia from "../../assets/svg/Wikipedia.svelte"
|
||||||
|
import Wikidatapreview from "./Wikidatapreview.svelte"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows a wikipedia-article + wikidata preview for the given item
|
* Shows a wikipedia-article + wikidata preview for the given item
|
||||||
|
@ -31,9 +32,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if $wikipediaDetails.wikidata}
|
{#if $wikipediaDetails.wikidata}
|
||||||
<ToSvelte
|
<Wikidatapreview wikidata={$wikipediaDetails.wikidata} />
|
||||||
construct={() => WikidataPreviewBox.WikidataResponsePreview($wikipediaDetails.wikidata)}
|
|
||||||
/>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if $wikipediaDetails.articleUrl}
|
{#if $wikipediaDetails.articleUrl}
|
||||||
|
|
|
@ -26,9 +26,7 @@ export class Translation extends BaseUIElement {
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
this._strictLanguages = strictLanguages
|
this._strictLanguages = strictLanguages
|
||||||
if (strictLanguages) {
|
|
||||||
console.log(">>> strict:", translations)
|
|
||||||
}
|
|
||||||
if (translations === undefined) {
|
if (translations === undefined) {
|
||||||
console.error("Translation without content at " + context)
|
console.error("Translation without content at " + context)
|
||||||
throw `Translation without content (${context})`
|
throw `Translation without content (${context})`
|
||||||
|
@ -138,7 +136,6 @@ export class Translation extends BaseUIElement {
|
||||||
|
|
||||||
static fromMap(transl: Map<string, string>, strictLanguages: boolean = false) {
|
static fromMap(transl: Map<string, string>, strictLanguages: boolean = false) {
|
||||||
const translations = {}
|
const translations = {}
|
||||||
console.log("Strict:", strictLanguages)
|
|
||||||
let hasTranslation = false
|
let hasTranslation = false
|
||||||
transl?.forEach((value, key) => {
|
transl?.forEach((value, key) => {
|
||||||
translations[key] = value
|
translations[key] = value
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue