diff --git a/Logic/Actors/AvailableBaseLayers.ts b/Logic/Actors/AvailableBaseLayers.ts index ce77e46d7..89a5435e6 100644 --- a/Logic/Actors/AvailableBaseLayers.ts +++ b/Logic/Actors/AvailableBaseLayers.ts @@ -23,11 +23,11 @@ export default class AvailableBaseLayers { private static implementation: AvailableBaseLayersObj static AvailableLayersAt(location: UIEventSource): UIEventSource { - return AvailableBaseLayers.implementation.AvailableLayersAt(location); + return AvailableBaseLayers.implementation?.AvailableLayersAt(location) ?? new UIEventSource([]); } static SelectBestLayerAccordingTo(location: UIEventSource, preferedCategory: UIEventSource): UIEventSource { - return AvailableBaseLayers.implementation.SelectBestLayerAccordingTo(location, preferedCategory); + return AvailableBaseLayers.implementation?.SelectBestLayerAccordingTo(location, preferedCategory) ?? new UIEventSource(undefined); } diff --git a/Logic/Actors/BackgroundLayerResetter.ts b/Logic/Actors/BackgroundLayerResetter.ts index d193d8132..60666c53d 100644 --- a/Logic/Actors/BackgroundLayerResetter.ts +++ b/Logic/Actors/BackgroundLayerResetter.ts @@ -2,6 +2,7 @@ import {UIEventSource} from "../UIEventSource"; import BaseLayer from "../../Models/BaseLayer"; import AvailableBaseLayers from "./AvailableBaseLayers"; import Loc from "../../Models/Loc"; +import {Utils} from "../../Utils"; /** * Sets the current background layer to a layer that is actually available @@ -12,6 +13,11 @@ export default class BackgroundLayerResetter { location: UIEventSource, availableLayers: UIEventSource, defaultLayerId: string = undefined) { + + if(Utils.runningFromConsole){ + return + } + defaultLayerId = defaultLayerId ?? AvailableBaseLayers.osmCarto.id; // Change the baselayer back to OSM if we go out of the current range of the layer diff --git a/Logic/Actors/OverpassFeatureSource.ts b/Logic/Actors/OverpassFeatureSource.ts index 5062133b6..49c605137 100644 --- a/Logic/Actors/OverpassFeatureSource.ts +++ b/Logic/Actors/OverpassFeatureSource.ts @@ -29,7 +29,7 @@ export default class OverpassFeatureSource implements FeatureSource { private readonly retries: UIEventSource = new UIEventSource(0); - + private readonly state: { readonly locationControl: UIEventSource, readonly layoutToUse: LayoutConfig, @@ -39,6 +39,7 @@ export default class OverpassFeatureSource implements FeatureSource { } private readonly _isActive: UIEventSource; private readonly onBboxLoaded: (bbox: BBox, date: Date, layers: LayerConfig[], zoomlevel: number) => void; + constructor( state: { readonly locationControl: UIEventSource, @@ -90,7 +91,7 @@ export default class OverpassFeatureSource implements FeatureSource { } const self = this; this.updateAsync(paddedZoomLevel).then(bboxDate => { - if(bboxDate === undefined || self.onBboxLoaded === undefined){ + if (bboxDate === undefined || self.onBboxLoaded === undefined) { return; } const [bbox, date, layers] = bboxDate @@ -109,41 +110,43 @@ export default class OverpassFeatureSource implements FeatureSource { return undefined; } - const bounds = this.state.currentBounds.data?.pad(this.state.layoutToUse.widenFactor)?.expandToTileBounds(padToZoomLevel); - - if (bounds === undefined) { - return undefined; - } - const self = this; - - - const layersToDownload = [] - for (const layer of this.state.layoutToUse.layers) { - - if (typeof (layer) === "string") { - throw "A layer was not expanded!" - } - if(this.state.locationControl.data.zoom < layer.minzoom){ - continue; - } - if (layer.doNotDownload) { - continue; - } - if (layer.source.geojsonSource !== undefined) { - // Not our responsibility to download this layer! - continue; - } - layersToDownload.push(layer) - } - let data: any = undefined let date: Date = undefined - const overpassUrls = self.state.overpassUrl.data let lastUsed = 0; + + + const layersToDownload = [] + for (const layer of this.state.layoutToUse.layers) { + + if (typeof (layer) === "string") { + throw "A layer was not expanded!" + } + if (this.state.locationControl.data.zoom < layer.minzoom) { + continue; + } + if (layer.doNotDownload) { + continue; + } + if (layer.source.geojsonSource !== undefined) { + // Not our responsibility to download this layer! + continue; + } + layersToDownload.push(layer) + } + + const self = this; + const overpassUrls = self.state.overpassUrl.data + let bounds : BBox do { try { - + + bounds = this.state.currentBounds.data?.pad(this.state.layoutToUse.widenFactor)?.expandToTileBounds(padToZoomLevel); + + if (bounds === undefined) { + return undefined; + } + const overpass = this.GetFilter(overpassUrls[lastUsed], layersToDownload); if (overpass === undefined) { @@ -175,16 +178,21 @@ export default class OverpassFeatureSource implements FeatureSource { } } } - } while (data === undefined); + } while (data === undefined && this._isActive.data); - self.retries.setData(0); + try { + if(data === undefined){ + return undefined + } data.features.forEach(feature => SimpleMetaTagger.objectMetaInfo.applyMetaTagsOnFeature(feature, date, undefined)); self.features.setData(data.features.map(f => ({feature: f, freshness: date}))); return [bounds, date, layersToDownload]; } catch (e) { console.error("Got the overpass response, but could not process it: ", e, e.stack) + return undefined } finally { + self.retries.setData(0); self.runningQuery.setData(false); } diff --git a/Logic/Actors/PendingChangesUploader.ts b/Logic/Actors/PendingChangesUploader.ts index bd1e93fd6..675491467 100644 --- a/Logic/Actors/PendingChangesUploader.ts +++ b/Logic/Actors/PendingChangesUploader.ts @@ -1,6 +1,7 @@ import {Changes} from "../Osm/Changes"; import Constants from "../../Models/Constants"; import {UIEventSource} from "../UIEventSource"; +import {Utils} from "../../Utils"; export default class PendingChangesUploader { @@ -30,6 +31,10 @@ export default class PendingChangesUploader { } }); + if(Utils.runningFromConsole){ + return; + } + document.addEventListener('mouseout', e => { // @ts-ignore if (!e.toElement && !e.relatedTarget) { diff --git a/Logic/Actors/SelectedElementTagsUpdater.ts b/Logic/Actors/SelectedElementTagsUpdater.ts index 35d725c31..765c9ee19 100644 --- a/Logic/Actors/SelectedElementTagsUpdater.ts +++ b/Logic/Actors/SelectedElementTagsUpdater.ts @@ -9,6 +9,13 @@ import {OsmConnection} from "../Osm/OsmConnection"; export default class SelectedElementTagsUpdater { + private static readonly metatags = new Set(["timestamp", + "version", + "changeset", + "user", + "uid", + "id"] ) + constructor(state: { selectedElement: UIEventSource, allElements: ElementStorage, @@ -18,15 +25,15 @@ export default class SelectedElementTagsUpdater { state.osmConnection.isLoggedIn.addCallbackAndRun(isLoggedIn => { - if(isLoggedIn){ + if (isLoggedIn) { SelectedElementTagsUpdater.installCallback(state) return true; } }) } - - private static installCallback(state: { + + public static installCallback(state: { selectedElement: UIEventSource, allElements: ElementStorage, changes: Changes, @@ -36,80 +43,99 @@ export default class SelectedElementTagsUpdater { state.selectedElement.addCallbackAndRunD(s => { let id = s.properties?.id - + const backendUrl = state.osmConnection._oauth_config.url - if(id.startsWith(backendUrl)){ + if (id.startsWith(backendUrl)) { id = id.substring(backendUrl.length) } - - if(!(id.startsWith("way") || id.startsWith("node") || id.startsWith("relation"))){ + + if (!(id.startsWith("way") || id.startsWith("node") || id.startsWith("relation"))) { // This object is _not_ from OSM, so we skip it! return; } - - if(id.indexOf("-") >= 0){ + + if (id.indexOf("-") >= 0) { // This is a new object return; } - - OsmObject.DownloadPropertiesOf(id).then(tags => { - SelectedElementTagsUpdater.applyUpdate(state, tags, id) - }).catch(e => { - console.error("Could not update tags of ", id, "due to", e) + OsmObject.DownloadPropertiesOf(id).then(latestTags => { + SelectedElementTagsUpdater.applyUpdate(state, latestTags, id) }) + }); } - private static applyUpdate(state: { - selectedElement: UIEventSource, - allElements: ElementStorage, - changes: Changes, - osmConnection: OsmConnection - }, latestTags: any, id: string + public static applyUpdate(state: { + selectedElement: UIEventSource, + allElements: ElementStorage, + changes: Changes, + osmConnection: OsmConnection + }, latestTags: any, id: string ) { - const pendingChanges = state.changes.pendingChanges.data - .filter(change => change.type +"/"+ change.id === id) - .filter(change => change.tags !== undefined); - - for (const pendingChange of pendingChanges) { - const tagChanges = pendingChange.tags; - for (const tagChange of tagChanges) { - const key = tagChange.k - const v = tagChange.v - if (v === undefined || v === "") { - delete latestTags[key] - } else { - latestTags[key] = v + try { + + const pendingChanges = state.changes.pendingChanges.data + .filter(change => change.type + "/" + change.id === id) + .filter(change => change.tags !== undefined); + + for (const pendingChange of pendingChanges) { + const tagChanges = pendingChange.tags; + for (const tagChange of tagChanges) { + const key = tagChange.k + const v = tagChange.v + if (v === undefined || v === "") { + delete latestTags[key] + } else { + latestTags[key] = v + } } } - } - // With the changes applied, we merge them onto the upstream object - let somethingChanged = false; - const currentTagsSource = state.allElements.getEventSourceById(id); - const currentTags = currentTagsSource.data - for (const key in latestTags) { - let osmValue = latestTags[key] - - if(typeof osmValue === "number"){ - osmValue = ""+osmValue - } - - const localValue = currentTags[key] - if (localValue !== osmValue) { - console.log("Local value for ", key ,":", localValue, "upstream", osmValue) - somethingChanged = true; - currentTags[key] = osmValue - } - } - if (somethingChanged) { - console.log("Detected upstream changes to the object when opening it, updating...") - currentTagsSource.ping() - }else{ - console.debug("Fetched latest tags for ", id, "but detected no changes") - } + // With the changes applied, we merge them onto the upstream object + let somethingChanged = false; + const currentTagsSource = state.allElements.getEventSourceById(id); + const currentTags = currentTagsSource.data + for (const key in latestTags) { + let osmValue = latestTags[key] + if (typeof osmValue === "number") { + osmValue = "" + osmValue + } + + const localValue = currentTags[key] + if (localValue !== osmValue) { + console.log("Local value for ", key, ":", localValue, "upstream", osmValue) + somethingChanged = true; + currentTags[key] = osmValue + } + } + + for (const currentKey in currentTags) { + if (currentKey.startsWith("_")) { + continue + } + if(this.metatags.has(currentKey)){ + continue + } + if (currentKey in latestTags) { + continue + } + console.log("Removing key as deleted upstream", currentKey) + delete currentTags[currentKey] + somethingChanged = true + } + + + if (somethingChanged) { + console.log("Detected upstream changes to the object when opening it, updating...") + currentTagsSource.ping() + } else { + console.debug("Fetched latest tags for ", id, "but detected no changes") + } + } catch (e) { + console.error("Updating the tags of selected element ", id, "failed due to", e) + } } diff --git a/Logic/Actors/SelectedFeatureHandler.ts b/Logic/Actors/SelectedFeatureHandler.ts index 73e69d72c..b45e87b93 100644 --- a/Logic/Actors/SelectedFeatureHandler.ts +++ b/Logic/Actors/SelectedFeatureHandler.ts @@ -3,15 +3,20 @@ import {OsmObject} from "../Osm/OsmObject"; import Loc from "../../Models/Loc"; import {ElementStorage} from "../ElementStorage"; import FeaturePipeline from "../FeatureSource/FeaturePipeline"; +import {GeoOperations} from "../GeoOperations"; +import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; /** * Makes sure the hash shows the selected element and vice-versa. */ export default class SelectedFeatureHandler { private static readonly _no_trigger_on = new Set(["welcome", "copyright", "layers", "new", "", undefined]) - hash: UIEventSource; + private readonly hash: UIEventSource; private readonly state: { - selectedElement: UIEventSource + selectedElement: UIEventSource, + allElements: ElementStorage, + locationControl: UIEventSource, + layoutToUse: LayoutConfig } constructor( @@ -19,7 +24,9 @@ export default class SelectedFeatureHandler { state: { selectedElement: UIEventSource, allElements: ElementStorage, - featurePipeline: FeaturePipeline + featurePipeline: FeaturePipeline, + locationControl: UIEventSource, + layoutToUse: LayoutConfig } ) { this.hash = hash; @@ -27,30 +34,9 @@ export default class SelectedFeatureHandler { // If the hash changes, set the selected element correctly - function setSelectedElementFromHash(h){ - if (h === undefined || h === "") { - // Hash has been cleared - we clear the selected element - state.selectedElement.setData(undefined); - }else{ - // we search the element to select - const feature = state.allElements.ContainingFeatures.get(h) - if(feature === undefined){ - return; - } - const currentlySeleced = state.selectedElement.data - if(currentlySeleced === undefined){ - state.selectedElement.setData(feature) - return; - } - if(currentlySeleced.properties?.id === feature.properties.id){ - // We already have the right feature - return; - } - state.selectedElement.setData(feature) - } - } - hash.addCallback(setSelectedElementFromHash) + const self = this; + hash.addCallback(() => self.setSelectedElementFromHash()) // IF the selected element changes, set the hash correctly @@ -66,41 +52,103 @@ export default class SelectedFeatureHandler { hash.setData(h) } }) - - state.featurePipeline.newDataLoadedSignal.addCallbackAndRunD(_ => { + + state.featurePipeline?.newDataLoadedSignal?.addCallbackAndRunD(_ => { // New data was loaded. In initial startup, the hash might be set (via the URL) but might not be selected yet - if(hash.data === undefined || SelectedFeatureHandler._no_trigger_on.has(hash.data)){ + if (hash.data === undefined || SelectedFeatureHandler._no_trigger_on.has(hash.data)) { // This is an invalid hash anyway return; } - if(state.selectedElement.data !== undefined){ + if (state.selectedElement.data !== undefined) { // We already have something selected return; } - setSelectedElementFromHash(hash.data) + self.setSelectedElementFromHash() + }) + + + this.initialLoad() + + } + + + /** + * On startup: check if the hash is loaded and eventually zoom to it + * @private + */ + private initialLoad() { + const hash = this.hash.data + if (hash === undefined || hash === "" || hash.indexOf("-") >= 0) { + return; + } + if (SelectedFeatureHandler._no_trigger_on.has(hash)) { + return; + } + + OsmObject.DownloadObjectAsync(hash).then(obj => { + + try { + + console.log("Downloaded selected object from OSM-API for initial load: ", hash) + const geojson = obj.asGeoJson() + this.state.allElements.addOrGetElement(geojson) + this.state.selectedElement.setData(geojson) + this.zoomToSelectedFeature() + } catch (e) { + console.error(e) + } + }) } - // If a feature is selected via the hash, zoom there - public zoomToSelectedFeature(location: UIEventSource) { - const hash = this.hash.data; - if (hash === undefined || SelectedFeatureHandler._no_trigger_on.has(hash)) { - return; // No valid feature selected - } - // We should have a valid osm-ID and zoom to it... But we wrap it in try-catch to be sure - try { - - OsmObject.DownloadObject(hash).addCallbackAndRunD(element => { - const centerpoint = element.centerpoint(); - console.log("Zooming to location for select point: ", centerpoint) - location.data.lat = centerpoint[0] - location.data.lon = centerpoint[1] - location.ping(); - }) - } catch (e) { - console.error("Could not download OSM-object with id", hash, " - probably a weird hash") + private setSelectedElementFromHash() { + const state = this.state + const h = this.hash.data + if (h === undefined || h === "") { + // Hash has been cleared - we clear the selected element + state.selectedElement.setData(undefined); + } else { + // we search the element to select + const feature = state.allElements.ContainingFeatures.get(h) + if (feature === undefined) { + return; + } + const currentlySeleced = state.selectedElement.data + if (currentlySeleced === undefined) { + state.selectedElement.setData(feature) + return; + } + if (currentlySeleced.properties?.id === feature.properties.id) { + // We already have the right feature + return; + } + state.selectedElement.setData(feature) } } + // If a feature is selected via the hash, zoom there + private zoomToSelectedFeature() { + + const selected = this.state.selectedElement.data + if(selected === undefined){ + return + } + + const centerpoint= GeoOperations.centerpointCoordinates(selected) + const location = this.state.locationControl; + location.data.lon = centerpoint[0] + location.data.lat = centerpoint[1] + + const minZoom = Math.max(14, ...(this.state.layoutToUse?.layers?.map(l => l.minzoomVisible) ?? [])) + if(location.data.zoom < minZoom ){ + location.data.zoom = minZoom + } + + location.ping(); + + + + } + } \ No newline at end of file diff --git a/Logic/Actors/TitleHandler.ts b/Logic/Actors/TitleHandler.ts index 91ec89b0c..d3d2f78fb 100644 --- a/Logic/Actors/TitleHandler.ts +++ b/Logic/Actors/TitleHandler.ts @@ -5,6 +5,7 @@ import TagRenderingAnswer from "../../UI/Popup/TagRenderingAnswer"; import Combine from "../../UI/Base/Combine"; import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; import {ElementStorage} from "../ElementStorage"; +import {Utils} from "../../Utils"; export default class TitleHandler { constructor(state : { @@ -38,6 +39,9 @@ export default class TitleHandler { currentTitle.addCallbackAndRunD(title => { + if(Utils.runningFromConsole){ + return + } document.title = title }) } diff --git a/Logic/ExtraFunction.ts b/Logic/ExtraFunction.ts index a022eb11a..8f0265bb1 100644 --- a/Logic/ExtraFunction.ts +++ b/Logic/ExtraFunction.ts @@ -5,8 +5,6 @@ import State from "../State"; import BaseUIElement from "../UI/BaseUIElement"; import List from "../UI/Base/List"; import Title from "../UI/Base/Title"; -import {UIEventSourceTools} from "./UIEventSource"; -import AspectedRouting from "./Osm/aspectedRouting"; import {BBox} from "./BBox"; export interface ExtraFuncParams { diff --git a/Logic/ImageProviders/AllImageProviders.ts b/Logic/ImageProviders/AllImageProviders.ts index 745c367d4..5d428f262 100644 --- a/Logic/ImageProviders/AllImageProviders.ts +++ b/Logic/ImageProviders/AllImageProviders.ts @@ -21,11 +21,13 @@ export default class AllImageProviders { ) ] + + public static defaultKeys = [].concat(AllImageProviders.ImageAttributionSource.map(provider => provider.defaultKeyPrefixes)) private static _cache: Map> = new Map>() - public static LoadImagesFor(tags: UIEventSource, tagKey?: string): UIEventSource { + public static LoadImagesFor(tags: UIEventSource, tagKey?: string[]): UIEventSource { if (tags.data.id === undefined) { return undefined; } @@ -44,7 +46,7 @@ export default class AllImageProviders { let prefixes = imageProvider.defaultKeyPrefixes if(tagKey !== undefined){ - prefixes = [...prefixes, tagKey] + prefixes = tagKey } const singleSource = imageProvider.GetRelevantUrls(tags, { diff --git a/Logic/ImageProviders/ImgurUploader.ts b/Logic/ImageProviders/ImgurUploader.ts index b8addab5a..10bf43609 100644 --- a/Logic/ImageProviders/ImgurUploader.ts +++ b/Logic/ImageProviders/ImgurUploader.ts @@ -27,7 +27,8 @@ export default class ImgurUploader { files, function (url) { console.log("File saved at", url); - self.success.setData([...self.success.data, url]); + self.success.data.push(url) + self.success.ping(); self._handleSuccessUrl(url); }, function () { diff --git a/Logic/ImageProviders/Mapillary.ts b/Logic/ImageProviders/Mapillary.ts index 68491a529..07e0473d4 100644 --- a/Logic/ImageProviders/Mapillary.ts +++ b/Logic/ImageProviders/Mapillary.ts @@ -1,11 +1,9 @@ import ImageProvider, {ProvidedImage} from "./ImageProvider"; import BaseUIElement from "../../UI/BaseUIElement"; -import {UIEventSource} from "../UIEventSource"; import Svg from "../../Svg"; import {Utils} from "../../Utils"; import {LicenseInfo} from "./LicenseInfo"; import Constants from "../../Models/Constants"; -import {fail} from "assert"; export class Mapillary extends ImageProvider { @@ -13,7 +11,7 @@ export class Mapillary extends ImageProvider { public static readonly singleton = new Mapillary(); private static readonly valuePrefix = "https://a.mapillary.com" - public static readonly valuePrefixes = [Mapillary.valuePrefix, "http://mapillary.com","https://mapillary.com"] + public static readonly valuePrefixes = [Mapillary.valuePrefix, "http://mapillary.com","https://mapillary.com","http://www.mapillary.com","https://www.mapillary.com"] private static ExtractKeyFromURL(value: string, failIfNoMath = false): { key: string, diff --git a/Logic/Osm/Changes.ts b/Logic/Osm/Changes.ts index 6c90cccbb..7faa23494 100644 --- a/Logic/Osm/Changes.ts +++ b/Logic/Osm/Changes.ts @@ -97,6 +97,7 @@ export class Changes { console.log("Is already uploading... Abort") return; } + console.log("Uploading changes due to: ", flushreason) this.isUploading.setData(true) this.flushChangesAsync() @@ -287,7 +288,7 @@ export class Changes { v = undefined; } - const oldV = obj.type[k] + const oldV = obj.tags[k] if (oldV === v) { continue; } diff --git a/Logic/Osm/OsmConnection.ts b/Logic/Osm/OsmConnection.ts index c0795aad0..7fd107838 100644 --- a/Logic/Osm/OsmConnection.ts +++ b/Logic/Osm/OsmConnection.ts @@ -8,7 +8,6 @@ import Svg from "../../Svg"; import Img from "../../UI/Base/Img"; import {Utils} from "../../Utils"; import {OsmObject} from "./OsmObject"; -import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; import {Changes} from "./Changes"; export default class UserDetails { @@ -97,7 +96,6 @@ export class OsmConnection { self.AttemptLogin() } }); - this.isLoggedIn.addCallbackAndRunD(li => console.log("User is logged in!", li)) this._dryRun = options.dryRun; this.updateAuthObject(); diff --git a/Logic/Osm/OsmObject.ts b/Logic/Osm/OsmObject.ts index 91b4b6e89..faf1ee79d 100644 --- a/Logic/Osm/OsmObject.ts +++ b/Logic/Osm/OsmObject.ts @@ -263,7 +263,7 @@ export abstract class OsmObject { continue; } const v = this.tags[key]; - if (v !== "") { + if (v !== "" && v !== undefined) { tags += ' \n' } } diff --git a/Logic/State/FeatureSwitchState.ts b/Logic/State/FeatureSwitchState.ts index 215c819f8..2315280ca 100644 --- a/Logic/State/FeatureSwitchState.ts +++ b/Logic/State/FeatureSwitchState.ts @@ -5,6 +5,7 @@ import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; import {UIEventSource} from "../UIEventSource"; import {QueryParameters} from "../Web/QueryParameters"; import Constants from "../../Models/Constants"; +import {Utils} from "../../Utils"; export default class FeatureSwitchState { @@ -137,7 +138,7 @@ export default class FeatureSwitchState { let testingDefaultValue = false; - if (this.featureSwitchApiURL.data !== "osm-test" && + if (this.featureSwitchApiURL.data !== "osm-test" && !Utils.runningFromConsole && (location.hostname === "localhost" || location.hostname === "127.0.0.1")) { testingDefaultValue = true } diff --git a/Logic/UIEventSource.ts b/Logic/UIEventSource.ts index 77431441f..571c74f8e 100644 --- a/Logic/UIEventSource.ts +++ b/Logic/UIEventSource.ts @@ -286,6 +286,9 @@ export class UIEventSource { } public stabilized(millisToStabilize): UIEventSource { + if(Utils.runningFromConsole){ + return this; + } const newSource = new UIEventSource(this.data); @@ -334,21 +337,4 @@ export class UIEventSource { } ) } -} - -export class UIEventSourceTools { - - private static readonly _download_cache = new Map>() - - public static downloadJsonCached(url: string): UIEventSource { - const cached = UIEventSourceTools._download_cache.get(url) - if (cached !== undefined) { - return cached; - } - const src = new UIEventSource(undefined) - UIEventSourceTools._download_cache.set(url, src) - Utils.downloadJson(url).then(r => src.setData(r)) - return src; - } - } \ No newline at end of file diff --git a/Models/Constants.ts b/Models/Constants.ts index b8e2df07d..ec2b8dcb8 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.11.0-rc-1"; + public static vNumber = "0.11.0"; public static ImgurApiKey = '7070e7167f0a25a' public static readonly mapillary_client_token_v3 = 'TXhLaWthQ1d4RUg0czVxaTVoRjFJZzowNDczNjUzNmIyNTQyYzI2' public static readonly mapillary_client_token_v4 = "MLY|4441509239301885|b40ad2d3ea105435bd40c7e76993ae85" diff --git a/UI/BigComponents/SimpleAddUI.ts b/UI/BigComponents/SimpleAddUI.ts index 204e7f955..84e14b682 100644 --- a/UI/BigComponents/SimpleAddUI.ts +++ b/UI/BigComponents/SimpleAddUI.ts @@ -113,9 +113,9 @@ export default class SimpleAddUI extends Toggle { new Toggle( new Toggle( new Toggle( - addUi, Translations.t.general.add.stillLoading.Clone().SetClass("alert"), - state.featurePipeline.somethingLoaded + addUi, + state.featurePipeline.runningQuery ), Translations.t.general.add.zoomInFurther.Clone().SetClass("alert"), state.locationControl.map(loc => loc.zoom >= Constants.userJourney.minZoomLevelToAddNewPoints) diff --git a/UI/BigComponents/UploadFlowStateUI.ts b/UI/BigComponents/UploadFlowStateUI.ts deleted file mode 100644 index c98c1a941..000000000 --- a/UI/BigComponents/UploadFlowStateUI.ts +++ /dev/null @@ -1,45 +0,0 @@ -import {UIEventSource} from "../../Logic/UIEventSource"; -import BaseUIElement from "../BaseUIElement"; -import {VariableUiElement} from "../Base/VariableUIElement"; -import Translations from "../i18n/Translations"; - -/** - * Shows that 'images are uploading', 'all images are uploaded' as relevant... - */ -export default class UploadFlowStateUI extends VariableUiElement { - - - constructor(queue: UIEventSource, failed: UIEventSource, success: UIEventSource) { - const t = Translations.t.image; - - super( - queue.map(queue => { - const failedReasons = failed.data - const successCount = success.data.length - const pendingCount = queue.length - successCount - failedReasons.length; - - let stateMessages: BaseUIElement[] = [] - - if (pendingCount == 1) { - stateMessages.push(t.uploadingPicture.Clone().SetClass("alert")) - } - if (pendingCount > 1) { - stateMessages.push(t.uploadingMultiple.Subs({count: "" + pendingCount}).SetClass("alert")) - } - if (failedReasons.length > 0) { - stateMessages.push(t.uploadFailed.Clone().SetClass("alert")) - } - if (successCount > 0 && pendingCount == 0) { - stateMessages.push(t.uploadDone.SetClass("thanks")) - } - - stateMessages.forEach(msg => msg.SetStyle("display: block ruby")) - - return stateMessages - }, [failed, success]) - ); - - - } - -} \ No newline at end of file diff --git a/UI/DefaultGUI.ts b/UI/DefaultGUI.ts index 5c7f1b9a6..fc195f90d 100644 --- a/UI/DefaultGUI.ts +++ b/UI/DefaultGUI.ts @@ -23,6 +23,7 @@ import ScrollableFullScreen from "./Base/ScrollableFullScreen"; import Translations from "./i18n/Translations"; import SimpleAddUI from "./BigComponents/SimpleAddUI"; import StrayClickHandler from "../Logic/Actors/StrayClickHandler"; +import Lazy from "./Base/Lazy"; export class DefaultGuiState { public readonly welcomeMessageIsOpened; @@ -81,12 +82,22 @@ export default class DefaultGUI { constructor(state: FeaturePipelineState, guiState: DefaultGuiState) { this.state = state; this._guiState = guiState; - const self = this; if (state.layoutToUse.customCss !== undefined) { Utils.LoadCustomCss(state.layoutToUse.customCss); } + + this.SetupUIElements(); + this.SetupMap() + + } + + + private SetupMap(){ + const state = this.state; + const guiState = this._guiState; + // Attach the map state.mainMapObject.SetClass("w-full h-full") .AttachTo("leafletDiv") @@ -96,8 +107,28 @@ export default class DefaultGUI { state ) - this.InitWelcomeMessage(); + new ShowDataLayer({ + leafletMap: state.leafletMap, + layerToShow: AllKnownLayers.sharedLayers.get("home_location"), + features: state.homeLocation, + enablePopups: false, + }) + + state.leafletMap.addCallbackAndRunD(_ => { + // Lets assume that all showDataLayers are initialized at this point + state.selectedElement.ping() + State.state.locationControl.ping(); + return true; + }) + + } + + private SetupUIElements(){ + const state = this.state; + const guiState = this._guiState; + + const self =this Toggle.If(state.featureSwitchUserbadge, () => new UserBadge(state) ).AttachTo("userbadge") @@ -119,36 +150,21 @@ export default class DefaultGUI { } - new Toggle(self.InitWelcomeMessage(), - Toggle.If(state.featureSwitchIframePopoutEnabled, iframePopout), + new Toggle(new Lazy(() => self.InitWelcomeMessage()), + Toggle.If(state.featureSwitchIframePopoutEnabled, iframePopout), state.featureSwitchWelcomeMessage ).AttachTo("messagesbox"); new LeftControls(state, guiState).AttachTo("bottom-left"); new RightControls(state).AttachTo("bottom-right"); - State.state.locationControl.ping(); + new CenterMessageBox(state).AttachTo("centermessage"); document .getElementById("centermessage") .classList.add("pointer-events-none"); - - - new ShowDataLayer({ - leafletMap: state.leafletMap, - layerToShow: AllKnownLayers.sharedLayers.get("home_location"), - features: state.homeLocation, - enablePopups: false, - }) - - state.leafletMap.addCallbackAndRunD(_ => { - // Lets assume that all showDataLayers are initialized at this point - state.selectedElement.ping() - return true; - }) - } - private InitWelcomeMessage() { + private InitWelcomeMessage() : BaseUIElement{ const isOpened = this._guiState.welcomeMessageIsOpened const fullOptions = new FullWelcomePaneWithTabs(isOpened, this._guiState.welcomeMessageOpenedTab, this.state); @@ -180,7 +196,7 @@ export default class DefaultGUI { public setupClickDialogOnMap(filterViewIsOpened: UIEventSource, state: FeaturePipelineState) { - function setup(){ + function setup() { let presetCount = 0; for (const layer of state.layoutToUse.layers) { for (const preset of layer.presets) { diff --git a/UI/Image/ImageCarousel.ts b/UI/Image/ImageCarousel.ts index 9932da616..43ce32682 100644 --- a/UI/Image/ImageCarousel.ts +++ b/UI/Image/ImageCarousel.ts @@ -9,7 +9,9 @@ import ImageProvider from "../../Logic/ImageProviders/ImageProvider"; export class ImageCarousel extends Toggle { - constructor(images: UIEventSource<{ key: string, url: string, provider: ImageProvider }[]>, tags: UIEventSource) { + constructor(images: UIEventSource<{ key: string, url: string, provider: ImageProvider }[]>, + tags: UIEventSource, + keys: string[]) { const uiElements = images.map((imageURLS: { key: string, url: string, provider: ImageProvider }[]) => { const uiElements: BaseUIElement[] = []; for (const url of imageURLS) { diff --git a/UI/Image/ImageUploadFlow.ts b/UI/Image/ImageUploadFlow.ts index 80ebe427c..c0407c208 100644 --- a/UI/Image/ImageUploadFlow.ts +++ b/UI/Image/ImageUploadFlow.ts @@ -9,14 +9,23 @@ import LicensePicker from "../BigComponents/LicensePicker"; import Toggle from "../Input/Toggle"; import FileSelectorButton from "../Input/FileSelectorButton"; import ImgurUploader from "../../Logic/ImageProviders/ImgurUploader"; -import UploadFlowStateUI from "../BigComponents/UploadFlowStateUI"; import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"; import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; import {FixedUiElement} from "../Base/FixedUiElement"; +import {VariableUiElement} from "../Base/VariableUIElement"; export class ImageUploadFlow extends Toggle { + + private static readonly uploadCountsPerId = new Map>() + constructor(tagsSource: UIEventSource, imagePrefix: string = "image", text: string = undefined) { + const perId = ImageUploadFlow.uploadCountsPerId + const id = tagsSource.data.id + if(!perId.has(id)){ + perId.set(id, new UIEventSource(0)) + } + const uploadedCount = perId.get(id) const uploader = new ImgurUploader(url => { // A file was uploaded - we add it to the tags of the object @@ -30,7 +39,9 @@ export class ImageUploadFlow extends Toggle { key = imagePrefix + ":" + freeIndex; } console.log("Adding image:" + key, url); - Promise.resolve(State.state.changes + uploadedCount.data ++ + uploadedCount.ping() + Promise.resolve(State.state.changes .applyAction(new ChangeTagAction( tags.id, new Tag(key, url), tagsSource.data, { @@ -40,10 +51,6 @@ export class ImageUploadFlow extends Toggle { ))) }) - uploader.queue.addCallbackD(q => console.log("Image upload queue is ", q)) - uploader.failed.addCallbackD(q => console.log("Image upload fail list is ", q)) - uploader.success.addCallbackD(q => console.log("Image upload success list is ", q)) - const licensePicker = new LicensePicker() const t = Translations.t.image; @@ -105,10 +112,33 @@ export class ImageUploadFlow extends Toggle { }) - const uploadStateUi = new UploadFlowStateUI(uploader.queue, uploader.failed, uploader.success) - const uploadFlow: BaseUIElement = new Combine([ - uploadStateUi, + new VariableUiElement(uploader.queue.map(q => q.length).map(l => { + if(l == 0){ + return undefined; + } + if(l == 1){ + return t.uploadingPicture.Clone().SetClass("alert") + }else{ + return t.uploadingMultiple.Subs({count: "" + l}).SetClass("alert") + } + })), + new VariableUiElement(uploader.failed.map(q => q.length).map(l => { + if(l==0){ + return undefined + } + return t.uploadFailed.Clone().SetClass("alert"); + })), + new VariableUiElement(uploadedCount.map(l => { + if(l == 0){ + return undefined; + } + if(l == 1){ + return t.uploadDone.Clone().SetClass("thanks"); + } + return t.uploadMultipleDone.Subs({count: l}).SetClass("thanks") + })), + fileSelector, Translations.t.image.respectPrivacy.Clone().SetStyle("font-size:small;"), licensePicker diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts index 7e9ef1e65..188215296 100644 --- a/UI/SpecialVisualizations.ts +++ b/UI/SpecialVisualizations.ts @@ -83,15 +83,15 @@ export default class SpecialVisualizations { docs: "Creates an image carousel for the given sources. An attempt will be made to guess what source is used. Supported: Wikidata identifiers, Wikipedia pages, Wikimedia categories, IMGUR (with attribution, direct links)", args: [{ name: "image key/prefix (multiple values allowed if comma-seperated)", - defaultValue: "image", + defaultValue: AllImageProviders.defaultKeys.join(","), doc: "The keys given to the images, e.g. if image is given, the first picture URL will be added as image, the second as image:0, the third as image:1, etc... " }], constr: (state: State, tags, args) => { - let imagePrefixes = undefined; + let imagePrefixes: string[] = undefined; if(args.length > 0){ - imagePrefixes = args; + imagePrefixes = [].concat(...args.map(a => a.split(","))); } - return new ImageCarousel(AllImageProviders.LoadImagesFor(tags, imagePrefixes), tags); + return new ImageCarousel(AllImageProviders.LoadImagesFor(tags, imagePrefixes), tags, imagePrefixes); } }, { diff --git a/Utils.ts b/Utils.ts index 2aae7c869..a732f1ab8 100644 --- a/Utils.ts +++ b/Utils.ts @@ -330,7 +330,7 @@ export class Utils { return cached.promise } } - const promise = Utils.downloadJson(url, headers) + const promise = /*NO AWAIT as we work with the promise directly */Utils.downloadJson(url, headers) Utils._download_cache.set(url, {promise, timestamp: new Date().getTime()}) return await promise } diff --git a/assets/layers/bike_shop/bike_shop.json b/assets/layers/bike_shop/bike_shop.json index 8b80b5b04..6cd8890c3 100644 --- a/assets/layers/bike_shop/bike_shop.json +++ b/assets/layers/bike_shop/bike_shop.json @@ -174,8 +174,8 @@ "condition": "service:bicycle:diy=yes", "render": "" }, - { "condition": "service:bicycle:cleaning=yes", - + { + "condition": "service:bicycle:cleaning=yes", "render": "" }, "defaults" diff --git a/assets/layers/cycleways_and_roads/cycleways_and_roads.json b/assets/layers/cycleways_and_roads/cycleways_and_roads.json index 1480f007a..8b10ac7b3 100644 --- a/assets/layers/cycleways_and_roads/cycleways_and_roads.json +++ b/assets/layers/cycleways_and_roads/cycleways_and_roads.json @@ -668,8 +668,8 @@ ] }, "question": { - "en": "What is the carriage width of this road (in meters)?", - "nl": "Hoe breed is de rijbaan in deze straat (in meters)?" + "en": "What is the carriage width of this road (in meters)?
This is measured curb to curb and thus includes the width of parallell parking lanes", + "nl": "Hoe breed is de rijbaan in deze straat (in meters)?
Dit is
Meet dit van stoepsteen tot stoepsteen, dus inclusief een parallelle parkeerstrook" }, "id": "width:carriageway" }, diff --git a/assets/themes/aed/aed_brugge.json b/assets/themes/aed/aed_brugge.json index aad99cd44..2ccf774d8 100644 --- a/assets/themes/aed/aed_brugge.json +++ b/assets/themes/aed/aed_brugge.json @@ -15,6 +15,9 @@ "startLat": 51.25634, "startLon": 3.195682, "startZoom": 12, + "clustering": { + "maxZoom": 0 + }, "layers": [ "defibrillator", { diff --git a/assets/themes/buurtnatuur/buurtnatuur.json b/assets/themes/buurtnatuur/buurtnatuur.json index 03d04cffe..0f1c8648a 100644 --- a/assets/themes/buurtnatuur/buurtnatuur.json +++ b/assets/themes/buurtnatuur/buurtnatuur.json @@ -1,7 +1,6 @@ { "id": "buurtnatuur", "title": { - "#": "DO NOT TRANSLATE THIS THEME - this one is only meant to be in dutch!", "nl": "Breng jouw buurtnatuur in kaart" }, "shortDescription": { diff --git a/assets/themes/cyclestreets/cyclestreets.json b/assets/themes/cyclestreets/cyclestreets.json index 23343c762..9f332e157 100644 --- a/assets/themes/cyclestreets/cyclestreets.json +++ b/assets/themes/cyclestreets/cyclestreets.json @@ -207,7 +207,8 @@ "overrideAll": { "allowSplit": true, "tagRenderings+": [ - {"id": "is_cyclestreet", + { + "id": "is_cyclestreet", "question": { "nl": "Is deze straat een fietsstraat?", "en": "Is this street a cyclestreet?", @@ -283,7 +284,8 @@ } ] }, - {"id": "future_cyclestreet", + { + "id": "future_cyclestreet", "question": { "nl": "Wanneer wordt deze straat een fietsstraat?", "en": "When will this street become a cyclestreet?", diff --git a/langs/en.json b/langs/en.json index 8fd307d98..6b7d7723a 100644 --- a/langs/en.json +++ b/langs/en.json @@ -10,7 +10,8 @@ "ccb": "under the CC-BY-license", "uploadFailed": "Could not upload your picture. Are you connected to the Internet, and allow third party API's? The Brave browser or the uMatrix plugin might block them.", "respectPrivacy": "Do not photograph people nor license plates. Do not upload Google Maps, Google Streetview or other copyrighted sources.", - "uploadDone": "Your picture has been added. Thanks for helping out!", + "uploadDone": "Your picture has been added. Thanks for helping out!", + "uploadMultipleDone": "{count} pictures have been added. Thanks for helping out!", "dontDelete": "Cancel", "doDelete": "Remove image", "isDeleted": "Deleted", diff --git a/langs/layers/en.json b/langs/layers/en.json index dd3acaee9..f32bf146e 100644 --- a/langs/layers/en.json +++ b/langs/layers/en.json @@ -2376,7 +2376,7 @@ "question": "Is this street lit?" }, "width:carriageway": { - "question": "What is the carriage width of this road (in meters)?", + "question": "What is the carriage width of this road (in meters)?
This is measured curb to curb and thus includes the width of parallell parking lanes", "render": "The carriage width of this road is {width:carriageway}m" } }, diff --git a/langs/layers/nl.json b/langs/layers/nl.json index b484dc8c4..fb5c7fbb8 100644 --- a/langs/layers/nl.json +++ b/langs/layers/nl.json @@ -2329,7 +2329,7 @@ "question": "Is deze weg verlicht?" }, "width:carriageway": { - "question": "Hoe breed is de rijbaan in deze straat (in meters)?", + "question": "Hoe breed is de rijbaan in deze straat (in meters)?
Dit is
Meet dit van stoepsteen tot stoepsteen, dus inclusief een parallelle parkeerstrook", "render": "De breedte van deze rijbaan in deze straat is {width:carriageway}m" } }, diff --git a/langs/themes/de.json b/langs/themes/de.json index d81b08749..f7680b81b 100644 --- a/langs/themes/de.json +++ b/langs/themes/de.json @@ -233,21 +233,23 @@ } } }, - "roamingRenderings": { - "0": { - "question": "Wer betreibt diesen Ort?", - "render": "Dieser Ort wird betrieben von {operator}" - }, - "1": { - "mappings": { - "0": { - "then": "Dieser Ort hat eine Stromversorgung" - }, - "1": { - "then": "Dieser Ort hat keine Stromversorgung" - } + "overrideAll": { + "tagRenderings+": { + "0": { + "question": "Wer betreibt diesen Ort?", + "render": "Dieser Ort wird betrieben von {operator}" }, - "question": "Hat dieser Ort eine Stromversorgung?" + "1": { + "mappings": { + "0": { + "then": "Dieser Ort hat eine Stromversorgung" + }, + "1": { + "then": "Dieser Ort hat keine Stromversorgung" + } + }, + "question": "Hat dieser Ort eine Stromversorgung?" + } } }, "shortDescription": "Finden Sie Plätze zum Übernachten mit Ihrem Wohnmobil", @@ -428,6 +430,113 @@ } }, "overrideAll": { + "tagRenderings+": { + "0": { + "question": "Gibt es eine (inoffizielle) Website mit mehr Informationen (z.B. Topos)?" + }, + "2": { + "mappings": { + "0": { + "then": "Öffentlich zugänglich für jedermann" + }, + "1": { + "then": "Zugang nur mit Genehmigung" + }, + "2": { + "then": "Nur für Kunden" + }, + "3": { + "then": "Nur für Vereinsmitglieder" + } + }, + "question": "Wer hat hier Zugang?" + }, + "4": { + "question": "Wie lang sind die Routen (durchschnittlich) in Metern?", + "render": "Die Routen sind durchschnittlich {canonical(climbing:length)} lang" + }, + "5": { + "question": "Welche Schwierigkeit hat hier die leichteste Route (französisch/belgisches System)?", + "render": "Die leichteste Route hat hier die Schwierigkeit {climbing:grade:french:min} (französisch/belgisches System)" + }, + "6": { + "question": "Welche Schwierigkeit hat hier die schwerste Route (französisch/belgisches System)?", + "render": "Die schwerste Route hat hier die Schwierigkeit {climbing:grade:french:min} (französisch/belgisches System)" + }, + "7": { + "mappings": { + "0": { + "then": "Hier kann gebouldert werden" + }, + "1": { + "then": "Hier kann nicht gebouldert werden" + }, + "2": { + "then": "Bouldern ist hier nur an wenigen Routen möglich" + }, + "3": { + "then": "Hier gibt es {climbing:boulder} Boulder-Routen" + } + }, + "question": "Kann hier gebouldert werden?" + }, + "8": { + "mappings": { + "0": { + "then": "Toprope-Klettern ist hier möglich" + }, + "1": { + "then": "Toprope-Climbing ist hier nicht möglich" + }, + "2": { + "then": "Hier gibt es {climbing:toprope} Toprope-Routen" + } + }, + "question": "Ist Toprope-Klettern hier möglich?" + }, + "9": { + "mappings": { + "0": { + "then": "Sportklettern ist hier möglich" + }, + "1": { + "then": "Sportklettern ist hier nicht möglich" + }, + "2": { + "then": "Hier gibt es {climbing:sport} Sportkletter-Routen" + } + }, + "question": "Ist hier Sportklettern möglich (feste Ankerpunkte)?" + }, + "10": { + "mappings": { + "0": { + "then": "Traditionelles Klettern ist hier möglich" + }, + "1": { + "then": "Traditionelles Klettern ist hier nicht möglich" + }, + "2": { + "then": "Hier gibt es {climbing:traditional} Routen für traditionelles Klettern" + } + }, + "question": "Ist hier traditionelles Klettern möglich (eigene Sicherung z.B. mit Klemmkleilen)?" + }, + "11": { + "mappings": { + "0": { + "then": "Hier gibt es eine Speedkletter-Wand" + }, + "1": { + "then": "Hier gibt es keine Speedkletter-Wand" + }, + "2": { + "then": "Hier gibt es {climbing:speed} Speedkletter-Routen" + } + }, + "question": "Gibt es hier eine Speedkletter-Wand?" + } + }, "units+": { "0": { "applicableUnits": { @@ -441,113 +550,6 @@ } } }, - "roamingRenderings": { - "0": { - "question": "Gibt es eine (inoffizielle) Website mit mehr Informationen (z.B. Topos)?" - }, - "2": { - "mappings": { - "0": { - "then": "Öffentlich zugänglich für jedermann" - }, - "1": { - "then": "Zugang nur mit Genehmigung" - }, - "2": { - "then": "Nur für Kunden" - }, - "3": { - "then": "Nur für Vereinsmitglieder" - } - }, - "question": "Wer hat hier Zugang?" - }, - "4": { - "question": "Wie lang sind die Routen (durchschnittlich) in Metern?", - "render": "Die Routen sind durchschnittlich {canonical(climbing:length)} lang" - }, - "5": { - "question": "Welche Schwierigkeit hat hier die leichteste Route (französisch/belgisches System)?", - "render": "Die leichteste Route hat hier die Schwierigkeit {climbing:grade:french:min} (französisch/belgisches System)" - }, - "6": { - "question": "Welche Schwierigkeit hat hier die schwerste Route (französisch/belgisches System)?", - "render": "Die schwerste Route hat hier die Schwierigkeit {climbing:grade:french:min} (französisch/belgisches System)" - }, - "7": { - "mappings": { - "0": { - "then": "Hier kann gebouldert werden" - }, - "1": { - "then": "Hier kann nicht gebouldert werden" - }, - "2": { - "then": "Bouldern ist hier nur an wenigen Routen möglich" - }, - "3": { - "then": "Hier gibt es {climbing:boulder} Boulder-Routen" - } - }, - "question": "Kann hier gebouldert werden?" - }, - "8": { - "mappings": { - "0": { - "then": "Toprope-Klettern ist hier möglich" - }, - "1": { - "then": "Toprope-Climbing ist hier nicht möglich" - }, - "2": { - "then": "Hier gibt es {climbing:toprope} Toprope-Routen" - } - }, - "question": "Ist Toprope-Klettern hier möglich?" - }, - "9": { - "mappings": { - "0": { - "then": "Sportklettern ist hier möglich" - }, - "1": { - "then": "Sportklettern ist hier nicht möglich" - }, - "2": { - "then": "Hier gibt es {climbing:sport} Sportkletter-Routen" - } - }, - "question": "Ist hier Sportklettern möglich (feste Ankerpunkte)?" - }, - "10": { - "mappings": { - "0": { - "then": "Traditionelles Klettern ist hier möglich" - }, - "1": { - "then": "Traditionelles Klettern ist hier nicht möglich" - }, - "2": { - "then": "Hier gibt es {climbing:traditional} Routen für traditionelles Klettern" - } - }, - "question": "Ist hier traditionelles Klettern möglich (eigene Sicherung z.B. mit Klemmkleilen)?" - }, - "11": { - "mappings": { - "0": { - "then": "Hier gibt es eine Speedkletter-Wand" - }, - "1": { - "then": "Hier gibt es keine Speedkletter-Wand" - }, - "2": { - "then": "Hier gibt es {climbing:speed} Speedkletter-Routen" - } - }, - "question": "Gibt es hier eine Speedkletter-Wand?" - } - }, "title": "Offene Kletterkarte" }, "cycle_highways": { @@ -594,27 +596,29 @@ } } }, - "roamingRenderings": { - "0": { - "mappings": { - "0": { - "then": "Diese Straße ist eine Fahrradstraße (mit einer Geschwindigkeitsbegrenzung von 30 km/h)" + "overrideAll": { + "tagRenderings+": { + "0": { + "mappings": { + "0": { + "then": "Diese Straße ist eine Fahrradstraße (mit einer Geschwindigkeitsbegrenzung von 30 km/h)" + }, + "1": { + "then": "Diese Straße ist eine Fahrradstraße" + }, + "2": { + "then": "Diese Straße wird bald eine Fahrradstraße sein" + }, + "3": { + "then": "Diese Straße ist keine Fahrradstraße" + } }, - "1": { - "then": "Diese Straße ist eine Fahrradstraße" - }, - "2": { - "then": "Diese Straße wird bald eine Fahrradstraße sein" - }, - "3": { - "then": "Diese Straße ist keine Fahrradstraße" - } + "question": "Ist diese Straße eine Fahrradstraße?" }, - "question": "Ist diese Straße eine Fahrradstraße?" - }, - "1": { - "question": "Wann wird diese Straße eine Fahrradstraße?", - "render": "Diese Straße wird am {cyclestreet:start_date} zu einer Fahrradstraße" + "1": { + "question": "Wann wird diese Straße eine Fahrradstraße?", + "render": "Diese Straße wird am {cyclestreet:start_date} zu einer Fahrradstraße" + } } }, "shortDescription": "Eine Karte von Fahrradstraßen", diff --git a/langs/themes/en.json b/langs/themes/en.json index 8bf77665b..54905e273 100644 --- a/langs/themes/en.json +++ b/langs/themes/en.json @@ -233,21 +233,23 @@ } } }, - "roamingRenderings": { - "0": { - "question": "Who operates this place?", - "render": "This place is operated by {operator}" - }, - "1": { - "mappings": { - "0": { - "then": "This place has a power supply" - }, - "1": { - "then": "This place does not have power supply" - } + "overrideAll": { + "tagRenderings+": { + "0": { + "question": "Who operates this place?", + "render": "This place is operated by {operator}" }, - "question": "Does this place have a power supply?" + "1": { + "mappings": { + "0": { + "then": "This place has a power supply" + }, + "1": { + "then": "This place does not have power supply" + } + }, + "question": "Does this place have a power supply?" + } } }, "shortDescription": "Find sites to spend the night with your camper", @@ -452,6 +454,129 @@ } }, "overrideAll": { + "tagRenderings+": { + "0": { + "question": "Is there a (unofficial) website with more informations (e.g. topos)?" + }, + "1": { + "mappings": { + "0": { + "then": "The containing feature states that this is publicly accessible
{_embedding_feature:access:description}" + }, + "1": { + "then": "The containing feature states that a permit is needed to access
{_embedding_feature:access:description}" + }, + "2": { + "then": "The containing feature states that this is only accessible to customers
{_embedding_feature:access:description}" + }, + "3": { + "then": "The containing feature states that this is only accessible to club members
{_embedding_feature:access:description}" + } + } + }, + "2": { + "mappings": { + "0": { + "then": "Publicly accessible to anyone" + }, + "1": { + "then": "You need a permit to access here" + }, + "2": { + "then": "Only custumers" + }, + "3": { + "then": "Only club members" + } + }, + "question": "Who can access here?" + }, + "4": { + "question": "What is the (average) length of the routes in meters?", + "render": "The routes are {canonical(climbing:length)} long on average" + }, + "5": { + "question": "What is the level of the easiest route here, accoring to the french classification system?", + "render": "The minimal difficulty is {climbing:grade:french:min} according to the french/belgian system" + }, + "6": { + "question": "What is the level of the most difficult route here, accoring to the french classification system?", + "render": "The maximal difficulty is {climbing:grade:french:max} according to the french/belgian system" + }, + "7": { + "mappings": { + "0": { + "then": "Bouldering is possible here" + }, + "1": { + "then": "Bouldering is not possible here" + }, + "2": { + "then": "Bouldering is possible, allthough there are only a few routes" + }, + "3": { + "then": "There are {climbing:boulder} boulder routes" + } + }, + "question": "Is bouldering possible here?" + }, + "8": { + "mappings": { + "0": { + "then": "Toprope climbing is possible here" + }, + "1": { + "then": "Toprope climbing is not possible here" + }, + "2": { + "then": "There are {climbing:toprope} toprope routes" + } + }, + "question": "Is toprope climbing possible here?" + }, + "9": { + "mappings": { + "0": { + "then": "Sport climbing is possible here" + }, + "1": { + "then": "Sport climbing is not possible here" + }, + "2": { + "then": "There are {climbing:sport} sport climbing routes" + } + }, + "question": "Is sport climbing possible here on fixed anchors?" + }, + "10": { + "mappings": { + "0": { + "then": "Traditional climbing is possible here" + }, + "1": { + "then": "Traditional climbing is not possible here" + }, + "2": { + "then": "There are {climbing:traditional} traditional climbing routes" + } + }, + "question": "Is traditional climbing possible here (using own gear e.g. chocks)?" + }, + "11": { + "mappings": { + "0": { + "then": "There is a speed climbing wall" + }, + "1": { + "then": "There is no speed climbing wall" + }, + "2": { + "then": "There are {climbing:speed} speed climbing walls" + } + }, + "question": "Is there a speed climbing wall?" + } + }, "units+": { "0": { "applicableUnits": { @@ -465,129 +590,6 @@ } } }, - "roamingRenderings": { - "0": { - "question": "Is there a (unofficial) website with more informations (e.g. topos)?" - }, - "1": { - "mappings": { - "0": { - "then": "The containing feature states that this is publicly accessible
{_embedding_feature:access:description}" - }, - "1": { - "then": "The containing feature states that a permit is needed to access
{_embedding_feature:access:description}" - }, - "2": { - "then": "The containing feature states that this is only accessible to customers
{_embedding_feature:access:description}" - }, - "3": { - "then": "The containing feature states that this is only accessible to club members
{_embedding_feature:access:description}" - } - } - }, - "2": { - "mappings": { - "0": { - "then": "Publicly accessible to anyone" - }, - "1": { - "then": "You need a permit to access here" - }, - "2": { - "then": "Only custumers" - }, - "3": { - "then": "Only club members" - } - }, - "question": "Who can access here?" - }, - "4": { - "question": "What is the (average) length of the routes in meters?", - "render": "The routes are {canonical(climbing:length)} long on average" - }, - "5": { - "question": "What is the level of the easiest route here, accoring to the french classification system?", - "render": "The minimal difficulty is {climbing:grade:french:min} according to the french/belgian system" - }, - "6": { - "question": "What is the level of the most difficult route here, accoring to the french classification system?", - "render": "The maximal difficulty is {climbing:grade:french:max} according to the french/belgian system" - }, - "7": { - "mappings": { - "0": { - "then": "Bouldering is possible here" - }, - "1": { - "then": "Bouldering is not possible here" - }, - "2": { - "then": "Bouldering is possible, allthough there are only a few routes" - }, - "3": { - "then": "There are {climbing:boulder} boulder routes" - } - }, - "question": "Is bouldering possible here?" - }, - "8": { - "mappings": { - "0": { - "then": "Toprope climbing is possible here" - }, - "1": { - "then": "Toprope climbing is not possible here" - }, - "2": { - "then": "There are {climbing:toprope} toprope routes" - } - }, - "question": "Is toprope climbing possible here?" - }, - "9": { - "mappings": { - "0": { - "then": "Sport climbing is possible here" - }, - "1": { - "then": "Sport climbing is not possible here" - }, - "2": { - "then": "There are {climbing:sport} sport climbing routes" - } - }, - "question": "Is sport climbing possible here on fixed anchors?" - }, - "10": { - "mappings": { - "0": { - "then": "Traditional climbing is possible here" - }, - "1": { - "then": "Traditional climbing is not possible here" - }, - "2": { - "then": "There are {climbing:traditional} traditional climbing routes" - } - }, - "question": "Is traditional climbing possible here (using own gear e.g. chocks)?" - }, - "11": { - "mappings": { - "0": { - "then": "There is a speed climbing wall" - }, - "1": { - "then": "There is no speed climbing wall" - }, - "2": { - "then": "There are {climbing:speed} speed climbing walls" - } - }, - "question": "Is there a speed climbing wall?" - } - }, "title": "Open Climbing Map" }, "cycle_highways": { @@ -634,27 +636,29 @@ } } }, - "roamingRenderings": { - "0": { - "mappings": { - "0": { - "then": "This street is a cyclestreet (and has a speed limit of 30 km/h)" + "overrideAll": { + "tagRenderings+": { + "0": { + "mappings": { + "0": { + "then": "This street is a cyclestreet (and has a speed limit of 30 km/h)" + }, + "1": { + "then": "This street is a cyclestreet" + }, + "2": { + "then": "This street will become a cyclstreet soon" + }, + "3": { + "then": "This street is not a cyclestreet" + } }, - "1": { - "then": "This street is a cyclestreet" - }, - "2": { - "then": "This street will become a cyclstreet soon" - }, - "3": { - "then": "This street is not a cyclestreet" - } + "question": "Is this street a cyclestreet?" }, - "question": "Is this street a cyclestreet?" - }, - "1": { - "question": "When will this street become a cyclestreet?", - "render": "This street will become a cyclestreet at {cyclestreet:start_date}" + "1": { + "question": "When will this street become a cyclestreet?", + "render": "This street will become a cyclestreet at {cyclestreet:start_date}" + } } }, "shortDescription": "A map of cyclestreets", diff --git a/langs/themes/fr.json b/langs/themes/fr.json index 0c73fa52c..f258a5cb0 100644 --- a/langs/themes/fr.json +++ b/langs/themes/fr.json @@ -225,21 +225,23 @@ } } }, - "roamingRenderings": { - "0": { - "question": "Qui est l’exploitant du site ?", - "render": "Ce site est exploité par {operator}" - }, - "1": { - "mappings": { - "0": { - "then": "Ce site a une source d’alimentation" - }, - "1": { - "then": "Ce site n’a pas de source d’alimentation" - } + "overrideAll": { + "tagRenderings+": { + "0": { + "question": "Qui est l’exploitant du site ?", + "render": "Ce site est exploité par {operator}" }, - "question": "Ce site a-t’il une source d’électricité ?" + "1": { + "mappings": { + "0": { + "then": "Ce site a une source d’alimentation" + }, + "1": { + "then": "Ce site n’a pas de source d’alimentation" + } + }, + "question": "Ce site a-t’il une source d’électricité ?" + } } }, "shortDescription": "Trouver des sites pour passer la nuit avec votre camping-car", @@ -439,6 +441,87 @@ } }, "overrideAll": { + "tagRenderings+": { + "0": { + "question": "Existe-t’il un site avec plus d’informations (ex : topographie) ?" + }, + "1": { + "mappings": { + "0": { + "then": "L’élément englobant indique un accès libre
{_embedding_feature:access:description}" + }, + "1": { + "then": "L’élément englobant indique qu’ une autorisation d’accès est nécessaire
{_embedding_feature:access:description}" + }, + "2": { + "then": "L’élément englobant indique que l’accès est réservés aux clients
{_embedding_feature:access:description}" + }, + "3": { + "then": "L’élément englobant indique que l’accès est réservé aux membres
{_embedding_feature:access:description}" + } + } + }, + "2": { + "mappings": { + "0": { + "then": "Libre d’accès" + }, + "1": { + "then": "Une autorisation est nécessaire" + }, + "2": { + "then": "Réservé aux clients" + }, + "3": { + "then": "Réservé aux membres" + } + }, + "question": "Qui peut y accéder ?" + }, + "4": { + "question": "Quelle est la longueur moyenne des voies en mètres ?", + "render": "Les voies font {canonical(climbing:length)} de long en moyenne" + }, + "5": { + "question": "Quel est le niveau de la voie la plus simple selon la classification franco-belge ?", + "render": "La difficulté minimale est {climbing:grade:french:min} selon la classification franco-belge" + }, + "6": { + "question": "Quel est le niveau de la voie la plus difficile selon la classification franco-belge ?", + "render": "La difficulté maximale est {climbing:grade:french:max} selon la classification franco-belge" + }, + "7": { + "mappings": { + "0": { + "then": "L’escalade de bloc est possible" + }, + "1": { + "then": "L’escalade de bloc n’est pas possible" + }, + "2": { + "then": "L’escalade de bloc est possible sur des voies précises" + }, + "3": { + "then": "Il y a {climbing:boulder} voies d’escalade de bloc" + } + }, + "question": "L’escalade de bloc est-elle possible ici ?" + }, + "8": { + "mappings": { + "0": { + "then": "L’escalade à la moulinette est possible" + }, + "1": { + "then": "L’escalade à la moulinette n’est pas possible" + }, + "2": { + "then": "{climbing:toprope} voies sont équipées de moulinettes" + } + }, + "question": "Est-il possible d’escalader à la moulinette ?" + } + }, "units+": { "0": { "applicableUnits": { @@ -452,87 +535,6 @@ } } }, - "roamingRenderings": { - "0": { - "question": "Existe-t’il un site avec plus d’informations (ex : topographie) ?" - }, - "1": { - "mappings": { - "0": { - "then": "L’élément englobant indique un accès libre
{_embedding_feature:access:description}" - }, - "1": { - "then": "L’élément englobant indique qu’ une autorisation d’accès est nécessaire
{_embedding_feature:access:description}" - }, - "2": { - "then": "L’élément englobant indique que l’accès est réservés aux clients
{_embedding_feature:access:description}" - }, - "3": { - "then": "L’élément englobant indique que l’accès est réservé aux membres
{_embedding_feature:access:description}" - } - } - }, - "2": { - "mappings": { - "0": { - "then": "Libre d’accès" - }, - "1": { - "then": "Une autorisation est nécessaire" - }, - "2": { - "then": "Réservé aux clients" - }, - "3": { - "then": "Réservé aux membres" - } - }, - "question": "Qui peut y accéder ?" - }, - "4": { - "question": "Quelle est la longueur moyenne des voies en mètres ?", - "render": "Les voies font {canonical(climbing:length)} de long en moyenne" - }, - "5": { - "question": "Quel est le niveau de la voie la plus simple selon la classification franco-belge ?", - "render": "La difficulté minimale est {climbing:grade:french:min} selon la classification franco-belge" - }, - "6": { - "question": "Quel est le niveau de la voie la plus difficile selon la classification franco-belge ?", - "render": "La difficulté maximale est {climbing:grade:french:max} selon la classification franco-belge" - }, - "7": { - "mappings": { - "0": { - "then": "L’escalade de bloc est possible" - }, - "1": { - "then": "L’escalade de bloc n’est pas possible" - }, - "2": { - "then": "L’escalade de bloc est possible sur des voies précises" - }, - "3": { - "then": "Il y a {climbing:boulder} voies d’escalade de bloc" - } - }, - "question": "L’escalade de bloc est-elle possible ici ?" - }, - "8": { - "mappings": { - "0": { - "then": "L’escalade à la moulinette est possible" - }, - "1": { - "then": "L’escalade à la moulinette n’est pas possible" - }, - "2": { - "then": "{climbing:toprope} voies sont équipées de moulinettes" - } - }, - "question": "Est-il possible d’escalader à la moulinette ?" - } - }, "title": "Open Climbing Map" }, "cyclofix": { diff --git a/langs/themes/id.json b/langs/themes/id.json index b9e07e1ad..8fd97982c 100644 --- a/langs/themes/id.json +++ b/langs/themes/id.json @@ -52,14 +52,16 @@ } } }, - "roamingRenderings": { - "1": { - "mappings": { - "0": { - "then": "Tempat ini memiliki catu daya" - }, - "1": { - "then": "Tempat ini tidak memiliki sumber listrik" + "overrideAll": { + "tagRenderings+": { + "1": { + "mappings": { + "0": { + "then": "Tempat ini memiliki catu daya" + }, + "1": { + "then": "Tempat ini tidak memiliki sumber listrik" + } } } } diff --git a/langs/themes/it.json b/langs/themes/it.json index b1a398028..2b71cfcff 100644 --- a/langs/themes/it.json +++ b/langs/themes/it.json @@ -225,21 +225,23 @@ } } }, - "roamingRenderings": { - "0": { - "question": "Chi gestisce questo luogo?", - "render": "Questo luogo è gestito da {operator}" - }, - "1": { - "mappings": { - "0": { - "then": "Questo luogo fornisce corrente elettrica" - }, - "1": { - "then": "Questo luogo non fornisce corrente elettrica" - } + "overrideAll": { + "tagRenderings+": { + "0": { + "question": "Chi gestisce questo luogo?", + "render": "Questo luogo è gestito da {operator}" }, - "question": "Questo luogo fornisce corrente elettrica?" + "1": { + "mappings": { + "0": { + "then": "Questo luogo fornisce corrente elettrica" + }, + "1": { + "then": "Questo luogo non fornisce corrente elettrica" + } + }, + "question": "Questo luogo fornisce corrente elettrica?" + } } }, "shortDescription": "Trova aree dove passare la notte con il tuo camper", @@ -310,17 +312,19 @@ } } }, - "roamingRenderings": { - "0": { - "mappings": { - "3": { - "then": "Questa strada non è una strada ciclabile" + "overrideAll": { + "tagRenderings+": { + "0": { + "mappings": { + "3": { + "then": "Questa strada non è una strada ciclabile" + } } + }, + "1": { + "question": "Questa strada diventerà una strada ciclabile quando?", + "render": "Questa strada diventerà una strada ciclabile dal {cyclestreet:start_date}" } - }, - "1": { - "question": "Questa strada diventerà una strada ciclabile quando?", - "render": "Questa strada diventerà una strada ciclabile dal {cyclestreet:start_date}" } } }, diff --git a/langs/themes/ja.json b/langs/themes/ja.json index a42d749bb..de0493785 100644 --- a/langs/themes/ja.json +++ b/langs/themes/ja.json @@ -225,21 +225,23 @@ } } }, - "roamingRenderings": { - "0": { - "question": "この店は誰が経営しているんですか?", - "render": "この場所は{operator}によって運営されます" - }, - "1": { - "mappings": { - "0": { - "then": "この場所には電源があります" - }, - "1": { - "then": "この場所には電源がありません" - } + "overrideAll": { + "tagRenderings+": { + "0": { + "question": "この店は誰が経営しているんですか?", + "render": "この場所は{operator}によって運営されます" }, - "question": "この場所に電源はありますか?" + "1": { + "mappings": { + "0": { + "then": "この場所には電源があります" + }, + "1": { + "then": "この場所には電源がありません" + } + }, + "question": "この場所に電源はありますか?" + } } }, "shortDescription": "キャンパーと夜を共にするキャンプサイトを見つける", @@ -379,94 +381,96 @@ } } }, - "roamingRenderings": { - "0": { - "question": "もっと情報のある(非公式の)ウェブサイトはありますか(例えば、topos)?" - }, - "4": { - "question": "ルートの(平均)長さはメートル単位でいくつですか?", - "render": "ルートの長さは平均で{canonical(climbing:length)}です" - }, - "5": { - "question": "ここで一番簡単なルートのレベルは、フランスのランク評価システムで何ですか?", - "render": "フランス/ベルギーのランク評価システムでは、最小の難易度は{climbing:grade:french:min}です" - }, - "6": { - "question": "フランスのランク評価によると、ここで一番難しいルートのレベルはどれくらいですか?", - "render": "フランス/ベルギーのランク評価システムでは、最大の難易度は{climbing:grade:french:max}です" - }, - "7": { - "mappings": { - "0": { - "then": "ボルダリングはここで可能です" - }, - "1": { - "then": "ここではボルダリングはできません" - }, - "2": { - "then": "ボルダリングは可能ですが、少しのルートしかありません" - }, - "3": { - "then": "{climbing:boulder} ボルダールートがある" - } + "overrideAll": { + "tagRenderings+": { + "0": { + "question": "もっと情報のある(非公式の)ウェブサイトはありますか(例えば、topos)?" }, - "question": "ここでボルダリングはできますか?" - }, - "8": { - "mappings": { - "0": { - "then": "ここでToprope登坂ができます" - }, - "1": { - "then": "ここではToprope登坂はできません" - }, - "2": { - "then": "{climbing:toprope} 登坂ルートがある" - } + "4": { + "question": "ルートの(平均)長さはメートル単位でいくつですか?", + "render": "ルートの長さは平均で{canonical(climbing:length)}です" }, - "question": "ここでtoprope登坂はできますか?" - }, - "9": { - "mappings": { - "0": { - "then": "ここでスポーツクライミングができます" - }, - "1": { - "then": "ここではスポーツクライミングはできません" - }, - "2": { - "then": "スポーツクライミングの {climbing:sport} ルートがある" - } + "5": { + "question": "ここで一番簡単なルートのレベルは、フランスのランク評価システムで何ですか?", + "render": "フランス/ベルギーのランク評価システムでは、最小の難易度は{climbing:grade:french:min}です" }, - "question": "ここでは固定アンカー式のスポーツクライミングはできますか?" - }, - "10": { - "mappings": { - "0": { - "then": "ここでは伝統的な登山が可能です" - }, - "1": { - "then": "伝統的な登山はここではできない" - }, - "2": { - "then": "{climbing:traditional} の伝統的な登山ルートがある" - } + "6": { + "question": "フランスのランク評価によると、ここで一番難しいルートのレベルはどれくらいですか?", + "render": "フランス/ベルギーのランク評価システムでは、最大の難易度は{climbing:grade:french:max}です" }, - "question": "伝統的な登山はここで可能ですか(例えば、チョックのような独自のギアを使用して)?" - }, - "11": { - "mappings": { - "0": { - "then": "スピードクライミングウォールがある" + "7": { + "mappings": { + "0": { + "then": "ボルダリングはここで可能です" + }, + "1": { + "then": "ここではボルダリングはできません" + }, + "2": { + "then": "ボルダリングは可能ですが、少しのルートしかありません" + }, + "3": { + "then": "{climbing:boulder} ボルダールートがある" + } }, - "1": { - "then": "スピードクライミングウォールがない" - }, - "2": { - "then": "{climbing:speed} のスピードクライミングウォールがある" - } + "question": "ここでボルダリングはできますか?" }, - "question": "スピードクライミングウォールはありますか?" + "8": { + "mappings": { + "0": { + "then": "ここでToprope登坂ができます" + }, + "1": { + "then": "ここではToprope登坂はできません" + }, + "2": { + "then": "{climbing:toprope} 登坂ルートがある" + } + }, + "question": "ここでtoprope登坂はできますか?" + }, + "9": { + "mappings": { + "0": { + "then": "ここでスポーツクライミングができます" + }, + "1": { + "then": "ここではスポーツクライミングはできません" + }, + "2": { + "then": "スポーツクライミングの {climbing:sport} ルートがある" + } + }, + "question": "ここでは固定アンカー式のスポーツクライミングはできますか?" + }, + "10": { + "mappings": { + "0": { + "then": "ここでは伝統的な登山が可能です" + }, + "1": { + "then": "伝統的な登山はここではできない" + }, + "2": { + "then": "{climbing:traditional} の伝統的な登山ルートがある" + } + }, + "question": "伝統的な登山はここで可能ですか(例えば、チョックのような独自のギアを使用して)?" + }, + "11": { + "mappings": { + "0": { + "then": "スピードクライミングウォールがある" + }, + "1": { + "then": "スピードクライミングウォールがない" + }, + "2": { + "then": "{climbing:speed} のスピードクライミングウォールがある" + } + }, + "question": "スピードクライミングウォールはありますか?" + } } }, "title": "登山地図を開く" @@ -498,27 +502,29 @@ } } }, - "roamingRenderings": { - "0": { - "mappings": { - "0": { - "then": "cyclestreet(最高速度は30km/h)" + "overrideAll": { + "tagRenderings+": { + "0": { + "mappings": { + "0": { + "then": "cyclestreet(最高速度は30km/h)" + }, + "1": { + "then": "この通りはcyclestreetだ" + }, + "2": { + "then": "この通りはまもなくcyclstreetになるだろう" + }, + "3": { + "then": "この通りはcyclestreetではない" + } }, - "1": { - "then": "この通りはcyclestreetだ" - }, - "2": { - "then": "この通りはまもなくcyclstreetになるだろう" - }, - "3": { - "then": "この通りはcyclestreetではない" - } + "question": "この通りはcyclestreetですか?" }, - "question": "この通りはcyclestreetですか?" - }, - "1": { - "question": "この通りはいつcyclestreetになるんですか?", - "render": "この通りは{cyclestreet:start_date}に、cyclestreetになります" + "1": { + "question": "この通りはいつcyclestreetになるんですか?", + "render": "この通りは{cyclestreet:start_date}に、cyclestreetになります" + } } }, "shortDescription": "cyclestreetsの地図", diff --git a/langs/themes/nb_NO.json b/langs/themes/nb_NO.json index dc5599dab..d00366f03 100644 --- a/langs/themes/nb_NO.json +++ b/langs/themes/nb_NO.json @@ -108,17 +108,19 @@ } } }, - "roamingRenderings": { - "7": { - "mappings": { - "0": { - "then": "Buldring er mulig her" + "overrideAll": { + "tagRenderings+": { + "7": { + "mappings": { + "0": { + "then": "Buldring er mulig her" + }, + "1": { + "then": "Buldring er ikke mulig her" + } }, - "1": { - "then": "Buldring er ikke mulig her" - } - }, - "question": "Er buldring mulig her?" + "question": "Er buldring mulig her?" + } } }, "title": "Åpent klatrekart" @@ -136,23 +138,25 @@ "name": "Alle gater" } }, - "roamingRenderings": { - "0": { - "mappings": { - "0": { - "then": "Denne gaten er en sykkelvei (og har en fartsgrense på 30 km/t)" + "overrideAll": { + "tagRenderings+": { + "0": { + "mappings": { + "0": { + "then": "Denne gaten er en sykkelvei (og har en fartsgrense på 30 km/t)" + }, + "1": { + "then": "Denne gaten er en sykkelvei" + }, + "2": { + "then": "Denne gaten vil bli sykkelvei ganske snart" + }, + "3": { + "then": "Denne gaten er ikke en sykkelvei" + } }, - "1": { - "then": "Denne gaten er en sykkelvei" - }, - "2": { - "then": "Denne gaten vil bli sykkelvei ganske snart" - }, - "3": { - "then": "Denne gaten er ikke en sykkelvei" - } - }, - "question": "Er denne gaten en sykkelvei?" + "question": "Er denne gaten en sykkelvei?" + } } }, "shortDescription": "Et kart over sykkelveier" diff --git a/langs/themes/nl.json b/langs/themes/nl.json index a6322e3d5..165e71b13 100644 --- a/langs/themes/nl.json +++ b/langs/themes/nl.json @@ -97,67 +97,69 @@ } } }, - "roamingRenderings": { - "0": { - "mappings": { - "0": { - "then": "Dit gebied is vrij toegankelijk" + "overrideAll": { + "tagRenderings+": { + "0": { + "mappings": { + "0": { + "then": "Dit gebied is vrij toegankelijk" + }, + "1": { + "then": "Vrij toegankelijk" + }, + "2": { + "then": "Niet toegankelijk" + }, + "3": { + "then": "Niet toegankelijk, want privégebied" + }, + "4": { + "then": "Toegankelijk, ondanks dat het privegebied is" + }, + "5": { + "then": "Enkel toegankelijk met een gids of tijdens een activiteit" + }, + "6": { + "then": "Toegankelijk mits betaling" + } }, - "1": { - "then": "Vrij toegankelijk" - }, - "2": { - "then": "Niet toegankelijk" - }, - "3": { - "then": "Niet toegankelijk, want privégebied" - }, - "4": { - "then": "Toegankelijk, ondanks dat het privegebied is" - }, - "5": { - "then": "Enkel toegankelijk met een gids of tijdens een activiteit" - }, - "6": { - "then": "Toegankelijk mits betaling" - } + "question": "Is dit gebied toegankelijk?", + "render": "De toegankelijkheid van dit gebied is: {access:description}" }, - "question": "Is dit gebied toegankelijk?", - "render": "De toegankelijkheid van dit gebied is: {access:description}" - }, - "1": { - "mappings": { - "1": { - "then": "Dit gebied wordt beheerd door Natuurpunt" + "1": { + "mappings": { + "1": { + "then": "Dit gebied wordt beheerd door Natuurpunt" + }, + "2": { + "then": "Dit gebied wordt beheerd door {operator}" + }, + "3": { + "then": "Dit gebied wordt beheerd door het Agentschap Natuur en Bos" + } }, - "2": { - "then": "Dit gebied wordt beheerd door {operator}" + "question": "Wie beheert dit gebied?", + "render": "Beheer door {operator}" + }, + "2": { + "render": "Extra info: {description}" + }, + "3": { + "render": "Extra info via buurtnatuur.be: {description:0}" + }, + "4": { + "question": "Wat is de Nederlandstalige naam van dit gebied?", + "render": "Dit gebied heet {name:nl}" + }, + "5": { + "mappings": { + "0": { + "then": "Dit gebied heeft geen naam" + } }, - "3": { - "then": "Dit gebied wordt beheerd door het Agentschap Natuur en Bos" - } - }, - "question": "Wie beheert dit gebied?", - "render": "Beheer door {operator}" - }, - "2": { - "render": "Extra info: {description}" - }, - "3": { - "render": "Extra info via buurtnatuur.be: {description:0}" - }, - "4": { - "question": "Wat is de Nederlandstalige naam van dit gebied?", - "render": "Dit gebied heet {name:nl}" - }, - "5": { - "mappings": { - "0": { - "then": "Dit gebied heeft geen naam" - } - }, - "question": "Wat is de naam van dit gebied?", - "render": "Dit gebied heet {name}" + "question": "Wat is de naam van dit gebied?", + "render": "Dit gebied heet {name}" + } } }, "shortDescription": "Met deze tool kan je natuur in je buurt in kaart brengen en meer informatie geven over je favoriete plekje", @@ -368,6 +370,106 @@ } }, "overrideAll": { + "tagRenderings+": { + "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": { + "question": "Wat is de (gemiddelde) lengte van de klimroutes, in meter?", + "render": "De klimroutes zijn gemiddeld {canonical(climbing:length)} lang" + }, + "5": { + "question": "Wat is het niveau van de makkelijkste route, volgens het Franse classificatiesysteem?", + "render": "De minimale klimmoeilijkheid is {climbing:grade:french:min} volgens het Franse/Belgische systeem" + }, + "6": { + "question": "Wat is het niveau van de moeilijkste route, volgens het Franse classificatiesysteem?", + "render": "De maximale klimmoeilijkheid is {climbing:grade:french:max} volgens het Franse/Belgische systeem" + }, + "7": { + "mappings": { + "0": { + "then": "Bolderen kan hier" + }, + "1": { + "then": "Bolderen kan hier niet" + }, + "2": { + "then": "Bolderen kan hier, maar er zijn niet zoveel routes" + }, + "3": { + "then": "Er zijn hier {climbing:boulder} bolderroutes" + } + }, + "question": "Is het mogelijk om hier te bolderen?" + }, + "8": { + "mappings": { + "0": { + "then": "Toprope-klimmen kan hier" + }, + "1": { + "then": "Toprope-klimmen kan hier niet" + }, + "2": { + "then": "Er zijn hier {climbing:toprope} toprope routes" + } + }, + "question": "Is het mogelijk om hier te toprope-klimmen?" + }, + "9": { + "mappings": { + "0": { + "then": "Sportklimmen/voorklimmen kan hier" + }, + "1": { + "then": "Sportklimmen/voorklimmen kan hier niet" + }, + "2": { + "then": "Er zijn hier {climbing:sport} sportklimroutes/voorklimroutes" + } + }, + "question": "Is het mogelijk om hier te sportklimmen/voorklimmen op reeds aangebrachte haken?" + }, + "10": { + "mappings": { + "0": { + "then": "Traditioneel klimmen kan hier" + }, + "1": { + "then": "Traditioneel klimmen kan hier niet" + }, + "2": { + "then": "Er zijn hier {climbing:traditional} traditionele klimroutes" + } + }, + "question": "Is het mogelijk om hier traditioneel te klimmen?
(Dit is klimmen met klemblokjes en friends)" + }, + "11": { + "mappings": { + "0": { + "then": "Er is een snelklimmuur voor speed climbing" + }, + "1": { + "then": "Er is geen snelklimmuur voor speed climbing" + }, + "2": { + "then": "Er zijn hier {climbing:speed} snelklimmuren" + } + }, + "question": "Is er een snelklimmuur (speed climbing)?" + } + }, "units+": { "0": { "applicableUnits": { @@ -381,106 +483,6 @@ } } }, - "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": { - "question": "Wat is de (gemiddelde) lengte van de klimroutes, in meter?", - "render": "De klimroutes zijn gemiddeld {canonical(climbing:length)} lang" - }, - "5": { - "question": "Wat is het niveau van de makkelijkste route, volgens het Franse classificatiesysteem?", - "render": "De minimale klimmoeilijkheid is {climbing:grade:french:min} volgens het Franse/Belgische systeem" - }, - "6": { - "question": "Wat is het niveau van de moeilijkste route, volgens het Franse classificatiesysteem?", - "render": "De maximale klimmoeilijkheid is {climbing:grade:french:max} volgens het Franse/Belgische systeem" - }, - "7": { - "mappings": { - "0": { - "then": "Bolderen kan hier" - }, - "1": { - "then": "Bolderen kan hier niet" - }, - "2": { - "then": "Bolderen kan hier, maar er zijn niet zoveel routes" - }, - "3": { - "then": "Er zijn hier {climbing:boulder} bolderroutes" - } - }, - "question": "Is het mogelijk om hier te bolderen?" - }, - "8": { - "mappings": { - "0": { - "then": "Toprope-klimmen kan hier" - }, - "1": { - "then": "Toprope-klimmen kan hier niet" - }, - "2": { - "then": "Er zijn hier {climbing:toprope} toprope routes" - } - }, - "question": "Is het mogelijk om hier te toprope-klimmen?" - }, - "9": { - "mappings": { - "0": { - "then": "Sportklimmen/voorklimmen kan hier" - }, - "1": { - "then": "Sportklimmen/voorklimmen kan hier niet" - }, - "2": { - "then": "Er zijn hier {climbing:sport} sportklimroutes/voorklimroutes" - } - }, - "question": "Is het mogelijk om hier te sportklimmen/voorklimmen op reeds aangebrachte haken?" - }, - "10": { - "mappings": { - "0": { - "then": "Traditioneel klimmen kan hier" - }, - "1": { - "then": "Traditioneel klimmen kan hier niet" - }, - "2": { - "then": "Er zijn hier {climbing:traditional} traditionele klimroutes" - } - }, - "question": "Is het mogelijk om hier traditioneel te klimmen?
(Dit is klimmen met klemblokjes en friends)" - }, - "11": { - "mappings": { - "0": { - "then": "Er is een snelklimmuur voor speed climbing" - }, - "1": { - "then": "Er is geen snelklimmuur voor speed climbing" - }, - "2": { - "then": "Er zijn hier {climbing:speed} snelklimmuren" - } - }, - "question": "Is er een snelklimmuur (speed climbing)?" - } - }, "title": "Open klimkaart" }, "cycle_infra": { @@ -515,27 +517,29 @@ } } }, - "roamingRenderings": { - "0": { - "mappings": { - "0": { - "then": "Deze straat is een fietsstraat (en dus zone 30)" + "overrideAll": { + "tagRenderings+": { + "0": { + "mappings": { + "0": { + "then": "Deze straat is een fietsstraat (en dus zone 30)" + }, + "1": { + "then": "Deze straat i een fietsstraat" + }, + "2": { + "then": "Deze straat wordt binnenkort een fietsstraat" + }, + "3": { + "then": "Deze straat is geen fietsstraat" + } }, - "1": { - "then": "Deze straat i een fietsstraat" - }, - "2": { - "then": "Deze straat wordt binnenkort een fietsstraat" - }, - "3": { - "then": "Deze straat is geen fietsstraat" - } + "question": "Is deze straat een fietsstraat?" }, - "question": "Is deze straat een fietsstraat?" - }, - "1": { - "question": "Wanneer wordt deze straat een fietsstraat?", - "render": "Deze straat wordt fietsstraat op {cyclestreet:start_date}" + "1": { + "question": "Wanneer wordt deze straat een fietsstraat?", + "render": "Deze straat wordt fietsstraat op {cyclestreet:start_date}" + } } }, "shortDescription": "Een kaart met alle gekende fietsstraten", diff --git a/langs/themes/ru.json b/langs/themes/ru.json index e473c91ce..692300526 100644 --- a/langs/themes/ru.json +++ b/langs/themes/ru.json @@ -269,24 +269,26 @@ } } }, - "roamingRenderings": { - "0": { - "question": "Есть ли (неофициальный) веб-сайт с более подробной информацией (напр., topos)?" - }, - "2": { - "mappings": { - "3": { - "then": "Только членам клуба" + "overrideAll": { + "tagRenderings+": { + "0": { + "question": "Есть ли (неофициальный) веб-сайт с более подробной информацией (напр., topos)?" + }, + "2": { + "mappings": { + "3": { + "then": "Только членам клуба" + } } - } - }, - "9": { - "mappings": { - "0": { - "then": "Здесь можно заняться спортивным скалолазанием" - }, - "1": { - "then": "Спортивное скалолазание здесь невозможно" + }, + "9": { + "mappings": { + "0": { + "then": "Здесь можно заняться спортивным скалолазанием" + }, + "1": { + "then": "Спортивное скалолазание здесь невозможно" + } } } } diff --git a/package-lock.json b/package-lock.json index a21cb12e7..ca84f2e65 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,7 +30,7 @@ "jspdf": "^2.3.1", "latlon2country": "^1.1.3", "leaflet": "^1.7.1", - "leaflet-providers": "^1.10.2", + "leaflet-providers": "^1.13.0", "leaflet-simple-map-screenshoter": "^0.4.4", "leaflet.markercluster": "^1.4.1", "libphonenumber": "0.0.10", @@ -10035,9 +10035,9 @@ "integrity": "sha512-/xwPEBidtg69Q3HlqPdU3DnrXQOvQU/CCHA1tcDQVzOwm91YMYaILjNp7L4Eaw5Z4sOYdbBz6koWyibppd8Zqw==" }, "node_modules/leaflet-providers": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/leaflet-providers/-/leaflet-providers-1.12.0.tgz", - "integrity": "sha512-pU/mR4B+NbayBGCg5/88dmRq7t1EGiNPhsVGV3yqHuDn594vIwus4CiPVW0RtiKJNKg8Vf1pILAbFl0i+yk+lQ==" + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/leaflet-providers/-/leaflet-providers-1.13.0.tgz", + "integrity": "sha512-f/sN5wdgBbVA2jcCYzScIfYNxKdn2wBJP9bu+5cRX9Xj6g8Bt1G9Sr8WgJAt/ckIFIc3LVVxCBNFpSCfTuUElg==" }, "node_modules/leaflet-simple-map-screenshoter": { "version": "0.4.4", @@ -25965,9 +25965,9 @@ "integrity": "sha512-/xwPEBidtg69Q3HlqPdU3DnrXQOvQU/CCHA1tcDQVzOwm91YMYaILjNp7L4Eaw5Z4sOYdbBz6koWyibppd8Zqw==" }, "leaflet-providers": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/leaflet-providers/-/leaflet-providers-1.12.0.tgz", - "integrity": "sha512-pU/mR4B+NbayBGCg5/88dmRq7t1EGiNPhsVGV3yqHuDn594vIwus4CiPVW0RtiKJNKg8Vf1pILAbFl0i+yk+lQ==" + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/leaflet-providers/-/leaflet-providers-1.13.0.tgz", + "integrity": "sha512-f/sN5wdgBbVA2jcCYzScIfYNxKdn2wBJP9bu+5cRX9Xj6g8Bt1G9Sr8WgJAt/ckIFIc3LVVxCBNFpSCfTuUElg==" }, "leaflet-simple-map-screenshoter": { "version": "0.4.4", diff --git a/package.json b/package.json index f480e7dc0..ea7d04f0f 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "jspdf": "^2.3.1", "latlon2country": "^1.1.3", "leaflet": "^1.7.1", - "leaflet-providers": "^1.10.2", + "leaflet-providers": "^1.13.0", "leaflet-simple-map-screenshoter": "^0.4.4", "leaflet.markercluster": "^1.4.1", "libphonenumber": "0.0.10", diff --git a/test/Actors.spec.ts b/test/Actors.spec.ts new file mode 100644 index 000000000..b53cfd5c7 --- /dev/null +++ b/test/Actors.spec.ts @@ -0,0 +1,143 @@ +import T from "./TestHelper"; +import State from "../State"; +import {AllKnownLayouts} from "../Customizations/AllKnownLayouts"; +import SelectedElementTagsUpdater from "../Logic/Actors/SelectedElementTagsUpdater"; +import UserRelatedState from "../Logic/State/UserRelatedState"; +import {Utils} from "../Utils"; +import ScriptUtils from "../scripts/ScriptUtils"; +import SelectedFeatureHandler from "../Logic/Actors/SelectedFeatureHandler"; +import {UIEventSource} from "../Logic/UIEventSource"; +import {ElementStorage} from "../Logic/ElementStorage"; +import Loc from "../Models/Loc"; + +export default class ActorsSpec extends T { + + constructor() { + + const latestTags = { + "amenity": "public_bookcase", + "books": "children;adults", + "capacity": "25", + "description": "Deze boekenruilkast vindt je recht tegenover de Pim Pam Poem", + "image:0": "https://i.imgur.com/Z8a69UG.jpg", + "name": "Stubbekwartier-buurtbibliotheek", + "nobrand": "yes", + "opening_hours": "24/7", + "operator": "Huisbewoner", + "public_bookcase:type": "reading_box" + } + + Utils.injectJsonDownloadForTests( + "https://www.openstreetmap.org/api/0.6/node/5568693115", + { + "version": "0.6", + "generator": "CGImap 0.8.5 (1815943 spike-06.openstreetmap.org)", + "copyright": "OpenStreetMap and contributors", + "attribution": "http://www.openstreetmap.org/copyright", + "license": "http://opendatacommons.org/licenses/odbl/1-0/", + "elements": [{ + "type": "node", + "id": 5568693115, + "lat": 51.2179199, + "lon": 3.2154662, + "timestamp": "2021-08-21T16:22:55Z", + "version": 6, + "changeset": 110034454, + "user": "Pieter Vander Vennet", + "uid": 3818858, + "tags": latestTags + }] + } + ) + + super("Actors", [ + [ + "download latest version", + () => { + const state = new UserRelatedState(AllKnownLayouts.allKnownLayouts.get("bookcases")) + const feature = { + "type": "Feature", + "id": "node/5568693115", + "properties": { + "amenity": "public_bookcase", + "books": "children;adults", + "capacity": "25", + "description": "Deze boekenruilkast vindt je recht tegenover de Pim Pam Poem", + "image:0": "https://i.imgur.com/Z8a69UG.jpg", + "name": "OUTDATED NAME", + "nobrand": "yes", + "opening_hours": "24/7", + "operator": "Huisbewoner", + "public_bookcase:type": "reading_box", + "id": "node/5568693115", + "_lat": "51.2179199", + "_lon": "3.2154662", + "fixme": "SOME FIXME" + }, + "geometry": { + "type": "Point", + "coordinates": [ + 3.2154662, + 51.2179199 + ] + }, + "bbox": { + "maxLat": 51.2179199, + "maxLon": 3.2154662, + "minLat": 51.2179199, + "minLon": 3.2154662 + }, + "_lon": 3.2154662, + "_lat": 51.2179199 + } + state.allElements.addOrGetElement(feature) + SelectedElementTagsUpdater.installCallback(state) + + // THis should trigger a download of the latest feaures and update the tags + // However, this doesn't work with ts-node for some reason + state.selectedElement.setData(feature) + + SelectedElementTagsUpdater.applyUpdate(state, latestTags, feature.properties.id) + + // The name should be updated + T.equals("Stubbekwartier-buurtbibliotheek", feature.properties.name) + // The fixme should be removed + T.equals(undefined, feature.properties.fixme) + + }], + ["Hash without selected element should download geojson from OSM-API", async () => { + const hash = new UIEventSource("node/5568693115") + const selected = new UIEventSource(undefined) + const loc = new UIEventSource({ + lat: 0, + lon: 0, + zoom: 0 + }) + + + loc.addCallback(_ => { + T.equals("node/5568693115", selected.data.properties.id) + T.equals(14, loc.data.zoom) + T.equals( 51.2179199, loc.data.lat) + }) + + new SelectedFeatureHandler(hash, { + selectedElement: selected, + allElements: new ElementStorage(), + featurePipeline: undefined, + locationControl: loc, + layoutToUse: undefined + }) + + + + + }] + + + ]); + + } + + +} \ No newline at end of file diff --git a/test/TestAll.ts b/test/TestAll.ts index cf1d4d174..9b23957d1 100644 --- a/test/TestAll.ts +++ b/test/TestAll.ts @@ -12,6 +12,7 @@ import {Utils} from "../Utils"; import TileFreshnessCalculatorSpec from "./TileFreshnessCalculator.spec"; import WikidataSpecTest from "./Wikidata.spec.test"; import ImageProviderSpec from "./ImageProvider.spec"; +import ActorsSpec from "./Actors.spec"; ScriptUtils.fixUtils() @@ -27,7 +28,8 @@ const allTests = [ new SplitActionSpec(), new TileFreshnessCalculatorSpec(), new WikidataSpecTest(), - new ImageProviderSpec() + new ImageProviderSpec(), + new ActorsSpec() ] Utils.externalDownloadFunction = async (url) => { diff --git a/test/TestHelper.ts b/test/TestHelper.ts index b55186e7c..ab7139160 100644 --- a/test/TestHelper.ts +++ b/test/TestHelper.ts @@ -19,6 +19,7 @@ export default class T { try { test(); } catch (e) { + console.log("ERROR: ", e, e.stack) failures.push({testsuite: this.name, name: name, msg: "" + e}); } }