2020-08-30 01:13:18 +02:00
import { Layout } from "../Layout" ;
import { LayoutConfigJson } from "./LayoutConfigJson" ;
import { AndOrTagConfigJson } from "./TagConfigJson" ;
2020-09-02 11:37:34 +02:00
import { And , Or , RegexTag , Tag , TagsFilter } from "../../Logic/Tags" ;
2020-08-30 01:13:18 +02:00
import { TagRenderingConfigJson } from "./TagRenderingConfigJson" ;
import { TagRenderingOptions } from "../TagRenderingOptions" ;
import Translation from "../../UI/i18n/Translation" ;
import { LayerConfigJson } from "./LayerConfigJson" ;
import { LayerDefinition , Preset } from "../LayerDefinition" ;
import { TagDependantUIElementConstructor } from "../UIElementConstructor" ;
import Combine from "../../UI/Base/Combine" ;
2020-08-31 02:59:47 +02:00
import * as drinkingWater from "../../assets/layers/drinking_water/drinking_water.json" ;
import * as ghostbikes from "../../assets/layers/ghost_bike/ghost_bike.json"
import * as viewpoint from "../../assets/layers/viewpoint/viewpoint.json"
2020-09-03 02:05:09 +02:00
import * as bike_parking from "../../assets/layers/bike_parking/bike_parking.json"
2020-09-05 17:43:30 +02:00
import * as bike_repair_station from "../../assets/layers/bike_repair_station/bike_repair_station.json"
2020-09-03 19:05:18 +02:00
import * as birdhides from "../../assets/layers/bird_hide/birdhides.json"
2020-09-09 18:42:13 +02:00
import * as nature_reserve from "../../assets/layers/nature_reserve/nature_reserve.json"
2020-09-11 02:14:16 +02:00
import * as bike_cafes from "../../assets/layers/bike_cafe/bike_cafes.json"
2020-09-11 02:42:22 +02:00
import * as cycling_themed_objects from "../../assets/layers/cycling_themed_object/cycling_themed_objects.json"
2020-09-16 01:29:38 +02:00
import * as bike_shops from "../../assets/layers/bike_shop/bike_shop.json"
2020-09-19 13:37:04 +02:00
import * as maps from "../../assets/layers/maps/maps.json"
2020-09-20 23:01:08 +02:00
import * as information_boards from "../../assets/layers/information_board/information_board.json"
2020-08-31 02:59:47 +02:00
import { Utils } from "../../Utils" ;
2020-09-10 19:33:06 +02:00
import ImageCarouselWithUploadConstructor from "../../UI/Image/ImageCarouselWithUpload" ;
import { ImageCarouselConstructor } from "../../UI/Image/ImageCarousel" ;
2020-10-02 19:00:24 +02:00
import State from "../../State" ;
2020-08-30 01:13:18 +02:00
export class FromJSON {
2020-08-31 02:59:47 +02:00
public static sharedLayers : Map < string , LayerDefinition > = FromJSON . getSharedLayers ( ) ;
private static getSharedLayers() {
2020-09-10 19:33:06 +02:00
// We inject a function into state while we are busy
State . FromBase64 = FromJSON . FromBase64 ;
2020-08-31 02:59:47 +02:00
const sharedLayers = new Map < string , LayerDefinition > ( ) ;
const sharedLayersList = [
FromJSON . Layer ( drinkingWater ) ,
FromJSON . Layer ( ghostbikes ) ,
FromJSON . Layer ( viewpoint ) ,
2020-09-03 02:05:09 +02:00
FromJSON . Layer ( bike_parking ) ,
2020-09-05 17:43:30 +02:00
FromJSON . Layer ( bike_repair_station ) ,
2020-09-03 19:05:18 +02:00
FromJSON . Layer ( birdhides ) ,
2020-09-09 18:42:13 +02:00
FromJSON . Layer ( nature_reserve ) ,
2020-09-11 02:14:16 +02:00
FromJSON . Layer ( bike_cafes ) ,
2020-09-16 01:29:38 +02:00
FromJSON . Layer ( cycling_themed_objects ) ,
2020-09-19 13:37:04 +02:00
FromJSON . Layer ( bike_shops ) ,
2020-09-20 23:01:08 +02:00
FromJSON . Layer ( maps ) ,
FromJSON . Layer ( information_boards )
2020-08-31 02:59:47 +02:00
] ;
for ( const layer of sharedLayersList ) {
sharedLayers . set ( layer . id , layer ) ;
}
return sharedLayers ;
}
2020-08-30 01:13:18 +02:00
public static FromBase64 ( layoutFromBase64 : string ) : Layout {
return FromJSON . LayoutFromJSON ( JSON . parse ( atob ( layoutFromBase64 ) ) ) ;
}
public static LayoutFromJSON ( json : LayoutConfigJson ) : Layout {
const tr = FromJSON . Translation ;
const layers = json . layers . map ( FromJSON . Layer ) ;
2020-09-07 02:25:45 +02:00
const roaming : TagDependantUIElementConstructor [ ] = json . roamingRenderings ? . map ( ( tr , i ) = > FromJSON . TagRendering ( tr , "Roaming rendering " + i ) ) ? ? [ ] ;
2020-08-30 01:13:18 +02:00
for ( const layer of layers ) {
layer . elementsToShow . push ( . . . roaming ) ;
}
const layout = new Layout (
json . id ,
typeof ( json . language ) === "string" ? [ json . language ] : json . language ,
2020-09-05 15:27:35 +02:00
tr ( json . title ? ? "Title not defined" ) ,
2020-08-30 01:13:18 +02:00
layers ,
json . startZoom ,
json . startLat ,
json . startLon ,
new Combine ( [ "<h3>" , tr ( json . title ) , "</h3>" , tr ( json . description ) ] ) ,
2020-09-17 19:24:57 +02:00
undefined ,
undefined ,
tr ( json . descriptionTail )
2020-08-30 01:13:18 +02:00
) ;
2020-09-27 23:37:47 +02:00
layout . defaultBackground = json . defaultBackgroundId ? ? "osm" ;
2020-08-30 01:13:18 +02:00
layout . widenFactor = json . widenFactor ? ? 0.07 ;
layout . icon = json . icon ;
layout . maintainer = json . maintainer ;
layout . version = json . version ;
layout . socialImage = json . socialImage ;
2020-09-17 13:13:02 +02:00
layout . description = tr ( json . shortDescription ) ? ? tr ( json . description ) ? . FirstSentence ( ) ;
2020-08-30 01:13:18 +02:00
layout . changesetMessage = json . changesetmessage ;
return layout ;
}
2020-09-12 23:15:17 +02:00
public static Translation ( json : string | any ) : Translation {
2020-08-30 01:13:18 +02:00
if ( json === undefined ) {
return undefined ;
}
if ( typeof ( json ) === "string" ) {
2020-09-12 23:15:17 +02:00
return new Translation ( { "*" : json } ) ;
2020-08-30 01:13:18 +02:00
}
2020-09-25 23:59:19 +02:00
if ( json . render !== undefined ) {
console . error ( "Using a 'render' where a translation is expected. Content is" , json . render ) ;
throw "ERROR: using a 'render' where none is expected"
}
2020-08-30 01:13:18 +02:00
const tr = { } ;
2020-09-02 11:37:34 +02:00
let keyCount = 0 ;
2020-08-30 01:13:18 +02:00
for ( let key in json ) {
2020-09-12 23:15:17 +02:00
keyCount ++ ;
2020-08-30 01:13:18 +02:00
tr [ key ] = json [ key ] ; // I'm doing this wrong, I know
}
2020-09-02 11:37:34 +02:00
if ( keyCount == 0 ) {
return undefined ;
}
2020-09-12 23:15:17 +02:00
const transl = new Translation ( tr ) ;
return transl ;
2020-08-30 01:13:18 +02:00
}
2020-09-07 02:25:45 +02:00
public static TagRendering ( json : TagRenderingConfigJson | string , propertyeName : string ) : TagDependantUIElementConstructor {
return FromJSON . TagRenderingWithDefault ( json , propertyeName , undefined ) ;
2020-08-30 01:13:18 +02:00
}
public static TagRenderingWithDefault ( json : TagRenderingConfigJson | string , propertyName , defaultValue : string ) : TagDependantUIElementConstructor {
2020-09-02 11:37:34 +02:00
if ( json === undefined ) {
2020-08-30 01:13:18 +02:00
if ( defaultValue !== undefined ) {
2020-09-07 02:25:45 +02:00
return FromJSON . TagRendering ( defaultValue , propertyName ) ;
2020-08-30 01:13:18 +02:00
}
throw ` Tagrendering ${ propertyName } is undefined... `
}
2020-09-08 00:33:05 +02:00
2020-09-02 11:37:34 +02:00
2020-08-30 01:13:18 +02:00
if ( typeof json === "string" ) {
switch ( json ) {
case "picture" : {
return new ImageCarouselWithUploadConstructor ( )
}
case "pictures" : {
return new ImageCarouselWithUploadConstructor ( )
}
case "image" : {
return new ImageCarouselWithUploadConstructor ( )
}
case "images" : {
2020-09-10 19:33:06 +02:00
return new ImageCarouselWithUploadConstructor ( )
2020-08-30 01:13:18 +02:00
}
case "picturesNoUpload" : {
return new ImageCarouselConstructor ( )
}
}
return new TagRenderingOptions ( {
freeform : {
key : "id" ,
renderTemplate : json ,
template : "$$$"
}
} ) ;
}
2020-09-08 00:33:05 +02:00
// It's the question that drives us, neo
const question = FromJSON . Translation ( json . question ) ;
2020-08-30 01:13:18 +02:00
let template = FromJSON . Translation ( json . render ) ;
let freeform = undefined ;
2020-09-03 00:00:37 +02:00
if ( json . freeform ? . key && json . freeform . key !== "" ) {
2020-09-02 11:37:34 +02:00
// Setup the freeform
if ( template === undefined ) {
console . error ( "Freeform.key is defined, but render is not. This is not allowed." , json )
2020-09-07 02:25:45 +02:00
throw ` Freeform is defined in tagrendering ${ propertyName } , but render is not. This is not allowed. `
2020-08-30 01:13:18 +02:00
}
2020-09-02 11:37:34 +02:00
2020-08-30 01:13:18 +02:00
freeform = {
template : ` $ ${ json . freeform . type ? ? "string" } $ ` ,
renderTemplate : template ,
key : json.freeform.key
} ;
if ( json . freeform . addExtraTags ) {
2020-09-02 11:37:34 +02:00
freeform . extraTags = new And ( json . freeform . addExtraTags . map ( FromJSON . SimpleTag ) )
2020-08-30 01:13:18 +02:00
}
} else if ( json . render ) {
2020-09-02 11:37:34 +02:00
// Template (aka rendering) is defined, but freeform.key is not. We allow an input as string
2020-08-30 01:13:18 +02:00
freeform = {
2020-09-02 11:37:34 +02:00
template : undefined , // Template to ask is undefined -> we block asking for this key
2020-08-30 01:13:18 +02:00
renderTemplate : template ,
2020-09-02 11:37:34 +02:00
key : "id" // every object always has an id
2020-08-30 01:13:18 +02:00
}
}
2020-09-08 00:33:05 +02:00
const mappings = json . mappings ? . map ( ( mapping , i ) = > {
const k = FromJSON . Tag ( mapping . if , ` IN mapping # ${ i } of tagrendering ${ propertyName } ` )
if ( question !== undefined && ! mapping . hideInAnswer && ! k . isUsableAsAnswer ( ) ) {
2020-09-14 20:16:03 +02:00
throw ` Invalid mapping in ${ propertyName } . ${ i } : this mapping uses a regex tag or an OR, but is also answerable. Either mark 'Not an answer option' or only use '=' to map key/values. `
2020-09-08 00:33:05 +02:00
}
return {
k : k ,
txt : FromJSON.Translation ( mapping . then ) ,
hideInAnswer : mapping.hideInAnswer
} ;
}
2020-08-30 01:13:18 +02:00
) ;
2020-09-08 00:33:05 +02:00
if ( template === undefined && ( mappings === undefined || mappings . length === 0 ) ) {
console . error ( ` Empty tagrendering detected in ${ propertyName } : no mappings nor template given ` , json )
2020-09-07 02:25:45 +02:00
throw ` Empty tagrendering ${ propertyName } detected: no mappings nor template given `
2020-09-02 11:37:34 +02:00
}
2020-08-30 01:13:18 +02:00
2020-08-31 02:59:47 +02:00
let rendering = new TagRenderingOptions ( {
2020-09-08 00:33:05 +02:00
question : question ,
2020-08-30 01:13:18 +02:00
freeform : freeform ,
2020-09-10 19:33:06 +02:00
mappings : mappings ,
multiAnswer : json.multiAnswer
2020-08-30 01:13:18 +02:00
} ) ;
2020-09-10 19:33:06 +02:00
2020-08-31 02:59:47 +02:00
if ( json . condition ) {
2020-09-10 19:33:06 +02:00
const condition = FromJSON . Tag ( json . condition , ` In tagrendering ${ propertyName } .condition ` ) ;
return rendering . OnlyShowIf ( condition ) ;
2020-08-31 02:59:47 +02:00
}
return rendering ;
2020-08-30 01:13:18 +02:00
}
public static SimpleTag ( json : string ) : Tag {
2020-08-31 02:59:47 +02:00
const tag = Utils . SplitFirst ( json , "=" ) ;
2020-08-30 01:13:18 +02:00
return new Tag ( tag [ 0 ] , tag [ 1 ] ) ;
}
2020-09-09 18:42:13 +02:00
public static Tag ( json : AndOrTagConfigJson | string , context : string = "" ) : TagsFilter {
2020-09-02 11:37:34 +02:00
if ( json === undefined ) {
throw "Error while parsing a tag: nothing defined. Make sure all the tags are defined and at least one tag is present in a complex expression"
}
2020-08-30 01:13:18 +02:00
if ( typeof ( json ) == "string" ) {
const tag = json as string ;
if ( tag . indexOf ( "!~" ) >= 0 ) {
2020-08-31 02:59:47 +02:00
const split = Utils . SplitFirst ( tag , "!~" ) ;
if ( split [ 1 ] === "*" ) {
2020-09-08 00:33:05 +02:00
throw ` Don't use 'key!~*' - use 'key=' instead (empty string as value (in the tag ${ tag } while parsing ${ context } ) `
2020-08-30 01:13:18 +02:00
}
return new RegexTag (
2020-08-31 02:59:47 +02:00
split [ 0 ] ,
new RegExp ( "^" + split [ 1 ] + "$" ) ,
2020-08-30 01:13:18 +02:00
true
) ;
}
2020-09-08 00:33:05 +02:00
if ( tag . indexOf ( "~~" ) >= 0 ) {
const split = Utils . SplitFirst ( tag , "~~" ) ;
if ( split [ 1 ] === "*" ) {
split [ 1 ] = "..*"
}
return new RegexTag (
new RegExp ( "^" + split [ 0 ] + "$" ) ,
new RegExp ( "^" + split [ 1 ] + "$" )
) ;
}
2020-08-30 01:13:18 +02:00
if ( tag . indexOf ( "!=" ) >= 0 ) {
2020-08-31 02:59:47 +02:00
const split = Utils . SplitFirst ( tag , "!=" ) ;
if ( split [ 1 ] === "*" ) {
2020-09-03 02:05:09 +02:00
split [ 1 ] = "..*"
2020-08-31 02:59:47 +02:00
}
2020-08-30 01:13:18 +02:00
return new RegexTag (
2020-08-31 02:59:47 +02:00
split [ 0 ] ,
new RegExp ( "^" + split [ 1 ] + "$" ) ,
2020-08-30 01:13:18 +02:00
true
) ;
}
if ( tag . indexOf ( "~" ) >= 0 ) {
2020-08-31 02:59:47 +02:00
const split = Utils . SplitFirst ( tag , "~" ) ;
if ( split [ 1 ] === "*" ) {
2020-09-03 02:05:09 +02:00
split [ 1 ] = "..*"
2020-08-30 01:13:18 +02:00
}
return new RegexTag (
2020-08-31 02:59:47 +02:00
split [ 0 ] ,
new RegExp ( "^" + split [ 1 ] + "$" )
2020-08-30 01:13:18 +02:00
) ;
}
2020-08-31 02:59:47 +02:00
const split = Utils . SplitFirst ( tag , "=" ) ;
2020-09-08 00:33:05 +02:00
if ( split [ 1 ] == "*" ) {
throw ` Error while parsing tag ' ${ tag } ' in ${ context } : detected a wildcard on a normal value. Use a regex pattern instead `
}
2020-08-30 01:13:18 +02:00
return new Tag ( split [ 0 ] , split [ 1 ] )
}
if ( json . and !== undefined ) {
2020-09-08 00:33:05 +02:00
return new And ( json . and . map ( t = > FromJSON . Tag ( t , context ) ) ) ;
2020-08-30 01:13:18 +02:00
}
if ( json . or !== undefined ) {
2020-09-08 00:33:05 +02:00
return new Or ( json . or . map ( t = > FromJSON . Tag ( t , context ) ) ) ;
2020-08-30 01:13:18 +02:00
}
}
2020-08-31 02:59:47 +02:00
public static Layer ( json : LayerConfigJson | string ) : LayerDefinition {
if ( typeof ( json ) === "string" ) {
const cached = FromJSON . sharedLayers . get ( json ) ;
if ( cached ) {
return cached ;
}
2020-09-07 02:25:45 +02:00
throw ` Layer ${ json } not yet loaded... `
}
try {
return FromJSON . LayerUncaught ( json ) ;
} catch ( e ) {
throw ` While parsing layer ${ json . id } : ${ e } `
2020-08-31 02:59:47 +02:00
}
2020-09-07 02:25:45 +02:00
}
private static LayerUncaught ( json : LayerConfigJson ) : LayerDefinition {
2020-08-31 02:59:47 +02:00
2020-08-30 01:13:18 +02:00
const tr = FromJSON . Translation ;
2020-09-08 00:33:05 +02:00
const overpassTags = FromJSON . Tag ( json . overpassTags , "overpasstags for layer " + json . id ) ;
2020-09-07 02:25:45 +02:00
const icon = FromJSON . TagRenderingWithDefault ( json . icon , "icon" , "./assets/bug.svg" ) ;
2020-08-31 02:59:47 +02:00
const iconSize = FromJSON . TagRenderingWithDefault ( json . iconSize , "iconSize" , "40,40,center" ) ;
2020-09-07 02:25:45 +02:00
const color = FromJSON . TagRenderingWithDefault ( json . color , "color" , "#0000ff" ) ;
const width = FromJSON . TagRenderingWithDefault ( json . width , "width" , "10" ) ;
if ( json . title === "Layer" ) {
2020-09-07 01:03:23 +02:00
json . title = { } ;
}
2020-09-07 02:25:45 +02:00
let title = FromJSON . TagRendering ( json . title , "Popup title" ) ;
2020-09-03 16:44:48 +02:00
let tagRenderingDefs = json . tagRenderings ? ? [ ] ;
2020-09-05 01:40:43 +02:00
let hasImageElement = false ;
for ( const tagRenderingDef of tagRenderingDefs ) {
if ( typeof tagRenderingDef !== "string" ) {
continue ;
}
let str = tagRenderingDef as string ;
2020-09-07 02:25:45 +02:00
if ( tagRenderingDef . indexOf ( "images" ) >= 0 || str . indexOf ( "pictures" ) >= 0 ) {
2020-09-05 01:40:43 +02:00
hasImageElement = true ;
break ;
}
}
if ( ! hasImageElement ) {
2020-09-03 16:44:48 +02:00
tagRenderingDefs = [ "images" , . . . tagRenderingDefs ] ;
}
2020-09-07 02:25:45 +02:00
let tagRenderings = tagRenderingDefs . map ( ( tr , i ) = > FromJSON . TagRendering ( tr , "Tagrendering #" + i ) ) ;
2020-09-03 16:44:48 +02:00
2020-08-30 01:13:18 +02:00
const renderTags = { "id" : "node/-1" }
const presets : Preset [ ] = json ? . presets ? . map ( preset = > {
return ( {
title : tr ( preset . title ) ,
description : tr ( preset . description ) ,
tags : preset.tags.map ( FromJSON . SimpleTag )
} ) ;
} ) ? ? [ ] ;
function style ( tags ) {
2020-09-02 11:37:34 +02:00
const iconSizeStr =
iconSize . GetContent ( tags ) . txt . split ( "," ) ;
2020-08-31 02:59:47 +02:00
const iconwidth = Number ( iconSizeStr [ 0 ] ) ;
const iconheight = Number ( iconSizeStr [ 1 ] ) ;
const iconmode = iconSizeStr [ 2 ] ;
const iconAnchor = [ iconwidth / 2 , iconheight / 2 ] // x, y
// If iconAnchor is set to [0,0], then the top-left of the icon will be placed at the geographical location
if ( iconmode . indexOf ( "left" ) >= 0 ) {
iconAnchor [ 0 ] = 0 ;
}
if ( iconmode . indexOf ( "right" ) >= 0 ) {
iconAnchor [ 0 ] = iconwidth ;
}
if ( iconmode . indexOf ( "top" ) >= 0 ) {
iconAnchor [ 1 ] = 0 ;
}
if ( iconmode . indexOf ( "bottom" ) >= 0 ) {
iconAnchor [ 1 ] = iconheight ;
}
// the anchor is always set from the center of the point
// x, y with x going right and y going down if the values are bigger
2020-09-03 16:44:48 +02:00
const popupAnchor = [ 0 , 3 - iconAnchor [ 1 ] ] ;
2020-08-31 02:59:47 +02:00
2020-08-30 01:13:18 +02:00
return {
color : color.GetContent ( tags ) . txt ,
weight : width.GetContent ( tags ) . txt ,
icon : {
2020-08-31 02:59:47 +02:00
iconUrl : icon.GetContent ( tags ) . txt ,
iconSize : [ iconwidth , iconheight ] ,
popupAnchor : popupAnchor ,
iconAnchor : iconAnchor
2020-08-30 01:13:18 +02:00
} ,
}
}
const layer = new LayerDefinition (
json . id ,
{
name : tr ( json . name ) ,
description : tr ( json . description ) ,
icon : icon.GetContent ( renderTags ) . txt ,
overpassFilter : overpassTags ,
2020-09-07 01:03:23 +02:00
title : title ,
2020-08-30 01:13:18 +02:00
minzoom : json.minzoom ,
presets : presets ,
2020-09-03 02:05:09 +02:00
elementsToShow : tagRenderings ,
2020-08-30 01:13:18 +02:00
style : style ,
wayHandling : json.wayHandling
}
) ;
2020-09-11 19:14:32 +02:00
layer . maxAllowedOverlapPercentage = json . hideUnderlayingFeaturesMinPercentage ? ? 0 ;
2020-08-30 01:13:18 +02:00
return layer ;
}
}