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[]>>()
|
private static _cache: Map<string, UIEventSource<ProvidedImage[]>> = new Map<string, UIEventSource<ProvidedImage[]>>()
|
||||||
|
|
||||||
public static LoadImagesFor(tags: UIEventSource<any>, tagKey?: string): UIEventSource<ProvidedImage[]> {
|
public static LoadImagesFor(tags: UIEventSource<any>, tagKey?: string): UIEventSource<ProvidedImage[]> {
|
||||||
const id = tags.data.id
|
if (tags.data.id === undefined) {
|
||||||
if (id === undefined) {
|
|
||||||
return 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) {
|
if (cached !== undefined) {
|
||||||
return cached
|
return cached
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const source = new UIEventSource([])
|
const source = new UIEventSource([])
|
||||||
this._cache.set(id, source)
|
this._cache.set(cacheKey, source)
|
||||||
const allSources = []
|
const allSources = []
|
||||||
for (const imageProvider of AllImageProviders.ImageAttributionSource) {
|
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/")) {
|
if (value.toLowerCase().startsWith("https://commons.wikimedia.org/wiki/")) {
|
||||||
return value;
|
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:")) {
|
if (!image.startsWith("File:")) {
|
||||||
image = "File:" + image
|
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))
|
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/")){
|
if(value.startsWith("https://upload.wikimedia.org/")){
|
||||||
value = value.substring(value.lastIndexOf("/") + 1)
|
value = value.substring(value.lastIndexOf("/") + 1)
|
||||||
value = decodeURIComponent(value)
|
value = decodeURIComponent(value)
|
||||||
|
@ -130,26 +130,38 @@ export class WikimediaImageProvider extends ImageProvider {
|
||||||
return value;
|
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>[]> {
|
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){
|
if(key !== undefined && key !== this.commons_key && !hasCommonsPrefix){
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
value = this.removeCommonsPrefix(value)
|
value = WikimediaImageProvider.removeCommonsPrefix(value)
|
||||||
if (value.startsWith("Category:")) {
|
if (value.startsWith("Category:")) {
|
||||||
const urls = await Wikimedia.GetCategoryContents(value)
|
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:")) {
|
if (value.startsWith("File:")) {
|
||||||
return [this.UrlForImage(value)]
|
return [Promise.resolve(this.UrlForImage(value))]
|
||||||
}
|
}
|
||||||
if (value.startsWith("http")) {
|
if (value.startsWith("http")) {
|
||||||
// PRobably an error
|
// PRobably an error
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
// We do a last effort and assume this is a file
|
// 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
|
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.
|
* Converts a promise into a UIVentsource, sets the UIEVentSource when the result is calculated.
|
||||||
* If the promise fails, the value will stay undefined
|
* If the promise fails, the value will stay undefined
|
||||||
|
@ -195,16 +216,20 @@ export class UIEventSource<T> {
|
||||||
const sink = new UIEventSource<X>(undefined)
|
const sink = new UIEventSource<X>(undefined)
|
||||||
const seenEventSources = new Set<UIEventSource<X>>();
|
const seenEventSources = new Set<UIEventSource<X>>();
|
||||||
mapped.addCallbackAndRun(newEventSource => {
|
mapped.addCallbackAndRun(newEventSource => {
|
||||||
|
if (newEventSource === null) {
|
||||||
if (newEventSource === undefined) {
|
sink.setData(null)
|
||||||
|
} else if (newEventSource === undefined) {
|
||||||
sink.setData(undefined)
|
sink.setData(undefined)
|
||||||
} else if (!seenEventSources.has(newEventSource)) {
|
}else if (!seenEventSources.has(newEventSource)) {
|
||||||
seenEventSources.add(newEventSource)
|
seenEventSources.add(newEventSource)
|
||||||
newEventSource.addCallbackAndRun(resultData => {
|
newEventSource.addCallbackAndRun(resultData => {
|
||||||
if (mapped.data === newEventSource) {
|
if (mapped.data === newEventSource) {
|
||||||
sink.setData(resultData);
|
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
|
commons: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface WikidataSearchoptions {
|
||||||
|
lang?: "en" | string,
|
||||||
|
maxCount?: 20 | number
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility functions around wikidata
|
* Utility functions around wikidata
|
||||||
*/
|
*/
|
||||||
|
@ -47,10 +52,14 @@ export default class Wikidata {
|
||||||
const claimsList: any[] = entity.claims[claimId]
|
const claimsList: any[] = entity.claims[claimId]
|
||||||
const values = new Set<string>()
|
const values = new Set<string>()
|
||||||
for (const claim of claimsList) {
|
for (const claim of claimsList) {
|
||||||
const value = claim.mainsnak?.datavalue?.value;
|
let value = claim.mainsnak?.datavalue?.value;
|
||||||
if(value !== undefined){
|
if (value === undefined) {
|
||||||
values.add(value)
|
continue;
|
||||||
}
|
}
|
||||||
|
if(value.id !== undefined){
|
||||||
|
value = value.id
|
||||||
|
}
|
||||||
|
values.add(value)
|
||||||
}
|
}
|
||||||
claims.set(claimId, values);
|
claims.set(claimId, values);
|
||||||
}
|
}
|
||||||
|
@ -77,6 +86,82 @@ export default class Wikidata {
|
||||||
return src;
|
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{
|
private static ExtractKey(value: string | number) : number{
|
||||||
if (typeof value === "number") {
|
if (typeof value === "number") {
|
||||||
return value
|
return value
|
||||||
|
@ -99,6 +184,7 @@ export default class Wikidata {
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads a wikidata page
|
* Loads a wikidata page
|
||||||
* @returns the entity of the given value
|
* @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)
|
console.warn("Could not extract a wikidata entry from", value)
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
console.log("Requesting wikidata with id", id)
|
|
||||||
const url = "https://www.wikidata.org/wiki/Special:EntityData/Q" + id + ".json";
|
const url = "https://www.wikidata.org/wiki/Special:EntityData/Q" + id + ".json";
|
||||||
const response = await Utils.downloadJson(url)
|
const response = await Utils.downloadJson(url)
|
||||||
return Wikidata.ParseResponse(response.entities["Q" + id])
|
return Wikidata.ParseResponse(response.entities["Q" + id])
|
||||||
|
|
|
@ -52,7 +52,7 @@ export interface TagRenderingConfigJson {
|
||||||
* Extra parameters to initialize the input helper arguments.
|
* Extra parameters to initialize the input helper arguments.
|
||||||
* For semantics, see the 'SpecialInputElements.md'
|
* 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.
|
* If a value is added with the textfield, these extra tag is addded.
|
||||||
* Useful to add a 'fixme=freeform textfield used - to be checked'
|
* 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) {
|
if (subEl === undefined || subEl === null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
try{
|
||||||
|
|
||||||
const subHtml = subEl.ConstructElement()
|
const subHtml = subEl.ConstructElement()
|
||||||
if (subHtml !== undefined) {
|
if (subHtml !== undefined) {
|
||||||
el.appendChild(subHtml)
|
el.appendChild(subHtml)
|
||||||
}
|
}
|
||||||
|
}catch(e){
|
||||||
|
console.error("Could not generate subelement in combine due to ", e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const domExc = e as DOMException
|
const domExc = e as DOMException
|
||||||
|
|
|
@ -12,10 +12,11 @@ import ImgurUploader from "../../Logic/ImageProviders/ImgurUploader";
|
||||||
import UploadFlowStateUI from "../BigComponents/UploadFlowStateUI";
|
import UploadFlowStateUI from "../BigComponents/UploadFlowStateUI";
|
||||||
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction";
|
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction";
|
||||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
|
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
|
||||||
|
import {FixedUiElement} from "../Base/FixedUiElement";
|
||||||
|
|
||||||
export class ImageUploadFlow extends Toggle {
|
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 => {
|
const uploader = new ImgurUploader(url => {
|
||||||
// A file was uploaded - we add it to the tags of the object
|
// 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 licensePicker = new LicensePicker()
|
||||||
|
|
||||||
const t = Translations.t.image;
|
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([
|
const label = new Combine([
|
||||||
Svg.camera_plus_ui().SetClass("block w-12 h-12 p-1"),
|
Svg.camera_plus_ui().SetClass("block w-12 h-12 p-1 text-4xl "),
|
||||||
Translations.t.image.addPicture.Clone().SetClass("block align-middle mt-1 ml-3")
|
labelContent
|
||||||
]).SetClass("p-2 border-4 border-black rounded-full text-4xl font-bold h-full align-middle w-full flex justify-center")
|
]).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)
|
const fileSelector = new FileSelectorButton(label)
|
||||||
fileSelector.GetValue().addCallback(filelist => {
|
fileSelector.GetValue().addCallback(filelist => {
|
||||||
|
|
|
@ -16,6 +16,7 @@ import LengthInput from "./LengthInput";
|
||||||
import {GeoOperations} from "../../Logic/GeoOperations";
|
import {GeoOperations} from "../../Logic/GeoOperations";
|
||||||
import {Unit} from "../../Models/Unit";
|
import {Unit} from "../../Models/Unit";
|
||||||
import {FixedInputElement} from "./FixedInputElement";
|
import {FixedInputElement} from "./FixedInputElement";
|
||||||
|
import WikidataSearchBox from "../Wikipedia/WikidataSearchBox";
|
||||||
|
|
||||||
interface TextFieldDef {
|
interface TextFieldDef {
|
||||||
name: string,
|
name: string,
|
||||||
|
@ -147,7 +148,7 @@ export default class ValidatedTextField {
|
||||||
),
|
),
|
||||||
ValidatedTextField.tp(
|
ValidatedTextField.tp(
|
||||||
"wikidata",
|
"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) => {
|
(str) => {
|
||||||
if (str === undefined) {
|
if (str === undefined) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -163,7 +164,40 @@ export default class ValidatedTextField {
|
||||||
str = str.substr(wd.length)
|
str = str.substr(wd.length)
|
||||||
}
|
}
|
||||||
return str.toUpperCase();
|
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(
|
ValidatedTextField.tp(
|
||||||
"int",
|
"int",
|
||||||
|
@ -367,7 +401,7 @@ export default class ValidatedTextField {
|
||||||
|
|
||||||
const unitDropDown =
|
const unitDropDown =
|
||||||
unit.denominations.length === 1 ?
|
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("",
|
: new DropDown("",
|
||||||
unit.denominations.map(denom => {
|
unit.denominations.map(denom => {
|
||||||
return {
|
return {
|
||||||
|
@ -385,7 +419,7 @@ export default class ValidatedTextField {
|
||||||
unitDropDown,
|
unitDropDown,
|
||||||
// combine the value from the textfield and the dropdown into the resulting value that should go into OSM
|
// combine the value from the textfield and the dropdown into the resulting value that should go into OSM
|
||||||
(text, denom) => {
|
(text, denom) => {
|
||||||
if(denom === undefined){
|
if (denom === undefined) {
|
||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
return denom?.canonicalValue(text, true)
|
return denom?.canonicalValue(text, true)
|
||||||
|
|
|
@ -26,7 +26,7 @@ import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSou
|
||||||
import ShowDataMultiLayer from "./ShowDataLayer/ShowDataMultiLayer";
|
import ShowDataMultiLayer from "./ShowDataLayer/ShowDataMultiLayer";
|
||||||
import Minimap from "./Base/Minimap";
|
import Minimap from "./Base/Minimap";
|
||||||
import AllImageProviders from "../Logic/ImageProviders/AllImageProviders";
|
import AllImageProviders from "../Logic/ImageProviders/AllImageProviders";
|
||||||
import WikipediaBox from "./WikipediaBox";
|
import WikipediaBox from "./Wikipedia/WikipediaBox";
|
||||||
|
|
||||||
export interface SpecialVisualization {
|
export interface SpecialVisualization {
|
||||||
funcName: string,
|
funcName: string,
|
||||||
|
@ -83,9 +83,13 @@ export default class SpecialVisualizations {
|
||||||
name: "image-key",
|
name: "image-key",
|
||||||
doc: "Image tag to add the URL to (or image-tag:0, image-tag:1 when multiple images are added)",
|
doc: "Image tag to add the URL to (or image-tag:0, image-tag:1 when multiple images are added)",
|
||||||
defaultValue: "image"
|
defaultValue: "image"
|
||||||
|
},{
|
||||||
|
name:"label",
|
||||||
|
doc:"The text to show on the button",
|
||||||
|
defaultValue: "Add image"
|
||||||
}],
|
}],
|
||||||
constr: (state: State, tags, args) => {
|
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 BaseUIElement from "../BaseUIElement";
|
||||||
import {VariableUiElement} from "./Base/VariableUIElement";
|
import Locale from "../i18n/Locale";
|
||||||
import Wikipedia from "../Logic/Web/Wikipedia";
|
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||||
import Loading from "./Base/Loading";
|
import {Translation} from "../i18n/Translation";
|
||||||
import {FixedUiElement} from "./Base/FixedUiElement";
|
import Svg from "../../Svg";
|
||||||
import Combine from "./Base/Combine";
|
import Combine from "../Base/Combine";
|
||||||
import BaseUIElement from "./BaseUIElement";
|
import Title from "../Base/Title";
|
||||||
import Title from "./Base/Title";
|
import Wikipedia from "../../Logic/Web/Wikipedia";
|
||||||
import Translations from "./i18n/Translations";
|
import Wikidata, {WikidataResponse} from "../../Logic/Web/Wikidata";
|
||||||
import Svg from "../Svg";
|
import {TabbedComponent} from "../Base/TabbedComponent";
|
||||||
import Wikidata, {WikidataResponse} from "../Logic/Web/Wikidata";
|
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
import Locale from "./i18n/Locale";
|
import Loading from "../Base/Loading";
|
||||||
import Link from "./Base/Link";
|
import {FixedUiElement} from "../Base/FixedUiElement";
|
||||||
import {TabbedComponent} from "./Base/TabbedComponent";
|
import Translations from "../i18n/Translations";
|
||||||
import {Translation} from "./i18n/Translation";
|
import Link from "../Base/Link";
|
||||||
|
import WikidataPreviewBox from "./WikidataPreviewBox";
|
||||||
|
|
||||||
export default class WikipediaBox extends Combine {
|
export default class WikipediaBox extends Combine {
|
||||||
|
|
||||||
|
@ -116,7 +117,7 @@ export default class WikipediaBox extends Combine {
|
||||||
if (status[0] == "no page") {
|
if (status[0] == "no page") {
|
||||||
const [_, wd] = <[string, WikidataResponse]> status
|
const [_, wd] = <[string, WikidataResponse]> status
|
||||||
return new Combine([
|
return new Combine([
|
||||||
Translation.fromMap(wd.descriptions) ,
|
WikidataPreviewBox.WikidataResponsePreview(wd),
|
||||||
wp.noWikipediaPage.Clone().SetClass("subtle")]).SetClass("flex flex-col p-4")
|
wp.noWikipediaPage.Clone().SetClass("subtle")]).SetClass("flex flex-col p-4")
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
"render": "{image_carousel()}{image_upload()}"
|
"render": "{image_carousel()}{image_upload()}"
|
||||||
},
|
},
|
||||||
"wikipedia": {
|
"wikipedia": {
|
||||||
"render": "{wikipedia():max-height:25rem}"
|
"render": "{wikipedia():max-height:25rem}",
|
||||||
|
"condition": "wikidata~*"
|
||||||
},
|
},
|
||||||
"reviews": {
|
"reviews": {
|
||||||
"render": "{reviews()}"
|
"render": "{reviews()}"
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
"socialImage": "",
|
"socialImage": "",
|
||||||
"layers": [
|
"layers": [
|
||||||
{
|
{
|
||||||
"id": "has_a_name",
|
"id": "has_etymology",
|
||||||
"name": {
|
"name": {
|
||||||
"en": "Has etymolgy",
|
"en": "Has etymolgy",
|
||||||
"nl": "Heeft etymology info"
|
"nl": "Heeft etymology info"
|
||||||
|
@ -71,6 +71,21 @@
|
||||||
"render": {
|
"render": {
|
||||||
"*": "{wikipedia(name:etymology:wikidata):max-height:30rem}"
|
"*": "{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": {
|
"icon": {
|
||||||
|
@ -86,7 +101,114 @@
|
||||||
"render": "#00f"
|
"render": "#00f"
|
||||||
},
|
},
|
||||||
"presets": []
|
"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)}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"roamingRenderings": []
|
"color": "#fcb35388"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
|
@ -824,6 +824,10 @@ video {
|
||||||
margin: 1rem;
|
margin: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.m-px {
|
||||||
|
margin: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
.my-2 {
|
.my-2 {
|
||||||
margin-top: 0.5rem;
|
margin-top: 0.5rem;
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
|
@ -848,18 +852,14 @@ video {
|
||||||
margin-left: 0.75rem;
|
margin-left: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mr-3 {
|
.mb-2 {
|
||||||
margin-right: 0.75rem;
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mr-4 {
|
.mr-4 {
|
||||||
margin-right: 1rem;
|
margin-right: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mb-2 {
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mt-3 {
|
.mt-3 {
|
||||||
margin-top: 0.75rem;
|
margin-top: 0.75rem;
|
||||||
}
|
}
|
||||||
|
@ -912,6 +912,10 @@ video {
|
||||||
margin-bottom: 0.25rem;
|
margin-bottom: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mr-3 {
|
||||||
|
margin-right: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
.mb-4 {
|
.mb-4 {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
@ -1251,14 +1255,14 @@ video {
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rounded-xl {
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rounded-lg {
|
.rounded-lg {
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.rounded-xl {
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
.border {
|
.border {
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
}
|
}
|
||||||
|
@ -1390,18 +1394,6 @@ video {
|
||||||
padding-left: 0.5rem;
|
padding-left: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pr-2 {
|
|
||||||
padding-right: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pl-6 {
|
|
||||||
padding-left: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pt-2 {
|
|
||||||
padding-top: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pt-3 {
|
.pt-3 {
|
||||||
padding-top: 0.75rem;
|
padding-top: 0.75rem;
|
||||||
}
|
}
|
||||||
|
@ -1462,6 +1454,18 @@ video {
|
||||||
padding-top: 0px;
|
padding-top: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pr-2 {
|
||||||
|
padding-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pl-6 {
|
||||||
|
padding-left: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pt-2 {
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.text-center {
|
.text-center {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
@ -1582,14 +1586,14 @@ video {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.opacity-50 {
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.opacity-0 {
|
.opacity-0 {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.opacity-50 {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
.opacity-40 {
|
.opacity-40 {
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
}
|
}
|
||||||
|
@ -1632,6 +1636,12 @@ video {
|
||||||
transition-duration: 150ms;
|
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-opacity {
|
||||||
transition-property: opacity;
|
transition-property: opacity;
|
||||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
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: 5px solid var(--catch-detail-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.border-attention {
|
||||||
|
border-color: var(--catch-detail-color);
|
||||||
|
}
|
||||||
|
|
||||||
.direction-svg svg path {
|
.direction-svg svg path {
|
||||||
fill: var(--catch-detail-color) !important;
|
fill: var(--catch-detail-color) !important;
|
||||||
}
|
}
|
||||||
|
@ -2223,6 +2237,10 @@ li::marker {
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 640px) {
|
@media (min-width: 640px) {
|
||||||
|
.sm\:m-1 {
|
||||||
|
margin: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
.sm\:mx-auto {
|
.sm\:mx-auto {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
|
@ -2268,6 +2286,10 @@ li::marker {
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sm\:border-4 {
|
||||||
|
border-width: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.sm\:p-0\.5 {
|
.sm\:p-0\.5 {
|
||||||
padding: 0.125rem;
|
padding: 0.125rem;
|
||||||
}
|
}
|
||||||
|
@ -2284,6 +2306,10 @@ li::marker {
|
||||||
padding: 0.25rem;
|
padding: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sm\:p-2 {
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.sm\:pl-2 {
|
.sm\:pl-2 {
|
||||||
padding-left: 0.5rem;
|
padding-left: 0.5rem;
|
||||||
}
|
}
|
||||||
|
@ -2383,6 +2409,10 @@ li::marker {
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.md\:p-3 {
|
||||||
|
padding: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
.md\:pt-4 {
|
.md\:pt-4 {
|
||||||
padding-top: 1rem;
|
padding-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
|
@ -195,6 +195,10 @@ li::marker {
|
||||||
border: 5px solid var(--catch-detail-color);
|
border: 5px solid var(--catch-detail-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.border-attention {
|
||||||
|
border-color: var(--catch-detail-color);
|
||||||
|
}
|
||||||
|
|
||||||
.direction-svg svg path {
|
.direction-svg svg path {
|
||||||
fill: var(--catch-detail-color) !important;
|
fill: var(--catch-detail-color) !important;
|
||||||
}
|
}
|
||||||
|
|
|
@ -222,7 +222,11 @@
|
||||||
"wikipediaboxTitle": "Wikipedia",
|
"wikipediaboxTitle": "Wikipedia",
|
||||||
"failed":"Loading the wikipedia entry failed",
|
"failed":"Loading the wikipedia entry failed",
|
||||||
"loading": "Loading Wikipedia...",
|
"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": {
|
"favourite": {
|
||||||
|
|
|
@ -752,6 +752,24 @@
|
||||||
"tagRenderings": {
|
"tagRenderings": {
|
||||||
"simple etymology": {
|
"simple etymology": {
|
||||||
"render": "Named after {name: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": {
|
"tagRenderings": {
|
||||||
"simple etymology": {
|
"simple etymology": {
|
||||||
"render": "Vernoemd naar {name: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 {UIEventSource} from "./Logic/UIEventSource";
|
||||||
import AllKnownLayers from "./Customizations/AllKnownLayers";
|
import Wikidata from "./Logic/Web/Wikidata";
|
||||||
import State from "./State";
|
import WikidataSearchBox from "./UI/Wikipedia/WikidataSearchBox";
|
||||||
import {AllKnownLayouts} from "./Customizations/AllKnownLayouts";
|
|
||||||
|
|
||||||
State.state = new State(AllKnownLayouts.allKnownLayouts.get("charging_stations"))
|
new WikidataSearchBox({searchText: new UIEventSource("Brugge")}).AttachTo("maindiv")
|
||||||
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")
|
|
Loading…
Reference in a new issue