forked from MapComplete/MapComplete
		
	
		
			
				
	
	
		
			321 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			321 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
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 { Store, 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"
 | 
						|
import { Paragraph } from "../Base/Paragraph"
 | 
						|
 | 
						|
export interface WikipediaBoxOptions {
 | 
						|
    addHeader: boolean
 | 
						|
    firstParagraphOnly: boolean
 | 
						|
    noImages: boolean
 | 
						|
    currentState?: UIEventSource<"loading" | "loaded" | "error">
 | 
						|
}
 | 
						|
 | 
						|
export default class WikipediaBox extends Combine {
 | 
						|
    constructor(wikidataIds: string[], options?: WikipediaBoxOptions) {
 | 
						|
        const mainContents = []
 | 
						|
        options = options ?? { addHeader: false, firstParagraphOnly: true, noImages: false }
 | 
						|
        const pages = wikidataIds.map((entry) =>
 | 
						|
            WikipediaBox.createLinkedContent(entry.trim(), options)
 | 
						|
        )
 | 
						|
        if (wikidataIds.length == 1) {
 | 
						|
            const page = pages[0]
 | 
						|
            mainContents.push(
 | 
						|
                new Combine([
 | 
						|
                    new Combine([
 | 
						|
                        options.noImages
 | 
						|
                            ? undefined
 | 
						|
                            : Svg.wikipedia_ui()
 | 
						|
                                  .SetStyle("width: 1.5rem")
 | 
						|
                                  .SetClass("inline-block mr-3"),
 | 
						|
                        page.titleElement,
 | 
						|
                    ]).SetClass("flex"),
 | 
						|
                    page.linkElement,
 | 
						|
                ]).SetClass("flex justify-between align-middle")
 | 
						|
            )
 | 
						|
            mainContents.push(page.contents.SetClass("overflow-auto normal-background rounded-lg"))
 | 
						|
        } else if (wikidataIds.length > 1) {
 | 
						|
            const tabbed = new TabbedComponent(
 | 
						|
                pages.map((page) => {
 | 
						|
                    const contents = page.contents
 | 
						|
                        .SetClass("overflow-auto normal-background rounded-lg block")
 | 
						|
                        .SetStyle("max-height: inherit; height: inherit; padding-bottom: 3.3rem")
 | 
						|
                    return {
 | 
						|
                        header: page.titleElement.SetClass("pl-2 pr-2"),
 | 
						|
                        content: new Combine([
 | 
						|
                            page.linkElement
 | 
						|
                                .SetStyle("top: 2rem; right: 2.5rem;")
 | 
						|
                                .SetClass(
 | 
						|
                                    "absolute subtle-background rounded-full p-3 opacity-50 hover:opacity-100 transition-opacity"
 | 
						|
                                ),
 | 
						|
                            contents,
 | 
						|
                        ])
 | 
						|
                            .SetStyle("max-height: inherit; height: inherit")
 | 
						|
                            .SetClass("relative"),
 | 
						|
                    }
 | 
						|
                }),
 | 
						|
                0,
 | 
						|
                {
 | 
						|
                    leftOfHeader: options.noImages
 | 
						|
                        ? undefined
 | 
						|
                        : Svg.wikipedia_svg()
 | 
						|
                              .SetStyle("width: 1.5rem; align-self: center;")
 | 
						|
                              .SetClass("mr-4"),
 | 
						|
                    styleHeader: (header) =>
 | 
						|
                        header.SetClass("subtle-background").SetStyle("height: 3.3rem"),
 | 
						|
                }
 | 
						|
            )
 | 
						|
            tabbed.SetStyle("height: inherit; max-height: inherit; overflow: hidden")
 | 
						|
            mainContents.push(tabbed)
 | 
						|
        }
 | 
						|
 | 
						|
        super(mainContents)
 | 
						|
 | 
						|
        this.SetClass("block rounded-xl subtle-background m-1 p-2 flex flex-col").SetStyle(
 | 
						|
            "max-height: inherit"
 | 
						|
        )
 | 
						|
    }
 | 
						|
 | 
						|
    private static createLinkedContent(
 | 
						|
        entry: string,
 | 
						|
        options: WikipediaBoxOptions
 | 
						|
    ): {
 | 
						|
        titleElement: BaseUIElement
 | 
						|
        contents: BaseUIElement
 | 
						|
        linkElement: BaseUIElement
 | 
						|
    } {
 | 
						|
        if (entry.match("[qQ][0-9]+")) {
 | 
						|
            return WikipediaBox.createWikidatabox(entry, options)
 | 
						|
        } else {
 | 
						|
            return WikipediaBox.createWikipediabox(entry, options)
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Given a '<language>:<article-name>'-string, constructs the wikipedia article
 | 
						|
     */
 | 
						|
    private static createWikipediabox(
 | 
						|
        wikipediaArticle: string,
 | 
						|
        options: WikipediaBoxOptions
 | 
						|
    ): {
 | 
						|
        titleElement: BaseUIElement
 | 
						|
        contents: BaseUIElement
 | 
						|
        linkElement: BaseUIElement
 | 
						|
    } {
 | 
						|
        const wp = Translations.t.general.wikipedia
 | 
						|
 | 
						|
        const article = Wikipedia.extractLanguageAndName(wikipediaArticle)
 | 
						|
        if (article === undefined) {
 | 
						|
            return {
 | 
						|
                titleElement: undefined,
 | 
						|
                contents: wp.noWikipediaPage,
 | 
						|
                linkElement: undefined,
 | 
						|
            }
 | 
						|
        }
 | 
						|
        const wikipedia = new Wikipedia({ language: article.language })
 | 
						|
        const url = wikipedia.getPageUrl(article.pageName)
 | 
						|
        const linkElement = new Link(
 | 
						|
            Svg.pop_out_svg().SetStyle("width: 1.2rem").SetClass("block  "),
 | 
						|
            url,
 | 
						|
            true
 | 
						|
        ).SetClass("flex items-center enable-links")
 | 
						|
 | 
						|
        return {
 | 
						|
            titleElement: new Title(article.pageName, 3),
 | 
						|
            contents: WikipediaBox.createContents(article.pageName, wikipedia, options),
 | 
						|
            linkElement,
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Given a `Q1234`, constructs a wikipedia box (if a wikipedia page is available) or wikidata box as fallback.
 | 
						|
     *
 | 
						|
     */
 | 
						|
    private static createWikidatabox(
 | 
						|
        wikidataId: string,
 | 
						|
        options: WikipediaBoxOptions
 | 
						|
    ): {
 | 
						|
        titleElement: BaseUIElement
 | 
						|
        contents: BaseUIElement
 | 
						|
        linkElement: BaseUIElement
 | 
						|
    } {
 | 
						|
        const wp = Translations.t.general.wikipedia
 | 
						|
 | 
						|
        const wikiLink: Store<
 | 
						|
            | [string, string, WikidataResponse]
 | 
						|
            | "loading"
 | 
						|
            | "failed"
 | 
						|
            | ["no page", WikidataResponse]
 | 
						|
        > = Wikidata.LoadWikidataEntry(wikidataId).map(
 | 
						|
            (maybewikidata) => {
 | 
						|
                if (maybewikidata === undefined) {
 | 
						|
                    return "loading"
 | 
						|
                }
 | 
						|
                if (maybewikidata["error"] !== undefined) {
 | 
						|
                    return "failed"
 | 
						|
                }
 | 
						|
                const wikidata = <WikidataResponse>maybewikidata["success"]
 | 
						|
                if (wikidata === undefined) {
 | 
						|
                    return "failed"
 | 
						|
                }
 | 
						|
                if (wikidata.wikisites.size === 0) {
 | 
						|
                    return ["no page", wikidata]
 | 
						|
                }
 | 
						|
 | 
						|
                const preferredLanguage = [
 | 
						|
                    Locale.language.data,
 | 
						|
                    "en",
 | 
						|
                    Array.from(wikidata.wikisites.keys())[0],
 | 
						|
                ]
 | 
						|
                let language
 | 
						|
                let pagetitle
 | 
						|
                let i = 0
 | 
						|
                do {
 | 
						|
                    language = preferredLanguage[i]
 | 
						|
                    pagetitle = wikidata.wikisites.get(language)
 | 
						|
                    i++
 | 
						|
                } while (pagetitle === undefined)
 | 
						|
                return [pagetitle, language, wikidata]
 | 
						|
            },
 | 
						|
            [Locale.language]
 | 
						|
        )
 | 
						|
 | 
						|
        const contents = new VariableUiElement(
 | 
						|
            wikiLink.map((status) => {
 | 
						|
                if (status === "loading") {
 | 
						|
                    return new Loading(wp.loading.Clone()).SetClass("pl-6 pt-2")
 | 
						|
                }
 | 
						|
 | 
						|
                if (status === "failed") {
 | 
						|
                    return wp.failed.Clone().SetClass("alert p-4")
 | 
						|
                }
 | 
						|
                if (status[0] == "no page") {
 | 
						|
                    const [_, wd] = <[string, WikidataResponse]>status
 | 
						|
                    options.currentState?.setData("loaded")
 | 
						|
                    return new Combine([
 | 
						|
                        WikidataPreviewBox.WikidataResponsePreview(wd),
 | 
						|
                        wp.noWikipediaPage.Clone().SetClass("subtle"),
 | 
						|
                    ]).SetClass("flex flex-col p-4")
 | 
						|
                }
 | 
						|
 | 
						|
                const [pagetitle, language, wd] = <[string, string, WikidataResponse]>status
 | 
						|
                const wikipedia = new Wikipedia({ language })
 | 
						|
                const quickFacts = WikidataPreviewBox.QuickFacts(wd)
 | 
						|
                return WikipediaBox.createContents(pagetitle, wikipedia, {
 | 
						|
                    topBar: quickFacts,
 | 
						|
                    ...options,
 | 
						|
                })
 | 
						|
            })
 | 
						|
        )
 | 
						|
 | 
						|
        const titleElement = new VariableUiElement(
 | 
						|
            wikiLink.map((state) => {
 | 
						|
                if (typeof state !== "string") {
 | 
						|
                    const [pagetitle, _] = state
 | 
						|
                    if (pagetitle === "no page") {
 | 
						|
                        const wd = <WikidataResponse>state[1]
 | 
						|
                        return new Title(Translation.fromMap(wd.labels), 3)
 | 
						|
                    }
 | 
						|
                    return new Title(pagetitle, 3)
 | 
						|
                }
 | 
						|
                return new Link(
 | 
						|
                    new Title(wikidataId, 3),
 | 
						|
                    "https://www.wikidata.org/wiki/" + wikidataId,
 | 
						|
                    true
 | 
						|
                )
 | 
						|
            })
 | 
						|
        )
 | 
						|
 | 
						|
        const linkElement = new VariableUiElement(
 | 
						|
            wikiLink.map((state) => {
 | 
						|
                if (typeof state !== "string") {
 | 
						|
                    const [pagetitle, language] = state
 | 
						|
                    const popout = options.noImages
 | 
						|
                        ? "Source"
 | 
						|
                        : Svg.pop_out_svg().SetStyle("width: 1.2rem").SetClass("block")
 | 
						|
                    if (pagetitle === "no page") {
 | 
						|
                        const wd = <WikidataResponse>state[1]
 | 
						|
                        return new Link(popout, "https://www.wikidata.org/wiki/" + wd.id, true)
 | 
						|
                    }
 | 
						|
 | 
						|
                    const url = `https://${language}.wikipedia.org/wiki/${pagetitle}`
 | 
						|
                    return new Link(popout, url, true)
 | 
						|
                }
 | 
						|
                return undefined
 | 
						|
            })
 | 
						|
        ).SetClass("flex items-center enable-links")
 | 
						|
 | 
						|
        return {
 | 
						|
            contents: contents,
 | 
						|
            linkElement: linkElement,
 | 
						|
            titleElement: titleElement,
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Returns the actual content in a scrollable way for the given wikipedia page
 | 
						|
     */
 | 
						|
    private static createContents(
 | 
						|
        pagename: string,
 | 
						|
        wikipedia: Wikipedia,
 | 
						|
        options: {
 | 
						|
            topBar?: BaseUIElement
 | 
						|
        } & WikipediaBoxOptions
 | 
						|
    ): BaseUIElement {
 | 
						|
        const htmlContent = wikipedia.GetArticle(pagename, options)
 | 
						|
        const wp = Translations.t.general.wikipedia
 | 
						|
        const contents: VariableUiElement = new VariableUiElement(
 | 
						|
            htmlContent.map((htmlContent) => {
 | 
						|
                if (htmlContent === undefined) {
 | 
						|
                    // Still loading
 | 
						|
                    return new Loading(wp.loading.Clone())
 | 
						|
                }
 | 
						|
                if (htmlContent["success"] !== undefined) {
 | 
						|
                    let content: BaseUIElement = new FixedUiElement(htmlContent["success"])
 | 
						|
                    if (options?.addHeader) {
 | 
						|
                        content = new Combine([
 | 
						|
                            new Paragraph(
 | 
						|
                                new Link(wp.fromWikipedia, wikipedia.getPageUrl(pagename), true)
 | 
						|
                            ),
 | 
						|
                            new Paragraph(content),
 | 
						|
                        ])
 | 
						|
                    }
 | 
						|
                    return content.SetClass("wikipedia-article")
 | 
						|
                }
 | 
						|
                if (htmlContent["error"]) {
 | 
						|
                    console.warn("Loading wikipage failed due to", htmlContent["error"])
 | 
						|
                    return wp.failed.Clone().SetClass("alert p-4")
 | 
						|
                }
 | 
						|
 | 
						|
                return undefined
 | 
						|
            })
 | 
						|
        )
 | 
						|
 | 
						|
        htmlContent.addCallbackAndRunD((c) => {
 | 
						|
            if (c["success"] !== undefined) {
 | 
						|
                options.currentState?.setData("loaded")
 | 
						|
            } else if (c["error"] !== undefined) {
 | 
						|
                options.currentState?.setData("error")
 | 
						|
            } else {
 | 
						|
                options.currentState?.setData("loading")
 | 
						|
            }
 | 
						|
        })
 | 
						|
 | 
						|
        return new Combine([
 | 
						|
            options?.topBar?.SetClass("border-2 border-grey rounded-lg m-1 mb-0"),
 | 
						|
            contents.SetClass("block pl-6 pt-2"),
 | 
						|
        ])
 | 
						|
    }
 | 
						|
}
 |