diff --git a/InitUiElements.ts b/InitUiElements.ts deleted file mode 100644 index a21a8de798..0000000000 --- a/InitUiElements.ts +++ /dev/null @@ -1,567 +0,0 @@ -import {FixedUiElement} from "./UI/Base/FixedUiElement"; -import Toggle from "./UI/Input/Toggle"; -import State from "./State"; -import {UIEventSource} from "./Logic/UIEventSource"; -import {QueryParameters} from "./Logic/Web/QueryParameters"; -import StrayClickHandler from "./Logic/Actors/StrayClickHandler"; -import SimpleAddUI from "./UI/BigComponents/SimpleAddUI"; -import CenterMessageBox from "./UI/CenterMessageBox"; -import UserBadge from "./UI/BigComponents/UserBadge"; -import SearchAndGo from "./UI/BigComponents/SearchAndGo"; -import {LocalStorageSource} from "./Logic/Web/LocalStorageSource"; -import {Utils} from "./Utils"; -import Svg from "./Svg"; -import Link from "./UI/Base/Link"; -import * as personal from "./assets/themes/personal/personal.json"; -import * as L from "leaflet"; -import Img from "./UI/Base/Img"; -import Attribution from "./UI/BigComponents/Attribution"; -import BackgroundLayerResetter from "./Logic/Actors/BackgroundLayerResetter"; -import FullWelcomePaneWithTabs from "./UI/BigComponents/FullWelcomePaneWithTabs"; -import ShowDataLayer from "./UI/ShowDataLayer/ShowDataLayer"; -import Hash from "./Logic/Web/Hash"; -import FeaturePipeline from "./Logic/FeatureSource/FeaturePipeline"; -import ScrollableFullScreen from "./UI/Base/ScrollableFullScreen"; -import Translations from "./UI/i18n/Translations"; -import MapControlButton from "./UI/MapControlButton"; -import LZString from "lz-string"; -import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers"; -import LeftControls from "./UI/BigComponents/LeftControls"; -import RightControls from "./UI/BigComponents/RightControls"; -import {LayoutConfigJson} from "./Models/ThemeConfig/Json/LayoutConfigJson"; -import LayoutConfig from "./Models/ThemeConfig/LayoutConfig"; -import Minimap from "./UI/Base/Minimap"; -import SelectedFeatureHandler from "./Logic/Actors/SelectedFeatureHandler"; -import Combine from "./UI/Base/Combine"; -import {SubtleButton} from "./UI/Base/SubtleButton"; -import ShowTileInfo from "./UI/ShowDataLayer/ShowTileInfo"; -import {Tiles} from "./Models/TileRange"; -import {TileHierarchyAggregator} from "./UI/ShowDataLayer/TileHierarchyAggregator"; -import FilterConfig from "./Models/ThemeConfig/FilterConfig"; -import FilteredLayer from "./Models/FilteredLayer"; -import {AllKnownLayouts} from "./Customizations/AllKnownLayouts"; -import ShowOverlayLayer from "./UI/ShowDataLayer/ShowOverlayLayer"; - -export class InitUiElements { - static InitAll( - layoutToUse: LayoutConfig, - layoutFromBase64: string, - testing: UIEventSource, - layoutName: string, - layoutDefinition: string = "" - ) { - if (layoutToUse === undefined) { - console.log("Incorrect layout"); - new FixedUiElement( - `Error: incorrect layout ${layoutName}
Go back` - ) - .AttachTo("centermessage") - .onClick(() => { - }); - throw "Incorrect layout"; - } - - console.log( - "Using layout: ", - layoutToUse.id, - "LayoutFromBase64 is ", - layoutFromBase64 - ); - - if(layoutToUse.id === personal.id){ - layoutToUse.layers = AllKnownLayouts.AllPublicLayers() - for (const layer of layoutToUse.layers) { - layer.minzoomVisible = Math.max(layer.minzoomVisible, layer.minzoom) - layer.minzoom = Math.max(16, layer.minzoom) - } - } - - State.state = new State(layoutToUse); - - if(layoutToUse.id === personal.id) { - // Disable overpass all together - State.state.overpassMaxZoom.setData(0) - - } - - // This 'leaks' the global state via the window object, useful for debugging - // @ts-ignore - window.mapcomplete_state = State.state; - - if (layoutToUse.hideFromOverview) { - State.state.osmConnection - .GetPreference("hidden-theme-" + layoutToUse.id + "-enabled") - .setData("true"); - } - - if (layoutFromBase64 !== "false") { - State.state.layoutDefinition = layoutDefinition; - console.log( - "Layout definition:", - Utils.EllipsesAfter(State.state.layoutDefinition, 100) - ); - if (testing.data !== "true") { - State.state.osmConnection.OnLoggedIn(() => { - State.state.osmConnection - .GetLongPreference("installed-theme-" + layoutToUse.id) - .setData(State.state.layoutDefinition); - }); - } else { - console.warn( - "NOT saving custom layout to OSM as we are tesing -> probably in an iFrame" - ); - } - } - - if (layoutToUse.customCss !== undefined) { - Utils.LoadCustomCss(layoutToUse.customCss); - } - - InitUiElements.InitBaseMap(); - - InitUiElements.OnlyIf(State.state.featureSwitchUserbadge, () => { - new UserBadge().AttachTo("userbadge"); - }); - - InitUiElements.OnlyIf(State.state.featureSwitchSearch, () => { - new SearchAndGo().AttachTo("searchbox"); - }); - - InitUiElements.OnlyIf(State.state.featureSwitchWelcomeMessage, () => { - InitUiElements.InitWelcomeMessage(); - }); - - if (State.state.featureSwitchIframe.data) { - const currentLocation = State.state.locationControl; - const url = `${window.location.origin}${ - window.location.pathname - }?z=${currentLocation.data.zoom ?? 0}&lat=${ - currentLocation.data.lat ?? 0 - }&lon=${currentLocation.data.lon ?? 0}`; - new MapControlButton( - new Link(Svg.pop_out_img, url, true).SetClass( - "block w-full h-full p-1.5" - ) - ).AttachTo("messagesbox"); - } - - function addHomeMarker() { - const userDetails = State.state.osmConnection.userDetails.data; - if (userDetails === undefined) { - return false; - } - const home = userDetails.home; - if (home === undefined) { - return userDetails.loggedIn; // If logged in, the home is not set and we unregister. If not logged in, we stay registered if a login still comes - } - const leaflet = State.state.leafletMap.data; - if (leaflet === undefined) { - return false; - } - const color = getComputedStyle(document.body).getPropertyValue( - "--subtle-detail-color" - ); - const icon = L.icon({ - iconUrl: Img.AsData( - Svg.home_white_bg.replace(/#ffffff/g, color) - ), - iconSize: [30, 30], - iconAnchor: [15, 15], - }); - const marker = L.marker([home.lat, home.lon], {icon: icon}); - marker.addTo(leaflet); - return true; - } - - State.state.osmConnection.userDetails - .addCallbackAndRunD(_ => addHomeMarker()); - State.state.leafletMap.addCallbackAndRunD(_ => addHomeMarker()) - - InitUiElements.setupAllLayerElements(); - State.state.locationControl.ping(); - - new SelectedFeatureHandler(Hash.hash, State.state) - - // Reset the loading message once things are loaded - new CenterMessageBox().AttachTo("centermessage"); - document - .getElementById("centermessage") - .classList.add("pointer-events-none"); - } - - static LoadLayoutFromHash( - userLayoutParam: UIEventSource - ): [LayoutConfig, string] { - let hash = location.hash.substr(1); - try { - const layoutFromBase64 = userLayoutParam.data; - // layoutFromBase64 contains the name of the theme. This is partly to do tracking with goat counter - - const dedicatedHashFromLocalStorage = LocalStorageSource.Get( - "user-layout-" + layoutFromBase64.replace(" ", "_") - ); - if (dedicatedHashFromLocalStorage.data?.length < 10) { - dedicatedHashFromLocalStorage.setData(undefined); - } - - const hashFromLocalStorage = LocalStorageSource.Get( - "last-loaded-user-layout" - ); - if (hash.length < 10) { - hash = - dedicatedHashFromLocalStorage.data ?? - hashFromLocalStorage.data; - } else { - console.log("Saving hash to local storage"); - hashFromLocalStorage.setData(hash); - dedicatedHashFromLocalStorage.setData(hash); - } - - let json: {}; - try { - json = JSON.parse(atob(hash)); - } catch (e) { - // We try to decode with lz-string - json = JSON.parse( - Utils.UnMinify(LZString.decompressFromBase64(hash)) - ) as LayoutConfigJson; - } - - // @ts-ignore - const layoutToUse = new LayoutConfig(json, false); - userLayoutParam.setData(layoutToUse.id); - return [layoutToUse, btoa(Utils.MinifyJSON(JSON.stringify(json)))]; - } catch (e) { - - if (hash === undefined || hash.length < 10) { - e = "Did you effectively add a theme? It seems no data could be found." - } - - new Combine([ - "Error: could not parse the custom layout:", - new FixedUiElement("" + e).SetClass("alert"), - new SubtleButton("./assets/svg/mapcomplete_logo.svg", - "Go back to the theme overview", - {url: window.location.protocol + "//" + window.location.hostname + "/index.html", newTab: false}) - - ]) - .SetClass("flex flex-col") - .AttachTo("centermessage"); - throw e; - } - } - - private static OnlyIf( - featureSwitch: UIEventSource, - callback: () => void - ) { - featureSwitch.addCallbackAndRun(() => { - if (featureSwitch.data) { - callback(); - } - }); - } - - private static InitWelcomeMessage() { - const isOpened = new UIEventSource(false); - const fullOptions = new FullWelcomePaneWithTabs(isOpened); - - // ?-Button on Desktop, opens panel with close-X. - const help = new MapControlButton(Svg.help_svg()); - help.onClick(() => isOpened.setData(true)); - new Toggle( - fullOptions.SetClass("welcomeMessage pointer-events-auto"), - help.SetClass("pointer-events-auto"), - isOpened - ) - .AttachTo("messagesbox"); - const openedTime = new Date().getTime(); - State.state.locationControl.addCallback(() => { - if (new Date().getTime() - openedTime < 15 * 1000) { - // Don't autoclose the first 15 secs when the map is moving - return; - } - isOpened.setData(false); - }); - - State.state.selectedElement.addCallbackAndRunD((_) => { - isOpened.setData(false); - }); - isOpened.setData( - Hash.hash.data === undefined || - Hash.hash.data === "" || - Hash.hash.data == "welcome" - ); - } - - private static InitBaseMap() { - State.state.availableBackgroundLayers = - AvailableBaseLayers.AvailableLayersAt(State.state.locationControl); - 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) { - return layer; - } - } - return AvailableBaseLayers.osmCarto; - }, - [State.state.availableBackgroundLayers], - (layer) => layer.id - ); - - new BackgroundLayerResetter( - State.state.backgroundLayer, - State.state.locationControl, - State.state.availableBackgroundLayers, - State.state.layoutToUse.defaultBackgroundId - ); - - const attr = new Attribution( - State.state.locationControl, - State.state.osmConnection.userDetails, - State.state.layoutToUse, - State.state.currentBounds - ); - - Minimap.createMiniMap({ - background: State.state.backgroundLayer, - location: State.state.locationControl, - leafletMap: State.state.leafletMap, - bounds: State.state.currentBounds, - attribution: attr, - lastClickLocation: State.state.LastClickLocation - }).SetClass("w-full h-full") - .AttachTo("leafletDiv") - - const layout = State.state.layoutToUse; - if (layout.lockLocation) { - if (layout.lockLocation === true) { - const tile = Tiles.embedded_tile( - layout.startLat, - layout.startLon, - layout.startZoom - 1 - ); - const bounds = Tiles.tile_bounds(tile.z, tile.x, tile.y); - // We use the bounds to get a sense of distance for this zoom level - const latDiff = bounds[0][0] - bounds[1][0]; - const lonDiff = bounds[0][1] - bounds[1][1]; - layout.lockLocation = [ - [layout.startLat - latDiff, layout.startLon - lonDiff], - [layout.startLat + latDiff, layout.startLon + lonDiff], - ]; - } - console.warn("Locking the bounds to ", layout.lockLocation); - State.state.leafletMap.addCallbackAndRunD(map => { - // @ts-ignore - map.setMaxBounds(layout.lockLocation); - map.setMinZoom(layout.startZoom); - }) - } - } - - private static InitLayers(): void { - const state = State.state; - const empty = [] - - const flayers: FilteredLayer[] = []; - - for (const layer of state.layoutToUse.layers) { - let defaultShown = "true" - if(state.layoutToUse.id === personal.id){ - defaultShown = "false" - } - - let isDisplayed: UIEventSource - if(state.layoutToUse.id === personal.id){ - isDisplayed = State.state.osmConnection.GetPreference("personal-theme-layer-" + layer.id + "-enabled") - .map(value => value === "yes", [], enabled => { - return enabled ? "yes" : ""; - }) - isDisplayed.addCallbackAndRun(d =>console.log("IsDisplayed for layer", layer.id, "is currently", d) ) - }else{ - isDisplayed = QueryParameters.GetQueryParameter( - "layer-" + layer.id, - defaultShown, - "Wether or not layer " + layer.id + " is shown" - ).map( - (str) => str !== "false", - [], - (b) => b.toString() - ); - } - const flayer = { - isDisplayed: isDisplayed, - layerDef: layer, - appliedFilters: new UIEventSource<{ filter: FilterConfig, selected: number }[]>([]), - }; - - if (layer.filters.length > 0) { - const filtersPerName = new Map() - layer.filters.forEach(f => filtersPerName.set(f.id, f)) - const qp = QueryParameters.GetQueryParameter("filter-" + layer.id, "","Filtering state for a layer") - flayer.appliedFilters.map(filters => { - filters = filters ?? [] - return filters.map(f => f.filter.id + "." + f.selected).join(",") - }, [], textual => { - if(textual.length === 0){ - return empty - } - return textual.split(",").map(part => { - const [filterId, selected] = part.split("."); - return {filter: filtersPerName.get(filterId), selected: Number(selected)} - }).filter(f => f.filter !== undefined && !isNaN(f.selected)) - }).syncWith(qp, true) - } - - flayers.push(flayer); - } - state.filteredLayers = new UIEventSource(flayers); - - - - const clustering = State.state.layoutToUse.clustering - const clusterCounter = TileHierarchyAggregator.createHierarchy() - new ShowDataLayer({ - features: clusterCounter.getCountsForZoom(clustering, State.state.locationControl, State.state.layoutToUse.clustering.minNeededElements), - leafletMap: State.state.leafletMap, - layerToShow: ShowTileInfo.styling, - enablePopups: false - }) - - State.state.featurePipeline = new FeaturePipeline( - source => { - - clusterCounter.addTile(source) - - // Do show features indicates if the 'showDataLayer' should be shown - const doShowFeatures = source.features.map( - f => { - const z = State.state.locationControl.data.zoom - - if(!source.layer.isDisplayed.data){ - return false; - } - - const bounds = State.state.currentBounds.data - if(bounds === undefined){ - // Map is not yet displayed - return false; - } - - if (!source.bbox.overlapsWith(bounds)) { - // Not within range -> features are hidden - return false - } - - - if (z < source.layer.layerDef.minzoom) { - // Layer is always hidden for this zoom level - return false; - } - - if (z > clustering.maxZoom) { - return true - } - - if (f.length > clustering.minNeededElements) { - // This tile alone already has too much features - return false - } - - let [tileZ, tileX, tileY] = Tiles.tile_from_index(source.tileIndex); - if (tileZ >= z) { - - while (tileZ > z) { - tileZ-- - tileX = Math.floor(tileX / 2) - tileY = Math.floor(tileY / 2) - } - - if (clusterCounter.getTile(Tiles.tile_index(tileZ, tileX, tileY))?.totalValue > clustering.minNeededElements) { - // To much elements - return false - } - } - - - - return true - }, [State.state.currentBounds, source.layer.isDisplayed] - ) - - new ShowDataLayer( - { - features: source, - leafletMap: State.state.leafletMap, - layerToShow: source.layer.layerDef, - doShowLayer: doShowFeatures - } - ); - }, state - ); - - - const initialized =new Set() - for (const overlayToggle of State.state.overlayToggles) { - new ShowOverlayLayer(overlayToggle.config, state.leafletMap, overlayToggle.isDisplayed) - initialized.add(overlayToggle.config) - } - - for (const tileLayerSource of state.layoutToUse.tileLayerSources) { - if (initialized.has(tileLayerSource)) { - continue - } - new ShowOverlayLayer(tileLayerSource, state.leafletMap) - } - } - - private static setupAllLayerElements() { - // ------------- Setup the layers ------------------------------- - - InitUiElements.InitLayers(); - - new LeftControls(State.state).AttachTo("bottom-left"); - new RightControls().AttachTo("bottom-right"); - - // ------------------ Setup various other UI elements ------------ - - InitUiElements.OnlyIf(State.state.featureSwitchAddNew, () => { - let presetCount = 0; - for (const layer of State.state.filteredLayers.data) { - for (const preset of layer.layerDef.presets) { - presetCount++; - } - } - if (presetCount == 0) { - return; - } - - const newPointDialogIsShown = new UIEventSource(false); - const addNewPoint = new ScrollableFullScreen( - () => Translations.t.general.add.title.Clone(), - () => new SimpleAddUI(newPointDialogIsShown), - "new", - newPointDialogIsShown - ); - addNewPoint.isShown.addCallback((isShown) => { - if (!isShown) { - State.state.LastClickLocation.setData(undefined); - } - }); - - new StrayClickHandler( - State.state.LastClickLocation, - State.state.selectedElement, - State.state.filteredLayers, - State.state.leafletMap, - addNewPoint - ); - }); - } -} diff --git a/Logic/DetermineLayout.ts b/Logic/DetermineLayout.ts new file mode 100644 index 0000000000..9af285952e --- /dev/null +++ b/Logic/DetermineLayout.ts @@ -0,0 +1,168 @@ +import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"; +import {QueryParameters} from "./Web/QueryParameters"; +import {AllKnownLayouts} from "../Customizations/AllKnownLayouts"; +import {FixedUiElement} from "../UI/Base/FixedUiElement"; +import {Utils} from "../Utils"; +import Combine from "../UI/Base/Combine"; +import {SubtleButton} from "../UI/Base/SubtleButton"; +import BaseUIElement from "../UI/BaseUIElement"; +import {UIEventSource} from "./UIEventSource"; +import {LocalStorageSource} from "./Web/LocalStorageSource"; +import LZString from "lz-string"; +import * as personal from "../assets/themes/personal/personal.json"; + +export default class DetermineLayout { + + /** + * Gets the correct layout for this website + */ + public static async GetLayout(): Promise<[LayoutConfig, string]> { + + + const loadCustomThemeParam = QueryParameters.GetQueryParameter("userlayout", "false", "If not 'false', a custom (non-official) theme is loaded. This custom layout can be done in multiple ways: \n\n- The hash of the URL contains a base64-encoded .json-file containing the theme definition\n- The hash of the URL contains a lz-compressed .json-file, as generated by the custom theme generator\n- The parameter itself is an URL, in which case that URL will be downloaded. It should point to a .json of a theme") + const layoutFromBase64 = decodeURIComponent(loadCustomThemeParam.data); + + if (layoutFromBase64.startsWith("http")) { + // The userLayout is actually an url + const layout = await DetermineLayout.LoadRemoteTheme(layoutFromBase64) + return [layout, undefined] + } + + if (layoutFromBase64 !== "false") { + // We have to load something from the hash (or from disk) + let loaded = DetermineLayout.LoadLayoutFromHash(loadCustomThemeParam); + if (loaded === null) { + return [null, undefined] + } + return loaded + } + + let layoutId: string = undefined + if (location.href.indexOf("buurtnatuur.be") >= 0) { + layoutId = "buurtnatuur" + } + + + const path = window.location.pathname.split("/").slice(-1)[0]; + if (path !== "index.html" && path !== "") { + layoutId = path; + if (path.endsWith(".html")) { + layoutId = path.substr(0, path.length - 5); + } + console.log("Using layout", layoutId); + } + layoutId = QueryParameters.GetQueryParameter("layout", layoutId, "The layout to load into MapComplete").data; + const layoutToUse: LayoutConfig = AllKnownLayouts.allKnownLayouts.get(layoutId?.toLowerCase()); + + if (layoutToUse?.id === personal.id) { + layoutToUse.layers = AllKnownLayouts.AllPublicLayers() + for (const layer of layoutToUse.layers) { + layer.minzoomVisible = Math.max(layer.minzoomVisible, layer.minzoom) + layer.minzoom = Math.max(16, layer.minzoom) + } + } + + return [layoutToUse, undefined] + } + + private static async LoadRemoteTheme(link: string): Promise { + console.log("Downloading map theme from ", link); + + new FixedUiElement(`Downloading the theme from the link...`) + .AttachTo("centermessage"); + + try { + + const data = await Utils.downloadJson(link) + try { + let parsed = data; + if (typeof parsed == "string") { + parsed = JSON.parse(parsed); + } + // Overwrite the id to the url + parsed.id = link; + return new LayoutConfig(parsed, false).patchImages(link, data); + } catch (e) { + + DetermineLayout.ShowErrorOnCustomTheme( + `${link} is invalid:`, + new FixedUiElement(e) + ) + return null; + } + + } catch (e) { + DetermineLayout.ShowErrorOnCustomTheme( + `${link} is invalid - probably not found or invalid JSON:`, + new FixedUiElement(e) + ) + return null; + } + } + + public static LoadLayoutFromHash( + userLayoutParam: UIEventSource + ): [LayoutConfig, string] | null { + let hash = location.hash.substr(1); + try { + // layoutFromBase64 contains the name of the theme. This is partly to do tracking with goat counter + const dedicatedHashFromLocalStorage = LocalStorageSource.Get( + "user-layout-" + userLayoutParam.data.replace(" ", "_") + ); + if (dedicatedHashFromLocalStorage.data?.length < 10) { + dedicatedHashFromLocalStorage.setData(undefined); + } + + const hashFromLocalStorage = LocalStorageSource.Get( + "last-loaded-user-layout" + ); + if (hash.length < 10) { + hash = + dedicatedHashFromLocalStorage.data ?? + hashFromLocalStorage.data; + } else { + console.log("Saving hash to local storage"); + hashFromLocalStorage.setData(hash); + dedicatedHashFromLocalStorage.setData(hash); + } + + let json: any; + try { + json = JSON.parse(atob(hash)); + } catch (e) { + // We try to decode with lz-string + try { + json = JSON.parse(Utils.UnMinify(LZString.decompressFromBase64(hash))) + } catch (e) { + DetermineLayout.ShowErrorOnCustomTheme("Could not decode the hash", new FixedUiElement("Not a valid (LZ-compressed) JSON")) + return null; + } + } + + const layoutToUse = new LayoutConfig(json, false); + userLayoutParam.setData(layoutToUse.id); + return [layoutToUse, btoa(Utils.MinifyJSON(JSON.stringify(json)))]; + } catch (e) { + if (hash === undefined || hash.length < 10) { + DetermineLayout.ShowErrorOnCustomTheme("Could not load a theme from the hash", new FixedUiElement("Hash does not contain data")) + } + return null; + } + } + + public static ShowErrorOnCustomTheme( + intro: string = "Error: could not parse the custom layout:", + error: BaseUIElement) { + new Combine([ + intro, + error.SetClass("alert"), + new SubtleButton("./assets/svg/mapcomplete_logo.svg", + "Go back to the theme overview", + {url: window.location.protocol + "//" + window.location.hostname + "/index.html", newTab: false}) + + ]) + .SetClass("flex flex-col clickable") + .AttachTo("centermessage"); + } + +} \ No newline at end of file diff --git a/Logic/FeatureSource/FeaturePipeline.ts b/Logic/FeatureSource/FeaturePipeline.ts index a24f18d8c3..b14a42c31b 100644 --- a/Logic/FeatureSource/FeaturePipeline.ts +++ b/Logic/FeatureSource/FeaturePipeline.ts @@ -73,7 +73,7 @@ export default class FeaturePipeline { readonly overpassTimeout: UIEventSource; readonly overpassMaxZoom: UIEventSource; readonly osmConnection: OsmConnection - readonly currentBounds: UIEventSource + readonly currentBounds: UIEventSource, }) { this.state = state; diff --git a/Logic/FeatureSource/TiledFeatureSource/DynamicTileSource.ts b/Logic/FeatureSource/TiledFeatureSource/DynamicTileSource.ts index a6ef04458c..faeb5869a5 100644 --- a/Logic/FeatureSource/TiledFeatureSource/DynamicTileSource.ts +++ b/Logic/FeatureSource/TiledFeatureSource/DynamicTileSource.ts @@ -1,8 +1,5 @@ - -import State from "../../../State"; import FilteredLayer from "../../../Models/FilteredLayer"; import {FeatureSourceForLayer, Tiled} from "../FeatureSource"; -import {Utils} from "../../../Utils"; import {UIEventSource} from "../../UIEventSource"; import Loc from "../../../Models/Loc"; import TileHierarchy from "./TileHierarchy"; @@ -25,7 +22,6 @@ export default class DynamicTileSource implements TileHierarchy() diff --git a/Logic/State/ElementsState.ts b/Logic/State/ElementsState.ts new file mode 100644 index 0000000000..241badaa2b --- /dev/null +++ b/Logic/State/ElementsState.ts @@ -0,0 +1,93 @@ +import FeatureSwitchState from "./FeatureSwitchState"; +import {ElementStorage} from "../ElementStorage"; +import {Changes} from "../Osm/Changes"; +import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; +import {UIEventSource} from "../UIEventSource"; +import Loc from "../../Models/Loc"; +import {BBox} from "../BBox"; +import {QueryParameters} from "../Web/QueryParameters"; +import {LocalStorageSource} from "../Web/LocalStorageSource"; +import {Utils} from "../../Utils"; +import ChangeToElementsActor from "../Actors/ChangeToElementsActor"; +import PendingChangesUploader from "../Actors/PendingChangesUploader"; +import TitleHandler from "../Actors/TitleHandler"; + +/** + * The part of the state keeping track of where the elements, loading them, configuring the feature pipeline etc + */ +export default class ElementsState extends FeatureSwitchState{ + + /** + The mapping from id -> UIEventSource + */ + public allElements: ElementStorage = new ElementStorage(); + /** + THe change handler + */ + public changes: Changes = new Changes(); + + /** + The latest element that was selected + */ + public readonly selectedElement = new UIEventSource( + undefined, + "Selected element" + ); + + + /** + * The map location: currently centered lat, lon and zoom + */ + public readonly locationControl = new UIEventSource(undefined, "locationControl"); + + /** + * The current visible extent of the screen + */ + public readonly currentBounds = new UIEventSource(undefined) + + + constructor(layoutToUse: LayoutConfig) { + super(layoutToUse); + { + // -- Location control initialization + const zoom = UIEventSource.asFloat( + QueryParameters.GetQueryParameter( + "z", + "" + (layoutToUse?.startZoom ?? 1), + "The initial/current zoom level" + ).syncWith(LocalStorageSource.Get("zoom")) + ); + const lat = UIEventSource.asFloat( + QueryParameters.GetQueryParameter( + "lat", + "" + (layoutToUse?.startLat ?? 0), + "The initial/current latitude" + ).syncWith(LocalStorageSource.Get("lat")) + ); + const lon = UIEventSource.asFloat( + QueryParameters.GetQueryParameter( + "lon", + "" + (layoutToUse?.startLon ?? 0), + "The initial/current longitude of the app" + ).syncWith(LocalStorageSource.Get("lon")) + ); + + this.locationControl.setData({ + zoom: Utils.asFloat(zoom.data), + lat: Utils.asFloat(lat.data), + lon: Utils.asFloat(lon.data), + }) + this.locationControl.addCallback((latlonz) => { + // Sync th location controls + zoom.setData(latlonz.zoom); + lat.setData(latlonz.lat); + lon.setData(latlonz.lon); + }); + } + + new ChangeToElementsActor(this.changes, this.allElements) + new PendingChangesUploader(this.changes, this.selectedElement); + new TitleHandler(this); + + } +} \ No newline at end of file diff --git a/Logic/State/FeaturePipelineState.ts b/Logic/State/FeaturePipelineState.ts new file mode 100644 index 0000000000..d6a0967966 --- /dev/null +++ b/Logic/State/FeaturePipelineState.ts @@ -0,0 +1,172 @@ +import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; +import FeaturePipeline from "../FeatureSource/FeaturePipeline"; +import State from "../../State"; +import {Tiles} from "../../Models/TileRange"; +import ShowDataLayer from "../../UI/ShowDataLayer/ShowDataLayer"; +import {TileHierarchyAggregator} from "../../UI/ShowDataLayer/TileHierarchyAggregator"; +import ShowTileInfo from "../../UI/ShowDataLayer/ShowTileInfo"; +import {UIEventSource} from "../UIEventSource"; +import MapState from "./MapState"; +import SelectedFeatureHandler from "../Actors/SelectedFeatureHandler"; +import Hash from "../Web/Hash"; +import ScrollableFullScreen from "../../UI/Base/ScrollableFullScreen"; +import Translations from "../../UI/i18n/Translations"; +import SimpleAddUI from "../../UI/BigComponents/SimpleAddUI"; +import StrayClickHandler from "../Actors/StrayClickHandler"; + +export default class FeaturePipelineState extends MapState { + + /** + * The piece of code which fetches data from various sources and shows it on the background map + */ + public readonly featurePipeline: FeaturePipeline; + private readonly featureAggregator: TileHierarchyAggregator; + + constructor(layoutToUse: LayoutConfig) { + super(layoutToUse); + + const clustering = layoutToUse.clustering + this.featureAggregator = TileHierarchyAggregator.createHierarchy(); + const clusterCounter = this.featureAggregator + const self = this; + this.featurePipeline = new FeaturePipeline( + source => { + + clusterCounter.addTile(source) + + // Do show features indicates if the 'showDataLayer' should be shown + const doShowFeatures = source.features.map( + f => { + const z = State.state.locationControl.data.zoom + + if (!source.layer.isDisplayed.data) { + return false; + } + + const bounds = State.state.currentBounds.data + if (bounds === undefined) { + // Map is not yet displayed + return false; + } + + if (!source.bbox.overlapsWith(bounds)) { + // Not within range -> features are hidden + return false + } + + + if (z < source.layer.layerDef.minzoom) { + // Layer is always hidden for this zoom level + return false; + } + + if (z > clustering.maxZoom) { + return true + } + + if (f.length > clustering.minNeededElements) { + // This tile alone already has too much features + return false + } + + let [tileZ, tileX, tileY] = Tiles.tile_from_index(source.tileIndex); + if (tileZ >= z) { + + while (tileZ > z) { + tileZ-- + tileX = Math.floor(tileX / 2) + tileY = Math.floor(tileY / 2) + } + + if (clusterCounter.getTile(Tiles.tile_index(tileZ, tileX, tileY))?.totalValue > clustering.minNeededElements) { + // To much elements + return false + } + } + + + return true + }, [State.state.currentBounds, source.layer.isDisplayed] + ) + + new ShowDataLayer( + { + features: source, + leafletMap: self.leafletMap, + layerToShow: source.layer.layerDef, + doShowLayer: doShowFeatures, + allElements: self.allElements, + selectedElement: self.selectedElement + } + ); + }, this + ); + new SelectedFeatureHandler(Hash.hash, this) + + this.AddClusteringToMap(this.leafletMap) + + } + + /** + * Adds the cluster-tiles to the given map + * @param leafletMap: a UIEventSource possible having a leaflet map + * @constructor + */ + public AddClusteringToMap(leafletMap: UIEventSource) { + const clustering = this.layoutToUse.clustering + new ShowDataLayer({ + features: this.featureAggregator.getCountsForZoom(clustering, this.locationControl, clustering.minNeededElements), + leafletMap: leafletMap, + layerToShow: ShowTileInfo.styling, + enablePopups: false, + }) + } + + public setupClickDialogOnMap(filterViewIsOpened: UIEventSource, leafletMap: UIEventSource) { + + const self = this + function setup(){ + let presetCount = 0; + for (const layer of self.layoutToUse.layers) { + for (const preset of layer.presets) { + presetCount++; + } + } + if (presetCount == 0) { + return; + } + + const newPointDialogIsShown = new UIEventSource(false); + const addNewPoint = new ScrollableFullScreen( + () => Translations.t.general.add.title.Clone(), + () => new SimpleAddUI(newPointDialogIsShown, filterViewIsOpened, self), + "new", + newPointDialogIsShown + ); + addNewPoint.isShown.addCallback((isShown) => { + if (!isShown) { + self.LastClickLocation.setData(undefined); + } + }); + + new StrayClickHandler( + self.LastClickLocation, + self.selectedElement, + self.filteredLayers, + leafletMap, + addNewPoint + ); + } + + this.featureSwitchAddNew.addCallbackAndRunD(addNewAllowed => { + if (addNewAllowed) { + setup() + return true; + } + }) + + } + + + +} \ No newline at end of file diff --git a/Logic/State/FeatureSwitchState.ts b/Logic/State/FeatureSwitchState.ts new file mode 100644 index 0000000000..215c819f85 --- /dev/null +++ b/Logic/State/FeatureSwitchState.ts @@ -0,0 +1,208 @@ +/** + * The part of the global state which initializes the feature switches, based on default values and on the layoutToUse + */ +import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; +import {UIEventSource} from "../UIEventSource"; +import {QueryParameters} from "../Web/QueryParameters"; +import Constants from "../../Models/Constants"; + +export default class FeatureSwitchState { + + /** + * The layout that is being used in this run + */ + public readonly layoutToUse: LayoutConfig; + + public readonly featureSwitchUserbadge: UIEventSource; + public readonly featureSwitchSearch: UIEventSource; + public readonly featureSwitchBackgroundSlection: UIEventSource; + public readonly featureSwitchAddNew: UIEventSource; + public readonly featureSwitchWelcomeMessage: UIEventSource; + public readonly featureSwitchIframePopoutEnabled: UIEventSource; + public readonly featureSwitchMoreQuests: UIEventSource; + public readonly featureSwitchShareScreen: UIEventSource; + public readonly featureSwitchGeolocation: UIEventSource; + public readonly featureSwitchIsTesting: UIEventSource; + public readonly featureSwitchIsDebugging: UIEventSource; + public readonly featureSwitchShowAllQuestions: UIEventSource; + public readonly featureSwitchApiURL: UIEventSource; + public readonly featureSwitchFilter: UIEventSource; + public readonly featureSwitchEnableExport: UIEventSource; + public readonly featureSwitchFakeUser: UIEventSource; + public readonly featureSwitchExportAsPdf: UIEventSource; + public readonly overpassUrl: UIEventSource; + public readonly overpassTimeout: UIEventSource; + public readonly overpassMaxZoom: UIEventSource; + public readonly osmApiTileSize: UIEventSource; + public readonly backgroundLayerId: UIEventSource; + + protected constructor(layoutToUse: LayoutConfig) { + this.layoutToUse = layoutToUse; + + + // Helper function to initialize feature switches + function featSw( + key: string, + deflt: (layout: LayoutConfig) => boolean, + documentation: string + ): UIEventSource { + + const defaultValue = deflt(layoutToUse); + const queryParam = QueryParameters.GetQueryParameter( + key, + "" + defaultValue, + documentation + ); + + // It takes the current layout, extracts the default value for this query parameter. A query parameter event source is then retrieved and flattened + return queryParam.map((str) => + str === undefined ? defaultValue : str !== "false" + ) + + } + + this.featureSwitchUserbadge = featSw( + "fs-userbadge", + (layoutToUse) => layoutToUse?.enableUserBadge ?? true, + "Disables/Enables the user information pill (userbadge) at the top left. Disabling this disables logging in and thus disables editing all together, effectively putting MapComplete into read-only mode." + ); + this.featureSwitchSearch = featSw( + "fs-search", + (layoutToUse) => layoutToUse?.enableSearch ?? true, + "Disables/Enables the search bar" + ); + this.featureSwitchBackgroundSlection = featSw( + "fs-background", + (layoutToUse) => layoutToUse?.enableBackgroundLayerSelection ?? true, + "Disables/Enables the background layer control" + ); + + this.featureSwitchFilter = featSw( + "fs-filter", + (layoutToUse) => layoutToUse?.enableLayers ?? true, + "Disables/Enables the filter" + ); + this.featureSwitchAddNew = featSw( + "fs-add-new", + (layoutToUse) => layoutToUse?.enableAddNewPoints ?? true, + "Disables/Enables the 'add new feature'-popup. (A theme without presets might not have it in the first place)" + ); + this.featureSwitchWelcomeMessage = featSw( + "fs-welcome-message", + () => true, + "Disables/enables the help menu or welcome message" + ); + this.featureSwitchIframePopoutEnabled = featSw( + "fs-iframe-popout", + (layoutToUse) => layoutToUse?.enableIframePopout, + "Disables/Enables the iframe-popout button. If in iframe mode and the welcome message is hidden, a popout button to the full mapcomplete instance is shown instead (unless disabled with this switch)" + ); + this.featureSwitchMoreQuests = featSw( + "fs-more-quests", + (layoutToUse) => layoutToUse?.enableMoreQuests ?? true, + "Disables/Enables the 'More Quests'-tab in the welcome message" + ); + this.featureSwitchShareScreen = featSw( + "fs-share-screen", + (layoutToUse) => layoutToUse?.enableShareScreen ?? true, + "Disables/Enables the 'Share-screen'-tab in the welcome message" + ); + this.featureSwitchGeolocation = featSw( + "fs-geolocation", + (layoutToUse) => layoutToUse?.enableGeolocation ?? true, + "Disables/Enables the geolocation button" + ); + this.featureSwitchShowAllQuestions = featSw( + "fs-all-questions", + (layoutToUse) => layoutToUse?.enableShowAllQuestions ?? false, + "Always show all questions" + ); + + this.featureSwitchEnableExport = featSw( + "fs-export", + (layoutToUse) => layoutToUse?.enableExportButton ?? false, + "Enable the export as GeoJSON and CSV button" + ); + this.featureSwitchExportAsPdf = featSw( + "fs-pdf", + (layoutToUse) => layoutToUse?.enablePdfDownload ?? false, + "Enable the PDF download button" + ); + + this.featureSwitchApiURL = QueryParameters.GetQueryParameter( + "backend", + "osm", + "The OSM backend to use - can be used to redirect mapcomplete to the testing backend when using 'osm-test'" + ); + + + let testingDefaultValue = false; + if (this.featureSwitchApiURL.data !== "osm-test" && + (location.hostname === "localhost" || location.hostname === "127.0.0.1")) { + testingDefaultValue = true + } + + + this.featureSwitchIsTesting = QueryParameters.GetQueryParameter( + "test", + ""+testingDefaultValue, + "If true, 'dryrun' mode is activated. The app will behave as normal, except that changes to OSM will be printed onto the console instead of actually uploaded to osm.org" + ).map( + (str) => str === "true", + [], + (b) => "" + b + ); + + this.featureSwitchIsDebugging = QueryParameters.GetQueryParameter( + "debug", + "false", + "If true, shows some extra debugging help such as all the available tags on every object" + ).map( + (str) => str === "true", + [], + (b) => "" + b + ); + + this.featureSwitchFakeUser = QueryParameters.GetQueryParameter("fake-user", "false", + "If true, 'dryrun' mode is activated and a fake user account is loaded") + .map(str => str === "true", [], b => "" + b); + + + + + this.overpassUrl = QueryParameters.GetQueryParameter("overpassUrl", + (layoutToUse?.overpassUrl ?? Constants.defaultOverpassUrls).join(","), + "Point mapcomplete to a different overpass-instance. Example: https://overpass-api.de/api/interpreter" + ).map(param => param.split(","), [], urls => urls.join(",")) + + this.overpassTimeout = UIEventSource.asFloat(QueryParameters.GetQueryParameter("overpassTimeout", + "" + layoutToUse?.overpassTimeout, + "Set a different timeout (in seconds) for queries in overpass")) + + + this.overpassMaxZoom = + UIEventSource.asFloat(QueryParameters.GetQueryParameter("overpassMaxZoom", + "" + layoutToUse?.overpassMaxZoom, + " point to switch between OSM-api and overpass")) + + this.osmApiTileSize = + UIEventSource.asFloat(QueryParameters.GetQueryParameter("osmApiTileSize", + "" + layoutToUse?.osmApiTileSize, + "Tilesize when the OSM-API is used to fetch data within a BBOX")) + + this.featureSwitchUserbadge.addCallbackAndRun(userbadge => { + if (!userbadge) { + this.featureSwitchAddNew.setData(false) + } + }) + + this.backgroundLayerId = QueryParameters.GetQueryParameter( + "background", + layoutToUse?.defaultBackgroundId ?? "osm", + "The id of the background layer to start with" + ); + + } + + +} \ No newline at end of file diff --git a/Logic/State/MapState.ts b/Logic/State/MapState.ts new file mode 100644 index 0000000000..bf3422762a --- /dev/null +++ b/Logic/State/MapState.ts @@ -0,0 +1,272 @@ +import UserRelatedState from "./UserRelatedState"; +import {UIEventSource} from "../UIEventSource"; +import BaseLayer from "../../Models/BaseLayer"; +import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; +import AvailableBaseLayers from "../Actors/AvailableBaseLayers"; +import BackgroundLayerResetter from "../Actors/BackgroundLayerResetter"; +import Attribution from "../../UI/BigComponents/Attribution"; +import Minimap, {MinimapObj} from "../../UI/Base/Minimap"; +import {Tiles} from "../../Models/TileRange"; +import * as L from "leaflet"; +import Img from "../../UI/Base/Img"; +import Svg from "../../Svg"; +import BaseUIElement from "../../UI/BaseUIElement"; +import FilteredLayer from "../../Models/FilteredLayer"; +import TilesourceConfig from "../../Models/ThemeConfig/TilesourceConfig"; +import {QueryParameters} from "../Web/QueryParameters"; +import * as personal from "../../assets/themes/personal/personal.json"; +import FilterConfig from "../../Models/ThemeConfig/FilterConfig"; +import ShowOverlayLayer from "../../UI/ShowDataLayer/ShowOverlayLayer"; + +/** + * Contains all the leaflet-map related state + */ +export default class MapState extends UserRelatedState { + + /** + The leaflet instance of the big basemap + */ + public leafletMap = new UIEventSource(undefined, "leafletmap"); + /** + * A list of currently available background layers + */ + public availableBackgroundLayers: UIEventSource; + + /** + * The current background layer + */ + public backgroundLayer: UIEventSource; + /** + * Last location where a click was registered + */ + public readonly LastClickLocation: UIEventSource<{ + lat: number; + lon: number; + }> = new UIEventSource<{ lat: number; lon: number }>(undefined); + + /** + * The location as delivered by the GPS + */ + public currentGPSLocation: UIEventSource<{ + latlng: { lat: number; lng: number }; + accuracy: number; + }> = new UIEventSource<{ + latlng: { lat: number; lng: number }; + accuracy: number; + }>(undefined); + + public readonly mainMapObject: BaseUIElement & MinimapObj; + + + /** + * WHich layers are enabled in the current theme + */ + public filteredLayers: UIEventSource = new UIEventSource([], "filteredLayers"); + /** + * Which overlays are shown + */ + public overlayToggles: { config: TilesourceConfig, isDisplayed: UIEventSource }[] + + + + constructor(layoutToUse: LayoutConfig) { + super(layoutToUse); + + this.availableBackgroundLayers = AvailableBaseLayers.AvailableLayersAt(this.locationControl); + + this.backgroundLayer = this.backgroundLayerId.map( + (selectedId: string) => { + if (selectedId === undefined) { + return AvailableBaseLayers.osmCarto; + } + + const available = this.availableBackgroundLayers.data; + for (const layer of available) { + if (layer.id === selectedId) { + return layer; + } + } + return AvailableBaseLayers.osmCarto; + }, + [this.availableBackgroundLayers], + (layer) => layer.id + ); + + + /* + * Selects a different background layer if the background layer has no coverage at the current location + */ + new BackgroundLayerResetter( + this.backgroundLayer, + this.locationControl, + this.availableBackgroundLayers, + this.layoutToUse.defaultBackgroundId + ); + + const attr = new Attribution( + this.locationControl, + this.osmConnection.userDetails, + this.layoutToUse, + this.currentBounds + ); + + // Will write into this.leafletMap + this.mainMapObject = Minimap.createMiniMap({ + background: this.backgroundLayer, + location: this.locationControl, + leafletMap: this.leafletMap, + bounds: this.currentBounds, + attribution: attr, + lastClickLocation: this.LastClickLocation + }) + + + this.overlayToggles = this.layoutToUse.tileLayerSources.filter(c => c.name !== undefined).map(c => ({ + config: c, + isDisplayed: QueryParameters.GetQueryParameter("overlay-" + c.id, "" + c.defaultState, "Wether or not the overlay " + c.id + " is shown").map(str => str === "true", [], b => "" + b) + })) + this.filteredLayers = this.InitializeFilteredLayers() + + + this.lockBounds() + this.AddAllOverlaysToMap(this.leafletMap) + this.addHomeMarker() + + + + } + + private addHomeMarker() { + const leafletMap = this.leafletMap + const osmConnection = this.osmConnection + + function addHomeMarker() { + const userDetails = osmConnection.userDetails.data; + if (userDetails === undefined) { + return false; + } + const home = userDetails.home; + if (home === undefined) { + return userDetails.loggedIn; // If logged in, the home is not set and we unregister. If not logged in, we stay registered if a login still comes + } + const leaflet = leafletMap.data; + if (leaflet === undefined) { + return false; + } + const color = getComputedStyle(document.body).getPropertyValue( + "--subtle-detail-color" + ); + const icon = L.icon({ + iconUrl: Img.AsData( + Svg.home_white_bg.replace(/#ffffff/g, color) + ), + iconSize: [30, 30], + iconAnchor: [15, 15], + }); + const marker = L.marker([home.lat, home.lon], {icon: icon}); + marker.addTo(leaflet); + return true; + } + + osmConnection.userDetails.addCallbackAndRunD(_ => addHomeMarker()); + leafletMap.addCallbackAndRunD(_ => addHomeMarker()) + } + + private lockBounds() { + const layout = this.layoutToUse; + if (layout.lockLocation) { + if (layout.lockLocation === true) { + const tile = Tiles.embedded_tile( + layout.startLat, + layout.startLon, + layout.startZoom - 1 + ); + const bounds = Tiles.tile_bounds(tile.z, tile.x, tile.y); + // We use the bounds to get a sense of distance for this zoom level + const latDiff = bounds[0][0] - bounds[1][0]; + const lonDiff = bounds[0][1] - bounds[1][1]; + layout.lockLocation = [ + [layout.startLat - latDiff, layout.startLon - lonDiff], + [layout.startLat + latDiff, layout.startLon + lonDiff], + ]; + } + console.warn("Locking the bounds to ", layout.lockLocation); + this.leafletMap.addCallbackAndRunD(map => { + // @ts-ignore + map.setMaxBounds(layout.lockLocation); + map.setMinZoom(layout.startZoom); + }) + } + } + private InitializeFilteredLayers() { + // Initialize the filtered layers state + + const layoutToUse = this.layoutToUse; + const empty = [] + const flayers: FilteredLayer[] = []; + for (const layer of layoutToUse.layers) { + let isDisplayed: UIEventSource + if (layoutToUse.id === personal.id) { + isDisplayed = this.osmConnection.GetPreference("personal-theme-layer-" + layer.id + "-enabled") + .map(value => value === "yes", [], enabled => { + return enabled ? "yes" : ""; + }) + isDisplayed.addCallbackAndRun(d => console.log("IsDisplayed for layer", layer.id, "is currently", d)) + } else { + isDisplayed = QueryParameters.GetQueryParameter( + "layer-" + layer.id, + "true", + "Wether or not layer " + layer.id + " is shown" + ).map( + (str) => str !== "false", + [], + (b) => b.toString() + ); + } + const flayer = { + isDisplayed: isDisplayed, + layerDef: layer, + appliedFilters: new UIEventSource<{ filter: FilterConfig, selected: number }[]>([]), + }; + + if (layer.filters.length > 0) { + const filtersPerName = new Map() + layer.filters.forEach(f => filtersPerName.set(f.id, f)) + const qp = QueryParameters.GetQueryParameter("filter-" + layer.id, "", "Filtering state for a layer") + flayer.appliedFilters.map(filters => { + filters = filters ?? [] + return filters.map(f => f.filter.id + "." + f.selected).join(",") + }, [], textual => { + if (textual.length === 0) { + return empty + } + return textual.split(",").map(part => { + const [filterId, selected] = part.split("."); + return {filter: filtersPerName.get(filterId), selected: Number(selected)} + }).filter(f => f.filter !== undefined && !isNaN(f.selected)) + }).syncWith(qp, true) + } + + flayers.push(flayer); + } + return new UIEventSource(flayers); + } + + public AddAllOverlaysToMap(leafletMap: UIEventSource){ + const initialized =new Set() + for (const overlayToggle of this.overlayToggles) { + new ShowOverlayLayer(overlayToggle.config, leafletMap, overlayToggle.isDisplayed) + initialized.add(overlayToggle.config) + } + + for (const tileLayerSource of this.layoutToUse.tileLayerSources) { + if (initialized.has(tileLayerSource)) { + continue + } + new ShowOverlayLayer(tileLayerSource, leafletMap) + } + + } + + +} \ No newline at end of file diff --git a/Logic/State/UserRelatedState.ts b/Logic/State/UserRelatedState.ts new file mode 100644 index 0000000000..a010fa102e --- /dev/null +++ b/Logic/State/UserRelatedState.ts @@ -0,0 +1,107 @@ +import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; +import {OsmConnection} from "../Osm/OsmConnection"; +import {MangroveIdentity} from "../Web/MangroveReviews"; +import {UIEventSource} from "../UIEventSource"; +import {QueryParameters} from "../Web/QueryParameters"; +import InstalledThemes from "../Actors/InstalledThemes"; +import {LocalStorageSource} from "../Web/LocalStorageSource"; +import {Utils} from "../../Utils"; +import Locale from "../../UI/i18n/Locale"; +import ElementsState from "./ElementsState"; +import SelectedElementTagsUpdater from "../Actors/SelectedElementTagsUpdater"; + +/** + * The part of the state which keeps track of user-related stuff, e.g. the OSM-connection, + * which layers they enabled, ... + */ +export default class UserRelatedState extends ElementsState { + + + /** + The user credentials + */ + public osmConnection: OsmConnection; + /** + * The key for mangrove + */ + public mangroveIdentity: MangroveIdentity; + /** + * Which layers are enabled in the personal theme + */ + public favouriteLayers: UIEventSource; + + /** + * WHich other themes the user previously visited + */ + public installedThemes: UIEventSource<{ layout: LayoutConfig; definition: string }[]>; + + constructor(layoutToUse: LayoutConfig) { + super(layoutToUse); + + this.osmConnection = new OsmConnection({ + changes: this.changes, + dryRun: this.featureSwitchIsTesting.data, + fakeUser: this.featureSwitchFakeUser.data, + allElements: this.allElements, + oauth_token: QueryParameters.GetQueryParameter( + "oauth_token", + undefined, + "Used to complete the login" + ), + layoutName: layoutToUse?.id, + osmConfiguration: <'osm' | 'osm-test'>this.featureSwitchApiURL.data + }) + + this.mangroveIdentity = new MangroveIdentity( + this.osmConnection.GetLongPreference("identity", "mangrove") + ); + + if (layoutToUse?.hideFromOverview) { + this.osmConnection + .GetPreference("hidden-theme-" + layoutToUse?.id + "-enabled") + .setData("true"); + } + + this.installedThemes = new InstalledThemes( + this.osmConnection + ).installedThemes; + + // Important: the favourite layers are initialized _after_ the installed themes, as these might contain an installedTheme + this.favouriteLayers = LocalStorageSource.Get("favouriteLayers") + .syncWith(this.osmConnection.GetLongPreference("favouriteLayers")) + .map( + (str) => Utils.Dedup(str?.split(";")) ?? [], + [], + (layers) => Utils.Dedup(layers)?.join(";") + ); + + + this.InitializeLanguage(); + new SelectedElementTagsUpdater(this) + + } + + private InitializeLanguage() { + const layoutToUse = this.layoutToUse; + Locale.language.syncWith(this.osmConnection.GetPreference("language")); + Locale.language + .addCallback((currentLanguage) => { + if (layoutToUse === undefined) { + return; + } + if (this.layoutToUse.language.indexOf(currentLanguage) < 0) { + console.log( + "Resetting language to", + layoutToUse.language[0], + "as", + currentLanguage, + " is unsupported" + ); + // The current language is not supported -> switch to a supported one + Locale.language.setData(layoutToUse.language[0]); + } + }) + .ping(); + } + +} \ No newline at end of file diff --git a/Logic/UIEventSource.ts b/Logic/UIEventSource.ts index 4c7f3bad69..d597bd5727 100644 --- a/Logic/UIEventSource.ts +++ b/Logic/UIEventSource.ts @@ -318,6 +318,22 @@ export class UIEventSource { } }) } + + public static asFloat(source: UIEventSource): UIEventSource { + return source.map( + (str) => { + let parsed = parseFloat(str); + return isNaN(parsed) ? undefined : parsed; + }, + [], + (fl) => { + if (fl === undefined || isNaN(fl)) { + return undefined; + } + return ("" + fl).substr(0, 8); + } + ) + } } export class UIEventSourceTools { diff --git a/Models/ThemeConfig/Json/LayoutConfigJson.ts b/Models/ThemeConfig/Json/LayoutConfigJson.ts index 401cd5f689..e2df334ca5 100644 --- a/Models/ThemeConfig/Json/LayoutConfigJson.ts +++ b/Models/ThemeConfig/Json/LayoutConfigJson.ts @@ -1,6 +1,5 @@ import {TagRenderingConfigJson} from "./TagRenderingConfigJson"; import {LayerConfigJson} from "./LayerConfigJson"; -import TilesourceConfig from "../TilesourceConfig"; import TilesourceConfigJson from "./TilesourceConfigJson"; /** @@ -16,7 +15,7 @@ import TilesourceConfigJson from "./TilesourceConfigJson"; * General remark: a type (string | any) indicates either a fixed or a translatable string. */ export interface LayoutConfigJson { - + /** * The id of this layout. * @@ -106,7 +105,20 @@ export interface LayoutConfigJson { * IF widenfactor is 1, this feature is disabled. A recommended value is between 1 and 3 */ widenFactor?: number; + /** + * At low zoom levels, overpass is used to query features. + * At high zoom level, the OSM api is used to fetch one or more BBOX aligning with a slippy tile. + * The overpassMaxZoom controls the flipoverpoint: if the zoom is this or lower, overpass is used. + */ + overpassMaxZoom?: 17 | number + /** + * When the OSM-api is used to fetch features, it does so in a tiled fashion. + * These tiles are using a ceratin zoom level, that can be controlled here + * Default: overpassMaxZoom + 1 + */ + osmApiTileSize: number + /** * A tagrendering depicts how to show some tags or how to show a question for it. * @@ -269,6 +281,7 @@ export interface LayoutConfigJson { enableShowAllQuestions?: boolean; enableDownload?: boolean; enablePdfDownload?: boolean; + enableIframePopout?: true | boolean; /** * Set one or more overpass URLs to use for this theme.. diff --git a/Models/ThemeConfig/LayoutConfig.ts b/Models/ThemeConfig/LayoutConfig.ts index f56a561b01..7527dd2cc1 100644 --- a/Models/ThemeConfig/LayoutConfig.ts +++ b/Models/ThemeConfig/LayoutConfig.ts @@ -46,6 +46,7 @@ export default class LayoutConfig { public readonly enableShowAllQuestions: boolean; public readonly enableExportButton: boolean; public readonly enablePdfDownload: boolean; + public readonly enableIframePopout: boolean; public readonly customCss?: string; /* @@ -54,8 +55,10 @@ export default class LayoutConfig { public readonly cacheTimeout?: number; public readonly overpassUrl: string[]; public readonly overpassTimeout: number; + public readonly overpassMaxZoom: number + public readonly osmApiTileSize: number public readonly official: boolean; - + constructor(json: LayoutConfigJson, official = true, context?: string) { this.official = official; this.id = json.id; @@ -171,6 +174,7 @@ export default class LayoutConfig { this.enableShowAllQuestions = json.enableShowAllQuestions ?? false; this.enableExportButton = json.enableDownload ?? false; this.enablePdfDownload = json.enablePdfDownload ?? false; + this.enableIframePopout = json.enableIframePopout ?? true this.customCss = json.customCss; this.cacheTimeout = json.cacheTimout ?? (60 * 24 * 60 * 60) this.overpassUrl = Constants.defaultOverpassUrls @@ -182,6 +186,8 @@ export default class LayoutConfig { } } this.overpassTimeout = json.overpassTimeout ?? 30 + this.overpassMaxZoom = json.overpassMaxZoom ?? 17 + this.osmApiTileSize = json.osmApiTileSize ?? this.overpassMaxZoom + 1 } diff --git a/State.ts b/State.ts index a040e54cfe..f11113f4ae 100644 --- a/State.ts +++ b/State.ts @@ -1,448 +1,19 @@ -import {Utils} from "./Utils"; -import {ElementStorage} from "./Logic/ElementStorage"; -import {Changes} from "./Logic/Osm/Changes"; -import {OsmConnection} from "./Logic/Osm/OsmConnection"; -import Locale from "./UI/i18n/Locale"; -import {UIEventSource} from "./Logic/UIEventSource"; -import {LocalStorageSource} from "./Logic/Web/LocalStorageSource"; -import {QueryParameters} from "./Logic/Web/QueryParameters"; -import {MangroveIdentity} from "./Logic/Web/MangroveReviews"; -import InstalledThemes from "./Logic/Actors/InstalledThemes"; -import BaseLayer from "./Models/BaseLayer"; -import Loc from "./Models/Loc"; -import Constants from "./Models/Constants"; -import TitleHandler from "./Logic/Actors/TitleHandler"; -import PendingChangesUploader from "./Logic/Actors/PendingChangesUploader"; -import FeaturePipeline from "./Logic/FeatureSource/FeaturePipeline"; -import FilteredLayer from "./Models/FilteredLayer"; -import ChangeToElementsActor from "./Logic/Actors/ChangeToElementsActor"; import LayoutConfig from "./Models/ThemeConfig/LayoutConfig"; -import {BBox} from "./Logic/BBox"; -import SelectedElementTagsUpdater from "./Logic/Actors/SelectedElementTagsUpdater"; -import TilesourceConfig from "./Models/ThemeConfig/TilesourceConfig"; +import FeaturePipelineState from "./Logic/State/FeaturePipelineState"; /** * Contains the global state: a bunch of UI-event sources */ -export default class State { - // The singleton of the global state - public static state: State; - - public readonly layoutToUse : LayoutConfig; - - /** - The mapping from id -> UIEventSource +export default class State extends FeaturePipelineState { + /* The singleton of the global state */ - public allElements: ElementStorage = new ElementStorage(); - /** - THe change handler - */ - public changes: Changes = new Changes(); - /** - The leaflet instance of the big basemap - */ - public leafletMap = new UIEventSource(undefined, "leafletmap"); - /** - * Background layer id - */ - public availableBackgroundLayers: UIEventSource; - /** - The user credentials - */ - public osmConnection: OsmConnection; - - public mangroveIdentity: MangroveIdentity; - - public favouriteLayers: UIEventSource; - - public filteredLayers: UIEventSource = new UIEventSource([], "filteredLayers"); - - public overlayToggles : { config: TilesourceConfig, isDisplayed: UIEventSource}[] - - /** - The latest element that was selected - */ - public readonly selectedElement = new UIEventSource( - undefined, - "Selected element" - ); - - public readonly featureSwitchUserbadge: UIEventSource; - public readonly featureSwitchSearch: UIEventSource; - public readonly featureSwitchBackgroundSlection: UIEventSource; - public readonly featureSwitchAddNew: UIEventSource; - public readonly featureSwitchWelcomeMessage: UIEventSource; - public readonly featureSwitchIframe: UIEventSource; - public readonly featureSwitchMoreQuests: UIEventSource; - public readonly featureSwitchShareScreen: UIEventSource; - public readonly featureSwitchGeolocation: UIEventSource; - public readonly featureSwitchIsTesting: UIEventSource; - public readonly featureSwitchIsDebugging: UIEventSource; - public readonly featureSwitchShowAllQuestions: UIEventSource; - public readonly featureSwitchApiURL: UIEventSource; - public readonly featureSwitchFilter: UIEventSource; - public readonly featureSwitchEnableExport: UIEventSource; - public readonly featureSwitchFakeUser: UIEventSource; - public readonly featureSwitchExportAsPdf: UIEventSource; - public readonly overpassUrl: UIEventSource; - public readonly overpassTimeout: UIEventSource; - - - public readonly overpassMaxZoom: UIEventSource = new UIEventSource(17, "overpass-max-zoom: point to switch between OSM-api and overpass"); - - public featurePipeline: FeaturePipeline; - - - /** - * The map location: currently centered lat, lon and zoom - */ - public readonly locationControl = new UIEventSource(undefined, "locationControl"); - - /** - * The current visible extent of the screen - */ - public readonly currentBounds = new UIEventSource(undefined) - - public backgroundLayer; - public readonly backgroundLayerId: UIEventSource; - - /* Last location where a click was registered - */ - public readonly LastClickLocation: UIEventSource<{ - lat: number; - lon: number; - }> = new UIEventSource<{ lat: number; lon: number }>(undefined); - - /** - * The location as delivered by the GPS - */ - public currentGPSLocation: UIEventSource<{ - latlng: { lat: number; lng: number }; - accuracy: number; - }> = new UIEventSource<{ - latlng: { lat: number; lng: number }; - accuracy: number; - }>(undefined); - public layoutDefinition: string; - public installedThemes: UIEventSource<{ layout: LayoutConfig; definition: string }[]>; - - public downloadControlIsOpened: UIEventSource = - QueryParameters.GetQueryParameter( - "download-control-toggle", - "false", - "Whether or not the download panel is shown" - ).map( - (str) => str !== "false", - [], - (b) => "" + b - ); - - public filterIsOpened: UIEventSource = - QueryParameters.GetQueryParameter( - "filter-toggle", - "false", - "Whether or not the filter view is shown" - ).map( - (str) => str !== "false", - [], - (b) => "" + b - ); - - public welcomeMessageOpenedTab = QueryParameters.GetQueryParameter( - "tab", - "0", - `The tab that is shown in the welcome-message. 0 = the explanation of the theme,1 = OSM-credits, 2 = sharescreen, 3 = more themes, 4 = about mapcomplete (user must be logged in and have >${Constants.userJourney.mapCompleteHelpUnlock} changesets)` - ).map( - (str) => (isNaN(Number(str)) ? 0 : Number(str)), - [], - (n) => "" + n - ); + public static state: FeaturePipelineState; constructor(layoutToUse: LayoutConfig) { - const self = this; - this.layoutToUse = layoutToUse; + super(layoutToUse) + } - // -- Location control initialization - { - const zoom = State.asFloat( - QueryParameters.GetQueryParameter( - "z", - "" + (layoutToUse?.startZoom ?? 1), - "The initial/current zoom level" - ).syncWith(LocalStorageSource.Get("zoom")) - ); - const lat = State.asFloat( - QueryParameters.GetQueryParameter( - "lat", - "" + (layoutToUse?.startLat ?? 0), - "The initial/current latitude" - ).syncWith(LocalStorageSource.Get("lat")) - ); - const lon = State.asFloat( - QueryParameters.GetQueryParameter( - "lon", - "" + (layoutToUse?.startLon ?? 0), - "The initial/current longitude of the app" - ).syncWith(LocalStorageSource.Get("lon")) - ); - - this.locationControl.setData({ - zoom: Utils.asFloat(zoom.data), - lat: Utils.asFloat(lat.data), - lon: Utils.asFloat(lon.data), - }) - this.locationControl.addCallback((latlonz) => { - // Sync th location controls - zoom.setData(latlonz.zoom); - lat.setData(latlonz.lat); - lon.setData(latlonz.lon); - }); - - } - - // Helper function to initialize feature switches - function featSw( - key: string, - deflt: (layout: LayoutConfig) => boolean, - documentation: string - ): UIEventSource { - - const defaultValue = deflt(self.layoutToUse); - const queryParam = QueryParameters.GetQueryParameter( - key, - "" + defaultValue, - documentation - ); - - // It takes the current layout, extracts the default value for this query parameter. A query parameter event source is then retrieved and flattened - return queryParam.map((str) => - str === undefined ? defaultValue : str !== "false" - ) - } - // Feature switch initialization - not as a function as the UIEventSources are readonly - { - this.featureSwitchUserbadge = featSw( - "fs-userbadge", - (layoutToUse) => layoutToUse?.enableUserBadge ?? true, - "Disables/Enables the user information pill (userbadge) at the top left. Disabling this disables logging in and thus disables editing all together, effectively putting MapComplete into read-only mode." - ); - this.featureSwitchSearch = featSw( - "fs-search", - (layoutToUse) => layoutToUse?.enableSearch ?? true, - "Disables/Enables the search bar" - ); - this.featureSwitchBackgroundSlection = featSw( - "fs-background", - (layoutToUse) => layoutToUse?.enableBackgroundLayerSelection ?? true, - "Disables/Enables the background layer control" - ); - - this.featureSwitchFilter = featSw( - "fs-filter", - (layoutToUse) => layoutToUse?.enableLayers ?? true, - "Disables/Enables the filter" - ); - this.featureSwitchAddNew = featSw( - "fs-add-new", - (layoutToUse) => layoutToUse?.enableAddNewPoints ?? true, - "Disables/Enables the 'add new feature'-popup. (A theme without presets might not have it in the first place)" - ); - this.featureSwitchWelcomeMessage = featSw( - "fs-welcome-message", - () => true, - "Disables/enables the help menu or welcome message" - ); - this.featureSwitchIframe = featSw( - "fs-iframe", - () => false, - "Disables/Enables the iframe-popup" - ); - this.featureSwitchMoreQuests = featSw( - "fs-more-quests", - (layoutToUse) => layoutToUse?.enableMoreQuests ?? true, - "Disables/Enables the 'More Quests'-tab in the welcome message" - ); - this.featureSwitchShareScreen = featSw( - "fs-share-screen", - (layoutToUse) => layoutToUse?.enableShareScreen ?? true, - "Disables/Enables the 'Share-screen'-tab in the welcome message" - ); - this.featureSwitchGeolocation = featSw( - "fs-geolocation", - (layoutToUse) => layoutToUse?.enableGeolocation ?? true, - "Disables/Enables the geolocation button" - ); - this.featureSwitchShowAllQuestions = featSw( - "fs-all-questions", - (layoutToUse) => layoutToUse?.enableShowAllQuestions ?? false, - "Always show all questions" - ); - - this.featureSwitchEnableExport = featSw( - "fs-export", - (layoutToUse) => layoutToUse?.enableExportButton ?? false, - "Enable the export as GeoJSON and CSV button" - ); - this.featureSwitchExportAsPdf = featSw( - "fs-pdf", - (layoutToUse) => layoutToUse?.enablePdfDownload ?? false, - "Enable the PDF download button" - ); - - - this.featureSwitchIsTesting = QueryParameters.GetQueryParameter( - "test", - "false", - "If true, 'dryrun' mode is activated. The app will behave as normal, except that changes to OSM will be printed onto the console instead of actually uploaded to osm.org" - ).map( - (str) => str === "true", - [], - (b) => "" + b - ); - - this.featureSwitchIsDebugging = QueryParameters.GetQueryParameter( - "debug", - "false", - "If true, shows some extra debugging help such as all the available tags on every object" - ).map( - (str) => str === "true", - [], - (b) => "" + b - ); - - this.featureSwitchFakeUser = QueryParameters.GetQueryParameter("fake-user", "false", - "If true, 'dryrun' mode is activated and a fake user account is loaded") - .map(str => str === "true", [], b => "" + b); - - - this.featureSwitchApiURL = QueryParameters.GetQueryParameter( - "backend", - "osm", - "The OSM backend to use - can be used to redirect mapcomplete to the testing backend when using 'osm-test'" - ); - - this.overpassUrl = QueryParameters.GetQueryParameter("overpassUrl", - (layoutToUse?.overpassUrl ?? Constants.defaultOverpassUrls).join(",") , - "Point mapcomplete to a different overpass-instance. Example: https://overpass-api.de/api/interpreter" - ).map(param => param.split(","), [], urls => urls.join(",")) - - this.overpassTimeout = QueryParameters.GetQueryParameter("overpassTimeout", - "" + layoutToUse?.overpassTimeout, - "Set a different timeout (in seconds) for queries in overpass") - .map(str => Number(str), [], n => "" + n) - - this.featureSwitchUserbadge.addCallbackAndRun(userbadge => { - if (!userbadge) { - this.featureSwitchAddNew.setData(false) - } - }) - } - { - // Some other feature switches - const customCssQP = QueryParameters.GetQueryParameter( - "custom-css", - "", - "If specified, the custom css from the given link will be loaded additionaly" - ); - if (customCssQP.data !== undefined && customCssQP.data !== "") { - Utils.LoadCustomCss(customCssQP.data); - } - - this.backgroundLayerId = QueryParameters.GetQueryParameter( - "background", - layoutToUse?.defaultBackgroundId ?? "osm", - "The id of the background layer to start with" - ); - } - - if (Utils.runningFromConsole) { - return; - } - - this.osmConnection = new OsmConnection({ - changes: this.changes, - dryRun: this.featureSwitchIsTesting.data, - fakeUser: this.featureSwitchFakeUser.data, - allElements: this.allElements, - oauth_token: QueryParameters.GetQueryParameter( - "oauth_token", - undefined, - "Used to complete the login" - ), - layoutName: layoutToUse?.id, - osmConfiguration: <'osm' | 'osm-test'>this.featureSwitchApiURL.data - }) - - - new ChangeToElementsActor(this.changes, this.allElements) - - new PendingChangesUploader(this.changes, this.selectedElement); - - new SelectedElementTagsUpdater(this) - - this.mangroveIdentity = new MangroveIdentity( - this.osmConnection.GetLongPreference("identity", "mangrove") - ); - - this.installedThemes = new InstalledThemes( - this.osmConnection - ).installedThemes; - - // Important: the favourite layers are initialized _after_ the installed themes, as these might contain an installedTheme - this.favouriteLayers = LocalStorageSource.Get("favouriteLayers") - .syncWith(this.osmConnection.GetLongPreference("favouriteLayers")) - .map( - (str) => Utils.Dedup(str?.split(";")) ?? [], - [], - (layers) => Utils.Dedup(layers)?.join(";") - ); - - Locale.language.syncWith(this.osmConnection.GetPreference("language")); - - Locale.language - .addCallback((currentLanguage) => { - const layoutToUse = self.layoutToUse; - if (layoutToUse === undefined) { - return; - } - if (this.layoutToUse.language.indexOf(currentLanguage) < 0) { - console.log( - "Resetting language to", - layoutToUse.language[0], - "as", - currentLanguage, - " is unsupported" - ); - // The current language is not supported -> switch to a supported one - Locale.language.setData(layoutToUse.language[0]); - } - }) - .ping(); - - new TitleHandler(this); - - this.overlayToggles = this.layoutToUse.tileLayerSources.filter(c => c.name !== undefined).map(c => ({ - config: c, - isDisplayed: QueryParameters.GetQueryParameter("overlay-"+c.id, ""+c.defaultState,"Wether or not the overlay "+c.id+" is shown").map(str => str === "true", [], b => ""+b) - })) - } - - private static asFloat(source: UIEventSource): UIEventSource { - return source.map( - (str) => { - let parsed = parseFloat(str); - return isNaN(parsed) ? undefined : parsed; - }, - [], - (fl) => { - if (fl === undefined || isNaN(fl)) { - return undefined; - } - return ("" + fl).substr(0, 8); - } - ); - } } diff --git a/UI/AllThemesGui.ts b/UI/AllThemesGui.ts new file mode 100644 index 0000000000..0419b14890 --- /dev/null +++ b/UI/AllThemesGui.ts @@ -0,0 +1,20 @@ +import {FixedUiElement} from "./Base/FixedUiElement"; +import State from "../State"; +import Combine from "./Base/Combine"; +import MoreScreen from "./BigComponents/MoreScreen"; +import Translations from "./i18n/Translations"; +import Constants from "../Models/Constants"; +import UserRelatedState from "../Logic/State/UserRelatedState"; + +export default class AllThemesGui { + constructor() { + new FixedUiElement("").AttachTo("centermessage") + const state = new UserRelatedState(undefined); + new Combine([new MoreScreen(state, true), + Translations.t.general.aboutMapcomplete.SetClass("link-underline"), + new FixedUiElement("v" + Constants.vNumber) + ]).SetClass("block m-5 lg:w-3/4 lg:ml-40") + .SetStyle("pointer-events: all;") + .AttachTo("topleft-tools"); + } +} \ No newline at end of file diff --git a/UI/BigComponents/FullWelcomePaneWithTabs.ts b/UI/BigComponents/FullWelcomePaneWithTabs.ts index 3978bd3102..b4b3c3f695 100644 --- a/UI/BigComponents/FullWelcomePaneWithTabs.ts +++ b/UI/BigComponents/FullWelcomePaneWithTabs.ts @@ -1,4 +1,3 @@ -import State from "../../State"; import ThemeIntroductionPanel from "./ThemeIntroductionPanel"; import Svg from "../../Svg"; import Translations from "../i18n/Translations"; @@ -8,31 +7,46 @@ import Constants from "../../Models/Constants"; import Combine from "../Base/Combine"; import {TabbedComponent} from "../Base/TabbedComponent"; import {UIEventSource} from "../../Logic/UIEventSource"; -import UserDetails from "../../Logic/Osm/OsmConnection"; +import UserDetails, {OsmConnection} from "../../Logic/Osm/OsmConnection"; import ScrollableFullScreen from "../Base/ScrollableFullScreen"; import BaseUIElement from "../BaseUIElement"; import Toggle from "../Input/Toggle"; import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; import {Utils} from "../../Utils"; +import UserRelatedState from "../../Logic/State/UserRelatedState"; export default class FullWelcomePaneWithTabs extends ScrollableFullScreen { - constructor(isShown: UIEventSource) { - const layoutToUse = State.state.layoutToUse; + constructor(isShown: UIEventSource, + currentTab: UIEventSource, + state: { + layoutToUse: LayoutConfig, + osmConnection: OsmConnection, + featureSwitchShareScreen: UIEventSource, + featureSwitchMoreQuests: UIEventSource + } & UserRelatedState) { + const layoutToUse = state.layoutToUse; super( () => layoutToUse.title.Clone(), - () => FullWelcomePaneWithTabs.GenerateContents(layoutToUse, State.state.osmConnection.userDetails, isShown), - "welcome", isShown + () => FullWelcomePaneWithTabs.GenerateContents(state, currentTab, isShown), + undefined, isShown ) } - private static ConstructBaseTabs(layoutToUse: LayoutConfig, isShown: UIEventSource): { header: string | BaseUIElement; content: BaseUIElement }[] { + private static ConstructBaseTabs(state: { + layoutToUse: LayoutConfig, + osmConnection: OsmConnection, + featureSwitchShareScreen: UIEventSource, + featureSwitchMoreQuests: UIEventSource + } & UserRelatedState, + isShown: UIEventSource): + { header: string | BaseUIElement; content: BaseUIElement }[] { let welcome: BaseUIElement = new ThemeIntroductionPanel(isShown); - + const tabs: { header: string | BaseUIElement, content: BaseUIElement }[] = [ - {header: ``, content: welcome}, + {header: ``, content: welcome}, { header: Svg.osm_logo_img, content: Translations.t.general.openStreetMapIntro.Clone().SetClass("link-underline") @@ -40,31 +54,36 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen { ] - if (State.state.featureSwitchShareScreen.data) { + if (state.featureSwitchShareScreen.data) { tabs.push({header: Svg.share_img, content: new ShareScreen()}); } - if (State.state.featureSwitchMoreQuests.data) { + if (state.featureSwitchMoreQuests.data) { tabs.push({ header: Svg.add_img, - content: new MoreScreen() + content: new MoreScreen(state) }); } return tabs; } - private static GenerateContents(layoutToUse: LayoutConfig, userDetails: UIEventSource, isShown: UIEventSource) { + private static GenerateContents(state: { + layoutToUse: LayoutConfig, + osmConnection: OsmConnection, + featureSwitchShareScreen: UIEventSource, + featureSwitchMoreQuests: UIEventSource + } & UserRelatedState, currentTab: UIEventSource, isShown: UIEventSource) { - const tabs = FullWelcomePaneWithTabs.ConstructBaseTabs(layoutToUse, isShown) - const tabsWithAboutMc = [...FullWelcomePaneWithTabs.ConstructBaseTabs(layoutToUse, isShown)] + const tabs = FullWelcomePaneWithTabs.ConstructBaseTabs(state, isShown) + const tabsWithAboutMc = [...FullWelcomePaneWithTabs.ConstructBaseTabs(state, isShown)] const now = new Date() const lastWeek = new Date(now.getDate() - 7 * 24 * 60 * 60 * 1000) - const date = lastWeek.getFullYear()+"-"+Utils.TwoDigits(lastWeek.getMonth()+1)+"-"+Utils.TwoDigits(lastWeek.getDate()) + const date = lastWeek.getFullYear() + "-" + Utils.TwoDigits(lastWeek.getMonth() + 1) + "-" + Utils.TwoDigits(lastWeek.getDate()) const osmcha_link = `https://osmcha.org/?filters=%7B%22date__gte%22%3A%5B%7B%22label%22%3A%22${date}%22%2C%22value%22%3A%222021-01-01%22%7D%5D%2C%22editor%22%3A%5B%7B%22label%22%3A%22mapcomplete%22%2C%22value%22%3A%22mapcomplete%22%7D%5D%7D` - + tabsWithAboutMc.push({ header: Svg.help, content: new Combine([Translations.t.general.aboutMapcomplete.Clone() @@ -75,11 +94,11 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen { tabs.forEach(c => c.content.SetClass("p-4")) tabsWithAboutMc.forEach(c => c.content.SetClass("p-4")) - + return new Toggle( - new TabbedComponent(tabsWithAboutMc, State.state.welcomeMessageOpenedTab), - new TabbedComponent(tabs, State.state.welcomeMessageOpenedTab), - userDetails.map((userdetails: UserDetails) => + new TabbedComponent(tabsWithAboutMc, currentTab), + new TabbedComponent(tabs, currentTab), + state.osmConnection.userDetails.map((userdetails: UserDetails) => userdetails.loggedIn && userdetails.csCount >= Constants.userJourney.mapCompleteHelpUnlock) ) diff --git a/UI/BigComponents/LeftControls.ts b/UI/BigComponents/LeftControls.ts index fef132b4e7..b92adb54ab 100644 --- a/UI/BigComponents/LeftControls.ts +++ b/UI/BigComponents/LeftControls.ts @@ -2,7 +2,6 @@ import Combine from "../Base/Combine"; import ScrollableFullScreen from "../Base/ScrollableFullScreen"; import Translations from "../i18n/Translations"; import AttributionPanel from "./AttributionPanel"; -import State from "../../State"; import ContributorCount from "../../Logic/ContributorCount"; import Toggle from "../Input/Toggle"; import MapControlButton from "../MapControlButton"; @@ -13,16 +12,33 @@ import {UIEventSource} from "../../Logic/UIEventSource"; import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline"; import Loc from "../../Models/Loc"; import {BBox} from "../../Logic/BBox"; +import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; +import FilteredLayer from "../../Models/FilteredLayer"; export default class LeftControls extends Combine { - constructor(state: {featurePipeline: FeaturePipeline, currentBounds: UIEventSource, locationControl: UIEventSource, overlayToggles: any}) { + constructor(state: { + layoutToUse: LayoutConfig, + featurePipeline: FeaturePipeline, + currentBounds: UIEventSource, + locationControl: UIEventSource, + overlayToggles: any, + featureSwitchEnableExport: UIEventSource, + featureSwitchExportAsPdf: UIEventSource, + filteredLayers: UIEventSource, + featureSwitchFilter: UIEventSource, + selectedElement: UIEventSource + }, + guiState: { + downloadControlIsOpened: UIEventSource, + filterViewIsOpened: UIEventSource, + }) { const toggledCopyright = new ScrollableFullScreen( () => Translations.t.general.attribution.attributionTitle.Clone(), () => new AttributionPanel( - State.state.layoutToUse, + state.layoutToUse, new ContributorCount(state).Contributors ), undefined @@ -38,50 +54,50 @@ export default class LeftControls extends Combine { const toggledDownload = new Toggle( new AllDownloads( - State.state.downloadControlIsOpened + guiState.downloadControlIsOpened ).SetClass("block p-1 rounded-full"), new MapControlButton(Svg.download_svg()) - .onClick(() => State.state.downloadControlIsOpened.setData(true)), - State.state.downloadControlIsOpened + .onClick(() => guiState.downloadControlIsOpened.setData(true)), + guiState.downloadControlIsOpened ) const downloadButtonn = new Toggle( toggledDownload, undefined, - State.state.featureSwitchEnableExport.map(downloadEnabled => downloadEnabled || State.state.featureSwitchExportAsPdf.data, - [State.state.featureSwitchExportAsPdf]) + state.featureSwitchEnableExport.map(downloadEnabled => downloadEnabled || state.featureSwitchExportAsPdf.data, + [state.featureSwitchExportAsPdf]) ); const toggledFilter = new Toggle( new ScrollableFullScreen( () => Translations.t.general.layerSelection.title.Clone(), () => - new FilterView(State.state.filteredLayers, state.overlayToggles).SetClass( + new FilterView(state.filteredLayers, state.overlayToggles).SetClass( "block p-1 rounded-full" ), undefined, - State.state.filterIsOpened + guiState.filterViewIsOpened ), new MapControlButton(Svg.filter_svg()) - .onClick(() => State.state.filterIsOpened.setData(true)), - State.state.filterIsOpened + .onClick(() => guiState.filterViewIsOpened.setData(true)), + guiState.filterViewIsOpened ) const filterButton = new Toggle( toggledFilter, undefined, - State.state.featureSwitchFilter + state.featureSwitchFilter ); - State.state.locationControl.addCallback(() => { + state.locationControl.addCallback(() => { // Close the layer selection when the map is moved toggledDownload.isEnabled.setData(false); copyrightButton.isEnabled.setData(false); toggledFilter.isEnabled.setData(false); }); - State.state.selectedElement.addCallbackAndRunD((_) => { + state.selectedElement.addCallbackAndRunD((_) => { toggledDownload.isEnabled.setData(false); copyrightButton.isEnabled.setData(false); toggledFilter.isEnabled.setData(false); diff --git a/UI/BigComponents/MoreScreen.ts b/UI/BigComponents/MoreScreen.ts index 01e4b9f95b..719df82c98 100644 --- a/UI/BigComponents/MoreScreen.ts +++ b/UI/BigComponents/MoreScreen.ts @@ -1,7 +1,6 @@ import {VariableUiElement} from "../Base/VariableUIElement"; import {AllKnownLayouts} from "../../Customizations/AllKnownLayouts"; import Svg from "../../Svg"; -import State from "../../State"; import Combine from "../Base/Combine"; import {SubtleButton} from "../Base/SubtleButton"; import Translations from "../i18n/Translations"; @@ -11,15 +10,21 @@ import LanguagePicker from "../LanguagePicker"; import IndexText from "./IndexText"; import BaseUIElement from "../BaseUIElement"; import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; +import {UIEventSource} from "../../Logic/UIEventSource"; +import Loc from "../../Models/Loc"; +import {OsmConnection} from "../../Logic/Osm/OsmConnection"; +import UserRelatedState from "../../Logic/State/UserRelatedState"; +import Toggle from "../Input/Toggle"; +import {Utils} from "../../Utils"; +import Title from "../Base/Title"; export default class MoreScreen extends Combine { - constructor(onMainScreen: boolean = false) { - super(MoreScreen.Init(onMainScreen, State.state)); - } - - private static Init(onMainScreen: boolean, state: State): BaseUIElement [] { + constructor(state: UserRelatedState & { + locationControl?: UIEventSource, + layoutToUse?: LayoutConfig + }, onMainScreen: boolean = false) { const tr = Translations.t.general.morescreen; let intro: BaseUIElement = tr.intro.Clone(); let themeButtonStyle = "" @@ -35,30 +40,59 @@ export default class MoreScreen extends Combine { themeListStyle = "md:grid md:grid-flow-row md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-g4 gap-4" } - return [ + super([ intro, MoreScreen.createOfficialThemesList(state, themeButtonStyle).SetClass(themeListStyle), - MoreScreen.createUnofficialThemeList(themeButtonStyle)?.SetClass(themeListStyle), + MoreScreen.createPreviouslyVistedHiddenList(state, themeButtonStyle).SetClass(themeListStyle), + MoreScreen.createUnofficialThemeList(themeButtonStyle, state)?.SetClass(themeListStyle), tr.streetcomplete.Clone().SetClass("block text-base mx-10 my-3 mb-10") - ]; + ]); } - private static createUnofficialThemeList(buttonClass: string): BaseUIElement { - return new VariableUiElement(State.state.installedThemes.map(customThemes => { + + private static createUnofficialThemeList(buttonClass: string, state: UserRelatedState): BaseUIElement { + return new VariableUiElement(state.installedThemes.map(customThemes => { const els: BaseUIElement[] = [] if (customThemes.length > 0) { els.push(Translations.t.general.customThemeIntro.Clone()) const customThemesElement = new Combine( - customThemes.map(theme => MoreScreen.createLinkButton(theme.layout, theme.definition)?.SetClass(buttonClass)) + customThemes.map(theme => MoreScreen.createLinkButton(state, theme.layout, theme.definition)?.SetClass(buttonClass)) ) els.push(customThemesElement) } return els; })); } + + private static createPreviouslyVistedHiddenList(state: UserRelatedState, buttonClass: string){ + const t= Translations.t.general.morescreen + return new Toggle( + new Combine([ + new Title(t.previouslyHiddenTitle.Clone()), + t.hiddenExplanation, + + + new VariableUiElement( + state.osmConnection.preferencesHandler.preferences.map(allPreferences => { + const knownThemes = Utils.NoNull( Object.keys(allPreferences).filter(key => key.startsWith("hidden-theme-")) + .map(key => key.substr("hidden-theme-".length, key.length - "-enabled".length)) + .map(theme => AllKnownLayouts.allKnownLayouts.get(theme) )) + return new Combine(knownThemes.map(layout => + MoreScreen.createLinkButton(state, layout ).SetClass(buttonClass) + )) + + }) + + )]).SetClass("flex flex-col"), + undefined, + state.osmConnection.isLoggedIn + ) + - private static createOfficialThemesList(state: State, buttonClass: string): BaseUIElement { + } + + private static createOfficialThemesList(state: { osmConnection: OsmConnection, locationControl?: UIEventSource }, buttonClass: string): BaseUIElement { let officialThemes = AllKnownLayouts.layoutsList let buttons = officialThemes.map((layout) => { @@ -66,10 +100,10 @@ export default class MoreScreen extends Combine { console.trace("Layout is undefined") return undefined } - const button = MoreScreen.createLinkButton(layout)?.SetClass(buttonClass); + const button = MoreScreen.createLinkButton(state, layout)?.SetClass(buttonClass); if (layout.id === personal.id) { return new VariableUiElement( - State.state.osmConnection.userDetails.map(userdetails => userdetails.csCount) + state.osmConnection.userDetails.map(userdetails => userdetails.csCount) .map(csCount => { if (csCount < Constants.userJourney.personalLayoutUnlock) { return undefined @@ -91,7 +125,7 @@ export default class MoreScreen extends Combine { /* * Returns either a link to the issue tracker or a link to the custom generator, depending on the achieved number of changesets * */ - private static createCustomGeneratorButton(state: State): VariableUiElement { + private static createCustomGeneratorButton(state: { osmConnection: OsmConnection }): VariableUiElement { const tr = Translations.t.general.morescreen; return new VariableUiElement( state.osmConnection.userDetails.map(userDetails => { @@ -111,13 +145,22 @@ export default class MoreScreen extends Combine { /** * Creates a button linking to the given theme - * @param layout - * @param customThemeDefinition * @private */ - private static createLinkButton(layout: LayoutConfig, customThemeDefinition: string = undefined): BaseUIElement { - if (layout === undefined) { - return undefined; + private static createLinkButton( + state: { + locationControl?: UIEventSource, + layoutToUse?: LayoutConfig + }, layout: LayoutConfig, customThemeDefinition: string = undefined + ): + BaseUIElement { + if (layout + + === + undefined + ) { + return + undefined; } if (layout.id === undefined) { console.error("ID is undefined for layout", layout); @@ -126,11 +169,11 @@ export default class MoreScreen extends Combine { if (layout.hideFromOverview) { return undefined; } - if (layout.id === State.state.layoutToUse?.id) { + if (layout.id === state?.layoutToUse?.id) { return undefined; } - const currentLocation = State.state.locationControl; + const currentLocation = state?.locationControl; let path = window.location.pathname; // Path starts with a '/' and contains everything, e.g. '/dir/dir/page.html' @@ -151,7 +194,7 @@ export default class MoreScreen extends Combine { linkSuffix = `#${customThemeDefinition}` } - const linkText = currentLocation.map(currentLocation => { + const linkText = currentLocation?.map(currentLocation => { const params = [ ["z", currentLocation?.zoom], ["lat", currentLocation?.lat], @@ -160,7 +203,7 @@ export default class MoreScreen extends Combine { .map(part => part[0] + "=" + part[1]) .join("&") return `${linkPrefix}${params}${linkSuffix}`; - }) + }) ?? new UIEventSource(`${linkPrefix}${linkSuffix}`) let description = Translations.WT(layout.shortDescription).Clone(); diff --git a/UI/BigComponents/RightControls.ts b/UI/BigComponents/RightControls.ts index b98748017a..a8853a54fa 100644 --- a/UI/BigComponents/RightControls.ts +++ b/UI/BigComponents/RightControls.ts @@ -2,38 +2,38 @@ import Combine from "../Base/Combine"; import Toggle from "../Input/Toggle"; import MapControlButton from "../MapControlButton"; import GeoLocationHandler from "../../Logic/Actors/GeoLocationHandler"; -import State from "../../State"; import Svg from "../../Svg"; +import MapState from "../../Logic/State/MapState"; export default class RightControls extends Combine { - constructor() { + constructor(state:MapState) { const geolocationButton = new Toggle( new MapControlButton( new GeoLocationHandler( - State.state.currentGPSLocation, - State.state.leafletMap, - State.state.layoutToUse + state.currentGPSLocation, + state.leafletMap, + state.layoutToUse ), { dontStyle: true } ), undefined, - State.state.featureSwitchGeolocation + state.featureSwitchGeolocation ); const plus = new MapControlButton( Svg.plus_svg() ).onClick(() => { - State.state.locationControl.data.zoom++; - State.state.locationControl.ping(); + state.locationControl.data.zoom++; + state.locationControl.ping(); }); const min = new MapControlButton( Svg.min_svg() ).onClick(() => { - State.state.locationControl.data.zoom--; - State.state.locationControl.ping(); + state.locationControl.data.zoom--; + state.locationControl.ping(); }); super([plus, min, geolocationButton].map(el => el.SetClass("m-0.5 md:m-1"))) diff --git a/UI/BigComponents/SearchAndGo.ts b/UI/BigComponents/SearchAndGo.ts index c4e702a00b..a0cb766bd2 100644 --- a/UI/BigComponents/SearchAndGo.ts +++ b/UI/BigComponents/SearchAndGo.ts @@ -2,7 +2,6 @@ import {UIEventSource} from "../../Logic/UIEventSource"; import {Translation} from "../i18n/Translation"; import {VariableUiElement} from "../Base/VariableUIElement"; import Svg from "../../Svg"; -import State from "../../State"; import {TextField} from "../Input/TextField"; import {Geocoding} from "../../Logic/Osm/Geocoding"; import Translations from "../i18n/Translations"; @@ -10,7 +9,10 @@ import Hash from "../../Logic/Web/Hash"; import Combine from "../Base/Combine"; export default class SearchAndGo extends Combine { - constructor() { + constructor(state: { + leafletMap: UIEventSource, + selectedElement: UIEventSource + }) { const goButton = Svg.search_ui().SetClass( "w-8 h-8 full-rounded border-black float-right" ); @@ -64,9 +66,9 @@ export default class SearchAndGo extends Combine { [bb[0], bb[2]], [bb[1], bb[3]], ]; - State.state.selectedElement.setData(undefined); + state.selectedElement.setData(undefined); Hash.hash.setData(poi.osm_type + "/" + poi.osm_id); - State.state.leafletMap.data.fitBounds(bounds); + state.leafletMap.data.fitBounds(bounds); placeholder.setData(Translations.t.general.search.search); }, () => { diff --git a/UI/BigComponents/SimpleAddUI.ts b/UI/BigComponents/SimpleAddUI.ts index 0a6b97429e..204e7f955f 100644 --- a/UI/BigComponents/SimpleAddUI.ts +++ b/UI/BigComponents/SimpleAddUI.ts @@ -4,7 +4,6 @@ import {UIEventSource} from "../../Logic/UIEventSource"; import Svg from "../../Svg"; import {SubtleButton} from "../Base/SubtleButton"; -import State from "../../State"; import Combine from "../Base/Combine"; import Translations from "../i18n/Translations"; import Constants from "../../Models/Constants"; @@ -12,7 +11,7 @@ import {TagUtils} from "../../Logic/Tags/TagUtils"; import BaseUIElement from "../BaseUIElement"; import {VariableUiElement} from "../Base/VariableUIElement"; import Toggle from "../Input/Toggle"; -import UserDetails from "../../Logic/Osm/OsmConnection"; +import UserDetails, {OsmConnection} from "../../Logic/Osm/OsmConnection"; import LocationInput from "../Input/LocationInput"; import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers"; import CreateNewNodeAction from "../../Logic/Osm/Actions/CreateNewNodeAction"; @@ -20,6 +19,11 @@ import {OsmObject, OsmWay} from "../../Logic/Osm/OsmObject"; import PresetConfig from "../../Models/ThemeConfig/PresetConfig"; import FilteredLayer from "../../Models/FilteredLayer"; import {BBox} from "../../Logic/BBox"; +import Loc from "../../Models/Loc"; +import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; +import {Changes} from "../../Logic/Osm/Changes"; +import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline"; +import {ElementStorage} from "../../Logic/ElementStorage"; /* * The SimpleAddUI is a single panel, which can have multiple states: @@ -38,9 +42,22 @@ interface PresetInfo extends PresetConfig { export default class SimpleAddUI extends Toggle { - constructor(isShown: UIEventSource) { + constructor(isShown: UIEventSource, + filterViewIsOpened: UIEventSource, + state: { + layoutToUse: LayoutConfig, + osmConnection: OsmConnection, + changes: Changes, + allElements: ElementStorage, + LastClickLocation: UIEventSource<{ lat: number, lon: number }>, + featurePipeline: FeaturePipeline, + selectedElement: UIEventSource, + locationControl: UIEventSource, + filteredLayers: UIEventSource, + featureSwitchFilter: UIEventSource, + }) { const loginButton = new SubtleButton(Svg.osm_logo_ui(), Translations.t.general.add.pleaseLogin.Clone()) - .onClick(() => State.state.osmConnection.AttemptLogin()); + .onClick(() => state.osmConnection.AttemptLogin()); const readYourMessages = new Combine([ Translations.t.general.readYourMessages.Clone().SetClass("alert"), new SubtleButton(Svg.envelope_ui(), @@ -50,20 +67,21 @@ export default class SimpleAddUI extends Toggle { const selectedPreset = new UIEventSource(undefined); isShown.addCallback(_ => selectedPreset.setData(undefined)) // Clear preset selection when the UI is closed/opened - State.state.LastClickLocation.addCallback( _ => selectedPreset.setData(undefined)) - - const presetsOverview = SimpleAddUI.CreateAllPresetsPanel(selectedPreset) + state.LastClickLocation.addCallback(_ => selectedPreset.setData(undefined)) + + const presetsOverview = SimpleAddUI.CreateAllPresetsPanel(selectedPreset, state) - async function createNewPoint(tags: any[], location: { lat: number, lon: number }, snapOntoWay?: OsmWay) { + async function createNewPoint(tags: any[], location: { lat: number, lon: number }, snapOntoWay?: OsmWay) { const newElementAction = new CreateNewNodeAction(tags, location.lat, location.lon, { - theme: State.state?.layoutToUse?.id ?? "unkown", + theme: state.layoutToUse?.id ?? "unkown", changeType: "create", - snapOnto: snapOntoWay}) - await State.state.changes.applyAction(newElementAction) + snapOnto: snapOntoWay + }) + await state.changes.applyAction(newElementAction) selectedPreset.setData(undefined) isShown.setData(false) - State.state.selectedElement.setData(State.state.allElements.ContainingFeatures.get( + state.selectedElement.setData(state.allElements.ContainingFeatures.get( newElementAction.newElementId )) } @@ -73,7 +91,7 @@ export default class SimpleAddUI extends Toggle { if (preset === undefined) { return presetsOverview } - return SimpleAddUI.CreateConfirmButton(preset, + return SimpleAddUI.CreateConfirmButton(state, filterViewIsOpened, preset, (tags, location, snapOntoWayId?: string) => { if (snapOntoWayId === undefined) { createNewPoint(tags, location, undefined) @@ -97,18 +115,18 @@ export default class SimpleAddUI extends Toggle { new Toggle( addUi, Translations.t.general.add.stillLoading.Clone().SetClass("alert"), - State.state.featurePipeline.somethingLoaded + state.featurePipeline.somethingLoaded ), Translations.t.general.add.zoomInFurther.Clone().SetClass("alert"), - State.state.locationControl.map(loc => loc.zoom >= Constants.userJourney.minZoomLevelToAddNewPoints) + state.locationControl.map(loc => loc.zoom >= Constants.userJourney.minZoomLevelToAddNewPoints) ), readYourMessages, - State.state.osmConnection.userDetails.map((userdetails: UserDetails) => + state.osmConnection.userDetails.map((userdetails: UserDetails) => userdetails.csCount >= Constants.userJourney.addNewPointWithUnreadMessagesUnlock || userdetails.unreadMessages == 0) ), loginButton, - State.state.osmConnection.isLoggedIn + state.osmConnection.isLoggedIn ) @@ -116,11 +134,18 @@ export default class SimpleAddUI extends Toggle { } - private static CreateConfirmButton(preset: PresetInfo, + private static CreateConfirmButton( + state: { + LastClickLocation: UIEventSource<{ lat: number, lon: number }>, + osmConnection: OsmConnection, + featurePipeline: FeaturePipeline + }, + filterViewIsOpened: UIEventSource, + preset: PresetInfo, confirm: (tags: any[], location: { lat: number, lon: number }, snapOntoWayId: string) => void, cancel: () => void): BaseUIElement { - let location = State.state.LastClickLocation; + let location = state.LastClickLocation; let preciseInput: LocationInput = undefined if (preset.preciseInput !== undefined) { // We uncouple the event source @@ -143,7 +168,6 @@ export default class SimpleAddUI extends Toggle { } - const tags = TagUtils.KVtoProperties(preset.tags ?? []); preciseInput = new LocationInput({ mapBackground: backgroundLayer, @@ -160,24 +184,24 @@ export default class SimpleAddUI extends Toggle { if (preset.preciseInput.snapToLayers) { // We have to snap to certain layers. // Lets fetch them - - let loadedBbox : BBox= undefined + + let loadedBbox: BBox = undefined mapBounds?.addCallbackAndRunD(bbox => { - if(loadedBbox !== undefined && bbox.isContainedIn(loadedBbox)){ + if (loadedBbox !== undefined && bbox.isContainedIn(loadedBbox)) { // All is already there // return; } bbox = bbox.pad(2); loadedBbox = bbox; - const allFeatures: {feature: any}[] = [] + const allFeatures: { feature: any }[] = [] preset.preciseInput.snapToLayers.forEach(layerId => { - State.state.featurePipeline.GetFeaturesWithin(layerId, bbox).forEach(feats => allFeatures.push(...feats.map(f => ({feature :f})))) + state.featurePipeline.GetFeaturesWithin(layerId, bbox).forEach(feats => allFeatures.push(...feats.map(f => ({feature: f})))) }) snapToFeatures.setData(allFeatures) }) } - + } @@ -205,7 +229,7 @@ export default class SimpleAddUI extends Toggle { Translations.t.general.add.openLayerControl ]) ) - .onClick(() => State.state.filterIsOpened.setData(true)) + .onClick(() => filterViewIsOpened.setData(true)) const openLayerOrConfirm = new Toggle( @@ -234,36 +258,35 @@ export default class SimpleAddUI extends Toggle { openLayerOrConfirm, disableFilter, preset.layerToAddTo.appliedFilters.map(filters => { - if(filters === undefined || filters.length === 0){ + if (filters === undefined || filters.length === 0) { return true; } for (const filter of filters) { - if(filter.selected === 0 && filter.filter.options.length === 1){ + if (filter.selected === 0 && filter.filter.options.length === 1) { return false; } - if(filter.selected !== undefined){ + if (filter.selected !== undefined) { const tags = filter.filter.options[filter.selected].osmTags - if(tags !== undefined && tags["and"]?.length !== 0){ + if (tags !== undefined && tags["and"]?.length !== 0) { // This actually doesn't filter anything at all return false; } } } return true - + }) ) - const tagInfo = SimpleAddUI.CreateTagInfoFor(preset); + const tagInfo = SimpleAddUI.CreateTagInfoFor(preset, state.osmConnection); const cancelButton = new SubtleButton(Svg.close_ui(), Translations.t.general.cancel ).onClick(cancel) return new Combine([ - // Translations.t.general.add.confirmIntro.Subs({title: preset.name}), - State.state.osmConnection.userDetails.data.dryRun ? + state.osmConnection.userDetails.data.dryRun ? Translations.t.general.testing.Clone().SetClass("alert") : undefined, disableFiltersOrConfirm, cancelButton, @@ -274,24 +297,29 @@ export default class SimpleAddUI extends Toggle { } - private static CreateTagInfoFor(preset: PresetInfo, optionallyLinkToWiki = true) { - const csCount = State.state.osmConnection.userDetails.data.csCount; + private static CreateTagInfoFor(preset: PresetInfo, osmConnection: OsmConnection, optionallyLinkToWiki = true) { + const csCount = osmConnection.userDetails.data.csCount; return new Toggle( Translations.t.general.add.presetInfo.Subs({ tags: preset.tags.map(t => t.asHumanString(optionallyLinkToWiki && csCount > Constants.userJourney.tagsVisibleAndWikiLinked, true)).join("&"), }).SetStyle("word-break: break-all"), undefined, - State.state.osmConnection.userDetails.map(userdetails => userdetails.csCount >= Constants.userJourney.tagsVisibleAt) + osmConnection.userDetails.map(userdetails => userdetails.csCount >= Constants.userJourney.tagsVisibleAt) ); } - private static CreateAllPresetsPanel(selectedPreset: UIEventSource): BaseUIElement { - const presetButtons = SimpleAddUI.CreatePresetButtons(selectedPreset) + private static CreateAllPresetsPanel(selectedPreset: UIEventSource, + state: { + filteredLayers: UIEventSource, + featureSwitchFilter: UIEventSource, + osmConnection: OsmConnection + }): BaseUIElement { + const presetButtons = SimpleAddUI.CreatePresetButtons(state, selectedPreset) let intro: BaseUIElement = Translations.t.general.add.intro; let testMode: BaseUIElement = undefined; - if (State.state.osmConnection?.userDetails?.data?.dryRun) { + if (state.osmConnection?.userDetails?.data?.dryRun) { testMode = Translations.t.general.testing.Clone().SetClass("alert") } @@ -299,9 +327,9 @@ export default class SimpleAddUI extends Toggle { } - private static CreatePresetSelectButton(preset: PresetInfo) { + private static CreatePresetSelectButton(preset: PresetInfo, osmConnection: OsmConnection) { - const tagInfo = SimpleAddUI.CreateTagInfoFor(preset, false); + const tagInfo = SimpleAddUI.CreateTagInfoFor(preset, osmConnection ,false); return new SubtleButton( preset.icon(), new Combine([ @@ -316,11 +344,17 @@ export default class SimpleAddUI extends Toggle { /* * Generates the list with all the buttons.*/ - private static CreatePresetButtons(selectedPreset: UIEventSource): BaseUIElement { + private static CreatePresetButtons( + state: { + filteredLayers: UIEventSource, + featureSwitchFilter: UIEventSource, + osmConnection: OsmConnection + }, + selectedPreset: UIEventSource): BaseUIElement { const allButtons = []; - for (const layer of State.state.filteredLayers.data) { + for (const layer of state.filteredLayers.data) { - if (layer.isDisplayed.data === false && !State.state.featureSwitchFilter.data) { + if (layer.isDisplayed.data === false && !state.featureSwitchFilter.data) { // The layer is not displayed and we cannot enable the layer control -> we skip continue; } @@ -346,7 +380,7 @@ export default class SimpleAddUI extends Toggle { preciseInput: preset.preciseInput } - const button = SimpleAddUI.CreatePresetSelectButton(presetInfo); + const button = SimpleAddUI.CreatePresetSelectButton(presetInfo, state.osmConnection); button.onClick(() => { selectedPreset.setData(presetInfo) }) diff --git a/UI/BigComponents/UserBadge.ts b/UI/BigComponents/UserBadge.ts index f16d1c5f6e..e85cf6a303 100644 --- a/UI/BigComponents/UserBadge.ts +++ b/UI/BigComponents/UserBadge.ts @@ -1,6 +1,5 @@ import {VariableUiElement} from "../Base/VariableUIElement"; import Svg from "../../Svg"; -import State from "../../State"; import Combine from "../Base/Combine"; import {FixedUiElement} from "../Base/FixedUiElement"; import LanguagePicker from "../LanguagePicker"; @@ -8,24 +7,25 @@ import Translations from "../i18n/Translations"; import Link from "../Base/Link"; import Toggle from "../Input/Toggle"; import Img from "../Base/Img"; +import MapState from "../../Logic/State/MapState"; export default class UserBadge extends Toggle { - constructor() { + constructor(state: MapState) { - const userDetails = State.state.osmConnection.userDetails; + const userDetails = state.osmConnection.userDetails; const loginButton = Translations.t.general.loginWithOpenStreetMap .Clone() .SetClass("userbadge-login inline-flex justify-center items-center w-full h-full text-lg font-bold min-w-[20em]") - .onClick(() => State.state.osmConnection.AttemptLogin()); + .onClick(() => state.osmConnection.AttemptLogin()); const logout = Svg.logout_svg() .onClick(() => { - State.state.osmConnection.LogOut(); + state.osmConnection.LogOut(); }); @@ -39,15 +39,15 @@ export default class UserBadge extends Toggle { return " "; }) ).onClick(() => { - const home = State.state.osmConnection.userDetails.data?.home; + const home = state.osmConnection.userDetails.data?.home; if (home === undefined) { return; } - State.state.leafletMap.data.setView([home.lat, home.lon], 16); + state.leafletMap.data.setView([home.lat, home.lon], 16); }); const linkStyle = "flex items-baseline" - const languagePicker = (LanguagePicker.CreateLanguagePicker(State.state.layoutToUse.language) ?? new FixedUiElement("")) + const languagePicker = (LanguagePicker.CreateLanguagePicker(state.layoutToUse.language) ?? new FixedUiElement("")) .SetStyle("width:min-content;"); let messageSpan = @@ -129,7 +129,7 @@ export default class UserBadge extends Toggle { super( userBadge, loginButton, - State.state.osmConnection.isLoggedIn + state.osmConnection.isLoggedIn ) diff --git a/UI/CenterMessageBox.ts b/UI/CenterMessageBox.ts index fe1ef5c655..500fc4609d 100644 --- a/UI/CenterMessageBox.ts +++ b/UI/CenterMessageBox.ts @@ -1,12 +1,11 @@ import Translations from "./i18n/Translations"; -import State from "../State"; import {VariableUiElement} from "./Base/VariableUIElement"; +import FeaturePipelineState from "../Logic/State/FeaturePipelineState"; export default class CenterMessageBox extends VariableUiElement { - constructor() { - const state = State.state; - const updater = State.state.featurePipeline; + constructor(state: FeaturePipelineState) { + const updater = state.featurePipeline; const t = Translations.t.centerMessage; const message = updater.runningQuery.map( isRunning => { diff --git a/UI/DefaultGUI.ts b/UI/DefaultGUI.ts new file mode 100644 index 0000000000..e7273c29f6 --- /dev/null +++ b/UI/DefaultGUI.ts @@ -0,0 +1,161 @@ +import FeaturePipelineState from "../Logic/State/FeaturePipelineState"; +import State from "../State"; +import {Utils} from "../Utils"; +import {UIEventSource} from "../Logic/UIEventSource"; +import FullWelcomePaneWithTabs from "./BigComponents/FullWelcomePaneWithTabs"; +import MapControlButton from "./MapControlButton"; +import Svg from "../Svg"; +import Toggle from "./Input/Toggle"; +import Hash from "../Logic/Web/Hash"; +import {QueryParameters} from "../Logic/Web/QueryParameters"; +import Constants from "../Models/Constants"; +import UserBadge from "./BigComponents/UserBadge"; +import SearchAndGo from "./BigComponents/SearchAndGo"; +import Link from "./Base/Link"; +import BaseUIElement from "./BaseUIElement"; +import {VariableUiElement} from "./Base/VariableUIElement"; +import LeftControls from "./BigComponents/LeftControls"; +import RightControls from "./BigComponents/RightControls"; +import CenterMessageBox from "./CenterMessageBox"; + +export class DefaultGuiState { + public readonly welcomeMessageIsOpened; + public readonly downloadControlIsOpened: UIEventSource; + public readonly filterViewIsOpened: UIEventSource; + public readonly welcomeMessageOpenedTab + + constructor() { + this.filterViewIsOpened = QueryParameters.GetQueryParameter( + "filter-toggle", + "false", + "Whether or not the filter view is shown" + ).map( + (str) => str !== "false", + [], + (b) => "" + b + ); + this.welcomeMessageIsOpened = new UIEventSource(Hash.hash.data === undefined || + Hash.hash.data === "" || + Hash.hash.data == "welcome"); + + this.welcomeMessageOpenedTab = QueryParameters.GetQueryParameter( + "tab", + "0", + `The tab that is shown in the welcome-message. 0 = the explanation of the theme,1 = OSM-credits, 2 = sharescreen, 3 = more themes, 4 = about mapcomplete (user must be logged in and have >${Constants.userJourney.mapCompleteHelpUnlock} changesets)` + ).map( + (str) => (isNaN(Number(str)) ? 0 : Number(str)), + [], + (n) => "" + n + ); + this.downloadControlIsOpened = + QueryParameters.GetQueryParameter( + "download-control-toggle", + "false", + "Whether or not the download panel is shown" + ).map( + (str) => str !== "false", + [], + (b) => "" + b + ); + + } +} + + +/** + * The default MapComplete GUI initializor + * + * Adds a welcome pane, contorl buttons, ... etc to index.html + */ +export default class DefaultGUI { + private readonly _guiState: DefaultGuiState; + private readonly state: FeaturePipelineState; + + + constructor(state: FeaturePipelineState, guiState: DefaultGuiState) { + this.state = state; + this._guiState = guiState; + const self = this; + + if (state.layoutToUse.customCss !== undefined) { + Utils.LoadCustomCss(state.layoutToUse.customCss); + } + + // Attach the map + state.mainMapObject.SetClass("w-full h-full") + .AttachTo("leafletDiv") + + state.setupClickDialogOnMap( + guiState.filterViewIsOpened, + state.leafletMap + ) + + this.InitWelcomeMessage(); + + Toggle.If(state.featureSwitchUserbadge, + () => new UserBadge(state) + ).AttachTo("userbadge") + + Toggle.If(state.featureSwitchSearch, + () => new SearchAndGo(state)) + .AttachTo("searchbox"); + + + let iframePopout: () => BaseUIElement = undefined; + + if (window !== window.top) { + // MapComplete is running in an iframe + iframePopout = () => new VariableUiElement(state.locationControl.map(loc => { + const url = `${window.location.origin}${window.location.pathname}?z=${loc.zoom ?? 0}&lat=${loc.lat ?? 0}&lon=${loc.lon ?? 0}`; + const link = new Link(Svg.pop_out_img, url, true).SetClass("block w-full h-full p-1.5") + return new MapControlButton(link) + })) + + } + + new Toggle(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"); + + } + + private InitWelcomeMessage() { + const isOpened = this._guiState.welcomeMessageIsOpened + const fullOptions = new FullWelcomePaneWithTabs(isOpened, this._guiState.welcomeMessageOpenedTab, this.state); + + // ?-Button on Desktop, opens panel with close-X. + const help = new MapControlButton(Svg.help_svg()); + help.onClick(() => isOpened.setData(true)); + + + const openedTime = new Date().getTime(); + this.state.locationControl.addCallback(() => { + if (new Date().getTime() - openedTime < 15 * 1000) { + // Don't autoclose the first 15 secs when the map is moving + return; + } + isOpened.setData(false); + }); + + this.state.selectedElement.addCallbackAndRunD((_) => { + isOpened.setData(false); + }); + + return new Toggle( + fullOptions.SetClass("welcomeMessage pointer-events-auto"), + help.SetClass("pointer-events-auto"), + isOpened + ) + + } + +} \ No newline at end of file diff --git a/UI/ExportPDF.ts b/UI/ExportPDF.ts index d6711e493d..e030e30b49 100644 --- a/UI/ExportPDF.ts +++ b/UI/ExportPDF.ts @@ -104,19 +104,7 @@ export default class ExportPDF { }) - const initialized =new Set() - for (const overlayToggle of State.state.overlayToggles) { - new ShowOverlayLayer(overlayToggle.config, minimap.leafletMap, overlayToggle.isDisplayed) - initialized.add(overlayToggle.config) - } - - for (const tileLayerSource of State.state.layoutToUse.tileLayerSources) { - if (initialized.has(tileLayerSource)) { - continue - } - new ShowOverlayLayer(tileLayerSource, minimap.leafletMap) - } - + State.state.AddAllOverlaysToMap(minimap.leafletMap) } private cleanup() { diff --git a/UI/Input/LocationInput.ts b/UI/Input/LocationInput.ts index 7733dc3cd3..93144ee5ea 100644 --- a/UI/Input/LocationInput.ts +++ b/UI/Input/LocationInput.ts @@ -176,7 +176,8 @@ export default class LocationInput extends InputElement implements MinimapO enablePopups: false, zoomToFeatures: false, leafletMap: this.map.leafletMap, - layers: State.state.filteredLayers + layers: State.state.filteredLayers, + allElements: State.state.allElements } ) // Show the central point @@ -191,7 +192,9 @@ export default class LocationInput extends InputElement implements MinimapO enablePopups: false, zoomToFeatures: false, leafletMap: this.map.leafletMap, - layerToShow: this._matching_layer + layerToShow: this._matching_layer, + allElements: State.state.allElements, + selectedElement: State.state.selectedElement }) } diff --git a/UI/Input/Toggle.ts b/UI/Input/Toggle.ts index ff97477c0e..886412352f 100644 --- a/UI/Input/Toggle.ts +++ b/UI/Input/Toggle.ts @@ -1,6 +1,7 @@ import {UIEventSource} from "../../Logic/UIEventSource"; import BaseUIElement from "../BaseUIElement"; import {VariableUiElement} from "../Base/VariableUIElement"; +import Lazy from "../Base/Lazy"; /** * The 'Toggle' is a UIElement showing either one of two elements, depending on the state. @@ -24,4 +25,16 @@ export default class Toggle extends VariableUiElement { }) return this; } + + public static If(condition: UIEventSource, constructor: () => BaseUIElement): BaseUIElement { + if(constructor === undefined){ + return undefined + } + return new Toggle( + new Lazy(constructor), + undefined, + condition + ) + + } } \ No newline at end of file diff --git a/UI/Popup/SplitRoadWizard.ts b/UI/Popup/SplitRoadWizard.ts index 0127a865af..afa58631d1 100644 --- a/UI/Popup/SplitRoadWizard.ts +++ b/UI/Popup/SplitRoadWizard.ts @@ -68,7 +68,7 @@ export default class SplitRoadWizard extends Toggle { leafletMap: miniMap.leafletMap, zoomToFeatures: false, enablePopups: false, - layerToShow: SplitRoadWizard.splitLayerStyling + layerToShow: SplitRoadWizard.splitLayerStyling, }) new ShowDataMultiLayer({ @@ -76,7 +76,8 @@ export default class SplitRoadWizard extends Toggle { layers: State.state.filteredLayers, leafletMap: miniMap.leafletMap, enablePopups: false, - zoomToFeatures: true + zoomToFeatures: true, + allElements: State.state.allElements, }) /** diff --git a/UI/ShowDataLayer/ShowDataLayer.ts b/UI/ShowDataLayer/ShowDataLayer.ts index 5e8681bbfa..d59bb5cbd9 100644 --- a/UI/ShowDataLayer/ShowDataLayer.ts +++ b/UI/ShowDataLayer/ShowDataLayer.ts @@ -4,9 +4,9 @@ import {UIEventSource} from "../../Logic/UIEventSource"; import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; import FeatureInfoBox from "../Popup/FeatureInfoBox"; -import State from "../../State"; import {ShowDataLayerOptions} from "./ShowDataLayerOptions"; -import {FixedUiElement} from "../Base/FixedUiElement"; +import {ElementStorage} from "../../Logic/ElementStorage"; +import Hash from "../../Logic/Web/Hash"; export default class ShowDataLayer { @@ -14,7 +14,8 @@ export default class ShowDataLayer { private readonly _enablePopups: boolean; private readonly _features: UIEventSource<{ feature: any }[]> private readonly _layerToShow: LayerConfig; - + private readonly _selectedElement: UIEventSource + private readonly allElements : ElementStorage // Used to generate a fresh ID when needed private _cleanCount = 0; private geoLayer = undefined; @@ -43,6 +44,8 @@ export default class ShowDataLayer { const features = options.features.features.map(featFreshes => featFreshes.map(ff => ff.feature)); this._features = features; this._layerToShow = options.layerToShow; + this._selectedElement = options.selectedElement + this.allElements = options.allElements; const self = this; options.leafletMap.addCallbackAndRunD(_ => { @@ -71,7 +74,7 @@ export default class ShowDataLayer { }) - State.state.selectedElement.addCallbackAndRunD(selected => { + this._selectedElement?.addCallbackAndRunD(selected => { if (self._leafletMap.data === undefined) { return; } @@ -162,7 +165,7 @@ export default class ShowDataLayer { private createStyleFor(feature) { - const tagsSource = State.state.allElements.addOrGetElement(feature); + const tagsSource = this.allElements?.addOrGetElement(feature) ?? new UIEventSource(feature.properties.id); // Every object is tied to exactly one layer const layer = this._layerToShow return layer?.GenerateLeafletStyle(tagsSource, true); @@ -178,10 +181,7 @@ export default class ShowDataLayer { return; } - let tagSource = State.state.allElements.getEventSourceById(feature.properties.id) - if (tagSource === undefined) { - tagSource = new UIEventSource(feature.properties) - } + let tagSource = this.allElements?.getEventSourceById(feature.properties.id) ?? new UIEventSource(feature.properties) const clickable = !(layer.title === undefined && (layer.tagRenderings ?? []).length === 0) const style = layer.GenerateLeafletStyle(tagSource, clickable); const baseElement = style.icon.html; @@ -230,20 +230,20 @@ export default class ShowDataLayer { popup.setContent(`
Popup for ${feature.properties.id} ${feature.geometry.type} ${id} is loading
`) leafletLayer.on("popupopen", () => { if (infobox === undefined) { - const tags = State.state.allElements.getEventSourceById(feature.properties.id); + const tags = this.allElements?.getEventSourceById(feature.properties.id) ?? new UIEventSource(feature.properties); infobox = new FeatureInfoBox(tags, layer); infobox.isShown.addCallback(isShown => { if (!isShown) { - State.state.selectedElement.setData(undefined); + this._selectedElement?.setData(undefined); leafletLayer.closePopup() } }); } infobox.AttachTo(id) infobox.Activate(); - if (State.state?.selectedElement?.data?.properties?.id !== feature.properties.id) { - State.state.selectedElement.setData(feature) + if (this._selectedElement?.data?.properties?.id !== feature.properties.id) { + this._selectedElement?.setData(feature) } }); @@ -254,6 +254,7 @@ export default class ShowDataLayer { feature: feature, leafletlayer: leafletLayer }) + } diff --git a/UI/ShowDataLayer/ShowDataLayerOptions.ts b/UI/ShowDataLayer/ShowDataLayerOptions.ts index 2323ce0671..4b811be68a 100644 --- a/UI/ShowDataLayer/ShowDataLayerOptions.ts +++ b/UI/ShowDataLayer/ShowDataLayerOptions.ts @@ -1,8 +1,11 @@ import FeatureSource from "../../Logic/FeatureSource/FeatureSource"; import {UIEventSource} from "../../Logic/UIEventSource"; +import {ElementStorage} from "../../Logic/ElementStorage"; export interface ShowDataLayerOptions { features: FeatureSource, + selectedElement?: UIEventSource, + allElements?: ElementStorage, leafletMap: UIEventSource, enablePopups?: true | boolean, zoomToFeatures?: false | boolean, diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts index 24c87b8218..7e9ef1e65a 100644 --- a/UI/SpecialVisualizations.ts +++ b/UI/SpecialVisualizations.ts @@ -208,7 +208,8 @@ export default class SpecialVisualizations { enablePopups: false, zoomToFeatures: true, layers: State.state.filteredLayers, - features: new StaticFeatureSource(featuresToShow, true) + features: new StaticFeatureSource(featuresToShow, true), + allElements: State.state.allElements } ) diff --git a/Utils.ts b/Utils.ts index 26c8a75341..0dd2a83633 100644 --- a/Utils.ts +++ b/Utils.ts @@ -463,5 +463,18 @@ export class Utils { } return hours+":"+Utils.TwoDigits(minutes)+":"+Utils.TwoDigits(seconds) } + + public static DisableLongPresses(){ + // Remove all context event listeners on mobile to prevent long presses + window.addEventListener('contextmenu', (e) => { // Not compatible with IE < 9 + + if (e.target["nodeName"] === "INPUT") { + return; + } + e.preventDefault(); + return false; + }, false); + + } } diff --git a/assets/layers/charging_station/charging_station.json b/assets/layers/charging_station/charging_station.json index 5310006c84..47fd37bd5c 100644 --- a/assets/layers/charging_station/charging_station.json +++ b/assets/layers/charging_station/charging_station.json @@ -1,3650 +1,3650 @@ { - "id": "charging_station", - "name": { - "en": "Charging stations", - "it": "Stazioni di ricarica", - "ja": "充電ステーション", - "nb_NO": "Ladestasjoner", - "ru": "Зарядные станции", - "zh_Hant": "充電站" - }, - "minzoom": 10, - "source": { - "osmTags": { - "or": [ - "amenity=charging_station", - "disused:amenity=charging_station", - "planned:amenity=charging_station", - "construction:amenity=charging_station" - ] - } - }, - "title": { - "render": { - "en": "Charging station", - "it": "Stazione di ricarica", - "ja": "充電ステーション", - "nb_NO": "Ladestasjon", - "ru": "Зарядная станция", - "zh_Hant": "充電站" - } - }, - "description": { - "en": "A charging station", - "it": "Una stazione di ricarica", - "ja": "充電ステーション", - "nb_NO": "En ladestasjon", - "ru": "Зарядная станция", - "zh_Hant": "充電站" - }, - "tagRenderings": [ - "images", - { - "id": "Type", - "question": { - "en": "Which vehicles are allowed to charge here?" - }, - "multiAnswer": true, - "mappings": [ - { - "if": "bicycle=yes", - "ifnot": "bicycle=no", - "then": { - "en": "bicycles can be charged here" - } - }, - { - "if": "motorcar=yes", - "ifnot": "motorcar=no", - "then": { - "en": "Cars can be charged here" - } - }, - { - "if": "scooter=yes", - "ifnot": "scooter=no", - "then": { - "en": "Scooters can be charged here" - } - }, - { - "if": "hgv=yes", - "ifnot": "hgv=no", - "then": { - "en": "Heavy good vehicles (such as trucks) can be charged here" - } - }, - { - "if": "bus=yes", - "ifnot": "bus=no", - "then": { - "en": "Buses can be charged here" - } - } - ] + "id": "charging_station", + "name": { + "en": "Charging stations", + "it": "Stazioni di ricarica", + "ja": "充電ステーション", + "nb_NO": "Ladestasjoner", + "ru": "Зарядные станции", + "zh_Hant": "充電站" }, - { - "id": "access", - "question": { - "en": "Who is allowed to use this charging station?" - }, - "render": { - "en": "Access is {access}" - }, - "freeform": { - "key": "access", - "addExtraTags": [ - "fixme=Freeform field used for access - doublecheck the value" - ] - }, - "mappings": [ - { - "if": "access=yes", - "then": "Anyone can use this charging station (payment might be needed)" - }, - { - "if": { + "minzoom": 10, + "source": { + "osmTags": { "or": [ - "access=permissive", - "access=public" + "amenity=charging_station", + "disused:amenity=charging_station", + "planned:amenity=charging_station", + "construction:amenity=charging_station" ] - }, - "then": "Anyone can use this charging station (payment might be needed)", - "hideInAnswer": true - }, - { - "if": "access=customers", - "then": "Only customers of the place this station belongs to can use this charging station
E.g. a charging station operated by hotel which is only usable by their guests " - }, - { - "if": "access=private", - "then": "Not accessible to the general public (e.g. only accessible to the owners, employees, ...)" } - ] }, - { - "id": "capacity", - "render": { - "en": "{capacity} vehicles can be charged here at the same time", - "nl": "{capacity} voertuigen kunnen hier op hetzelfde moment opgeladen worden" - }, - "question": { - "en": "How much vehicles can be charged here at the same time?", - "nl": "Hoeveel voertuigen kunnen hier opgeladen worden?" - }, - "freeform": { - "key": "capacity", - "type": "pnat" - } + "title": { + "render": { + "en": "Charging station", + "it": "Stazione di ricarica", + "ja": "充電ステーション", + "nb_NO": "Ladestasjon", + "ru": "Зарядная станция", + "zh_Hant": "充電站" + } }, - { - "id": "Available_charging_stations (generated)", - "question": { - "en": "Which charging stations are available here?" - }, - "multiAnswer": true, - "mappings": [ + "description": { + "en": "A charging station", + "it": "Una stazione di ricarica", + "ja": "充電ステーション", + "nb_NO": "En ladestasjon", + "ru": "Зарядная станция", + "zh_Hant": "充電站" + }, + "tagRenderings": [ + "images", { - "if": "socket:schuko=1", - "ifnot": "socket:schuko=", - "then": { - "en": "
Schuko wall plug without ground pin (CEE7/4 type F)
", - "nl": "
Schuko stekker zonder aardingspin (CEE7/4 type F)
" - }, - "hideInAnswer": { - "or": [ - "_country!=be", - "_country!=fr", - "_country!=ma", - "_country!=tn", - "_country!=pl", - "_country!=cs", - "_country!=sk", - "_country!=mo" + "id": "Type", + "question": { + "en": "Which vehicles are allowed to charge here?" + }, + "multiAnswer": true, + "mappings": [ + { + "if": "bicycle=yes", + "ifnot": "bicycle=no", + "then": { + "en": "bicycles can be charged here" + } + }, + { + "if": "motorcar=yes", + "ifnot": "motorcar=no", + "then": { + "en": "Cars can be charged here" + } + }, + { + "if": "scooter=yes", + "ifnot": "scooter=no", + "then": { + "en": "Scooters can be charged here" + } + }, + { + "if": "hgv=yes", + "ifnot": "hgv=no", + "then": { + "en": "Heavy good vehicles (such as trucks) can be charged here" + } + }, + { + "if": "bus=yes", + "ifnot": "bus=no", + "then": { + "en": "Buses can be charged here" + } + } ] - } }, { - "if": { - "and": [ - "socket:schuko~*", - "socket:schuko!=1" - ] - }, - "then": { - "en": "
Schuko wall plug without ground pin (CEE7/4 type F)
", - "nl": "
Schuko stekker zonder aardingspin (CEE7/4 type F)
" - }, - "hideInAnswer": true - }, - { - "if": "socket:typee=1", - "ifnot": "socket:typee=", - "then": { - "en": "
European wall plug with ground pin (CEE7/4 type E)
", - "nl": "
Europese stekker met aardingspin (CEE7/4 type E)
" - } - }, - { - "if": { - "and": [ - "socket:typee~*", - "socket:typee!=1" - ] - }, - "then": { - "en": "
European wall plug with ground pin (CEE7/4 type E)
", - "nl": "
Europese stekker met aardingspin (CEE7/4 type E)
" - }, - "hideInAnswer": true - }, - { - "if": "socket:chademo=1", - "ifnot": "socket:chademo=", - "then": { - "en": "
Chademo
", - "nl": "
Chademo
" - }, - "hideInAnswer": { - "or": [ - { - "and": [ - "car=no", - "motorcar=no", - "hgv=no", - "bus=no" + "id": "access", + "question": { + "en": "Who is allowed to use this charging station?" + }, + "render": { + "en": "Access is {access}" + }, + "freeform": { + "key": "access", + "addExtraTags": [ + "fixme=Freeform field used for access - doublecheck the value" ] - }, - { - "and": [ - { - "or": [ - "bicycle=yes", - "scooter=yes" - ] - }, - "car!=yes", - "motorcar!=yes", - "hgv!=yes", - "bus!=yes" - ] - } + }, + "mappings": [ + { + "if": "access=yes", + "then": "Anyone can use this charging station (payment might be needed)" + }, + { + "if": { + "or": [ + "access=permissive", + "access=public" + ] + }, + "then": "Anyone can use this charging station (payment might be needed)", + "hideInAnswer": true + }, + { + "if": "access=customers", + "then": "Only customers of the place this station belongs to can use this charging station
E.g. a charging station operated by hotel which is only usable by their guests " + }, + { + "if": "access=private", + "then": "Not accessible to the general public (e.g. only accessible to the owners, employees, ...)" + } ] - } }, { - "if": { - "and": [ - "socket:chademo~*", - "socket:chademo!=1" - ] - }, - "then": { - "en": "
Chademo
", - "nl": "
Chademo
" - }, - "hideInAnswer": true - }, - { - "if": "socket:type1_cable=1", - "ifnot": "socket:type1_cable=", - "then": { - "en": "
Type 1 with cable (J1772)
", - "nl": "
Type 1 met kabel (J1772)
" - }, - "hideInAnswer": { - "or": [ - { - "and": [ - "car=no", - "motorcar=no", - "hgv=no", - "bus=no" - ] - }, - { - "and": [ - { - "or": [ - "bicycle=yes", - "scooter=yes" - ] - }, - "car!=yes", - "motorcar!=yes", - "hgv!=yes", - "bus!=yes" - ] - } - ] - } - }, - { - "if": { - "and": [ - "socket:type1_cable~*", - "socket:type1_cable!=1" - ] - }, - "then": { - "en": "
Type 1 with cable (J1772)
", - "nl": "
Type 1 met kabel (J1772)
" - }, - "hideInAnswer": true - }, - { - "if": "socket:type1=1", - "ifnot": "socket:type1=", - "then": { - "en": "
Type 1 without cable (J1772)
", - "nl": "
Type 1 zonder kabel (J1772)
" - }, - "hideInAnswer": { - "or": [ - { - "and": [ - "car=no", - "motorcar=no", - "hgv=no", - "bus=no" - ] - }, - { - "and": [ - { - "or": [ - "bicycle=yes", - "scooter=yes" - ] - }, - "car!=yes", - "motorcar!=yes", - "hgv!=yes", - "bus!=yes" - ] - } - ] - } - }, - { - "if": { - "and": [ - "socket:type1~*", - "socket:type1!=1" - ] - }, - "then": { - "en": "
Type 1 without cable (J1772)
", - "nl": "
Type 1 zonder kabel (J1772)
" - }, - "hideInAnswer": true - }, - { - "if": "socket:type1_combo=1", - "ifnot": "socket:type1_combo=", - "then": { - "en": "
Type 1 CCS (aka Type 1 Combo)
", - "nl": "
Type 1 CCS (ook gekend als Type 1 Combo)
" - }, - "hideInAnswer": { - "or": [ - { - "and": [ - "car=no", - "motorcar=no", - "hgv=no", - "bus=no" - ] - }, - { - "and": [ - { - "or": [ - "bicycle=yes", - "scooter=yes" - ] - }, - "car!=yes", - "motorcar!=yes", - "hgv!=yes", - "bus!=yes" - ] - } - ] - } - }, - { - "if": { - "and": [ - "socket:type1_combo~*", - "socket:type1_combo!=1" - ] - }, - "then": { - "en": "
Type 1 CCS (aka Type 1 Combo)
", - "nl": "
Type 1 CCS (ook gekend als Type 1 Combo)
" - }, - "hideInAnswer": true - }, - { - "if": "socket:tesla_supercharger=1", - "ifnot": "socket:tesla_supercharger=", - "then": { - "en": "
Tesla Supercharger
", - "nl": "
Tesla Supercharger
" - }, - "hideInAnswer": { - "or": [ - { - "and": [ - "car=no", - "motorcar=no", - "hgv=no", - "bus=no" - ] - }, - { - "and": [ - { - "or": [ - "bicycle=yes", - "scooter=yes" - ] - }, - "car!=yes", - "motorcar!=yes", - "hgv!=yes", - "bus!=yes" - ] - } - ] - } - }, - { - "if": { - "and": [ - "socket:tesla_supercharger~*", - "socket:tesla_supercharger!=1" - ] - }, - "then": { - "en": "
Tesla Supercharger
", - "nl": "
Tesla Supercharger
" - }, - "hideInAnswer": true - }, - { - "if": "socket:type2=1", - "ifnot": "socket:type2=", - "then": { - "en": "
Type 2 (mennekes)
", - "nl": "
Type 2 (mennekes)
" - }, - "hideInAnswer": { - "or": [ - { - "and": [ - "car=no", - "motorcar=no", - "hgv=no", - "bus=no" - ] - }, - { - "and": [ - { - "or": [ - "bicycle=yes", - "scooter=yes" - ] - }, - "car!=yes", - "motorcar!=yes", - "hgv!=yes", - "bus!=yes" - ] - } - ] - } - }, - { - "if": { - "and": [ - "socket:type2~*", - "socket:type2!=1" - ] - }, - "then": { - "en": "
Type 2 (mennekes)
", - "nl": "
Type 2 (mennekes)
" - }, - "hideInAnswer": true - }, - { - "if": "socket:type2_combo=1", - "ifnot": "socket:type2_combo=", - "then": { - "en": "
Type 2 CCS (mennekes)
", - "nl": "
Type 2 CCS (mennekes)
" - }, - "hideInAnswer": { - "or": [ - { - "and": [ - "car=no", - "motorcar=no", - "hgv=no", - "bus=no" - ] - }, - { - "and": [ - { - "or": [ - "bicycle=yes", - "scooter=yes" - ] - }, - "car!=yes", - "motorcar!=yes", - "hgv!=yes", - "bus!=yes" - ] - } - ] - } - }, - { - "if": { - "and": [ - "socket:type2_combo~*", - "socket:type2_combo!=1" - ] - }, - "then": { - "en": "
Type 2 CCS (mennekes)
", - "nl": "
Type 2 CCS (mennekes)
" - }, - "hideInAnswer": true - }, - { - "if": "socket:type2_cable=1", - "ifnot": "socket:type2_cable=", - "then": { - "en": "
Type 2 with cable (mennekes)
", - "nl": "
Type 2 met kabel (J1772)
" - }, - "hideInAnswer": { - "or": [ - { - "and": [ - "car=no", - "motorcar=no", - "hgv=no", - "bus=no" - ] - }, - { - "and": [ - { - "or": [ - "bicycle=yes", - "scooter=yes" - ] - }, - "car!=yes", - "motorcar!=yes", - "hgv!=yes", - "bus!=yes" - ] - } - ] - } - }, - { - "if": { - "and": [ - "socket:type2_cable~*", - "socket:type2_cable!=1" - ] - }, - "then": { - "en": "
Type 2 with cable (mennekes)
", - "nl": "
Type 2 met kabel (J1772)
" - }, - "hideInAnswer": true - }, - { - "if": "socket:tesla_supercharger_ccs=1", - "ifnot": "socket:tesla_supercharger_ccs=", - "then": { - "en": "
Tesla Supercharger CCS (a branded type2_css)
", - "nl": "
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
" - }, - "hideInAnswer": { - "or": [ - { - "and": [ - "car=no", - "motorcar=no", - "hgv=no", - "bus=no" - ] - }, - { - "and": [ - { - "or": [ - "bicycle=yes", - "scooter=yes" - ] - }, - "car!=yes", - "motorcar!=yes", - "hgv!=yes", - "bus!=yes" - ] - } - ] - } - }, - { - "if": { - "and": [ - "socket:tesla_supercharger_ccs~*", - "socket:tesla_supercharger_ccs!=1" - ] - }, - "then": { - "en": "
Tesla Supercharger CCS (a branded type2_css)
", - "nl": "
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
" - }, - "hideInAnswer": true - }, - { - "if": "socket:tesla_destination=1", - "ifnot": "socket:tesla_destination=", - "then": { - "en": "
Tesla Supercharger (destination)
", - "nl": "
Tesla Supercharger (destination)
" - }, - "hideInAnswer": { - "or": [ - { - "and": [ - "car=no", - "motorcar=no", - "hgv=no", - "bus=no" - ] - }, - { - "and": [ - { - "or": [ - "bicycle=yes", - "scooter=yes" - ] - }, - "car!=yes", - "motorcar!=yes", - "hgv!=yes", - "bus!=yes" - ] - }, - { - "or": [ - "_country!=us" - ] - } - ] - } - }, - { - "if": { - "and": [ - "socket:tesla_destination~*", - "socket:tesla_destination!=1" - ] - }, - "then": { - "en": "
Tesla Supercharger (destination)
", - "nl": "
Tesla Supercharger (destination)
" - }, - "hideInAnswer": true - }, - { - "if": "socket:tesla_destination=1", - "ifnot": "socket:tesla_destination=", - "then": { - "en": "
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
", - "nl": "
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
" - }, - "hideInAnswer": { - "or": [ - { - "and": [ - "car=no", - "motorcar=no", - "hgv=no", - "bus=no" - ] - }, - { - "and": [ - { - "or": [ - "bicycle=yes", - "scooter=yes" - ] - }, - "car!=yes", - "motorcar!=yes", - "hgv!=yes", - "bus!=yes" - ] - }, - { - "or": [ - "_country=us" - ] - } - ] - } - }, - { - "if": { - "and": [ - "socket:tesla_destination~*", - "socket:tesla_destination!=1" - ] - }, - "then": { - "en": "
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
", - "nl": "
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
" - }, - "hideInAnswer": true - }, - { - "if": "socket:USB-A=1", - "ifnot": "socket:USB-A=", - "then": { - "en": "
USB to charge phones and small electronics
", - "nl": "
USB om GSMs en kleine electronica op te laden
" - } - }, - { - "if": { - "and": [ - "socket:USB-A~*", - "socket:USB-A!=1" - ] - }, - "then": { - "en": "
USB to charge phones and small electronics
", - "nl": "
USB om GSMs en kleine electronica op te laden
" - }, - "hideInAnswer": true - }, - { - "if": "socket:bosch_3pin=1", - "ifnot": "socket:bosch_3pin=", - "then": { - "en": "
Bosch Active Connect with 3 pins and cable
", - "nl": "
Bosch Active Connect met 3 pinnen aan een kabel
" - }, - "hideInAnswer": { - "or": [ - { - "and": [ - "bicycle=no" - ] - }, - { - "and": [ - { - "or": [ - "car=yes", - "motorcar=yes", - "hgv=yes", - "bus=yes" - ] - }, - "bicycle!=yes" - ] - } - ] - } - }, - { - "if": { - "and": [ - "socket:bosch_3pin~*", - "socket:bosch_3pin!=1" - ] - }, - "then": { - "en": "
Bosch Active Connect with 3 pins and cable
", - "nl": "
Bosch Active Connect met 3 pinnen aan een kabel
" - }, - "hideInAnswer": true - }, - { - "if": "socket:bosch_5pin=1", - "ifnot": "socket:bosch_5pin=", - "then": { - "en": "
Bosch Active Connect with 5 pins and cable
", - "nl": "
Bosch Active Connect met 5 pinnen aan een kabel
" - }, - "hideInAnswer": { - "or": [ - { - "and": [ - "bicycle=no" - ] - }, - { - "and": [ - { - "or": [ - "car=yes", - "motorcar=yes", - "hgv=yes", - "bus=yes" - ] - }, - "bicycle!=yes" - ] - } - ] - } - }, - { - "if": { - "and": [ - "socket:bosch_5pin~*", - "socket:bosch_5pin!=1" - ] - }, - "then": { - "en": "
Bosch Active Connect with 5 pins and cable
", - "nl": "
Bosch Active Connect met 5 pinnen aan een kabel
" - }, - "hideInAnswer": true - } - ] - }, - { - "id": "plugs-0", - "question": { - "en": "How much plugs of type
Schuko wall plug without ground pin (CEE7/4 type F)
are available here?", - "nl": "Hoeveel stekkers van type
Schuko stekker zonder aardingspin (CEE7/4 type F)
heeft dit oplaadpunt?" - }, - "render": { - "en": "There are {socket:schuko} plugs of type
Schuko wall plug without ground pin (CEE7/4 type F)
available here", - "nl": "Hier zijn {socket:schuko} stekkers van het type
Schuko stekker zonder aardingspin (CEE7/4 type F)
" - }, - "freeform": { - "key": "socket:schuko", - "type": "pnat" - }, - "condition": { - "and": [ - "socket:schuko~*", - "socket:schuko!=0" - ] - } - }, - { - "id": "voltage-0", - "question": { - "en": "What voltage do the plugs with
Schuko wall plug without ground pin (CEE7/4 type F)
offer?", - "nl": "Welke spanning levert de stekker van type
Schuko stekker zonder aardingspin (CEE7/4 type F)
" - }, - "render": { - "en": "
Schuko wall plug without ground pin (CEE7/4 type F)
outputs {socket:schuko:voltage} volt", - "nl": "
Schuko stekker zonder aardingspin (CEE7/4 type F)
heeft een spanning van {socket:schuko:voltage} volt" - }, - "freeform": { - "key": "socket:schuko:voltage", - "type": "pfloat" - }, - "mappings": [ - { - "if": "socket:socket:schuko:voltage=230 V", - "then": { - "en": "
Schuko wall plug without ground pin (CEE7/4 type F)
outputs 230 volt", - "nl": "
Schuko stekker zonder aardingspin (CEE7/4 type F)
heeft een spanning van 230 volt" - } - } - ], - "condition": { - "and": [ - "socket:schuko~*", - "socket:schuko!=0" - ] - } - }, - { - "id": "current-0", - "question": { - "en": "What current do the plugs with
Schuko wall plug without ground pin (CEE7/4 type F)
offer?", - "nl": "Welke stroom levert de stekker van type
Schuko stekker zonder aardingspin (CEE7/4 type F)
?" - }, - "render": { - "en": "
Schuko wall plug without ground pin (CEE7/4 type F)
outputs at most {socket:schuko:current}A", - "nl": "
Schuko stekker zonder aardingspin (CEE7/4 type F)
levert een stroom van maximaal {socket:schuko:current}A" - }, - "freeform": { - "key": "socket:schuko:current", - "type": "pfloat" - }, - "mappings": [ - { - "if": "socket:socket:schuko:current=16 A", - "then": { - "en": "
Schuko wall plug without ground pin (CEE7/4 type F)
outputs at most 16 A", - "nl": "
Schuko stekker zonder aardingspin (CEE7/4 type F)
levert een stroom van maximaal 16 A" - } - } - ], - "condition": { - "and": [ - "socket:schuko~*", - "socket:schuko!=0" - ] - } - }, - { - "id": "power-output-0", - "question": { - "en": "What power output does a single plug of type
Schuko wall plug without ground pin (CEE7/4 type F)
offer?", - "nl": "Welk vermogen levert een enkele stekker van type
Schuko stekker zonder aardingspin (CEE7/4 type F)
?" - }, - "render": { - "en": "
Schuko wall plug without ground pin (CEE7/4 type F)
outputs at most {socket:schuko:output}", - "nl": "
Schuko stekker zonder aardingspin (CEE7/4 type F)
levert een vermogen van maximaal {socket:schuko:output}" - }, - "freeform": { - "key": "socket:schuko:output", - "type": "pfloat" - }, - "mappings": [ - { - "if": "socket:socket:schuko:output=3.6 kw", - "then": { - "en": "
Schuko wall plug without ground pin (CEE7/4 type F)
outputs at most 3.6 kw", - "nl": "
Schuko stekker zonder aardingspin (CEE7/4 type F)
levert een vermogen van maximaal 3.6 kw" - } - } - ], - "condition": { - "and": [ - "socket:schuko~*", - "socket:schuko!=0" - ] - } - }, - { - "id": "plugs-1", - "question": { - "en": "How much plugs of type
European wall plug with ground pin (CEE7/4 type E)
are available here?", - "nl": "Hoeveel stekkers van type
Europese stekker met aardingspin (CEE7/4 type E)
heeft dit oplaadpunt?" - }, - "render": { - "en": "There are {socket:typee} plugs of type
European wall plug with ground pin (CEE7/4 type E)
available here", - "nl": "Hier zijn {socket:typee} stekkers van het type
Europese stekker met aardingspin (CEE7/4 type E)
" - }, - "freeform": { - "key": "socket:typee", - "type": "pnat" - }, - "condition": { - "and": [ - "socket:typee~*", - "socket:typee!=0" - ] - } - }, - { - "id": "voltage-1", - "question": { - "en": "What voltage do the plugs with
European wall plug with ground pin (CEE7/4 type E)
offer?", - "nl": "Welke spanning levert de stekker van type
Europese stekker met aardingspin (CEE7/4 type E)
" - }, - "render": { - "en": "
European wall plug with ground pin (CEE7/4 type E)
outputs {socket:typee:voltage} volt", - "nl": "
Europese stekker met aardingspin (CEE7/4 type E)
heeft een spanning van {socket:typee:voltage} volt" - }, - "freeform": { - "key": "socket:typee:voltage", - "type": "pfloat" - }, - "mappings": [ - { - "if": "socket:socket:typee:voltage=230 V", - "then": { - "en": "
European wall plug with ground pin (CEE7/4 type E)
outputs 230 volt", - "nl": "
Europese stekker met aardingspin (CEE7/4 type E)
heeft een spanning van 230 volt" - } - } - ], - "condition": { - "and": [ - "socket:typee~*", - "socket:typee!=0" - ] - } - }, - { - "id": "current-1", - "question": { - "en": "What current do the plugs with
European wall plug with ground pin (CEE7/4 type E)
offer?", - "nl": "Welke stroom levert de stekker van type
Europese stekker met aardingspin (CEE7/4 type E)
?" - }, - "render": { - "en": "
European wall plug with ground pin (CEE7/4 type E)
outputs at most {socket:typee:current}A", - "nl": "
Europese stekker met aardingspin (CEE7/4 type E)
levert een stroom van maximaal {socket:typee:current}A" - }, - "freeform": { - "key": "socket:typee:current", - "type": "pfloat" - }, - "mappings": [ - { - "if": "socket:socket:typee:current=16 A", - "then": { - "en": "
European wall plug with ground pin (CEE7/4 type E)
outputs at most 16 A", - "nl": "
Europese stekker met aardingspin (CEE7/4 type E)
levert een stroom van maximaal 16 A" - } - } - ], - "condition": { - "and": [ - "socket:typee~*", - "socket:typee!=0" - ] - } - }, - { - "id": "power-output-1", - "question": { - "en": "What power output does a single plug of type
European wall plug with ground pin (CEE7/4 type E)
offer?", - "nl": "Welk vermogen levert een enkele stekker van type
Europese stekker met aardingspin (CEE7/4 type E)
?" - }, - "render": { - "en": "
European wall plug with ground pin (CEE7/4 type E)
outputs at most {socket:typee:output}", - "nl": "
Europese stekker met aardingspin (CEE7/4 type E)
levert een vermogen van maximaal {socket:typee:output}" - }, - "freeform": { - "key": "socket:typee:output", - "type": "pfloat" - }, - "mappings": [ - { - "if": "socket:socket:typee:output=3 kw", - "then": { - "en": "
European wall plug with ground pin (CEE7/4 type E)
outputs at most 3 kw", - "nl": "
Europese stekker met aardingspin (CEE7/4 type E)
levert een vermogen van maximaal 3 kw" - } - }, - { - "if": "socket:socket:typee:output=22 kw", - "then": { - "en": "
European wall plug with ground pin (CEE7/4 type E)
outputs at most 22 kw", - "nl": "
Europese stekker met aardingspin (CEE7/4 type E)
levert een vermogen van maximaal 22 kw" - } - } - ], - "condition": { - "and": [ - "socket:typee~*", - "socket:typee!=0" - ] - } - }, - { - "id": "plugs-2", - "question": { - "en": "How much plugs of type
Chademo
are available here?", - "nl": "Hoeveel stekkers van type
Chademo
heeft dit oplaadpunt?" - }, - "render": { - "en": "There are {socket:chademo} plugs of type
Chademo
available here", - "nl": "Hier zijn {socket:chademo} stekkers van het type
Chademo
" - }, - "freeform": { - "key": "socket:chademo", - "type": "pnat" - }, - "condition": { - "and": [ - "socket:chademo~*", - "socket:chademo!=0" - ] - } - }, - { - "id": "voltage-2", - "question": { - "en": "What voltage do the plugs with
Chademo
offer?", - "nl": "Welke spanning levert de stekker van type
Chademo
" - }, - "render": { - "en": "
Chademo
outputs {socket:chademo:voltage} volt", - "nl": "
Chademo
heeft een spanning van {socket:chademo:voltage} volt" - }, - "freeform": { - "key": "socket:chademo:voltage", - "type": "pfloat" - }, - "mappings": [ - { - "if": "socket:socket:chademo:voltage=500 V", - "then": { - "en": "
Chademo
outputs 500 volt", - "nl": "
Chademo
heeft een spanning van 500 volt" - } - } - ], - "condition": { - "and": [ - "socket:chademo~*", - "socket:chademo!=0" - ] - } - }, - { - "id": "current-2", - "question": { - "en": "What current do the plugs with
Chademo
offer?", - "nl": "Welke stroom levert de stekker van type
Chademo
?" - }, - "render": { - "en": "
Chademo
outputs at most {socket:chademo:current}A", - "nl": "
Chademo
levert een stroom van maximaal {socket:chademo:current}A" - }, - "freeform": { - "key": "socket:chademo:current", - "type": "pfloat" - }, - "mappings": [ - { - "if": "socket:socket:chademo:current=120 A", - "then": { - "en": "
Chademo
outputs at most 120 A", - "nl": "
Chademo
levert een stroom van maximaal 120 A" - } - } - ], - "condition": { - "and": [ - "socket:chademo~*", - "socket:chademo!=0" - ] - } - }, - { - "id": "power-output-2", - "question": { - "en": "What power output does a single plug of type
Chademo
offer?", - "nl": "Welk vermogen levert een enkele stekker van type
Chademo
?" - }, - "render": { - "en": "
Chademo
outputs at most {socket:chademo:output}", - "nl": "
Chademo
levert een vermogen van maximaal {socket:chademo:output}" - }, - "freeform": { - "key": "socket:chademo:output", - "type": "pfloat" - }, - "mappings": [ - { - "if": "socket:socket:chademo:output=50 kw", - "then": { - "en": "
Chademo
outputs at most 50 kw", - "nl": "
Chademo
levert een vermogen van maximaal 50 kw" - } - } - ], - "condition": { - "and": [ - "socket:chademo~*", - "socket:chademo!=0" - ] - } - }, - { - "id": "plugs-3", - "question": { - "en": "How much plugs of type
Type 1 with cable (J1772)
are available here?", - "nl": "Hoeveel stekkers van type
Type 1 met kabel (J1772)
heeft dit oplaadpunt?" - }, - "render": { - "en": "There are {socket:type1_cable} plugs of type
Type 1 with cable (J1772)
available here", - "nl": "Hier zijn {socket:type1_cable} stekkers van het type
Type 1 met kabel (J1772)
" - }, - "freeform": { - "key": "socket:type1_cable", - "type": "pnat" - }, - "condition": { - "and": [ - "socket:type1_cable~*", - "socket:type1_cable!=0" - ] - } - }, - { - "id": "voltage-3", - "question": { - "en": "What voltage do the plugs with
Type 1 with cable (J1772)
offer?", - "nl": "Welke spanning levert de stekker van type
Type 1 met kabel (J1772)
" - }, - "render": { - "en": "
Type 1 with cable (J1772)
outputs {socket:type1_cable:voltage} volt", - "nl": "
Type 1 met kabel (J1772)
heeft een spanning van {socket:type1_cable:voltage} volt" - }, - "freeform": { - "key": "socket:type1_cable:voltage", - "type": "pfloat" - }, - "mappings": [ - { - "if": "socket:socket:type1_cable:voltage=200 V", - "then": { - "en": "
Type 1 with cable (J1772)
outputs 200 volt", - "nl": "
Type 1 met kabel (J1772)
heeft een spanning van 200 volt" - } - }, - { - "if": "socket:socket:type1_cable:voltage=240 V", - "then": { - "en": "
Type 1 with cable (J1772)
outputs 240 volt", - "nl": "
Type 1 met kabel (J1772)
heeft een spanning van 240 volt" - } - } - ], - "condition": { - "and": [ - "socket:type1_cable~*", - "socket:type1_cable!=0" - ] - } - }, - { - "id": "current-3", - "question": { - "en": "What current do the plugs with
Type 1 with cable (J1772)
offer?", - "nl": "Welke stroom levert de stekker van type
Type 1 met kabel (J1772)
?" - }, - "render": { - "en": "
Type 1 with cable (J1772)
outputs at most {socket:type1_cable:current}A", - "nl": "
Type 1 met kabel (J1772)
levert een stroom van maximaal {socket:type1_cable:current}A" - }, - "freeform": { - "key": "socket:type1_cable:current", - "type": "pfloat" - }, - "mappings": [ - { - "if": "socket:socket:type1_cable:current=32 A", - "then": { - "en": "
Type 1 with cable (J1772)
outputs at most 32 A", - "nl": "
Type 1 met kabel (J1772)
levert een stroom van maximaal 32 A" - } - } - ], - "condition": { - "and": [ - "socket:type1_cable~*", - "socket:type1_cable!=0" - ] - } - }, - { - "id": "power-output-3", - "question": { - "en": "What power output does a single plug of type
Type 1 with cable (J1772)
offer?", - "nl": "Welk vermogen levert een enkele stekker van type
Type 1 met kabel (J1772)
?" - }, - "render": { - "en": "
Type 1 with cable (J1772)
outputs at most {socket:type1_cable:output}", - "nl": "
Type 1 met kabel (J1772)
levert een vermogen van maximaal {socket:type1_cable:output}" - }, - "freeform": { - "key": "socket:type1_cable:output", - "type": "pfloat" - }, - "mappings": [ - { - "if": "socket:socket:type1_cable:output=3.7 kw", - "then": { - "en": "
Type 1 with cable (J1772)
outputs at most 3.7 kw", - "nl": "
Type 1 met kabel (J1772)
levert een vermogen van maximaal 3.7 kw" - } - }, - { - "if": "socket:socket:type1_cable:output=7 kw", - "then": { - "en": "
Type 1 with cable (J1772)
outputs at most 7 kw", - "nl": "
Type 1 met kabel (J1772)
levert een vermogen van maximaal 7 kw" - } - } - ], - "condition": { - "and": [ - "socket:type1_cable~*", - "socket:type1_cable!=0" - ] - } - }, - { - "id": "plugs-4", - "question": { - "en": "How much plugs of type
Type 1 without cable (J1772)
are available here?", - "nl": "Hoeveel stekkers van type
Type 1 zonder kabel (J1772)
heeft dit oplaadpunt?" - }, - "render": { - "en": "There are {socket:type1} plugs of type
Type 1 without cable (J1772)
available here", - "nl": "Hier zijn {socket:type1} stekkers van het type
Type 1 zonder kabel (J1772)
" - }, - "freeform": { - "key": "socket:type1", - "type": "pnat" - }, - "condition": { - "and": [ - "socket:type1~*", - "socket:type1!=0" - ] - } - }, - { - "id": "voltage-4", - "question": { - "en": "What voltage do the plugs with
Type 1 without cable (J1772)
offer?", - "nl": "Welke spanning levert de stekker van type
Type 1 zonder kabel (J1772)
" - }, - "render": { - "en": "
Type 1 without cable (J1772)
outputs {socket:type1:voltage} volt", - "nl": "
Type 1 zonder kabel (J1772)
heeft een spanning van {socket:type1:voltage} volt" - }, - "freeform": { - "key": "socket:type1:voltage", - "type": "pfloat" - }, - "mappings": [ - { - "if": "socket:socket:type1:voltage=200 V", - "then": { - "en": "
Type 1 without cable (J1772)
outputs 200 volt", - "nl": "
Type 1 zonder kabel (J1772)
heeft een spanning van 200 volt" - } - }, - { - "if": "socket:socket:type1:voltage=240 V", - "then": { - "en": "
Type 1 without cable (J1772)
outputs 240 volt", - "nl": "
Type 1 zonder kabel (J1772)
heeft een spanning van 240 volt" - } - } - ], - "condition": { - "and": [ - "socket:type1~*", - "socket:type1!=0" - ] - } - }, - { - "id": "current-4", - "question": { - "en": "What current do the plugs with
Type 1 without cable (J1772)
offer?", - "nl": "Welke stroom levert de stekker van type
Type 1 zonder kabel (J1772)
?" - }, - "render": { - "en": "
Type 1 without cable (J1772)
outputs at most {socket:type1:current}A", - "nl": "
Type 1 zonder kabel (J1772)
levert een stroom van maximaal {socket:type1:current}A" - }, - "freeform": { - "key": "socket:type1:current", - "type": "pfloat" - }, - "mappings": [ - { - "if": "socket:socket:type1:current=32 A", - "then": { - "en": "
Type 1 without cable (J1772)
outputs at most 32 A", - "nl": "
Type 1 zonder kabel (J1772)
levert een stroom van maximaal 32 A" - } - } - ], - "condition": { - "and": [ - "socket:type1~*", - "socket:type1!=0" - ] - } - }, - { - "id": "power-output-4", - "question": { - "en": "What power output does a single plug of type
Type 1 without cable (J1772)
offer?", - "nl": "Welk vermogen levert een enkele stekker van type
Type 1 zonder kabel (J1772)
?" - }, - "render": { - "en": "
Type 1 without cable (J1772)
outputs at most {socket:type1:output}", - "nl": "
Type 1 zonder kabel (J1772)
levert een vermogen van maximaal {socket:type1:output}" - }, - "freeform": { - "key": "socket:type1:output", - "type": "pfloat" - }, - "mappings": [ - { - "if": "socket:socket:type1:output=3.7 kw", - "then": { - "en": "
Type 1 without cable (J1772)
outputs at most 3.7 kw", - "nl": "
Type 1 zonder kabel (J1772)
levert een vermogen van maximaal 3.7 kw" - } - }, - { - "if": "socket:socket:type1:output=6.6 kw", - "then": { - "en": "
Type 1 without cable (J1772)
outputs at most 6.6 kw", - "nl": "
Type 1 zonder kabel (J1772)
levert een vermogen van maximaal 6.6 kw" - } - }, - { - "if": "socket:socket:type1:output=7 kw", - "then": { - "en": "
Type 1 without cable (J1772)
outputs at most 7 kw", - "nl": "
Type 1 zonder kabel (J1772)
levert een vermogen van maximaal 7 kw" - } - }, - { - "if": "socket:socket:type1:output=7.2 kw", - "then": { - "en": "
Type 1 without cable (J1772)
outputs at most 7.2 kw", - "nl": "
Type 1 zonder kabel (J1772)
levert een vermogen van maximaal 7.2 kw" - } - } - ], - "condition": { - "and": [ - "socket:type1~*", - "socket:type1!=0" - ] - } - }, - { - "id": "plugs-5", - "question": { - "en": "How much plugs of type
Type 1 CCS (aka Type 1 Combo)
are available here?", - "nl": "Hoeveel stekkers van type
Type 1 CCS (ook gekend als Type 1 Combo)
heeft dit oplaadpunt?" - }, - "render": { - "en": "There are {socket:type1_combo} plugs of type
Type 1 CCS (aka Type 1 Combo)
available here", - "nl": "Hier zijn {socket:type1_combo} stekkers van het type
Type 1 CCS (ook gekend als Type 1 Combo)
" - }, - "freeform": { - "key": "socket:type1_combo", - "type": "pnat" - }, - "condition": { - "and": [ - "socket:type1_combo~*", - "socket:type1_combo!=0" - ] - } - }, - { - "id": "voltage-5", - "question": { - "en": "What voltage do the plugs with
Type 1 CCS (aka Type 1 Combo)
offer?", - "nl": "Welke spanning levert de stekker van type
Type 1 CCS (ook gekend als Type 1 Combo)
" - }, - "render": { - "en": "
Type 1 CCS (aka Type 1 Combo)
outputs {socket:type1_combo:voltage} volt", - "nl": "
Type 1 CCS (ook gekend als Type 1 Combo)
heeft een spanning van {socket:type1_combo:voltage} volt" - }, - "freeform": { - "key": "socket:type1_combo:voltage", - "type": "pfloat" - }, - "mappings": [ - { - "if": "socket:socket:type1_combo:voltage=400 V", - "then": { - "en": "
Type 1 CCS (aka Type 1 Combo)
outputs 400 volt", - "nl": "
Type 1 CCS (ook gekend als Type 1 Combo)
heeft een spanning van 400 volt" - } - }, - { - "if": "socket:socket:type1_combo:voltage=1000 V", - "then": { - "en": "
Type 1 CCS (aka Type 1 Combo)
outputs 1000 volt", - "nl": "
Type 1 CCS (ook gekend als Type 1 Combo)
heeft een spanning van 1000 volt" - } - } - ], - "condition": { - "and": [ - "socket:type1_combo~*", - "socket:type1_combo!=0" - ] - } - }, - { - "id": "current-5", - "question": { - "en": "What current do the plugs with
Type 1 CCS (aka Type 1 Combo)
offer?", - "nl": "Welke stroom levert de stekker van type
Type 1 CCS (ook gekend als Type 1 Combo)
?" - }, - "render": { - "en": "
Type 1 CCS (aka Type 1 Combo)
outputs at most {socket:type1_combo:current}A", - "nl": "
Type 1 CCS (ook gekend als Type 1 Combo)
levert een stroom van maximaal {socket:type1_combo:current}A" - }, - "freeform": { - "key": "socket:type1_combo:current", - "type": "pfloat" - }, - "mappings": [ - { - "if": "socket:socket:type1_combo:current=50 A", - "then": { - "en": "
Type 1 CCS (aka Type 1 Combo)
outputs at most 50 A", - "nl": "
Type 1 CCS (ook gekend als Type 1 Combo)
levert een stroom van maximaal 50 A" - } - }, - { - "if": "socket:socket:type1_combo:current=125 A", - "then": { - "en": "
Type 1 CCS (aka Type 1 Combo)
outputs at most 125 A", - "nl": "
Type 1 CCS (ook gekend als Type 1 Combo)
levert een stroom van maximaal 125 A" - } - } - ], - "condition": { - "and": [ - "socket:type1_combo~*", - "socket:type1_combo!=0" - ] - } - }, - { - "id": "power-output-5", - "question": { - "en": "What power output does a single plug of type
Type 1 CCS (aka Type 1 Combo)
offer?", - "nl": "Welk vermogen levert een enkele stekker van type
Type 1 CCS (ook gekend als Type 1 Combo)
?" - }, - "render": { - "en": "
Type 1 CCS (aka Type 1 Combo)
outputs at most {socket:type1_combo:output}", - "nl": "
Type 1 CCS (ook gekend als Type 1 Combo)
levert een vermogen van maximaal {socket:type1_combo:output}" - }, - "freeform": { - "key": "socket:type1_combo:output", - "type": "pfloat" - }, - "mappings": [ - { - "if": "socket:socket:type1_combo:output=50 kw", - "then": { - "en": "
Type 1 CCS (aka Type 1 Combo)
outputs at most 50 kw", - "nl": "
Type 1 CCS (ook gekend als Type 1 Combo)
levert een vermogen van maximaal 50 kw" - } - }, - { - "if": "socket:socket:type1_combo:output=62.5 kw", - "then": { - "en": "
Type 1 CCS (aka Type 1 Combo)
outputs at most 62.5 kw", - "nl": "
Type 1 CCS (ook gekend als Type 1 Combo)
levert een vermogen van maximaal 62.5 kw" - } - }, - { - "if": "socket:socket:type1_combo:output=150 kw", - "then": { - "en": "
Type 1 CCS (aka Type 1 Combo)
outputs at most 150 kw", - "nl": "
Type 1 CCS (ook gekend als Type 1 Combo)
levert een vermogen van maximaal 150 kw" - } - }, - { - "if": "socket:socket:type1_combo:output=350 kw", - "then": { - "en": "
Type 1 CCS (aka Type 1 Combo)
outputs at most 350 kw", - "nl": "
Type 1 CCS (ook gekend als Type 1 Combo)
levert een vermogen van maximaal 350 kw" - } - } - ], - "condition": { - "and": [ - "socket:type1_combo~*", - "socket:type1_combo!=0" - ] - } - }, - { - "id": "plugs-6", - "question": { - "en": "How much plugs of type
Tesla Supercharger
are available here?", - "nl": "Hoeveel stekkers van type
Tesla Supercharger
heeft dit oplaadpunt?" - }, - "render": { - "en": "There are {socket:tesla_supercharger} plugs of type
Tesla Supercharger
available here", - "nl": "Hier zijn {socket:tesla_supercharger} stekkers van het type
Tesla Supercharger
" - }, - "freeform": { - "key": "socket:tesla_supercharger", - "type": "pnat" - }, - "condition": { - "and": [ - "socket:tesla_supercharger~*", - "socket:tesla_supercharger!=0" - ] - } - }, - { - "id": "voltage-6", - "question": { - "en": "What voltage do the plugs with
Tesla Supercharger
offer?", - "nl": "Welke spanning levert de stekker van type
Tesla Supercharger
" - }, - "render": { - "en": "
Tesla Supercharger
outputs {socket:tesla_supercharger:voltage} volt", - "nl": "
Tesla Supercharger
heeft een spanning van {socket:tesla_supercharger:voltage} volt" - }, - "freeform": { - "key": "socket:tesla_supercharger:voltage", - "type": "pfloat" - }, - "mappings": [ - { - "if": "socket:socket:tesla_supercharger:voltage=480 V", - "then": { - "en": "
Tesla Supercharger
outputs 480 volt", - "nl": "
Tesla Supercharger
heeft een spanning van 480 volt" - } - } - ], - "condition": { - "and": [ - "socket:tesla_supercharger~*", - "socket:tesla_supercharger!=0" - ] - } - }, - { - "id": "current-6", - "question": { - "en": "What current do the plugs with
Tesla Supercharger
offer?", - "nl": "Welke stroom levert de stekker van type
Tesla Supercharger
?" - }, - "render": { - "en": "
Tesla Supercharger
outputs at most {socket:tesla_supercharger:current}A", - "nl": "
Tesla Supercharger
levert een stroom van maximaal {socket:tesla_supercharger:current}A" - }, - "freeform": { - "key": "socket:tesla_supercharger:current", - "type": "pfloat" - }, - "mappings": [ - { - "if": "socket:socket:tesla_supercharger:current=125 A", - "then": { - "en": "
Tesla Supercharger
outputs at most 125 A", - "nl": "
Tesla Supercharger
levert een stroom van maximaal 125 A" - } - }, - { - "if": "socket:socket:tesla_supercharger:current=350 A", - "then": { - "en": "
Tesla Supercharger
outputs at most 350 A", - "nl": "
Tesla Supercharger
levert een stroom van maximaal 350 A" - } - } - ], - "condition": { - "and": [ - "socket:tesla_supercharger~*", - "socket:tesla_supercharger!=0" - ] - } - }, - { - "id": "power-output-6", - "question": { - "en": "What power output does a single plug of type
Tesla Supercharger
offer?", - "nl": "Welk vermogen levert een enkele stekker van type
Tesla Supercharger
?" - }, - "render": { - "en": "
Tesla Supercharger
outputs at most {socket:tesla_supercharger:output}", - "nl": "
Tesla Supercharger
levert een vermogen van maximaal {socket:tesla_supercharger:output}" - }, - "freeform": { - "key": "socket:tesla_supercharger:output", - "type": "pfloat" - }, - "mappings": [ - { - "if": "socket:socket:tesla_supercharger:output=120 kw", - "then": { - "en": "
Tesla Supercharger
outputs at most 120 kw", - "nl": "
Tesla Supercharger
levert een vermogen van maximaal 120 kw" - } - }, - { - "if": "socket:socket:tesla_supercharger:output=150 kw", - "then": { - "en": "
Tesla Supercharger
outputs at most 150 kw", - "nl": "
Tesla Supercharger
levert een vermogen van maximaal 150 kw" - } - }, - { - "if": "socket:socket:tesla_supercharger:output=250 kw", - "then": { - "en": "
Tesla Supercharger
outputs at most 250 kw", - "nl": "
Tesla Supercharger
levert een vermogen van maximaal 250 kw" - } - } - ], - "condition": { - "and": [ - "socket:tesla_supercharger~*", - "socket:tesla_supercharger!=0" - ] - } - }, - { - "id": "plugs-7", - "question": { - "en": "How much plugs of type
Type 2 (mennekes)
are available here?", - "nl": "Hoeveel stekkers van type
Type 2 (mennekes)
heeft dit oplaadpunt?" - }, - "render": { - "en": "There are {socket:type2} plugs of type
Type 2 (mennekes)
available here", - "nl": "Hier zijn {socket:type2} stekkers van het type
Type 2 (mennekes)
" - }, - "freeform": { - "key": "socket:type2", - "type": "pnat" - }, - "condition": { - "and": [ - "socket:type2~*", - "socket:type2!=0" - ] - } - }, - { - "id": "voltage-7", - "question": { - "en": "What voltage do the plugs with
Type 2 (mennekes)
offer?", - "nl": "Welke spanning levert de stekker van type
Type 2 (mennekes)
" - }, - "render": { - "en": "
Type 2 (mennekes)
outputs {socket:type2:voltage} volt", - "nl": "
Type 2 (mennekes)
heeft een spanning van {socket:type2:voltage} volt" - }, - "freeform": { - "key": "socket:type2:voltage", - "type": "pfloat" - }, - "mappings": [ - { - "if": "socket:socket:type2:voltage=230 V", - "then": { - "en": "
Type 2 (mennekes)
outputs 230 volt", - "nl": "
Type 2 (mennekes)
heeft een spanning van 230 volt" - } - }, - { - "if": "socket:socket:type2:voltage=400 V", - "then": { - "en": "
Type 2 (mennekes)
outputs 400 volt", - "nl": "
Type 2 (mennekes)
heeft een spanning van 400 volt" - } - } - ], - "condition": { - "and": [ - "socket:type2~*", - "socket:type2!=0" - ] - } - }, - { - "id": "current-7", - "question": { - "en": "What current do the plugs with
Type 2 (mennekes)
offer?", - "nl": "Welke stroom levert de stekker van type
Type 2 (mennekes)
?" - }, - "render": { - "en": "
Type 2 (mennekes)
outputs at most {socket:type2:current}A", - "nl": "
Type 2 (mennekes)
levert een stroom van maximaal {socket:type2:current}A" - }, - "freeform": { - "key": "socket:type2:current", - "type": "pfloat" - }, - "mappings": [ - { - "if": "socket:socket:type2:current=16 A", - "then": { - "en": "
Type 2 (mennekes)
outputs at most 16 A", - "nl": "
Type 2 (mennekes)
levert een stroom van maximaal 16 A" - } - }, - { - "if": "socket:socket:type2:current=32 A", - "then": { - "en": "
Type 2 (mennekes)
outputs at most 32 A", - "nl": "
Type 2 (mennekes)
levert een stroom van maximaal 32 A" - } - } - ], - "condition": { - "and": [ - "socket:type2~*", - "socket:type2!=0" - ] - } - }, - { - "id": "power-output-7", - "question": { - "en": "What power output does a single plug of type
Type 2 (mennekes)
offer?", - "nl": "Welk vermogen levert een enkele stekker van type
Type 2 (mennekes)
?" - }, - "render": { - "en": "
Type 2 (mennekes)
outputs at most {socket:type2:output}", - "nl": "
Type 2 (mennekes)
levert een vermogen van maximaal {socket:type2:output}" - }, - "freeform": { - "key": "socket:type2:output", - "type": "pfloat" - }, - "mappings": [ - { - "if": "socket:socket:type2:output=11 kw", - "then": { - "en": "
Type 2 (mennekes)
outputs at most 11 kw", - "nl": "
Type 2 (mennekes)
levert een vermogen van maximaal 11 kw" - } - }, - { - "if": "socket:socket:type2:output=22 kw", - "then": { - "en": "
Type 2 (mennekes)
outputs at most 22 kw", - "nl": "
Type 2 (mennekes)
levert een vermogen van maximaal 22 kw" - } - } - ], - "condition": { - "and": [ - "socket:type2~*", - "socket:type2!=0" - ] - } - }, - { - "id": "plugs-8", - "question": { - "en": "How much plugs of type
Type 2 CCS (mennekes)
are available here?", - "nl": "Hoeveel stekkers van type
Type 2 CCS (mennekes)
heeft dit oplaadpunt?" - }, - "render": { - "en": "There are {socket:type2_combo} plugs of type
Type 2 CCS (mennekes)
available here", - "nl": "Hier zijn {socket:type2_combo} stekkers van het type
Type 2 CCS (mennekes)
" - }, - "freeform": { - "key": "socket:type2_combo", - "type": "pnat" - }, - "condition": { - "and": [ - "socket:type2_combo~*", - "socket:type2_combo!=0" - ] - } - }, - { - "id": "voltage-8", - "question": { - "en": "What voltage do the plugs with
Type 2 CCS (mennekes)
offer?", - "nl": "Welke spanning levert de stekker van type
Type 2 CCS (mennekes)
" - }, - "render": { - "en": "
Type 2 CCS (mennekes)
outputs {socket:type2_combo:voltage} volt", - "nl": "
Type 2 CCS (mennekes)
heeft een spanning van {socket:type2_combo:voltage} volt" - }, - "freeform": { - "key": "socket:type2_combo:voltage", - "type": "pfloat" - }, - "mappings": [ - { - "if": "socket:socket:type2_combo:voltage=500 V", - "then": { - "en": "
Type 2 CCS (mennekes)
outputs 500 volt", - "nl": "
Type 2 CCS (mennekes)
heeft een spanning van 500 volt" - } - }, - { - "if": "socket:socket:type2_combo:voltage=920 V", - "then": { - "en": "
Type 2 CCS (mennekes)
outputs 920 volt", - "nl": "
Type 2 CCS (mennekes)
heeft een spanning van 920 volt" - } - } - ], - "condition": { - "and": [ - "socket:type2_combo~*", - "socket:type2_combo!=0" - ] - } - }, - { - "id": "current-8", - "question": { - "en": "What current do the plugs with
Type 2 CCS (mennekes)
offer?", - "nl": "Welke stroom levert de stekker van type
Type 2 CCS (mennekes)
?" - }, - "render": { - "en": "
Type 2 CCS (mennekes)
outputs at most {socket:type2_combo:current}A", - "nl": "
Type 2 CCS (mennekes)
levert een stroom van maximaal {socket:type2_combo:current}A" - }, - "freeform": { - "key": "socket:type2_combo:current", - "type": "pfloat" - }, - "mappings": [ - { - "if": "socket:socket:type2_combo:current=125 A", - "then": { - "en": "
Type 2 CCS (mennekes)
outputs at most 125 A", - "nl": "
Type 2 CCS (mennekes)
levert een stroom van maximaal 125 A" - } - }, - { - "if": "socket:socket:type2_combo:current=350 A", - "then": { - "en": "
Type 2 CCS (mennekes)
outputs at most 350 A", - "nl": "
Type 2 CCS (mennekes)
levert een stroom van maximaal 350 A" - } - } - ], - "condition": { - "and": [ - "socket:type2_combo~*", - "socket:type2_combo!=0" - ] - } - }, - { - "id": "power-output-8", - "question": { - "en": "What power output does a single plug of type
Type 2 CCS (mennekes)
offer?", - "nl": "Welk vermogen levert een enkele stekker van type
Type 2 CCS (mennekes)
?" - }, - "render": { - "en": "
Type 2 CCS (mennekes)
outputs at most {socket:type2_combo:output}", - "nl": "
Type 2 CCS (mennekes)
levert een vermogen van maximaal {socket:type2_combo:output}" - }, - "freeform": { - "key": "socket:type2_combo:output", - "type": "pfloat" - }, - "mappings": [ - { - "if": "socket:socket:type2_combo:output=50 kw", - "then": { - "en": "
Type 2 CCS (mennekes)
outputs at most 50 kw", - "nl": "
Type 2 CCS (mennekes)
levert een vermogen van maximaal 50 kw" - } - } - ], - "condition": { - "and": [ - "socket:type2_combo~*", - "socket:type2_combo!=0" - ] - } - }, - { - "id": "plugs-9", - "question": { - "en": "How much plugs of type
Type 2 with cable (mennekes)
are available here?", - "nl": "Hoeveel stekkers van type
Type 2 met kabel (J1772)
heeft dit oplaadpunt?" - }, - "render": { - "en": "There are {socket:type2_cable} plugs of type
Type 2 with cable (mennekes)
available here", - "nl": "Hier zijn {socket:type2_cable} stekkers van het type
Type 2 met kabel (J1772)
" - }, - "freeform": { - "key": "socket:type2_cable", - "type": "pnat" - }, - "condition": { - "and": [ - "socket:type2_cable~*", - "socket:type2_cable!=0" - ] - } - }, - { - "id": "voltage-9", - "question": { - "en": "What voltage do the plugs with
Type 2 with cable (mennekes)
offer?", - "nl": "Welke spanning levert de stekker van type
Type 2 met kabel (J1772)
" - }, - "render": { - "en": "
Type 2 with cable (mennekes)
outputs {socket:type2_cable:voltage} volt", - "nl": "
Type 2 met kabel (J1772)
heeft een spanning van {socket:type2_cable:voltage} volt" - }, - "freeform": { - "key": "socket:type2_cable:voltage", - "type": "pfloat" - }, - "mappings": [ - { - "if": "socket:socket:type2_cable:voltage=230 V", - "then": { - "en": "
Type 2 with cable (mennekes)
outputs 230 volt", - "nl": "
Type 2 met kabel (J1772)
heeft een spanning van 230 volt" - } - }, - { - "if": "socket:socket:type2_cable:voltage=400 V", - "then": { - "en": "
Type 2 with cable (mennekes)
outputs 400 volt", - "nl": "
Type 2 met kabel (J1772)
heeft een spanning van 400 volt" - } - } - ], - "condition": { - "and": [ - "socket:type2_cable~*", - "socket:type2_cable!=0" - ] - } - }, - { - "id": "current-9", - "question": { - "en": "What current do the plugs with
Type 2 with cable (mennekes)
offer?", - "nl": "Welke stroom levert de stekker van type
Type 2 met kabel (J1772)
?" - }, - "render": { - "en": "
Type 2 with cable (mennekes)
outputs at most {socket:type2_cable:current}A", - "nl": "
Type 2 met kabel (J1772)
levert een stroom van maximaal {socket:type2_cable:current}A" - }, - "freeform": { - "key": "socket:type2_cable:current", - "type": "pfloat" - }, - "mappings": [ - { - "if": "socket:socket:type2_cable:current=16 A", - "then": { - "en": "
Type 2 with cable (mennekes)
outputs at most 16 A", - "nl": "
Type 2 met kabel (J1772)
levert een stroom van maximaal 16 A" - } - }, - { - "if": "socket:socket:type2_cable:current=32 A", - "then": { - "en": "
Type 2 with cable (mennekes)
outputs at most 32 A", - "nl": "
Type 2 met kabel (J1772)
levert een stroom van maximaal 32 A" - } - } - ], - "condition": { - "and": [ - "socket:type2_cable~*", - "socket:type2_cable!=0" - ] - } - }, - { - "id": "power-output-9", - "question": { - "en": "What power output does a single plug of type
Type 2 with cable (mennekes)
offer?", - "nl": "Welk vermogen levert een enkele stekker van type
Type 2 met kabel (J1772)
?" - }, - "render": { - "en": "
Type 2 with cable (mennekes)
outputs at most {socket:type2_cable:output}", - "nl": "
Type 2 met kabel (J1772)
levert een vermogen van maximaal {socket:type2_cable:output}" - }, - "freeform": { - "key": "socket:type2_cable:output", - "type": "pfloat" - }, - "mappings": [ - { - "if": "socket:socket:type2_cable:output=11 kw", - "then": { - "en": "
Type 2 with cable (mennekes)
outputs at most 11 kw", - "nl": "
Type 2 met kabel (J1772)
levert een vermogen van maximaal 11 kw" - } - }, - { - "if": "socket:socket:type2_cable:output=22 kw", - "then": { - "en": "
Type 2 with cable (mennekes)
outputs at most 22 kw", - "nl": "
Type 2 met kabel (J1772)
levert een vermogen van maximaal 22 kw" - } - } - ], - "condition": { - "and": [ - "socket:type2_cable~*", - "socket:type2_cable!=0" - ] - } - }, - { - "id": "plugs-10", - "question": { - "en": "How much plugs of type
Tesla Supercharger CCS (a branded type2_css)
are available here?", - "nl": "Hoeveel stekkers van type
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
heeft dit oplaadpunt?" - }, - "render": { - "en": "There are {socket:tesla_supercharger_ccs} plugs of type
Tesla Supercharger CCS (a branded type2_css)
available here", - "nl": "Hier zijn {socket:tesla_supercharger_ccs} stekkers van het type
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
" - }, - "freeform": { - "key": "socket:tesla_supercharger_ccs", - "type": "pnat" - }, - "condition": { - "and": [ - "socket:tesla_supercharger_ccs~*", - "socket:tesla_supercharger_ccs!=0" - ] - } - }, - { - "id": "voltage-10", - "question": { - "en": "What voltage do the plugs with
Tesla Supercharger CCS (a branded type2_css)
offer?", - "nl": "Welke spanning levert de stekker van type
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
" - }, - "render": { - "en": "
Tesla Supercharger CCS (a branded type2_css)
outputs {socket:tesla_supercharger_ccs:voltage} volt", - "nl": "
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
heeft een spanning van {socket:tesla_supercharger_ccs:voltage} volt" - }, - "freeform": { - "key": "socket:tesla_supercharger_ccs:voltage", - "type": "pfloat" - }, - "mappings": [ - { - "if": "socket:socket:tesla_supercharger_ccs:voltage=500 V", - "then": { - "en": "
Tesla Supercharger CCS (a branded type2_css)
outputs 500 volt", - "nl": "
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
heeft een spanning van 500 volt" - } - }, - { - "if": "socket:socket:tesla_supercharger_ccs:voltage=920 V", - "then": { - "en": "
Tesla Supercharger CCS (a branded type2_css)
outputs 920 volt", - "nl": "
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
heeft een spanning van 920 volt" - } - } - ], - "condition": { - "and": [ - "socket:tesla_supercharger_ccs~*", - "socket:tesla_supercharger_ccs!=0" - ] - } - }, - { - "id": "current-10", - "question": { - "en": "What current do the plugs with
Tesla Supercharger CCS (a branded type2_css)
offer?", - "nl": "Welke stroom levert de stekker van type
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
?" - }, - "render": { - "en": "
Tesla Supercharger CCS (a branded type2_css)
outputs at most {socket:tesla_supercharger_ccs:current}A", - "nl": "
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
levert een stroom van maximaal {socket:tesla_supercharger_ccs:current}A" - }, - "freeform": { - "key": "socket:tesla_supercharger_ccs:current", - "type": "pfloat" - }, - "mappings": [ - { - "if": "socket:socket:tesla_supercharger_ccs:current=125 A", - "then": { - "en": "
Tesla Supercharger CCS (a branded type2_css)
outputs at most 125 A", - "nl": "
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
levert een stroom van maximaal 125 A" - } - }, - { - "if": "socket:socket:tesla_supercharger_ccs:current=350 A", - "then": { - "en": "
Tesla Supercharger CCS (a branded type2_css)
outputs at most 350 A", - "nl": "
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
levert een stroom van maximaal 350 A" - } - } - ], - "condition": { - "and": [ - "socket:tesla_supercharger_ccs~*", - "socket:tesla_supercharger_ccs!=0" - ] - } - }, - { - "id": "power-output-10", - "question": { - "en": "What power output does a single plug of type
Tesla Supercharger CCS (a branded type2_css)
offer?", - "nl": "Welk vermogen levert een enkele stekker van type
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
?" - }, - "render": { - "en": "
Tesla Supercharger CCS (a branded type2_css)
outputs at most {socket:tesla_supercharger_ccs:output}", - "nl": "
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
levert een vermogen van maximaal {socket:tesla_supercharger_ccs:output}" - }, - "freeform": { - "key": "socket:tesla_supercharger_ccs:output", - "type": "pfloat" - }, - "mappings": [ - { - "if": "socket:socket:tesla_supercharger_ccs:output=50 kw", - "then": { - "en": "
Tesla Supercharger CCS (a branded type2_css)
outputs at most 50 kw", - "nl": "
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
levert een vermogen van maximaal 50 kw" - } - } - ], - "condition": { - "and": [ - "socket:tesla_supercharger_ccs~*", - "socket:tesla_supercharger_ccs!=0" - ] - } - }, - { - "id": "plugs-11", - "question": { - "en": "How much plugs of type
Tesla Supercharger (destination)
are available here?", - "nl": "Hoeveel stekkers van type
Tesla Supercharger (destination)
heeft dit oplaadpunt?" - }, - "render": { - "en": "There are {socket:tesla_destination} plugs of type
Tesla Supercharger (destination)
available here", - "nl": "Hier zijn {socket:tesla_destination} stekkers van het type
Tesla Supercharger (destination)
" - }, - "freeform": { - "key": "socket:tesla_destination", - "type": "pnat" - }, - "condition": { - "and": [ - "socket:tesla_destination~*", - "socket:tesla_destination!=0" - ] - } - }, - { - "id": "voltage-11", - "question": { - "en": "What voltage do the plugs with
Tesla Supercharger (destination)
offer?", - "nl": "Welke spanning levert de stekker van type
Tesla Supercharger (destination)
" - }, - "render": { - "en": "
Tesla Supercharger (destination)
outputs {socket:tesla_destination:voltage} volt", - "nl": "
Tesla Supercharger (destination)
heeft een spanning van {socket:tesla_destination:voltage} volt" - }, - "freeform": { - "key": "socket:tesla_destination:voltage", - "type": "pfloat" - }, - "mappings": [ - { - "if": "socket:socket:tesla_destination:voltage=480 V", - "then": { - "en": "
Tesla Supercharger (destination)
outputs 480 volt", - "nl": "
Tesla Supercharger (destination)
heeft een spanning van 480 volt" - } - } - ], - "condition": { - "and": [ - "socket:tesla_destination~*", - "socket:tesla_destination!=0" - ] - } - }, - { - "id": "current-11", - "question": { - "en": "What current do the plugs with
Tesla Supercharger (destination)
offer?", - "nl": "Welke stroom levert de stekker van type
Tesla Supercharger (destination)
?" - }, - "render": { - "en": "
Tesla Supercharger (destination)
outputs at most {socket:tesla_destination:current}A", - "nl": "
Tesla Supercharger (destination)
levert een stroom van maximaal {socket:tesla_destination:current}A" - }, - "freeform": { - "key": "socket:tesla_destination:current", - "type": "pfloat" - }, - "mappings": [ - { - "if": "socket:socket:tesla_destination:current=125 A", - "then": { - "en": "
Tesla Supercharger (destination)
outputs at most 125 A", - "nl": "
Tesla Supercharger (destination)
levert een stroom van maximaal 125 A" - } - }, - { - "if": "socket:socket:tesla_destination:current=350 A", - "then": { - "en": "
Tesla Supercharger (destination)
outputs at most 350 A", - "nl": "
Tesla Supercharger (destination)
levert een stroom van maximaal 350 A" - } - } - ], - "condition": { - "and": [ - "socket:tesla_destination~*", - "socket:tesla_destination!=0" - ] - } - }, - { - "id": "power-output-11", - "question": { - "en": "What power output does a single plug of type
Tesla Supercharger (destination)
offer?", - "nl": "Welk vermogen levert een enkele stekker van type
Tesla Supercharger (destination)
?" - }, - "render": { - "en": "
Tesla Supercharger (destination)
outputs at most {socket:tesla_destination:output}", - "nl": "
Tesla Supercharger (destination)
levert een vermogen van maximaal {socket:tesla_destination:output}" - }, - "freeform": { - "key": "socket:tesla_destination:output", - "type": "pfloat" - }, - "mappings": [ - { - "if": "socket:socket:tesla_destination:output=120 kw", - "then": { - "en": "
Tesla Supercharger (destination)
outputs at most 120 kw", - "nl": "
Tesla Supercharger (destination)
levert een vermogen van maximaal 120 kw" - } - }, - { - "if": "socket:socket:tesla_destination:output=150 kw", - "then": { - "en": "
Tesla Supercharger (destination)
outputs at most 150 kw", - "nl": "
Tesla Supercharger (destination)
levert een vermogen van maximaal 150 kw" - } - }, - { - "if": "socket:socket:tesla_destination:output=250 kw", - "then": { - "en": "
Tesla Supercharger (destination)
outputs at most 250 kw", - "nl": "
Tesla Supercharger (destination)
levert een vermogen van maximaal 250 kw" - } - } - ], - "condition": { - "and": [ - "socket:tesla_destination~*", - "socket:tesla_destination!=0" - ] - } - }, - { - "id": "plugs-12", - "question": { - "en": "How much plugs of type
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
are available here?", - "nl": "Hoeveel stekkers van type
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
heeft dit oplaadpunt?" - }, - "render": { - "en": "There are {socket:tesla_destination} plugs of type
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
available here", - "nl": "Hier zijn {socket:tesla_destination} stekkers van het type
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
" - }, - "freeform": { - "key": "socket:tesla_destination", - "type": "pnat" - }, - "condition": { - "and": [ - "socket:tesla_destination~*", - "socket:tesla_destination!=0" - ] - } - }, - { - "id": "voltage-12", - "question": { - "en": "What voltage do the plugs with
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
offer?", - "nl": "Welke spanning levert de stekker van type
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
" - }, - "render": { - "en": "
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
outputs {socket:tesla_destination:voltage} volt", - "nl": "
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
heeft een spanning van {socket:tesla_destination:voltage} volt" - }, - "freeform": { - "key": "socket:tesla_destination:voltage", - "type": "pfloat" - }, - "mappings": [ - { - "if": "socket:socket:tesla_destination:voltage=230 V", - "then": { - "en": "
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
outputs 230 volt", - "nl": "
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
heeft een spanning van 230 volt" - } - }, - { - "if": "socket:socket:tesla_destination:voltage=400 V", - "then": { - "en": "
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
outputs 400 volt", - "nl": "
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
heeft een spanning van 400 volt" - } - } - ], - "condition": { - "and": [ - "socket:tesla_destination~*", - "socket:tesla_destination!=0" - ] - } - }, - { - "id": "current-12", - "question": { - "en": "What current do the plugs with
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
offer?", - "nl": "Welke stroom levert de stekker van type
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
?" - }, - "render": { - "en": "
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
outputs at most {socket:tesla_destination:current}A", - "nl": "
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
levert een stroom van maximaal {socket:tesla_destination:current}A" - }, - "freeform": { - "key": "socket:tesla_destination:current", - "type": "pfloat" - }, - "mappings": [ - { - "if": "socket:socket:tesla_destination:current=16 A", - "then": { - "en": "
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
outputs at most 16 A", - "nl": "
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
levert een stroom van maximaal 16 A" - } - }, - { - "if": "socket:socket:tesla_destination:current=32 A", - "then": { - "en": "
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
outputs at most 32 A", - "nl": "
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
levert een stroom van maximaal 32 A" - } - } - ], - "condition": { - "and": [ - "socket:tesla_destination~*", - "socket:tesla_destination!=0" - ] - } - }, - { - "id": "power-output-12", - "question": { - "en": "What power output does a single plug of type
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
offer?", - "nl": "Welk vermogen levert een enkele stekker van type
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
?" - }, - "render": { - "en": "
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
outputs at most {socket:tesla_destination:output}", - "nl": "
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
levert een vermogen van maximaal {socket:tesla_destination:output}" - }, - "freeform": { - "key": "socket:tesla_destination:output", - "type": "pfloat" - }, - "mappings": [ - { - "if": "socket:socket:tesla_destination:output=11 kw", - "then": { - "en": "
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
outputs at most 11 kw", - "nl": "
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
levert een vermogen van maximaal 11 kw" - } - }, - { - "if": "socket:socket:tesla_destination:output=22 kw", - "then": { - "en": "
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
outputs at most 22 kw", - "nl": "
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
levert een vermogen van maximaal 22 kw" - } - } - ], - "condition": { - "and": [ - "socket:tesla_destination~*", - "socket:tesla_destination!=0" - ] - } - }, - { - "id": "plugs-13", - "question": { - "en": "How much plugs of type
USB to charge phones and small electronics
are available here?", - "nl": "Hoeveel stekkers van type
USB om GSMs en kleine electronica op te laden
heeft dit oplaadpunt?" - }, - "render": { - "en": "There are {socket:USB-A} plugs of type
USB to charge phones and small electronics
available here", - "nl": "Hier zijn {socket:USB-A} stekkers van het type
USB om GSMs en kleine electronica op te laden
" - }, - "freeform": { - "key": "socket:USB-A", - "type": "pnat" - }, - "condition": { - "and": [ - "socket:USB-A~*", - "socket:USB-A!=0" - ] - } - }, - { - "id": "voltage-13", - "question": { - "en": "What voltage do the plugs with
USB to charge phones and small electronics
offer?", - "nl": "Welke spanning levert de stekker van type
USB om GSMs en kleine electronica op te laden
" - }, - "render": { - "en": "
USB to charge phones and small electronics
outputs {socket:USB-A:voltage} volt", - "nl": "
USB om GSMs en kleine electronica op te laden
heeft een spanning van {socket:USB-A:voltage} volt" - }, - "freeform": { - "key": "socket:USB-A:voltage", - "type": "pfloat" - }, - "mappings": [ - { - "if": "socket:socket:USB-A:voltage=5 V", - "then": { - "en": "
USB to charge phones and small electronics
outputs 5 volt", - "nl": "
USB om GSMs en kleine electronica op te laden
heeft een spanning van 5 volt" - } - } - ], - "condition": { - "and": [ - "socket:USB-A~*", - "socket:USB-A!=0" - ] - } - }, - { - "id": "current-13", - "question": { - "en": "What current do the plugs with
USB to charge phones and small electronics
offer?", - "nl": "Welke stroom levert de stekker van type
USB om GSMs en kleine electronica op te laden
?" - }, - "render": { - "en": "
USB to charge phones and small electronics
outputs at most {socket:USB-A:current}A", - "nl": "
USB om GSMs en kleine electronica op te laden
levert een stroom van maximaal {socket:USB-A:current}A" - }, - "freeform": { - "key": "socket:USB-A:current", - "type": "pfloat" - }, - "mappings": [ - { - "if": "socket:socket:USB-A:current=1 A", - "then": { - "en": "
USB to charge phones and small electronics
outputs at most 1 A", - "nl": "
USB om GSMs en kleine electronica op te laden
levert een stroom van maximaal 1 A" - } - }, - { - "if": "socket:socket:USB-A:current=2 A", - "then": { - "en": "
USB to charge phones and small electronics
outputs at most 2 A", - "nl": "
USB om GSMs en kleine electronica op te laden
levert een stroom van maximaal 2 A" - } - } - ], - "condition": { - "and": [ - "socket:USB-A~*", - "socket:USB-A!=0" - ] - } - }, - { - "id": "power-output-13", - "question": { - "en": "What power output does a single plug of type
USB to charge phones and small electronics
offer?", - "nl": "Welk vermogen levert een enkele stekker van type
USB om GSMs en kleine electronica op te laden
?" - }, - "render": { - "en": "
USB to charge phones and small electronics
outputs at most {socket:USB-A:output}", - "nl": "
USB om GSMs en kleine electronica op te laden
levert een vermogen van maximaal {socket:USB-A:output}" - }, - "freeform": { - "key": "socket:USB-A:output", - "type": "pfloat" - }, - "mappings": [ - { - "if": "socket:socket:USB-A:output=5w", - "then": { - "en": "
USB to charge phones and small electronics
outputs at most 5w", - "nl": "
USB om GSMs en kleine electronica op te laden
levert een vermogen van maximaal 5w" - } - }, - { - "if": "socket:socket:USB-A:output=10w", - "then": { - "en": "
USB to charge phones and small electronics
outputs at most 10w", - "nl": "
USB om GSMs en kleine electronica op te laden
levert een vermogen van maximaal 10w" - } - } - ], - "condition": { - "and": [ - "socket:USB-A~*", - "socket:USB-A!=0" - ] - } - }, - { - "id": "plugs-14", - "question": { - "en": "How much plugs of type
Bosch Active Connect with 3 pins and cable
are available here?", - "nl": "Hoeveel stekkers van type
Bosch Active Connect met 3 pinnen aan een kabel
heeft dit oplaadpunt?" - }, - "render": { - "en": "There are {socket:bosch_3pin} plugs of type
Bosch Active Connect with 3 pins and cable
available here", - "nl": "Hier zijn {socket:bosch_3pin} stekkers van het type
Bosch Active Connect met 3 pinnen aan een kabel
" - }, - "freeform": { - "key": "socket:bosch_3pin", - "type": "pnat" - }, - "condition": { - "and": [ - "socket:bosch_3pin~*", - "socket:bosch_3pin!=0" - ] - } - }, - { - "id": "voltage-14", - "question": { - "en": "What voltage do the plugs with
Bosch Active Connect with 3 pins and cable
offer?", - "nl": "Welke spanning levert de stekker van type
Bosch Active Connect met 3 pinnen aan een kabel
" - }, - "render": { - "en": "
Bosch Active Connect with 3 pins and cable
outputs {socket:bosch_3pin:voltage} volt", - "nl": "
Bosch Active Connect met 3 pinnen aan een kabel
heeft een spanning van {socket:bosch_3pin:voltage} volt" - }, - "freeform": { - "key": "socket:bosch_3pin:voltage", - "type": "pfloat" - }, - "mappings": [], - "condition": { - "and": [ - "socket:bosch_3pin~*", - "socket:bosch_3pin!=0" - ] - } - }, - { - "id": "current-14", - "question": { - "en": "What current do the plugs with
Bosch Active Connect with 3 pins and cable
offer?", - "nl": "Welke stroom levert de stekker van type
Bosch Active Connect met 3 pinnen aan een kabel
?" - }, - "render": { - "en": "
Bosch Active Connect with 3 pins and cable
outputs at most {socket:bosch_3pin:current}A", - "nl": "
Bosch Active Connect met 3 pinnen aan een kabel
levert een stroom van maximaal {socket:bosch_3pin:current}A" - }, - "freeform": { - "key": "socket:bosch_3pin:current", - "type": "pfloat" - }, - "mappings": [], - "condition": { - "and": [ - "socket:bosch_3pin~*", - "socket:bosch_3pin!=0" - ] - } - }, - { - "id": "power-output-14", - "question": { - "en": "What power output does a single plug of type
Bosch Active Connect with 3 pins and cable
offer?", - "nl": "Welk vermogen levert een enkele stekker van type
Bosch Active Connect met 3 pinnen aan een kabel
?" - }, - "render": { - "en": "
Bosch Active Connect with 3 pins and cable
outputs at most {socket:bosch_3pin:output}", - "nl": "
Bosch Active Connect met 3 pinnen aan een kabel
levert een vermogen van maximaal {socket:bosch_3pin:output}" - }, - "freeform": { - "key": "socket:bosch_3pin:output", - "type": "pfloat" - }, - "mappings": [], - "condition": { - "and": [ - "socket:bosch_3pin~*", - "socket:bosch_3pin!=0" - ] - } - }, - { - "id": "plugs-15", - "question": { - "en": "How much plugs of type
Bosch Active Connect with 5 pins and cable
are available here?", - "nl": "Hoeveel stekkers van type
Bosch Active Connect met 5 pinnen aan een kabel
heeft dit oplaadpunt?" - }, - "render": { - "en": "There are {socket:bosch_5pin} plugs of type
Bosch Active Connect with 5 pins and cable
available here", - "nl": "Hier zijn {socket:bosch_5pin} stekkers van het type
Bosch Active Connect met 5 pinnen aan een kabel
" - }, - "freeform": { - "key": "socket:bosch_5pin", - "type": "pnat" - }, - "condition": { - "and": [ - "socket:bosch_5pin~*", - "socket:bosch_5pin!=0" - ] - } - }, - { - "id": "voltage-15", - "question": { - "en": "What voltage do the plugs with
Bosch Active Connect with 5 pins and cable
offer?", - "nl": "Welke spanning levert de stekker van type
Bosch Active Connect met 5 pinnen aan een kabel
" - }, - "render": { - "en": "
Bosch Active Connect with 5 pins and cable
outputs {socket:bosch_5pin:voltage} volt", - "nl": "
Bosch Active Connect met 5 pinnen aan een kabel
heeft een spanning van {socket:bosch_5pin:voltage} volt" - }, - "freeform": { - "key": "socket:bosch_5pin:voltage", - "type": "pfloat" - }, - "mappings": [], - "condition": { - "and": [ - "socket:bosch_5pin~*", - "socket:bosch_5pin!=0" - ] - } - }, - { - "id": "current-15", - "question": { - "en": "What current do the plugs with
Bosch Active Connect with 5 pins and cable
offer?", - "nl": "Welke stroom levert de stekker van type
Bosch Active Connect met 5 pinnen aan een kabel
?" - }, - "render": { - "en": "
Bosch Active Connect with 5 pins and cable
outputs at most {socket:bosch_5pin:current}A", - "nl": "
Bosch Active Connect met 5 pinnen aan een kabel
levert een stroom van maximaal {socket:bosch_5pin:current}A" - }, - "freeform": { - "key": "socket:bosch_5pin:current", - "type": "pfloat" - }, - "mappings": [], - "condition": { - "and": [ - "socket:bosch_5pin~*", - "socket:bosch_5pin!=0" - ] - } - }, - { - "id": "power-output-15", - "question": { - "en": "What power output does a single plug of type
Bosch Active Connect with 5 pins and cable
offer?", - "nl": "Welk vermogen levert een enkele stekker van type
Bosch Active Connect met 5 pinnen aan een kabel
?" - }, - "render": { - "en": "
Bosch Active Connect with 5 pins and cable
outputs at most {socket:bosch_5pin:output}", - "nl": "
Bosch Active Connect met 5 pinnen aan een kabel
levert een vermogen van maximaal {socket:bosch_5pin:output}" - }, - "freeform": { - "key": "socket:bosch_5pin:output", - "type": "pfloat" - }, - "mappings": [], - "condition": { - "and": [ - "socket:bosch_5pin~*", - "socket:bosch_5pin!=0" - ] - } - }, - { - "id": "Authentication", - "question": { - "en": "What kind of authentication is available at the charging station?", - "it": "Quali sono gli orari di apertura di questa stazione di ricarica?", - "ja": "この充電ステーションはいつオープンしますか?", - "nb_NO": "Når åpnet denne ladestasjonen?", - "ru": "В какое время работает эта зарядная станция?", - "zh_Hant": "何時是充電站開放使用的時間?" - }, - "multiAnswer": true, - "mappings": [ - { - "if": "authentication:membership_card=yes", - "ifnot": "authentication:membership_card=no", - "then": { - "en": "Authentication by a membership card" - } - }, - { - "if": "authentication:app=yes", - "ifnot": "authentication:app=no", - "then": { - "en": "Authentication by an app" - } - }, - { - "if": "authentication:phone_call=yes", - "ifnot": "authentication:phone_call=no", - "then": { - "en": "Authentication via phone call is available" - } - }, - { - "if": "authentication:short_message=yes", - "ifnot": "authentication:short_message=no", - "then": { - "en": "Authentication via phone call is available" - } - }, - { - "if": "authentication:nfc=yes", - "ifnot": "authentication:nfc=no", - "then": { - "en": "Authentication via NFC is available" - } - }, - { - "if": "authentication:money_card=yes", - "ifnot": "authentication:money_card=no", - "then": { - "en": "Authentication via Money Card is available" - } - }, - { - "if": "authentication:debit_card=yes", - "ifnot": "authentication:debit_card=no", - "then": { - "en": "Authentication via debit card is available" - } - }, - { - "if": "authentication:none=yes", - "ifnot": "authentication:none=no", - "then": { - "en": "Charging here is (also) possible without authentication" - } - } - ] - }, - { - "id": "Auth phone", - "render": { - "en": "Authenticate by calling or SMS'ing to {authentication:phone_call:number}", - "it": "{network}", - "ja": "{network}", - "nb_NO": "{network}", - "ru": "{network}", - "zh_Hant": "{network}" - }, - "question": { - "en": "What's the phone number for authentication call or SMS?", - "it": "A quale rete appartiene questa stazione di ricarica?", - "ja": "この充電ステーションの運営チェーンはどこですか?", - "ru": "К какой сети относится эта станция?", - "zh_Hant": "充電站所屬的網路是?" - }, - "freeform": { - "key": "authentication:phone_call:number", - "type": "phone" - }, - "condition": { - "or": [ - "authentication:phone_call=yes", - "authentication:short_message=yes" - ] - }, - "it": { - "0": { - "then": "Non appartiene a una rete" - } - }, - "ja": { - "0": { - "then": "大規模な運営チェーンの一部ではない" - } - }, - "ru": { - "0": { - "then": "Не является частью более крупной сети" - } - }, - "zh_Hant": { - "0": { - "then": "不屬於大型網路" - } - } - }, - { - "id": "OH", - "render": "{opening_hours_table(opening_hours)}", - "freeform": { - "key": "opening_hours", - "type": "opening_hours" - }, - "question": { - "en": "When is this charging station opened?" - }, - "mappings": [ - { - "if": "opening_hours=24/7", - "then": { - "en": "24/7 opened (including holidays)" - } - } - ] - }, - { - "id": "fee/charge", - "question": { - "en": "How much does one have to pay to use this charging station?", - "nl": "Hoeveel kost het gebruik van dit oplaadpunt?" - }, - "freeform": { - "key": "charge", - "addExtraTags": [ - "fee=yes" - ] - }, - "render": { - "en": "Using this charging station costs {charge}", - "nl": "Dit oplaadpunt gebruiken kost {charge}" - }, - "mappings": [ - { - "if": { - "and": [ - "fee=no", - "charge=" - ] - }, - "then": { - "nl": "Gratis te gebruiken", - "en": "Free to use" - } - } - ] - }, - { - "id": "payment-options", - "builtin": "payment-options", - "override": { - "condition": { - "or": [ - "fee=yes", - "charge~*" - ] - }, - "mappings+": [ - { - "if": "payment:app=yes", - "ifnot": "payment:app=no", - "then": { - "en": "Payment is done using a dedicated app", - "nl": "Betalen via een app van het netwerk" + "id": "capacity", + "render": { + "en": "{capacity} vehicles can be charged here at the same time", + "nl": "{capacity} voertuigen kunnen hier op hetzelfde moment opgeladen worden" + }, + "question": { + "en": "How much vehicles can be charged here at the same time?", + "nl": "Hoeveel voertuigen kunnen hier opgeladen worden?" + }, + "freeform": { + "key": "capacity", + "type": "pnat" } - }, - { - "if": "payment:membership_card=yes", - "ifnot": "payment:membership_card=no", - "then": { - "en": "Payment is done using a membership card", - "nl": "Betalen via een lidkaart van het netwerk" + }, + { + "id": "Available_charging_stations (generated)", + "question": { + "en": "Which charging stations are available here?" + }, + "multiAnswer": true, + "mappings": [ + { + "if": "socket:schuko=1", + "ifnot": "socket:schuko=", + "then": { + "en": "
Schuko wall plug without ground pin (CEE7/4 type F)
", + "nl": "
Schuko stekker zonder aardingspin (CEE7/4 type F)
" + }, + "hideInAnswer": { + "or": [ + "_country!=be", + "_country!=fr", + "_country!=ma", + "_country!=tn", + "_country!=pl", + "_country!=cs", + "_country!=sk", + "_country!=mo" + ] + } + }, + { + "if": { + "and": [ + "socket:schuko~*", + "socket:schuko!=1" + ] + }, + "then": { + "en": "
Schuko wall plug without ground pin (CEE7/4 type F)
", + "nl": "
Schuko stekker zonder aardingspin (CEE7/4 type F)
" + }, + "hideInAnswer": true + }, + { + "if": "socket:typee=1", + "ifnot": "socket:typee=", + "then": { + "en": "
European wall plug with ground pin (CEE7/4 type E)
", + "nl": "
Europese stekker met aardingspin (CEE7/4 type E)
" + } + }, + { + "if": { + "and": [ + "socket:typee~*", + "socket:typee!=1" + ] + }, + "then": { + "en": "
European wall plug with ground pin (CEE7/4 type E)
", + "nl": "
Europese stekker met aardingspin (CEE7/4 type E)
" + }, + "hideInAnswer": true + }, + { + "if": "socket:chademo=1", + "ifnot": "socket:chademo=", + "then": { + "en": "
Chademo
", + "nl": "
Chademo
" + }, + "hideInAnswer": { + "or": [ + { + "and": [ + "car=no", + "motorcar=no", + "hgv=no", + "bus=no" + ] + }, + { + "and": [ + { + "or": [ + "bicycle=yes", + "scooter=yes" + ] + }, + "car!=yes", + "motorcar!=yes", + "hgv!=yes", + "bus!=yes" + ] + } + ] + } + }, + { + "if": { + "and": [ + "socket:chademo~*", + "socket:chademo!=1" + ] + }, + "then": { + "en": "
Chademo
", + "nl": "
Chademo
" + }, + "hideInAnswer": true + }, + { + "if": "socket:type1_cable=1", + "ifnot": "socket:type1_cable=", + "then": { + "en": "
Type 1 with cable (J1772)
", + "nl": "
Type 1 met kabel (J1772)
" + }, + "hideInAnswer": { + "or": [ + { + "and": [ + "car=no", + "motorcar=no", + "hgv=no", + "bus=no" + ] + }, + { + "and": [ + { + "or": [ + "bicycle=yes", + "scooter=yes" + ] + }, + "car!=yes", + "motorcar!=yes", + "hgv!=yes", + "bus!=yes" + ] + } + ] + } + }, + { + "if": { + "and": [ + "socket:type1_cable~*", + "socket:type1_cable!=1" + ] + }, + "then": { + "en": "
Type 1 with cable (J1772)
", + "nl": "
Type 1 met kabel (J1772)
" + }, + "hideInAnswer": true + }, + { + "if": "socket:type1=1", + "ifnot": "socket:type1=", + "then": { + "en": "
Type 1 without cable (J1772)
", + "nl": "
Type 1 zonder kabel (J1772)
" + }, + "hideInAnswer": { + "or": [ + { + "and": [ + "car=no", + "motorcar=no", + "hgv=no", + "bus=no" + ] + }, + { + "and": [ + { + "or": [ + "bicycle=yes", + "scooter=yes" + ] + }, + "car!=yes", + "motorcar!=yes", + "hgv!=yes", + "bus!=yes" + ] + } + ] + } + }, + { + "if": { + "and": [ + "socket:type1~*", + "socket:type1!=1" + ] + }, + "then": { + "en": "
Type 1 without cable (J1772)
", + "nl": "
Type 1 zonder kabel (J1772)
" + }, + "hideInAnswer": true + }, + { + "if": "socket:type1_combo=1", + "ifnot": "socket:type1_combo=", + "then": { + "en": "
Type 1 CCS (aka Type 1 Combo)
", + "nl": "
Type 1 CCS (ook gekend als Type 1 Combo)
" + }, + "hideInAnswer": { + "or": [ + { + "and": [ + "car=no", + "motorcar=no", + "hgv=no", + "bus=no" + ] + }, + { + "and": [ + { + "or": [ + "bicycle=yes", + "scooter=yes" + ] + }, + "car!=yes", + "motorcar!=yes", + "hgv!=yes", + "bus!=yes" + ] + } + ] + } + }, + { + "if": { + "and": [ + "socket:type1_combo~*", + "socket:type1_combo!=1" + ] + }, + "then": { + "en": "
Type 1 CCS (aka Type 1 Combo)
", + "nl": "
Type 1 CCS (ook gekend als Type 1 Combo)
" + }, + "hideInAnswer": true + }, + { + "if": "socket:tesla_supercharger=1", + "ifnot": "socket:tesla_supercharger=", + "then": { + "en": "
Tesla Supercharger
", + "nl": "
Tesla Supercharger
" + }, + "hideInAnswer": { + "or": [ + { + "and": [ + "car=no", + "motorcar=no", + "hgv=no", + "bus=no" + ] + }, + { + "and": [ + { + "or": [ + "bicycle=yes", + "scooter=yes" + ] + }, + "car!=yes", + "motorcar!=yes", + "hgv!=yes", + "bus!=yes" + ] + } + ] + } + }, + { + "if": { + "and": [ + "socket:tesla_supercharger~*", + "socket:tesla_supercharger!=1" + ] + }, + "then": { + "en": "
Tesla Supercharger
", + "nl": "
Tesla Supercharger
" + }, + "hideInAnswer": true + }, + { + "if": "socket:type2=1", + "ifnot": "socket:type2=", + "then": { + "en": "
Type 2 (mennekes)
", + "nl": "
Type 2 (mennekes)
" + }, + "hideInAnswer": { + "or": [ + { + "and": [ + "car=no", + "motorcar=no", + "hgv=no", + "bus=no" + ] + }, + { + "and": [ + { + "or": [ + "bicycle=yes", + "scooter=yes" + ] + }, + "car!=yes", + "motorcar!=yes", + "hgv!=yes", + "bus!=yes" + ] + } + ] + } + }, + { + "if": { + "and": [ + "socket:type2~*", + "socket:type2!=1" + ] + }, + "then": { + "en": "
Type 2 (mennekes)
", + "nl": "
Type 2 (mennekes)
" + }, + "hideInAnswer": true + }, + { + "if": "socket:type2_combo=1", + "ifnot": "socket:type2_combo=", + "then": { + "en": "
Type 2 CCS (mennekes)
", + "nl": "
Type 2 CCS (mennekes)
" + }, + "hideInAnswer": { + "or": [ + { + "and": [ + "car=no", + "motorcar=no", + "hgv=no", + "bus=no" + ] + }, + { + "and": [ + { + "or": [ + "bicycle=yes", + "scooter=yes" + ] + }, + "car!=yes", + "motorcar!=yes", + "hgv!=yes", + "bus!=yes" + ] + } + ] + } + }, + { + "if": { + "and": [ + "socket:type2_combo~*", + "socket:type2_combo!=1" + ] + }, + "then": { + "en": "
Type 2 CCS (mennekes)
", + "nl": "
Type 2 CCS (mennekes)
" + }, + "hideInAnswer": true + }, + { + "if": "socket:type2_cable=1", + "ifnot": "socket:type2_cable=", + "then": { + "en": "
Type 2 with cable (mennekes)
", + "nl": "
Type 2 met kabel (J1772)
" + }, + "hideInAnswer": { + "or": [ + { + "and": [ + "car=no", + "motorcar=no", + "hgv=no", + "bus=no" + ] + }, + { + "and": [ + { + "or": [ + "bicycle=yes", + "scooter=yes" + ] + }, + "car!=yes", + "motorcar!=yes", + "hgv!=yes", + "bus!=yes" + ] + } + ] + } + }, + { + "if": { + "and": [ + "socket:type2_cable~*", + "socket:type2_cable!=1" + ] + }, + "then": { + "en": "
Type 2 with cable (mennekes)
", + "nl": "
Type 2 met kabel (J1772)
" + }, + "hideInAnswer": true + }, + { + "if": "socket:tesla_supercharger_ccs=1", + "ifnot": "socket:tesla_supercharger_ccs=", + "then": { + "en": "
Tesla Supercharger CCS (a branded type2_css)
", + "nl": "
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
" + }, + "hideInAnswer": { + "or": [ + { + "and": [ + "car=no", + "motorcar=no", + "hgv=no", + "bus=no" + ] + }, + { + "and": [ + { + "or": [ + "bicycle=yes", + "scooter=yes" + ] + }, + "car!=yes", + "motorcar!=yes", + "hgv!=yes", + "bus!=yes" + ] + } + ] + } + }, + { + "if": { + "and": [ + "socket:tesla_supercharger_ccs~*", + "socket:tesla_supercharger_ccs!=1" + ] + }, + "then": { + "en": "
Tesla Supercharger CCS (a branded type2_css)
", + "nl": "
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
" + }, + "hideInAnswer": true + }, + { + "if": "socket:tesla_destination=1", + "ifnot": "socket:tesla_destination=", + "then": { + "en": "
Tesla Supercharger (destination)
", + "nl": "
Tesla Supercharger (destination)
" + }, + "hideInAnswer": { + "or": [ + { + "and": [ + "car=no", + "motorcar=no", + "hgv=no", + "bus=no" + ] + }, + { + "and": [ + { + "or": [ + "bicycle=yes", + "scooter=yes" + ] + }, + "car!=yes", + "motorcar!=yes", + "hgv!=yes", + "bus!=yes" + ] + }, + { + "or": [ + "_country!=us" + ] + } + ] + } + }, + { + "if": { + "and": [ + "socket:tesla_destination~*", + "socket:tesla_destination!=1" + ] + }, + "then": { + "en": "
Tesla Supercharger (destination)
", + "nl": "
Tesla Supercharger (destination)
" + }, + "hideInAnswer": true + }, + { + "if": "socket:tesla_destination=1", + "ifnot": "socket:tesla_destination=", + "then": { + "en": "
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
", + "nl": "
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
" + }, + "hideInAnswer": { + "or": [ + { + "and": [ + "car=no", + "motorcar=no", + "hgv=no", + "bus=no" + ] + }, + { + "and": [ + { + "or": [ + "bicycle=yes", + "scooter=yes" + ] + }, + "car!=yes", + "motorcar!=yes", + "hgv!=yes", + "bus!=yes" + ] + }, + { + "or": [ + "_country=us" + ] + } + ] + } + }, + { + "if": { + "and": [ + "socket:tesla_destination~*", + "socket:tesla_destination!=1" + ] + }, + "then": { + "en": "
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
", + "nl": "
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
" + }, + "hideInAnswer": true + }, + { + "if": "socket:USB-A=1", + "ifnot": "socket:USB-A=", + "then": { + "en": "
USB to charge phones and small electronics
", + "nl": "
USB om GSMs en kleine electronica op te laden
" + } + }, + { + "if": { + "and": [ + "socket:USB-A~*", + "socket:USB-A!=1" + ] + }, + "then": { + "en": "
USB to charge phones and small electronics
", + "nl": "
USB om GSMs en kleine electronica op te laden
" + }, + "hideInAnswer": true + }, + { + "if": "socket:bosch_3pin=1", + "ifnot": "socket:bosch_3pin=", + "then": { + "en": "
Bosch Active Connect with 3 pins and cable
", + "nl": "
Bosch Active Connect met 3 pinnen aan een kabel
" + }, + "hideInAnswer": { + "or": [ + { + "and": [ + "bicycle=no" + ] + }, + { + "and": [ + { + "or": [ + "car=yes", + "motorcar=yes", + "hgv=yes", + "bus=yes" + ] + }, + "bicycle!=yes" + ] + } + ] + } + }, + { + "if": { + "and": [ + "socket:bosch_3pin~*", + "socket:bosch_3pin!=1" + ] + }, + "then": { + "en": "
Bosch Active Connect with 3 pins and cable
", + "nl": "
Bosch Active Connect met 3 pinnen aan een kabel
" + }, + "hideInAnswer": true + }, + { + "if": "socket:bosch_5pin=1", + "ifnot": "socket:bosch_5pin=", + "then": { + "en": "
Bosch Active Connect with 5 pins and cable
", + "nl": "
Bosch Active Connect met 5 pinnen aan een kabel
" + }, + "hideInAnswer": { + "or": [ + { + "and": [ + "bicycle=no" + ] + }, + { + "and": [ + { + "or": [ + "car=yes", + "motorcar=yes", + "hgv=yes", + "bus=yes" + ] + }, + "bicycle!=yes" + ] + } + ] + } + }, + { + "if": { + "and": [ + "socket:bosch_5pin~*", + "socket:bosch_5pin!=1" + ] + }, + "then": { + "en": "
Bosch Active Connect with 5 pins and cable
", + "nl": "
Bosch Active Connect met 5 pinnen aan een kabel
" + }, + "hideInAnswer": true + } + ] + }, + { + "id": "plugs-0", + "question": { + "en": "How much plugs of type
Schuko wall plug without ground pin (CEE7/4 type F)
are available here?", + "nl": "Hoeveel stekkers van type
Schuko stekker zonder aardingspin (CEE7/4 type F)
heeft dit oplaadpunt?" + }, + "render": { + "en": "There are {socket:schuko} plugs of type
Schuko wall plug without ground pin (CEE7/4 type F)
available here", + "nl": "Hier zijn {socket:schuko} stekkers van het type
Schuko stekker zonder aardingspin (CEE7/4 type F)
" + }, + "freeform": { + "key": "socket:schuko", + "type": "pnat" + }, + "condition": { + "and": [ + "socket:schuko~*", + "socket:schuko!=0" + ] + } + }, + { + "id": "voltage-0", + "question": { + "en": "What voltage do the plugs with
Schuko wall plug without ground pin (CEE7/4 type F)
offer?", + "nl": "Welke spanning levert de stekker van type
Schuko stekker zonder aardingspin (CEE7/4 type F)
" + }, + "render": { + "en": "
Schuko wall plug without ground pin (CEE7/4 type F)
outputs {socket:schuko:voltage} volt", + "nl": "
Schuko stekker zonder aardingspin (CEE7/4 type F)
heeft een spanning van {socket:schuko:voltage} volt" + }, + "freeform": { + "key": "socket:schuko:voltage", + "type": "pfloat" + }, + "mappings": [ + { + "if": "socket:socket:schuko:voltage=230 V", + "then": { + "en": "
Schuko wall plug without ground pin (CEE7/4 type F)
outputs 230 volt", + "nl": "
Schuko stekker zonder aardingspin (CEE7/4 type F)
heeft een spanning van 230 volt" + } + } + ], + "condition": { + "and": [ + "socket:schuko~*", + "socket:schuko!=0" + ] + } + }, + { + "id": "current-0", + "question": { + "en": "What current do the plugs with
Schuko wall plug without ground pin (CEE7/4 type F)
offer?", + "nl": "Welke stroom levert de stekker van type
Schuko stekker zonder aardingspin (CEE7/4 type F)
?" + }, + "render": { + "en": "
Schuko wall plug without ground pin (CEE7/4 type F)
outputs at most {socket:schuko:current}A", + "nl": "
Schuko stekker zonder aardingspin (CEE7/4 type F)
levert een stroom van maximaal {socket:schuko:current}A" + }, + "freeform": { + "key": "socket:schuko:current", + "type": "pfloat" + }, + "mappings": [ + { + "if": "socket:socket:schuko:current=16 A", + "then": { + "en": "
Schuko wall plug without ground pin (CEE7/4 type F)
outputs at most 16 A", + "nl": "
Schuko stekker zonder aardingspin (CEE7/4 type F)
levert een stroom van maximaal 16 A" + } + } + ], + "condition": { + "and": [ + "socket:schuko~*", + "socket:schuko!=0" + ] + } + }, + { + "id": "power-output-0", + "question": { + "en": "What power output does a single plug of type
Schuko wall plug without ground pin (CEE7/4 type F)
offer?", + "nl": "Welk vermogen levert een enkele stekker van type
Schuko stekker zonder aardingspin (CEE7/4 type F)
?" + }, + "render": { + "en": "
Schuko wall plug without ground pin (CEE7/4 type F)
outputs at most {socket:schuko:output}", + "nl": "
Schuko stekker zonder aardingspin (CEE7/4 type F)
levert een vermogen van maximaal {socket:schuko:output}" + }, + "freeform": { + "key": "socket:schuko:output", + "type": "pfloat" + }, + "mappings": [ + { + "if": "socket:socket:schuko:output=3.6 kw", + "then": { + "en": "
Schuko wall plug without ground pin (CEE7/4 type F)
outputs at most 3.6 kw", + "nl": "
Schuko stekker zonder aardingspin (CEE7/4 type F)
levert een vermogen van maximaal 3.6 kw" + } + } + ], + "condition": { + "and": [ + "socket:schuko~*", + "socket:schuko!=0" + ] + } + }, + { + "id": "plugs-1", + "question": { + "en": "How much plugs of type
European wall plug with ground pin (CEE7/4 type E)
are available here?", + "nl": "Hoeveel stekkers van type
Europese stekker met aardingspin (CEE7/4 type E)
heeft dit oplaadpunt?" + }, + "render": { + "en": "There are {socket:typee} plugs of type
European wall plug with ground pin (CEE7/4 type E)
available here", + "nl": "Hier zijn {socket:typee} stekkers van het type
Europese stekker met aardingspin (CEE7/4 type E)
" + }, + "freeform": { + "key": "socket:typee", + "type": "pnat" + }, + "condition": { + "and": [ + "socket:typee~*", + "socket:typee!=0" + ] + } + }, + { + "id": "voltage-1", + "question": { + "en": "What voltage do the plugs with
European wall plug with ground pin (CEE7/4 type E)
offer?", + "nl": "Welke spanning levert de stekker van type
Europese stekker met aardingspin (CEE7/4 type E)
" + }, + "render": { + "en": "
European wall plug with ground pin (CEE7/4 type E)
outputs {socket:typee:voltage} volt", + "nl": "
Europese stekker met aardingspin (CEE7/4 type E)
heeft een spanning van {socket:typee:voltage} volt" + }, + "freeform": { + "key": "socket:typee:voltage", + "type": "pfloat" + }, + "mappings": [ + { + "if": "socket:socket:typee:voltage=230 V", + "then": { + "en": "
European wall plug with ground pin (CEE7/4 type E)
outputs 230 volt", + "nl": "
Europese stekker met aardingspin (CEE7/4 type E)
heeft een spanning van 230 volt" + } + } + ], + "condition": { + "and": [ + "socket:typee~*", + "socket:typee!=0" + ] + } + }, + { + "id": "current-1", + "question": { + "en": "What current do the plugs with
European wall plug with ground pin (CEE7/4 type E)
offer?", + "nl": "Welke stroom levert de stekker van type
Europese stekker met aardingspin (CEE7/4 type E)
?" + }, + "render": { + "en": "
European wall plug with ground pin (CEE7/4 type E)
outputs at most {socket:typee:current}A", + "nl": "
Europese stekker met aardingspin (CEE7/4 type E)
levert een stroom van maximaal {socket:typee:current}A" + }, + "freeform": { + "key": "socket:typee:current", + "type": "pfloat" + }, + "mappings": [ + { + "if": "socket:socket:typee:current=16 A", + "then": { + "en": "
European wall plug with ground pin (CEE7/4 type E)
outputs at most 16 A", + "nl": "
Europese stekker met aardingspin (CEE7/4 type E)
levert een stroom van maximaal 16 A" + } + } + ], + "condition": { + "and": [ + "socket:typee~*", + "socket:typee!=0" + ] + } + }, + { + "id": "power-output-1", + "question": { + "en": "What power output does a single plug of type
European wall plug with ground pin (CEE7/4 type E)
offer?", + "nl": "Welk vermogen levert een enkele stekker van type
Europese stekker met aardingspin (CEE7/4 type E)
?" + }, + "render": { + "en": "
European wall plug with ground pin (CEE7/4 type E)
outputs at most {socket:typee:output}", + "nl": "
Europese stekker met aardingspin (CEE7/4 type E)
levert een vermogen van maximaal {socket:typee:output}" + }, + "freeform": { + "key": "socket:typee:output", + "type": "pfloat" + }, + "mappings": [ + { + "if": "socket:socket:typee:output=3 kw", + "then": { + "en": "
European wall plug with ground pin (CEE7/4 type E)
outputs at most 3 kw", + "nl": "
Europese stekker met aardingspin (CEE7/4 type E)
levert een vermogen van maximaal 3 kw" + } + }, + { + "if": "socket:socket:typee:output=22 kw", + "then": { + "en": "
European wall plug with ground pin (CEE7/4 type E)
outputs at most 22 kw", + "nl": "
Europese stekker met aardingspin (CEE7/4 type E)
levert een vermogen van maximaal 22 kw" + } + } + ], + "condition": { + "and": [ + "socket:typee~*", + "socket:typee!=0" + ] + } + }, + { + "id": "plugs-2", + "question": { + "en": "How much plugs of type
Chademo
are available here?", + "nl": "Hoeveel stekkers van type
Chademo
heeft dit oplaadpunt?" + }, + "render": { + "en": "There are {socket:chademo} plugs of type
Chademo
available here", + "nl": "Hier zijn {socket:chademo} stekkers van het type
Chademo
" + }, + "freeform": { + "key": "socket:chademo", + "type": "pnat" + }, + "condition": { + "and": [ + "socket:chademo~*", + "socket:chademo!=0" + ] + } + }, + { + "id": "voltage-2", + "question": { + "en": "What voltage do the plugs with
Chademo
offer?", + "nl": "Welke spanning levert de stekker van type
Chademo
" + }, + "render": { + "en": "
Chademo
outputs {socket:chademo:voltage} volt", + "nl": "
Chademo
heeft een spanning van {socket:chademo:voltage} volt" + }, + "freeform": { + "key": "socket:chademo:voltage", + "type": "pfloat" + }, + "mappings": [ + { + "if": "socket:socket:chademo:voltage=500 V", + "then": { + "en": "
Chademo
outputs 500 volt", + "nl": "
Chademo
heeft een spanning van 500 volt" + } + } + ], + "condition": { + "and": [ + "socket:chademo~*", + "socket:chademo!=0" + ] + } + }, + { + "id": "current-2", + "question": { + "en": "What current do the plugs with
Chademo
offer?", + "nl": "Welke stroom levert de stekker van type
Chademo
?" + }, + "render": { + "en": "
Chademo
outputs at most {socket:chademo:current}A", + "nl": "
Chademo
levert een stroom van maximaal {socket:chademo:current}A" + }, + "freeform": { + "key": "socket:chademo:current", + "type": "pfloat" + }, + "mappings": [ + { + "if": "socket:socket:chademo:current=120 A", + "then": { + "en": "
Chademo
outputs at most 120 A", + "nl": "
Chademo
levert een stroom van maximaal 120 A" + } + } + ], + "condition": { + "and": [ + "socket:chademo~*", + "socket:chademo!=0" + ] + } + }, + { + "id": "power-output-2", + "question": { + "en": "What power output does a single plug of type
Chademo
offer?", + "nl": "Welk vermogen levert een enkele stekker van type
Chademo
?" + }, + "render": { + "en": "
Chademo
outputs at most {socket:chademo:output}", + "nl": "
Chademo
levert een vermogen van maximaal {socket:chademo:output}" + }, + "freeform": { + "key": "socket:chademo:output", + "type": "pfloat" + }, + "mappings": [ + { + "if": "socket:socket:chademo:output=50 kw", + "then": { + "en": "
Chademo
outputs at most 50 kw", + "nl": "
Chademo
levert een vermogen van maximaal 50 kw" + } + } + ], + "condition": { + "and": [ + "socket:chademo~*", + "socket:chademo!=0" + ] + } + }, + { + "id": "plugs-3", + "question": { + "en": "How much plugs of type
Type 1 with cable (J1772)
are available here?", + "nl": "Hoeveel stekkers van type
Type 1 met kabel (J1772)
heeft dit oplaadpunt?" + }, + "render": { + "en": "There are {socket:type1_cable} plugs of type
Type 1 with cable (J1772)
available here", + "nl": "Hier zijn {socket:type1_cable} stekkers van het type
Type 1 met kabel (J1772)
" + }, + "freeform": { + "key": "socket:type1_cable", + "type": "pnat" + }, + "condition": { + "and": [ + "socket:type1_cable~*", + "socket:type1_cable!=0" + ] + } + }, + { + "id": "voltage-3", + "question": { + "en": "What voltage do the plugs with
Type 1 with cable (J1772)
offer?", + "nl": "Welke spanning levert de stekker van type
Type 1 met kabel (J1772)
" + }, + "render": { + "en": "
Type 1 with cable (J1772)
outputs {socket:type1_cable:voltage} volt", + "nl": "
Type 1 met kabel (J1772)
heeft een spanning van {socket:type1_cable:voltage} volt" + }, + "freeform": { + "key": "socket:type1_cable:voltage", + "type": "pfloat" + }, + "mappings": [ + { + "if": "socket:socket:type1_cable:voltage=200 V", + "then": { + "en": "
Type 1 with cable (J1772)
outputs 200 volt", + "nl": "
Type 1 met kabel (J1772)
heeft een spanning van 200 volt" + } + }, + { + "if": "socket:socket:type1_cable:voltage=240 V", + "then": { + "en": "
Type 1 with cable (J1772)
outputs 240 volt", + "nl": "
Type 1 met kabel (J1772)
heeft een spanning van 240 volt" + } + } + ], + "condition": { + "and": [ + "socket:type1_cable~*", + "socket:type1_cable!=0" + ] + } + }, + { + "id": "current-3", + "question": { + "en": "What current do the plugs with
Type 1 with cable (J1772)
offer?", + "nl": "Welke stroom levert de stekker van type
Type 1 met kabel (J1772)
?" + }, + "render": { + "en": "
Type 1 with cable (J1772)
outputs at most {socket:type1_cable:current}A", + "nl": "
Type 1 met kabel (J1772)
levert een stroom van maximaal {socket:type1_cable:current}A" + }, + "freeform": { + "key": "socket:type1_cable:current", + "type": "pfloat" + }, + "mappings": [ + { + "if": "socket:socket:type1_cable:current=32 A", + "then": { + "en": "
Type 1 with cable (J1772)
outputs at most 32 A", + "nl": "
Type 1 met kabel (J1772)
levert een stroom van maximaal 32 A" + } + } + ], + "condition": { + "and": [ + "socket:type1_cable~*", + "socket:type1_cable!=0" + ] + } + }, + { + "id": "power-output-3", + "question": { + "en": "What power output does a single plug of type
Type 1 with cable (J1772)
offer?", + "nl": "Welk vermogen levert een enkele stekker van type
Type 1 met kabel (J1772)
?" + }, + "render": { + "en": "
Type 1 with cable (J1772)
outputs at most {socket:type1_cable:output}", + "nl": "
Type 1 met kabel (J1772)
levert een vermogen van maximaal {socket:type1_cable:output}" + }, + "freeform": { + "key": "socket:type1_cable:output", + "type": "pfloat" + }, + "mappings": [ + { + "if": "socket:socket:type1_cable:output=3.7 kw", + "then": { + "en": "
Type 1 with cable (J1772)
outputs at most 3.7 kw", + "nl": "
Type 1 met kabel (J1772)
levert een vermogen van maximaal 3.7 kw" + } + }, + { + "if": "socket:socket:type1_cable:output=7 kw", + "then": { + "en": "
Type 1 with cable (J1772)
outputs at most 7 kw", + "nl": "
Type 1 met kabel (J1772)
levert een vermogen van maximaal 7 kw" + } + } + ], + "condition": { + "and": [ + "socket:type1_cable~*", + "socket:type1_cable!=0" + ] + } + }, + { + "id": "plugs-4", + "question": { + "en": "How much plugs of type
Type 1 without cable (J1772)
are available here?", + "nl": "Hoeveel stekkers van type
Type 1 zonder kabel (J1772)
heeft dit oplaadpunt?" + }, + "render": { + "en": "There are {socket:type1} plugs of type
Type 1 without cable (J1772)
available here", + "nl": "Hier zijn {socket:type1} stekkers van het type
Type 1 zonder kabel (J1772)
" + }, + "freeform": { + "key": "socket:type1", + "type": "pnat" + }, + "condition": { + "and": [ + "socket:type1~*", + "socket:type1!=0" + ] + } + }, + { + "id": "voltage-4", + "question": { + "en": "What voltage do the plugs with
Type 1 without cable (J1772)
offer?", + "nl": "Welke spanning levert de stekker van type
Type 1 zonder kabel (J1772)
" + }, + "render": { + "en": "
Type 1 without cable (J1772)
outputs {socket:type1:voltage} volt", + "nl": "
Type 1 zonder kabel (J1772)
heeft een spanning van {socket:type1:voltage} volt" + }, + "freeform": { + "key": "socket:type1:voltage", + "type": "pfloat" + }, + "mappings": [ + { + "if": "socket:socket:type1:voltage=200 V", + "then": { + "en": "
Type 1 without cable (J1772)
outputs 200 volt", + "nl": "
Type 1 zonder kabel (J1772)
heeft een spanning van 200 volt" + } + }, + { + "if": "socket:socket:type1:voltage=240 V", + "then": { + "en": "
Type 1 without cable (J1772)
outputs 240 volt", + "nl": "
Type 1 zonder kabel (J1772)
heeft een spanning van 240 volt" + } + } + ], + "condition": { + "and": [ + "socket:type1~*", + "socket:type1!=0" + ] + } + }, + { + "id": "current-4", + "question": { + "en": "What current do the plugs with
Type 1 without cable (J1772)
offer?", + "nl": "Welke stroom levert de stekker van type
Type 1 zonder kabel (J1772)
?" + }, + "render": { + "en": "
Type 1 without cable (J1772)
outputs at most {socket:type1:current}A", + "nl": "
Type 1 zonder kabel (J1772)
levert een stroom van maximaal {socket:type1:current}A" + }, + "freeform": { + "key": "socket:type1:current", + "type": "pfloat" + }, + "mappings": [ + { + "if": "socket:socket:type1:current=32 A", + "then": { + "en": "
Type 1 without cable (J1772)
outputs at most 32 A", + "nl": "
Type 1 zonder kabel (J1772)
levert een stroom van maximaal 32 A" + } + } + ], + "condition": { + "and": [ + "socket:type1~*", + "socket:type1!=0" + ] + } + }, + { + "id": "power-output-4", + "question": { + "en": "What power output does a single plug of type
Type 1 without cable (J1772)
offer?", + "nl": "Welk vermogen levert een enkele stekker van type
Type 1 zonder kabel (J1772)
?" + }, + "render": { + "en": "
Type 1 without cable (J1772)
outputs at most {socket:type1:output}", + "nl": "
Type 1 zonder kabel (J1772)
levert een vermogen van maximaal {socket:type1:output}" + }, + "freeform": { + "key": "socket:type1:output", + "type": "pfloat" + }, + "mappings": [ + { + "if": "socket:socket:type1:output=3.7 kw", + "then": { + "en": "
Type 1 without cable (J1772)
outputs at most 3.7 kw", + "nl": "
Type 1 zonder kabel (J1772)
levert een vermogen van maximaal 3.7 kw" + } + }, + { + "if": "socket:socket:type1:output=6.6 kw", + "then": { + "en": "
Type 1 without cable (J1772)
outputs at most 6.6 kw", + "nl": "
Type 1 zonder kabel (J1772)
levert een vermogen van maximaal 6.6 kw" + } + }, + { + "if": "socket:socket:type1:output=7 kw", + "then": { + "en": "
Type 1 without cable (J1772)
outputs at most 7 kw", + "nl": "
Type 1 zonder kabel (J1772)
levert een vermogen van maximaal 7 kw" + } + }, + { + "if": "socket:socket:type1:output=7.2 kw", + "then": { + "en": "
Type 1 without cable (J1772)
outputs at most 7.2 kw", + "nl": "
Type 1 zonder kabel (J1772)
levert een vermogen van maximaal 7.2 kw" + } + } + ], + "condition": { + "and": [ + "socket:type1~*", + "socket:type1!=0" + ] + } + }, + { + "id": "plugs-5", + "question": { + "en": "How much plugs of type
Type 1 CCS (aka Type 1 Combo)
are available here?", + "nl": "Hoeveel stekkers van type
Type 1 CCS (ook gekend als Type 1 Combo)
heeft dit oplaadpunt?" + }, + "render": { + "en": "There are {socket:type1_combo} plugs of type
Type 1 CCS (aka Type 1 Combo)
available here", + "nl": "Hier zijn {socket:type1_combo} stekkers van het type
Type 1 CCS (ook gekend als Type 1 Combo)
" + }, + "freeform": { + "key": "socket:type1_combo", + "type": "pnat" + }, + "condition": { + "and": [ + "socket:type1_combo~*", + "socket:type1_combo!=0" + ] + } + }, + { + "id": "voltage-5", + "question": { + "en": "What voltage do the plugs with
Type 1 CCS (aka Type 1 Combo)
offer?", + "nl": "Welke spanning levert de stekker van type
Type 1 CCS (ook gekend als Type 1 Combo)
" + }, + "render": { + "en": "
Type 1 CCS (aka Type 1 Combo)
outputs {socket:type1_combo:voltage} volt", + "nl": "
Type 1 CCS (ook gekend als Type 1 Combo)
heeft een spanning van {socket:type1_combo:voltage} volt" + }, + "freeform": { + "key": "socket:type1_combo:voltage", + "type": "pfloat" + }, + "mappings": [ + { + "if": "socket:socket:type1_combo:voltage=400 V", + "then": { + "en": "
Type 1 CCS (aka Type 1 Combo)
outputs 400 volt", + "nl": "
Type 1 CCS (ook gekend als Type 1 Combo)
heeft een spanning van 400 volt" + } + }, + { + "if": "socket:socket:type1_combo:voltage=1000 V", + "then": { + "en": "
Type 1 CCS (aka Type 1 Combo)
outputs 1000 volt", + "nl": "
Type 1 CCS (ook gekend als Type 1 Combo)
heeft een spanning van 1000 volt" + } + } + ], + "condition": { + "and": [ + "socket:type1_combo~*", + "socket:type1_combo!=0" + ] + } + }, + { + "id": "current-5", + "question": { + "en": "What current do the plugs with
Type 1 CCS (aka Type 1 Combo)
offer?", + "nl": "Welke stroom levert de stekker van type
Type 1 CCS (ook gekend als Type 1 Combo)
?" + }, + "render": { + "en": "
Type 1 CCS (aka Type 1 Combo)
outputs at most {socket:type1_combo:current}A", + "nl": "
Type 1 CCS (ook gekend als Type 1 Combo)
levert een stroom van maximaal {socket:type1_combo:current}A" + }, + "freeform": { + "key": "socket:type1_combo:current", + "type": "pfloat" + }, + "mappings": [ + { + "if": "socket:socket:type1_combo:current=50 A", + "then": { + "en": "
Type 1 CCS (aka Type 1 Combo)
outputs at most 50 A", + "nl": "
Type 1 CCS (ook gekend als Type 1 Combo)
levert een stroom van maximaal 50 A" + } + }, + { + "if": "socket:socket:type1_combo:current=125 A", + "then": { + "en": "
Type 1 CCS (aka Type 1 Combo)
outputs at most 125 A", + "nl": "
Type 1 CCS (ook gekend als Type 1 Combo)
levert een stroom van maximaal 125 A" + } + } + ], + "condition": { + "and": [ + "socket:type1_combo~*", + "socket:type1_combo!=0" + ] + } + }, + { + "id": "power-output-5", + "question": { + "en": "What power output does a single plug of type
Type 1 CCS (aka Type 1 Combo)
offer?", + "nl": "Welk vermogen levert een enkele stekker van type
Type 1 CCS (ook gekend als Type 1 Combo)
?" + }, + "render": { + "en": "
Type 1 CCS (aka Type 1 Combo)
outputs at most {socket:type1_combo:output}", + "nl": "
Type 1 CCS (ook gekend als Type 1 Combo)
levert een vermogen van maximaal {socket:type1_combo:output}" + }, + "freeform": { + "key": "socket:type1_combo:output", + "type": "pfloat" + }, + "mappings": [ + { + "if": "socket:socket:type1_combo:output=50 kw", + "then": { + "en": "
Type 1 CCS (aka Type 1 Combo)
outputs at most 50 kw", + "nl": "
Type 1 CCS (ook gekend als Type 1 Combo)
levert een vermogen van maximaal 50 kw" + } + }, + { + "if": "socket:socket:type1_combo:output=62.5 kw", + "then": { + "en": "
Type 1 CCS (aka Type 1 Combo)
outputs at most 62.5 kw", + "nl": "
Type 1 CCS (ook gekend als Type 1 Combo)
levert een vermogen van maximaal 62.5 kw" + } + }, + { + "if": "socket:socket:type1_combo:output=150 kw", + "then": { + "en": "
Type 1 CCS (aka Type 1 Combo)
outputs at most 150 kw", + "nl": "
Type 1 CCS (ook gekend als Type 1 Combo)
levert een vermogen van maximaal 150 kw" + } + }, + { + "if": "socket:socket:type1_combo:output=350 kw", + "then": { + "en": "
Type 1 CCS (aka Type 1 Combo)
outputs at most 350 kw", + "nl": "
Type 1 CCS (ook gekend als Type 1 Combo)
levert een vermogen van maximaal 350 kw" + } + } + ], + "condition": { + "and": [ + "socket:type1_combo~*", + "socket:type1_combo!=0" + ] + } + }, + { + "id": "plugs-6", + "question": { + "en": "How much plugs of type
Tesla Supercharger
are available here?", + "nl": "Hoeveel stekkers van type
Tesla Supercharger
heeft dit oplaadpunt?" + }, + "render": { + "en": "There are {socket:tesla_supercharger} plugs of type
Tesla Supercharger
available here", + "nl": "Hier zijn {socket:tesla_supercharger} stekkers van het type
Tesla Supercharger
" + }, + "freeform": { + "key": "socket:tesla_supercharger", + "type": "pnat" + }, + "condition": { + "and": [ + "socket:tesla_supercharger~*", + "socket:tesla_supercharger!=0" + ] + } + }, + { + "id": "voltage-6", + "question": { + "en": "What voltage do the plugs with
Tesla Supercharger
offer?", + "nl": "Welke spanning levert de stekker van type
Tesla Supercharger
" + }, + "render": { + "en": "
Tesla Supercharger
outputs {socket:tesla_supercharger:voltage} volt", + "nl": "
Tesla Supercharger
heeft een spanning van {socket:tesla_supercharger:voltage} volt" + }, + "freeform": { + "key": "socket:tesla_supercharger:voltage", + "type": "pfloat" + }, + "mappings": [ + { + "if": "socket:socket:tesla_supercharger:voltage=480 V", + "then": { + "en": "
Tesla Supercharger
outputs 480 volt", + "nl": "
Tesla Supercharger
heeft een spanning van 480 volt" + } + } + ], + "condition": { + "and": [ + "socket:tesla_supercharger~*", + "socket:tesla_supercharger!=0" + ] + } + }, + { + "id": "current-6", + "question": { + "en": "What current do the plugs with
Tesla Supercharger
offer?", + "nl": "Welke stroom levert de stekker van type
Tesla Supercharger
?" + }, + "render": { + "en": "
Tesla Supercharger
outputs at most {socket:tesla_supercharger:current}A", + "nl": "
Tesla Supercharger
levert een stroom van maximaal {socket:tesla_supercharger:current}A" + }, + "freeform": { + "key": "socket:tesla_supercharger:current", + "type": "pfloat" + }, + "mappings": [ + { + "if": "socket:socket:tesla_supercharger:current=125 A", + "then": { + "en": "
Tesla Supercharger
outputs at most 125 A", + "nl": "
Tesla Supercharger
levert een stroom van maximaal 125 A" + } + }, + { + "if": "socket:socket:tesla_supercharger:current=350 A", + "then": { + "en": "
Tesla Supercharger
outputs at most 350 A", + "nl": "
Tesla Supercharger
levert een stroom van maximaal 350 A" + } + } + ], + "condition": { + "and": [ + "socket:tesla_supercharger~*", + "socket:tesla_supercharger!=0" + ] + } + }, + { + "id": "power-output-6", + "question": { + "en": "What power output does a single plug of type
Tesla Supercharger
offer?", + "nl": "Welk vermogen levert een enkele stekker van type
Tesla Supercharger
?" + }, + "render": { + "en": "
Tesla Supercharger
outputs at most {socket:tesla_supercharger:output}", + "nl": "
Tesla Supercharger
levert een vermogen van maximaal {socket:tesla_supercharger:output}" + }, + "freeform": { + "key": "socket:tesla_supercharger:output", + "type": "pfloat" + }, + "mappings": [ + { + "if": "socket:socket:tesla_supercharger:output=120 kw", + "then": { + "en": "
Tesla Supercharger
outputs at most 120 kw", + "nl": "
Tesla Supercharger
levert een vermogen van maximaal 120 kw" + } + }, + { + "if": "socket:socket:tesla_supercharger:output=150 kw", + "then": { + "en": "
Tesla Supercharger
outputs at most 150 kw", + "nl": "
Tesla Supercharger
levert een vermogen van maximaal 150 kw" + } + }, + { + "if": "socket:socket:tesla_supercharger:output=250 kw", + "then": { + "en": "
Tesla Supercharger
outputs at most 250 kw", + "nl": "
Tesla Supercharger
levert een vermogen van maximaal 250 kw" + } + } + ], + "condition": { + "and": [ + "socket:tesla_supercharger~*", + "socket:tesla_supercharger!=0" + ] + } + }, + { + "id": "plugs-7", + "question": { + "en": "How much plugs of type
Type 2 (mennekes)
are available here?", + "nl": "Hoeveel stekkers van type
Type 2 (mennekes)
heeft dit oplaadpunt?" + }, + "render": { + "en": "There are {socket:type2} plugs of type
Type 2 (mennekes)
available here", + "nl": "Hier zijn {socket:type2} stekkers van het type
Type 2 (mennekes)
" + }, + "freeform": { + "key": "socket:type2", + "type": "pnat" + }, + "condition": { + "and": [ + "socket:type2~*", + "socket:type2!=0" + ] + } + }, + { + "id": "voltage-7", + "question": { + "en": "What voltage do the plugs with
Type 2 (mennekes)
offer?", + "nl": "Welke spanning levert de stekker van type
Type 2 (mennekes)
" + }, + "render": { + "en": "
Type 2 (mennekes)
outputs {socket:type2:voltage} volt", + "nl": "
Type 2 (mennekes)
heeft een spanning van {socket:type2:voltage} volt" + }, + "freeform": { + "key": "socket:type2:voltage", + "type": "pfloat" + }, + "mappings": [ + { + "if": "socket:socket:type2:voltage=230 V", + "then": { + "en": "
Type 2 (mennekes)
outputs 230 volt", + "nl": "
Type 2 (mennekes)
heeft een spanning van 230 volt" + } + }, + { + "if": "socket:socket:type2:voltage=400 V", + "then": { + "en": "
Type 2 (mennekes)
outputs 400 volt", + "nl": "
Type 2 (mennekes)
heeft een spanning van 400 volt" + } + } + ], + "condition": { + "and": [ + "socket:type2~*", + "socket:type2!=0" + ] + } + }, + { + "id": "current-7", + "question": { + "en": "What current do the plugs with
Type 2 (mennekes)
offer?", + "nl": "Welke stroom levert de stekker van type
Type 2 (mennekes)
?" + }, + "render": { + "en": "
Type 2 (mennekes)
outputs at most {socket:type2:current}A", + "nl": "
Type 2 (mennekes)
levert een stroom van maximaal {socket:type2:current}A" + }, + "freeform": { + "key": "socket:type2:current", + "type": "pfloat" + }, + "mappings": [ + { + "if": "socket:socket:type2:current=16 A", + "then": { + "en": "
Type 2 (mennekes)
outputs at most 16 A", + "nl": "
Type 2 (mennekes)
levert een stroom van maximaal 16 A" + } + }, + { + "if": "socket:socket:type2:current=32 A", + "then": { + "en": "
Type 2 (mennekes)
outputs at most 32 A", + "nl": "
Type 2 (mennekes)
levert een stroom van maximaal 32 A" + } + } + ], + "condition": { + "and": [ + "socket:type2~*", + "socket:type2!=0" + ] + } + }, + { + "id": "power-output-7", + "question": { + "en": "What power output does a single plug of type
Type 2 (mennekes)
offer?", + "nl": "Welk vermogen levert een enkele stekker van type
Type 2 (mennekes)
?" + }, + "render": { + "en": "
Type 2 (mennekes)
outputs at most {socket:type2:output}", + "nl": "
Type 2 (mennekes)
levert een vermogen van maximaal {socket:type2:output}" + }, + "freeform": { + "key": "socket:type2:output", + "type": "pfloat" + }, + "mappings": [ + { + "if": "socket:socket:type2:output=11 kw", + "then": { + "en": "
Type 2 (mennekes)
outputs at most 11 kw", + "nl": "
Type 2 (mennekes)
levert een vermogen van maximaal 11 kw" + } + }, + { + "if": "socket:socket:type2:output=22 kw", + "then": { + "en": "
Type 2 (mennekes)
outputs at most 22 kw", + "nl": "
Type 2 (mennekes)
levert een vermogen van maximaal 22 kw" + } + } + ], + "condition": { + "and": [ + "socket:type2~*", + "socket:type2!=0" + ] + } + }, + { + "id": "plugs-8", + "question": { + "en": "How much plugs of type
Type 2 CCS (mennekes)
are available here?", + "nl": "Hoeveel stekkers van type
Type 2 CCS (mennekes)
heeft dit oplaadpunt?" + }, + "render": { + "en": "There are {socket:type2_combo} plugs of type
Type 2 CCS (mennekes)
available here", + "nl": "Hier zijn {socket:type2_combo} stekkers van het type
Type 2 CCS (mennekes)
" + }, + "freeform": { + "key": "socket:type2_combo", + "type": "pnat" + }, + "condition": { + "and": [ + "socket:type2_combo~*", + "socket:type2_combo!=0" + ] + } + }, + { + "id": "voltage-8", + "question": { + "en": "What voltage do the plugs with
Type 2 CCS (mennekes)
offer?", + "nl": "Welke spanning levert de stekker van type
Type 2 CCS (mennekes)
" + }, + "render": { + "en": "
Type 2 CCS (mennekes)
outputs {socket:type2_combo:voltage} volt", + "nl": "
Type 2 CCS (mennekes)
heeft een spanning van {socket:type2_combo:voltage} volt" + }, + "freeform": { + "key": "socket:type2_combo:voltage", + "type": "pfloat" + }, + "mappings": [ + { + "if": "socket:socket:type2_combo:voltage=500 V", + "then": { + "en": "
Type 2 CCS (mennekes)
outputs 500 volt", + "nl": "
Type 2 CCS (mennekes)
heeft een spanning van 500 volt" + } + }, + { + "if": "socket:socket:type2_combo:voltage=920 V", + "then": { + "en": "
Type 2 CCS (mennekes)
outputs 920 volt", + "nl": "
Type 2 CCS (mennekes)
heeft een spanning van 920 volt" + } + } + ], + "condition": { + "and": [ + "socket:type2_combo~*", + "socket:type2_combo!=0" + ] + } + }, + { + "id": "current-8", + "question": { + "en": "What current do the plugs with
Type 2 CCS (mennekes)
offer?", + "nl": "Welke stroom levert de stekker van type
Type 2 CCS (mennekes)
?" + }, + "render": { + "en": "
Type 2 CCS (mennekes)
outputs at most {socket:type2_combo:current}A", + "nl": "
Type 2 CCS (mennekes)
levert een stroom van maximaal {socket:type2_combo:current}A" + }, + "freeform": { + "key": "socket:type2_combo:current", + "type": "pfloat" + }, + "mappings": [ + { + "if": "socket:socket:type2_combo:current=125 A", + "then": { + "en": "
Type 2 CCS (mennekes)
outputs at most 125 A", + "nl": "
Type 2 CCS (mennekes)
levert een stroom van maximaal 125 A" + } + }, + { + "if": "socket:socket:type2_combo:current=350 A", + "then": { + "en": "
Type 2 CCS (mennekes)
outputs at most 350 A", + "nl": "
Type 2 CCS (mennekes)
levert een stroom van maximaal 350 A" + } + } + ], + "condition": { + "and": [ + "socket:type2_combo~*", + "socket:type2_combo!=0" + ] + } + }, + { + "id": "power-output-8", + "question": { + "en": "What power output does a single plug of type
Type 2 CCS (mennekes)
offer?", + "nl": "Welk vermogen levert een enkele stekker van type
Type 2 CCS (mennekes)
?" + }, + "render": { + "en": "
Type 2 CCS (mennekes)
outputs at most {socket:type2_combo:output}", + "nl": "
Type 2 CCS (mennekes)
levert een vermogen van maximaal {socket:type2_combo:output}" + }, + "freeform": { + "key": "socket:type2_combo:output", + "type": "pfloat" + }, + "mappings": [ + { + "if": "socket:socket:type2_combo:output=50 kw", + "then": { + "en": "
Type 2 CCS (mennekes)
outputs at most 50 kw", + "nl": "
Type 2 CCS (mennekes)
levert een vermogen van maximaal 50 kw" + } + } + ], + "condition": { + "and": [ + "socket:type2_combo~*", + "socket:type2_combo!=0" + ] + } + }, + { + "id": "plugs-9", + "question": { + "en": "How much plugs of type
Type 2 with cable (mennekes)
are available here?", + "nl": "Hoeveel stekkers van type
Type 2 met kabel (J1772)
heeft dit oplaadpunt?" + }, + "render": { + "en": "There are {socket:type2_cable} plugs of type
Type 2 with cable (mennekes)
available here", + "nl": "Hier zijn {socket:type2_cable} stekkers van het type
Type 2 met kabel (J1772)
" + }, + "freeform": { + "key": "socket:type2_cable", + "type": "pnat" + }, + "condition": { + "and": [ + "socket:type2_cable~*", + "socket:type2_cable!=0" + ] + } + }, + { + "id": "voltage-9", + "question": { + "en": "What voltage do the plugs with
Type 2 with cable (mennekes)
offer?", + "nl": "Welke spanning levert de stekker van type
Type 2 met kabel (J1772)
" + }, + "render": { + "en": "
Type 2 with cable (mennekes)
outputs {socket:type2_cable:voltage} volt", + "nl": "
Type 2 met kabel (J1772)
heeft een spanning van {socket:type2_cable:voltage} volt" + }, + "freeform": { + "key": "socket:type2_cable:voltage", + "type": "pfloat" + }, + "mappings": [ + { + "if": "socket:socket:type2_cable:voltage=230 V", + "then": { + "en": "
Type 2 with cable (mennekes)
outputs 230 volt", + "nl": "
Type 2 met kabel (J1772)
heeft een spanning van 230 volt" + } + }, + { + "if": "socket:socket:type2_cable:voltage=400 V", + "then": { + "en": "
Type 2 with cable (mennekes)
outputs 400 volt", + "nl": "
Type 2 met kabel (J1772)
heeft een spanning van 400 volt" + } + } + ], + "condition": { + "and": [ + "socket:type2_cable~*", + "socket:type2_cable!=0" + ] + } + }, + { + "id": "current-9", + "question": { + "en": "What current do the plugs with
Type 2 with cable (mennekes)
offer?", + "nl": "Welke stroom levert de stekker van type
Type 2 met kabel (J1772)
?" + }, + "render": { + "en": "
Type 2 with cable (mennekes)
outputs at most {socket:type2_cable:current}A", + "nl": "
Type 2 met kabel (J1772)
levert een stroom van maximaal {socket:type2_cable:current}A" + }, + "freeform": { + "key": "socket:type2_cable:current", + "type": "pfloat" + }, + "mappings": [ + { + "if": "socket:socket:type2_cable:current=16 A", + "then": { + "en": "
Type 2 with cable (mennekes)
outputs at most 16 A", + "nl": "
Type 2 met kabel (J1772)
levert een stroom van maximaal 16 A" + } + }, + { + "if": "socket:socket:type2_cable:current=32 A", + "then": { + "en": "
Type 2 with cable (mennekes)
outputs at most 32 A", + "nl": "
Type 2 met kabel (J1772)
levert een stroom van maximaal 32 A" + } + } + ], + "condition": { + "and": [ + "socket:type2_cable~*", + "socket:type2_cable!=0" + ] + } + }, + { + "id": "power-output-9", + "question": { + "en": "What power output does a single plug of type
Type 2 with cable (mennekes)
offer?", + "nl": "Welk vermogen levert een enkele stekker van type
Type 2 met kabel (J1772)
?" + }, + "render": { + "en": "
Type 2 with cable (mennekes)
outputs at most {socket:type2_cable:output}", + "nl": "
Type 2 met kabel (J1772)
levert een vermogen van maximaal {socket:type2_cable:output}" + }, + "freeform": { + "key": "socket:type2_cable:output", + "type": "pfloat" + }, + "mappings": [ + { + "if": "socket:socket:type2_cable:output=11 kw", + "then": { + "en": "
Type 2 with cable (mennekes)
outputs at most 11 kw", + "nl": "
Type 2 met kabel (J1772)
levert een vermogen van maximaal 11 kw" + } + }, + { + "if": "socket:socket:type2_cable:output=22 kw", + "then": { + "en": "
Type 2 with cable (mennekes)
outputs at most 22 kw", + "nl": "
Type 2 met kabel (J1772)
levert een vermogen van maximaal 22 kw" + } + } + ], + "condition": { + "and": [ + "socket:type2_cable~*", + "socket:type2_cable!=0" + ] + } + }, + { + "id": "plugs-10", + "question": { + "en": "How much plugs of type
Tesla Supercharger CCS (a branded type2_css)
are available here?", + "nl": "Hoeveel stekkers van type
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
heeft dit oplaadpunt?" + }, + "render": { + "en": "There are {socket:tesla_supercharger_ccs} plugs of type
Tesla Supercharger CCS (a branded type2_css)
available here", + "nl": "Hier zijn {socket:tesla_supercharger_ccs} stekkers van het type
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
" + }, + "freeform": { + "key": "socket:tesla_supercharger_ccs", + "type": "pnat" + }, + "condition": { + "and": [ + "socket:tesla_supercharger_ccs~*", + "socket:tesla_supercharger_ccs!=0" + ] + } + }, + { + "id": "voltage-10", + "question": { + "en": "What voltage do the plugs with
Tesla Supercharger CCS (a branded type2_css)
offer?", + "nl": "Welke spanning levert de stekker van type
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
" + }, + "render": { + "en": "
Tesla Supercharger CCS (a branded type2_css)
outputs {socket:tesla_supercharger_ccs:voltage} volt", + "nl": "
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
heeft een spanning van {socket:tesla_supercharger_ccs:voltage} volt" + }, + "freeform": { + "key": "socket:tesla_supercharger_ccs:voltage", + "type": "pfloat" + }, + "mappings": [ + { + "if": "socket:socket:tesla_supercharger_ccs:voltage=500 V", + "then": { + "en": "
Tesla Supercharger CCS (a branded type2_css)
outputs 500 volt", + "nl": "
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
heeft een spanning van 500 volt" + } + }, + { + "if": "socket:socket:tesla_supercharger_ccs:voltage=920 V", + "then": { + "en": "
Tesla Supercharger CCS (a branded type2_css)
outputs 920 volt", + "nl": "
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
heeft een spanning van 920 volt" + } + } + ], + "condition": { + "and": [ + "socket:tesla_supercharger_ccs~*", + "socket:tesla_supercharger_ccs!=0" + ] + } + }, + { + "id": "current-10", + "question": { + "en": "What current do the plugs with
Tesla Supercharger CCS (a branded type2_css)
offer?", + "nl": "Welke stroom levert de stekker van type
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
?" + }, + "render": { + "en": "
Tesla Supercharger CCS (a branded type2_css)
outputs at most {socket:tesla_supercharger_ccs:current}A", + "nl": "
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
levert een stroom van maximaal {socket:tesla_supercharger_ccs:current}A" + }, + "freeform": { + "key": "socket:tesla_supercharger_ccs:current", + "type": "pfloat" + }, + "mappings": [ + { + "if": "socket:socket:tesla_supercharger_ccs:current=125 A", + "then": { + "en": "
Tesla Supercharger CCS (a branded type2_css)
outputs at most 125 A", + "nl": "
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
levert een stroom van maximaal 125 A" + } + }, + { + "if": "socket:socket:tesla_supercharger_ccs:current=350 A", + "then": { + "en": "
Tesla Supercharger CCS (a branded type2_css)
outputs at most 350 A", + "nl": "
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
levert een stroom van maximaal 350 A" + } + } + ], + "condition": { + "and": [ + "socket:tesla_supercharger_ccs~*", + "socket:tesla_supercharger_ccs!=0" + ] + } + }, + { + "id": "power-output-10", + "question": { + "en": "What power output does a single plug of type
Tesla Supercharger CCS (a branded type2_css)
offer?", + "nl": "Welk vermogen levert een enkele stekker van type
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
?" + }, + "render": { + "en": "
Tesla Supercharger CCS (a branded type2_css)
outputs at most {socket:tesla_supercharger_ccs:output}", + "nl": "
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
levert een vermogen van maximaal {socket:tesla_supercharger_ccs:output}" + }, + "freeform": { + "key": "socket:tesla_supercharger_ccs:output", + "type": "pfloat" + }, + "mappings": [ + { + "if": "socket:socket:tesla_supercharger_ccs:output=50 kw", + "then": { + "en": "
Tesla Supercharger CCS (a branded type2_css)
outputs at most 50 kw", + "nl": "
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
levert een vermogen van maximaal 50 kw" + } + } + ], + "condition": { + "and": [ + "socket:tesla_supercharger_ccs~*", + "socket:tesla_supercharger_ccs!=0" + ] + } + }, + { + "id": "plugs-11", + "question": { + "en": "How much plugs of type
Tesla Supercharger (destination)
are available here?", + "nl": "Hoeveel stekkers van type
Tesla Supercharger (destination)
heeft dit oplaadpunt?" + }, + "render": { + "en": "There are {socket:tesla_destination} plugs of type
Tesla Supercharger (destination)
available here", + "nl": "Hier zijn {socket:tesla_destination} stekkers van het type
Tesla Supercharger (destination)
" + }, + "freeform": { + "key": "socket:tesla_destination", + "type": "pnat" + }, + "condition": { + "and": [ + "socket:tesla_destination~*", + "socket:tesla_destination!=0" + ] + } + }, + { + "id": "voltage-11", + "question": { + "en": "What voltage do the plugs with
Tesla Supercharger (destination)
offer?", + "nl": "Welke spanning levert de stekker van type
Tesla Supercharger (destination)
" + }, + "render": { + "en": "
Tesla Supercharger (destination)
outputs {socket:tesla_destination:voltage} volt", + "nl": "
Tesla Supercharger (destination)
heeft een spanning van {socket:tesla_destination:voltage} volt" + }, + "freeform": { + "key": "socket:tesla_destination:voltage", + "type": "pfloat" + }, + "mappings": [ + { + "if": "socket:socket:tesla_destination:voltage=480 V", + "then": { + "en": "
Tesla Supercharger (destination)
outputs 480 volt", + "nl": "
Tesla Supercharger (destination)
heeft een spanning van 480 volt" + } + } + ], + "condition": { + "and": [ + "socket:tesla_destination~*", + "socket:tesla_destination!=0" + ] + } + }, + { + "id": "current-11", + "question": { + "en": "What current do the plugs with
Tesla Supercharger (destination)
offer?", + "nl": "Welke stroom levert de stekker van type
Tesla Supercharger (destination)
?" + }, + "render": { + "en": "
Tesla Supercharger (destination)
outputs at most {socket:tesla_destination:current}A", + "nl": "
Tesla Supercharger (destination)
levert een stroom van maximaal {socket:tesla_destination:current}A" + }, + "freeform": { + "key": "socket:tesla_destination:current", + "type": "pfloat" + }, + "mappings": [ + { + "if": "socket:socket:tesla_destination:current=125 A", + "then": { + "en": "
Tesla Supercharger (destination)
outputs at most 125 A", + "nl": "
Tesla Supercharger (destination)
levert een stroom van maximaal 125 A" + } + }, + { + "if": "socket:socket:tesla_destination:current=350 A", + "then": { + "en": "
Tesla Supercharger (destination)
outputs at most 350 A", + "nl": "
Tesla Supercharger (destination)
levert een stroom van maximaal 350 A" + } + } + ], + "condition": { + "and": [ + "socket:tesla_destination~*", + "socket:tesla_destination!=0" + ] + } + }, + { + "id": "power-output-11", + "question": { + "en": "What power output does a single plug of type
Tesla Supercharger (destination)
offer?", + "nl": "Welk vermogen levert een enkele stekker van type
Tesla Supercharger (destination)
?" + }, + "render": { + "en": "
Tesla Supercharger (destination)
outputs at most {socket:tesla_destination:output}", + "nl": "
Tesla Supercharger (destination)
levert een vermogen van maximaal {socket:tesla_destination:output}" + }, + "freeform": { + "key": "socket:tesla_destination:output", + "type": "pfloat" + }, + "mappings": [ + { + "if": "socket:socket:tesla_destination:output=120 kw", + "then": { + "en": "
Tesla Supercharger (destination)
outputs at most 120 kw", + "nl": "
Tesla Supercharger (destination)
levert een vermogen van maximaal 120 kw" + } + }, + { + "if": "socket:socket:tesla_destination:output=150 kw", + "then": { + "en": "
Tesla Supercharger (destination)
outputs at most 150 kw", + "nl": "
Tesla Supercharger (destination)
levert een vermogen van maximaal 150 kw" + } + }, + { + "if": "socket:socket:tesla_destination:output=250 kw", + "then": { + "en": "
Tesla Supercharger (destination)
outputs at most 250 kw", + "nl": "
Tesla Supercharger (destination)
levert een vermogen van maximaal 250 kw" + } + } + ], + "condition": { + "and": [ + "socket:tesla_destination~*", + "socket:tesla_destination!=0" + ] + } + }, + { + "id": "plugs-12", + "question": { + "en": "How much plugs of type
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
are available here?", + "nl": "Hoeveel stekkers van type
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
heeft dit oplaadpunt?" + }, + "render": { + "en": "There are {socket:tesla_destination} plugs of type
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
available here", + "nl": "Hier zijn {socket:tesla_destination} stekkers van het type
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
" + }, + "freeform": { + "key": "socket:tesla_destination", + "type": "pnat" + }, + "condition": { + "and": [ + "socket:tesla_destination~*", + "socket:tesla_destination!=0" + ] + } + }, + { + "id": "voltage-12", + "question": { + "en": "What voltage do the plugs with
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
offer?", + "nl": "Welke spanning levert de stekker van type
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
" + }, + "render": { + "en": "
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
outputs {socket:tesla_destination:voltage} volt", + "nl": "
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
heeft een spanning van {socket:tesla_destination:voltage} volt" + }, + "freeform": { + "key": "socket:tesla_destination:voltage", + "type": "pfloat" + }, + "mappings": [ + { + "if": "socket:socket:tesla_destination:voltage=230 V", + "then": { + "en": "
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
outputs 230 volt", + "nl": "
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
heeft een spanning van 230 volt" + } + }, + { + "if": "socket:socket:tesla_destination:voltage=400 V", + "then": { + "en": "
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
outputs 400 volt", + "nl": "
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
heeft een spanning van 400 volt" + } + } + ], + "condition": { + "and": [ + "socket:tesla_destination~*", + "socket:tesla_destination!=0" + ] + } + }, + { + "id": "current-12", + "question": { + "en": "What current do the plugs with
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
offer?", + "nl": "Welke stroom levert de stekker van type
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
?" + }, + "render": { + "en": "
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
outputs at most {socket:tesla_destination:current}A", + "nl": "
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
levert een stroom van maximaal {socket:tesla_destination:current}A" + }, + "freeform": { + "key": "socket:tesla_destination:current", + "type": "pfloat" + }, + "mappings": [ + { + "if": "socket:socket:tesla_destination:current=16 A", + "then": { + "en": "
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
outputs at most 16 A", + "nl": "
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
levert een stroom van maximaal 16 A" + } + }, + { + "if": "socket:socket:tesla_destination:current=32 A", + "then": { + "en": "
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
outputs at most 32 A", + "nl": "
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
levert een stroom van maximaal 32 A" + } + } + ], + "condition": { + "and": [ + "socket:tesla_destination~*", + "socket:tesla_destination!=0" + ] + } + }, + { + "id": "power-output-12", + "question": { + "en": "What power output does a single plug of type
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
offer?", + "nl": "Welk vermogen levert een enkele stekker van type
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
?" + }, + "render": { + "en": "
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
outputs at most {socket:tesla_destination:output}", + "nl": "
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
levert een vermogen van maximaal {socket:tesla_destination:output}" + }, + "freeform": { + "key": "socket:tesla_destination:output", + "type": "pfloat" + }, + "mappings": [ + { + "if": "socket:socket:tesla_destination:output=11 kw", + "then": { + "en": "
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
outputs at most 11 kw", + "nl": "
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
levert een vermogen van maximaal 11 kw" + } + }, + { + "if": "socket:socket:tesla_destination:output=22 kw", + "then": { + "en": "
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
outputs at most 22 kw", + "nl": "
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
levert een vermogen van maximaal 22 kw" + } + } + ], + "condition": { + "and": [ + "socket:tesla_destination~*", + "socket:tesla_destination!=0" + ] + } + }, + { + "id": "plugs-13", + "question": { + "en": "How much plugs of type
USB to charge phones and small electronics
are available here?", + "nl": "Hoeveel stekkers van type
USB om GSMs en kleine electronica op te laden
heeft dit oplaadpunt?" + }, + "render": { + "en": "There are {socket:USB-A} plugs of type
USB to charge phones and small electronics
available here", + "nl": "Hier zijn {socket:USB-A} stekkers van het type
USB om GSMs en kleine electronica op te laden
" + }, + "freeform": { + "key": "socket:USB-A", + "type": "pnat" + }, + "condition": { + "and": [ + "socket:USB-A~*", + "socket:USB-A!=0" + ] + } + }, + { + "id": "voltage-13", + "question": { + "en": "What voltage do the plugs with
USB to charge phones and small electronics
offer?", + "nl": "Welke spanning levert de stekker van type
USB om GSMs en kleine electronica op te laden
" + }, + "render": { + "en": "
USB to charge phones and small electronics
outputs {socket:USB-A:voltage} volt", + "nl": "
USB om GSMs en kleine electronica op te laden
heeft een spanning van {socket:USB-A:voltage} volt" + }, + "freeform": { + "key": "socket:USB-A:voltage", + "type": "pfloat" + }, + "mappings": [ + { + "if": "socket:socket:USB-A:voltage=5 V", + "then": { + "en": "
USB to charge phones and small electronics
outputs 5 volt", + "nl": "
USB om GSMs en kleine electronica op te laden
heeft een spanning van 5 volt" + } + } + ], + "condition": { + "and": [ + "socket:USB-A~*", + "socket:USB-A!=0" + ] + } + }, + { + "id": "current-13", + "question": { + "en": "What current do the plugs with
USB to charge phones and small electronics
offer?", + "nl": "Welke stroom levert de stekker van type
USB om GSMs en kleine electronica op te laden
?" + }, + "render": { + "en": "
USB to charge phones and small electronics
outputs at most {socket:USB-A:current}A", + "nl": "
USB om GSMs en kleine electronica op te laden
levert een stroom van maximaal {socket:USB-A:current}A" + }, + "freeform": { + "key": "socket:USB-A:current", + "type": "pfloat" + }, + "mappings": [ + { + "if": "socket:socket:USB-A:current=1 A", + "then": { + "en": "
USB to charge phones and small electronics
outputs at most 1 A", + "nl": "
USB om GSMs en kleine electronica op te laden
levert een stroom van maximaal 1 A" + } + }, + { + "if": "socket:socket:USB-A:current=2 A", + "then": { + "en": "
USB to charge phones and small electronics
outputs at most 2 A", + "nl": "
USB om GSMs en kleine electronica op te laden
levert een stroom van maximaal 2 A" + } + } + ], + "condition": { + "and": [ + "socket:USB-A~*", + "socket:USB-A!=0" + ] + } + }, + { + "id": "power-output-13", + "question": { + "en": "What power output does a single plug of type
USB to charge phones and small electronics
offer?", + "nl": "Welk vermogen levert een enkele stekker van type
USB om GSMs en kleine electronica op te laden
?" + }, + "render": { + "en": "
USB to charge phones and small electronics
outputs at most {socket:USB-A:output}", + "nl": "
USB om GSMs en kleine electronica op te laden
levert een vermogen van maximaal {socket:USB-A:output}" + }, + "freeform": { + "key": "socket:USB-A:output", + "type": "pfloat" + }, + "mappings": [ + { + "if": "socket:socket:USB-A:output=5w", + "then": { + "en": "
USB to charge phones and small electronics
outputs at most 5w", + "nl": "
USB om GSMs en kleine electronica op te laden
levert een vermogen van maximaal 5w" + } + }, + { + "if": "socket:socket:USB-A:output=10w", + "then": { + "en": "
USB to charge phones and small electronics
outputs at most 10w", + "nl": "
USB om GSMs en kleine electronica op te laden
levert een vermogen van maximaal 10w" + } + } + ], + "condition": { + "and": [ + "socket:USB-A~*", + "socket:USB-A!=0" + ] + } + }, + { + "id": "plugs-14", + "question": { + "en": "How much plugs of type
Bosch Active Connect with 3 pins and cable
are available here?", + "nl": "Hoeveel stekkers van type
Bosch Active Connect met 3 pinnen aan een kabel
heeft dit oplaadpunt?" + }, + "render": { + "en": "There are {socket:bosch_3pin} plugs of type
Bosch Active Connect with 3 pins and cable
available here", + "nl": "Hier zijn {socket:bosch_3pin} stekkers van het type
Bosch Active Connect met 3 pinnen aan een kabel
" + }, + "freeform": { + "key": "socket:bosch_3pin", + "type": "pnat" + }, + "condition": { + "and": [ + "socket:bosch_3pin~*", + "socket:bosch_3pin!=0" + ] + } + }, + { + "id": "voltage-14", + "question": { + "en": "What voltage do the plugs with
Bosch Active Connect with 3 pins and cable
offer?", + "nl": "Welke spanning levert de stekker van type
Bosch Active Connect met 3 pinnen aan een kabel
" + }, + "render": { + "en": "
Bosch Active Connect with 3 pins and cable
outputs {socket:bosch_3pin:voltage} volt", + "nl": "
Bosch Active Connect met 3 pinnen aan een kabel
heeft een spanning van {socket:bosch_3pin:voltage} volt" + }, + "freeform": { + "key": "socket:bosch_3pin:voltage", + "type": "pfloat" + }, + "mappings": [], + "condition": { + "and": [ + "socket:bosch_3pin~*", + "socket:bosch_3pin!=0" + ] + } + }, + { + "id": "current-14", + "question": { + "en": "What current do the plugs with
Bosch Active Connect with 3 pins and cable
offer?", + "nl": "Welke stroom levert de stekker van type
Bosch Active Connect met 3 pinnen aan een kabel
?" + }, + "render": { + "en": "
Bosch Active Connect with 3 pins and cable
outputs at most {socket:bosch_3pin:current}A", + "nl": "
Bosch Active Connect met 3 pinnen aan een kabel
levert een stroom van maximaal {socket:bosch_3pin:current}A" + }, + "freeform": { + "key": "socket:bosch_3pin:current", + "type": "pfloat" + }, + "mappings": [], + "condition": { + "and": [ + "socket:bosch_3pin~*", + "socket:bosch_3pin!=0" + ] + } + }, + { + "id": "power-output-14", + "question": { + "en": "What power output does a single plug of type
Bosch Active Connect with 3 pins and cable
offer?", + "nl": "Welk vermogen levert een enkele stekker van type
Bosch Active Connect met 3 pinnen aan een kabel
?" + }, + "render": { + "en": "
Bosch Active Connect with 3 pins and cable
outputs at most {socket:bosch_3pin:output}", + "nl": "
Bosch Active Connect met 3 pinnen aan een kabel
levert een vermogen van maximaal {socket:bosch_3pin:output}" + }, + "freeform": { + "key": "socket:bosch_3pin:output", + "type": "pfloat" + }, + "mappings": [], + "condition": { + "and": [ + "socket:bosch_3pin~*", + "socket:bosch_3pin!=0" + ] + } + }, + { + "id": "plugs-15", + "question": { + "en": "How much plugs of type
Bosch Active Connect with 5 pins and cable
are available here?", + "nl": "Hoeveel stekkers van type
Bosch Active Connect met 5 pinnen aan een kabel
heeft dit oplaadpunt?" + }, + "render": { + "en": "There are {socket:bosch_5pin} plugs of type
Bosch Active Connect with 5 pins and cable
available here", + "nl": "Hier zijn {socket:bosch_5pin} stekkers van het type
Bosch Active Connect met 5 pinnen aan een kabel
" + }, + "freeform": { + "key": "socket:bosch_5pin", + "type": "pnat" + }, + "condition": { + "and": [ + "socket:bosch_5pin~*", + "socket:bosch_5pin!=0" + ] + } + }, + { + "id": "voltage-15", + "question": { + "en": "What voltage do the plugs with
Bosch Active Connect with 5 pins and cable
offer?", + "nl": "Welke spanning levert de stekker van type
Bosch Active Connect met 5 pinnen aan een kabel
" + }, + "render": { + "en": "
Bosch Active Connect with 5 pins and cable
outputs {socket:bosch_5pin:voltage} volt", + "nl": "
Bosch Active Connect met 5 pinnen aan een kabel
heeft een spanning van {socket:bosch_5pin:voltage} volt" + }, + "freeform": { + "key": "socket:bosch_5pin:voltage", + "type": "pfloat" + }, + "mappings": [], + "condition": { + "and": [ + "socket:bosch_5pin~*", + "socket:bosch_5pin!=0" + ] + } + }, + { + "id": "current-15", + "question": { + "en": "What current do the plugs with
Bosch Active Connect with 5 pins and cable
offer?", + "nl": "Welke stroom levert de stekker van type
Bosch Active Connect met 5 pinnen aan een kabel
?" + }, + "render": { + "en": "
Bosch Active Connect with 5 pins and cable
outputs at most {socket:bosch_5pin:current}A", + "nl": "
Bosch Active Connect met 5 pinnen aan een kabel
levert een stroom van maximaal {socket:bosch_5pin:current}A" + }, + "freeform": { + "key": "socket:bosch_5pin:current", + "type": "pfloat" + }, + "mappings": [], + "condition": { + "and": [ + "socket:bosch_5pin~*", + "socket:bosch_5pin!=0" + ] + } + }, + { + "id": "power-output-15", + "question": { + "en": "What power output does a single plug of type
Bosch Active Connect with 5 pins and cable
offer?", + "nl": "Welk vermogen levert een enkele stekker van type
Bosch Active Connect met 5 pinnen aan een kabel
?" + }, + "render": { + "en": "
Bosch Active Connect with 5 pins and cable
outputs at most {socket:bosch_5pin:output}", + "nl": "
Bosch Active Connect met 5 pinnen aan een kabel
levert een vermogen van maximaal {socket:bosch_5pin:output}" + }, + "freeform": { + "key": "socket:bosch_5pin:output", + "type": "pfloat" + }, + "mappings": [], + "condition": { + "and": [ + "socket:bosch_5pin~*", + "socket:bosch_5pin!=0" + ] + } + }, + { + "id": "Authentication", + "question": { + "en": "What kind of authentication is available at the charging station?", + "it": "Quali sono gli orari di apertura di questa stazione di ricarica?", + "ja": "この充電ステーションはいつオープンしますか?", + "nb_NO": "Når åpnet denne ladestasjonen?", + "ru": "В какое время работает эта зарядная станция?", + "zh_Hant": "何時是充電站開放使用的時間?" + }, + "multiAnswer": true, + "mappings": [ + { + "if": "authentication:membership_card=yes", + "ifnot": "authentication:membership_card=no", + "then": { + "en": "Authentication by a membership card" + } + }, + { + "if": "authentication:app=yes", + "ifnot": "authentication:app=no", + "then": { + "en": "Authentication by an app" + } + }, + { + "if": "authentication:phone_call=yes", + "ifnot": "authentication:phone_call=no", + "then": { + "en": "Authentication via phone call is available" + } + }, + { + "if": "authentication:short_message=yes", + "ifnot": "authentication:short_message=no", + "then": { + "en": "Authentication via phone call is available" + } + }, + { + "if": "authentication:nfc=yes", + "ifnot": "authentication:nfc=no", + "then": { + "en": "Authentication via NFC is available" + } + }, + { + "if": "authentication:money_card=yes", + "ifnot": "authentication:money_card=no", + "then": { + "en": "Authentication via Money Card is available" + } + }, + { + "if": "authentication:debit_card=yes", + "ifnot": "authentication:debit_card=no", + "then": { + "en": "Authentication via debit card is available" + } + }, + { + "if": "authentication:none=yes", + "ifnot": "authentication:none=no", + "then": { + "en": "Charging here is (also) possible without authentication" + } + } + ] + }, + { + "id": "Auth phone", + "render": { + "en": "Authenticate by calling or SMS'ing to {authentication:phone_call:number}", + "it": "{network}", + "ja": "{network}", + "nb_NO": "{network}", + "ru": "{network}", + "zh_Hant": "{network}" + }, + "question": { + "en": "What's the phone number for authentication call or SMS?", + "it": "A quale rete appartiene questa stazione di ricarica?", + "ja": "この充電ステーションの運営チェーンはどこですか?", + "ru": "К какой сети относится эта станция?", + "zh_Hant": "充電站所屬的網路是?" + }, + "freeform": { + "key": "authentication:phone_call:number", + "type": "phone" + }, + "condition": { + "or": [ + "authentication:phone_call=yes", + "authentication:short_message=yes" + ] + }, + "it": { + "0": { + "then": "Non appartiene a una rete" + } + }, + "ja": { + "0": { + "then": "大規模な運営チェーンの一部ではない" + } + }, + "ru": { + "0": { + "then": "Не является частью более крупной сети" + } + }, + "zh_Hant": { + "0": { + "then": "不屬於大型網路" + } + } + }, + { + "id": "OH", + "render": "{opening_hours_table(opening_hours)}", + "freeform": { + "key": "opening_hours", + "type": "opening_hours" + }, + "question": { + "en": "When is this charging station opened?" + }, + "mappings": [ + { + "if": "opening_hours=24/7", + "then": { + "en": "24/7 opened (including holidays)" + } + } + ] + }, + { + "id": "fee/charge", + "question": { + "en": "How much does one have to pay to use this charging station?", + "nl": "Hoeveel kost het gebruik van dit oplaadpunt?" + }, + "freeform": { + "key": "charge", + "addExtraTags": [ + "fee=yes" + ] + }, + "render": { + "en": "Using this charging station costs {charge}", + "nl": "Dit oplaadpunt gebruiken kost {charge}" + }, + "mappings": [ + { + "if": { + "and": [ + "fee=no", + "charge=" + ] + }, + "then": { + "nl": "Gratis te gebruiken", + "en": "Free to use" + } + } + ] + }, + { + "id": "payment-options", + "builtin": "payment-options", + "override": { + "condition": { + "or": [ + "fee=yes", + "charge~*" + ] + }, + "mappings+": [ + { + "if": "payment:app=yes", + "ifnot": "payment:app=no", + "then": { + "en": "Payment is done using a dedicated app", + "nl": "Betalen via een app van het netwerk" + } + }, + { + "if": "payment:membership_card=yes", + "ifnot": "payment:membership_card=no", + "then": { + "en": "Payment is done using a membership card", + "nl": "Betalen via een lidkaart van het netwerk" + } + } + ] + } + }, + { + "id": "maxstay", + "question": { + "en": "What is the maximum amount of time one is allowed to stay here?", + "nl": "Hoelang mag een voertuig hier blijven staan?" + }, + "freeform": { + "key": "maxstay" + }, + "render": { + "en": "One can stay at most {canonical(maxstay)}", + "nl": "De maximale parkeertijd hier is {canonical(maxstay)}" + }, + "mappings": [ + { + "if": "maxstay=unlimited", + "then": { + "en": "No timelimit on leaving your vehicle here", + "nl": "Geen maximum parkeertijd" + } + } + ] + }, + { + "id": "Network", + "render": { + "en": "Part of the network {network}" + }, + "question": { + "en": "Is this charging station part of a network?" + }, + "freeform": { + "key": "network" + }, + "mappings": [ + { + "if": "no:network=yes", + "then": { + "en": "Not part of a bigger network" + } + }, + { + "if": "network=none", + "then": { + "en": "Not part of a bigger network" + }, + "hideInAnswer": true + }, + { + "if": "network=AeroVironment", + "then": "AeroVironment" + }, + { + "if": "network=Blink", + "then": "Blink" + }, + { + "if": "network=eVgo", + "then": "eVgo" + } + ] + }, + { + "id": "Operator", + "question": { + "en": "Who is the operator of this charging station?" + }, + "render": { + "en": "This charging station is operated by {operator}" + }, + "freeform": { + "key": "operator" + }, + "mappings": [ + { + "if": { + "and": [ + "network:={operator}" + ] + }, + "then": { + "en": "Actually, {operator} is the network" + }, + "addExtraTags": [ + "operator=" + ], + "hideInAnswer": "operator=" + } + ] + }, + { + "id": "phone", + "question": { + "en": "What number can one call if there is a problem with this charging station?" + }, + "render": { + "en": "In case of problems, call {phone}" + }, + "freeform": { + "key": "phone", + "type": "phone" + } + }, + { + "id": "email", + "question": { + "en": "What is the email address of the operator?" + }, + "render": { + "en": "In case of problems, send an email to {email}" + }, + "freeform": { + "key": "email", + "type": "email" + } + }, + { + "id": "website", + "question": { + "en": "What is the website of the operator?" + }, + "render": { + "en": "More info on {website}" + }, + "freeform": { + "key": "website", + "type": "url" + } + }, + "level", + { + "id": "ref", + "question": { + "en": "What is the reference number of this charging station?" + }, + "render": { + "en": "Reference number is {ref}" + }, + "freeform": { + "key": "ref" + } + }, + { + "id": "Operational status", + "question": { + "en": "Is this charging point in use?", + "nl": "Is dit oplaadpunt operationeel?" + }, + "mappings": [ + { + "if": "operational_status=broken", + "then": { + "en": "This charging station is broken", + "nl": "Dit oplaadpunt is kapot" + } + }, + { + "if": { + "and": [ + "planned:amenity=charging_station", + "amenity=" + ] + }, + "then": { + "en": "A charging station is planned here", + "nl": "Hier zal binnenkort een oplaadpunt gebouwd worden" + } + }, + { + "if": { + "and": [ + "construction:amenity=charging_station", + "amenity=" + ] + }, + "then": { + "en": "A charging station is constructed here", + "nl": "Hier wordt op dit moment een oplaadpunt gebouwd" + } + }, + { + "if": { + "and": [ + "disused:amenity=charging_station", + "amenity=" + ] + }, + "then": { + "en": "This charging station has beed permanently disabled and is not in use anymore but is still visible", + "nl": "Dit oplaadpunt is niet meer in gebruik maar is wel nog aanwezig" + } + }, + { + "if": { + "and": [ + "amenity=charging_station", + "operational_status=" + ] + }, + "then": { + "en": "This charging station works", + "nl": "Dit oplaadpunt werkt" + } + } + ] + }, + { + "id": "Parking:fee", + "question": { + "en": "Does one have to pay a parking fee while charging?" + }, + "mappings": [ + { + "if": "parking:fee=no", + "then": { + "en": "No additional parking cost while charging" + } + }, + { + "if": "parking:fee=yes", + "then": { + "en": "An additional parking fee should be paid while charging" + } + } + ] + } + ], + "icon": { + "render": "pin:#fff;./assets/themes/charging_stations/plug.svg", + "mappings": [ + { + "if": "bicycle=yes", + "then": "pin:#fff;./assets/themes/charging_stations/bicycle.svg" + }, + { + "if": { + "or": [ + "car=yes", + "motorcar=yes" + ] + }, + "then": "pin:#fff;./assets/themes/charging_stations/car.svg" } - } ] - } }, - { - "id": "maxstay", - "question": { - "en": "What is the maximum amount of time one is allowed to stay here?", - "nl": "Hoelang mag een voertuig hier blijven staan?" - }, - "freeform": { - "key": "maxstay" - }, - "render": { - "en": "One can stay at most {canonical(maxstay)}", - "nl": "De maximale parkeertijd hier is {canonical(maxstay)}" - }, - "mappings": [ + "iconOverlays": [ { - "if": "maxstay=unlimited", - "then": { - "en": "No timelimit on leaving your vehicle here", - "nl": "Geen maximum parkeertijd" - } + "if": { + "or": [ + "disused:amenity=charging_station", + "operational_status=broken" + ] + }, + "then": "cross_bottom_right:#c22;" + }, + { + "if": { + "or": [ + "proposed:amenity=charging_station", + "planned:amenity=charging_station" + ] + }, + "then": "./assets/layers/charging_station/under_construction.svg", + "badge": true + }, + { + "if": { + "and": [ + "bicycle=yes", + { + "or": [ + "motorcar=yes", + "car=yes" + ] + } + ] + }, + "then": "circle:#fff;./assets/themes/charging_stations/car.svg", + "badge": true } - ] + ], + "width": { + "render": "8" }, - { - "id": "Network", - "render": { - "en": "Part of the network {network}" - }, - "question": { - "en": "Is this charging station part of a network?" - }, - "freeform": { - "key": "network" - }, - "mappings": [ + "iconSize": { + "render": "50,50,bottom" + }, + "color": { + "render": "#00f" + }, + "presets": [ { - "if": "no:network=yes", - "then": { - "en": "Not part of a bigger network" - } - }, - { - "if": "network=none", - "then": { - "en": "Not part of a bigger network" - }, - "hideInAnswer": true - }, - { - "if": "network=AeroVironment", - "then": "AeroVironment" - }, - { - "if": "network=Blink", - "then": "Blink" - }, - { - "if": "network=eVgo", - "then": "eVgo" + "tags": [ + "amenity=charging_station" + ], + "title": { + "en": "Charging station" + }, + "preciseInput": { + "preferredBackground": "map" + } } - ] - }, - { - "id": "Operator", - "question": { - "en": "Who is the operator of this charging station?" - }, - "render": { - "en": "This charging station is operated by {operator}" - }, - "freeform": { - "key": "operator" - }, - "mappings": [ + ], + "wayHandling": 1, + "filter": [ { - "if": { - "and": [ - "network:={operator}" + "id": "vehicle-type", + "options": [ + { + "question": { + "en": "All vehicle types", + "nl": "Alle voertuigen" + } + }, + { + "question": { + "en": "Charging station for bicycles", + "nl": "Oplaadpunten voor fietsen" + }, + "osmTags": "bicycle=yes" + }, + { + "question": { + "en": "Charging station for cars", + "nl": "Oplaadpunten voor auto's" + }, + "osmTags": { + "or": [ + "car=yes", + "motorcar=yes" + ] + } + } + ] + }, + { + "id": "working", + "options": [ + { + "question": { + "en": "Only working charging stations" + }, + "osmTags": { + "and": [ + "operational_status!=broken", + "amenity=charging_station" + ] + } + } + ] + }, + { + "id": "connection_type", + "options": [ + { + "question": { + "en": "All connectors", + "nl": "Alle types" + } + }, + { + "question": { + "en": "Has a
Schuko wall plug without ground pin (CEE7/4 type F)
connector", + "nl": "Heeft een
Schuko stekker zonder aardingspin (CEE7/4 type F)
" + }, + "osmTags": "socket:schuko~*" + }, + { + "question": { + "en": "Has a
European wall plug with ground pin (CEE7/4 type E)
connector", + "nl": "Heeft een
Europese stekker met aardingspin (CEE7/4 type E)
" + }, + "osmTags": "socket:typee~*" + }, + { + "question": { + "en": "Has a
Chademo
connector", + "nl": "Heeft een
Chademo
" + }, + "osmTags": "socket:chademo~*" + }, + { + "question": { + "en": "Has a
Type 1 with cable (J1772)
connector", + "nl": "Heeft een
Type 1 met kabel (J1772)
" + }, + "osmTags": "socket:type1_cable~*" + }, + { + "question": { + "en": "Has a
Type 1 without cable (J1772)
connector", + "nl": "Heeft een
Type 1 zonder kabel (J1772)
" + }, + "osmTags": "socket:type1~*" + }, + { + "question": { + "en": "Has a
Type 1 CCS (aka Type 1 Combo)
connector", + "nl": "Heeft een
Type 1 CCS (ook gekend als Type 1 Combo)
" + }, + "osmTags": "socket:type1_combo~*" + }, + { + "question": { + "en": "Has a
Tesla Supercharger
connector", + "nl": "Heeft een
Tesla Supercharger
" + }, + "osmTags": "socket:tesla_supercharger~*" + }, + { + "question": { + "en": "Has a
Type 2 (mennekes)
connector", + "nl": "Heeft een
Type 2 (mennekes)
" + }, + "osmTags": "socket:type2~*" + }, + { + "question": { + "en": "Has a
Type 2 CCS (mennekes)
connector", + "nl": "Heeft een
Type 2 CCS (mennekes)
" + }, + "osmTags": "socket:type2_combo~*" + }, + { + "question": { + "en": "Has a
Type 2 with cable (mennekes)
connector", + "nl": "Heeft een
Type 2 met kabel (J1772)
" + }, + "osmTags": "socket:type2_cable~*" + }, + { + "question": { + "en": "Has a
Tesla Supercharger CCS (a branded type2_css)
connector", + "nl": "Heeft een
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
" + }, + "osmTags": "socket:tesla_supercharger_ccs~*" + }, + { + "question": { + "en": "Has a
Tesla Supercharger (destination)
connector", + "nl": "Heeft een
Tesla Supercharger (destination)
" + }, + "osmTags": "socket:tesla_destination~*" + }, + { + "question": { + "en": "Has a
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
connector", + "nl": "Heeft een
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
" + }, + "osmTags": "socket:tesla_destination~*" + }, + { + "question": { + "en": "Has a
USB to charge phones and small electronics
connector", + "nl": "Heeft een
USB om GSMs en kleine electronica op te laden
" + }, + "osmTags": "socket:USB-A~*" + }, + { + "question": { + "en": "Has a
Bosch Active Connect with 3 pins and cable
connector", + "nl": "Heeft een
Bosch Active Connect met 3 pinnen aan een kabel
" + }, + "osmTags": "socket:bosch_3pin~*" + }, + { + "question": { + "en": "Has a
Bosch Active Connect with 5 pins and cable
connector", + "nl": "Heeft een
Bosch Active Connect met 5 pinnen aan een kabel
" + }, + "osmTags": "socket:bosch_5pin~*" + } ] - }, - "then": { - "en": "Actually, {operator} is the network" - }, - "addExtraTags": [ - "operator=" - ], - "hideInAnswer": "operator=" } - ] - }, - { - "id": "phone", - "question": { - "en": "What number can one call if there is a problem with this charging station?" - }, - "render": { - "en": "In case of problems, call {phone}" - }, - "freeform": { - "key": "phone", - "type": "phone" - } - }, - { - "id": "email", - "question": { - "en": "What is the email address of the operator?" - }, - "render": { - "en": "In case of problems, send an email to {email}" - }, - "freeform": { - "key": "email", - "type": "email" - } - }, - { - "id": "website", - "question": { - "en": "What is the website of the operator?" - }, - "render": { - "en": "More info on {website}" - }, - "freeform": { - "key": "website", - "type": "url" - } - }, - "level", - { - "id": "ref", - "question": { - "en": "What is the reference number of this charging station?" - }, - "render": { - "en": "Reference number is {ref}" - }, - "freeform": { - "key": "ref" - } - }, - { - "id": "Operational status", - "question": { - "en": "Is this charging point in use?", - "nl": "Is dit oplaadpunt operationeel?" - }, - "mappings": [ + ], + "units": [ { - "if": "operational_status=broken", - "then": { - "en": "This charging station is broken", - "nl": "Dit oplaadpunt is kapot" - } + "appliesToKey": [ + "maxstay" + ], + "applicableUnits": [ + { + "canonicalDenomination": "minutes", + "canonicalDenominationSingular": "minute", + "alternativeDenomination": [ + "m", + "min", + "mins", + "minuten", + "mns" + ], + "human": { + "en": " minutes", + "nl": " minuten" + }, + "humanSingular": { + "en": " minute", + "nl": " minuut" + } + }, + { + "canonicalDenomination": "hours", + "canonicalDenominationSingular": "hour", + "alternativeDenomination": [ + "h", + "hrs", + "hours", + "u", + "uur", + "uren" + ], + "human": { + "en": " hours", + "nl": " uren" + }, + "humanSingular": { + "en": " hour", + "nl": " uur" + } + }, + { + "canonicalDenomination": "days", + "canonicalDenominationSingular": "day", + "alternativeDenomination": [ + "dys", + "dagen", + "dag" + ], + "human": { + "en": " days", + "nl": " day" + }, + "humanSingular": { + "en": " day", + "nl": " dag" + } + } + ] }, { - "if": { - "and": [ - "planned:amenity=charging_station", - "amenity=" - ] - }, - "then": { - "en": "A charging station is planned here", - "nl": "Hier zal binnenkort een oplaadpunt gebouwd worden" - } + "appliesToKey": [ + "socket:schuko:voltage", + "socket:typee:voltage", + "socket:chademo:voltage", + "socket:type1_cable:voltage", + "socket:type1:voltage", + "socket:type1_combo:voltage", + "socket:tesla_supercharger:voltage", + "socket:type2:voltage", + "socket:type2_combo:voltage", + "socket:type2_cable:voltage", + "socket:tesla_supercharger_ccs:voltage", + "socket:tesla_destination:voltage", + "socket:tesla_destination:voltage", + "socket:USB-A:voltage", + "socket:bosch_3pin:voltage", + "socket:bosch_5pin:voltage" + ], + "applicableUnits": [ + { + "canonicalDenomination": "V", + "alternativeDenomination": [ + "v", + "volt", + "voltage", + "V", + "Volt" + ], + "human": { + "en": "Volts", + "nl": "volt" + } + } + ], + "eraseInvalidValues": true }, { - "if": { - "and": [ - "construction:amenity=charging_station", - "amenity=" - ] - }, - "then": { - "en": "A charging station is constructed here", - "nl": "Hier wordt op dit moment een oplaadpunt gebouwd" - } + "appliesToKey": [ + "socket:schuko:current", + "socket:typee:current", + "socket:chademo:current", + "socket:type1_cable:current", + "socket:type1:current", + "socket:type1_combo:current", + "socket:tesla_supercharger:current", + "socket:type2:current", + "socket:type2_combo:current", + "socket:type2_cable:current", + "socket:tesla_supercharger_ccs:current", + "socket:tesla_destination:current", + "socket:tesla_destination:current", + "socket:USB-A:current", + "socket:bosch_3pin:current", + "socket:bosch_5pin:current" + ], + "applicableUnits": [ + { + "canonicalDenomination": "A", + "alternativeDenomination": [ + "a", + "amp", + "amperage", + "A" + ], + "human": { + "en": "A", + "nl": "A" + } + } + ], + "eraseInvalidValues": true }, { - "if": { - "and": [ - "disused:amenity=charging_station", - "amenity=" - ] - }, - "then": { - "en": "This charging station has beed permanently disabled and is not in use anymore but is still visible", - "nl": "Dit oplaadpunt is niet meer in gebruik maar is wel nog aanwezig" - } - }, - { - "if": { - "and": [ - "amenity=charging_station", - "operational_status=" - ] - }, - "then": { - "en": "This charging station works", - "nl": "Dit oplaadpunt werkt" - } + "appliesToKey": [ + "socket:schuko:output", + "socket:typee:output", + "socket:chademo:output", + "socket:type1_cable:output", + "socket:type1:output", + "socket:type1_combo:output", + "socket:tesla_supercharger:output", + "socket:type2:output", + "socket:type2_combo:output", + "socket:type2_cable:output", + "socket:tesla_supercharger_ccs:output", + "socket:tesla_destination:output", + "socket:tesla_destination:output", + "socket:USB-A:output", + "socket:bosch_3pin:output", + "socket:bosch_5pin:output" + ], + "applicableUnits": [ + { + "canonicalDenomination": "kW", + "alternativeDenomination": [ + "kilowatt" + ], + "human": { + "en": "kilowatt", + "nl": "kilowatt" + } + }, + { + "canonicalDenomination": "mW", + "alternativeDenomination": [ + "megawatt" + ], + "human": { + "en": "megawatt", + "nl": "megawatt" + } + } + ], + "eraseInvalidValues": true } - ] - }, - { - "id": "Parking:fee", - "question": { - "en": "Does one have to pay a parking fee while charging?" - }, - "mappings": [ - { - "if": "parking:fee=no", - "then": { - "en": "No additional parking cost while charging" - } - }, - { - "if": "parking:fee=yes", - "then": { - "en": "An additional parking fee should be paid while charging" - } - } - ] - } - ], - "icon": { - "render": "pin:#fff;./assets/themes/charging_stations/plug.svg", - "mappings": [ - { - "if": "bicycle=yes", - "then": "pin:#fff;./assets/themes/charging_stations/bicycle.svg" - }, - { - "if": { - "or": [ - "car=yes", - "motorcar=yes" - ] - }, - "then": "pin:#fff;./assets/themes/charging_stations/car.svg" - } ] - }, - "iconOverlays": [ - { - "if": { - "or": [ - "disused:amenity=charging_station", - "operational_status=broken" - ] - }, - "then": "cross_bottom_right:#c22;" - }, - { - "if": { - "or": [ - "proposed:amenity=charging_station", - "planned:amenity=charging_station" - ] - }, - "then": "./assets/layers/charging_station/under_construction.svg", - "badge": true - }, - { - "if": { - "and": [ - "bicycle=yes", - { - "or": [ - "motorcar=yes", - "car=yes" - ] - } - ] - }, - "then": "circle:#fff;./assets/themes/charging_stations/car.svg", - "badge": true - } - ], - "width": { - "render": "8" - }, - "iconSize": { - "render": "50,50,bottom" - }, - "color": { - "render": "#00f" - }, - "presets": [ - { - "tags": [ - "amenity=charging_station" - ], - "title": { - "en": "Charging station" - }, - "preciseInput": { - "preferredBackground": "map" - } - } - ], - "wayHandling": 1, - "filter": [ - { - "id": "vehicle-type", - "options": [ - { - "question": { - "en": "All vehicle types", - "nl": "Alle voertuigen" - } - }, - { - "question": { - "en": "Charging station for bicycles", - "nl": "Oplaadpunten voor fietsen" - }, - "osmTags": "bicycle=yes" - }, - { - "question": { - "en": "Charging station for cars", - "nl": "Oplaadpunten voor auto's" - }, - "osmTags": { - "or": [ - "car=yes", - "motorcar=yes" - ] - } - } - ] - }, - { - "id": "working", - "options": [ - { - "question": { - "en": "Only working charging stations" - }, - "osmTags": { - "and": [ - "operational_status!=broken", - "amenity=charging_station" - ] - } - } - ] - }, - { - "id": "connection_type", - "options": [ - { - "question": { - "en": "All connectors", - "nl": "Alle types" - } - }, - { - "question": { - "en": "Has a
Schuko wall plug without ground pin (CEE7/4 type F)
connector", - "nl": "Heeft een
Schuko stekker zonder aardingspin (CEE7/4 type F)
" - }, - "osmTags": "socket:schuko~*" - }, - { - "question": { - "en": "Has a
European wall plug with ground pin (CEE7/4 type E)
connector", - "nl": "Heeft een
Europese stekker met aardingspin (CEE7/4 type E)
" - }, - "osmTags": "socket:typee~*" - }, - { - "question": { - "en": "Has a
Chademo
connector", - "nl": "Heeft een
Chademo
" - }, - "osmTags": "socket:chademo~*" - }, - { - "question": { - "en": "Has a
Type 1 with cable (J1772)
connector", - "nl": "Heeft een
Type 1 met kabel (J1772)
" - }, - "osmTags": "socket:type1_cable~*" - }, - { - "question": { - "en": "Has a
Type 1 without cable (J1772)
connector", - "nl": "Heeft een
Type 1 zonder kabel (J1772)
" - }, - "osmTags": "socket:type1~*" - }, - { - "question": { - "en": "Has a
Type 1 CCS (aka Type 1 Combo)
connector", - "nl": "Heeft een
Type 1 CCS (ook gekend als Type 1 Combo)
" - }, - "osmTags": "socket:type1_combo~*" - }, - { - "question": { - "en": "Has a
Tesla Supercharger
connector", - "nl": "Heeft een
Tesla Supercharger
" - }, - "osmTags": "socket:tesla_supercharger~*" - }, - { - "question": { - "en": "Has a
Type 2 (mennekes)
connector", - "nl": "Heeft een
Type 2 (mennekes)
" - }, - "osmTags": "socket:type2~*" - }, - { - "question": { - "en": "Has a
Type 2 CCS (mennekes)
connector", - "nl": "Heeft een
Type 2 CCS (mennekes)
" - }, - "osmTags": "socket:type2_combo~*" - }, - { - "question": { - "en": "Has a
Type 2 with cable (mennekes)
connector", - "nl": "Heeft een
Type 2 met kabel (J1772)
" - }, - "osmTags": "socket:type2_cable~*" - }, - { - "question": { - "en": "Has a
Tesla Supercharger CCS (a branded type2_css)
connector", - "nl": "Heeft een
Tesla Supercharger CCS (een type2 CCS met Tesla-logo)
" - }, - "osmTags": "socket:tesla_supercharger_ccs~*" - }, - { - "question": { - "en": "Has a
Tesla Supercharger (destination)
connector", - "nl": "Heeft een
Tesla Supercharger (destination)
" - }, - "osmTags": "socket:tesla_destination~*" - }, - { - "question": { - "en": "Has a
Tesla supercharger (destination (A Type 2 with cable branded as tesla)
connector", - "nl": "Heeft een
Tesla supercharger (destination (Een Type 2 met kabel en Tesla-logo)
" - }, - "osmTags": "socket:tesla_destination~*" - }, - { - "question": { - "en": "Has a
USB to charge phones and small electronics
connector", - "nl": "Heeft een
USB om GSMs en kleine electronica op te laden
" - }, - "osmTags": "socket:USB-A~*" - }, - { - "question": { - "en": "Has a
Bosch Active Connect with 3 pins and cable
connector", - "nl": "Heeft een
Bosch Active Connect met 3 pinnen aan een kabel
" - }, - "osmTags": "socket:bosch_3pin~*" - }, - { - "question": { - "en": "Has a
Bosch Active Connect with 5 pins and cable
connector", - "nl": "Heeft een
Bosch Active Connect met 5 pinnen aan een kabel
" - }, - "osmTags": "socket:bosch_5pin~*" - } - ] - } - ], - "units": [ - { - "appliesToKey": [ - "maxstay" - ], - "applicableUnits": [ - { - "canonicalDenomination": "minutes", - "canonicalDenominationSingular": "minute", - "alternativeDenomination": [ - "m", - "min", - "mins", - "minuten", - "mns" - ], - "human": { - "en": " minutes", - "nl": " minuten" - }, - "humanSingular": { - "en": " minute", - "nl": " minuut" - } - }, - { - "canonicalDenomination": "hours", - "canonicalDenominationSingular": "hour", - "alternativeDenomination": [ - "h", - "hrs", - "hours", - "u", - "uur", - "uren" - ], - "human": { - "en": " hours", - "nl": " uren" - }, - "humanSingular": { - "en": " hour", - "nl": " uur" - } - }, - { - "canonicalDenomination": "days", - "canonicalDenominationSingular": "day", - "alternativeDenomination": [ - "dys", - "dagen", - "dag" - ], - "human": { - "en": " days", - "nl": " day" - }, - "humanSingular": { - "en": " day", - "nl": " dag" - } - } - ] - }, - { - "appliesToKey": [ - "socket:schuko:voltage", - "socket:typee:voltage", - "socket:chademo:voltage", - "socket:type1_cable:voltage", - "socket:type1:voltage", - "socket:type1_combo:voltage", - "socket:tesla_supercharger:voltage", - "socket:type2:voltage", - "socket:type2_combo:voltage", - "socket:type2_cable:voltage", - "socket:tesla_supercharger_ccs:voltage", - "socket:tesla_destination:voltage", - "socket:tesla_destination:voltage", - "socket:USB-A:voltage", - "socket:bosch_3pin:voltage", - "socket:bosch_5pin:voltage" - ], - "applicableUnits": [ - { - "canonicalDenomination": "V", - "alternativeDenomination": [ - "v", - "volt", - "voltage", - "V", - "Volt" - ], - "human": { - "en": "Volts", - "nl": "volt" - } - } - ], - "eraseInvalidValues": true - }, - { - "appliesToKey": [ - "socket:schuko:current", - "socket:typee:current", - "socket:chademo:current", - "socket:type1_cable:current", - "socket:type1:current", - "socket:type1_combo:current", - "socket:tesla_supercharger:current", - "socket:type2:current", - "socket:type2_combo:current", - "socket:type2_cable:current", - "socket:tesla_supercharger_ccs:current", - "socket:tesla_destination:current", - "socket:tesla_destination:current", - "socket:USB-A:current", - "socket:bosch_3pin:current", - "socket:bosch_5pin:current" - ], - "applicableUnits": [ - { - "canonicalDenomination": "A", - "alternativeDenomination": [ - "a", - "amp", - "amperage", - "A" - ], - "human": { - "en": "A", - "nl": "A" - } - } - ], - "eraseInvalidValues": true - }, - { - "appliesToKey": [ - "socket:schuko:output", - "socket:typee:output", - "socket:chademo:output", - "socket:type1_cable:output", - "socket:type1:output", - "socket:type1_combo:output", - "socket:tesla_supercharger:output", - "socket:type2:output", - "socket:type2_combo:output", - "socket:type2_cable:output", - "socket:tesla_supercharger_ccs:output", - "socket:tesla_destination:output", - "socket:tesla_destination:output", - "socket:USB-A:output", - "socket:bosch_3pin:output", - "socket:bosch_5pin:output" - ], - "applicableUnits": [ - { - "canonicalDenomination": "kW", - "alternativeDenomination": [ - "kilowatt" - ], - "human": { - "en": "kilowatt", - "nl": "kilowatt" - } - }, - { - "canonicalDenomination": "mW", - "alternativeDenomination": [ - "megawatt" - ], - "human": { - "en": "megawatt", - "nl": "megawatt" - } - } - ], - "eraseInvalidValues": true - } - ] } \ No newline at end of file diff --git a/assets/themes/natuurpunt/natuurpunt.json b/assets/themes/natuurpunt/natuurpunt.json index ba59302079..ecbe8afe95 100644 --- a/assets/themes/natuurpunt/natuurpunt.json +++ b/assets/themes/natuurpunt/natuurpunt.json @@ -263,5 +263,6 @@ } } } - ] + ], + "enableIframePopout": false } \ No newline at end of file diff --git a/assets/themes/personal/personal.json b/assets/themes/personal/personal.json index 2896e5fbc4..d0218d014e 100644 --- a/assets/themes/personal/personal.json +++ b/assets/themes/personal/personal.json @@ -45,6 +45,7 @@ "startLon": 0, "startZoom": 16, "widenFactor": 1.2, + "overpassMaxZoom": 0, "layers": [], "roamingRenderings": [] } \ No newline at end of file diff --git a/index.ts b/index.ts index 7126a0a7cc..027cb1b56b 100644 --- a/index.ts +++ b/index.ts @@ -1,27 +1,24 @@ -import {AllKnownLayouts} from "./Customizations/AllKnownLayouts"; import {FixedUiElement} from "./UI/Base/FixedUiElement"; -import {InitUiElements} from "./InitUiElements"; import {QueryParameters} from "./Logic/Web/QueryParameters"; -import {UIEventSource} from "./Logic/UIEventSource"; -import * as $ from "jquery"; -import MoreScreen from "./UI/BigComponents/MoreScreen"; -import State from "./State"; import Combine from "./UI/Base/Combine"; -import Translations from "./UI/i18n/Translations"; import ValidatedTextField from "./UI/Input/ValidatedTextField"; import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers"; -import LayoutConfig from "./Models/ThemeConfig/LayoutConfig"; -import Constants from "./Models/Constants"; import MinimapImplementation from "./UI/Base/MinimapImplementation"; import CountryCoder from "latlon2country/index"; import SimpleMetaTagger from "./Logic/SimpleMetaTagger"; +import {Utils} from "./Utils"; +import AllThemesGui from "./UI/AllThemesGui"; +import DetermineLayout from "./Logic/DetermineLayout"; +import LayoutConfig from "./Models/ThemeConfig/LayoutConfig"; +import DefaultGUI, {DefaultGuiState} from "./UI/DefaultGUI"; +import State from "./State"; MinimapImplementation.initialize() // Workaround for a stupid crash: inject some functions which would give stupid circular dependencies or crash the other nodejs scripts ValidatedTextField.bestLayerAt = (location, layerPref) => AvailableBaseLayers.SelectBestLayerAccordingTo(location, layerPref) SimpleMetaTagger.coder = new CountryCoder("https://pietervdvn.github.io/latlon2country/"); +Utils.DisableLongPresses() -let defaultLayout = "" // --------------------- Special actions based on the parameters ----------------- // @ts-ignore if (location.href.startsWith("http://buurtnatuur.be")) { @@ -30,60 +27,65 @@ if (location.href.startsWith("http://buurtnatuur.be")) { } -if (location.href.indexOf("buurtnatuur.be") >= 0) { - defaultLayout = "buurtnatuur" -} +class Init { -let testing: UIEventSource; -if (QueryParameters.GetQueryParameter("backend", undefined).data !== "osm-test" && - (location.hostname === "localhost" || location.hostname === "127.0.0.1")) { - testing = QueryParameters.GetQueryParameter("test", "true"); - // Set to true if testing and changes should NOT be saved - testing.setData(testing.data ?? "true") - // If you have a testfile somewhere, enable this to spoof overpass - // This should be hosted independantly, e.g. with `cd assets; webfsd -p 8080` + a CORS plugin to disable cors rules - // Overpass.testUrl = "http://127.0.0.1:8080/streetwidths.geojson"; -} else { - testing = QueryParameters.GetQueryParameter("test", "false"); -} + public static Init(layoutToUse: LayoutConfig, encoded: string) { + + if(layoutToUse === null){ + // Something went wrong, error message is already on screen + return; + } + + if (layoutToUse === undefined) { + // No layout found + new AllThemesGui() + return; + } + + // Workaround/legacy to keep the old paramters working as I renamed some of them + if (layoutToUse?.id === "cyclofix") { + const legacy = QueryParameters.GetQueryParameter("layer-bike_shops", "true", "Legacy - keep De Fietsambassade working"); + const correct = QueryParameters.GetQueryParameter("layer-bike_shop", "true", "Legacy - keep De Fietsambassade working") + if (legacy.data !== "true") { + correct.setData(legacy.data) + } + console.log("layer-bike_shop toggles: legacy:", legacy.data, "new:", correct.data) + + const legacyCafe = QueryParameters.GetQueryParameter("layer-bike_cafes", "true", "Legacy - keep De Fietsambassade working") + const correctCafe = QueryParameters.GetQueryParameter("layer-bike_cafe", "true", "Legacy - keep De Fietsambassade working") + if (legacyCafe.data !== "true") { + correctCafe.setData(legacy.data) + } + } -// ----------------- SELECT THE RIGHT Theme ----------------- + const guiState = new DefaultGuiState() + State.state = new State(layoutToUse); + // This 'leaks' the global state via the window object, useful for debugging + // @ts-ignore + window.mapcomplete_state = State.state; + new DefaultGUI(State.state, guiState) + + + if (encoded !== undefined && encoded.length > 10) { + // We save the layout to the user settings and local storage + State.state.osmConnection.OnLoggedIn(() => { + State.state.osmConnection + .GetLongPreference("installed-theme-" + layoutToUse.id) + .setData(encoded); + }); + + } -const path = window.location.pathname.split("/").slice(-1)[0]; -if (path !== "index.html" && path !== "") { - defaultLayout = path; - if (path.endsWith(".html")) { - defaultLayout = path.substr(0, path.length - 5); } - console.log("Using layout", defaultLayout); -} -defaultLayout = QueryParameters.GetQueryParameter("layout", defaultLayout, "The layout to load into MapComplete").data; -let layoutToUse: LayoutConfig = AllKnownLayouts.allKnownLayouts.get(defaultLayout.toLowerCase()); -const userLayoutParam = QueryParameters.GetQueryParameter("userlayout", "false", "If not 'false', a custom (non-official) theme is loaded. This custom layout can be done in multiple ways: \n\n- The hash of the URL contains a base64-encoded .json-file containing the theme definition\n- The hash of the URL contains a lz-compressed .json-file, as generated by the custom theme generator\n- The parameter itself is an URL, in which case that URL will be downloaded. It should point to a .json of a theme"); -// Workaround/legacy to keep the old paramters working as I renamed some of them -if (layoutToUse?.id === "cyclofix") { - const legacy = QueryParameters.GetQueryParameter("layer-bike_shops", "true", "Legacy - keep De Fietsambassade working"); - const correct = QueryParameters.GetQueryParameter("layer-bike_shop", "true", "Legacy - keep De Fietsambassade working") - if (legacy.data !== "true") { - correct.setData(legacy.data) - } - console.log("layer-bike_shop toggles: legacy:", legacy.data, "new:", correct.data) - - const legacyCafe = QueryParameters.GetQueryParameter("layer-bike_cafes", "true", "Legacy - keep De Fietsambassade working") - const correctCafe = QueryParameters.GetQueryParameter("layer-bike_cafe", "true", "Legacy - keep De Fietsambassade working") - if (legacyCafe.data !== "true") { - correctCafe.setData(legacy.data) - } } -const layoutFromBase64 = decodeURIComponent(userLayoutParam.data); - +document.getElementById("decoration-desktop").remove(); new Combine(["Initializing...
", new FixedUiElement("If this message persist, something went wrong - click here to try again") .SetClass("link-underline small") @@ -93,71 +95,13 @@ new Combine(["Initializing...
", })]) .AttachTo("centermessage"); // Add an initialization and reset button if something goes wrong -document.getElementById("decoration-desktop").remove(); -if (layoutFromBase64.startsWith("http")) { - const link = layoutFromBase64; - console.log("Downloading map theme from ", link); - new FixedUiElement(`Downloading the theme from the link...`) - .AttachTo("centermessage"); +DetermineLayout.GetLayout().then(value => { + console.log("Got ", value) + Init.Init(value[0], value[1]) +}).catch(err => { + console.error(err) +}) - $.ajax({ - url: link, - success: (data) => { - try { - console.log("Received ", data) - let parsed = data; - if (typeof parsed == "string") { - parsed = JSON.parse(data); - } else { - data = JSON.stringify(parsed) // De wereld op zijn kop - } - // Overwrite the id to the wiki:value - parsed.id = link; - const layout = new LayoutConfig(parsed, false).patchImages(link, data); - InitUiElements.InitAll(layout, layoutFromBase64, testing, layoutFromBase64, btoa(data)); - } catch (e) { - new FixedUiElement(`${link} is invalid:
${e}
Go back`) - .SetClass("clickable") - .AttachTo("centermessage"); - console.error("Could not parse the text", data) - throw e; - } - }, - }).fail((_, textstatus, error) => { - console.error("Could not download the wiki theme:", textstatus, error) - new FixedUiElement(`${link} is invalid:
Could not download - wrong URL?
` + - error + - "Go back") - .SetClass("clickable") - .AttachTo("centermessage"); - }); - -} else if (layoutFromBase64 !== "false") { - let [layoutToUse, encoded] = InitUiElements.LoadLayoutFromHash(userLayoutParam); - InitUiElements.InitAll(layoutToUse, layoutFromBase64, testing, defaultLayout, encoded); -} else if (layoutToUse !== undefined) { - // This is the default case: a builtin theme - InitUiElements.InitAll(layoutToUse, layoutFromBase64, testing, defaultLayout); -} else { - // We fall through: no theme loaded: just show an overview of layouts - new FixedUiElement("").AttachTo("centermessage") - State.state = new State(undefined); - new Combine([new MoreScreen(true), - Translations.t.general.aboutMapcomplete.SetClass("link-underline"), - new FixedUiElement("v" + Constants.vNumber) - ]).SetClass("block m-5 lg:w-3/4 lg:ml-40") - .SetStyle("pointer-events: all;") - .AttachTo("topleft-tools"); -} -// Remove all context event listeners on mobile to prevent long presses -window.addEventListener('contextmenu', (e) => { // Not compatible with IE < 9 - - if (e.target["nodeName"] === "INPUT") { - return; - } - e.preventDefault(); - return false; -}, false); diff --git a/langs/en.json b/langs/en.json index 11af03dd40..b6463cd732 100644 --- a/langs/en.json +++ b/langs/en.json @@ -124,7 +124,9 @@ "intro": "

More thematic maps?

Do you enjoy collecting geodata?
There are more themes available.", "requestATheme": "If you want a custom-built theme, request it in the issue tracker", "streetcomplete": "Another, similar application is StreetComplete.", - "createYourOwnTheme": "Create your own MapComplete theme from scratch" + "createYourOwnTheme": "Create your own MapComplete theme from scratch", + "previouslyHiddenTitle": "Previously visited hidden themes", + "hiddenExplanation": "These themes are only visible if you know the link..." }, "sharescreen": { "intro": "

Share this map

Share this map by copying the link below and sending it to friends and family:", diff --git a/langs/themes/en.json b/langs/themes/en.json index 30fa6d574f..c0bd581ab0 100644 --- a/langs/themes/en.json +++ b/langs/themes/en.json @@ -1271,6 +1271,11 @@ } }, "shortDescription": "Help to build an open dataset of UK addresses", + "tileLayerSources": { + "0": { + "name": "Property boundaries by osmuk.org" + } + }, "title": "UK Addresses" }, "waste_basket": {