From 88336ae08d98f8212f707a2a8fa1adc350b21a6c Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 21 Jun 2021 15:13:25 +0000 Subject: [PATCH 01/18] Translated using Weblate (Dutch) Currently translated at 48.1% (195 of 405 strings) Translation: MapComplete/themes Translate-URL: https://hosted.weblate.org/projects/mapcomplete/themes/nl/ --- langs/themes/nl.json | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/langs/themes/nl.json b/langs/themes/nl.json index 26c50fcf9c..0579271631 100644 --- a/langs/themes/nl.json +++ b/langs/themes/nl.json @@ -239,7 +239,8 @@ } }, "campersite": { - "title": "Kampeersite" + "title": "Kampeersite", + "shortDescription": "Vind locaties waar je de nacht kan doorbrengen met je mobilehome" }, "climbing": { "title": "Open Klimkaart", @@ -368,7 +369,22 @@ "title": { "render": "Klimgelegenheid?" }, - "description": "Een klimgelegenheid?" + "description": "Een klimgelegenheid?", + "tagRenderings": { + "1": { + "mappings": { + "2": { + "then": "Klimmen is hier niet toegelaten" + }, + "1": { + "then": "Klimmen is hier niet toegelaten" + }, + "0": { + "then": "Klimmen is hier niet mogelijk" + } + } + } + } } }, "roamingRenderings": { @@ -456,6 +472,19 @@ "then": "Er zijn hier {climbing:speed} snelklimmuren" } } + }, + "1": { + "mappings": { + "1": { + "then": "Een omvattend element geeft aan dat een toelating nodig is om hier te klimmen
{_embedding_feature:access:description}" + }, + "0": { + "then": "Een omvattend element geeft aan dat dit publiek toegangkelijk is
{_embedding_feature:access:description}" + } + } + }, + "0": { + "question": "Is er een (onofficiële) website met meer informatie (b.v. met topos)?" } } }, @@ -946,4 +975,4 @@ } } } -} \ No newline at end of file +} From aaaf876257df921955f462f076505c8ae7202f3f Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Tue, 22 Jun 2021 14:21:32 +0200 Subject: [PATCH 02/18] Move imageAttributionSources around, improve fixTheme script --- Logic/Actors/ImageSearcher.ts | 2 +- Logic/ImageProviders/AllImageProviders.ts | 9 ++++ .../ImageAttributionSource.ts | 2 +- Logic/{Web => ImageProviders}/Imgur.ts | 0 .../{Web => ImageProviders}/ImgurUploader.ts | 0 Logic/{Web => ImageProviders}/Mapillary.ts | 0 Logic/{Web => ImageProviders}/Wikimedia.ts | 36 ++++++++------ UI/Image/AttributedImage.ts | 2 +- UI/Image/Attribution.ts | 2 +- UI/Image/ImageCarousel.ts | 7 ++- UI/Image/ImageUploadFlow.ts | 2 +- Utils.ts | 29 ++++++++++- scripts/ScriptUtils.ts | 27 ++++++++++- scripts/fixTheme.ts | 48 ++++++++++++------- test.ts | 2 +- 15 files changed, 125 insertions(+), 43 deletions(-) create mode 100644 Logic/ImageProviders/AllImageProviders.ts rename Logic/{Web => ImageProviders}/ImageAttributionSource.ts (91%) rename Logic/{Web => ImageProviders}/Imgur.ts (100%) rename Logic/{Web => ImageProviders}/ImgurUploader.ts (100%) rename Logic/{Web => ImageProviders}/Mapillary.ts (100%) rename Logic/{Web => ImageProviders}/Wikimedia.ts (82%) diff --git a/Logic/Actors/ImageSearcher.ts b/Logic/Actors/ImageSearcher.ts index 3ea510152a..165442d6f0 100644 --- a/Logic/Actors/ImageSearcher.ts +++ b/Logic/Actors/ImageSearcher.ts @@ -1,4 +1,4 @@ -import {ImagesInCategory, Wikidata, Wikimedia} from "../Web/Wikimedia"; +import {ImagesInCategory, Wikidata, Wikimedia} from "../ImageProviders/Wikimedia"; import {UIEventSource} from "../UIEventSource"; /** diff --git a/Logic/ImageProviders/AllImageProviders.ts b/Logic/ImageProviders/AllImageProviders.ts new file mode 100644 index 0000000000..89ae0a1427 --- /dev/null +++ b/Logic/ImageProviders/AllImageProviders.ts @@ -0,0 +1,9 @@ +import {Mapillary} from "./Mapillary"; +import {Wikimedia} from "./Wikimedia"; +import {Imgur} from "./Imgur"; + +export default class AllImageProviders{ + + public static ImageAttributionSource = [Imgur.singleton, Mapillary.singleton, Wikimedia.singleton] + +} \ No newline at end of file diff --git a/Logic/Web/ImageAttributionSource.ts b/Logic/ImageProviders/ImageAttributionSource.ts similarity index 91% rename from Logic/Web/ImageAttributionSource.ts rename to Logic/ImageProviders/ImageAttributionSource.ts index 689a32c46c..451909cfba 100644 --- a/Logic/Web/ImageAttributionSource.ts +++ b/Logic/ImageProviders/ImageAttributionSource.ts @@ -5,7 +5,6 @@ import BaseUIElement from "../../UI/BaseUIElement"; export default abstract class ImageAttributionSource { - private _cache = new Map>() GetAttributionFor(url: string): UIEventSource { @@ -22,6 +21,7 @@ export default abstract class ImageAttributionSource { public abstract SourceIcon(backlinkSource?: string) : BaseUIElement; protected abstract DownloadAttribution(url: string): UIEventSource; + /*Converts a value to a URL. Can return null if not applicable*/ public PrepareUrl(value: string): string{ return value; } diff --git a/Logic/Web/Imgur.ts b/Logic/ImageProviders/Imgur.ts similarity index 100% rename from Logic/Web/Imgur.ts rename to Logic/ImageProviders/Imgur.ts diff --git a/Logic/Web/ImgurUploader.ts b/Logic/ImageProviders/ImgurUploader.ts similarity index 100% rename from Logic/Web/ImgurUploader.ts rename to Logic/ImageProviders/ImgurUploader.ts diff --git a/Logic/Web/Mapillary.ts b/Logic/ImageProviders/Mapillary.ts similarity index 100% rename from Logic/Web/Mapillary.ts rename to Logic/ImageProviders/Mapillary.ts diff --git a/Logic/Web/Wikimedia.ts b/Logic/ImageProviders/Wikimedia.ts similarity index 82% rename from Logic/Web/Wikimedia.ts rename to Logic/ImageProviders/Wikimedia.ts index 4668f35116..9d74b77694 100644 --- a/Logic/Web/Wikimedia.ts +++ b/Logic/ImageProviders/Wikimedia.ts @@ -4,6 +4,7 @@ import BaseUIElement from "../../UI/BaseUIElement"; import Svg from "../../Svg"; import {UIEventSource} from "../UIEventSource"; import Link from "../../UI/Base/Link"; +import {Utils} from "../../Utils"; /** * This module provides endpoints for wikipedia/wikimedia and others @@ -138,21 +139,28 @@ export class Wikimedia extends ImageAttributionSource { "api.php?action=query&prop=imageinfo&iiprop=extmetadata&" + "titles=" + filename + "&format=json&origin=*"; - console.log("Getting attribution at ", url) - $.getJSON(url, function (data) { - const licenseInfo = new LicenseInfo(); - const license = data.query.pages[-1].imageinfo[0].extmetadata; + Utils.downloadJson(url).then( + data =>{ + const licenseInfo = new LicenseInfo(); + const license = (data.query.pages[-1].imageinfo ?? [])[0]?.extmetadata; + if(license === undefined){ + console.error("This file has no usable metedata or license attached... Please fix the license info file yourself!") + source.setData(null) + return; + } - licenseInfo.artist = license.Artist?.value; - licenseInfo.license = license.License?.value; - licenseInfo.copyrighted = license.Copyrighted?.value; - licenseInfo.attributionRequired = license.AttributionRequired?.value; - licenseInfo.usageTerms = license.UsageTerms?.value; - licenseInfo.licenseShortName = license.LicenseShortName?.value; - licenseInfo.credit = license.Credit?.value; - licenseInfo.description = license.ImageDescription?.value; - source.setData(licenseInfo); - }); + licenseInfo.artist = license.Artist?.value; + licenseInfo.license = license.License?.value; + licenseInfo.copyrighted = license.Copyrighted?.value; + licenseInfo.attributionRequired = license.AttributionRequired?.value; + licenseInfo.usageTerms = license.UsageTerms?.value; + licenseInfo.licenseShortName = license.LicenseShortName?.value; + licenseInfo.credit = license.Credit?.value; + licenseInfo.description = license.ImageDescription?.value; + source.setData(licenseInfo); + } + ) + return source; } diff --git a/UI/Image/AttributedImage.ts b/UI/Image/AttributedImage.ts index 260c5306f8..43bfc3ea21 100644 --- a/UI/Image/AttributedImage.ts +++ b/UI/Image/AttributedImage.ts @@ -1,7 +1,7 @@ import Combine from "../Base/Combine"; import Attribution from "./Attribution"; import Img from "../Base/Img"; -import ImageAttributionSource from "../../Logic/Web/ImageAttributionSource"; +import ImageAttributionSource from "../../Logic/ImageProviders/ImageAttributionSource"; export class AttributedImage extends Combine { diff --git a/UI/Image/Attribution.ts b/UI/Image/Attribution.ts index 514bc475a9..0fddcc6f34 100644 --- a/UI/Image/Attribution.ts +++ b/UI/Image/Attribution.ts @@ -3,7 +3,7 @@ import Translations from "../i18n/Translations"; import BaseUIElement from "../BaseUIElement"; import {VariableUiElement} from "../Base/VariableUIElement"; import {UIEventSource} from "../../Logic/UIEventSource"; -import {LicenseInfo} from "../../Logic/Web/Wikimedia"; +import {LicenseInfo} from "../../Logic/ImageProviders/Wikimedia"; export default class Attribution extends VariableUiElement { diff --git a/UI/Image/ImageCarousel.ts b/UI/Image/ImageCarousel.ts index a28d89a2cf..7c5d90335b 100644 --- a/UI/Image/ImageCarousel.ts +++ b/UI/Image/ImageCarousel.ts @@ -6,10 +6,9 @@ import {AttributedImage} from "./AttributedImage"; import BaseUIElement from "../BaseUIElement"; import Img from "../Base/Img"; import Toggle from "../Input/Toggle"; -import ImageAttributionSource from "../../Logic/Web/ImageAttributionSource"; -import {Wikimedia} from "../../Logic/Web/Wikimedia"; -import {Mapillary} from "../../Logic/Web/Mapillary"; -import {Imgur} from "../../Logic/Web/Imgur"; +import {Wikimedia} from "../../Logic/ImageProviders/Wikimedia"; +import {Imgur} from "../../Logic/ImageProviders/Imgur"; +import {Mapillary} from "../../Logic/ImageProviders/Mapillary"; export class ImageCarousel extends Toggle { diff --git a/UI/Image/ImageUploadFlow.ts b/UI/Image/ImageUploadFlow.ts index cf5881f64e..58d9a37602 100644 --- a/UI/Image/ImageUploadFlow.ts +++ b/UI/Image/ImageUploadFlow.ts @@ -8,7 +8,7 @@ import BaseUIElement from "../BaseUIElement"; import LicensePicker from "../BigComponents/LicensePicker"; import Toggle from "../Input/Toggle"; import FileSelectorButton from "../Input/FileSelectorButton"; -import ImgurUploader from "../../Logic/Web/ImgurUploader"; +import ImgurUploader from "../../Logic/ImageProviders/ImgurUploader"; import UploadFlowStateUI from "../BigComponents/UploadFlowStateUI"; import LayerConfig from "../../Customizations/JSON/LayerConfig"; diff --git a/Utils.ts b/Utils.ts index 111939b1ad..fa3e55ef88 100644 --- a/Utils.ts +++ b/Utils.ts @@ -1,5 +1,4 @@ import * as colors from "./assets/colors.json" -import {Util} from "leaflet"; export class Utils { @@ -324,6 +323,34 @@ export class Utils { } return result; } + + public static externalDownloadFunction: (url: string) => Promise; + + public static downloadJson(url: string): Promise{ + if(this.externalDownloadFunction !== undefined){ + return this.externalDownloadFunction(url) + } + + return new Promise( + (resolve, reject) => { + try{ + const xhr = new XMLHttpRequest(); + xhr.onload = () => { + if (xhr.status == 200) { + resolve(JSON.parse(xhr.response)) + } else { + reject(xhr.statusText) + } + }; + xhr.open('GET', url); + xhr.send(); + }catch(e){ + reject(e) + } + } + ) + + } /** * Triggers a 'download file' popup which will download the contents diff --git a/scripts/ScriptUtils.ts b/scripts/ScriptUtils.ts index 220c9e2809..86c3584146 100644 --- a/scripts/ScriptUtils.ts +++ b/scripts/ScriptUtils.ts @@ -2,11 +2,20 @@ import {lstatSync, readdirSync, readFileSync} from "fs"; import * as https from "https"; import {LayerConfigJson} from "../Customizations/JSON/LayerConfigJson"; import {LayoutConfigJson} from "../Customizations/JSON/LayoutConfigJson"; +import * as fs from "fs"; +import {Utils} from "../Utils"; export default class ScriptUtils { + + + public static fixUtils() { + Utils.externalDownloadFunction = ScriptUtils.DownloadJSON + } + + public static readDirRecSync(path, maxDepth = 999): string[] { const result = [] - if(maxDepth <= 0){ + if (maxDepth <= 0) { return [] } for (const entry of readdirSync(path)) { @@ -23,6 +32,20 @@ export default class ScriptUtils { return result; } + public static DownloadFileTo(url, targetFilePath: string): void { + console.log("Downloading ", url, "to", targetFilePath) + https.get(url, (res) => { + const filePath = fs.createWriteStream(targetFilePath); + res.pipe(filePath); + filePath.on('finish', () => { + filePath.close(); + console.log('Download Completed'); + }) + + + }) + } + public static DownloadJSON(url): Promise { return new Promise((resolve, reject) => { try { @@ -77,7 +100,7 @@ export default class ScriptUtils { }) } - public static getThemeFiles() : {parsed: LayoutConfigJson, path: string}[] { + public static getThemeFiles(): { parsed: LayoutConfigJson, path: string }[] { return ScriptUtils.readDirRecSync("./assets/themes") .filter(path => path.endsWith(".json")) .filter(path => path.indexOf("license_info.json") < 0) diff --git a/scripts/fixTheme.ts b/scripts/fixTheme.ts index b3d0404691..e11bce26e1 100644 --- a/scripts/fixTheme.ts +++ b/scripts/fixTheme.ts @@ -1,14 +1,18 @@ - /* * This script attempt to automatically fix some basic issues when a theme from the custom generator is loaded */ import {Utils} from "../Utils" Utils.runningFromConsole = true; + import {readFileSync, writeFileSync} from "fs"; import {LayoutConfigJson} from "../Customizations/JSON/LayoutConfigJson"; -import {Layer} from "leaflet"; import LayerConfig from "../Customizations/JSON/LayerConfig"; import SmallLicense from "../Models/smallLicense"; +import ScriptUtils from "./ScriptUtils"; +import AllImageProviders from "../Logic/ImageProviders/AllImageProviders"; + + +ScriptUtils.fixUtils() if(process.argv.length == 2){ console.log("USAGE: ts-node scripts/fixTheme ") @@ -22,7 +26,6 @@ console.log("Fixing up ", path) const themeConfigJson : LayoutConfigJson = JSON.parse(readFileSync(path, "UTF8")) -const linuxHints = [] const licenses : SmallLicense[] = [] const replacements: {source: string, destination: string}[] = [] @@ -40,15 +43,32 @@ for (const layerConfigJson of themeConfigJson.layers) { const layerConfig = new LayerConfig(layerConfigJson, true) const images : string[] = Array.from(layerConfig.ExtractImages()) const remoteImages = images.filter(img => img.startsWith("http")) + for (const remoteImage of remoteImages) { - linuxHints.push("wget " + remoteImage) + + + const filename = remoteImage.substring(remoteImage.lastIndexOf("/")) + ScriptUtils.DownloadFileTo(remoteImage, dir + "/" + filename) + + const imgPath = remoteImage.substring(remoteImage.lastIndexOf("/") + 1) - licenses.push({ - path: imgPath, - license: "", - authors: [], - sources: [remoteImage] - }) + + for (const attributionSrc of AllImageProviders.ImageAttributionSource) { + try { + attributionSrc.GetAttributionFor(remoteImage).addCallbackAndRun(license => { + console.log("Downloaded an attribution!") + licenses.push({ + path: imgPath, + license: license?.license ?? "", + authors:Utils.NoNull([license?.artist]), + sources: [remoteImage] + }) + }) + }catch(e){ + // Hush hush + } + } + replacements.push({source: remoteImage, destination: `${dir}/${imgPath}`}) } } @@ -58,13 +78,9 @@ for (const replacement of replacements) { fixedThemeJson = fixedThemeJson.replace(new RegExp(replacement.source, "g"), replacement.destination) } -const fixScriptPath = dir + "/fix_script_"+path.replace(/\//g,"_")+".sh" writeFileSync(dir + "/generated.license_info.json", JSON.stringify(licenses, null, " ")) -writeFileSync(fixScriptPath, linuxHints.join("\n")) writeFileSync(path+".autofixed.json", fixedThemeJson) console.log(`IMPORTANT: - 1) run ${fixScriptPath} - 2) Copy generated.license_info.json over into license_info.json and add the missing attributions and authors - 3) Verify ${path}.autofixed.json as theme, and rename it to ${path} - 4) Delete the fix script and other unneeded files`) \ No newline at end of file + 1) Copy generated.license_info.json over into license_info.json and add the missing attributions and authors + 2) Verify ${path}.autofixed.json as theme, and rename it to ${path}`) \ No newline at end of file diff --git a/test.ts b/test.ts index 1736e2aa98..cdc00c93f6 100644 --- a/test.ts +++ b/test.ts @@ -9,7 +9,7 @@ import {SlideShow} from "./UI/Image/SlideShow"; import {FixedUiElement} from "./UI/Base/FixedUiElement"; import Img from "./UI/Base/Img"; import {AttributedImage} from "./UI/Image/AttributedImage"; -import {Imgur} from "./Logic/Web/Imgur"; +import {Imgur} from "./Logic/ImageProviders/Imgur"; import ReviewForm from "./UI/Reviews/ReviewForm"; import {OsmConnection} from "./Logic/Osm/OsmConnection"; From c6e4ebc347bd88b01d257c80c7ae24ec0bc08b67 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Tue, 22 Jun 2021 14:29:22 +0200 Subject: [PATCH 03/18] Allow _ in preferences again, version bump --- Logic/Osm/OsmPreferences.ts | 2 +- Models/Constants.ts | 2 +- preferences.ts | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Logic/Osm/OsmPreferences.ts b/Logic/Osm/OsmPreferences.ts index cd688b89c7..bff50eae60 100644 --- a/Logic/Osm/OsmPreferences.ts +++ b/Logic/Osm/OsmPreferences.ts @@ -97,7 +97,7 @@ export class OsmPreferences { public GetPreference(key: string, prefix: string = "mapcomplete-"): UIEventSource { key = prefix + key; - key = key.replace(/[:\\\/"' {}.%_]/g, '') + key = key.replace(/[:\\\/"' {}.%]/g, '') if (key.length >= 255) { throw "Preferences: key length to big"; } diff --git a/Models/Constants.ts b/Models/Constants.ts index e572076d86..4bda2a0c32 100644 --- a/Models/Constants.ts +++ b/Models/Constants.ts @@ -2,7 +2,7 @@ import { Utils } from "../Utils"; export default class Constants { - public static vNumber = "0.8.0a"; + public static vNumber = "0.8.0b"; // The user journey states thresholds when a new feature gets unlocked public static userJourney = { diff --git a/preferences.ts b/preferences.ts index 4b1dce30ab..1c1773a143 100644 --- a/preferences.ts +++ b/preferences.ts @@ -3,7 +3,6 @@ import Combine from "./UI/Base/Combine"; import {Button} from "./UI/Base/Button"; import {TextField} from "./UI/Input/TextField"; import {FixedUiElement} from "./UI/Base/FixedUiElement"; -import {UIElement} from "./UI/UIElement"; import {UIEventSource} from "./Logic/UIEventSource"; import {Utils} from "./Utils"; import {SubtleButton} from "./UI/Base/SubtleButton"; From 57d1455a7d34c418e47adbb512f1f8d5087b1a38 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Tue, 22 Jun 2021 14:34:06 +0200 Subject: [PATCH 04/18] Add 'morescreen'-english texts again --- langs/en.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/langs/en.json b/langs/en.json index 23633c0c04..dda19d6008 100644 --- a/langs/en.json +++ b/langs/en.json @@ -72,6 +72,12 @@ "emailOf": "What is the email address of {category}?", "emailIs": "The email address of this {category} is {email}" }, + "morescreen": { + "intro": "

More thematic maps?

Do you enjoy collecting geodata?
There are more themes available.", + "requestATheme": "If you want a custom-built quest, request it in the issue tracker", + "streetcomplete": "Another, similar application is StreetComplete.", + "createYourOwnTheme": "Create your own MapComplete theme from scratch" + }, "sharescreen": { "intro": "

Share this map

Share this map by copying the link below and sending it to friends and family:", "addToHomeScreen": "

Add to your home screen

You can easily add this website to your smartphone home screen for a native feel. Click the 'add to home screen button' in the URL bar to do this.", From 3e343dcf88dbb3d21d62588f31f13e4adf1a6233 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Tue, 22 Jun 2021 15:12:18 +0200 Subject: [PATCH 05/18] Update contributors --- assets/contributors.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/contributors.json b/assets/contributors.json index 02c30c2ced..2b2edda4ff 100644 --- a/assets/contributors.json +++ b/assets/contributors.json @@ -1 +1 @@ -{"contributors":[{"contributor":"Pieter Vander Vennet", "commits":738},{"contributor":"pietervdvn", "commits":718},{"contributor":"Weblate", "commits":35},{"contributor":"Tobias", "commits":35},{"contributor":"Christian Neumann", "commits":33},{"contributor":"Win Olario", "commits":31},{"contributor":"Pieter Fiers", "commits":31},{"contributor":"Sebastian Kürten", "commits":16},{"contributor":"Marco", "commits":16},{"contributor":"Joost", "commits":16},{"contributor":"ToastHawaii", "commits":15},{"contributor":"J. Lavoie", "commits":14},{"contributor":"Bavo Vanderghote", "commits":12},{"contributor":"Artem", "commits":12},{"contributor":"Supaplex", "commits":9},{"contributor":"Jacque Fresco", "commits":9},{"contributor":"Midgard", "commits":8},{"contributor":"Mateusz Konieczny", "commits":8},{"contributor":"yopaseopor", "commits":7},{"contributor":"Flo Edelmann", "commits":7},{"contributor":"Binnette", "commits":7},{"contributor":"Allan Nordhøy", "commits":7},{"contributor":"pelderson", "commits":6},{"contributor":"lvgx", "commits":6},{"contributor":"dependabot[bot]", "commits":6},{"contributor":"Alexey Shabanov", "commits":6},{"contributor":"SiegbjornSitumeang", "commits":4},{"contributor":"Polgár Sándor", "commits":4},{"contributor":"Hiroshi Miura", "commits":4},{"contributor":"vankos", "commits":3},{"contributor":"Léo Villeveygoux", "commits":3},{"contributor":"JCGF-OSM", "commits":3},{"contributor":"Jan Zabel", "commits":3},{"contributor":"Hosted Weblate", "commits":3},{"contributor":"David Haberthür", "commits":3},{"contributor":"快乐的老鼠宝宝", "commits":2},{"contributor":"Wiktor Przybylski", "commits":2},{"contributor":"Vinicius", "commits":2},{"contributor":"Stanislas Gueniffey", "commits":2},{"contributor":"Robin van der Linde", "commits":2},{"contributor":"riiga", "commits":2},{"contributor":"pbarban", "commits":2},{"contributor":"mic140", "commits":2},{"contributor":"Leo Alcaraz", "commits":2},{"contributor":"Jose Luis Infante", "commits":2},{"contributor":"Heiko", "commits":2},{"contributor":"graveelius", "commits":2},{"contributor":"Tomas Fiers", "commits":1},{"contributor":"Thibault Molleman", "commits":1},{"contributor":"tbowdecl97", "commits":1},{"contributor":"Sebastian", "commits":1},{"contributor":"Sean Young", "commits":1},{"contributor":"Schouppe Joost", "commits":1},{"contributor":"Noémie", "commits":1},{"contributor":"mozita", "commits":1},{"contributor":"Michał Targoński", "commits":1},{"contributor":"Iváns", "commits":1},{"contributor":"Eric Armijo", "commits":1},{"contributor":"Damian Pułka", "commits":1},{"contributor":"Carlos Ramos Carreño", "commits":1},{"contributor":"Beardhatcode", "commits":1}]} \ No newline at end of file +{"contributors":[{"contributor":"pietervdvn", "commits":794},{"contributor":"Pieter Vander Vennet", "commits":744},{"contributor":"Weblate", "commits":38},{"contributor":"Tobias", "commits":35},{"contributor":"Christian Neumann", "commits":33},{"contributor":"Win Olario", "commits":31},{"contributor":"Pieter Fiers", "commits":31},{"contributor":"Sebastian Kürten", "commits":17},{"contributor":"Joost", "commits":17},{"contributor":"Marco", "commits":16},{"contributor":"Artem", "commits":16},{"contributor":"Allan Nordhøy", "commits":16},{"contributor":"ToastHawaii", "commits":15},{"contributor":"Supaplex", "commits":14},{"contributor":"J. Lavoie", "commits":14},{"contributor":"Bavo Vanderghote", "commits":12},{"contributor":"Jacque Fresco", "commits":9},{"contributor":"Midgard", "commits":8},{"contributor":"Mateusz Konieczny", "commits":8},{"contributor":"yopaseopor", "commits":7},{"contributor":"Hosted Weblate", "commits":7},{"contributor":"Flo Edelmann", "commits":7},{"contributor":"Binnette", "commits":7},{"contributor":"pelderson", "commits":6},{"contributor":"lvgx", "commits":6},{"contributor":"dependabot[bot]", "commits":6},{"contributor":"Alexey Shabanov", "commits":6},{"contributor":"SiegbjornSitumeang", "commits":4},{"contributor":"Polgár Sándor", "commits":4},{"contributor":"Hiroshi Miura", "commits":4},{"contributor":"Wiktor Przybylski", "commits":3},{"contributor":"vankos", "commits":3},{"contributor":"Léo Villeveygoux", "commits":3},{"contributor":"JCGF-OSM", "commits":3},{"contributor":"Jan Zabel", "commits":3},{"contributor":"Erik Palm", "commits":3},{"contributor":"David Haberthür", "commits":3},{"contributor":"快乐的老鼠宝宝", "commits":2},{"contributor":"Vinicius", "commits":2},{"contributor":"Stanislas Gueniffey", "commits":2},{"contributor":"Robin van der Linde", "commits":2},{"contributor":"riiga", "commits":2},{"contributor":"pbarban", "commits":2},{"contributor":"mic140", "commits":2},{"contributor":"Leo Alcaraz", "commits":2},{"contributor":"Jose Luis Infante", "commits":2},{"contributor":"Heiko", "commits":2},{"contributor":"graveelius", "commits":2},{"contributor":"Damian Tokarski", "commits":2},{"contributor":"Tomas Fiers", "commits":1},{"contributor":"Thibault Molleman", "commits":1},{"contributor":"tbowdecl97", "commits":1},{"contributor":"Sebastian", "commits":1},{"contributor":"Sean Young", "commits":1},{"contributor":"Schouppe Joost", "commits":1},{"contributor":"Noémie", "commits":1},{"contributor":"mozita", "commits":1},{"contributor":"Michał Targoński", "commits":1},{"contributor":"liimee", "commits":1},{"contributor":"Jeff Huang", "commits":1},{"contributor":"Iváns", "commits":1},{"contributor":"Eric Armijo", "commits":1},{"contributor":"Damian Pułka", "commits":1},{"contributor":"Carlos Ramos Carreño", "commits":1},{"contributor":"Beardhatcode", "commits":1}]} \ No newline at end of file From aacbb74cafdea1a07b198b1b26b90bfbea2a75ac Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Tue, 22 Jun 2021 19:00:30 +0200 Subject: [PATCH 06/18] Remove obsolete code fragment, remove w-min from label --- Customizations/JSON/LayerConfig.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Customizations/JSON/LayerConfig.ts b/Customizations/JSON/LayerConfig.ts index 10d8ddfe1b..9c143216db 100644 --- a/Customizations/JSON/LayerConfig.ts +++ b/Customizations/JSON/LayerConfig.ts @@ -361,12 +361,6 @@ export default class LayerConfig { const self = this; const mappedHtml = tags.map(tgs => { function genHtmlFromString(sourcePart: string): BaseUIElement { - if (sourcePart.indexOf("html:") == 0) { - // We use § as a replacement for ; - const html = sourcePart.substring("html:".length) - const inner = new FixedUiElement(SubstitutingTag.substituteString(html, tgs)).SetClass("block w-min text-center") - return new Combine([inner]).SetClass("flex flex-col items-center"); - } const style = `width:100%;height:100%;transform: rotate( ${rotation} );display:block;position: absolute; top: 0; left: 0`; let html: BaseUIElement = new FixedUiElement(``); @@ -428,7 +422,7 @@ export default class LayerConfig { try { const label = self.label?.GetRenderValue(tgs)?.Subs(tgs) - ?.SetClass("block w-min text-center") + ?.SetClass("block text-center") ?.SetStyle("margin-top: " + (iconH + 2) + "px") if (label !== undefined) { htmlParts.push(new Combine([label]).SetClass("flex flex-col items-center")) From a365172e0cb05adc23c60f099f6cbe34154b830c Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Wed, 23 Jun 2021 01:58:11 +0200 Subject: [PATCH 07/18] Add new direction indicators --- .../public_bookcase/public_bookcase.json | 1 + assets/svg/direction_gradient.svg | 113 +++++++++++++----- assets/svg/direction_masked.svg | 70 +++++++++++ assets/svg/direction_outline.svg | 72 +++++++++++ assets/svg/direction_stroke.svg | 72 +++++++++++ assets/tagRenderings/questions.json | 3 + 6 files changed, 299 insertions(+), 32 deletions(-) create mode 100644 assets/svg/direction_masked.svg create mode 100644 assets/svg/direction_outline.svg create mode 100644 assets/svg/direction_stroke.svg diff --git a/assets/layers/public_bookcase/public_bookcase.json b/assets/layers/public_bookcase/public_bookcase.json index f3db0d0b7b..a300a0f0e1 100644 --- a/assets/layers/public_bookcase/public_bookcase.json +++ b/assets/layers/public_bookcase/public_bookcase.json @@ -72,6 +72,7 @@ ], "tagRenderings": [ "images", + "minimap", { "render": { "en": "The name of this bookcase is {name}", diff --git a/assets/svg/direction_gradient.svg b/assets/svg/direction_gradient.svg index 54af33ba00..50b10be0ee 100644 --- a/assets/svg/direction_gradient.svg +++ b/assets/svg/direction_gradient.svg @@ -6,48 +6,97 @@ xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" - id="svg8" - version="1.1" - viewBox="0 0 100 100" - height="100" - width="100"> - - - - image/svg+xml - - - - + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.0" + width="860.50732pt" + height="860.50732pt" + viewBox="0 0 860.50732 860.50732" + preserveAspectRatio="xMidYMid meet" + id="svg14" + sodipodi:docname="direction_gradient.svg" + inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"> + id="defs18"> + inkscape:collect="always" + id="linearGradient832"> + id="stop828" /> + id="stop830" /> + + + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + image/svg+xml + + + + + + style="fill:url(#radialGradient838);fill-opacity:1;stroke:none;stroke-width:2.83464575;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 735.79979,124.70799 C 654.79116,43.598883 544.88842,-2.0206645 430.25389,-2.121103 315.61937,-2.0206592 205.71663,43.598888 124.70801,124.70799 l 305.54588,305.54589 z" + id="path836" + inkscape:connector-curvature="0" + sodipodi:nodetypes="ccccc" /> diff --git a/assets/svg/direction_masked.svg b/assets/svg/direction_masked.svg new file mode 100644 index 0000000000..8a591c2138 --- /dev/null +++ b/assets/svg/direction_masked.svg @@ -0,0 +1,70 @@ + + + + + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + image/svg+xml + + + + + + diff --git a/assets/svg/direction_outline.svg b/assets/svg/direction_outline.svg new file mode 100644 index 0000000000..679c50f380 --- /dev/null +++ b/assets/svg/direction_outline.svg @@ -0,0 +1,72 @@ + + + + + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + image/svg+xml + + + + + + + diff --git a/assets/svg/direction_stroke.svg b/assets/svg/direction_stroke.svg new file mode 100644 index 0000000000..af96495107 --- /dev/null +++ b/assets/svg/direction_stroke.svg @@ -0,0 +1,72 @@ + + + + + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + image/svg+xml + + + + + + + diff --git a/assets/tagRenderings/questions.json b/assets/tagRenderings/questions.json index 0ac2cd7f77..a7f632e394 100644 --- a/assets/tagRenderings/questions.json +++ b/assets/tagRenderings/questions.json @@ -5,6 +5,9 @@ "reviews": { "render": "{reviews()}" }, + "minimap": { + "render": "
{minimap(19, {id})}
" + }, "phone": { "question": { "en": "What is the phone number of {name}?", From 89df28ae066e0707125d1e5547ba6001b3112fe2 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Wed, 23 Jun 2021 02:14:15 +0200 Subject: [PATCH 08/18] Formatting, remove old, unused code --- UI/BaseUIElement.ts | 124 ++++++++++++++++++++++---------------------- 1 file changed, 62 insertions(+), 62 deletions(-) diff --git a/UI/BaseUIElement.ts b/UI/BaseUIElement.ts index a919f0609c..2d046a3dd2 100644 --- a/UI/BaseUIElement.ts +++ b/UI/BaseUIElement.ts @@ -3,25 +3,21 @@ import {UIEventSource} from "../Logic/UIEventSource"; /** * A thin wrapper around a html element, which allows to generate a HTML-element. - * + * * Assumes a read-only configuration, so it has no 'ListenTo' */ export default abstract class BaseUIElement { + protected _constructedHtmlElement: HTMLElement; private clss: Set = new Set(); private style: string; private _onClick: () => void; private _onHover: UIEventSource; - - protected _constructedHtmlElement: HTMLElement; - - protected abstract InnerConstructElement(): HTMLElement; - public onClick(f: (() => void)) { this._onClick = f; this.SetClass("clickable") - if(this._constructedHtmlElement !== undefined){ + if (this._constructedHtmlElement !== undefined) { this._constructedHtmlElement.onclick = f; } return this; @@ -38,12 +34,13 @@ export default abstract class BaseUIElement { element.removeChild(element.firstChild); } const el = this.ConstructElement(); - if(el !== undefined){ + if (el !== undefined) { element.appendChild(el) } - + return this; } + /** * Adds all the relevant classes, space seperated */ @@ -55,7 +52,7 @@ export default abstract class BaseUIElement { if (this.clss.has(clss)) { continue; } - if(c === undefined || c === ""){ + if (c === undefined || c === "") { continue; } this.clss.add(c); @@ -74,19 +71,19 @@ export default abstract class BaseUIElement { } return this; } - - public HasClass(clss: string): boolean{ + + public HasClass(clss: string): boolean { return this.clss.has(clss) } public SetStyle(style: string): BaseUIElement { this.style = style; - if(this._constructedHtmlElement !== undefined){ + if (this._constructedHtmlElement !== undefined) { this._constructedHtmlElement.style.cssText = style; } return this; } - + /** * The same as 'Render', but creates a HTML element instead of the HTML representation */ @@ -99,68 +96,71 @@ export default abstract class BaseUIElement { return this._constructedHtmlElement } - if(this.InnerConstructElement === undefined){ - throw "ERROR! This is not a correct baseUIElement: "+this.constructor.name + if (this.InnerConstructElement === undefined) { + throw "ERROR! This is not a correct baseUIElement: " + this.constructor.name } -try{ - + try { - const el = this.InnerConstructElement(); - if(el === undefined){ - return undefined; - } + const el = this.InnerConstructElement(); - this._constructedHtmlElement = el; - const style = this.style - if (style !== undefined && style !== "") { - el.style.cssText = style - } - if (this.clss.size > 0) { - try{ - el.classList.add(...Array.from(this.clss)) - }catch(e){ - console.error("Invalid class name detected in:", Array.from(this.clss).join(" "),"\nErr msg is ",e) + if (el === undefined) { + return undefined; } - } - if (this._onClick !== undefined) { - const self = this; - el.onclick = (e) => { - // @ts-ignore - if (e.consumed) { - return; + this._constructedHtmlElement = el; + const style = this.style + if (style !== undefined && style !== "") { + el.style.cssText = style + } + if (this.clss.size > 0) { + try { + el.classList.add(...Array.from(this.clss)) + } catch (e) { + console.error("Invalid class name detected in:", Array.from(this.clss).join(" "), "\nErr msg is ", e) } - self._onClick(); - // @ts-ignore - e.consumed = true; } - el.style.pointerEvents = "all"; - el.style.cursor = "pointer"; - } - if (this._onHover !== undefined) { - const self = this; - el.addEventListener('mouseover', () => self._onHover.setData(true)); - el.addEventListener('mouseout', () => self._onHover.setData(false)); - } + if (this._onClick !== undefined) { + const self = this; + el.onclick = (e) => { + // @ts-ignore + if (e.consumed) { + return; + } + self._onClick(); + // @ts-ignore + e.consumed = true; + } + el.style.pointerEvents = "all"; + el.style.cursor = "pointer"; + } - if (this._onHover !== undefined) { - const self = this; - el.addEventListener('mouseover', () => self._onHover.setData(true)); - el.addEventListener('mouseout', () => self._onHover.setData(false)); - } + if (this._onHover !== undefined) { + const self = this; + el.addEventListener('mouseover', () => self._onHover.setData(true)); + el.addEventListener('mouseout', () => self._onHover.setData(false)); + } - return el}catch(e){ + if (this._onHover !== undefined) { + const self = this; + el.addEventListener('mouseover', () => self._onHover.setData(true)); + el.addEventListener('mouseout', () => self._onHover.setData(false)); + } + + return el + } catch (e) { const domExc = e as DOMException; - if(domExc){ - console.log("An exception occured", domExc.code, domExc.message, domExc.name ) + if (domExc) { + console.log("An exception occured", domExc.code, domExc.message, domExc.name) } console.error(e) -} + } } - - public AsMarkdown(): string{ - throw "AsMarkdown is not implemented by "+this.constructor.name + + public AsMarkdown(): string { + throw "AsMarkdown is not implemented by " + this.constructor.name } + + protected abstract InnerConstructElement(): HTMLElement; } \ No newline at end of file From eba1772ab984334536e25e3914b63344bb6e90f2 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Wed, 23 Jun 2021 02:15:28 +0200 Subject: [PATCH 09/18] Add minimap baseElement; add this as special rendering, add minimap beneath direction input element --- InitUiElements.ts | 5 + Logic/Actors/AvailableBaseLayers.ts | 24 ++--- Models/BaseLayer.ts | 2 +- Svg.ts | 19 +++- UI/Base/Minimap.ts | 144 ++++++++++++++++++++++++++++ UI/BigComponents/Basemap.ts | 16 ++-- UI/Input/DirectionInput.ts | 32 +++++-- UI/Input/ValidatedTextField.ts | 98 +++---------------- UI/Popup/TagRenderingQuestion.ts | 3 +- UI/ShowDataLayer.ts | 33 ++++--- UI/SpecialVisualizations.ts | 56 ++++++++++- UI/SubstitutedTranslation.ts | 22 +++-- assets/tagRenderings/questions.json | 2 +- index.ts | 6 +- test.html | 1 + test.ts | 101 ++++++++++++++++--- 16 files changed, 411 insertions(+), 153 deletions(-) create mode 100644 UI/Base/Minimap.ts diff --git a/InitUiElements.ts b/InitUiElements.ts index a5b738e9ca..65a4485e06 100644 --- a/InitUiElements.ts +++ b/InitUiElements.ts @@ -352,6 +352,11 @@ export class InitUiElements { State.state.backgroundLayer = State.state.backgroundLayerId .map((selectedId: string) => { + if(selectedId === undefined){ + return AvailableBaseLayers.osmCarto + } + + const available = State.state.availableBackgroundLayers.data; for (const layer of available) { if (layer.id === selectedId) { diff --git a/Logic/Actors/AvailableBaseLayers.ts b/Logic/Actors/AvailableBaseLayers.ts index e9855a1cb8..1a025d5a02 100644 --- a/Logic/Actors/AvailableBaseLayers.ts +++ b/Logic/Actors/AvailableBaseLayers.ts @@ -14,18 +14,19 @@ import {Utils} from "../../Utils"; export default class AvailableBaseLayers { - public static osmCarto: BaseLayer = + public static osmCarto: BaseLayer = { id: "osm", - name: "OpenStreetMap", - layer: AvailableBaseLayers.CreateBackgroundLayer("osm", "OpenStreetMap", - "https://tile.openstreetmap.org/{z}/{x}/{y}.png", "OpenStreetMap", "https://openStreetMap.org/copyright", - 19, - false, false), + name: "OpenStreetMap", + layer: () => AvailableBaseLayers.CreateBackgroundLayer("osm", "OpenStreetMap", + "https://tile.openstreetmap.org/{z}/{x}/{y}.png", "OpenStreetMap", "https://openStreetMap.org/copyright", + 19, + false, false), feature: null, max_zoom: 19, min_zoom: 0 - } + } + public static layerOverview = AvailableBaseLayers.LoadRasterIndex().concat(AvailableBaseLayers.LoadProviderIndex()); @@ -123,7 +124,7 @@ export default class AvailableBaseLayers { continue } - const leafletLayer = AvailableBaseLayers.CreateBackgroundLayer( + const leafletLayer: () => TileLayer = () => AvailableBaseLayers.CreateBackgroundLayer( props.id, props.name, props.url, @@ -150,10 +151,10 @@ export default class AvailableBaseLayers { private static LoadProviderIndex(): BaseLayer[] { // @ts-ignore X; // Import X to make sure the namespace is not optimized away - function l(id: string, name: string) { + function l(id: string, name: string) : BaseLayer{ try { - const layer: any = L.tileLayer.provider(id, undefined); - return { + const layer: any = () => L.tileLayer.provider(id, undefined); + const baseLayer : BaseLayer = { feature: null, id: id, name: name, @@ -161,6 +162,7 @@ export default class AvailableBaseLayers { min_zoom: layer.minzoom, max_zoom: layer.maxzoom } + return baseLayer } catch (e) { console.error("Could not find provided layer", name, e); return null; diff --git a/Models/BaseLayer.ts b/Models/BaseLayer.ts index 63c3abbc15..01eb8e9d75 100644 --- a/Models/BaseLayer.ts +++ b/Models/BaseLayer.ts @@ -3,7 +3,7 @@ import {TileLayer} from "leaflet"; export default interface BaseLayer { id: string, name: string, - layer: TileLayer, + layer: () => TileLayer, max_zoom: number, min_zoom: number; feature: any, diff --git a/Svg.ts b/Svg.ts index 3f0ff50fe8..9a5c94b8f2 100644 --- a/Svg.ts +++ b/Svg.ts @@ -104,11 +104,26 @@ export default class Svg { public static direction_svg() { return new Img(Svg.direction, true);} public static direction_ui() { return new FixedUiElement(Svg.direction_img);} - public static direction_gradient = " image/svg+xml " + public static direction_gradient = " Created by potrace 1.15, written by Peter Selinger 2001-2017 image/svg+xml " public static direction_gradient_img = Img.AsImageElement(Svg.direction_gradient) public static direction_gradient_svg() { return new Img(Svg.direction_gradient, true);} public static direction_gradient_ui() { return new FixedUiElement(Svg.direction_gradient_img);} + public static direction_masked = " Created by potrace 1.15, written by Peter Selinger 2001-2017 image/svg+xml " + public static direction_masked_img = Img.AsImageElement(Svg.direction_masked) + public static direction_masked_svg() { return new Img(Svg.direction_masked, true);} + public static direction_masked_ui() { return new FixedUiElement(Svg.direction_masked_img);} + + public static direction_outline = " Created by potrace 1.15, written by Peter Selinger 2001-2017 image/svg+xml " + public static direction_outline_img = Img.AsImageElement(Svg.direction_outline) + public static direction_outline_svg() { return new Img(Svg.direction_outline, true);} + public static direction_outline_ui() { return new FixedUiElement(Svg.direction_outline_img);} + + public static direction_stroke = " Created by potrace 1.15, written by Peter Selinger 2001-2017 image/svg+xml " + public static direction_stroke_img = Img.AsImageElement(Svg.direction_stroke) + public static direction_stroke_svg() { return new Img(Svg.direction_stroke, true);} + public static direction_stroke_ui() { return new FixedUiElement(Svg.direction_stroke_img);} + public static down = " image/svg+xml " public static down_img = Img.AsImageElement(Svg.down) public static down_svg() { return new Img(Svg.down, true);} @@ -319,4 +334,4 @@ export default class Svg { public static wikipedia_svg() { return new Img(Svg.wikipedia, true);} public static wikipedia_ui() { return new FixedUiElement(Svg.wikipedia_img);} -public static All = {"SocialImageForeground.svg": Svg.SocialImageForeground,"add.svg": Svg.add,"addSmall.svg": Svg.addSmall,"ampersand.svg": Svg.ampersand,"arrow-left-smooth.svg": Svg.arrow_left_smooth,"arrow-right-smooth.svg": Svg.arrow_right_smooth,"back.svg": Svg.back,"bug.svg": Svg.bug,"camera-plus.svg": Svg.camera_plus,"checkmark.svg": Svg.checkmark,"circle.svg": Svg.circle,"clock.svg": Svg.clock,"close.svg": Svg.close,"compass.svg": Svg.compass,"cross_bottom_right.svg": Svg.cross_bottom_right,"crosshair-blue-center.svg": Svg.crosshair_blue_center,"crosshair-blue.svg": Svg.crosshair_blue,"crosshair.svg": Svg.crosshair,"delete_icon.svg": Svg.delete_icon,"direction.svg": Svg.direction,"direction_gradient.svg": Svg.direction_gradient,"down.svg": Svg.down,"envelope.svg": Svg.envelope,"floppy.svg": Svg.floppy,"gear.svg": Svg.gear,"help.svg": Svg.help,"home.svg": Svg.home,"home_white_bg.svg": Svg.home_white_bg,"josm_logo.svg": Svg.josm_logo,"layers.svg": Svg.layers,"layersAdd.svg": Svg.layersAdd,"logo.svg": Svg.logo,"logout.svg": Svg.logout,"mapcomplete_logo.svg": Svg.mapcomplete_logo,"mapillary.svg": Svg.mapillary,"mapillary_black.svg": Svg.mapillary_black,"min.svg": Svg.min,"no_checkmark.svg": Svg.no_checkmark,"or.svg": Svg.or,"osm-copyright.svg": Svg.osm_copyright,"osm-logo-us.svg": Svg.osm_logo_us,"osm-logo.svg": Svg.osm_logo,"pencil.svg": Svg.pencil,"phone.svg": Svg.phone,"pin.svg": Svg.pin,"plus.svg": Svg.plus,"pop-out.svg": Svg.pop_out,"reload.svg": Svg.reload,"ring.svg": Svg.ring,"search.svg": Svg.search,"send_email.svg": Svg.send_email,"share.svg": Svg.share,"square.svg": Svg.square,"star.svg": Svg.star,"star_half.svg": Svg.star_half,"star_outline.svg": Svg.star_outline,"star_outline_half.svg": Svg.star_outline_half,"statistics.svg": Svg.statistics,"translate.svg": Svg.translate,"up.svg": Svg.up,"wikidata.svg": Svg.wikidata,"wikimedia-commons-white.svg": Svg.wikimedia_commons_white,"wikipedia.svg": Svg.wikipedia};} +public static All = {"SocialImageForeground.svg": Svg.SocialImageForeground,"add.svg": Svg.add,"addSmall.svg": Svg.addSmall,"ampersand.svg": Svg.ampersand,"arrow-left-smooth.svg": Svg.arrow_left_smooth,"arrow-right-smooth.svg": Svg.arrow_right_smooth,"back.svg": Svg.back,"bug.svg": Svg.bug,"camera-plus.svg": Svg.camera_plus,"checkmark.svg": Svg.checkmark,"circle.svg": Svg.circle,"clock.svg": Svg.clock,"close.svg": Svg.close,"compass.svg": Svg.compass,"cross_bottom_right.svg": Svg.cross_bottom_right,"crosshair-blue-center.svg": Svg.crosshair_blue_center,"crosshair-blue.svg": Svg.crosshair_blue,"crosshair.svg": Svg.crosshair,"delete_icon.svg": Svg.delete_icon,"direction.svg": Svg.direction,"direction_gradient.svg": Svg.direction_gradient,"direction_masked.svg": Svg.direction_masked,"direction_outline.svg": Svg.direction_outline,"direction_stroke.svg": Svg.direction_stroke,"down.svg": Svg.down,"envelope.svg": Svg.envelope,"floppy.svg": Svg.floppy,"gear.svg": Svg.gear,"help.svg": Svg.help,"home.svg": Svg.home,"home_white_bg.svg": Svg.home_white_bg,"josm_logo.svg": Svg.josm_logo,"layers.svg": Svg.layers,"layersAdd.svg": Svg.layersAdd,"logo.svg": Svg.logo,"logout.svg": Svg.logout,"mapcomplete_logo.svg": Svg.mapcomplete_logo,"mapillary.svg": Svg.mapillary,"mapillary_black.svg": Svg.mapillary_black,"min.svg": Svg.min,"no_checkmark.svg": Svg.no_checkmark,"or.svg": Svg.or,"osm-copyright.svg": Svg.osm_copyright,"osm-logo-us.svg": Svg.osm_logo_us,"osm-logo.svg": Svg.osm_logo,"pencil.svg": Svg.pencil,"phone.svg": Svg.phone,"pin.svg": Svg.pin,"plus.svg": Svg.plus,"pop-out.svg": Svg.pop_out,"reload.svg": Svg.reload,"ring.svg": Svg.ring,"search.svg": Svg.search,"send_email.svg": Svg.send_email,"share.svg": Svg.share,"square.svg": Svg.square,"star.svg": Svg.star,"star_half.svg": Svg.star_half,"star_outline.svg": Svg.star_outline,"star_outline_half.svg": Svg.star_outline_half,"statistics.svg": Svg.statistics,"translate.svg": Svg.translate,"up.svg": Svg.up,"wikidata.svg": Svg.wikidata,"wikimedia-commons-white.svg": Svg.wikimedia_commons_white,"wikipedia.svg": Svg.wikipedia};} diff --git a/UI/Base/Minimap.ts b/UI/Base/Minimap.ts new file mode 100644 index 0000000000..de7de17186 --- /dev/null +++ b/UI/Base/Minimap.ts @@ -0,0 +1,144 @@ +import BaseUIElement from "../BaseUIElement"; +import * as L from "leaflet"; +import {UIEventSource} from "../../Logic/UIEventSource"; +import Loc from "../../Models/Loc"; +import BaseLayer from "../../Models/BaseLayer"; +import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers"; +import {Map} from "leaflet"; + +export default class Minimap extends BaseUIElement { + + private static _nextId = 0; + public readonly leafletMap: UIEventSource = new UIEventSource(undefined) + private readonly _id: string; + private readonly _background: UIEventSource; + private readonly _location: UIEventSource; + private _isInited = false; + private _allowMoving: boolean; + + constructor(options?: { + background?: UIEventSource, + location?: UIEventSource, + allowMoving?: boolean + } + ) { + super() + options = options ?? {} + this._background = options?.background ?? new UIEventSource(AvailableBaseLayers.osmCarto) + this._location = options?.location ?? new UIEventSource(undefined) + this._id = "minimap" + Minimap._nextId; + this._allowMoving = options.allowMoving ?? true; + Minimap._nextId++ + + } + + protected InnerConstructElement(): HTMLElement { + const div = document.createElement("div") + div.id = this._id; + div.style.height = "100%" + div.style.width = "100%" + div.style.minWidth = "40px" + div.style.minHeight = "40px" + const wrapper = document.createElement("div") + wrapper.appendChild(div) + const self = this; + // @ts-ignore + const resizeObserver = new ResizeObserver(_ => { + console.log("Change in size detected!") + self.InitMap(); + self.leafletMap?.data?.invalidateSize() + }); + + resizeObserver.observe(div); + return wrapper; + + } + + private InitMap() { + if (this._constructedHtmlElement === undefined) { + // This element isn't initialized yet + return; + } + + if (document.getElementById(this._id) === null) { + // not yet attached, we probably got some other event + return; + } + + if (this._isInited) { + return; + } + this._isInited = true; + const location = this._location; + + let currentLayer = this._background.data.layer() + const map = L.map(this._id, { + center: [location.data?.lat ?? 0, location.data?.lon ?? 0], + zoom: location.data?.zoom ?? 2, + layers: [currentLayer], + zoomControl: false, + attributionControl: false, + dragging: this._allowMoving, + scrollWheelZoom: this._allowMoving, + doubleClickZoom: this._allowMoving, + keyboard: this._allowMoving, + touchZoom: this._allowMoving + }); + + map.setMaxBounds( + [[-100, -200], [100, 200]] + ); + + this._background.addCallbackAndRun(layer => { + const newLayer = layer.layer() + if (currentLayer !== undefined) { + map.removeLayer(currentLayer); + } + currentLayer = newLayer; + map.addLayer(newLayer); + }) + + + let isRecursing = false; + map.on("moveend", function () { + if (isRecursing) { + return + } + if (map.getZoom() === location.data.zoom && + map.getCenter().lat === location.data.lat && + map.getCenter().lng === location.data.lon) { + return; + } + console.trace(map.getZoom(), map.getCenter(), location.data) + + location.data.zoom = map.getZoom(); + location.data.lat = map.getCenter().lat; + location.data.lon = map.getCenter().lng; + isRecursing = true; + location.ping(); + isRecursing = false; // This is ugly, I know + }) + + + location.addCallback(loc => { + const mapLoc = map.getCenter() + const dlat = Math.abs(loc.lat - mapLoc[0]) + const dlon = Math.abs(loc.lon - mapLoc[1]) + + if (dlat < 0.000001 && dlon < 0.000001 && map.getZoom() === loc.zoom) { + return; + } + map.setView([loc.lat, loc.lon], loc.zoom) + }) + + location.map(loc => loc.zoom) + .addCallback(zoom => { + if (Math.abs(map.getZoom() - zoom) > 0.1) { + map.setZoom(zoom, {}); + } + }) + + + this.leafletMap.setData(map) + } +} \ No newline at end of file diff --git a/UI/BigComponents/Basemap.ts b/UI/BigComponents/Basemap.ts index 5c2844bd0f..4ad5bc8f6a 100644 --- a/UI/BigComponents/Basemap.ts +++ b/UI/BigComponents/Basemap.ts @@ -14,10 +14,14 @@ export class Basemap { currentLayer: UIEventSource, lastClickLocation?: UIEventSource<{ lat: number, lon: number }>, extraAttribution?: BaseUIElement) { + + console.log("Currentlayer is" ,currentLayer, currentLayer.data, currentLayer.data?.id) + let previousLayer = currentLayer.data.layer(); + this.map = L.map(leafletElementId, { center: [location.data.lat ?? 0, location.data.lon ?? 0], zoom: location.data.zoom ?? 2, - layers: [currentLayer.data.layer], + layers: [previousLayer], zoomControl: false, attributionControl: extraAttribution !== undefined }); @@ -42,16 +46,16 @@ export class Basemap { extraAttribution.AttachTo('leaflet-attribution') const self = this; - let previousLayer = currentLayer.data; currentLayer.addCallbackAndRun(layer => { - if (layer === previousLayer) { + const newLayer = layer.layer() + if (newLayer === previousLayer) { return; } if (previousLayer !== undefined) { - self.map.removeLayer(previousLayer.layer); + self.map.removeLayer(previousLayer); } - previousLayer = layer; - self.map.addLayer(layer.layer); + previousLayer = newLayer; + self.map.addLayer(newLayer); }) diff --git a/UI/Input/DirectionInput.ts b/UI/Input/DirectionInput.ts index 4be40b99e3..c356d34a85 100644 --- a/UI/Input/DirectionInput.ts +++ b/UI/Input/DirectionInput.ts @@ -2,21 +2,30 @@ import {InputElement} from "./InputElement"; import {UIEventSource} from "../../Logic/UIEventSource"; import Combine from "../Base/Combine"; import Svg from "../../Svg"; +import BaseUIElement from "../BaseUIElement"; import {FixedUiElement} from "../Base/FixedUiElement"; +import {Utils} from "../../Utils"; +import Loc from "../../Models/Loc"; /** * Selects a direction in degrees */ export default class DirectionInput extends InputElement { + public static constructMinimap: ((any) => BaseUIElement); + private readonly _location: UIEventSource; public readonly IsSelected: UIEventSource = new UIEventSource(false); private readonly value: UIEventSource; + private background; - constructor(value?: UIEventSource) { + constructor(mapBackground: UIEventSource, + location: UIEventSource, + value?: UIEventSource) { super(); + this._location = location; this.value = value ?? new UIEventSource(undefined); - + this.background = mapBackground; } GetValue(): UIEventSource { @@ -30,16 +39,23 @@ export default class DirectionInput extends InputElement { protected InnerConstructElement(): HTMLElement { + let map: BaseUIElement = new FixedUiElement("") + if (!Utils.runningFromConsole) { + map = DirectionInput.constructMinimap({ + background: this.background, + allowMoving: false, + location: this._location + }) + } const element = new Combine([ - new FixedUiElement("").SetClass("w-full h-full absolute top-0 left-O rounded-full"), - Svg.direction_svg().SetStyle( + Svg.direction_stroke_svg().SetStyle( `position: absolute;top: 0;left: 0;width: 100%;height: 100%;transform:rotate(${this.value.data ?? 0}deg);`) - .SetClass("direction-svg"), - Svg.compass_svg().SetStyle( - "position: absolute;top: 0;left: 0;width: 100%;height: 100%;") + .SetClass("direction-svg relative") + .SetStyle("z-index: 1000"), + map.SetClass("w-full h-full absolute top-0 left-O rounded-full overflow-hidden"), ]) - .SetStyle("position:relative;display:block;width: min(100%, 25em); padding-top: min(100% , 25em); background:white; border: 1px solid black; border-radius: 999em") + .SetStyle("position:relative;display:block;width: min(100%, 25em); height: min(100% , 25em); background:white; border: 1px solid black; border-radius: 999em") .ConstructElement() diff --git a/UI/Input/ValidatedTextField.ts b/UI/Input/ValidatedTextField.ts index 51b24b2e4b..f1ce4dd690 100644 --- a/UI/Input/ValidatedTextField.ts +++ b/UI/Input/ValidatedTextField.ts @@ -1,7 +1,6 @@ import {DropDown} from "./DropDown"; import * as EmailValidator from "email-validator"; import {parsePhoneNumberFromString} from "libphonenumber-js"; -import InputElementMap from "./InputElementMap"; import {InputElement} from "./InputElement"; import {TextField} from "./TextField"; import {UIElement} from "../UIElement"; @@ -12,6 +11,7 @@ import OpeningHoursInput from "../OpeningHours/OpeningHoursInput"; import DirectionInput from "./DirectionInput"; import ColorPicker from "./ColorPicker"; import {Utils} from "../../Utils"; +import Loc from "../../Models/Loc"; interface TextFieldDef { name: string, @@ -19,7 +19,8 @@ interface TextFieldDef { isValid: ((s: string, country?: () => string) => boolean), reformat?: ((s: string, country?: () => string) => string), inputHelper?: (value: UIEventSource, options?: { - location: [number, number] + location: [number, number], + mapBackgroundLayer?: UIEventSource }) => InputElement, inputmode?: string @@ -118,8 +119,12 @@ export default class ValidatedTextField { str = "" + str; return str !== undefined && str.indexOf(".") < 0 && !isNaN(Number(str)) && Number(str) >= 0 && Number(str) <= 360 }, str => str, - (value) => { - return new DirectionInput(value); + (value, options) => { + return new DirectionInput(options.mapBackgroundLayer , new UIEventSource({ + lat: options.location[0], + lon: options.location[1], + zoom: 19 + }),value); }, "numeric" ), @@ -235,7 +240,8 @@ export default class ValidatedTextField { textAreaRows?: number, isValid?: ((s: string, country: () => string) => boolean), country?: () => string, - location?: [number /*lat*/, number /*lon*/] + location?: [number /*lat*/, number /*lon*/], + mapBackgroundLayer?: UIEventSource }): InputElement { options = options ?? {}; options.placeholder = options.placeholder ?? type; @@ -269,87 +275,12 @@ export default class ValidatedTextField { if (tp.inputHelper) { input = new CombinedInputElement(input, tp.inputHelper(input.GetValue(), { - location: options.location + location: options.location, + mapBackgroundLayer: options.mapBackgroundLayer })); } return input; } - - public static NumberInput(type: string = "int", extraValidation: (number: Number) => boolean = undefined): InputElement { - const isValid = ValidatedTextField.AllTypes[type].isValid; - extraValidation = extraValidation ?? (() => true) - - const fromString = str => { - if (!isValid(str)) { - return undefined; - } - const n = Number(str); - if (!extraValidation(n)) { - return undefined; - } - return n; - }; - const toString = num => { - if (num === undefined) { - return undefined; - } - return "" + num; - }; - const textField = ValidatedTextField.InputForType(type); - return new InputElementMap(textField, (n0, n1) => n0 === n1, fromString, toString) - } - - public static KeyInput(allowEmpty: boolean = false): InputElement { - - function fromString(str) { - if (str?.match(/^[a-zA-Z][a-zA-Z0-9:_-]*$/)) { - return str; - } - if (str === "" && allowEmpty) { - return ""; - } - - return undefined - } - - const toString = str => str - - function isSame(str0, str1) { - return str0 === str1; - } - - const textfield = new TextField({ - placeholder: "key", - isValid: str => fromString(str) !== undefined, - value: new UIEventSource("") - }); - - return new InputElementMap(textfield, isSame, fromString, toString); - } - - static Mapped(fromString: (str) => T, toString: (T) => string, options?: { - placeholder?: string | UIElement, - type?: string, - value?: UIEventSource, - startValidated?: boolean, - textArea?: boolean, - textAreaRows?: number, - isValid?: ((string: string) => boolean), - country?: () => string - }): InputElement { - let textField: InputElement; - if (options?.type) { - textField = ValidatedTextField.InputForType(options.type, options); - } else { - textField = new TextField(options); - } - return new InputElementMap( - textField, (a, b) => a === b, - fromString, toString - ); - - } - public static HelpText(): string { const explanations = ValidatedTextField.tpList.map(type => ["## " + type.name, "", type.explanation].join("\n")).join("\n\n") return "# Available types for text fields\n\nThe listed types here trigger a special input element. Use them in `tagrendering.freeform.type` of your tagrendering to activate them\n\n" + explanations @@ -360,7 +291,8 @@ export default class ValidatedTextField { isValid?: ((s: string, country?: () => string) => boolean), reformat?: ((s: string, country?: () => string) => string), inputHelper?: (value: UIEventSource, options?: { - location: [number, number] + location: [number, number], + mapBackgroundLayer: UIEventSource }) => InputElement, inputmode?: string): TextFieldDef { diff --git a/UI/Popup/TagRenderingQuestion.ts b/UI/Popup/TagRenderingQuestion.ts index 2b6dd7bc46..57c51ac5dc 100644 --- a/UI/Popup/TagRenderingQuestion.ts +++ b/UI/Popup/TagRenderingQuestion.ts @@ -326,7 +326,8 @@ export default class TagRenderingQuestion extends UIElement { const textField = ValidatedTextField.InputForType(this._configuration.freeform.type, { isValid: (str) => (str.length <= 255), country: () => this._tags.data._country, - location: [this._tags.data._lat, this._tags.data._lon] + location: [this._tags.data._lat, this._tags.data._lon], + mapBackgroundLayer: State.state.backgroundLayer }); textField.GetValue().setData(this._tags.data[this._configuration.freeform.key]); diff --git a/UI/ShowDataLayer.ts b/UI/ShowDataLayer.ts index 77e5eff9b8..b383e19259 100644 --- a/UI/ShowDataLayer.ts +++ b/UI/ShowDataLayer.ts @@ -15,13 +15,15 @@ export default class ShowDataLayer { private _layerDict; private readonly _leafletMap: UIEventSource; private _cleanCount = 0; + private readonly _enablePopups: boolean; constructor(features: UIEventSource<{ feature: any, freshness: Date }[]>, leafletMap: UIEventSource, - layoutToUse: UIEventSource) { + layoutToUse: UIEventSource, + enablePopups= true) { this._leafletMap = leafletMap; + this._enablePopups = enablePopups; const self = this; - const mp = leafletMap.data; self._layerDict = {}; layoutToUse.addCallbackAndRun(layoutToUse => { @@ -39,7 +41,9 @@ export default class ShowDataLayer { if (features.data === undefined) { return; } - if (leafletMap.data === undefined) { + const mp = leafletMap.data; + + if(mp === undefined){ return; } @@ -119,6 +123,11 @@ export default class ShowDataLayer { // No popup action defined -> Don't do anything return; } + if(!this._enablePopups){ + // Probably a map in the popup - no popups needed! + return; + } + const popup = L.popup({ autoPan: true, closeOnEscapeKey: true, @@ -171,15 +180,15 @@ export default class ShowDataLayer { } private CreateGeojsonLayer(): L.Layer { - const self = this; - const data = { - type: "FeatureCollection", - features: [] - } - // @ts-ignore - return L.geoJSON(data, { - style: feature => self.createStyleFor(feature), - pointToLayer: (feature, latLng) => self.pointToLayer(feature, latLng), + const self = this; + const data = { + type: "FeatureCollection", + features: [] + } + // @ts-ignore + return L.geoJSON(data, { + style: feature => self.createStyleFor(feature), + pointToLayer: (feature, latLng) => self.pointToLayer(feature, latLng), onEachFeature: (feature, leafletLayer) => self.postProcessFeature(feature, leafletLayer) }); diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts index 1ee996af19..1375a4ff8a 100644 --- a/UI/SpecialVisualizations.ts +++ b/UI/SpecialVisualizations.ts @@ -21,10 +21,13 @@ import LayerConfig from "../Customizations/JSON/LayerConfig"; import Title from "./Base/Title"; import Table from "./Base/Table"; import Histogram from "./BigComponents/Histogram"; +import Loc from "../Models/Loc"; +import ShowDataLayer from "./ShowDataLayer"; +import Minimap from "./Base/Minimap"; export default class SpecialVisualizations { - + public static specialVisualizations: { funcName: string, constr: ((state: State, tagSource: UIEventSource, argument: string[]) => BaseUIElement), @@ -32,7 +35,6 @@ export default class SpecialVisualizations { example?: string, args: { name: string, defaultValue?: string, doc: string }[] }[] = - [{ funcName: "all_tags", docs: "Prints all key-value pairs of the object - used for debugging", @@ -85,7 +87,57 @@ export default class SpecialVisualizations { return new ImageUploadFlow(tags, args[0]) } }, + { + funcName: "minimap", + docs: "A small map showing the selected feature. Note that no styling is applied, wrap this in a div", + args: [ + { + doc: "The zoomlevel: the higher, the more zoomed in with 1 being the entire world and 19 being really close", + name: "zoomlevel", + defaultValue: "18" + } + ], + example: "`{minimap()}`", + constr: (state, tagSource, args) => { + const properties = tagSource.data; + const feature = state.allElements.ContainingFeatures.get(properties.id) + let zoom = 18 + if(args[0] ){ + const parsed = Number(args[0]) + if(!isNaN(parsed) && parsed > 0 && parsed < 25){ + zoom = parsed; + } + } + const minimap = new Minimap( + { + background: state.backgroundLayer, + location: new UIEventSource({ + lat: Number(properties._lat), + lon: Number(properties._lon), + zoom: zoom + }), + allowMoving: false + } + ) + + new ShowDataLayer( + new UIEventSource<{ feature: any; freshness: Date }[]>([ + { + freshness: new Date(), + feature: feature + } + ]), + minimap.leafletMap, + State.state.layoutToUse, + false + ) + + minimap.SetStyle("overflow: hidden; pointer-events: none;") + return minimap; + + } + }, { funcName: "reviews", docs: "Adds an overview of the mangrove-reviews of this object. Mangrove.Reviews needs - in order to identify the reviewed object - a coordinate and a name. By default, the name of the object is given, but this can be overwritten", diff --git a/UI/SubstitutedTranslation.ts b/UI/SubstitutedTranslation.ts index c99699dda4..2c7f573adf 100644 --- a/UI/SubstitutedTranslation.ts +++ b/UI/SubstitutedTranslation.ts @@ -17,14 +17,14 @@ export class SubstitutedTranslation extends VariableUiElement { super( tagsSource.map(tags => { const txt = Utils.SubstituteKeys(translation.txt, tags) - if (txt === undefined) { + if (txt === undefined) { return undefined } - return new Combine(SubstitutedTranslation.EvaluateSpecialComponents(txt, tagsSource)) + return new Combine(SubstitutedTranslation.EvaluateSpecialComponents(txt, tagsSource)) }, [Locale.language]) ) - - + + this.SetClass("w-full") } @@ -34,13 +34,14 @@ export class SubstitutedTranslation extends VariableUiElement { for (const knownSpecial of SpecialVisualizations.specialVisualizations) { // Note: the '.*?' in the regex reads as 'any character, but in a non-greedy way' - const matched = template.match(`(.*){${knownSpecial.funcName}\\((.*?)\\)}(.*)`); + const matched = template.match(`(.*){${knownSpecial.funcName}\\((.*?)\\)(:.*)?}(.*)`); if (matched != null) { // We found a special component that should be brought to live const partBefore = SubstitutedTranslation.EvaluateSpecialComponents(matched[1], tags); const argument = matched[2].trim(); - const partAfter = SubstitutedTranslation.EvaluateSpecialComponents(matched[3], tags); + const style = matched[3] ?? "" + const partAfter = SubstitutedTranslation.EvaluateSpecialComponents(matched[4], tags); try { const args = knownSpecial.args.map(arg => arg.defaultValue ?? ""); if (argument.length > 0) { @@ -56,13 +57,14 @@ export class SubstitutedTranslation extends VariableUiElement { let element: BaseUIElement = new FixedUiElement(`Constructing ${knownSpecial}(${args.join(", ")})`) - try{ - element = knownSpecial.constr(State.state, tags, args); - }catch(e){ + try { + element = knownSpecial.constr(State.state, tags, args); + element.SetStyle(style) + } catch (e) { console.error("SPECIALRENDERING FAILED for", tags.data.id, e) element = new FixedUiElement(`Could not generate special rendering for ${knownSpecial}(${args.join(", ")}) ${e}`).SetClass("alert") } - + return [...partBefore, element, ...partAfter] } catch (e) { console.error(e); diff --git a/assets/tagRenderings/questions.json b/assets/tagRenderings/questions.json index a7f632e394..16eb121e3c 100644 --- a/assets/tagRenderings/questions.json +++ b/assets/tagRenderings/questions.json @@ -6,7 +6,7 @@ "render": "{reviews()}" }, "minimap": { - "render": "
{minimap(19, {id})}
" + "render": "{minimap(19, id): width:100%; height:6rem; border-radius:999rem; overflow: hidden; pointer-events: none;}" }, "phone": { "question": { diff --git a/index.ts b/index.ts index 472fb943fb..83abe95d48 100644 --- a/index.ts +++ b/index.ts @@ -14,10 +14,12 @@ import Translations from "./UI/i18n/Translations"; import CountryCoder from "latlon2country" import SimpleMetaTagger from "./Logic/SimpleMetaTagger"; +import Minimap from "./UI/Base/Minimap"; +import DirectionInput from "./UI/Input/DirectionInput"; -// Workaround for a stupid crash: inject the function +// Workaround for a stupid crash: inject some functions which would give stupid circular dependencies or crash the other nodejs scripts SimpleMetaTagger.coder = new CountryCoder("https://pietervdvn.github.io/latlon2country/"); - +DirectionInput.constructMinimap = options => new Minimap(options) let defaultLayout = "" // --------------------- Special actions based on the parameters ----------------- diff --git a/test.html b/test.html index 839114efe4..8b6c44878d 100644 --- a/test.html +++ b/test.html @@ -3,6 +3,7 @@ Small tests + diff --git a/test.ts b/test.ts index cdc00c93f6..3bd1a5f99b 100644 --- a/test.ts +++ b/test.ts @@ -10,11 +10,15 @@ import {FixedUiElement} from "./UI/Base/FixedUiElement"; import Img from "./UI/Base/Img"; import {AttributedImage} from "./UI/Image/AttributedImage"; import {Imgur} from "./Logic/ImageProviders/Imgur"; -import ReviewForm from "./UI/Reviews/ReviewForm"; -import {OsmConnection} from "./Logic/Osm/OsmConnection"; +import Minimap from "./UI/Base/Minimap"; +import Loc from "./Models/Loc"; +import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers"; +import ShowDataLayer from "./UI/ShowDataLayer"; +import LayoutConfig from "./Customizations/JSON/LayoutConfig"; +import {AllKnownLayouts} from "./Customizations/AllKnownLayouts"; -function TestSlideshow(){ +function TestSlideshow() { const elems = new UIEventSource([ new FixedUiElement("A"), new FixedUiElement("qmsldkfjqmlsdkjfmqlskdjfmqlksdf").SetClass("text-xl"), @@ -25,17 +29,17 @@ function TestSlideshow(){ new SlideShow(elems).AttachTo("maindiv") } -function TestTagRendering(){ +function TestTagRendering() { State.state = new State(undefined) const tagsSource = new UIEventSource({ - id:"node/1" + id: "node/1" }) new TagRenderingQuestion( tagsSource, new TagRenderingConfig({ multiAnswer: false, freeform: { - key:"valve" + key: "valve" }, question: "What valves are supported?", render: "This pump supports {valve}", @@ -45,25 +49,94 @@ function TestTagRendering(){ then: "This pump supports dunlop" }, { - if:"valve=shrader", - then:"shrader is supported", + if: "valve=shrader", + then: "shrader is supported", } ], - + }, undefined, "test") ).AttachTo("maindiv") new VariableUiElement(tagsSource.map(tags => tags["valves"])).SetClass("alert").AttachTo("extradiv") } -function TestAllInputMethods(){ +function TestAllInputMethods() { new Combine(ValidatedTextField.tpList.map(tp => { const tf = ValidatedTextField.InputForType(tp.name); return new Combine([tf, new VariableUiElement(tf.GetValue()).SetClass("alert")]); - })).AttachTo("maindiv") + })).AttachTo("maindiv") } -new ReviewForm(() => { - return undefined; -}, new OsmConnection(true, new UIEventSource(undefined), "test")).AttachTo("maindiv"); \ No newline at end of file + +const location = new UIEventSource({ + lon: 4.84771728515625, + lat: 51.17920846421931, + zoom: 14 +}) +const map0 = new Minimap({ + location: location, + allowMoving: true, + background: new AvailableBaseLayers(location).availableEditorLayers.map(layers => layers[2]) +}) +map0.SetStyle("width: 500px; height: 250px; overflow: hidden; border: 2px solid red") + .AttachTo("maindiv") + +const layout = AllKnownLayouts.layoutsList[1] +State.state = new State(layout) +console.log("LAYOUT is", layout.id) + +const feature = { + "type": "Feature", + _matching_layer_id: "bike_repair_station", + "properties": { + id: "node/-1", + "amenity": "bicycle_repair_station" + }, + "geometry": { + "type": "Point", + "coordinates": [ + 4.84771728515625, + 51.17920846421931 + ] + } + } + +; + +State.state.allElements.addOrGetElement(feature) + +const featureSource = new UIEventSource([{ + freshness: new Date(), + feature: feature +}]) + +new ShowDataLayer( + featureSource, + map0.leafletMap, + new UIEventSource(layout) +) + +const map1 = new Minimap({ + location: location, + allowMoving: true, + background: new AvailableBaseLayers(location).availableEditorLayers.map(layers => layers[5]) + }, +) + +map1.SetStyle("width: 500px; height: 250px; overflow: hidden; border : 2px solid black") + .AttachTo("extradiv") + + + + + +new ShowDataLayer( + featureSource, + map1.leafletMap, + new UIEventSource(layout) +) + +featureSource.ping() + +// */ \ No newline at end of file From c13e6727c36945b694fcc240f0af31a48f969a05 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Wed, 23 Jun 2021 02:28:52 +0200 Subject: [PATCH 10/18] Remove 'generateContributors' from prepare deploy, as deploy preparation is done on a shallow git clone --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 75dcc75ac7..a1c2fc096e 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "reset:layeroverview": "echo {\\\"layers\\\":[], \\\"themes\\\":[]} > ./assets/generated/known_layers_and_themes.json", "generate": "mkdir -p ./assets/generated && npm run reset:layeroverview && npm run generate:images && npm run generate:translations && npm run generate:licenses && npm run generate:licenses && npm run generate:layeroverview", "build": "rm -rf dist/ && npm run generate && parcel build --public-url ./ *.html assets/** assets/**/** assets/**/**/** vendor/* vendor/*/*", - "prepare-deploy": "npm run generate:contributor-list && npm run generate && npm run test && npm run generate:editor-layer-index && npm run generate:layeroverview && npm run generate:layouts && npm run build && rm -rf .cache && npm run generate:docs", + "prepare-deploy": "npm run generate && npm run test && npm run generate:editor-layer-index && npm run generate:layeroverview && npm run generate:layouts && npm run build && rm -rf .cache && npm run generate:docs", "deploy:staging": "npm run prepare-deploy && rm -rf ~/git/pietervdvn.github.io/Staging/* && cp -r dist/* ~/git/pietervdvn.github.io/Staging/ && cd ~/git/pietervdvn.github.io/ && git add * && git commit -m 'New MapComplete Version' && git push && cd - && npm run clean", "deploy:pietervdvn": "cd ~/git/pietervdvn.github.io/ && git pull && cd - && npm run prepare-deploy && rm -rf ~/git/pietervdvn.github.io/MapComplete/* && cp -r dist/* ~/git/pietervdvn.github.io/MapComplete/ && cd ~/git/pietervdvn.github.io/ && git add * && git commit -m 'New MapComplete Version' && git push && cd - && npm run clean", "deploy:production": "cd ~/git/mapcomplete.github.io/ && git pull && cd - && rm -rf ./assets/generated && npm run prepare-deploy && npm run optimize-images && rm -rf ~/git/mapcomplete.github.io/* && cp -r dist/* ~/git/mapcomplete.github.io/ && cd ~/git/mapcomplete.github.io/ && echo \"mapcomplete.osm.be\" > CNAME && git add * && git commit -m 'New MapComplete Version' && git push && cd - && npm run clean && npm run gittag", From 5e991bdc02925b76c5a066dcf3bf00539b9e1842 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Wed, 23 Jun 2021 02:41:30 +0200 Subject: [PATCH 11/18] Allow to style special visualisations, no cursor if popups are disabled --- Customizations/JSON/LayerConfig.ts | 2 +- UI/ShowDataLayer.ts | 13 +++++++------ UI/SpecialVisualizations.ts | 2 +- UI/SubstitutedTranslation.ts | 2 +- assets/layers/public_bookcase/public_bookcase.json | 4 +++- scripts/ScriptUtils.ts | 4 +++- scripts/generateLayerOverview.ts | 7 +++---- 7 files changed, 19 insertions(+), 15 deletions(-) diff --git a/Customizations/JSON/LayerConfig.ts b/Customizations/JSON/LayerConfig.ts index 9c143216db..0b14dd1beb 100644 --- a/Customizations/JSON/LayerConfig.ts +++ b/Customizations/JSON/LayerConfig.ts @@ -183,7 +183,7 @@ export default class LayerConfig { const keys = Array.from(SharedTagRenderings.SharedTagRendering.keys()) - throw `Predefined tagRendering ${renderingJson} not found in ${context}.\n Try one of ${(keys.join(", "))}`; + throw `Predefined tagRendering ${renderingJson} not found in ${context}.\n Try one of ${(keys.join(", "))}\n If you intent to output this text literally, use {\"render\": } instead"}`; } return new TagRenderingConfig(renderingJson, self.source.osmTags, `${context}.tagrendering[${i}]`); }); diff --git a/UI/ShowDataLayer.ts b/UI/ShowDataLayer.ts index b383e19259..485e5cf2e2 100644 --- a/UI/ShowDataLayer.ts +++ b/UI/ShowDataLayer.ts @@ -101,9 +101,13 @@ export default class ShowDataLayer { } const style = layer.GenerateLeafletStyle(tagSource, !(layer.title === undefined && (layer.tagRenderings ?? []).length === 0)); + const baseElement = style.icon.html; + if(!this._enablePopups){ + baseElement.SetStyle("cursor: initial !important") + } return L.marker(latLng, { icon: L.divIcon({ - html: style.icon.html.ConstructElement(), + html: baseElement.ConstructElement(), className: style.icon.className, iconAnchor: style.icon.iconAnchor, iconUrl: style.icon.iconUrl, @@ -119,12 +123,9 @@ export default class ShowDataLayer { console.warn("No layer found for object (probably a now disabled layer)", feature, this._layerDict) return; } - if (layer.title === undefined) { + if (layer.title === undefined || !this._enablePopups) { // No popup action defined -> Don't do anything - return; - } - if(!this._enablePopups){ - // Probably a map in the popup - no popups needed! + // or probably a map in the popup - no popups needed! return; } diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts index 1375a4ff8a..40f2849c50 100644 --- a/UI/SpecialVisualizations.ts +++ b/UI/SpecialVisualizations.ts @@ -337,7 +337,7 @@ export default class SpecialVisualizations { return new Combine([ new Title("Special tag renderings", 3), "In a tagrendering, some special values are substituted by an advanced UI-element. This allows advanced features and visualizations to be reused by custom themes or even to query third-party API's.", - "General usage is {func_name()} or {func_name(arg, someotherarg)}. Note that you do not need to use quotes around your arguments, the comma is enough to seperate them. This also implies you cannot use a comma in your args", + "General usage is {func_name()}, {func_name(arg, someotherarg)} or {func_name(args):cssStyle}. Note that you do not need to use quotes around your arguments, the comma is enough to seperate them. This also implies you cannot use a comma in your args", ...helpTexts ] ); diff --git a/UI/SubstitutedTranslation.ts b/UI/SubstitutedTranslation.ts index 2c7f573adf..5bd7c68829 100644 --- a/UI/SubstitutedTranslation.ts +++ b/UI/SubstitutedTranslation.ts @@ -40,7 +40,7 @@ export class SubstitutedTranslation extends VariableUiElement { // We found a special component that should be brought to live const partBefore = SubstitutedTranslation.EvaluateSpecialComponents(matched[1], tags); const argument = matched[2].trim(); - const style = matched[3] ?? "" + const style = matched[3]?.substring(1) ?? "" const partAfter = SubstitutedTranslation.EvaluateSpecialComponents(matched[4], tags); try { const args = knownSpecial.args.map(arg => arg.defaultValue ?? ""); diff --git a/assets/layers/public_bookcase/public_bookcase.json b/assets/layers/public_bookcase/public_bookcase.json index a300a0f0e1..6d8ecc87e5 100644 --- a/assets/layers/public_bookcase/public_bookcase.json +++ b/assets/layers/public_bookcase/public_bookcase.json @@ -72,7 +72,9 @@ ], "tagRenderings": [ "images", - "minimap", + { + "render": "{minimap():height: 9rem; border-radius: 2.5rem; overflow:hidden;border:1px solid gray}" + }, { "render": { "en": "The name of this bookcase is {name}", diff --git a/scripts/ScriptUtils.ts b/scripts/ScriptUtils.ts index 86c3584146..54b340a7cf 100644 --- a/scripts/ScriptUtils.ts +++ b/scripts/ScriptUtils.ts @@ -1,9 +1,11 @@ import {lstatSync, readdirSync, readFileSync} from "fs"; +import {Utils} from "../Utils"; +Utils.runningFromConsole = true import * as https from "https"; import {LayerConfigJson} from "../Customizations/JSON/LayerConfigJson"; import {LayoutConfigJson} from "../Customizations/JSON/LayoutConfigJson"; import * as fs from "fs"; -import {Utils} from "../Utils"; + export default class ScriptUtils { diff --git a/scripts/generateLayerOverview.ts b/scripts/generateLayerOverview.ts index 6f09acec27..becfee7218 100644 --- a/scripts/generateLayerOverview.ts +++ b/scripts/generateLayerOverview.ts @@ -1,14 +1,13 @@ import ScriptUtils from "./ScriptUtils"; -import {Utils} from "../Utils"; -import {readFileSync, writeFileSync} from "fs"; - -Utils.runningFromConsole = true +import {writeFileSync} from "fs"; import LayerConfig from "../Customizations/JSON/LayerConfig"; import * as licenses from "../assets/generated/license_info.json" import LayoutConfig from "../Customizations/JSON/LayoutConfig"; import {LayerConfigJson} from "../Customizations/JSON/LayerConfigJson"; import {Translation} from "../UI/i18n/Translation"; import {LayoutConfigJson} from "../Customizations/JSON/LayoutConfigJson"; + + // This scripts scans 'assets/layers/*.json' for layer definition files and 'assets/themes/*.json' for theme definition files. // It spits out an overview of those to be used to load them From b8a72a5119f22284faa7e16753fa2320146e71f1 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Wed, 23 Jun 2021 03:00:21 +0200 Subject: [PATCH 12/18] Restore personal theme button in MoreScreen --- Models/Constants.ts | 2 +- UI/BigComponents/MoreScreen.ts | 24 +++++++++++++++++++----- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/Models/Constants.ts b/Models/Constants.ts index 4bda2a0c32..185cd7a5d4 100644 --- a/Models/Constants.ts +++ b/Models/Constants.ts @@ -2,7 +2,7 @@ import { Utils } from "../Utils"; export default class Constants { - public static vNumber = "0.8.0b"; + public static vNumber = "0.8.0c"; // The user journey states thresholds when a new feature gets unlocked public static userJourney = { diff --git a/UI/BigComponents/MoreScreen.ts b/UI/BigComponents/MoreScreen.ts index 8976abb29a..bfab0567d6 100644 --- a/UI/BigComponents/MoreScreen.ts +++ b/UI/BigComponents/MoreScreen.ts @@ -29,7 +29,8 @@ export default class MoreScreen extends Combine { LanguagePicker.CreateLanguagePicker(Translations.t.index.title.SupportedLanguages()) .SetClass("absolute top-2 right-3"), new IndexText() - ]) + ]); + themeButtonStyle = "h-32 min-h-32 max-h-32 overflow-ellipsis overflow-hidden" themeListStyle = "md:grid md:grid-flow-row md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-g4 gap-4" } @@ -59,10 +60,23 @@ export default class MoreScreen extends Combine { private static createOfficialThemesList(state: State, buttonClass: string): BaseUIElement { let officialThemes = AllKnownLayouts.layoutsList - if (State.state.osmConnection.userDetails.data.csCount < Constants.userJourney.personalLayoutUnlock) { - officialThemes = officialThemes.filter(theme => theme.id !== personal.id) - } - let buttons = officialThemes.map((layout) => MoreScreen.createLinkButton(layout)?.SetClass(buttonClass)) + + let buttons = officialThemes.map((layout) => { + const button = MoreScreen.createLinkButton(layout)?.SetClass(buttonClass); + if(layout.id === personal.id){ + return new VariableUiElement( + State.state.osmConnection.userDetails.map(userdetails => userdetails.csCount) + .map(csCount => { + if(csCount < Constants.userJourney.personalLayoutUnlock){ + return undefined + }else{ + return button + } + }) + ) + } + return button; + }) let customGeneratorLink = MoreScreen.createCustomGeneratorButton(state) buttons.splice(0, 0, customGeneratorLink); From 3cec4eccff553ca300d47a340953f093bdf0e978 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Thu, 24 Jun 2021 01:17:29 +0200 Subject: [PATCH 13/18] Add zoom functionality to minimap --- UI/Base/Minimap.ts | 2 +- UI/ShowDataLayer.ts | 11 +++++++- UI/SpecialVisualizations.ts | 52 +++++++++++++++++++++++++++---------- 3 files changed, 49 insertions(+), 16 deletions(-) diff --git a/UI/Base/Minimap.ts b/UI/Base/Minimap.ts index de7de17186..ad0f7eb7a7 100644 --- a/UI/Base/Minimap.ts +++ b/UI/Base/Minimap.ts @@ -31,7 +31,7 @@ export default class Minimap extends BaseUIElement { Minimap._nextId++ } - + protected InnerConstructElement(): HTMLElement { const div = document.createElement("div") div.id = this._id; diff --git a/UI/ShowDataLayer.ts b/UI/ShowDataLayer.ts index 485e5cf2e2..cb48a10fb7 100644 --- a/UI/ShowDataLayer.ts +++ b/UI/ShowDataLayer.ts @@ -16,13 +16,16 @@ export default class ShowDataLayer { private readonly _leafletMap: UIEventSource; private _cleanCount = 0; private readonly _enablePopups: boolean; + private readonly _features : UIEventSource<{ feature: any, freshness: Date }[]> constructor(features: UIEventSource<{ feature: any, freshness: Date }[]>, leafletMap: UIEventSource, layoutToUse: UIEventSource, - enablePopups= true) { + enablePopups= true, + zoomToFeatures = false) { this._leafletMap = leafletMap; this._enablePopups = enablePopups; + this._features = features; const self = this; self._layerDict = {}; @@ -72,6 +75,11 @@ export default class ShowDataLayer { mp.addLayer(geoLayer) } + if(zoomToFeatures){ + mp.fitBounds(geoLayer.getBounds()) + } + + State.state.selectedElement.ping(); } @@ -81,6 +89,7 @@ export default class ShowDataLayer { } + private createStyleFor(feature) { const tagsSource = State.state.allElements.addOrGetElement(feature); // Every object is tied to exactly one layer diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts index 40f2849c50..2cf6972fe4 100644 --- a/UI/SpecialVisualizations.ts +++ b/UI/SpecialVisualizations.ts @@ -24,10 +24,11 @@ import Histogram from "./BigComponents/Histogram"; import Loc from "../Models/Loc"; import ShowDataLayer from "./ShowDataLayer"; import Minimap from "./Base/Minimap"; +import {Utils} from "../Utils"; export default class SpecialVisualizations { - + public static specialVisualizations: { funcName: string, constr: ((state: State, tagSource: UIEventSource, argument: string[]) => BaseUIElement), @@ -95,17 +96,43 @@ export default class SpecialVisualizations { doc: "The zoomlevel: the higher, the more zoomed in with 1 being the entire world and 19 being really close", name: "zoomlevel", defaultValue: "18" + }, + { + doc: "(Matches all resting arguments) This argument should be the key of a property of the feature. The corresponding value is interpreted as either the id or the a list of ID's. The features with these ID's will be shown on this minimap.", + name: "idKey", + defaultValue: "id" } ], - example: "`{minimap()}`", + example: "`{minimap()}`, `{minimap(17, id, _list_of_embedded_feature_ids_calculated_by_calculated_tag):height:10rem; border: 2px solid black}`", constr: (state, tagSource, args) => { + + const keys = [...args] + keys.splice(0, 1) + const featureStore = state.allElements.ContainingFeatures + const featuresToShow : UIEventSource<{ freshness: Date, feature: any }[]> = tagSource.map(properties => { + const values: string[] = Utils.NoNull(keys.map(key => properties[key])) + const features: { freshness: Date, feature: any }[] = [] + for (const value of values) { + let idList = [value] + if (value.startsWith("[")) { + // This is a list of values + idList = JSON.parse(value) + } + for (const id of idList) { + features.push({ + freshness: new Date(), + feature: featureStore.get(id) + }) + } + } + return features + }) const properties = tagSource.data; - const feature = state.allElements.ContainingFeatures.get(properties.id) let zoom = 18 - if(args[0] ){ + if (args[0]) { const parsed = Number(args[0]) - if(!isNaN(parsed) && parsed > 0 && parsed < 25){ + if (!isNaN(parsed) && parsed > 0 && parsed < 25) { zoom = parsed; } } @@ -122,17 +149,14 @@ export default class SpecialVisualizations { ) new ShowDataLayer( - new UIEventSource<{ feature: any; freshness: Date }[]>([ - { - freshness: new Date(), - feature: feature - } - ]), + featuresToShow, minimap.leafletMap, State.state.layoutToUse, - false + false, + true ) - + + minimap.SetStyle("overflow: hidden; pointer-events: none;") return minimap; @@ -220,7 +244,7 @@ export default class SpecialVisualizations { defaultValue: "" }, { - name: "colors", + name: "colors*", doc: "(Matches all resting arguments - optional) Matches a regex onto a color value, e.g. `3[a-zA-Z+-]*:#33cc33`" } From 27126dc27866dff19272fc895bd30e4ae8480f08 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Thu, 24 Jun 2021 01:17:53 +0200 Subject: [PATCH 14/18] Add small theme listing --- Docs/Tools/csvGrapher.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Docs/Tools/csvGrapher.py b/Docs/Tools/csvGrapher.py index f4c090c03c..ddd826ed21 100644 --- a/Docs/Tools/csvGrapher.py +++ b/Docs/Tools/csvGrapher.py @@ -424,13 +424,13 @@ def clean_input(contents): yield row -def contributor_count(stats): +def contributor_count(stats, index=1, item = "contributor"): seen_contributors = set() for line in stats: - contributor = line[1] + contributor = line[index] if(contributor in seen_contributors): continue - print("New contributor " + str(len(seen_contributors) + 1) + ": "+contributor) + print("New " + item + " " + str(len(seen_contributors) + 1) + ": "+contributor) seen_contributors.add(contributor) print(line) @@ -440,10 +440,10 @@ def main(): stats = list(clean_input(csv.reader(csvfile, delimiter=',', quotechar='"'))) print("Found " + str(len(stats)) + " changesets") - contributor_count(stats) - create_graphs(stats) - create_per_theme_graphs(stats, 15) - create_per_contributor_graphs(stats, 25) + contributor_count(stats, 3, "theme") + # create_graphs(stats) + # create_per_theme_graphs(stats, 15) + # create_per_contributor_graphs(stats, 25) print("All done!") From 7dc9d5cdb9e1948c6775f3980537bbbe1814eb8e Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Thu, 24 Jun 2021 01:19:33 +0200 Subject: [PATCH 15/18] Translation sync --- .../public_bookcase/public_bookcase.json | 3 -- assets/themes/campersites/campersites.json | 3 +- assets/themes/climbing/climbing.json | 18 ++++++---- langs/themes/nl.json | 36 +++++++++---------- 4 files changed, 32 insertions(+), 28 deletions(-) diff --git a/assets/layers/public_bookcase/public_bookcase.json b/assets/layers/public_bookcase/public_bookcase.json index 6d8ecc87e5..f3db0d0b7b 100644 --- a/assets/layers/public_bookcase/public_bookcase.json +++ b/assets/layers/public_bookcase/public_bookcase.json @@ -72,9 +72,6 @@ ], "tagRenderings": [ "images", - { - "render": "{minimap():height: 9rem; border-radius: 2.5rem; overflow:hidden;border:1px solid gray}" - }, { "render": { "en": "The name of this bookcase is {name}", diff --git a/assets/themes/campersites/campersites.json b/assets/themes/campersites/campersites.json index 512ed671d6..eca693f1a1 100644 --- a/assets/themes/campersites/campersites.json +++ b/assets/themes/campersites/campersites.json @@ -15,7 +15,8 @@ "ru": "Найти места остановки, чтобы провести ночь в автофургоне", "ja": "キャンパーと夜を共にするキャンプサイトを見つける", "fr": "Trouver des sites pour passer la nuit avec votre camping-car", - "zh_Hant": "露營者尋找渡過夜晚的場地" + "zh_Hant": "露營者尋找渡過夜晚的場地", + "nl": "Vind locaties waar je de nacht kan doorbrengen met je mobilehome" }, "description": { "en": "This site collects all official camper stopover places and places where you can dump grey and black water. You can add details about the services provided and the cost. Add pictures and reviews. This is a website and a webapp. The data is stored in OpenStreetMap, so it will be free forever and can be re-used by any app.", diff --git a/assets/themes/climbing/climbing.json b/assets/themes/climbing/climbing.json index 13c2cbc734..a256a7ab3f 100644 --- a/assets/themes/climbing/climbing.json +++ b/assets/themes/climbing/climbing.json @@ -780,7 +780,8 @@ "en": "Climbing is not possible here", "de": "Hier kann nicht geklettert werden", "ja": "ここでは登ることができない", - "nb_NO": "Klatring er ikke mulig her" + "nb_NO": "Klatring er ikke mulig her", + "nl": "Klimmen is hier niet mogelijk" }, "hideInAnswer": true }, @@ -794,7 +795,8 @@ "en": "Climbing is possible here", "de": "Hier kann geklettert werden", "ja": "ここでは登ることができる", - "nb_NO": "Klatring er mulig her" + "nb_NO": "Klatring er mulig her", + "nl": "Klimmen is hier niet toegelaten" } }, { @@ -803,7 +805,8 @@ "en": "Climbing is not possible here", "de": "Hier kann nicht geklettert werden", "ja": "ここでは登ることができない", - "nb_NO": "Klatring er ikke mulig her" + "nb_NO": "Klatring er ikke mulig her", + "nl": "Klimmen is hier niet toegelaten" } } ] @@ -826,7 +829,8 @@ "question": { "en": "Is there a (unofficial) website with more informations (e.g. topos)?", "de": "Gibt es eine (inoffizielle) Website mit mehr Informationen (z.B. Topos)?", - "ja": "もっと情報のある(非公式の)ウェブサイトはありますか(例えば、topos)?" + "ja": "もっと情報のある(非公式の)ウェブサイトはありますか(例えば、topos)?", + "nl": "Is er een (onofficiële) website met meer informatie (b.v. met topos)?" }, "condition": { "and": [ @@ -848,13 +852,15 @@ { "if": "_embedding_feature:access=yes", "then": { - "en": "The containing feature states that this is publicly accessible
{_embedding_feature:access:description}" + "en": "The containing feature states that this is publicly accessible
{_embedding_feature:access:description}", + "nl": "Een omvattend element geeft aan dat dit publiek toegangkelijk is
{_embedding_feature:access:description}" } }, { "if": "_embedding_feature:access=permit", "then": { - "en": "The containing feature states that a permit is needed to access
{_embedding_feature:access:description}" + "en": "The containing feature states that a permit is needed to access
{_embedding_feature:access:description}", + "nl": "Een omvattend element geeft aan dat een toelating nodig is om hier te klimmen
{_embedding_feature:access:description}" } }, { diff --git a/langs/themes/nl.json b/langs/themes/nl.json index 0579271631..92b057af46 100644 --- a/langs/themes/nl.json +++ b/langs/themes/nl.json @@ -373,14 +373,14 @@ "tagRenderings": { "1": { "mappings": { - "2": { - "then": "Klimmen is hier niet toegelaten" + "0": { + "then": "Klimmen is hier niet mogelijk" }, "1": { "then": "Klimmen is hier niet toegelaten" }, - "0": { - "then": "Klimmen is hier niet mogelijk" + "2": { + "then": "Klimmen is hier niet toegelaten" } } } @@ -388,6 +388,19 @@ } }, "roamingRenderings": { + "0": { + "question": "Is er een (onofficiële) website met meer informatie (b.v. met topos)?" + }, + "1": { + "mappings": { + "0": { + "then": "Een omvattend element geeft aan dat dit publiek toegangkelijk is
{_embedding_feature:access:description}" + }, + "1": { + "then": "Een omvattend element geeft aan dat een toelating nodig is om hier te klimmen
{_embedding_feature:access:description}" + } + } + }, "4": { "render": "De klimroutes zijn gemiddeld {climbing:length}m lang", "question": "Wat is de (gemiddelde) lengte van de klimroutes, in meter?" @@ -472,19 +485,6 @@ "then": "Er zijn hier {climbing:speed} snelklimmuren" } } - }, - "1": { - "mappings": { - "1": { - "then": "Een omvattend element geeft aan dat een toelating nodig is om hier te klimmen
{_embedding_feature:access:description}" - }, - "0": { - "then": "Een omvattend element geeft aan dat dit publiek toegangkelijk is
{_embedding_feature:access:description}" - } - } - }, - "0": { - "question": "Is er een (onofficiële) website met meer informatie (b.v. met topos)?" } } }, @@ -975,4 +975,4 @@ } } } -} +} \ No newline at end of file From 9bbe13beff71bacbba6b549f81a8535534efe8f5 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Thu, 24 Jun 2021 01:20:25 +0200 Subject: [PATCH 16/18] Fix translations for bookcase theme --- .../public_bookcase/public_bookcase.json | 3 +++ langs/layers/de.json | 20 +++++++++---------- langs/layers/en.json | 20 +++++++++---------- langs/layers/fr.json | 20 +++++++++---------- langs/layers/nl.json | 20 +++++++++---------- langs/layers/ru.json | 8 ++++---- 6 files changed, 47 insertions(+), 44 deletions(-) diff --git a/assets/layers/public_bookcase/public_bookcase.json b/assets/layers/public_bookcase/public_bookcase.json index f3db0d0b7b..6d8ecc87e5 100644 --- a/assets/layers/public_bookcase/public_bookcase.json +++ b/assets/layers/public_bookcase/public_bookcase.json @@ -72,6 +72,9 @@ ], "tagRenderings": [ "images", + { + "render": "{minimap():height: 9rem; border-radius: 2.5rem; overflow:hidden;border:1px solid gray}" + }, { "render": { "en": "The name of this bookcase is {name}", diff --git a/langs/layers/de.json b/langs/layers/de.json index 656df35020..3ed8abf070 100644 --- a/langs/layers/de.json +++ b/langs/layers/de.json @@ -1072,7 +1072,7 @@ } }, "tagRenderings": { - "1": { + "2": { "render": "Der Name dieses Bücherschrank lautet {name}", "question": "Wie heißt dieser öffentliche Bücherschrank?", "mappings": { @@ -1081,11 +1081,11 @@ } } }, - "2": { + "3": { "render": "{capacity} Bücher passen in diesen Bücherschrank", "question": "Wie viele Bücher passen in diesen öffentlichen Bücherschrank?" }, - "3": { + "4": { "question": "Welche Art von Büchern sind in diesem öffentlichen Bücherschrank zu finden?", "mappings": { "0": { @@ -1099,7 +1099,7 @@ } } }, - "4": { + "5": { "question": "Befindet sich dieser Bücherschrank im Freien?", "mappings": { "0": { @@ -1113,7 +1113,7 @@ } } }, - "5": { + "6": { "question": "Ist dieser öffentliche Bücherschrank frei zugänglich?", "mappings": { "0": { @@ -1124,11 +1124,11 @@ } } }, - "6": { + "7": { "question": "Wer unterhält diesen öffentlichen Bücherschrank?", "render": "Betrieben von {operator}" }, - "7": { + "8": { "question": "Ist dieser öffentliche Bücherschrank Teil eines größeren Netzwerks?", "render": "Dieser Bücherschrank ist Teil von {brand}", "mappings": { @@ -1140,7 +1140,7 @@ } } }, - "8": { + "9": { "render": "Die Referenznummer dieses öffentlichen Bücherschranks innerhalb {brand} lautet {ref}", "question": "Wie lautet die Referenznummer dieses öffentlichen Bücherschranks?", "mappings": { @@ -1149,11 +1149,11 @@ } } }, - "9": { + "10": { "question": "Wann wurde dieser öffentliche Bücherschrank installiert?", "render": "Installiert am {start_date}" }, - "10": { + "11": { "render": "Weitere Informationen auf der Webseite", "question": "Gibt es eine Website mit weiteren Informationen über diesen öffentlichen Bücherschrank?" } diff --git a/langs/layers/en.json b/langs/layers/en.json index 782a80eb75..24a6bfac60 100644 --- a/langs/layers/en.json +++ b/langs/layers/en.json @@ -1183,7 +1183,7 @@ } }, "tagRenderings": { - "1": { + "2": { "render": "The name of this bookcase is {name}", "question": "What is the name of this public bookcase?", "mappings": { @@ -1192,11 +1192,11 @@ } } }, - "2": { + "3": { "render": "{capacity} books fit in this bookcase", "question": "How many books fit into this public bookcase?" }, - "3": { + "4": { "question": "What kind of books can be found in this public bookcase?", "mappings": { "0": { @@ -1210,7 +1210,7 @@ } } }, - "4": { + "5": { "question": "Is this bookcase located outdoors?", "mappings": { "0": { @@ -1224,7 +1224,7 @@ } } }, - "5": { + "6": { "question": "Is this public bookcase freely accessible?", "mappings": { "0": { @@ -1235,11 +1235,11 @@ } } }, - "6": { + "7": { "question": "Who maintains this public bookcase?", "render": "Operated by {operator}" }, - "7": { + "8": { "question": "Is this public bookcase part of a bigger network?", "render": "This public bookcase is part of {brand}", "mappings": { @@ -1251,7 +1251,7 @@ } } }, - "8": { + "9": { "render": "The reference number of this public bookcase within {brand} is {ref}", "question": "What is the reference number of this public bookcase?", "mappings": { @@ -1260,11 +1260,11 @@ } } }, - "9": { + "10": { "question": "When was this public bookcase installed?", "render": "Installed on {start_date}" }, - "10": { + "11": { "render": "More info on the website", "question": "Is there a website with more information about this public bookcase?" } diff --git a/langs/layers/fr.json b/langs/layers/fr.json index 0f0a41d814..ccf1dc807d 100644 --- a/langs/layers/fr.json +++ b/langs/layers/fr.json @@ -1073,7 +1073,7 @@ } }, "tagRenderings": { - "1": { + "2": { "render": "Le nom de cette microbibliothèque est {name}", "question": "Quel est le nom de cette microbibliothèque ?", "mappings": { @@ -1082,11 +1082,11 @@ } } }, - "2": { + "3": { "render": "{capacity} livres peuvent entrer dans cette microbibliothèque", "question": "Combien de livres peuvent entrer dans cette microbibliothèque ?" }, - "3": { + "4": { "question": "Quel type de livres peut-on dans cette microbibliothèque ?", "mappings": { "0": { @@ -1100,7 +1100,7 @@ } } }, - "4": { + "5": { "question": "Cette microbiliothèque est-elle en extérieur ?", "mappings": { "0": { @@ -1114,7 +1114,7 @@ } } }, - "5": { + "6": { "question": "Cette microbibliothèque est-elle librement accèssible ?", "mappings": { "0": { @@ -1125,11 +1125,11 @@ } } }, - "6": { + "7": { "question": "Qui entretien cette microbibliothèque ?", "render": "Entretenue par {operator}" }, - "7": { + "8": { "question": "Cette microbibliothèque fait-elle partie d'un réseau/groupe ?", "render": "Cette microbibliothèque fait partie du groupe {brand}", "mappings": { @@ -1141,7 +1141,7 @@ } } }, - "8": { + "9": { "render": "Cette microbibliothèque du réseau {brand} possède le numéro {ref}", "question": "Quelle est le numéro de référence de cette microbibliothèque ?", "mappings": { @@ -1150,11 +1150,11 @@ } } }, - "9": { + "10": { "question": "Quand a été installée cette microbibliothèque ?", "render": "Installée le {start_date}" }, - "10": { + "11": { "render": "Plus d'infos sur le site web", "question": "Y a-t-il un site web avec plus d'informations sur cette microbibliothèque ?" } diff --git a/langs/layers/nl.json b/langs/layers/nl.json index 3fc2503625..8bc452325c 100644 --- a/langs/layers/nl.json +++ b/langs/layers/nl.json @@ -1361,7 +1361,7 @@ } }, "tagRenderings": { - "1": { + "2": { "render": "De naam van dit boekenruilkastje is {name}", "question": "Wat is de naam van dit boekenuilkastje?", "mappings": { @@ -1370,11 +1370,11 @@ } } }, - "2": { + "3": { "render": "Er passen {capacity} boeken", "question": "Hoeveel boeken passen er in dit boekenruilkastje?" }, - "3": { + "4": { "question": "Voor welke doelgroep zijn de meeste boeken in dit boekenruilkastje?", "mappings": { "0": { @@ -1388,7 +1388,7 @@ } } }, - "4": { + "5": { "question": "Staat dit boekenruilkastje binnen of buiten?", "mappings": { "0": { @@ -1402,7 +1402,7 @@ } } }, - "5": { + "6": { "question": "Is dit boekenruilkastje publiek toegankelijk?", "mappings": { "0": { @@ -1413,11 +1413,11 @@ } } }, - "6": { + "7": { "question": "Wie is verantwoordelijk voor dit boekenruilkastje?", "render": "Onderhouden door {operator}" }, - "7": { + "8": { "question": "Is dit boekenruilkastje deel van een netwerk?", "render": "Dit boekenruilkastje is deel van het netwerk {brand}", "mappings": { @@ -1429,7 +1429,7 @@ } } }, - "8": { + "9": { "render": "Het referentienummer binnen {brand} is {ref}", "question": "Wat is het referentienummer van dit boekenruilkastje?", "mappings": { @@ -1438,11 +1438,11 @@ } } }, - "9": { + "10": { "question": "Op welke dag werd dit boekenruilkastje geinstalleerd?", "render": "Geplaatst op {start_date}" }, - "10": { + "11": { "render": "Meer info op de website", "question": "Is er een website over dit boekenruilkastje?" } diff --git a/langs/layers/ru.json b/langs/layers/ru.json index 3aef434813..e22919e492 100644 --- a/langs/layers/ru.json +++ b/langs/layers/ru.json @@ -578,7 +578,7 @@ } }, "tagRenderings": { - "1": { + "2": { "render": "Название книжного шкафа — {name}", "question": "Как называется общественный книжный шкаф?", "mappings": { @@ -587,10 +587,10 @@ } } }, - "2": { + "3": { "question": "Сколько книг помещается в этом общественном книжном шкафу?" }, - "3": { + "4": { "mappings": { "0": { "then": "В основном детские книги" @@ -600,7 +600,7 @@ } } }, - "10": { + "11": { "render": "Более подробная информация на сайте" } } From ad04dd0abac247c088580848b8c2c541986ab111 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Thu, 24 Jun 2021 01:33:15 +0200 Subject: [PATCH 17/18] Make anchor position more robust --- Customizations/JSON/LayerConfig.ts | 2 +- Models/Constants.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Customizations/JSON/LayerConfig.ts b/Customizations/JSON/LayerConfig.ts index 10d8ddfe1b..81975504f9 100644 --- a/Customizations/JSON/LayerConfig.ts +++ b/Customizations/JSON/LayerConfig.ts @@ -339,7 +339,7 @@ export default class LayerConfig { const iconW = num(iconSize[0]); let iconH = num(iconSize[1]); - const mode = iconSize[2] ?? "center" + const mode = iconSize[2]?.trim()?.toLowerCase() ?? "center" let anchorW = iconW / 2; let anchorH = iconH / 2; diff --git a/Models/Constants.ts b/Models/Constants.ts index 185cd7a5d4..f819e48f57 100644 --- a/Models/Constants.ts +++ b/Models/Constants.ts @@ -2,7 +2,7 @@ import { Utils } from "../Utils"; export default class Constants { - public static vNumber = "0.8.0c"; + public static vNumber = "0.8.0d"; // The user journey states thresholds when a new feature gets unlocked public static userJourney = { From c1e0ba50f55649e4a2718c10e20b6ab244417051 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Thu, 24 Jun 2021 01:38:43 +0200 Subject: [PATCH 18/18] Merge develop, version bump. Minimaps are available now --- Models/Constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Models/Constants.ts b/Models/Constants.ts index f819e48f57..a8d6f58a4a 100644 --- a/Models/Constants.ts +++ b/Models/Constants.ts @@ -2,7 +2,7 @@ import { Utils } from "../Utils"; export default class Constants { - public static vNumber = "0.8.0d"; + public static vNumber = "0.8.1"; // The user journey states thresholds when a new feature gets unlocked public static userJourney = {