2021-10-08 04:33:39 +02:00
import Combine from "../Base/Combine"
import { InputElement } from "../Input/InputElement"
import { TextField } from "../Input/TextField"
import Translations from "../i18n/Translations"
2022-06-05 02:24:14 +02:00
import { ImmutableStore , Store , Stores , UIEventSource } from "../../Logic/UIEventSource"
2021-10-08 04:33:39 +02:00
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"
2022-06-05 02:24:14 +02:00
import Loading from "../Base/Loading"
2023-04-16 03:42:26 +02:00
import Table from "../Base/Table"
2021-10-08 04:33:39 +02:00
export default class WikidataSearchBox extends InputElement < string > {
2023-04-16 03:42:26 +02:00
public static docs = new Combine ( [
new Title ( "Helper arguments" ) ,
new Table (
[ "name" , "doc" ] ,
[
[ "key" , "the value of this tag will initialize search (default: name)" ] ,
[
"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 ]
} ]
}
\ ` \` \`
` ,
] )
2023-04-21 16:02:36 +02:00
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 [ ]
2023-04-16 03:42:26 +02:00
2021-10-08 04:33:39 +02:00
constructor ( options ? : {
searchText? : UIEventSource < string >
2022-04-22 01:45:54 +02:00
value? : UIEventSource < string >
notInstanceOf? : number [ ]
instanceOf? : number [ ]
2021-10-08 04:33:39 +02:00
} ) {
super ( )
this . searchText = options ? . searchText
this . wikidataId = options ? . value ? ? new UIEventSource < string > ( undefined )
2022-04-22 01:45:54 +02:00
this . instanceOf = options ? . instanceOf
this . notInstanceOf = options ? . notInstanceOf
2021-10-08 04:33:39 +02:00
}
GetValue ( ) : UIEventSource < string > {
return this . wikidataId
}
2021-11-07 02:23:28 +01:00
IsValid ( t : string ) : boolean {
return t . startsWith ( "Q" ) && ! isNaN ( Number ( t . substring ( 1 ) ) )
}
2021-10-08 04:33:39 +02:00
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
2022-06-05 02:24:14 +02:00
const tooShort = new ImmutableStore < { success : WikidataResponse [ ] } > ( { success : undefined } )
const searchResult : Store < { success? : WikidataResponse [ ] ; error? : any } > = searchField
. GetValue ( )
. bind ( ( searchText ) = > {
if ( searchText . length < 3 ) {
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 )
} )
2021-11-07 02:23:28 +01:00
2022-06-05 02:24:14 +02:00
const previews = new VariableUiElement (
searchResult . map (
( searchResultsOrFail ) = > {
if ( searchField . GetValue ( ) . data . length === 0 ) {
return Translations . t . general . wikipedia . doSearch
2022-09-08 21:40:48 +02:00
}
2022-06-05 02:24:14 +02:00
2022-04-22 01:45:54 +02:00
if ( searchField . GetValue ( ) . data . length < 3 ) {
return Translations . t . general . wikipedia . searchToShort
2021-10-08 04:33:39 +02:00
}
2022-06-05 02:24:14 +02:00
if ( searchResultsOrFail === undefined ) {
return new Loading ( Translations . t . general . loading )
2022-09-08 21:40:48 +02:00
}
2022-04-22 01:45:54 +02:00
2021-10-08 04:33:39 +02:00
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 ? ? "" ,
} )
2022-09-08 21:40:48 +02:00
}
2021-10-08 04:33:39 +02:00
2022-06-05 02:24:14 +02:00
return new Combine (
searchResults . map ( ( wikidataresponse ) = > {
2021-10-08 04:33:39 +02:00
const el = WikidataPreviewBox . WikidataResponsePreview (
wikidataresponse
2022-06-05 02:24:14 +02:00
) . SetClass (
2021-10-08 04:33:39 +02:00
"rounded-xl p-1 sm:p-2 md:p-3 m-px border-2 sm:border-4 transition-colors"
2022-09-08 21:40:48 +02:00
)
2021-10-08 04:33:39 +02:00
el . onClick ( ( ) = > {
selectedWikidataId . setData ( wikidataresponse . id )
2022-09-08 21:40:48 +02:00
} )
2021-10-08 04:33:39 +02:00
selectedWikidataId . addCallbackAndRunD ( ( selected ) = > {
if ( selected === wikidataresponse . id ) {
el . SetClass ( "subtle-background border-attention" )
2022-09-08 21:40:48 +02:00
} else {
2021-10-08 04:33:39 +02:00
el . RemoveClass ( "subtle-background" )
el . RemoveClass ( "border-attention" )
2022-09-08 21:40:48 +02:00
}
} )
return el
} )
2021-10-08 04:33:39 +02:00
) . SetClass ( "flex flex-col" )
2022-06-05 02:24:14 +02:00
} ,
[ searchField . GetValue ( ) ]
2022-09-08 21:40:48 +02:00
)
)
2021-10-08 04:33:39 +02:00
2023-04-21 16:02:36 +02:00
return new Combine ( [
2021-10-08 04:33:39 +02:00
new Title ( Translations . t . general . wikipedia . searchWikidata , 3 ) . SetClass ( "m-2" ) ,
new Combine ( [
2023-05-08 01:55:21 +02:00
Svg . search_svg ( ) . SetStyle ( "width: 1.5rem" ) ,
2021-10-08 04:33:39 +02:00
searchField . SetClass ( "m-2 w-full" ) ,
] ) . SetClass ( "flex" ) ,
previews ,
2023-04-21 16:02:36 +02:00
] )
. SetClass ( "flex flex-col border-2 border-black rounded-xl m-2 p-2" )
. ConstructElement ( )
2021-10-08 04:33:39 +02:00
}
}