From d8e14927c809e507197f7331bc71b99a1768ff8f Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 21 Apr 2023 16:02:36 +0200 Subject: [PATCH] Refactoring: port wikipedia panel to Svelte --- Logic/Web/Wikidata.ts | 33 +- Logic/Web/Wikipedia.ts | 160 ++++++--- Models/ThemeViewState.ts | 16 +- UI/Base/ScrollableFullScreen.ts | 163 --------- UI/Base/TabbedComponent.ts | 57 ---- UI/Base/TabbedGroup.svelte | 12 + UI/BigComponents/AllDownloads.ts | 33 +- UI/BigComponents/LeftControls.ts | 4 - UI/BigComponents/PlantNetSpeciesSearch.ts | 12 +- UI/DefaultGUI.ts | 15 - UI/Image/ImageUploadFlow.ts | 2 - UI/ImportFlow/MapPreview.ts | 20 -- .../Validators/WikidataValidator.ts | 58 ---- UI/Popup/FeatureInfoBox.ts | 49 --- UI/SpecialVisualizations.ts | 30 +- UI/ThemeViewGUI.svelte | 14 - UI/Wikipedia/WikidataSearchBox.ts | 31 +- UI/Wikipedia/WikipediaArticle.svelte | 46 +++ UI/Wikipedia/WikipediaBox.ts | 321 ------------------ UI/Wikipedia/WikipediaBoxOptions.ts | 7 + UI/Wikipedia/WikipediaPanel.svelte | 56 +++ UI/Wikipedia/WikipediaTitle.svelte | 13 + assets/layers/gps_track/gps_track.json | 2 +- assets/tagRenderings/questions.json | 2 +- assets/themes/uk_addresses/uk_addresses.json | 2 +- langs/en.json | 1 + langs/shared-questions/ca.json | 7 + langs/shared-questions/de.json | 7 + langs/shared-questions/en.json | 7 + langs/shared-questions/fr.json | 7 + langs/shared-questions/nl.json | 7 + test.ts | 15 +- 32 files changed, 362 insertions(+), 847 deletions(-) delete mode 100644 UI/Base/ScrollableFullScreen.ts delete mode 100644 UI/Base/TabbedComponent.ts delete mode 100644 UI/Popup/FeatureInfoBox.ts create mode 100644 UI/Wikipedia/WikipediaArticle.svelte delete mode 100644 UI/Wikipedia/WikipediaBox.ts create mode 100644 UI/Wikipedia/WikipediaBoxOptions.ts create mode 100644 UI/Wikipedia/WikipediaPanel.svelte create mode 100644 UI/Wikipedia/WikipediaTitle.svelte diff --git a/Logic/Web/Wikidata.ts b/Logic/Web/Wikidata.ts index 8818be52f..aed400358 100644 --- a/Logic/Web/Wikidata.ts +++ b/Logic/Web/Wikidata.ts @@ -1,5 +1,5 @@ import { Utils } from "../../Utils" -import { UIEventSource } from "../UIEventSource" +import { Store, UIEventSource } from "../UIEventSource" import * as wds from "wikidata-sdk" export class WikidataResponse { @@ -131,11 +131,10 @@ export default class Wikidata { "Lexeme:", ].map((str) => str.toLowerCase()) - private static readonly _cache = new Map< + private static readonly _storeCache = new Map< string, - UIEventSource<{ success: WikidataResponse } | { error: any }> + Store<{ success: WikidataResponse } | { error: any }> >() - /** * Same as LoadWikidataEntry, but wrapped into a UIEventSource * @param value @@ -143,14 +142,14 @@ export default class Wikidata { */ public static LoadWikidataEntry( value: string | number - ): UIEventSource<{ success: WikidataResponse } | { error: any }> { + ): Store<{ success: WikidataResponse } | { error: any }> { const key = this.ExtractKey(value) - const cached = Wikidata._cache.get(key) - if (cached !== undefined) { + const cached = Wikidata._storeCache.get(key) + if (cached) { return cached } const src = UIEventSource.FromPromiseWithErr(Wikidata.LoadWikidataEntryAsync(key)) - Wikidata._cache.set(key, src) + Wikidata._storeCache.set(key, src) return src } @@ -278,6 +277,9 @@ export default class Wikidata { * * Wikidata.ExtractKey("https://www.wikidata.org/wiki/Lexeme:L614072") // => "L614072" * Wikidata.ExtractKey("http://www.wikidata.org/entity/Q55008046") // => "Q55008046" + * Wikidata.ExtractKey("Q55008046") // => "Q55008046" + * Wikidata.ExtractKey("A55008046") // => undefined + * Wikidata.ExtractKey("Q55008046X") // => undefined */ public static ExtractKey(value: string | number): string { if (typeof value === "number") { @@ -385,11 +387,24 @@ export default class Wikidata { return result.results.bindings } + private static _cache = new Map>() + public static async LoadWikidataEntryAsync(value: string | number): Promise { + const key = "" + value + const cached = Wikidata._cache.get(key) + if (cached) { + return cached + } + const uncached = Wikidata.LoadWikidataEntryUncachedAsync(value) + Wikidata._cache.set(key, uncached) + return uncached + } /** * Loads a wikidata page * @returns the entity of the given value */ - public static async LoadWikidataEntryAsync(value: string | number): Promise { + private static async LoadWikidataEntryUncachedAsync( + value: string | number + ): Promise { const id = Wikidata.ExtractKey(value) if (id === undefined) { console.warn("Could not extract a wikidata entry from", value) diff --git a/Logic/Web/Wikipedia.ts b/Logic/Web/Wikipedia.ts index 00bc1b798..9d8b6bb09 100644 --- a/Logic/Web/Wikipedia.ts +++ b/Logic/Web/Wikipedia.ts @@ -1,9 +1,17 @@ -/** - * Some usefull utility functions around the wikipedia API - */ import { Utils } from "../../Utils" -import { UIEventSource } from "../UIEventSource" -import { WikipediaBoxOptions } from "../../UI/Wikipedia/WikipediaBox" +import Wikidata, { WikidataResponse } from "./Wikidata" +import { Store, UIEventSource } from "../UIEventSource" + +export interface FullWikipediaDetails { + articleUrl?: string + language?: string + pagename?: string + fullArticle?: string + firstParagraph?: string + restOfArticle?: string + wikidata?: WikidataResponse + title?: string +} export default class Wikipedia { /** @@ -26,11 +34,8 @@ export default class Wikipedia { private static readonly idsToRemove = ["sjabloon_zie"] - private static readonly _cache = new Map< - string, - UIEventSource<{ success: string } | { error: any }> - >() - + private static readonly _cache = new Map>() + private static _fullDetailsCache = new Map>() public readonly backend: string constructor(options?: { language?: "en" | string } | { backend?: string }) { @@ -56,23 +61,81 @@ export default class Wikipedia { } /** - * Extracts the actual pagename; returns undefined if this came from a different wikimedia entry + * Fetch all useful information for the given entity. * - * new Wikipedia({backend: "https://wiki.openstreetmap.org"}).extractPageName("https://wiki.openstreetmap.org/wiki/NL:Speelbos") // => "NL:Speelbos" - * new Wikipedia().extractPageName("https://wiki.openstreetmap.org/wiki/NL:Speelbos") // => undefined */ - public extractPageName(input: string): string | undefined { - if (!input.startsWith(this.backend)) { - return undefined + public static fetchArticleAndWikidata( + wikidataOrPageId: string, + preferedLanguage: string + ): Store { + const cachekey = preferedLanguage + wikidataOrPageId + const cached = Wikipedia._fullDetailsCache.get(cachekey) + if (cached) { + return cached } - input = input.substring(this.backend.length) + console.log("Constructing store for", cachekey) + const store = new UIEventSource({}, cachekey) + Wikipedia._fullDetailsCache.set(cachekey, store) - const matched = input.match("/?wiki/(.+)") - if (matched === undefined || matched === null) { - return undefined + // Are we dealing with a wikidata item? + const wikidataId = Wikidata.ExtractKey(wikidataOrPageId) + if (!wikidataId) { + // We are dealing with a wikipedia identifier, e.g. 'NL:articlename', 'https://nl.wikipedia.org/wiki/article', ... + const { language, pageName } = Wikipedia.extractLanguageAndName(wikidataOrPageId) + store.data.articleUrl = new Wikipedia({ language }).getPageUrl(pageName) + store.data.language = language + store.data.pagename = pageName + store.data.title = pageName + } else { + // Jup, this is a wikidata item + // Lets fetch the wikidata + store.data.title = wikidataId + Wikidata.LoadWikidataEntryAsync(wikidataId).then((wikidata) => { + store.data.wikidata = wikidata + store.ping() + // With the wikidata, we can search for the appropriate wikipedia page + const preferredLanguage = [ + preferedLanguage, + "en", + Array.from(wikidata.wikisites.keys())[0], + ] + + for (const language of preferredLanguage) { + const pagetitle = wikidata.wikisites.get(language) + if (pagetitle) { + store.data.articleUrl = new Wikipedia({ language }).getPageUrl(pagetitle) + store.data.pagename = pagetitle + store.data.language = language + store.data.title = pagetitle + store.ping() + break + } + } + }) } - const [_, pageName] = matched - return pageName + + // Now that the pageURL has been setup, we can focus on downloading the actual article + // We setup a listener. As soon as the article-URL is know, we'll fetch the actual page + // This url can either be set by the Wikidata-response or directly if we are dealing with a wikipedia-url + store.addCallbackAndRun((data) => { + if (data.language === undefined || data.pagename === undefined) { + return + } + const wikipedia = new Wikipedia({ language: data.language }) + wikipedia.GetArticleHtml(data.pagename).then((article) => { + data.fullArticle = article + const content = document.createElement("div") + content.innerHTML = article + const firstParagraph = content.getElementsByTagName("p").item(0) + data.firstParagraph = firstParagraph.innerHTML + content.removeChild(firstParagraph) + data.restOfArticle = content.innerHTML + store.ping() + }) + return true // unregister + }) + + return store } private static getBackendUrl( @@ -90,18 +153,24 @@ export default class Wikipedia { return backend } - public GetArticle( - pageName: string, - options: WikipediaBoxOptions - ): UIEventSource<{ success: string } | { error: any }> { - const key = this.backend + ":" + pageName + ":" + (options.firstParagraphOnly ?? false) - const cached = Wikipedia._cache.get(key) - if (cached !== undefined) { - return cached + /** + * Extracts the actual pagename; returns undefined if this came from a different wikimedia entry + * + * new Wikipedia({backend: "https://wiki.openstreetmap.org"}).extractPageName("https://wiki.openstreetmap.org/wiki/NL:Speelbos") // => "NL:Speelbos" + * new Wikipedia().extractPageName("https://wiki.openstreetmap.org/wiki/NL:Speelbos") // => undefined + */ + public extractPageName(input: string): string | undefined { + if (!input.startsWith(this.backend)) { + return undefined } - const v = UIEventSource.FromPromiseWithErr(this.GetArticleAsync(pageName, options)) - Wikipedia._cache.set(key, v) - return v + input = input.substring(this.backend.length) + + const matched = input.match("/?wiki/(.+)") + if (matched === undefined || matched === null) { + return undefined + } + const [_, pageName] = matched + return pageName } public getDataUrl(pageName: string): string { @@ -172,12 +241,23 @@ export default class Wikipedia { }) } - public async GetArticleAsync( - pageName: string, - options: { - firstParagraphOnly?: false | boolean + /** + * Returns the innerHTML for the given article as string. + * Some cleanup is applied to this. + * + * This method uses a static, local cache, so each article will be retrieved only once via the network + */ + public GetArticleHtml(pageName: string): Promise { + const cacheKey = this.backend + "/" + pageName + if (Wikipedia._cache.has(cacheKey)) { + return Wikipedia._cache.get(cacheKey) } - ): Promise { + const promise = this.GetArticleUncachedAsync(pageName) + Wikipedia._cache.set(cacheKey, promise) + return promise + } + + private async GetArticleUncachedAsync(pageName: string): Promise { const response = await Utils.downloadJson(this.getDataUrl(pageName)) if (response?.parse?.text === undefined) { return undefined @@ -213,10 +293,6 @@ export default class Wikipedia { link.href = `${this.backend}${link.getAttribute("href")}` }) - if (options?.firstParagraphOnly) { - return content.getElementsByTagName("p").item(0).innerHTML - } - return content.innerHTML } } diff --git a/Models/ThemeViewState.ts b/Models/ThemeViewState.ts index 19bf46d9a..b36be04b0 100644 --- a/Models/ThemeViewState.ts +++ b/Models/ThemeViewState.ts @@ -44,6 +44,7 @@ import ChangeGeometryApplicator from "../Logic/FeatureSource/Sources/ChangeGeome import { NewGeometryFromChangesFeatureSource } from "../Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource" import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader" import ShowOverlayRasterLayer from "../UI/Map/ShowOverlayRasterLayer" +import { Utils } from "../Utils" /** * @@ -263,6 +264,10 @@ export default class ThemeViewState implements SpecialVisualizationState { } this.lastClickObject.features.setData([]) }) + + if (this.layout.customCss !== undefined && window.location.pathname.indexOf("theme") >= 0) { + Utils.LoadCustomCss(this.layout.customCss) + } } private initHotkeys() { @@ -371,14 +376,19 @@ export default class ThemeViewState implements SpecialVisualizationState { .get("range") ?.isDisplayed?.syncWith(this.featureSwitches.featureSwitchIsTesting, true) + // The following layers are _not_ indexed; they trigger to much and thus trigger the metatagging + const dontInclude = new Set(["gps_location", "gps_location_history", "gps_track"]) this.layerState.filteredLayers.forEach((flayer) => { - const features: FeatureSource = specialLayers[flayer.layerDef.id] + const id = flayer.layerDef.id + const features: FeatureSource = specialLayers[id] if (features === undefined) { return } - this.featureProperties.trackFeatureSource(features) - this.indexedFeatures.addSource(features) + if (!dontInclude.has(id)) { + this.featureProperties.trackFeatureSource(features) + this.indexedFeatures.addSource(features) + } new ShowDataLayer(this.map, { features, doShowLayer: flayer.isDisplayed, diff --git a/UI/Base/ScrollableFullScreen.ts b/UI/Base/ScrollableFullScreen.ts deleted file mode 100644 index 7cb97f831..000000000 --- a/UI/Base/ScrollableFullScreen.ts +++ /dev/null @@ -1,163 +0,0 @@ -import Svg from "../../Svg" -import Combine from "./Combine" -import { FixedUiElement } from "./FixedUiElement" -import { UIEventSource } from "../../Logic/UIEventSource" -import Hash from "../../Logic/Web/Hash" -import BaseUIElement from "../BaseUIElement" -import Title from "./Title" -import Hotkeys from "./Hotkeys" -import Translations from "../i18n/Translations" - -/** - * - * The scrollableFullScreen is a bit of a peculiar component: - * - It shows a title and some contents, constructed from the respective functions passed into the constructor - * - When the element is 'activated', one clone of title+contents is attached to the fullscreen - * - The element itself will - upon rendering - also show the title and contents (allthough it'll be a different clone) - * - * - */ -export default class ScrollableFullScreen { - private static readonly empty = ScrollableFullScreen.initEmpty() - private static _currentlyOpen: ScrollableFullScreen - public isShown: UIEventSource - private hashToShow: string - private _fullscreencomponent: BaseUIElement - private _resetScrollSignal: UIEventSource = new UIEventSource(undefined) - private _setHash: boolean - - constructor( - title: (options: { mode: string }) => BaseUIElement, - content: (options: { - mode: string - resetScrollSignal: UIEventSource - }) => BaseUIElement, - hashToShow: string, - isShown: UIEventSource = new UIEventSource(false), - options?: { - setHash?: boolean - } - ) { - this.hashToShow = hashToShow - this.isShown = isShown - this._setHash = options?.setHash ?? true - - if ((hashToShow === undefined || hashToShow === "") && this._setHash) { - throw "HashToShow should be defined as it is vital for the 'back' key functionality" - } - - const mobileOptions = { - mode: "mobile", - resetScrollSignal: this._resetScrollSignal, - } - - this._fullscreencomponent = this.BuildComponent( - title(mobileOptions), - content(mobileOptions).SetClass("pb-20") - ) - - const self = this - if (this._setHash) { - Hash.hash.addCallback((h) => { - if (h === undefined) { - isShown.setData(false) - } - }) - } - - isShown.addCallbackD((isShown) => { - if (isShown) { - // We first must set the hash, then activate the panel - // If the order is wrong, this will cause the panel to disactivate again - ScrollableFullScreen._currentlyOpen = self - self.Activate() - } else { - if (self.hashToShow !== undefined) { - Hash.hash.setData(undefined) - } - // Some cleanup... - ScrollableFullScreen.collapse() - } - }) - if (isShown.data) { - ScrollableFullScreen._currentlyOpen = self - this.Activate() - } - } - - private static initEmpty(): FixedUiElement { - Hotkeys.RegisterHotkey( - { nomod: "Escape", onUp: true }, - Translations.t.hotkeyDocumentation.closeSidebar, - ScrollableFullScreen.collapse - ) - - return new FixedUiElement("") - } - public static collapse() { - const fs = document.getElementById("fullscreen") - if (fs !== null) { - ScrollableFullScreen.empty.AttachTo("fullscreen") - fs.classList.add("hidden") - } - - const opened = ScrollableFullScreen._currentlyOpen - if (opened !== undefined) { - opened?.isShown?.setData(false) - } - } - - - /** - * Actually show this in the 'fullscreen'-div - * @constructor - */ - public Activate(): void { - if (this.hashToShow && this.hashToShow !== "" && this._setHash) { - Hash.hash.setData(this.hashToShow) - } - this.isShown.setData(true) - this._fullscreencomponent.AttachTo("fullscreen") - const fs = document.getElementById("fullscreen") - ScrollableFullScreen._currentlyOpen = this - fs?.classList?.remove("hidden") - } - - private BuildComponent(title: BaseUIElement, content: BaseUIElement): BaseUIElement { - const returnToTheMap = new Combine([ - Svg.back_svg().SetClass("block md:hidden w-12 h-12 p-2 svg-foreground"), - Svg.close_svg().SetClass("hidden md:block w-12 h-12 p-3 svg-foreground"), - ]).SetClass("rounded-full p-0 flex-shrink-0 self-center") - - returnToTheMap.onClick(() => { - this.isShown.setData(false) - Hash.hash.setData(undefined) - }) - - title = new Title(title, 2) - title.SetClass( - "text-l sm:text-xl md:text-2xl w-full p-0 max-h-20vh overflow-y-auto self-center" - ) - - const contentWrapper = new Combine([content]).SetClass( - "block p-2 md:pt-4 w-full h-full overflow-y-auto" - ) - - this._resetScrollSignal.addCallback((_) => { - contentWrapper.ScrollToTop() - }) - - return new Combine([ - new Combine([ - new Combine([returnToTheMap, title]).SetClass( - "border-b-1 border-black shadow bg-white flex flex-shrink-0 pt-1 pb-1 md:pt-0 md:pb-0" - ), - contentWrapper, - // We add an ornament which takes around 5em. This is in order to make sure the Web UI doesn't hide - ]).SetClass("flex flex-col h-full relative bg-white"), - ]).SetClass( - "fixed top-0 left-0 right-0 h-screen w-screen md:w-auto md:relative z-above-controls md:rounded-xl overflow-hidden" - ) - } - -} diff --git a/UI/Base/TabbedComponent.ts b/UI/Base/TabbedComponent.ts deleted file mode 100644 index 4e3eff50f..000000000 --- a/UI/Base/TabbedComponent.ts +++ /dev/null @@ -1,57 +0,0 @@ -import Translations from "../i18n/Translations" -import { UIEventSource } from "../../Logic/UIEventSource" -import Combine from "./Combine" -import BaseUIElement from "../BaseUIElement" -import { VariableUiElement } from "./VariableUIElement" - -export class TabbedComponent extends Combine { - /** - * @deprecated - */ - constructor( - elements: { header: BaseUIElement | string; content: BaseUIElement | string }[], - openedTab: UIEventSource | number = 0, - options?: { - leftOfHeader?: BaseUIElement - styleHeader?: (header: BaseUIElement) => void - } - ) { - const openedTabSrc = - typeof openedTab === "number" - ? new UIEventSource(openedTab) - : openedTab ?? new UIEventSource(0) - - const tabs: BaseUIElement[] = [options?.leftOfHeader] - const contentElements: BaseUIElement[] = [] - for (let i = 0; i < elements.length; i++) { - let element = elements[i] - const header = Translations.W(element.header).onClick(() => openedTabSrc.setData(i)) - openedTabSrc.addCallbackAndRun((selected) => { - if (selected >= elements.length) { - selected = 0 - } - if (selected === i) { - header.SetClass("tab-active") - header.RemoveClass("tab-non-active") - } else { - header.SetClass("tab-non-active") - header.RemoveClass("tab-active") - } - }) - const content = Translations.W(element.content) - content.SetClass("relative w-full inline-block") - contentElements.push(content) - const tab = header.SetClass("block tab-single-header") - tabs.push(tab) - } - - const header = new Combine(tabs).SetClass("tabs-header-bar") - if (options?.styleHeader) { - options.styleHeader(header) - } - const actualContent = new VariableUiElement( - openedTabSrc.map((i) => contentElements[i]) - ).SetStyle("max-height: inherit; height: inherit") - super([header, actualContent]) - } -} diff --git a/UI/Base/TabbedGroup.svelte b/UI/Base/TabbedGroup.svelte index 86c7bbab7..9f0d5e169 100644 --- a/UI/Base/TabbedGroup.svelte +++ b/UI/Base/TabbedGroup.svelte @@ -66,3 +66,15 @@ + + diff --git a/UI/BigComponents/AllDownloads.ts b/UI/BigComponents/AllDownloads.ts index 33ea33a49..20c454b7b 100644 --- a/UI/BigComponents/AllDownloads.ts +++ b/UI/BigComponents/AllDownloads.ts @@ -1,62 +1,33 @@ import Combine from "../Base/Combine" -import ScrollableFullScreen from "../Base/ScrollableFullScreen" import Translations from "../i18n/Translations" import { UIEventSource } from "../../Logic/UIEventSource" -import BaseUIElement from "../BaseUIElement" import Toggle from "../Input/Toggle" import { SubtleButton } from "../Base/SubtleButton" import Svg from "../../Svg" import ExportPDF from "../ExportPDF" import FilteredLayer from "../../Models/FilteredLayer" -import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline" import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" import { BBox } from "../../Logic/BBox" -import BaseLayer from "../../Models/BaseLayer" import Loc from "../../Models/Loc" -interface DownloadState { - filteredLayers: UIEventSource - featurePipeline: FeaturePipeline - layoutToUse: LayoutConfig - currentBounds: UIEventSource - backgroundLayer: UIEventSource - locationControl: UIEventSource - featureSwitchExportAsPdf: UIEventSource - featureSwitchEnableExport: UIEventSource -} - -export default class AllDownloads extends ScrollableFullScreen { +export default class AllDownloads extends SubtleButton { constructor( isShown: UIEventSource, state: { filteredLayers: UIEventSource - featurePipeline: FeaturePipeline layoutToUse: LayoutConfig currentBounds: UIEventSource - backgroundLayer: UIEventSource locationControl: UIEventSource featureSwitchExportAsPdf: UIEventSource featureSwitchEnableExport: UIEventSource } ) { - super(AllDownloads.GenTitle, () => AllDownloads.GeneratePanel(state), "downloads", isShown) - } - - private static GenTitle(): BaseUIElement { - return Translations.t.general.download.title - .Clone() - .SetClass("text-2xl break-words font-bold p-2") - } - - private static GeneratePanel(state: DownloadState): BaseUIElement { const isExporting = new UIEventSource(false, "Pdf-is-exporting") const generatePdf = () => { isExporting.setData(true) new ExportPDF({ freeDivId: "belowmap", - background: state.backgroundLayer, location: state.locationControl, - features: state.featurePipeline, layout: state.layoutToUse, }).isRunning.addCallbackAndRun((isRunning) => isExporting.setData(isRunning)) } @@ -78,6 +49,6 @@ export default class AllDownloads extends ScrollableFullScreen { isExporting ) - return new SubtleButton(icon, text) + super(icon, text) } } diff --git a/UI/BigComponents/LeftControls.ts b/UI/BigComponents/LeftControls.ts index 4021c23d8..a0e71a5c4 100644 --- a/UI/BigComponents/LeftControls.ts +++ b/UI/BigComponents/LeftControls.ts @@ -6,8 +6,6 @@ import AllDownloads from "./AllDownloads" import { Store, UIEventSource } from "../../Logic/UIEventSource" import Lazy from "../Base/Lazy" import { VariableUiElement } from "../Base/VariableUIElement" -import FeatureInfoBox from "../Popup/FeatureInfoBox" -import FeaturePipelineState from "../../Logic/State/FeaturePipelineState" import { DefaultGuiState } from "../DefaultGuiState" export default class LeftControls extends Combine { @@ -57,8 +55,6 @@ export default class LeftControls extends Combine { new AllDownloads(guiState.downloadControlIsOpened, state) - - super([currentViewAction]) this.SetClass("flex flex-col") diff --git a/UI/BigComponents/PlantNetSpeciesSearch.ts b/UI/BigComponents/PlantNetSpeciesSearch.ts index 5b133def2..b7e503f6e 100644 --- a/UI/BigComponents/PlantNetSpeciesSearch.ts +++ b/UI/BigComponents/PlantNetSpeciesSearch.ts @@ -7,7 +7,6 @@ import WikidataPreviewBox from "../Wikipedia/WikidataPreviewBox" import { Button } from "../Base/Button" import Combine from "../Base/Combine" import Title from "../Base/Title" -import WikipediaBox from "../Wikipedia/WikipediaBox" import Translations from "../i18n/Translations" import List from "../Base/List" import Svg from "../../Svg" @@ -97,7 +96,7 @@ export default class PlantNetSpeciesSearch extends VariableUiElement { if (wikidataSpecies === undefined) { return plantOverview } - const buttons = new Combine([ + return new Combine([ new Button( new Combine([ Svg.back_svg().SetClass( @@ -120,15 +119,6 @@ export default class PlantNetSpeciesSearch extends VariableUiElement { } ).SetClass("btn"), ]).SetClass("flex justify-between") - - return new Combine([ - new WikipediaBox([wikidataSpecies], { - firstParagraphOnly: false, - noImages: false, - addHeader: false, - }).SetClass("h-96"), - buttons, - ]).SetClass("flex flex-col self-end") }) ) }) diff --git a/UI/DefaultGUI.ts b/UI/DefaultGUI.ts index a5b27c22d..c5d08a8e7 100644 --- a/UI/DefaultGUI.ts +++ b/UI/DefaultGUI.ts @@ -1,15 +1,11 @@ -import { Utils } from "../Utils" import Toggle from "./Input/Toggle" import LeftControls from "./BigComponents/LeftControls" import RightControls from "./BigComponents/RightControls" import CenterMessageBox from "./CenterMessageBox" -import ScrollableFullScreen from "./Base/ScrollableFullScreen" -import Translations from "./i18n/Translations" import { DefaultGuiState } from "./DefaultGuiState" import Combine from "./Base/Combine" import ExtraLinkButton from "./BigComponents/ExtraLinkButton" import GeoLocationHandler from "../Logic/Actors/GeoLocationHandler" -import CopyrightPanel from "./BigComponents/CopyrightPanel" /** * The default MapComplete GUI initializer @@ -25,17 +21,6 @@ export default class DefaultGUI { } public setup() { - this.SetupUIElements() - - if ( - this.state.layoutToUse.customCss !== undefined && - window.location.pathname.indexOf("index") >= 0 - ) { - Utils.LoadCustomCss(this.state.layoutToUse.customCss) - } - } - - private SetupUIElements() { const extraLink = Toggle.If( state.featureSwitchExtraLinkEnabled, () => new ExtraLinkButton(state, state.layoutToUse.extraLink) diff --git a/UI/Image/ImageUploadFlow.ts b/UI/Image/ImageUploadFlow.ts index a2457bf07..6d6c13952 100644 --- a/UI/Image/ImageUploadFlow.ts +++ b/UI/Image/ImageUploadFlow.ts @@ -14,8 +14,6 @@ import { VariableUiElement } from "../Base/VariableUIElement" import Loading from "../Base/Loading" import { LoginToggle } from "../Popup/LoginButton" import Constants from "../../Models/Constants" -import { DefaultGuiState } from "../DefaultGuiState" -import ScrollableFullScreen from "../Base/ScrollableFullScreen" import { SpecialVisualizationState } from "../SpecialVisualization" export class ImageUploadFlow extends Toggle { diff --git a/UI/ImportFlow/MapPreview.ts b/UI/ImportFlow/MapPreview.ts index 929a2ed0f..854486044 100644 --- a/UI/ImportFlow/MapPreview.ts +++ b/UI/ImportFlow/MapPreview.ts @@ -11,33 +11,15 @@ import Loc from "../../Models/Loc" import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource" import Toggle from "../Input/Toggle" import { VariableUiElement } from "../Base/VariableUIElement" -import { FixedUiElement } from "../Base/FixedUiElement" import { FlowStep } from "./FlowStep" -import ScrollableFullScreen from "../Base/ScrollableFullScreen" import Title from "../Base/Title" import CheckBoxes from "../Input/Checkboxes" -import AllTagsPanel from "../Popup/AllTagsPanel.svelte" import { Feature, Point } from "geojson" import DivContainer from "../Base/DivContainer" -import SvelteUIElement from "../Base/SvelteUIElement" import { AvailableRasterLayers, RasterLayerPolygon } from "../../Models/RasterLayers" import { MapLibreAdaptor } from "../Map/MapLibreAdaptor" import ShowDataLayer from "../Map/ShowDataLayer" -class PreviewPanel extends ScrollableFullScreen { - constructor(tags: UIEventSource) { - super( - (_) => new FixedUiElement("Element to import"), - (_) => - new Combine([ - "The tags are:", - new SvelteUIElement(AllTagsPanel, { tags }), - ]).SetClass("flex flex-col"), - "element" - ) - } -} - /** * Shows the data to import on a map, asks for the correct layer to be selected */ @@ -111,7 +93,6 @@ export class MapPreview const currentBounds = new UIEventSource(undefined) const { ui, mapproperties, map } = MapLibreAdaptor.construct() - ui.SetClass("w-full").SetStyle("height: 500px") layerPicker.GetValue().addCallbackAndRunD((layerToShow) => { @@ -119,7 +100,6 @@ export class MapPreview layer: layerToShow, zoomToFeatures: true, features: new StaticFeatureSource(matching), - buildPopup: (tag) => new PreviewPanel(tag), }) }) diff --git a/UI/InputElement/Validators/WikidataValidator.ts b/UI/InputElement/Validators/WikidataValidator.ts index cb031f447..53eac4e70 100644 --- a/UI/InputElement/Validators/WikidataValidator.ts +++ b/UI/InputElement/Validators/WikidataValidator.ts @@ -34,62 +34,4 @@ export default class WikidataValidator extends Validator { } return out } - - public inputHelper(currentValue, inputHelperOptions) { - const args = inputHelperOptions.args ?? [] - const searchKey = args[0] ?? "name" - - const searchFor = ( - (inputHelperOptions.feature?.properties[searchKey]?.toLowerCase() ?? "") - ) - - let searchForValue: UIEventSource = new UIEventSource(searchFor) - const options: any = args[1] - if (searchFor !== undefined && options !== undefined) { - const prefixes = >options["removePrefixes"] ?? [] - const postfixes = >options["removePostfixes"] ?? [] - const defaultValueCandidate = Locale.language.map((lg) => { - const prefixesUnrwapped: RegExp[] = ( - Array.isArray(prefixes) ? prefixes : prefixes[lg] ?? [] - ).map((s) => new RegExp("^" + s, "i")) - const postfixesUnwrapped: RegExp[] = ( - Array.isArray(postfixes) ? postfixes : postfixes[lg] ?? [] - ).map((s) => new RegExp(s + "$", "i")) - let clipped = searchFor - - for (const postfix of postfixesUnwrapped) { - const match = searchFor.match(postfix) - if (match !== null) { - clipped = searchFor.substring(0, searchFor.length - match[0].length) - break - } - } - - for (const prefix of prefixesUnrwapped) { - const match = searchFor.match(prefix) - if (match !== null) { - clipped = searchFor.substring(match[0].length) - break - } - } - return clipped - }) - - defaultValueCandidate.addCallbackAndRun((clipped) => searchForValue.setData(clipped)) - } - - let instanceOf: number[] = Utils.NoNull( - (options?.instanceOf ?? []).map((i) => Wikidata.QIdToNumber(i)) - ) - let notInstanceOf: number[] = Utils.NoNull( - (options?.notInstanceOf ?? []).map((i) => Wikidata.QIdToNumber(i)) - ) - - return new WikidataSearchBox({ - value: currentValue, - searchText: searchForValue, - instanceOf, - notInstanceOf, - }) - } } diff --git a/UI/Popup/FeatureInfoBox.ts b/UI/Popup/FeatureInfoBox.ts deleted file mode 100644 index 8b358059e..000000000 --- a/UI/Popup/FeatureInfoBox.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { UIEventSource } from "../../Logic/UIEventSource" -import Combine from "../Base/Combine" -import ScrollableFullScreen from "../Base/ScrollableFullScreen" -import BaseUIElement from "../BaseUIElement" -import LayerConfig from "../../Models/ThemeConfig/LayerConfig" -import Toggle from "../Input/Toggle" -import FeaturePipelineState from "../../Logic/State/FeaturePipelineState" -import Svg from "../../Svg" -import Translations from "../i18n/Translations" - -export default class FeatureInfoBox extends ScrollableFullScreen { - public constructor( - tags: UIEventSource, - layerConfig: LayerConfig, - state: FeaturePipelineState, - options?: { - hashToShow?: string - isShown?: UIEventSource - setHash?: true | boolean - } - ) { - const showAllQuestions = state.featureSwitchShowAllQuestions.map( - (fsShow) => fsShow || state.showAllQuestionsAtOnce.data, - [state.showAllQuestionsAtOnce] - ) - super( - () => undefined, - () => FeatureInfoBox.GenerateContent(tags, layerConfig), - options?.hashToShow ?? tags.data.id ?? "item", - options?.isShown, - options - ) - - if (layerConfig === undefined) { - throw "Undefined layerconfig" - } - } - - public static GenerateContent(tags: UIEventSource): BaseUIElement { - return new Toggle( - new Combine([ - Svg.delete_icon_svg().SetClass("w-8 h-8"), - Translations.t.delete.isDeleted, - ]).SetClass("flex justify-center font-bold items-center"), - new Combine([]).SetClass("block"), - tags.map((t) => t["_deleted"] == "yes") - ) - } -} diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts index b556136c3..a9d62655b 100644 --- a/UI/SpecialVisualizations.ts +++ b/UI/SpecialVisualizations.ts @@ -19,14 +19,13 @@ import { ConflateButton, ImportPointButton, ImportWayButton } from "./Popup/Impo import TagApplyButton from "./Popup/TagApplyButton" import { CloseNoteButton } from "./Popup/CloseNoteButton" import { MapillaryLinkVis } from "./Popup/MapillaryLinkVis" -import { Stores, UIEventSource } from "../Logic/UIEventSource" +import { Store, Stores, UIEventSource } from "../Logic/UIEventSource" import AllTagsPanel from "./Popup/AllTagsPanel.svelte" import AllImageProviders from "../Logic/ImageProviders/AllImageProviders" import { ImageCarousel } from "./Image/ImageCarousel" import { ImageUploadFlow } from "./Image/ImageUploadFlow" import { VariableUiElement } from "./Base/VariableUIElement" import { Utils } from "../Utils" -import WikipediaBox from "./Wikipedia/WikipediaBox" import Wikidata, { WikidataResponse } from "../Logic/Web/Wikidata" import { Translation } from "./i18n/Translation" import Translations from "./i18n/Translations" @@ -80,6 +79,7 @@ import { OsmId, OsmTags, WayId } from "../Models/OsmFeature" import MoveWizard from "./Popup/MoveWizard" import SplitRoadWizard from "./Popup/SplitRoadWizard" import { ExportAsGpxViz } from "./Popup/ExportAsGpxViz" +import WikipediaPanel from "./Wikipedia/WikipediaPanel.svelte" class NearbyImageVis implements SpecialVisualization { // Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests @@ -628,7 +628,7 @@ export default class SpecialVisualizations { { funcName: "wikipedia", - docs: "A box showing the corresponding wikipedia article - based on the wikidata tag", + docs: "A box showing the corresponding wikipedia article(s) - based on the **wikidata** tag.", args: [ { name: "keyToShowWikipediaFor", @@ -638,23 +638,15 @@ export default class SpecialVisualizations { ], 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) => { + constr: (_, tagsSource, args, feature, layer) => { const keys = args[0].split(";").map((k) => k.trim()) - return new VariableUiElement( - tagsSource - .map((tags) => { - const key = keys.find( - (k) => tags[k] !== undefined && tags[k] !== "" - ) - return tags[key] - }) - .map((wikidata) => { - const wikidatas: string[] = Utils.NoEmpty( - wikidata?.split(";")?.map((wd) => wd.trim()) ?? [] - ) - return new WikipediaBox(wikidatas) - }) - ) + const wikiIds: Store = tagsSource.map((tags) => { + const key = keys.find((k) => tags[k] !== undefined && tags[k] !== "") + return tags[key]?.split(";")?.map((id) => id.trim()) + }) + return new SvelteUIElement(WikipediaPanel, { + wikiIds, + }) }, }, { diff --git a/UI/ThemeViewGUI.svelte b/UI/ThemeViewGUI.svelte index 461efe048..10b45698c 100644 --- a/UI/ThemeViewGUI.svelte +++ b/UI/ThemeViewGUI.svelte @@ -276,17 +276,3 @@ - - diff --git a/UI/Wikipedia/WikidataSearchBox.ts b/UI/Wikipedia/WikidataSearchBox.ts index d411031aa..070d25e6e 100644 --- a/UI/Wikipedia/WikidataSearchBox.ts +++ b/UI/Wikipedia/WikidataSearchBox.ts @@ -8,20 +8,12 @@ import Locale from "../i18n/Locale" import { VariableUiElement } from "../Base/VariableUIElement" import WikidataPreviewBox from "./WikidataPreviewBox" import Title from "../Base/Title" -import WikipediaBox from "./WikipediaBox" import Svg from "../../Svg" import Loading from "../Base/Loading" import Table from "../Base/Table" export default class WikidataSearchBox extends InputElement { - private static readonly _searchCache = new Map>() - private readonly wikidataId: UIEventSource - private readonly searchText: UIEventSource - private readonly instanceOf?: number[] - private readonly notInstanceOf?: number[] - public static docs = new Combine([ - , new Title("Helper arguments"), new Table( ["name", "doc"], @@ -100,6 +92,11 @@ Another example is to search for species and trees: \`\`\` `, ]) + private static readonly _searchCache = new Map>() + private readonly wikidataId: UIEventSource + private readonly searchText: UIEventSource + private readonly instanceOf?: number[] + private readonly notInstanceOf?: number[] constructor(options?: { searchText?: UIEventSource @@ -207,25 +204,15 @@ Another example is to search for species and trees: ) ) - const full = new Combine([ + return 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.split(";")) - }) - ).SetStyle("max-height:12.5rem"), - full, - ]).ConstructElement() + ]) + .SetClass("flex flex-col border-2 border-black rounded-xl m-2 p-2") + .ConstructElement() } } diff --git a/UI/Wikipedia/WikipediaArticle.svelte b/UI/Wikipedia/WikipediaArticle.svelte new file mode 100644 index 000000000..e7067b4b8 --- /dev/null +++ b/UI/Wikipedia/WikipediaArticle.svelte @@ -0,0 +1,46 @@ + + + + + + + +{#if $wikipediaDetails.wikidata} + +{/if} + +{#if $wikipediaDetails.firstParagraph === "" || $wikipediaDetails.firstParagraph === undefined} + + + +{:else} + + + + + + Read the rest of the article + + + + + + + +{/if} diff --git a/UI/Wikipedia/WikipediaBox.ts b/UI/Wikipedia/WikipediaBox.ts deleted file mode 100644 index 0ce23fb13..000000000 --- a/UI/Wikipedia/WikipediaBox.ts +++ /dev/null @@ -1,321 +0,0 @@ -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 ':'-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 = 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 = 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 = 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"), - ]) - } -} diff --git a/UI/Wikipedia/WikipediaBoxOptions.ts b/UI/Wikipedia/WikipediaBoxOptions.ts new file mode 100644 index 000000000..52a81c8ea --- /dev/null +++ b/UI/Wikipedia/WikipediaBoxOptions.ts @@ -0,0 +1,7 @@ +import { UIEventSource } from "../../Logic/UIEventSource" + +export interface WikipediaBoxOptions { + addHeader?: boolean + firstParagraphOnly?: true | boolean + allowToAdd?: boolean +} diff --git a/UI/Wikipedia/WikipediaPanel.svelte b/UI/Wikipedia/WikipediaPanel.svelte new file mode 100644 index 000000000..335d76673 --- /dev/null +++ b/UI/Wikipedia/WikipediaPanel.svelte @@ -0,0 +1,56 @@ + +{#if _wikipediaStores !== undefined} + + + {#each _wikipediaStores as store (store.tag)} + selected ? "tab-selected" : "tab-unselected"}> + + + {/each} + + + + {#each _wikipediaStores as store (store.tag)} + + + + + {/each} + + +{/if} + diff --git a/UI/Wikipedia/WikipediaTitle.svelte b/UI/Wikipedia/WikipediaTitle.svelte new file mode 100644 index 000000000..1255a7603 --- /dev/null +++ b/UI/Wikipedia/WikipediaTitle.svelte @@ -0,0 +1,13 @@ + + +{$wikipediaDetails.title} + diff --git a/assets/layers/gps_track/gps_track.json b/assets/layers/gps_track/gps_track.json index 94bb2a40b..eb0110171 100644 --- a/assets/layers/gps_track/gps_track.json +++ b/assets/layers/gps_track/gps_track.json @@ -44,4 +44,4 @@ } ], "syncSelection": "global" -} +} \ No newline at end of file diff --git a/assets/tagRenderings/questions.json b/assets/tagRenderings/questions.json index 985db6ddc..65526d7d9 100644 --- a/assets/tagRenderings/questions.json +++ b/assets/tagRenderings/questions.json @@ -2110,4 +2110,4 @@ } ] } -} +} \ No newline at end of file diff --git a/assets/themes/uk_addresses/uk_addresses.json b/assets/themes/uk_addresses/uk_addresses.json index b39453b08..2c9ca9562 100644 --- a/assets/themes/uk_addresses/uk_addresses.json +++ b/assets/themes/uk_addresses/uk_addresses.json @@ -695,4 +695,4 @@ "enableShareScreen": false, "enableMoreQuests": false, "credits": "Pieter Vander Vennet, Rob Nickerson, Russ Garrett" -} +} \ No newline at end of file diff --git a/langs/en.json b/langs/en.json index b744bdf89..dc34045bc 100644 --- a/langs/en.json +++ b/langs/en.json @@ -362,6 +362,7 @@ "general": "On this map, you can see, edit and add points of interest. Zoom around to see the POI, tap one to see or edit the information. All data is sourced from and saved to OpenStreetMap, which can be freely reused." }, "wikipedia": { + "addEntry": "Add another Wikipedia page", "createNewWikidata": "Create a new Wikidata item", "doSearch": "Search above to see results", "failed": "Loading the Wikipedia entry failed", diff --git a/langs/shared-questions/ca.json b/langs/shared-questions/ca.json index 9e8db263a..690d20012 100644 --- a/langs/shared-questions/ca.json +++ b/langs/shared-questions/ca.json @@ -131,6 +131,13 @@ "question": "Quin és el nom de la xarxa per a l'accés inalàmbric a internet?", "render": "El nom de la xarxa és {internet_access:ssid}" }, + "just_created": { + "mappings": { + "0": { + "then": "Acabeu de crear aquest element! Gràcies per compartir aquesta informació amb el mon i ajudar a persones al voltant del món." + } + } + }, "level": { "mappings": { "0": { diff --git a/langs/shared-questions/de.json b/langs/shared-questions/de.json index 5e67ec134..2d36b7938 100644 --- a/langs/shared-questions/de.json +++ b/langs/shared-questions/de.json @@ -131,6 +131,13 @@ "question": "Wie lautet der Netzwerkname für den drahtlosen Internetzugang?", "render": "Der Netzwerkname lautet {internet_access:ssid}" }, + "just_created": { + "mappings": { + "0": { + "then": "Sie haben gerade dieses Element erstellt! Vielen Dank, dass Sie diese Informationen mit der Welt teilen und Menschen weltweit helfen." + } + } + }, "level": { "mappings": { "0": { diff --git a/langs/shared-questions/en.json b/langs/shared-questions/en.json index 2ceea5242..1ac9eee06 100644 --- a/langs/shared-questions/en.json +++ b/langs/shared-questions/en.json @@ -131,6 +131,13 @@ "question": "What is the network name for the wireless internet access?", "render": "The network name is {internet_access:ssid}" }, + "just_created": { + "mappings": { + "0": { + "then": "You just created this element! Thanks for sharing this info with the world and helping people worldwide." + } + } + }, "last_edit": { "render": { "special": { diff --git a/langs/shared-questions/fr.json b/langs/shared-questions/fr.json index 393ddebd7..fb9fac6e0 100644 --- a/langs/shared-questions/fr.json +++ b/langs/shared-questions/fr.json @@ -131,6 +131,13 @@ "question": "Quel est le nom du réseau pour l'accès Internet sans fil ?", "render": "Le nom du réseau est {internet_access:ssid}" }, + "just_created": { + "mappings": { + "0": { + "then": "Vous venez de créer cet élément ! Merci d'avoir partagé cette information avec le monde et d'aider les autres personnes." + } + } + }, "level": { "mappings": { "0": { diff --git a/langs/shared-questions/nl.json b/langs/shared-questions/nl.json index a1a66199a..fe4e3ddd6 100644 --- a/langs/shared-questions/nl.json +++ b/langs/shared-questions/nl.json @@ -131,6 +131,13 @@ "question": "Wat is de netwerknaam voor de draadloze internettoegang?", "render": "De netwerknaam is {internet_access:ssid}" }, + "just_created": { + "mappings": { + "0": { + "then": "Je hebt dit punt net toegevoegd! Bedankt om deze info met iedereen te delen en om de mensen wereldwijd te helpen." + } + } + }, "last_edit": { "render": { "special": { diff --git a/test.ts b/test.ts index b54dbd3aa..ddca5d174 100644 --- a/test.ts +++ b/test.ts @@ -9,10 +9,12 @@ import { UIEventSource } from "./Logic/UIEventSource" import { VariableUiElement } from "./UI/Base/VariableUIElement" import { FixedUiElement } from "./UI/Base/FixedUiElement" import Title from "./UI/Base/Title" -import WaySplitMap from "./UI/BigComponents/WaySplitMap.svelte" +import { WikipediaBoxOptions } from "./UI/Wikipedia/WikipediaBoxOptions" +import Wikipedia from "./Logic/Web/Wikipedia" +import WikipediaPanel from "./UI/Wikipedia/WikipediaPanel.svelte" import SvelteUIElement from "./UI/Base/SvelteUIElement" -import { OsmObject } from "./Logic/Osm/OsmObject" -import SplitRoadWizard from "./UI/Popup/SplitRoadWizard" +import LanguagePicker from "./UI/LanguagePicker" +import { Utils } from "./Utils" function testspecial() { const layout = new LayoutConfig(theme, true) // qp.data === "" ? : new AllKnownLayoutsLazy().get(qp.data) @@ -47,7 +49,12 @@ function testinput() { } async function testWaySplit() { - new SplitRoadWizard("way/28717919", {}).SetClass("w-full h-full").AttachTo("maindiv") + const ids = new UIEventSource(["Q42", "Q1"]) + new SvelteUIElement(WikipediaPanel, { wikiIds: ids, addEntry: true }).AttachTo("maindiv") + new LanguagePicker(["en", "nl"]).AttachTo("extradiv") + await Utils.waitFor(5000) + ids.data.push("Q430") + ids.ping() } testWaySplit().then((_) => console.log("inited")) //testinput()