forked from MapComplete/MapComplete
		
	Add a wikidata search box
This commit is contained in:
		
							parent
							
								
									54bc4f24da
								
							
						
					
					
						commit
						b5a2ee1757
					
				
					 21 changed files with 4141 additions and 3590 deletions
				
			
		|  | @ -26,19 +26,19 @@ export default class AllImageProviders { | |||
|     private static _cache: Map<string, UIEventSource<ProvidedImage[]>> = new Map<string, UIEventSource<ProvidedImage[]>>() | ||||
| 
 | ||||
|     public static LoadImagesFor(tags: UIEventSource<any>, tagKey?: string): UIEventSource<ProvidedImage[]> { | ||||
|         const id = tags.data.id | ||||
|         if (id === undefined) { | ||||
|         if (tags.data.id === undefined) { | ||||
|             return undefined; | ||||
|         } | ||||
| 
 | ||||
|         const cached = this._cache.get(tags.data.id) | ||||
|         const cacheKey = tags.data.id+tagKey | ||||
|         const cached = this._cache.get(cacheKey) | ||||
|         if (cached !== undefined) { | ||||
|             return cached | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         const source = new UIEventSource([]) | ||||
|         this._cache.set(id, source) | ||||
|         this._cache.set(cacheKey, source) | ||||
|         const allSources = [] | ||||
|         for (const imageProvider of AllImageProviders.ImageAttributionSource) { | ||||
|              | ||||
|  |  | |||
|  | @ -44,7 +44,7 @@ export class WikimediaImageProvider extends ImageProvider { | |||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     private PrepareUrl(value: string): string { | ||||
|     private static PrepareUrl(value: string): string { | ||||
| 
 | ||||
|         if (value.toLowerCase().startsWith("https://commons.wikimedia.org/wiki/")) { | ||||
|             return value; | ||||
|  | @ -97,18 +97,18 @@ export class WikimediaImageProvider extends ImageProvider { | |||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     private async UrlForImage(image: string): Promise<ProvidedImage> { | ||||
|     private UrlForImage(image: string): ProvidedImage { | ||||
|         if (!image.startsWith("File:")) { | ||||
|             image = "File:" + image | ||||
|         } | ||||
|         return {url: this.PrepareUrl(image), key: undefined, provider: this} | ||||
|         return {url: WikimediaImageProvider.PrepareUrl(image), key: undefined, provider: this} | ||||
|     } | ||||
|      | ||||
|     private startsWithCommonsPrefix(value: string){ | ||||
|     private static startsWithCommonsPrefix(value: string): boolean{ | ||||
|         return  WikimediaImageProvider.commonsPrefixes.some(prefix => value.startsWith(prefix)) | ||||
|     } | ||||
|      | ||||
|     private removeCommonsPrefix(value: string){ | ||||
|     private static removeCommonsPrefix(value: string): string{ | ||||
|         if(value.startsWith("https://upload.wikimedia.org/")){ | ||||
|             value = value.substring(value.lastIndexOf("/") + 1) | ||||
|             value = decodeURIComponent(value) | ||||
|  | @ -130,26 +130,38 @@ export class WikimediaImageProvider extends ImageProvider { | |||
|         return value; | ||||
|     } | ||||
| 
 | ||||
|     public PrepUrl(value: string): ProvidedImage { | ||||
|         const hasCommonsPrefix = WikimediaImageProvider.startsWithCommonsPrefix(value) | ||||
|         value = WikimediaImageProvider.removeCommonsPrefix(value) | ||||
| 
 | ||||
|         if (value.startsWith("File:")) { | ||||
|             return this.UrlForImage(value) | ||||
|         } | ||||
| 
 | ||||
|         // We do a last effort and assume this is a file
 | ||||
|         return this.UrlForImage("File:" + value) | ||||
|     } | ||||
| 
 | ||||
|     public async ExtractUrls(key: string, value: string): Promise<Promise<ProvidedImage>[]> { | ||||
|         const hasCommonsPrefix = this.startsWithCommonsPrefix(value) | ||||
|         const hasCommonsPrefix = WikimediaImageProvider.startsWithCommonsPrefix(value) | ||||
|         if(key !== undefined && key !== this.commons_key && !hasCommonsPrefix){ | ||||
|             return [] | ||||
|         } | ||||
|          | ||||
|         value = this.removeCommonsPrefix(value) | ||||
|         value = WikimediaImageProvider.removeCommonsPrefix(value) | ||||
|         if (value.startsWith("Category:")) { | ||||
|             const urls = await Wikimedia.GetCategoryContents(value) | ||||
|             return urls.filter(url => url.startsWith("File:")).map(image => this.UrlForImage(image)) | ||||
|             return urls.filter(url => url.startsWith("File:")).map(image => Promise.resolve(this.UrlForImage(image))) | ||||
|         } | ||||
|         if (value.startsWith("File:")) { | ||||
|             return [this.UrlForImage(value)] | ||||
|             return [Promise.resolve(this.UrlForImage(value))] | ||||
|         } | ||||
|         if (value.startsWith("http")) { | ||||
|             // PRobably an error
 | ||||
|             return [] | ||||
|         } | ||||
|         // We do a last effort and assume this is a file
 | ||||
|         return [this.UrlForImage("File:" + value)] | ||||
|         return [Promise.resolve(this.UrlForImage("File:" + value))] | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -76,6 +76,27 @@ export class UIEventSource<T> { | |||
|         return src | ||||
|     } | ||||
|      | ||||
|     public AsPromise(): Promise<T>{ | ||||
|         const self = this; | ||||
|         return new Promise((resolve, reject) => { | ||||
|             if(self.data !== undefined){ | ||||
|                 resolve(self.data) | ||||
|             }else{ | ||||
|                 self.addCallbackD(data => { | ||||
|                     resolve(data) | ||||
|                     return true; // return true to unregister as we only need to be called once
 | ||||
|                 }) | ||||
|             } | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     public WaitForPromise(promise: Promise<T>, onFail: ((any) => void)): UIEventSource<T> { | ||||
|         const self = this; | ||||
|         promise?.then(d => self.setData(d)) | ||||
|         promise?.catch(err =>onFail(err)) | ||||
|         return this | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Converts a promise into a UIVentsource, sets the UIEVentSource when the result is calculated. | ||||
|      * If the promise fails, the value will stay undefined | ||||
|  | @ -195,16 +216,20 @@ export class UIEventSource<T> { | |||
|         const sink = new UIEventSource<X>(undefined) | ||||
|         const seenEventSources = new Set<UIEventSource<X>>(); | ||||
|         mapped.addCallbackAndRun(newEventSource => { | ||||
|              | ||||
|             if (newEventSource === undefined) { | ||||
|             if (newEventSource === null) { | ||||
|                 sink.setData(null) | ||||
|             } else if (newEventSource === undefined) { | ||||
|                 sink.setData(undefined) | ||||
|             } else if (!seenEventSources.has(newEventSource)) { | ||||
|             }else if (!seenEventSources.has(newEventSource)) { | ||||
|                 seenEventSources.add(newEventSource) | ||||
|                 newEventSource.addCallbackAndRun(resultData => { | ||||
|                     if (mapped.data === newEventSource) { | ||||
|                         sink.setData(resultData); | ||||
|                     } | ||||
|                 }) | ||||
|             }else{ | ||||
|                 // Already seen, so we don't have to add a callback, just update the value
 | ||||
|                 sink.setData(newEventSource.data) | ||||
|             } | ||||
|         }) | ||||
| 
 | ||||
|  |  | |||
|  | @ -12,6 +12,11 @@ export interface WikidataResponse { | |||
|     commons: string | ||||
| } | ||||
| 
 | ||||
| export interface WikidataSearchoptions { | ||||
|     lang?: "en" | string, | ||||
|     maxCount?: 20 | number | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Utility functions around wikidata | ||||
|  */ | ||||
|  | @ -47,10 +52,14 @@ export default class Wikidata { | |||
|             const claimsList: any[] = entity.claims[claimId] | ||||
|             const values = new Set<string>() | ||||
|             for (const claim of claimsList) { | ||||
|                 const value = claim.mainsnak?.datavalue?.value; | ||||
|                 if(value !== undefined){ | ||||
|                     values.add(value) | ||||
|                 let value = claim.mainsnak?.datavalue?.value; | ||||
|                 if (value === undefined) { | ||||
|                     continue; | ||||
|                 } | ||||
|                 if(value.id !== undefined){ | ||||
|                     value = value.id | ||||
|                 } | ||||
|                 values.add(value) | ||||
|             } | ||||
|             claims.set(claimId, values); | ||||
|         } | ||||
|  | @ -77,6 +86,82 @@ export default class Wikidata { | |||
|         return src; | ||||
|     } | ||||
|      | ||||
|     public static async search( | ||||
|             search: string, | ||||
|             options?:WikidataSearchoptions, | ||||
|             page = 1 | ||||
|         ): Promise<{ | ||||
|         id: string, | ||||
|         label: string, | ||||
|         description: string | ||||
|     }[]> { | ||||
|             const maxCount = options?.maxCount ?? 20 | ||||
|             let pageCount =  Math.min(maxCount,50) | ||||
|             const start = page * pageCount - pageCount; | ||||
|             const lang = (options?.lang ?? "en") | ||||
|             const url = | ||||
|                 "https://www.wikidata.org/w/api.php?action=wbsearchentities&search=" + | ||||
|                 search + | ||||
|                 "&language=" + | ||||
|                 lang + | ||||
|                 "&limit="+pageCount+"&continue=" + | ||||
|                 start + | ||||
|                 "&format=json&uselang=" + | ||||
|                 lang + | ||||
|                 "&type=item&origin=*"+ | ||||
|             "&props=" ;// props= removes some unused values in the result
 | ||||
|             const response = await Utils.downloadJson(url) | ||||
|          | ||||
|             const result : any[] = response.search | ||||
|          | ||||
|             if(result.length < pageCount){ | ||||
|                 // No next page
 | ||||
|                 return result; | ||||
|             } | ||||
|             if(result.length < maxCount){ | ||||
|                 const newOptions = {...options} | ||||
|                 newOptions.maxCount = maxCount - result.length | ||||
|                 result.push(...await Wikidata.search(search,  | ||||
|                     newOptions, | ||||
|                     page + 1 | ||||
|                     )) | ||||
|             } | ||||
|          | ||||
|             return result; | ||||
|     } | ||||
|      | ||||
|     public static async searchAndFetch( | ||||
|     search: string, | ||||
|     options?:WikidataSearchoptions | ||||
| ) : Promise<WikidataResponse[]> | ||||
|     { | ||||
|         const maxCount = options.maxCount | ||||
|         // We provide some padding to filter away invalid values
 | ||||
|         options.maxCount = Math.ceil((options.maxCount ?? 20) * 1.5) | ||||
|         const searchResults = await Wikidata.search(search, options) | ||||
|         const maybeResponses =  await Promise.all(searchResults.map(async r => { | ||||
|            try{ | ||||
|                return await Wikidata.LoadWikidataEntry(r.id).AsPromise() | ||||
|             }catch(e){ | ||||
|                console.error(e) | ||||
|                return undefined; | ||||
|            } | ||||
|         })) | ||||
|         const responses = maybeResponses | ||||
|             .map(r => <WikidataResponse> r["success"]) | ||||
|             .filter(wd => { | ||||
|             if(wd === undefined){ | ||||
|                 return false; | ||||
|             } | ||||
|             if(wd.claims.get("P31" /*Instance of*/)?.has("Q4167410"/* Wikimedia Disambiguation page*/)){ | ||||
|                 return false; | ||||
|             } | ||||
|             return true; | ||||
|         }) | ||||
|         responses.splice(maxCount, responses.length - maxCount) | ||||
|         return responses    | ||||
|     } | ||||
|      | ||||
|     private static ExtractKey(value: string | number) : number{ | ||||
|         if (typeof value === "number") { | ||||
|            return value | ||||
|  | @ -99,6 +184,7 @@ export default class Wikidata { | |||
|         return n; | ||||
|     } | ||||
| 
 | ||||
|      | ||||
|     /** | ||||
|      * Loads a wikidata page | ||||
|      * @returns the entity of the given value | ||||
|  | @ -109,7 +195,7 @@ export default class Wikidata { | |||
|             console.warn("Could not extract a wikidata entry from", value) | ||||
|             return undefined; | ||||
|         } | ||||
|         console.log("Requesting wikidata with id", id) | ||||
|          | ||||
|         const url = "https://www.wikidata.org/wiki/Special:EntityData/Q" + id + ".json"; | ||||
|         const response = await Utils.downloadJson(url) | ||||
|         return Wikidata.ParseResponse(response.entities["Q" + id]) | ||||
|  |  | |||
|  | @ -52,7 +52,7 @@ export interface TagRenderingConfigJson { | |||
|          * Extra parameters to initialize the input helper arguments. | ||||
|          * For semantics, see the 'SpecialInputElements.md' | ||||
|          */ | ||||
|         helperArgs?: (string | number | boolean)[]; | ||||
|         helperArgs?: (string | number | boolean | any)[]; | ||||
|         /** | ||||
|          * If a value is added with the textfield, these extra tag is addded. | ||||
|          * Useful to add a 'fixme=freeform textfield used - to be checked' | ||||
|  |  | |||
|  | @ -30,10 +30,15 @@ export default class Combine extends BaseUIElement { | |||
|                 if (subEl === undefined || subEl === null) { | ||||
|                     continue; | ||||
|                 } | ||||
|                 try{ | ||||
|                      | ||||
|                 const subHtml = subEl.ConstructElement() | ||||
|                 if (subHtml !== undefined) { | ||||
|                     el.appendChild(subHtml) | ||||
|                 } | ||||
|                 }catch(e){ | ||||
|                     console.error("Could not generate subelement in combine due to ", e) | ||||
|                 } | ||||
|             } | ||||
|         } catch (e) { | ||||
|             const domExc = e as DOMException | ||||
|  |  | |||
|  | @ -12,10 +12,11 @@ import ImgurUploader from "../../Logic/ImageProviders/ImgurUploader"; | |||
| import UploadFlowStateUI from "../BigComponents/UploadFlowStateUI"; | ||||
| import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"; | ||||
| import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; | ||||
| import {FixedUiElement} from "../Base/FixedUiElement"; | ||||
| 
 | ||||
| export class ImageUploadFlow extends Toggle { | ||||
| 
 | ||||
|     constructor(tagsSource: UIEventSource<any>, imagePrefix: string = "image") { | ||||
|     constructor(tagsSource: UIEventSource<any>, imagePrefix: string = "image", text: string = undefined) { | ||||
|         const uploader = new ImgurUploader(url => { | ||||
|             // A file was uploaded - we add it to the tags of the object
 | ||||
| 
 | ||||
|  | @ -43,10 +44,17 @@ export class ImageUploadFlow extends Toggle { | |||
|         const licensePicker = new LicensePicker() | ||||
| 
 | ||||
|         const t = Translations.t.image; | ||||
|          | ||||
|         let labelContent : BaseUIElement | ||||
|             if(text === undefined) { | ||||
|                 labelContent = Translations.t.image.addPicture.Clone().SetClass("block align-middle mt-1 ml-3 text-4xl ") | ||||
|             }else{ | ||||
|                 labelContent = new FixedUiElement(text).SetClass("block align-middle mt-1 ml-3 text-2xl ") | ||||
|             } | ||||
|         const label = new Combine([ | ||||
|             Svg.camera_plus_ui().SetClass("block w-12 h-12 p-1"), | ||||
|             Translations.t.image.addPicture.Clone().SetClass("block align-middle mt-1 ml-3") | ||||
|         ]).SetClass("p-2 border-4 border-black rounded-full text-4xl font-bold h-full align-middle w-full flex justify-center") | ||||
|             Svg.camera_plus_ui().SetClass("block w-12 h-12 p-1 text-4xl "), | ||||
|             labelContent | ||||
|         ]).SetClass("p-2 border-4 border-black rounded-full font-bold h-full align-middle w-full flex justify-center") | ||||
| 
 | ||||
|         const fileSelector = new FileSelectorButton(label) | ||||
|         fileSelector.GetValue().addCallback(filelist => { | ||||
|  |  | |||
|  | @ -16,6 +16,7 @@ import LengthInput from "./LengthInput"; | |||
| import {GeoOperations} from "../../Logic/GeoOperations"; | ||||
| import {Unit} from "../../Models/Unit"; | ||||
| import {FixedInputElement} from "./FixedInputElement"; | ||||
| import WikidataSearchBox from "../Wikipedia/WikidataSearchBox"; | ||||
| 
 | ||||
| interface TextFieldDef { | ||||
|     name: string, | ||||
|  | @ -147,7 +148,7 @@ export default class ValidatedTextField { | |||
|         ), | ||||
|         ValidatedTextField.tp( | ||||
|             "wikidata", | ||||
|             "A wikidata identifier, e.g. Q42", | ||||
|             "A wikidata identifier, e.g. Q42. Input helper arguments: [ key: the value of this tag will initialize search (default: name), options: { removePrefixes: string[], removePostfixes: string[] }  these prefixes and postfixes will be removed from the initial search value]", | ||||
|             (str) => { | ||||
|                 if (str === undefined) { | ||||
|                     return false; | ||||
|  | @ -163,7 +164,40 @@ export default class ValidatedTextField { | |||
|                     str = str.substr(wd.length) | ||||
|                 } | ||||
|                 return str.toUpperCase(); | ||||
|             }), | ||||
|             }, | ||||
|             (currentValue, inputHelperOptions) => { | ||||
|                 const args = inputHelperOptions.args | ||||
|                 const searchKey = args[0] ?? "name" | ||||
| 
 | ||||
|                 let searchFor = <string>inputHelperOptions.feature?.properties[searchKey]?.toLowerCase() | ||||
| 
 | ||||
|                 const options = args[1] | ||||
|                 if (searchFor !== undefined && options !== undefined) { | ||||
|                     const prefixes = <string[]>options["removePrefixes"] | ||||
|                     const postfixes = <string[]>options["removePostfixes"] | ||||
| 
 | ||||
|                     for (const postfix of postfixes ?? []) { | ||||
|                         if (searchFor.endsWith(postfix)) { | ||||
|                             searchFor = searchFor.substring(0, searchFor.length - postfix.length) | ||||
|                             break; | ||||
|                         } | ||||
|                     } | ||||
| 
 | ||||
|                     for (const prefix of prefixes ?? []) { | ||||
|                         if (searchFor.startsWith(prefix)) { | ||||
|                             searchFor = searchFor.substring(prefix.length) | ||||
|                             break; | ||||
|                         } | ||||
|                     } | ||||
| 
 | ||||
|                 } | ||||
| 
 | ||||
|                 return new WikidataSearchBox({ | ||||
|                     value: currentValue, | ||||
|                     searchText: new UIEventSource<string>(searchFor) | ||||
|                 }) | ||||
|             } | ||||
|         ), | ||||
| 
 | ||||
|         ValidatedTextField.tp( | ||||
|             "int", | ||||
|  | @ -367,7 +401,7 @@ export default class ValidatedTextField { | |||
| 
 | ||||
|             const unitDropDown = | ||||
|                 unit.denominations.length === 1 ? | ||||
|                     new FixedInputElement( unit.denominations[0].getToggledHuman(isSingular), unit.denominations[0]) | ||||
|                     new FixedInputElement(unit.denominations[0].getToggledHuman(isSingular), unit.denominations[0]) | ||||
|                     : new DropDown("", | ||||
|                         unit.denominations.map(denom => { | ||||
|                             return { | ||||
|  | @ -379,13 +413,13 @@ export default class ValidatedTextField { | |||
|             unitDropDown.GetValue().setData(unit.defaultDenom) | ||||
|             unitDropDown.SetClass("w-min") | ||||
| 
 | ||||
|             const fixedDenom =  unit.denominations.length === 1 ? unit.denominations[0] : undefined | ||||
|             const fixedDenom = unit.denominations.length === 1 ? unit.denominations[0] : undefined | ||||
|             input = new CombinedInputElement( | ||||
|                 input, | ||||
|                 unitDropDown, | ||||
|                 // combine the value from the textfield and the dropdown into the resulting value that should go into OSM
 | ||||
|                 (text, denom) => { | ||||
|                     if(denom === undefined){ | ||||
|                     if (denom === undefined) { | ||||
|                         return text | ||||
|                     } | ||||
|                     return denom?.canonicalValue(text, true) | ||||
|  |  | |||
|  | @ -26,7 +26,7 @@ import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSou | |||
| import ShowDataMultiLayer from "./ShowDataLayer/ShowDataMultiLayer"; | ||||
| import Minimap from "./Base/Minimap"; | ||||
| import AllImageProviders from "../Logic/ImageProviders/AllImageProviders"; | ||||
| import WikipediaBox from "./WikipediaBox"; | ||||
| import WikipediaBox from "./Wikipedia/WikipediaBox"; | ||||
| 
 | ||||
| export interface SpecialVisualization { | ||||
|     funcName: string, | ||||
|  | @ -83,9 +83,13 @@ export default class SpecialVisualizations { | |||
|                     name: "image-key", | ||||
|                     doc: "Image tag to add the URL to (or image-tag:0, image-tag:1 when multiple images are added)", | ||||
|                     defaultValue: "image" | ||||
|                 },{ | ||||
|                     name:"label", | ||||
|                     doc:"The text to show on the button", | ||||
|                     defaultValue: "Add image" | ||||
|                 }], | ||||
|                 constr: (state: State, tags, args) => { | ||||
|                     return new ImageUploadFlow(tags, args[0]) | ||||
|                     return new ImageUploadFlow(tags, args[0], args[1]) | ||||
|                 } | ||||
|             }, | ||||
|             { | ||||
|  |  | |||
							
								
								
									
										80
									
								
								UI/Wikipedia/WikidataPreviewBox.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								UI/Wikipedia/WikidataPreviewBox.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,80 @@ | |||
| import {VariableUiElement} from "../Base/VariableUIElement"; | ||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | ||||
| import Wikidata, {WikidataResponse} from "../../Logic/Web/Wikidata"; | ||||
| import {Translation} from "../i18n/Translation"; | ||||
| import {FixedUiElement} from "../Base/FixedUiElement"; | ||||
| import Loading from "../Base/Loading"; | ||||
| import {Transform} from "stream"; | ||||
| 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 Svg from "../../Svg"; | ||||
| import BaseUIElement from "../BaseUIElement"; | ||||
| 
 | ||||
| export default class WikidataPreviewBox extends VariableUiElement { | ||||
|      | ||||
|     constructor(wikidataId : UIEventSource<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 undefined; | ||||
|             } | ||||
|              | ||||
|             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"] | ||||
|             return WikidataPreviewBox.WikidataResponsePreview(wikidata) | ||||
|         })) | ||||
|              | ||||
|     } | ||||
|      | ||||
|     public static WikidataResponsePreview(wikidata: WikidataResponse): BaseUIElement{ | ||||
|         let link = new Link( | ||||
|             new Combine([ | ||||
|                 wikidata.id, | ||||
|                 Svg.wikidata_ui().SetStyle("width: 2.5rem").SetClass("block") | ||||
|             ]).SetClass("flex"),  | ||||
|             "https://wikidata.org/wiki/"+wikidata.id ,true) | ||||
|      | ||||
|         let info = new Combine( [ | ||||
|             new Combine([Translation.fromMap(wikidata.labels).SetClass("font-bold"),  | ||||
|                 link]).SetClass("flex justify-between"), | ||||
|             Translation.fromMap(wikidata.descriptions) | ||||
|         ]).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){ | ||||
|             imageUrl =  WikimediaImageProvider.singleton.PrepUrl(imageUrl).url | ||||
|             info = new Combine([ new Img(imageUrl).SetStyle("max-width: 5rem; width: unset; height: 4rem").SetClass("rounded-xl mr-2"),  | ||||
|                 info.SetClass("w-full")]).SetClass("flex") | ||||
|         } | ||||
| 
 | ||||
|         info.SetClass("p-2 w-full") | ||||
| 
 | ||||
|         return info | ||||
|     } | ||||
|      | ||||
| } | ||||
							
								
								
									
										119
									
								
								UI/Wikipedia/WikidataSearchBox.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								UI/Wikipedia/WikidataSearchBox.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,119 @@ | |||
| import Combine from "../Base/Combine"; | ||||
| import {InputElement} from "../Input/InputElement"; | ||||
| import {TextField} from "../Input/TextField"; | ||||
| import Translations from "../i18n/Translations"; | ||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | ||||
| import Wikidata, {WikidataResponse} from "../../Logic/Web/Wikidata"; | ||||
| import Locale from "../i18n/Locale"; | ||||
| import {VariableUiElement} from "../Base/VariableUIElement"; | ||||
| import {FixedUiElement} from "../Base/FixedUiElement"; | ||||
| import WikidataPreviewBox from "./WikidataPreviewBox"; | ||||
| import Title from "../Base/Title"; | ||||
| import WikipediaBox from "./WikipediaBox"; | ||||
| import Svg from "../../Svg"; | ||||
| import Link from "../Base/Link"; | ||||
| 
 | ||||
| export default class WikidataSearchBox extends InputElement<string> { | ||||
| 
 | ||||
|     private readonly wikidataId: UIEventSource<string> | ||||
|     private readonly searchText: UIEventSource<string> | ||||
| 
 | ||||
|     constructor(options?: { | ||||
|         searchText?: UIEventSource<string>, | ||||
|         value?: UIEventSource<string> | ||||
|     }) { | ||||
|         super(); | ||||
|         this.searchText = options?.searchText | ||||
|         this.wikidataId = options?.value ?? new UIEventSource<string>(undefined); | ||||
|     } | ||||
| 
 | ||||
|     GetValue(): UIEventSource<string> { | ||||
|         return this.wikidataId; | ||||
|     } | ||||
| 
 | ||||
|     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 lastSearchResults = new UIEventSource<WikidataResponse[]>([]) | ||||
|         const searchFailMessage = new UIEventSource(undefined) | ||||
|         searchField.GetValue().addCallbackAndRunD(searchText => { | ||||
|             if (searchText.length < 3) { | ||||
|                 return; | ||||
|             } | ||||
|             searchFailMessage.setData(undefined) | ||||
|             lastSearchResults.WaitForPromise( | ||||
|                 Wikidata.searchAndFetch(searchText, { | ||||
|                         lang: Locale.language.data, | ||||
|                         maxCount: 5 | ||||
|                     } | ||||
|                 ), err => searchFailMessage.setData(err)) | ||||
| 
 | ||||
|         }) | ||||
| 
 | ||||
| 
 | ||||
|         const previews = new VariableUiElement(lastSearchResults.map(searchResults => { | ||||
|             if (searchFailMessage.data !== undefined) { | ||||
|                 return new Combine([Translations.t.general.wikipedia.failed.Clone().SetClass("alert"), searchFailMessage.data]) | ||||
|             } | ||||
| 
 | ||||
|             if(searchResults.length === 0){ | ||||
|                 return Translations.t.general.wikipedia.noResults.Subs({search: searchField.GetValue().data ?? ""}) | ||||
|             } | ||||
|              | ||||
|             if (searchResults.length === 0) { | ||||
|                 return Translations.t.general.wikipedia.doSearch | ||||
|             } | ||||
| 
 | ||||
|             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") | ||||
| 
 | ||||
|         }, [searchFailMessage])) | ||||
| 
 | ||||
|         //
 | ||||
|         const full = new Combine([ | ||||
|             new Title(Translations.t.general.wikipedia.searchWikidata, 3).SetClass("m-2"), | ||||
|             new Combine([ | ||||
|                 Svg.search_ui().SetStyle("width: 1.5rem"), | ||||
|                 searchField.SetClass("m-2 w-full")]).SetClass("flex"), | ||||
|             previews | ||||
|         ]).SetClass("flex flex-col border-2 border-black rounded-xl m-2 p-2") | ||||
| 
 | ||||
|         return new Combine([ | ||||
|             new VariableUiElement(selectedWikidataId.map(wid => { | ||||
|                 if (wid === undefined) { | ||||
|                     return undefined | ||||
|                 } | ||||
|                 return new WikipediaBox([wid]); | ||||
|             })).SetStyle("max-height:12.5rem"), | ||||
|             full | ||||
|         ]).ConstructElement(); | ||||
|     } | ||||
| 
 | ||||
|     IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false); | ||||
| 
 | ||||
|     IsValid(t: string): boolean { | ||||
|         return t.startsWith("Q") && !isNaN(Number(t.substring(1))); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -1,18 +1,19 @@ | |||
| import {UIEventSource} from "../Logic/UIEventSource"; | ||||
| import {VariableUiElement} from "./Base/VariableUIElement"; | ||||
| import Wikipedia from "../Logic/Web/Wikipedia"; | ||||
| import Loading from "./Base/Loading"; | ||||
| import {FixedUiElement} from "./Base/FixedUiElement"; | ||||
| import Combine from "./Base/Combine"; | ||||
| import BaseUIElement from "./BaseUIElement"; | ||||
| import Title from "./Base/Title"; | ||||
| import Translations from "./i18n/Translations"; | ||||
| import Svg from "../Svg"; | ||||
| import Wikidata, {WikidataResponse} from "../Logic/Web/Wikidata"; | ||||
| import Locale from "./i18n/Locale"; | ||||
| import Link from "./Base/Link"; | ||||
| import {TabbedComponent} from "./Base/TabbedComponent"; | ||||
| import {Translation} from "./i18n/Translation"; | ||||
| import BaseUIElement from "../BaseUIElement"; | ||||
| import Locale from "../i18n/Locale"; | ||||
| import {VariableUiElement} from "../Base/VariableUIElement"; | ||||
| import {Translation} from "../i18n/Translation"; | ||||
| import Svg from "../../Svg"; | ||||
| import Combine from "../Base/Combine"; | ||||
| import Title from "../Base/Title"; | ||||
| import Wikipedia from "../../Logic/Web/Wikipedia"; | ||||
| import Wikidata, {WikidataResponse} from "../../Logic/Web/Wikidata"; | ||||
| import {TabbedComponent} from "../Base/TabbedComponent"; | ||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | ||||
| import Loading from "../Base/Loading"; | ||||
| import {FixedUiElement} from "../Base/FixedUiElement"; | ||||
| import Translations from "../i18n/Translations"; | ||||
| import Link from "../Base/Link"; | ||||
| import WikidataPreviewBox from "./WikidataPreviewBox"; | ||||
| 
 | ||||
| export default class WikipediaBox extends Combine { | ||||
| 
 | ||||
|  | @ -116,7 +117,7 @@ export default class WikipediaBox extends Combine { | |||
|                 if (status[0] == "no page") { | ||||
|                     const [_, wd] = <[string, WikidataResponse]> status | ||||
|                     return new Combine([ | ||||
|                         Translation.fromMap(wd.descriptions) , | ||||
|                         WikidataPreviewBox.WikidataResponsePreview(wd), | ||||
|                         wp.noWikipediaPage.Clone().SetClass("subtle")]).SetClass("flex flex-col p-4") | ||||
|                 } | ||||
| 
 | ||||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -3,7 +3,8 @@ | |||
|     "render": "{image_carousel()}{image_upload()}" | ||||
|   }, | ||||
|   "wikipedia": { | ||||
|     "render": "{wikipedia():max-height:25rem}" | ||||
|     "render": "{wikipedia():max-height:25rem}", | ||||
|     "condition": "wikidata~*" | ||||
|   }, | ||||
|   "reviews": { | ||||
|     "render": "{reviews()}" | ||||
|  |  | |||
|  | @ -26,7 +26,7 @@ | |||
|   "socialImage": "", | ||||
|   "layers": [ | ||||
|     { | ||||
|       "id": "has_a_name", | ||||
|       "id": "has_etymology", | ||||
|       "name": { | ||||
|         "en": "Has etymolgy", | ||||
|         "nl": "Heeft etymology info" | ||||
|  | @ -71,6 +71,21 @@ | |||
|           "render": { | ||||
|             "*": "{wikipedia(name:etymology:wikidata):max-height:30rem}" | ||||
|           } | ||||
|         }, | ||||
|         { | ||||
|           "id": "wikidata-embed", | ||||
|           "render": { | ||||
|             "*": "<iframe src='https://m.wikidata.org/wiki/{name:etymology:wikidata}' style='width: 100%; height: 25rem'></iframe>" | ||||
|           }, | ||||
|           "condition": "name:etymology:wikidata~*" | ||||
|         }, | ||||
|         "wikipedia", | ||||
|         { | ||||
|           "id": "street-name-sign-image", | ||||
|           "render": { | ||||
|             "en": "{image_carousel(image:streetsign)}<br/>{image_upload(image:streetsign, Add image of a street name sign)}", | ||||
|             "nl": "{image_carousel(image:streetsign)}<br/>{image_upload(image:streetsign, Voeg afbeelding van straatnaambordje toe)}" | ||||
|           } | ||||
|         } | ||||
|       ], | ||||
|       "icon": { | ||||
|  | @ -86,7 +101,114 @@ | |||
|         "render": "#00f" | ||||
|       }, | ||||
|       "presets": [] | ||||
|     }, | ||||
|     { | ||||
|       "id": "etymology_missing", | ||||
|       "name": { | ||||
|         "en": "No etymology data yet", | ||||
|         "nl": "Zonder etymology" | ||||
|       }, | ||||
|       "source": { | ||||
|         "osmTags": { | ||||
|           "and": [ | ||||
|             "name~*", | ||||
|             { | ||||
|               "or": [ | ||||
|                 "highway~*", | ||||
|                 "building~*", | ||||
|                 "amenity=place_of_worship", | ||||
|                 "man_made=bridge", | ||||
|                 "heritage~*", | ||||
|                 "leisure=park", | ||||
|                 "leisure=nature_reserve", | ||||
|                 "landuse=forest", | ||||
|                 "natural=water" | ||||
|               ] | ||||
|             }, | ||||
|             { | ||||
|               "#": "We remove various features which often are too big and clutter the map", | ||||
|               "and": [ | ||||
|                 "healtcare=", | ||||
|                 "shop=", | ||||
|                 "school=", | ||||
|                 "place=", | ||||
|                 "landuse!=residential" | ||||
|               ] | ||||
|             } | ||||
|           ] | ||||
|         } | ||||
|       }, | ||||
|       "minZoom": 16, | ||||
|       "title": { | ||||
|         "render": { | ||||
|           "*": "{name}" | ||||
|         } | ||||
|       }, | ||||
|       "tagRenderings": [ | ||||
|         { | ||||
|           "id": "name-origin-wikidata", | ||||
|           "question": { | ||||
|             "en": "What is the wikidata entry for the thing this feature is named after?", | ||||
|             "nl": "Wat is de wikidata-entry voor het object waarnaar dit vernoemd is?<br /><span class='subtle'>Plak hier de wikidata entry of sla over</span>" | ||||
|           }, | ||||
|           "render": { | ||||
|             "*": "{name:etymology:wikidata}" | ||||
|           }, | ||||
|           "freeform": { | ||||
|             "key": "name:etymology:wikidata", | ||||
|             "type": "wikidata", | ||||
|             "helperArgs": [ | ||||
|               "name", | ||||
|               { | ||||
|                 "removePostfixes": [ | ||||
|                   "steenweg", | ||||
|                   "heirbaan", | ||||
|                   "baan", | ||||
|                   "straat", | ||||
|                   "street", | ||||
|                   "weg", | ||||
|                   "dreef", | ||||
|                   "laan", | ||||
|                   "boulevard", | ||||
|                   "pad", | ||||
|                   "path", | ||||
|                   "plein", | ||||
|                   "square", | ||||
|                   "plaza", | ||||
|                   "wegel", | ||||
|                   "kerk", | ||||
|                   "church", | ||||
|                   "kaai" | ||||
|                 ] | ||||
|               } | ||||
|             ] | ||||
|           } | ||||
|         }, | ||||
|         { | ||||
|           "id": "name-origin", | ||||
|           "question": { | ||||
|             "en": "What is the origin of this name?", | ||||
|             "nl": "Naar wat is dit vernoemd?" | ||||
|           }, | ||||
|           "render": { | ||||
|             "en": "<div class='subtle'>This feature is named after</div><br />{name:etymology{", | ||||
|             "nl": "<div class='subtle'>Dit is vernoemd naar</div><br />{name:etymology{" | ||||
|           }, | ||||
|           "freeform": { | ||||
|             "key": "name:etymology" | ||||
|           }, | ||||
|           "condition": "name:etymology:wikidata=" | ||||
|         }, | ||||
|         "wikipedia", | ||||
|         { | ||||
|           "id": "street-name-sign-image", | ||||
|           "render": { | ||||
|             "en": "{image_carousel(image:streetsign)}<br/>{image_upload(image:streetsign, Add image of a street name sign)}", | ||||
|             "nl": "{image_carousel(image:streetsign)}<br/>{image_upload(image:streetsign, Voeg afbeelding van straatnaambordje toe)}" | ||||
|           } | ||||
|         } | ||||
|       ], | ||||
|       "color": "#fcb35388" | ||||
|     } | ||||
|   ], | ||||
|   "roamingRenderings": [] | ||||
|   ] | ||||
| } | ||||
|  | @ -824,6 +824,10 @@ video { | |||
|   margin: 1rem; | ||||
| } | ||||
| 
 | ||||
| .m-px { | ||||
|   margin: 1px; | ||||
| } | ||||
| 
 | ||||
| .my-2 { | ||||
|   margin-top: 0.5rem; | ||||
|   margin-bottom: 0.5rem; | ||||
|  | @ -848,18 +852,14 @@ video { | |||
|   margin-left: 0.75rem; | ||||
| } | ||||
| 
 | ||||
| .mr-3 { | ||||
|   margin-right: 0.75rem; | ||||
| .mb-2 { | ||||
|   margin-bottom: 0.5rem; | ||||
| } | ||||
| 
 | ||||
| .mr-4 { | ||||
|   margin-right: 1rem; | ||||
| } | ||||
| 
 | ||||
| .mb-2 { | ||||
|   margin-bottom: 0.5rem; | ||||
| } | ||||
| 
 | ||||
| .mt-3 { | ||||
|   margin-top: 0.75rem; | ||||
| } | ||||
|  | @ -912,6 +912,10 @@ video { | |||
|   margin-bottom: 0.25rem; | ||||
| } | ||||
| 
 | ||||
| .mr-3 { | ||||
|   margin-right: 0.75rem; | ||||
| } | ||||
| 
 | ||||
| .mb-4 { | ||||
|   margin-bottom: 1rem; | ||||
| } | ||||
|  | @ -1251,14 +1255,14 @@ video { | |||
|   border-radius: 0.25rem; | ||||
| } | ||||
| 
 | ||||
| .rounded-xl { | ||||
|   border-radius: 0.75rem; | ||||
| } | ||||
| 
 | ||||
| .rounded-lg { | ||||
|   border-radius: 0.5rem; | ||||
| } | ||||
| 
 | ||||
| .rounded-xl { | ||||
|   border-radius: 0.75rem; | ||||
| } | ||||
| 
 | ||||
| .border { | ||||
|   border-width: 1px; | ||||
| } | ||||
|  | @ -1390,18 +1394,6 @@ video { | |||
|   padding-left: 0.5rem; | ||||
| } | ||||
| 
 | ||||
| .pr-2 { | ||||
|   padding-right: 0.5rem; | ||||
| } | ||||
| 
 | ||||
| .pl-6 { | ||||
|   padding-left: 1.5rem; | ||||
| } | ||||
| 
 | ||||
| .pt-2 { | ||||
|   padding-top: 0.5rem; | ||||
| } | ||||
| 
 | ||||
| .pt-3 { | ||||
|   padding-top: 0.75rem; | ||||
| } | ||||
|  | @ -1462,6 +1454,18 @@ video { | |||
|   padding-top: 0px; | ||||
| } | ||||
| 
 | ||||
| .pr-2 { | ||||
|   padding-right: 0.5rem; | ||||
| } | ||||
| 
 | ||||
| .pl-6 { | ||||
|   padding-left: 1.5rem; | ||||
| } | ||||
| 
 | ||||
| .pt-2 { | ||||
|   padding-top: 0.5rem; | ||||
| } | ||||
| 
 | ||||
| .text-center { | ||||
|   text-align: center; | ||||
| } | ||||
|  | @ -1582,14 +1586,14 @@ video { | |||
|   text-decoration: underline; | ||||
| } | ||||
| 
 | ||||
| .opacity-50 { | ||||
|   opacity: 0.5; | ||||
| } | ||||
| 
 | ||||
| .opacity-0 { | ||||
|   opacity: 0; | ||||
| } | ||||
| 
 | ||||
| .opacity-50 { | ||||
|   opacity: 0.5; | ||||
| } | ||||
| 
 | ||||
| .opacity-40 { | ||||
|   opacity: 0.4; | ||||
| } | ||||
|  | @ -1632,6 +1636,12 @@ video { | |||
|   transition-duration: 150ms; | ||||
| } | ||||
| 
 | ||||
| .transition-colors { | ||||
|   transition-property: background-color, border-color, color, fill, stroke; | ||||
|   transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); | ||||
|   transition-duration: 150ms; | ||||
| } | ||||
| 
 | ||||
| .transition-opacity { | ||||
|   transition-property: opacity; | ||||
|   transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); | ||||
|  | @ -1902,6 +1912,10 @@ li::marker { | |||
|   border: 5px solid var(--catch-detail-color); | ||||
| } | ||||
| 
 | ||||
| .border-attention { | ||||
|   border-color: var(--catch-detail-color); | ||||
| } | ||||
| 
 | ||||
| .direction-svg svg path { | ||||
|   fill: var(--catch-detail-color) !important; | ||||
| } | ||||
|  | @ -2223,6 +2237,10 @@ li::marker { | |||
| } | ||||
| 
 | ||||
| @media (min-width: 640px) { | ||||
|   .sm\:m-1 { | ||||
|     margin: 0.25rem; | ||||
|   } | ||||
| 
 | ||||
|   .sm\:mx-auto { | ||||
|     margin-left: auto; | ||||
|     margin-right: auto; | ||||
|  | @ -2268,6 +2286,10 @@ li::marker { | |||
|     justify-content: space-between; | ||||
|   } | ||||
| 
 | ||||
|   .sm\:border-4 { | ||||
|     border-width: 4px; | ||||
|   } | ||||
| 
 | ||||
|   .sm\:p-0\.5 { | ||||
|     padding: 0.125rem; | ||||
|   } | ||||
|  | @ -2284,6 +2306,10 @@ li::marker { | |||
|     padding: 0.25rem; | ||||
|   } | ||||
| 
 | ||||
|   .sm\:p-2 { | ||||
|     padding: 0.5rem; | ||||
|   } | ||||
| 
 | ||||
|   .sm\:pl-2 { | ||||
|     padding-left: 0.5rem; | ||||
|   } | ||||
|  | @ -2383,6 +2409,10 @@ li::marker { | |||
|     padding: 0.5rem; | ||||
|   } | ||||
| 
 | ||||
|   .md\:p-3 { | ||||
|     padding: 0.75rem; | ||||
|   } | ||||
| 
 | ||||
|   .md\:pt-4 { | ||||
|     padding-top: 1rem; | ||||
|   } | ||||
|  |  | |||
|  | @ -195,6 +195,10 @@ li::marker { | |||
|     border: 5px solid var(--catch-detail-color); | ||||
| } | ||||
| 
 | ||||
| .border-attention { | ||||
|     border-color: var(--catch-detail-color); | ||||
| } | ||||
| 
 | ||||
| .direction-svg svg path { | ||||
|     fill: var(--catch-detail-color) !important; | ||||
| } | ||||
|  |  | |||
|  | @ -222,7 +222,11 @@ | |||
|             "wikipediaboxTitle": "Wikipedia", | ||||
|             "failed":"Loading the wikipedia entry failed", | ||||
|             "loading": "Loading Wikipedia...", | ||||
|             "noWikipediaPage": "This wikidata item has no corresponding wikipedia page yet." | ||||
|             "noWikipediaPage": "This wikidata item has no corresponding wikipedia page yet.", | ||||
|             "searchWikidata": "Search on wikidata", | ||||
|             "doSearch": "Search above to see results", | ||||
|             "noResults": "Nothing found for <i>{search}</i>", | ||||
|             "createNewWikidata": "Create a new wikidata item" | ||||
|         } | ||||
|     }, | ||||
|     "favourite": { | ||||
|  |  | |||
|  | @ -752,6 +752,24 @@ | |||
|                 "tagRenderings": { | ||||
|                     "simple etymology": { | ||||
|                         "render": "Named after {name:etymology}" | ||||
|                     }, | ||||
|                     "street-name-sign-image": { | ||||
|                         "render": "{image_carousel(image:streetsign)}<br/>{image_upload(image:streetsign, Add image of a street name sign)}" | ||||
|                     } | ||||
|                 } | ||||
|             }, | ||||
|             "1": { | ||||
|                 "name": "No etymology data yet", | ||||
|                 "tagRenderings": { | ||||
|                     "name-origin": { | ||||
|                         "question": "What is the origin of this name?", | ||||
|                         "render": "<div class='subtle'>This feature is named after</div><br />{name:etymology{" | ||||
|                     }, | ||||
|                     "name-origin-wikidata": { | ||||
|                         "question": "What is the wikidata entry for the thing this feature is named after?" | ||||
|                     }, | ||||
|                     "street-name-sign-image": { | ||||
|                         "render": "{image_carousel(image:streetsign)}<br/>{image_upload(image:streetsign, Add image of a street name sign)}" | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  |  | |||
|  | @ -633,6 +633,24 @@ | |||
|                 "tagRenderings": { | ||||
|                     "simple etymology": { | ||||
|                         "render": "Vernoemd naar {name:etymology}" | ||||
|                     }, | ||||
|                     "street-name-sign-image": { | ||||
|                         "render": "{image_carousel(image:streetsign)}<br/>{image_upload(image:streetsign, Voeg afbeelding van straatnaambordje toe)}" | ||||
|                     } | ||||
|                 } | ||||
|             }, | ||||
|             "1": { | ||||
|                 "name": "Zonder etymology", | ||||
|                 "tagRenderings": { | ||||
|                     "name-origin": { | ||||
|                         "question": "Naar wat is dit vernoemd?", | ||||
|                         "render": "<div class='subtle'>Dit is vernoemd naar</div><br />{name:etymology{" | ||||
|                     }, | ||||
|                     "name-origin-wikidata": { | ||||
|                         "question": "Wat is de wikidata-entry voor het object waarnaar dit vernoemd is?<br /><span class='subtle'>Plak hier de wikidata entry of sla over</span>" | ||||
|                     }, | ||||
|                     "street-name-sign-image": { | ||||
|                         "render": "{image_carousel(image:streetsign)}<br/>{image_upload(image:streetsign, Voeg afbeelding van straatnaambordje toe)}" | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  |  | |||
							
								
								
									
										28
									
								
								test.ts
									
										
									
									
									
								
							
							
						
						
									
										28
									
								
								test.ts
									
										
									
									
									
								
							|  | @ -1,26 +1,6 @@ | |||
| import FeatureInfoBox from "./UI/Popup/FeatureInfoBox"; | ||||
| import WikidataPreviewBox from "./UI/Wikipedia/WikidataPreviewBox"; | ||||
| import {UIEventSource} from "./Logic/UIEventSource"; | ||||
| import AllKnownLayers from "./Customizations/AllKnownLayers"; | ||||
| import State from "./State"; | ||||
| import {AllKnownLayouts} from "./Customizations/AllKnownLayouts"; | ||||
| import Wikidata from "./Logic/Web/Wikidata"; | ||||
| import WikidataSearchBox from "./UI/Wikipedia/WikidataSearchBox"; | ||||
| 
 | ||||
| State.state = new State(AllKnownLayouts.allKnownLayouts.get("charging_stations")) | ||||
| State.state.changes.pendingChanges.setData([]) | ||||
| const geojson = { | ||||
|     type: "Feature", | ||||
|     geometry: { | ||||
|         type: "Point", | ||||
|         coordinates: [51.0, 4] | ||||
|     }, | ||||
|     properties: | ||||
|         { | ||||
|             id: "node/42", | ||||
|             amenity: "charging_station", | ||||
|         } | ||||
| } | ||||
| State.state.allElements.addOrGetElement(geojson) | ||||
| const tags = State.state.allElements.getEventSourceById("node/42") | ||||
| new FeatureInfoBox( | ||||
|     tags, | ||||
|     AllKnownLayers.sharedLayers.get("charging_station") | ||||
| ).AttachTo("maindiv") | ||||
| new WikidataSearchBox({searchText: new UIEventSource("Brugge")}).AttachTo("maindiv") | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue