Add wikipedia box

This commit is contained in:
pietervdvn 2021-10-02 22:31:16 +02:00
parent 1edf829cad
commit 8b4442c8cc
20 changed files with 401 additions and 149 deletions

View file

@ -3,6 +3,7 @@ import ImageProvider, {ProvidedImage} from "./ImageProvider";
import BaseUIElement from "../../UI/BaseUIElement"; import BaseUIElement from "../../UI/BaseUIElement";
import Svg from "../../Svg"; import Svg from "../../Svg";
import {WikimediaImageProvider} from "./WikimediaImageProvider"; import {WikimediaImageProvider} from "./WikimediaImageProvider";
import Wikidata from "../Web/Wikidata";
export class WikidataImageProvider extends ImageProvider { export class WikidataImageProvider extends ImageProvider {
@ -22,31 +23,18 @@ export class WikidataImageProvider extends ImageProvider {
} }
public async ExtractUrls(key: string, value: string): Promise<Promise<ProvidedImage>[]> { public async ExtractUrls(key: string, value: string): Promise<Promise<ProvidedImage>[]> {
const wikidataUrl = "https://www.wikidata.org/wiki/" const entity = await Wikidata.LoadWikidataEntry(value)
if (value.startsWith(wikidataUrl)) {
value = value.substring(wikidataUrl.length) const allImages : Promise<ProvidedImage>[] = []
}
if(value.startsWith("http")){
// Probably some random link in the image field - we skip it
return undefined
}
if (!value.startsWith("Q")) {
value = "Q" + value
}
const url = "https://www.wikidata.org/wiki/Special:EntityData/" + value + ".json";
const response = await Utils.downloadJson(url)
const entity = response.entities[value];
const commons = entity.sitelinks.commonswiki;
// P18 is the claim 'depicted in this image' // P18 is the claim 'depicted in this image'
const image = entity.claims.P18?.[0]?.mainsnak?.datavalue?.value; for (const img of Array.from(entity.claims.get("P18") ?? [])) {
const allImages = [] const promises = await WikimediaImageProvider.singleton.ExtractUrls(undefined, img)
if (image !== undefined) {
// We found a 'File://'
const promises = await WikimediaImageProvider.singleton.ExtractUrls(key, image)
allImages.push(...promises) allImages.push(...promises)
} }
const commons =entity.wikisites.get("commons")
if (commons !== undefined) { if (commons !== undefined) {
const promises = await WikimediaImageProvider.singleton.ExtractUrls(commons, image) const promises = await WikimediaImageProvider.singleton.ExtractUrls(undefined , commons)
allImages.push(...promises) allImages.push(...promises)
} }
return allImages return allImages

View file

@ -44,7 +44,6 @@ export class WikimediaImageProvider extends ImageProvider {
if (continueParameter !== undefined) { if (continueParameter !== undefined) {
url = `${url}&cmcontinue=${continueParameter}`; url = `${url}&cmcontinue=${continueParameter}`;
} }
console.debug("Loading a wikimedia category: ", url)
const response = await Utils.downloadJson(url) const response = await Utils.downloadJson(url)
const members = response.query?.categorymembers ?? []; const members = response.query?.categorymembers ?? [];
const imageOverview: string[] = members.map(member => member.title); const imageOverview: string[] = members.map(member => member.title);
@ -135,8 +134,7 @@ export class WikimediaImageProvider extends ImageProvider {
} }
public async ExtractUrls(key: string, value: string): Promise<Promise<ProvidedImage>[]> { public async ExtractUrls(key: string, value: string): Promise<Promise<ProvidedImage>[]> {
if(key !== undefined && key !== this.commons_key && !value.startsWith(WikimediaImageProvider.commonsPrefix)){
if(key !== this.commons_key && !value.startsWith(WikimediaImageProvider.commonsPrefix)){
return [] return []
} }

89
Logic/Web/Wikidata.ts Normal file
View file

@ -0,0 +1,89 @@
import {Utils} from "../../Utils";
export interface WikidataResponse {
id: string,
labels: Map<string, string>,
descriptions: Map<string, string>,
claims: Map<string, Set<string>>,
wikisites: Map<string, string>
commons: string
}
/**
* Utility functions around wikidata
*/
export default class Wikidata {
private static ParseResponse(entity: any): WikidataResponse {
const labels = new Map<string, string>()
for (const labelName in entity.labels) {
// The labelname is the language code
labels.set(labelName, entity.labels[labelName].value)
}
const descr = new Map<string, string>()
for (const labelName in entity.descriptions) {
// The labelname is the language code
descr.set(labelName, entity.descriptions[labelName].value)
}
const sitelinks = new Map<string, string>();
for (const labelName in entity.sitelinks) {
// labelName is `${language}wiki`
const language = labelName.substring(0, labelName.length - 4)
const title = entity.sitelinks[labelName].title
sitelinks.set(language, title)
}
const commons = sitelinks.get("commons")
sitelinks.delete("commons")
const claims = new Map<string, Set<string>>();
for (const claimId of entity.claims) {
const claimsList: any[] = entity.claims[claimId]
const values = new Set<string>()
for (const claim of claimsList) {
const value = claim.mainsnak.datavalue.value;
values.add(value)
}
claims.set(claimId, values);
}
return {
claims: claims,
descriptions: descr,
id: entity.id,
labels: labels,
wikisites: sitelinks,
commons: commons
}
}
/**
* Loads a wikidata page
* @returns the entity of the given value
*/
public static async LoadWikidataEntry(value: string | number): Promise<WikidataResponse> {
const wikidataUrl = "https://www.wikidata.org/wiki/"
if (typeof value === "number") {
value = "Q" + value
}
if (value.startsWith(wikidataUrl)) {
value = value.substring(wikidataUrl.length)
}
if (value.startsWith("http")) {
// Probably some random link in the image field - we skip it
return undefined
}
if (!value.startsWith("Q")) {
value = "Q" + value
}
const url = "https://www.wikidata.org/wiki/Special:EntityData/" + value + ".json";
const response = await Utils.downloadJson(url)
return Wikidata.ParseResponse(response.entities[value]);
}
}

View file

@ -2,7 +2,8 @@
* Some usefull utility functions around the wikipedia API * Some usefull utility functions around the wikipedia API
*/ */
import {Utils} from "../../Utils"; import {Utils} from "../../Utils";
import WikipediaBox from "../../UI/WikipediaBox"; import {UIEventSource} from "../UIEventSource";
import Wikidata from "./Wikidata";
export default class Wikipedia { export default class Wikipedia {
@ -14,22 +15,36 @@ export default class Wikipedia {
private static readonly classesToRemove = [ private static readonly classesToRemove = [
"shortdescription", "shortdescription",
"sidebar", "sidebar",
"infobox", "infobox","infobox_v2",
"noprint",
"ambox",
"mw-editsection", "mw-editsection",
"mw-selflink",
"hatnote" // Often redirects "hatnote" // Often redirects
] ]
public static async GetArticle(options: { private static readonly _cache = new Map<string, UIEventSource<{ success: string } | { error: any }>>()
public static GetArticle(options: {
pageName: string, pageName: string,
language?: "en" | string, language?: "en" | string}): UIEventSource<{ success: string } | { error: any }>{
section?: number, const key = (options.language ?? "en")+":"+options.pageName
const cached = Wikipedia._cache.get(key)
if(cached !== undefined){
return cached
}
const v = UIEventSource.FromPromiseWithErr(Wikipedia.GetArticleAsync(options))
Wikipedia._cache.set(key, v)
return v;
}
public static async GetArticleAsync(options: {
pageName: string,
language?: "en" | string
}): Promise<string> { }): Promise<string> {
let section = "" const language = options.language ?? "en"
if (options.section !== undefined) { const url = `https://${language}.wikipedia.org/w/api.php?action=parse&format=json&origin=*&prop=text&page=` + options.pageName
section = "&section=" + options.section
}
const url = `https://${options.language ?? "en"}.wikipedia.org/w/api.php?action=parse${section}&format=json&origin=*&prop=text&page=` + options.pageName
const response = await Utils.downloadJson(url) const response = await Utils.downloadJson(url)
const html = response["parse"]["text"]["*"]; const html = response["parse"]["text"]["*"];
@ -43,7 +58,19 @@ export default class Wikipedia {
toRemoveElement.parentElement?.removeChild(toRemoveElement) toRemoveElement.parentElement?.removeChild(toRemoveElement)
} }
} }
return content.innerHTML;
const links = Array.from(content.getElementsByTagName("a"))
console.log("Links are", links)
// Rewrite relative links to absolute links + open them in a new tab
links.filter(link => link.getAttribute("href")?.startsWith("/") ?? false).
forEach(link => {
link.target = '_blank'
// note: link.getAttribute("href") gets the textual value, link.href is the rewritten version which'll contain the host for relative paths
link.href = `https://${language}.wikipedia.org${link.getAttribute("href")}`;
})
return content.innerHTML
} }
} }

View file

@ -9,9 +9,9 @@ export default class Loading extends Combine {
const t = Translations.T(msg ) ?? Translations.t.general.loading.Clone(); const t = Translations.T(msg ) ?? Translations.t.general.loading.Clone();
t.SetClass("pl-2") t.SetClass("pl-2")
super([ super([
Svg.loading_svg().SetClass("animate-spin").SetStyle("width: 1.5rem; height: 1.5rem; margin-bottom: 4px;"), Svg.loading_svg().SetClass("animate-spin").SetStyle("width: 1.5rem; height: 1.5rem;"),
t t
]) ])
this.SetClass("flex m-1") this.SetClass("flex p-1")
} }
} }

View file

@ -225,7 +225,6 @@ export default class ShowDataLayer {
popup.setContent(`<div style='height: 65vh' id='${id}'>Popup for ${feature.properties.id} ${feature.geometry.type}</div>`) popup.setContent(`<div style='height: 65vh' id='${id}'>Popup for ${feature.properties.id} ${feature.geometry.type}</div>`)
leafletLayer.on("popupopen", () => { leafletLayer.on("popupopen", () => {
console.trace(`Opening the popup for ${feature.properties.id} ${feature.geometry.type}`)
if (infobox === undefined) { if (infobox === undefined) {
const tags = State.state.allElements.getEventSourceById(feature.properties.id); const tags = State.state.allElements.getEventSourceById(feature.properties.id);
infobox = new FeatureInfoBox(tags, layer); infobox = new FeatureInfoBox(tags, layer);
@ -239,7 +238,9 @@ export default class ShowDataLayer {
} }
infobox.AttachTo(id) infobox.AttachTo(id)
infobox.Activate(); infobox.Activate();
State.state.selectedElement.setData(feature) if (State.state?.selectedElement?.data?.properties?.id !== feature.properties.id) {
State.state.selectedElement.setData(feature)
}
}); });

View file

@ -26,6 +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";
export interface SpecialVisualization { export interface SpecialVisualization {
funcName: string, funcName: string,
@ -84,6 +85,20 @@ export default class SpecialVisualizations {
return new ImageUploadFlow(tags, args[0]) return new ImageUploadFlow(tags, args[0])
} }
}, },
{
funcName: "wikipedia",
docs: "A box showing the corresponding wikipedia article - based on the wikidata tag",
args: [
{
name: "keyToShowWikipediaFor",
doc: "Use the wikidata entry from this key to show the wikipedia article for",
defaultValue: "wikidata"
}
],
example: "`{wikipedia()}` is a basic example, `{wikipedia(name:etymology:wikidata)}` to show the wikipedia page of whom the feature was named after. Also remember that these can be styled, e.g. `{wikipedia():max-height: 10rem}` to limit the height",
constr: (_, tagsSource, args) =>
new WikipediaBox( tagsSource.map(tags => tags[args[0]]))
},
{ {
funcName: "minimap", funcName: "minimap",
docs: "A small map showing the selected feature. Note that no styling is applied, wrap this in a div", docs: "A small map showing the selected feature. Note that no styling is applied, wrap this in a div",
@ -153,10 +168,10 @@ export default class SpecialVisualizations {
} }
}) })
new ShowDataMultiLayer( new ShowDataMultiLayer(
{ {
leafletMap: minimap["leafletMap"], leafletMap: minimap["leafletMap"],
enablePopups : false, enablePopups: false,
zoomToFeatures: true, zoomToFeatures: true,
layers: State.state.filteredLayers, layers: State.state.filteredLayers,
features: new StaticFeatureSource(featuresToShow, true) features: new StaticFeatureSource(featuresToShow, true)
@ -351,17 +366,17 @@ export default class SpecialVisualizations {
const key = args [0] const key = args [0]
return new VariableUiElement( return new VariableUiElement(
tagSource.map(tags => tags[key]).map(value => { tagSource.map(tags => tags[key]).map(value => {
if (value === undefined) { if (value === undefined) {
return undefined return undefined
} }
const allUnits = [].concat(...state.layoutToUse.layers.map(lyr => lyr.units)) const allUnits = [].concat(...state.layoutToUse.layers.map(lyr => lyr.units))
const unit = allUnits.filter(unit => unit.isApplicableToKey(key))[0] const unit = allUnits.filter(unit => unit.isApplicableToKey(key))[0]
if (unit === undefined) { if (unit === undefined) {
return value; return value;
} }
return unit.asHumanLongValue(value); return unit.asHumanLongValue(value);
}) })
) )
} }
}, },
@ -411,8 +426,8 @@ There are also some technicalities in your theme to keep in mind:
} }
return kv return kv
}) })
const rewrittenTags : UIEventSource<Tag[]> = tagSource.map(tags => { const rewrittenTags: UIEventSource<Tag[]> = tagSource.map(tags => {
const newTags : Tag [] = [] const newTags: Tag [] = []
for (const [key, value] of tgsSpec) { for (const [key, value] of tgsSpec) {
if (value.startsWith('$')) { if (value.startsWith('$')) {
const origKey = value.substring(1) const origKey = value.substring(1)
@ -446,9 +461,9 @@ There are also some technicalities in your theme to keep in mind:
[ [
new Title(viz.funcName, 3), new Title(viz.funcName, 3),
viz.docs, viz.docs,
new Table(["name", "default", "description"], viz.args.length > 0 ? new Table(["name", "default", "description"],
viz.args.map(arg => [arg.name, arg.defaultValue ?? "undefined", arg.doc]) viz.args.map(arg => [arg.name, arg.defaultValue ?? "undefined", arg.doc])
), ) : undefined,
new Title("Example usage", 4), new Title("Example usage", 4),
new FixedUiElement( new FixedUiElement(
viz.example ?? "`{" + viz.funcName + "(" + viz.args.map(arg => arg.defaultValue).join(",") + ")}`" viz.example ?? "`{" + viz.funcName + "(" + viz.args.map(arg => arg.defaultValue).join(",") + ")}`"

View file

@ -8,46 +8,109 @@ import BaseUIElement from "./BaseUIElement";
import Title from "./Base/Title"; import Title from "./Base/Title";
import Translations from "./i18n/Translations"; import Translations from "./i18n/Translations";
import Svg from "../Svg"; import Svg from "../Svg";
import Wikidata from "../Logic/Web/Wikidata";
import Locale from "./i18n/Locale";
export default class WikipediaBox extends Combine{ export default class WikipediaBox extends Combine {
constructor(options: { private static async ExtractWikiPages(wikidata): Promise<Map<string, string>> {
pagename: string, return (await Wikidata.LoadWikidataEntry(wikidata)).wikisites
language: string
}) {
const htmlContent = UIEventSource.FromPromiseWithErr(Wikipedia.GetArticle({
pageName: options.pagename,
language: options.language,
removeInfoBoxes: true
}))
const contents : UIEventSource<string | BaseUIElement> = htmlContent.map(htmlContent => {
if(htmlContent === undefined){
// Still loading
return new Loading("Loading wikipedia page").SetClass("p-4")
}
if(htmlContent["success"] !== undefined){
return new FixedUiElement(htmlContent["success"]).SetClass("wikipedia-article")
}
if(htmlContent["error"]){
return new FixedUiElement(htmlContent["error"]).SetClass("alert p-4")
}
return undefined
})
const scrollable = new Combine([new VariableUiElement(contents).SetClass("block pl-6 pt-2")])
.SetClass("block overflow-auto normal-background rounded-lg")
super([
new Combine([Svg.wikipedia_svg().SetStyle("width: 1.5rem").SetClass("mr-3"),
new Title(Translations.t.general.wikipedia.wikipediaboxTitle, 2)]).SetClass("flex"),
scrollable])
this
.SetClass("block rounded-xl subtle-background m-1 p-2 flex flex-col")
} }
private static _cache = new Map()
constructor(wikidataId: string | UIEventSource<string>) {
const wp = Translations.t.general.wikipedia;
if(typeof wikidataId === "string"){
wikidataId = new UIEventSource(wikidataId)
}
const knownPages = new UIEventSource<{success:Map<string, string>}|{error:any}>(undefined)
wikidataId.addCallbackAndRunD(wikidataId => {
WikipediaBox.ExtractWikiPages(wikidataId).then(pages => {
knownPages.setData({success:pages})
}).catch(err=> {
knownPages.setData({error: err})
})
})
const cachedPages = new Map<string, BaseUIElement>()
const contents = new VariableUiElement(
knownPages.map(pages => {
if (pages === undefined) {
return new Loading(wp.loading.Clone())
}
if (pages["error"] !== undefined) {
return wp.failed.Clone().SetClass("alert p-4")
}
const dict: Map<string, string> = pages["success"]
const preferredLanguage = [Locale.language.data, "en", Array.from(dict.keys())[0]]
let language
let pagetitle;
let i = 0
do {
language = preferredLanguage[i]
pagetitle = dict.get(language)
i++;
if(i >= preferredLanguage.length){
return wp.noWikipediaPage.Clone()
}
} while (pagetitle === undefined)
if(cachedPages.has(language)){
return cachedPages.get(language)
}
const page = WikipediaBox.createContents(pagetitle, language);
cachedPages.set(language, page)
return page
}, [Locale.language])
).SetClass("overflow-auto normal-background rounded-lg")
super([
new Combine([Svg.wikipedia_ui().SetStyle("width: 1.5rem").SetClass("mr-3"),
new Title(Translations.t.general.wikipedia.wikipediaboxTitle.Clone(), 2)]).SetClass("flex"),
contents])
this
.SetClass("block rounded-xl subtle-background m-1 p-2 flex flex-col")
}
/**
* Returns the actual content in a scrollable way
* @param pagename
* @param language
* @private
*/
private static createContents(pagename: string, language: string): BaseUIElement {
const htmlContent = Wikipedia.GetArticle({
pageName: pagename,
language: language
})
const wp = Translations.t.general.wikipedia
const contents: UIEventSource<string | BaseUIElement> = htmlContent.map(htmlContent => {
if (htmlContent === undefined) {
// Still loading
return new Loading(wp.loading.Clone())
}
if (htmlContent["success"] !== undefined) {
return new FixedUiElement(htmlContent["success"]).SetClass("wikipedia-article")
}
if (htmlContent["error"]) {
return wp.failed.Clone().SetClass("alert p-4")
}
return undefined
})
return new Combine([new VariableUiElement(contents).SetClass("block pl-6 pt-2")])
.SetClass("block")
}
} }

View file

@ -392,7 +392,8 @@
} }
], ],
"id": "Surface area" "id": "Surface area"
} },
"wikipedia"
], ],
"wayHandling": 2, "wayHandling": 2,
"icon": { "icon": {

View file

@ -122,7 +122,8 @@
}, },
"id": "Payment methods" "id": "Payment methods"
}, },
"wheelchair-access" "wheelchair-access",
"wikipedia"
], ],
"wayHandling": 1, "wayHandling": 1,
"icon": { "icon": {

View file

@ -5,12 +5,17 @@
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
fill="none" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
viewBox="0 0 25 25" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
viewBox="0 0 24.022156 24.021992"
version="1.1" version="1.1"
id="svg6"> id="svg9"
sodipodi:docname="loading.svg"
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
width="24.022156"
height="24.021992">
<metadata <metadata
id="metadata12"> id="metadata13">
<rdf:RDF> <rdf:RDF>
<cc:Work <cc:Work
rdf:about=""> rdf:about="">
@ -21,18 +26,53 @@
</cc:Work> </cc:Work>
</rdf:RDF> </rdf:RDF>
</metadata> </metadata>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1043"
id="namedview11"
showgrid="false"
inkscape:zoom="19.666667"
inkscape:cx="-1.7824593"
inkscape:cy="7.7694192"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg9" />
<defs <defs
id="defs10" /> id="defs4">
<circle <style
class="opacity-25" id="style2">.cls-1{fill:#000;}</style>
cx="12.529661" </defs>
cy="12.529661"
r="10.441384"
id="circle2"
style="stroke:#000000;stroke-width:4.17655373;stroke-opacity:0.33976835" />
<path <path
style="fill:currentColor;stroke-width:1.04413843" style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.26200151;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.26635515"
class="opacity-75" id="path821"
d="M 4.1765537,12.529661 A 8.3531073,8.3531073 0 0 1 12.529661,4.1765537 V 0 C 5.6101557,0 0,5.6101557 0,12.529661 Z m 2.0882768,5.524536 A 8.3134301,8.3134301 0 0 1 4.1765537,12.529661 H 0 c 0,3.176269 1.1850971,6.081062 3.1324153,8.288371 z" sodipodi:type="arc"
id="path4" /> sodipodi:cx="12.010992"
sodipodi:cy="12.010992"
sodipodi:rx="10.379992"
sodipodi:ry="10.379992"
sodipodi:start="0"
sodipodi:end="6.2828013"
sodipodi:open="true"
d="M 22.390984,12.010992 A 10.379992,10.379992 0 0 1 12.011989,22.390984 10.379992,10.379992 0 0 1 1.6310007,12.012985 10.379992,10.379992 0 0 1 12.008003,1.6310009 10.379992,10.379992 0 0 1 22.390983,12.007006" />
<path
d="m 22.390984,12.010992 a 10.379992,10.379992 0 0 1 -3.26001,7.55315 10.379992,10.379992 0 0 1 -7.732307,2.808765"
sodipodi:open="true"
sodipodi:end="1.6298215"
sodipodi:start="0"
sodipodi:ry="10.379992"
sodipodi:rx="10.379992"
sodipodi:cy="12.010992"
sodipodi:cx="12.010992"
sodipodi:type="arc"
id="path838"
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.26200151;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View file

@ -2,6 +2,9 @@
"images": { "images": {
"render": "{image_carousel()}{image_upload()}" "render": "{image_carousel()}{image_upload()}"
}, },
"wikipedia": {
"render": "{wikipedia():max-height:25rem}"
},
"reviews": { "reviews": {
"render": "{reviews()}" "render": "{reviews()}"
}, },

View file

@ -812,10 +812,6 @@ video {
margin: 0px; margin: 0px;
} }
.m-4 {
margin: 1rem;
}
.m-2 { .m-2 {
margin: 0.5rem; margin: 0.5rem;
} }
@ -824,8 +820,8 @@ video {
margin: 0.75rem; margin: 0.75rem;
} }
.m-6 { .m-4 {
margin: 1.5rem; margin: 1rem;
} }
.my-2 { .my-2 {
@ -852,8 +848,8 @@ video {
margin-left: 0.75rem; margin-left: 0.75rem;
} }
.ml-2 { .mr-3 {
margin-left: 0.5rem; margin-right: 0.75rem;
} }
.mb-2 { .mb-2 {
@ -892,6 +888,10 @@ video {
margin-top: 0px; margin-top: 0px;
} }
.ml-2 {
margin-left: 0.5rem;
}
.mt-4 { .mt-4 {
margin-top: 1rem; margin-top: 1rem;
} }
@ -912,18 +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;
} }
.ml-4 {
margin-left: 1rem;
}
.box-border { .box-border {
box-sizing: border-box; box-sizing: border-box;
} }
@ -1794,8 +1786,6 @@ svg, img {
box-sizing: content-box; box-sizing: content-box;
width: 100%; width: 100%;
height: 100%; height: 100%;
display: unset;
vertical-align: unset;
} }
.mapcontrol svg path { .mapcontrol svg path {

View file

@ -1,7 +1,7 @@
/* This stylesheet reimplements a few classes from wikipedia to show their articles prettily */ /* This stylesheet reimplements a few classes from wikipedia to show their articles prettily */
.wikipedia-article { .wikipedia-article {
font-family: sans-serif; font-family: sans-serif !important;
} }
.wikipedia-article .tright { .wikipedia-article .tright {
@ -9,6 +9,12 @@
clear: right; clear: right;
} }
.wikipedia-article svg, img {
width: unset;
height: unset;
display: unset;
}
.wikipedia-article .thumb { .wikipedia-article .thumb {
background: var(--subtle-detail-color); background: var(--subtle-detail-color);
margin: 1rem; margin: 1rem;
@ -17,15 +23,17 @@
border-radius: 0.5rem; border-radius: 0.5rem;
} }
.wikipedia-article a { .wikipedia-article a:hover a:focus {
color: #0645ad; text-decoration: underline !important;
background: none;
} }
.wikipedia-article a:hover a:focus { .wikipedia-article a {
text-decoration: underline; color: #0645ad !important;
background: none !important;
text-decoration: none;
} }
.wikipedia-article p { .wikipedia-article p {
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
} }

View file

@ -91,8 +91,6 @@ svg, img {
box-sizing: content-box; box-sizing: content-box;
width: 100%; width: 100%;
height: 100%; height: 100%;
display: unset;
vertical-align: unset;
} }
.mapcontrol svg path { .mapcontrol svg path {

View file

@ -14,6 +14,7 @@
<link href="vendor/MarkerCluster.css" rel="stylesheet"/> <link href="vendor/MarkerCluster.css" rel="stylesheet"/>
<link href="vendor/MarkerCluster.Default.css" rel="stylesheet"/> <link href="vendor/MarkerCluster.Default.css" rel="stylesheet"/>
<link href="./css/index-tailwind-output.css" rel="stylesheet" /> <link href="./css/index-tailwind-output.css" rel="stylesheet" />
<link href="./css/wikipedia.css" rel="stylesheet" />
<meta content="website" property="og:type"> <meta content="website" property="og:type">
<!-- THEME-SPECIFIC --> <!-- THEME-SPECIFIC -->

View file

@ -218,7 +218,10 @@
"error_loading": "Could not load the histogram" "error_loading": "Could not load the histogram"
}, },
"wikipedia": { "wikipedia": {
"wikipediaboxTitle": "Wikipedia" "wikipediaboxTitle": "Wikipedia",
"failed":"Loading the wikipedia entry failed",
"loading": "Loading Wikipedia...",
"noWikipediaPage": "This wikidata item has no corresponding wikipedia page yet."
} }
}, },
"favourite": { "favourite": {

View file

@ -743,6 +743,22 @@
"description": "On this map, publicly accessible drinking water spots are shown and can be easily added", "description": "On this map, publicly accessible drinking water spots are shown and can be easily added",
"title": "Drinking Water" "title": "Drinking Water"
}, },
"etymology": {
"description": "On this map, you can see what an object is named after. The streets, buildings, ... come from OpenStreetMap which got linked with Wikidata. The information comes from Wpikipedia.",
"layers": {
"0": {
"description": "All objects which have an etymology known",
"name": "Has etymolgy",
"tagRenderings": {
"simple etymology": {
"render": "Named after {name:etymology}"
}
}
}
},
"shortDescription": "What is the origin of a toponym?",
"title": "Open Etymology Map"
},
"facadegardens": { "facadegardens": {
"description": "<a href='https://nl.wikipedia.org/wiki/Geveltuin' target=_blank>Facade gardens</a>, green facades and trees in the city not only bring peace and quiet, but also a more beautiful city, greater biodiversity, a cooling effect and better air quality. <br/> Klimaan VZW and Mechelen Klimaatneutraal want to map existing and new facade gardens as an example for people who want to build their own garden or for city walkers who love nature.<br/>More info about the project at <a href='https://klimaan.be/' target=_blank>klimaan.be</a>.", "description": "<a href='https://nl.wikipedia.org/wiki/Geveltuin' target=_blank>Facade gardens</a>, green facades and trees in the city not only bring peace and quiet, but also a more beautiful city, greater biodiversity, a cooling effect and better air quality. <br/> Klimaan VZW and Mechelen Klimaatneutraal want to map existing and new facade gardens as an example for people who want to build their own garden or for city walkers who love nature.<br/>More info about the project at <a href='https://klimaan.be/' target=_blank>klimaan.be</a>.",
"layers": { "layers": {

View file

@ -624,6 +624,22 @@
"description": "Op deze kaart staan publiek toegankelijke drinkwaterpunten en kan je makkelijk een nieuw drinkwaterpunt toevoegen", "description": "Op deze kaart staan publiek toegankelijke drinkwaterpunten en kan je makkelijk een nieuw drinkwaterpunt toevoegen",
"title": "Drinkwaterpunten" "title": "Drinkwaterpunten"
}, },
"etymology": {
"description": "Op deze kaart zie je waar een plaats naar is vernoemd. De straten, gebouwen, ... komen uit OpenStreetMap, waar een link naar Wikidata werd gelegd. De informatie komt uit wikipedia.",
"layers": {
"0": {
"description": "Alle lagen met een gelinkt etymology",
"name": "Heeft etymology info",
"tagRenderings": {
"simple etymology": {
"render": "Vernoemd naar {name:etymology}"
}
}
}
},
"shortDescription": "Wat is de oorsprong van een plaatsnaam?",
"title": "Open Etymology-kaart"
},
"facadegardens": { "facadegardens": {
"description": "Ontharde voortuintjes, groene gevels en bomen ín de stad brengen naast rust ook een mooiere stad, een grotere biodiversiteit, een verkoelend effect en een betere luchtkwaliteit. <br/> Klimaan VZW en 'Mechelen Klimaatneutraal' willen met het project Klim(t)aan je Gevel bestaande en nieuwe geveltuintjes in kaart brengen als voorbeeld voor mensen zelf een tuintje willen aanleggen of voor stadwandelaars die houden van de natuur. <br/>Meer info over het project op <a href='https://klimaan.be/' target=_blank>klimaan.be</a>.", "description": "Ontharde voortuintjes, groene gevels en bomen ín de stad brengen naast rust ook een mooiere stad, een grotere biodiversiteit, een verkoelend effect en een betere luchtkwaliteit. <br/> Klimaan VZW en 'Mechelen Klimaatneutraal' willen met het project Klim(t)aan je Gevel bestaande en nieuwe geveltuintjes in kaart brengen als voorbeeld voor mensen zelf een tuintje willen aanleggen of voor stadwandelaars die houden van de natuur. <br/>Meer info over het project op <a href='https://klimaan.be/' target=_blank>klimaan.be</a>.",
"layers": { "layers": {

16
test.ts
View file

@ -1,14 +1,8 @@
import Wikipedia from "./Logic/Web/Wikipedia"; import Wikidata from "./Logic/Web/Wikidata";
import {FixedUiElement} from "./UI/Base/FixedUiElement";
import WikipediaBox from "./UI/WikipediaBox"; import WikipediaBox from "./UI/WikipediaBox";
import Loading from "./UI/Base/Loading"; import Locale from "./UI/i18n/Locale";
import LanguagePicker from "./UI/LanguagePicker";
new WikipediaBox("Q177").SetStyle("max-height: 25rem")
new WikipediaBox({
pagename: "Poertoren",
language: "nl"
})
.SetStyle("max-height: 20rem;")
.AttachTo("maindiv") .AttachTo("maindiv")
LanguagePicker.CreateLanguagePicker(["en","nl","fr","de"]).AttachTo("extradiv")