2020-08-17 17:23:15 +02:00
import { UIElement } from "../UIElement" ;
import { VerticalCombine } from "../Base/VerticalCombine" ;
import { VariableUiElement } from "../Base/VariableUIElement" ;
import Combine from "../Base/Combine" ;
import {
LayerConfigJson ,
LayoutConfigJson ,
TagRenderingConfigJson
} from "../../Customizations/JSON/CustomLayoutFromJSON" ;
import { TabbedComponent } from "../Base/TabbedComponent" ;
import { UIEventSource } from "../../Logic/UIEventSource" ;
import { OsmConnection , UserDetails } from "../../Logic/Osm/OsmConnection" ;
import { Button } from "../Base/Button" ;
import { FixedUiElement } from "../Base/FixedUiElement" ;
import { TextField } from "../Input/TextField" ;
2020-08-08 21:17:17 +02:00
function TagsToString ( tags : string | string [ ] | { k : string , v : string } [ ] ) {
if ( tags === undefined ) {
return undefined ;
}
if ( typeof ( tags ) == "string" ) {
return tags ;
}
const newTags = [ ] ;
console . log ( tags )
for ( const tag of tags ) {
if ( typeof ( tag ) == "string" ) {
newTags . push ( tag )
} else {
newTags . push ( tag . k + "=" + tag . v ) ;
}
}
return newTags . join ( "," ) ;
}
2020-08-08 22:18:10 +02:00
2020-08-17 17:23:15 +02:00
let createFieldUI : ( label : string , key : string , root : any , options ? : { deflt? : string } ) = > UIElement ;
2020-08-08 21:17:17 +02:00
class MappingGenerator extends UIElement {
private elements : UIElement [ ] ;
constructor ( fullConfig : UIEventSource < LayoutConfigJson > ,
layerConfig : LayerConfigJson ,
tagRendering : TagRenderingConfigJson ,
2020-08-17 17:23:15 +02:00
mapping : { if : string | string [ ] | { k : string , v : string } [ ] } ) {
2020-08-08 21:17:17 +02:00
super ( undefined ) ;
2020-08-17 17:23:15 +02:00
this . CreateElements ( fullConfig , layerConfig , tagRendering , mapping )
2020-08-08 21:17:17 +02:00
}
private CreateElements ( fullConfig : UIEventSource < LayoutConfigJson > , layerConfig : LayerConfigJson ,
tagRendering : TagRenderingConfigJson ,
2020-08-17 17:23:15 +02:00
mapping ) {
2020-08-08 21:17:17 +02:00
{
const self = this ;
this . elements = [
2020-08-17 17:23:15 +02:00
createFieldUI ( "If these tags apply" , "if" , mapping ) ,
createFieldUI ( "Then: show this text" , "then" , mapping ) ,
2020-08-08 21:17:17 +02:00
new Button ( "Remove this mapping" , ( ) = > {
for ( let i = 0 ; i < tagRendering . mappings . length ; i ++ ) {
if ( tagRendering . mappings [ i ] === mapping ) {
tagRendering . mappings . splice ( i , 1 ) ;
self . elements = [
new FixedUiElement ( "Tag mapping removed" )
]
self . Update ( ) ;
break ;
}
}
} )
] ;
}
}
InnerRender ( ) : string {
const combine = new VerticalCombine ( this . elements ) ;
combine . clss = "bordered" ;
return combine . Render ( ) ;
}
}
class TagRenderingGenerator
extends UIElement {
private elements : UIElement [ ] ;
constructor ( fullConfig : UIEventSource < LayoutConfigJson > ,
layerConfig : LayerConfigJson ,
tagRendering : TagRenderingConfigJson ,
isTitle : boolean = false ) {
super ( undefined ) ;
2020-08-17 17:23:15 +02:00
this . CreateElements ( fullConfig , layerConfig , tagRendering , isTitle )
2020-08-08 21:17:17 +02:00
}
2020-08-17 17:23:15 +02:00
private CreateElements ( fullConfig : UIEventSource < LayoutConfigJson > , layerConfig : LayerConfigJson , tagRendering : TagRenderingConfigJson , isTitle : boolean ) {
2020-08-08 21:17:17 +02:00
const self = this ;
this . elements = [
new FixedUiElement ( isTitle ? "<h3>Popup title</h3>" : "<h3>TagRendering/TagQuestion</h3>" ) ,
2020-08-17 17:23:15 +02:00
createFieldUI ( "Key" , "key" , tagRendering ) ,
createFieldUI ( "Rendering" , "render" , tagRendering ) ,
createFieldUI ( "Type" , "type" , tagRendering ) ,
createFieldUI ( "Question" , "question" , tagRendering ) ,
createFieldUI ( "Extra tags" , "addExtraTags" , tagRendering ) ,
2020-08-08 21:17:17 +02:00
. . . ( tagRendering . mappings ? ? [ ] ) . map ( ( mapping ) = > {
2020-08-17 17:23:15 +02:00
return new MappingGenerator ( fullConfig , layerConfig , tagRendering , mapping )
2020-08-08 21:17:17 +02:00
} ) ,
new Button ( "Add mapping" , ( ) = > {
2020-08-17 17:23:15 +02:00
if ( tagRendering . mappings === undefined ) {
2020-08-08 21:49:39 +02:00
tagRendering . mappings = [ ]
}
2020-08-08 21:17:17 +02:00
tagRendering . mappings . push ( { if : "" , then : "" } ) ;
2020-08-17 17:23:15 +02:00
self . CreateElements ( fullConfig , layerConfig , tagRendering , isTitle ) ;
2020-08-08 21:17:17 +02:00
self . Update ( ) ;
} )
]
if ( ! isTitle ) {
const b = new Button ( "Remove this preset" , ( ) = > {
for ( let i = 0 ; i < layerConfig . tagRenderings . length ; i ++ ) {
if ( layerConfig . tagRenderings [ i ] === tagRendering ) {
layerConfig . tagRenderings . splice ( i , 1 ) ;
self . elements = [
new FixedUiElement ( "Tag rendering removed" )
]
self . Update ( ) ;
break ;
}
}
} ) ;
this . elements . push ( b ) ;
}
}
InnerRender ( ) : string {
const combine = new VerticalCombine ( this . elements ) ;
combine . clss = "bordered" ;
return combine . Render ( ) ;
}
}
class PresetGenerator extends UIElement {
private elements : UIElement [ ] ;
constructor ( fullConfig : UIEventSource < LayoutConfigJson > , layerConfig : LayerConfigJson ,
2020-08-17 17:23:15 +02:00
preset0 : { title? : string , description? : string , icon? : string , tags? : string | string [ ] | { k : string , v : string } [ ] } ) {
2020-08-08 21:17:17 +02:00
super ( undefined ) ;
const self = this ;
this . elements = [
new FixedUiElement ( "<h3>Preset</h3>" ) ,
2020-08-17 17:23:15 +02:00
createFieldUI ( "Title" , "title" , preset0 ) ,
createFieldUI ( "Description" , "description" , preset0 , { deflt : layerConfig.description } ) ,
createFieldUI ( "icon" , "icon" , preset0 , { deflt : layerConfig.icon } ) ,
createFieldUI ( "tags" , "tags" , preset0 , { deflt : TagsToString ( layerConfig . overpassTags ) } ) ,
2020-08-08 21:17:17 +02:00
new Button ( "Remove this preset" , ( ) = > {
for ( let i = 0 ; i < layerConfig . presets . length ; i ++ ) {
if ( layerConfig . presets [ i ] === preset0 ) {
layerConfig . presets . splice ( i , 1 ) ;
self . elements = [
new FixedUiElement ( "Preset removed" )
]
self . Update ( ) ;
break ;
}
}
} )
]
}
InnerRender ( ) : string {
const combine = new VerticalCombine ( this . elements ) ;
combine . clss = "bordered" ;
return combine . Render ( ) ;
}
}
class LayerGenerator extends UIElement {
private fullConfig : UIEventSource < LayoutConfigJson > ;
private layerConfig : UIEventSource < LayerConfigJson > ;
private generateField : ( ( label : string , key : string , root : any , deflt? : string ) = > UIElement ) ;
private uielements : UIElement [ ] ;
constructor ( fullConfig : UIEventSource < LayoutConfigJson > ,
2020-08-17 17:23:15 +02:00
layerConfig : LayerConfigJson ) {
2020-08-08 21:17:17 +02:00
super ( undefined ) ;
this . layerConfig = new UIEventSource < LayerConfigJson > ( layerConfig ) ;
this . fullConfig = fullConfig ;
2020-08-17 17:23:15 +02:00
this . CreateElements ( fullConfig , layerConfig )
2020-08-08 21:17:17 +02:00
}
2020-08-17 17:23:15 +02:00
private CreateElements ( fullConfig : UIEventSource < LayoutConfigJson > , layerConfig : LayerConfigJson ) {
2020-08-08 21:17:17 +02:00
const self = this ;
this . uielements = [
2020-08-17 17:23:15 +02:00
createFieldUI ( "The name of this layer" , "id" , layerConfig ) ,
createFieldUI ( "A description of objects for this layer" , "description" , layerConfig ) ,
createFieldUI ( "The icon of this layer, either a URL or a base64-encoded svg" , "icon" , layerConfig ) ,
createFieldUI ( "The default stroke color" , "color" , layerConfig ) ,
createFieldUI ( "The minimal needed zoom to start loading" , "minzoom" , layerConfig ) ,
createFieldUI ( "The tags to load from overpass" , "overpassTags" , layerConfig ) ,
. . . layerConfig . presets . map ( preset = > new PresetGenerator ( fullConfig , layerConfig , preset ) ) ,
2020-08-08 21:17:17 +02:00
new Button ( "Add a preset" , ( ) = > {
layerConfig . presets . push ( {
icon : undefined ,
title : "" ,
description : "" ,
tags : TagsToString ( layerConfig . overpassTags )
} ) ;
2020-08-17 17:23:15 +02:00
self . CreateElements ( fullConfig , layerConfig ) ;
2020-08-08 21:17:17 +02:00
self . Update ( ) ;
} ) ,
2020-08-08 21:49:39 +02:00
new TagRenderingGenerator ( fullConfig , layerConfig , layerConfig . title ? ? {
key : "" ,
addExtraTags : "" ,
mappings : [ ] ,
question : "" ,
render : "Title" ,
type : "text"
2020-08-17 17:23:15 +02:00
} , true ) ,
. . . layerConfig . tagRenderings . map ( tr = > new TagRenderingGenerator ( fullConfig , layerConfig , tr ) ) ,
2020-08-08 21:17:17 +02:00
new Button ( "Add a tag rendering" , ( ) = > {
layerConfig . tagRenderings . push ( {
key : "" ,
addExtraTags : "" ,
mappings : [ ] ,
question : "" ,
render : "" ,
type : "text"
} ) ;
2020-08-17 17:23:15 +02:00
self . CreateElements ( fullConfig , layerConfig ) ;
2020-08-08 21:17:17 +02:00
self . Update ( ) ;
} ) ,
]
}
InnerRender ( ) : string {
return new VerticalCombine ( this . uielements ) . Render ( ) ;
}
}
class AllLayerComponent extends UIElement {
private tabs : TabbedComponent ;
private config : UIEventSource < LayoutConfigJson > ;
2020-08-17 17:23:15 +02:00
constructor ( config : UIEventSource < LayoutConfigJson > ) {
2020-08-08 21:17:17 +02:00
super ( undefined ) ;
this . config = config ;
const self = this ;
let previousLayerAmount = config . data . layers . length ;
config . addCallback ( ( data ) = > {
if ( data . layers . length != previousLayerAmount ) {
previousLayerAmount = data . layers . length ;
self . UpdateTabs ( ) ;
self . Update ( ) ;
}
} ) ;
this . UpdateTabs ( ) ;
}
private UpdateTabs() {
const layerPanes : { header : UIElement | string , content : UIElement | string } [ ] = [ ] ;
const config = this . config ;
for ( const layer of this . config . data . layers ) {
const header = this . config . map ( ( ) = > {
return ` <img src=" ${ layer ? . icon ? ? "./assets/help.svg" } "> `
} ) ;
layerPanes . push ( {
header : new VariableUiElement ( header ) ,
2020-08-17 17:23:15 +02:00
content : new LayerGenerator ( config , layer )
2020-08-08 21:17:17 +02:00
} )
}
layerPanes . push ( {
header : "<img src='./assets/add.svg'>" ,
content : new Button ( "Add a new layer" , ( ) = > {
config . data . layers . push ( {
id : "" ,
title : {
render : "Title"
} ,
icon : "./assets/bug.svg" ,
color : "" ,
description : "" ,
minzoom : 12 ,
overpassTags : "" ,
presets : [ { } ] ,
tagRenderings : [ ]
} ) ;
config . ping ( ) ;
} )
} )
this . tabs = new TabbedComponent ( layerPanes ) ;
}
InnerRender ( ) : string {
return this . tabs . Render ( ) ;
}
}
export class ThemeGenerator extends UIElement {
private readonly userDetails : UIEventSource < UserDetails > ;
2020-08-08 21:49:39 +02:00
public readonly themeObject : UIEventSource < LayoutConfigJson > ;
2020-08-08 21:17:17 +02:00
private readonly allQuestionFields : UIElement [ ] ;
public url : UIEventSource < string > ;
constructor ( connection : OsmConnection , windowHash ) {
super ( connection . userDetails ) ;
this . userDetails = connection . userDetails ;
const defaultTheme = { layers : [ ] , icon : "./assets/bug.svg" } ;
let loadedTheme = undefined ;
if ( windowHash !== undefined && windowHash . length > 4 ) {
loadedTheme = JSON . parse ( atob ( windowHash ) ) ;
}
this . themeObject = new UIEventSource < LayoutConfigJson > ( loadedTheme ? ? defaultTheme ) ;
const jsonObjectRoot = this . themeObject . data ;
const base64 = this . themeObject . map ( JSON . stringify ) . map ( btoa ) ;
2020-08-17 17:23:15 +02:00
this . url = base64 . map ( ( data ) = > ` https://pietervdvn.github.io/MapComplete/index.html?test=true&userlayout=true# ` + data ) ;
2020-08-08 21:17:17 +02:00
const self = this ;
2020-08-17 17:23:15 +02:00
createFieldUI = ( label , key , root , options ) = > {
2020-08-08 21:17:17 +02:00
2020-08-17 17:23:15 +02:00
const value = new UIEventSource < string > ( TagsToString ( root [ key ] ) ? ? options ? . deflt ) ;
value . addCallback ( ( v ) = > {
root [ key ] = v ;
self . themeObject . ping ( ) ; // We assume the root is a part of the themeObject
} )
return new Combine ( [
label ,
new TextField < string > ( {
fromString : ( str ) = > str ,
toString : ( str ) = > str ,
value : value
} ) ] ) ;
}
this . allQuestionFields = [
createFieldUI ( "Name of this theme" , "name" , jsonObjectRoot ) ,
createFieldUI ( "Title (shown in the window and in the welcome message)" , "title" , jsonObjectRoot ) ,
createFieldUI ( "Description (shown in the welcome message and various other places)" , "description" , jsonObjectRoot ) ,
createFieldUI ( "The supported language" , "language" , jsonObjectRoot ) ,
createFieldUI ( "startLat" , "startLat" , jsonObjectRoot ) ,
createFieldUI ( "startLon" , "startLon" , jsonObjectRoot ) ,
createFieldUI ( "startzoom" , "startZoom" , jsonObjectRoot ) ,
createFieldUI ( "icon: either a URL to an image file, a relative url to a MapComplete asset ('./asset/help.svg') or a base64-encoded value (including 'data:image/svg+xml;base64,'" , "icon" , jsonObjectRoot , { deflt : "./assets/bug.svg" } ) ,
new AllLayerComponent ( this . themeObject )
]
2020-08-08 21:17:17 +02:00
}
InnerRender ( ) : string {
if ( ! this . userDetails . data . loggedIn ) {
2020-08-17 17:23:15 +02:00
return "Not logged in. You need to be logged in to create a theme."
2020-08-08 21:17:17 +02:00
}
if ( this . userDetails . data . csCount < 500 ) {
2020-08-17 17:23:15 +02:00
return "You need at least 500 changesets to create your own theme." ;
2020-08-08 21:17:17 +02:00
}
return new VerticalCombine ( [
// new VariableUiElement(this.themeObject.map(JSON.stringify)),
2020-08-17 17:23:15 +02:00
// new VariableUiElement(this.url.map((url) => `Current URL: <a href="${url}" target="_blank">Click here to open</a>`)),
2020-08-08 21:17:17 +02:00
. . . this . allQuestionFields ,
] ) . Render ( ) ;
}
}