forked from MapComplete/MapComplete
		
	Refactoring: port wikipedia panel to Svelte
This commit is contained in:
		
							parent
							
								
									24f7610d0a
								
							
						
					
					
						commit
						d8e14927c8
					
				
					 32 changed files with 362 additions and 847 deletions
				
			
		|  | @ -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<string, Promise<WikidataResponse>>() | ||||
|     public static async LoadWikidataEntryAsync(value: string | number): Promise<WikidataResponse> { | ||||
|         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<WikidataResponse> { | ||||
|     private static async LoadWikidataEntryUncachedAsync( | ||||
|         value: string | number | ||||
|     ): Promise<WikidataResponse> { | ||||
|         const id = Wikidata.ExtractKey(value) | ||||
|         if (id === undefined) { | ||||
|             console.warn("Could not extract a wikidata entry from", value) | ||||
|  |  | |||
|  | @ -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<string, Promise<string>>() | ||||
|     private static _fullDetailsCache = new Map<string, Store<FullWikipediaDetails>>() | ||||
|     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<FullWikipediaDetails> { | ||||
|         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<FullWikipediaDetails>({}, 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<string> { | ||||
|         const cacheKey = this.backend + "/" + pageName | ||||
|         if (Wikipedia._cache.has(cacheKey)) { | ||||
|             return Wikipedia._cache.get(cacheKey) | ||||
|         } | ||||
|     ): Promise<string | undefined> { | ||||
|         const promise = this.GetArticleUncachedAsync(pageName) | ||||
|         Wikipedia._cache.set(cacheKey, promise) | ||||
|         return promise | ||||
|     } | ||||
| 
 | ||||
|     private async GetArticleUncachedAsync(pageName: string): Promise<string> { | ||||
|         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 | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -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, | ||||
|  |  | |||
|  | @ -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<boolean> | ||||
|     private hashToShow: string | ||||
|     private _fullscreencomponent: BaseUIElement | ||||
|     private _resetScrollSignal: UIEventSource<void> = new UIEventSource<void>(undefined) | ||||
|     private _setHash: boolean | ||||
| 
 | ||||
|     constructor( | ||||
|         title: (options: { mode: string }) => BaseUIElement, | ||||
|         content: (options: { | ||||
|             mode: string | ||||
|             resetScrollSignal: UIEventSource<void> | ||||
|         }) => BaseUIElement, | ||||
|         hashToShow: string, | ||||
|         isShown: UIEventSource<boolean> = new UIEventSource<boolean>(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" | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -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> | number = 0, | ||||
|         options?: { | ||||
|             leftOfHeader?: BaseUIElement | ||||
|             styleHeader?: (header: BaseUIElement) => void | ||||
|         } | ||||
|     ) { | ||||
|         const openedTabSrc = | ||||
|             typeof openedTab === "number" | ||||
|                 ? new UIEventSource(openedTab) | ||||
|                 : openedTab ?? new UIEventSource<number>(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]) | ||||
|     } | ||||
| } | ||||
|  | @ -66,3 +66,15 @@ | |||
|     </TabPanel> | ||||
|   </TabPanels> | ||||
| </TabGroup> | ||||
| 
 | ||||
| <style> | ||||
|     .tab-selected { | ||||
|         background-color: rgb(59 130 246); | ||||
|         color: rgb(255 255 255); | ||||
|     } | ||||
| 
 | ||||
|     .tab-unselected { | ||||
|         background-color: rgb(255 255 255); | ||||
|         color: rgb(0 0 0); | ||||
|     } | ||||
| </style> | ||||
|  |  | |||
|  | @ -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<FilteredLayer[]> | ||||
|     featurePipeline: FeaturePipeline | ||||
|     layoutToUse: LayoutConfig | ||||
|     currentBounds: UIEventSource<BBox> | ||||
|     backgroundLayer: UIEventSource<BaseLayer> | ||||
|     locationControl: UIEventSource<Loc> | ||||
|     featureSwitchExportAsPdf: UIEventSource<boolean> | ||||
|     featureSwitchEnableExport: UIEventSource<boolean> | ||||
| } | ||||
| 
 | ||||
| export default class AllDownloads extends ScrollableFullScreen { | ||||
| export default class AllDownloads extends SubtleButton { | ||||
|     constructor( | ||||
|         isShown: UIEventSource<boolean>, | ||||
|         state: { | ||||
|             filteredLayers: UIEventSource<FilteredLayer[]> | ||||
|             featurePipeline: FeaturePipeline | ||||
|             layoutToUse: LayoutConfig | ||||
|             currentBounds: UIEventSource<BBox> | ||||
|             backgroundLayer: UIEventSource<BaseLayer> | ||||
|             locationControl: UIEventSource<Loc> | ||||
|             featureSwitchExportAsPdf: UIEventSource<boolean> | ||||
|             featureSwitchEnableExport: UIEventSource<boolean> | ||||
|         } | ||||
|     ) { | ||||
|         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) | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -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") | ||||
|  |  | |||
|  | @ -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") | ||||
|                         }) | ||||
|                     ) | ||||
|                 }) | ||||
|  |  | |||
|  | @ -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) | ||||
|  |  | |||
|  | @ -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 { | ||||
|  |  | |||
|  | @ -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<any>) { | ||||
|         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<BBox>(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), | ||||
|             }) | ||||
|         }) | ||||
| 
 | ||||
|  |  | |||
|  | @ -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 = <string>( | ||||
|             (inputHelperOptions.feature?.properties[searchKey]?.toLowerCase() ?? "") | ||||
|         ) | ||||
| 
 | ||||
|         let searchForValue: UIEventSource<string> = new UIEventSource(searchFor) | ||||
|         const options: any = args[1] | ||||
|         if (searchFor !== undefined && options !== undefined) { | ||||
|             const prefixes = <string[] | Record<string, string[]>>options["removePrefixes"] ?? [] | ||||
|             const postfixes = <string[] | Record<string, string[]>>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, | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -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<any>, | ||||
|         layerConfig: LayerConfig, | ||||
|         state: FeaturePipelineState, | ||||
|         options?: { | ||||
|             hashToShow?: string | ||||
|             isShown?: UIEventSource<boolean> | ||||
|             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<any>): 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") | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  | @ -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<string[]> = 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, | ||||
|                     }) | ||||
|                 }, | ||||
|             }, | ||||
|             { | ||||
|  |  | |||
|  | @ -276,17 +276,3 @@ | |||
|   </FloatOver> | ||||
| </If> | ||||
| 
 | ||||
| 
 | ||||
| <style> | ||||
|     /* WARNING: This is just for demonstration. | ||||
|         Using :global() in this way can be risky. */ | ||||
|     :global(.tab-selected) { | ||||
|         background-color: rgb(59 130 246); | ||||
|         color: rgb(255 255 255); | ||||
|     } | ||||
| 
 | ||||
|     :global(.tab-unselected) { | ||||
|         background-color: rgb(255 255 255); | ||||
|         color: rgb(0 0 0); | ||||
|     } | ||||
| </style> | ||||
|  |  | |||
|  | @ -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<string> { | ||||
|     private static readonly _searchCache = new Map<string, Promise<WikidataResponse[]>>() | ||||
|     private readonly wikidataId: UIEventSource<string> | ||||
|     private readonly searchText: UIEventSource<string> | ||||
|     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<string, Promise<WikidataResponse[]>>() | ||||
|     private readonly wikidataId: UIEventSource<string> | ||||
|     private readonly searchText: UIEventSource<string> | ||||
|     private readonly instanceOf?: number[] | ||||
|     private readonly notInstanceOf?: number[] | ||||
| 
 | ||||
|     constructor(options?: { | ||||
|         searchText?: UIEventSource<string> | ||||
|  | @ -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() | ||||
|     } | ||||
| } | ||||
|  |  | |||
							
								
								
									
										46
									
								
								UI/Wikipedia/WikipediaArticle.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								UI/Wikipedia/WikipediaArticle.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,46 @@ | |||
| <script lang="ts"> | ||||
|   import type { FullWikipediaDetails } from "../../Logic/Web/Wikipedia"; | ||||
|   import { Store } from "../../Logic/UIEventSource"; | ||||
|   import FromHtml from "../Base/FromHtml.svelte"; | ||||
|   import Loading from "../Base/Loading.svelte"; | ||||
|   import { Disclosure, DisclosureButton, DisclosurePanel } from "@rgossiaux/svelte-headlessui"; | ||||
|   import { ChevronRightIcon } from "@rgossiaux/svelte-heroicons/solid"; | ||||
|   import ToSvelte from "../Base/ToSvelte.svelte"; | ||||
|   import WikidataPreviewBox from "./WikidataPreviewBox"; | ||||
|   import Tr from "../Base/Tr.svelte"; | ||||
|   import Translations from "../i18n/Translations"; | ||||
| 
 | ||||
|   /** | ||||
|    * Small helper | ||||
|    */ | ||||
|   export let wikipediaDetails: Store<FullWikipediaDetails>; | ||||
| </script> | ||||
| 
 | ||||
| <a href={$wikipediaDetails.articleUrl} target="_blank" rel="noreferrer" class="flex"> | ||||
|   <img src="./assets/svg/wikipedia.svg" class="w-6 h-6"/> | ||||
|   <Tr t={Translations.t.general.wikipedia.fromWikipedia}/> | ||||
| </a> | ||||
| 
 | ||||
| {#if $wikipediaDetails.wikidata} | ||||
|   <ToSvelte construct={WikidataPreviewBox.WikidataResponsePreview($wikipediaDetails.wikidata)} /> | ||||
| {/if} | ||||
| 
 | ||||
| {#if $wikipediaDetails.firstParagraph === "" || $wikipediaDetails.firstParagraph === undefined} | ||||
|   <Loading > | ||||
|     <Tr t={Translations.t.general.wikipedia.loading}/> | ||||
|   </Loading> | ||||
| {:else} | ||||
|   <FromHtml src={$wikipediaDetails.firstParagraph} /> | ||||
|   <Disclosure let:open> | ||||
|     <DisclosureButton> | ||||
|       <span class="flex"> | ||||
|       <ChevronRightIcon class="w-6 h-6" style={open ? "transform: rotate(90deg);" : ""} /> | ||||
|       Read the rest of the article | ||||
|          | ||||
|       </span> | ||||
|     </DisclosureButton> | ||||
|     <DisclosurePanel> | ||||
|       <FromHtml src={$wikipediaDetails.restOfArticle} /> | ||||
|     </DisclosurePanel> | ||||
|   </Disclosure> | ||||
| {/if} | ||||
|  | @ -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 '<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"), | ||||
|         ]) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										7
									
								
								UI/Wikipedia/WikipediaBoxOptions.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								UI/Wikipedia/WikipediaBoxOptions.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,7 @@ | |||
| import { UIEventSource } from "../../Logic/UIEventSource" | ||||
| 
 | ||||
| export interface WikipediaBoxOptions { | ||||
|     addHeader?: boolean | ||||
|     firstParagraphOnly?: true | boolean | ||||
|     allowToAdd?: boolean | ||||
| } | ||||
							
								
								
									
										56
									
								
								UI/Wikipedia/WikipediaPanel.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								UI/Wikipedia/WikipediaPanel.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,56 @@ | |||
| <script lang="ts"> | ||||
|   /** | ||||
|    * Shows one or more wikidata info boxes or wikipedia articles in a tabbed component. | ||||
|    */ | ||||
|   import type { FullWikipediaDetails } from "../../Logic/Web/Wikipedia"; | ||||
|   import Wikipedia from "../../Logic/Web/Wikipedia"; | ||||
|   import Locale from "../i18n/Locale"; | ||||
|   import { Store } from "../../Logic/UIEventSource"; | ||||
|   import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@rgossiaux/svelte-headlessui"; | ||||
|   import WikipediaTitle from "./WikipediaTitle.svelte"; | ||||
|   import WikipediaArticle from "./WikipediaArticle.svelte"; | ||||
|   import { onDestroy } from "svelte"; | ||||
| 
 | ||||
| 
 | ||||
|   /** | ||||
|    * Either a wikidata item or a '<language>:<article>' link | ||||
|    */ | ||||
|   export let wikiIds: Store<string[]>; | ||||
|   let wikipediaStores: Store<Store<FullWikipediaDetails>[]> = Locale.language.bind(language => | ||||
|     wikiIds.map(wikiIds => wikiIds.map(id => Wikipedia.fetchArticleAndWikidata(id, language)))); | ||||
|   let _wikipediaStores; | ||||
|   onDestroy(wikipediaStores.addCallbackAndRunD(wikipediaStores => { | ||||
|     _wikipediaStores = wikipediaStores; | ||||
|   })); | ||||
| </script> | ||||
| {#if _wikipediaStores !== undefined} | ||||
|   <TabGroup> | ||||
|     <TabList> | ||||
|       {#each _wikipediaStores as store (store.tag)} | ||||
|         <Tab class={({selected}) => selected ? "tab-selected" : "tab-unselected"}> | ||||
|           <WikipediaTitle wikipediaDetails={store} /> | ||||
|         </Tab> | ||||
|       {/each} | ||||
|     </TabList> | ||||
|     <TabPanels> | ||||
| 
 | ||||
|       {#each _wikipediaStores as store (store.tag)} | ||||
|         <TabPanel> | ||||
|           <WikipediaArticle wikipediaDetails={store} /> | ||||
| 
 | ||||
|         </TabPanel> | ||||
|       {/each} | ||||
|     </TabPanels> | ||||
|   </TabGroup> | ||||
| {/if} | ||||
| <style> | ||||
|     .tab-selected { | ||||
|         background-color: rgb(59 130 246); | ||||
|         color: rgb(255 255 255); | ||||
|     } | ||||
| 
 | ||||
|     .tab-unselected { | ||||
|         background-color: rgb(255 255 255); | ||||
|         color: rgb(0 0 0); | ||||
|     } | ||||
| </style> | ||||
							
								
								
									
										13
									
								
								UI/Wikipedia/WikipediaTitle.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								UI/Wikipedia/WikipediaTitle.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,13 @@ | |||
| <script lang="ts"> | ||||
|   import type { FullWikipediaDetails } from "../../Logic/Web/Wikipedia"; | ||||
|   import { Store } from "../../Logic/UIEventSource"; | ||||
|   import { onDestroy } from "svelte"; | ||||
| 
 | ||||
|   /** | ||||
|    * Small helper | ||||
|    */ | ||||
|   export let wikipediaDetails: Store<FullWikipediaDetails> | ||||
| </script> | ||||
| 
 | ||||
| {$wikipediaDetails.title} | ||||
| 
 | ||||
|  | @ -44,4 +44,4 @@ | |||
|     } | ||||
|   ], | ||||
|   "syncSelection": "global" | ||||
| } | ||||
| } | ||||
|  | @ -2110,4 +2110,4 @@ | |||
|       } | ||||
|     ] | ||||
|   } | ||||
| } | ||||
| } | ||||
|  | @ -695,4 +695,4 @@ | |||
|   "enableShareScreen": false, | ||||
|   "enableMoreQuests": false, | ||||
|   "credits": "Pieter Vander Vennet, Rob Nickerson, Russ Garrett" | ||||
| } | ||||
| } | ||||
|  | @ -362,6 +362,7 @@ | |||
|             "general": "On this map, you can see, edit and add <i>points of interest</i>. 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", | ||||
|  |  | |||
|  | @ -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 <b>{internet_access:ssid}</b>" | ||||
|         }, | ||||
|         "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": { | ||||
|  |  | |||
|  | @ -131,6 +131,13 @@ | |||
|             "question": "Wie lautet der Netzwerkname für den drahtlosen Internetzugang?", | ||||
|             "render": "Der Netzwerkname lautet <b>{internet_access:ssid}</b>" | ||||
|         }, | ||||
|         "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": { | ||||
|  |  | |||
|  | @ -131,6 +131,13 @@ | |||
|             "question": "What is the network name for the wireless internet access?", | ||||
|             "render": "The network name is <b>{internet_access:ssid}</b>" | ||||
|         }, | ||||
|         "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": { | ||||
|  |  | |||
|  | @ -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 <b>{internet_access:ssid}</b>" | ||||
|         }, | ||||
|         "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": { | ||||
|  |  | |||
|  | @ -131,6 +131,13 @@ | |||
|             "question": "Wat is de netwerknaam voor de draadloze internettoegang?", | ||||
|             "render": "De netwerknaam is <b>{internet_access:ssid}</b>" | ||||
|         }, | ||||
|         "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": { | ||||
|  |  | |||
							
								
								
									
										15
									
								
								test.ts
									
										
									
									
									
								
							
							
						
						
									
										15
									
								
								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(<any>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()
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue