forked from MapComplete/MapComplete
		
	Fixing too many bugs, cleaning up some old parts of the code
This commit is contained in:
		
							parent
							
								
									3d05999f85
								
							
						
					
					
						commit
						00a6611e1f
					
				
					 21 changed files with 706 additions and 436 deletions
				
			
		|  | @ -6,6 +6,7 @@ import {LayoutConfigJson} from "../../Customizations/JSON/LayoutConfigJson"; | |||
| import Combine from "../Base/Combine"; | ||||
| import {GenerateEmpty} from "./GenerateEmpty"; | ||||
| import LayerPanelWithPreview from "./LayerPanelWithPreview"; | ||||
| import {UserDetails} from "../../Logic/Osm/OsmConnection"; | ||||
| 
 | ||||
| export default class AllLayersPanel extends UIElement { | ||||
| 
 | ||||
|  | @ -15,28 +16,29 @@ export default class AllLayersPanel extends UIElement { | |||
|     private readonly languages: UIEventSource<string[]>; | ||||
| 
 | ||||
|     constructor(config: UIEventSource<LayoutConfigJson>, | ||||
|                 languages: UIEventSource<any>) { | ||||
|                 languages: UIEventSource<any>, userDetails: UserDetails) { | ||||
|         super(undefined); | ||||
|         this._config = config; | ||||
|         this.languages = languages; | ||||
| 
 | ||||
|         this.createPanels(); | ||||
|         this.createPanels(userDetails); | ||||
|         const self = this; | ||||
|         config.map<number>(config => config.layers.length).addCallback(() => self.createPanels()); | ||||
|         config.map<number>(config => config.layers.length).addCallback(() => self.createPanels(userDetails)); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     private createPanels() { | ||||
|     private createPanels(userDetails: UserDetails) { | ||||
|         const self = this; | ||||
|         const tabs = []; | ||||
| 
 | ||||
|         const layers = this._config.data.layers; | ||||
|         for (let i = 0; i < layers.length; i++) { | ||||
|    | ||||
| 
 | ||||
|             tabs.push({ | ||||
|                 header: "<img src='./assets/bug.svg'>", | ||||
|                 content: new LayerPanelWithPreview(this._config, this.languages, i)}); | ||||
|                 content: new LayerPanelWithPreview(this._config, this.languages, i, userDetails) | ||||
|             }); | ||||
|         } | ||||
|         tabs.push({ | ||||
|             header: "<img src='./assets/layersAdd.svg'>", | ||||
|  |  | |||
|  | @ -0,0 +1,115 @@ | |||
| import {UIElement} from "../UIElement"; | ||||
| import {OsmConnection, UserDetails} from "../../Logic/Osm/OsmConnection"; | ||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | ||||
| import SingleSetting from "./SingleSetting"; | ||||
| import GeneralSettings from "./GeneralSettings"; | ||||
| import Combine from "../Base/Combine"; | ||||
| import {VariableUiElement} from "../Base/VariableUIElement"; | ||||
| import {TabbedComponent} from "../Base/TabbedComponent"; | ||||
| import PageSplit from "../Base/PageSplit"; | ||||
| import HelpText from "../../Customizations/HelpText"; | ||||
| import AllLayersPanel from "./AllLayersPanel"; | ||||
| import SharePanel from "./SharePanel"; | ||||
| import {LayoutConfigJson} from "../../Customizations/JSON/LayoutConfigJson"; | ||||
| import {SubtleButton} from "../Base/SubtleButton"; | ||||
| import {State} from "../../State"; | ||||
| import {FixedUiElement} from "../Base/FixedUiElement"; | ||||
| import SavePanel from "./SavePanel"; | ||||
| import {LocalStorageSource} from "../../Logic/Web/LocalStorageSource"; | ||||
| 
 | ||||
| 
 | ||||
| export default class CustomGeneratorPanel extends UIElement { | ||||
|     private mainPanel: UIElement; | ||||
|     private loginButton: UIElement; | ||||
| 
 | ||||
|     private connection: OsmConnection; | ||||
| 
 | ||||
|     constructor(connection: OsmConnection, layout: LayoutConfigJson) { | ||||
|         super(connection.userDetails); | ||||
|         this.connection = connection; | ||||
|         this.SetClass("main-tabs"); | ||||
|         this.loginButton = new SubtleButton("", "Login to create a custom theme").onClick(() => connection.AttemptLogin()) | ||||
|         const self = this; | ||||
|         self.mainPanel = new FixedUiElement("Attempting to log in..."); | ||||
|         connection.OnLoggedIn(userDetails => { | ||||
|             self.InitMainPanel(layout, userDetails, connection); | ||||
|             self.Update(); | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     private InitMainPanel(layout: LayoutConfigJson, userDetails: UserDetails, connection: OsmConnection) { | ||||
|         const es = new UIEventSource(layout); | ||||
|         const encoded = es.map(config => btoa(JSON.stringify(config))); | ||||
|         encoded.addCallback(encoded => LocalStorageSource.Get("\"last-custom-theme\"")) | ||||
|         const liveUrl = encoded.map(encoded => `./index.html?userlayout=${es.data.id}#${encoded}`) | ||||
|         const iframe = liveUrl.map(url => `<iframe src='${url}' width='100%' height='99%' style="box-sizing: border-box" title='Theme Preview'></iframe>`); | ||||
|         const currentSetting = new UIEventSource<SingleSetting<any>>(undefined) | ||||
|         const generalSettings = new GeneralSettings(es, currentSetting); | ||||
|         const languages = generalSettings.languages; | ||||
| 
 | ||||
|         const chronic = UIEventSource.Chronic(120 * 1000) | ||||
|             .map(date => { | ||||
|                 if (es.data.id == undefined) { | ||||
|                     return undefined | ||||
|                 } | ||||
|                 if (es.data.id === "") { | ||||
|                     return undefined; | ||||
|                 } | ||||
|                 const pref = connection.GetLongPreference("installed-theme-" + es.data.id); | ||||
|                 pref.setData(encoded.data); | ||||
|                 return date; | ||||
|             }); | ||||
| 
 | ||||
|         const preview = new Combine([ | ||||
|             new VariableUiElement(iframe.stabilized(2500)) | ||||
|         ]).SetClass("preview") | ||||
|         this.mainPanel = new TabbedComponent([ | ||||
|             { | ||||
|                 header: "<img src='./assets/gear.svg'>", | ||||
|                 content: | ||||
|                     new PageSplit( | ||||
|                         generalSettings.SetStyle("width: 50vw;"), | ||||
|                         new Combine([ | ||||
|                             new HelpText(currentSetting).SetStyle("height:calc(100% - 65vh); width: 100%; display:block; overflow-y: auto"), | ||||
|                             preview.SetStyle("height:65vh; width:100%; display:block") | ||||
|                         ]).SetStyle("position:relative; width: 50%;") | ||||
|                     ) | ||||
|             }, | ||||
|             { | ||||
|                 header: "<img src='./assets/layers.svg'>", | ||||
|                 content: new AllLayersPanel(es, languages, userDetails) | ||||
|             }, | ||||
|             { | ||||
|                 header: "<img src='./assets/floppy.svg'>", | ||||
|                 content: new SavePanel(this.connection, es, chronic) | ||||
| 
 | ||||
|             }, | ||||
|             { | ||||
|                 header: "<img src='./assets/share.svg'>", | ||||
|                 content: new SharePanel(es, liveUrl) | ||||
|             } | ||||
|         ]) | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     InnerRender(): string { | ||||
|         const ud = this.connection.userDetails.data; | ||||
|         if (!ud.loggedIn) { | ||||
|             return new Combine([ | ||||
|                 "<h3>Not Logged in</h3>", | ||||
|                 "You need to be logged in in order to create a custom theme", | ||||
|                 this.loginButton | ||||
|             ]).Render(); | ||||
|         } | ||||
|         if (ud.csCount <= State.userJourney.themeGeneratorReadOnlyUnlock) { | ||||
|             return new Combine([ | ||||
|                 "<h3>Too little experience/h3>", | ||||
|                 `Creating your own (readonly) themes can only be done if you have more then <b>${State.userJourney.themeGeneratorReadOnlyUnlock}</b> changesets made`, | ||||
|                 `Making a theme including survey options can be done at <b>${State.userJourney.themeGeneratorFullUnlock}</b> changesets`, | ||||
|                 this.loginButton | ||||
|             ]).Render(); | ||||
|         } | ||||
|         return this.mainPanel.Render() | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -16,6 +16,9 @@ import {TagRenderingConfigJson} from "../../Customizations/JSON/TagRenderingConf | |||
| import {MultiInput} from "../Input/MultiInput"; | ||||
| import {LayerConfigJson} from "../../Customizations/JSON/LayerConfigJson"; | ||||
| import PresetInputPanel from "./PresetInputPanel"; | ||||
| import {UserDetails} from "../../Logic/Osm/OsmConnection"; | ||||
| import {State} from "../../State"; | ||||
| import {FixedUiElement} from "../Base/FixedUiElement"; | ||||
| 
 | ||||
| /** | ||||
|  * Shows the configuration for a single layer | ||||
|  | @ -38,10 +41,11 @@ export default class LayerPanel extends UIElement { | |||
|     constructor(config: UIEventSource<LayoutConfigJson>, | ||||
|                 languages: UIEventSource<string[]>, | ||||
|                 index: number, | ||||
|                 currentlySelected: UIEventSource<SingleSetting<any>>) { | ||||
|                 currentlySelected: UIEventSource<SingleSetting<any>>, | ||||
|                 userDetails: UserDetails) { | ||||
|         super(); | ||||
|         this._config = config; | ||||
|         this.mapRendering = this.setupRenderOptions(config, languages, index, currentlySelected); | ||||
|         this.mapRendering = this.setupRenderOptions(config, languages, index, currentlySelected, userDetails); | ||||
| 
 | ||||
|         const actualDeleteButton = new SubtleButton( | ||||
|             "./assets/delete.svg", | ||||
|  | @ -100,7 +104,7 @@ export default class LayerPanel extends UIElement { | |||
|             currentlySelected); | ||||
|         const self = this; | ||||
| 
 | ||||
|         const popupTitleRendering = new TagRenderingPanel(languages, currentlySelected, { | ||||
|         const popupTitleRendering = new TagRenderingPanel(languages, currentlySelected, userDetails, { | ||||
|             title: "Popup title", | ||||
|             description: "This is the rendering shown as title in the popup for this element", | ||||
|             disableQuestions: true | ||||
|  | @ -113,7 +117,7 @@ export default class LayerPanel extends UIElement { | |||
|         const tagRenderings = new MultiInput<TagRenderingConfigJson>("Add a tag rendering/question", | ||||
|             () => ({}), | ||||
|             () => { | ||||
|                 const tagPanel = new TagRenderingPanel(languages, currentlySelected) | ||||
|                 const tagPanel = new TagRenderingPanel(languages, currentlySelected, userDetails) | ||||
|                 self.registerTagRendering(tagPanel); | ||||
|                 return tagPanel; | ||||
|             }); | ||||
|  | @ -124,11 +128,16 @@ export default class LayerPanel extends UIElement { | |||
|             } | ||||
|         ) | ||||
| 
 | ||||
|         const presetPanel = new MultiInput("Add a preset", | ||||
|             () => ({tags: [], title: {}}), | ||||
|             () => new PresetInputPanel(currentlySelected, languages)); | ||||
|         this.presetsPanel = presetPanel; | ||||
|         new SingleSetting(config, presetPanel, ["layers", index, "presets"], "Presets", "") | ||||
|         if (userDetails.csCount >= State.userJourney.themeGeneratorFullUnlock) { | ||||
| 
 | ||||
|             const presetPanel = new MultiInput("Add a preset", | ||||
|                 () => ({tags: [], title: {}}), | ||||
|                 () => new PresetInputPanel(currentlySelected, languages)); | ||||
|             new SingleSetting(config, presetPanel, ["layers", index, "presets"], "Presets", "") | ||||
|             this.presetsPanel = presetPanel; | ||||
|         } else { | ||||
|             this.presetsPanel = new FixedUiElement(`Creating a custom theme which also edits OSM is only unlocked after ${State.userJourney.themeGeneratorFullUnlock} changesets`).SetClass("alert"); | ||||
|         } | ||||
| 
 | ||||
|         function loadTagRenderings() { | ||||
|             const values = (config.data.layers[index] as LayerConfigJson).tagRenderings; | ||||
|  | @ -152,27 +161,29 @@ export default class LayerPanel extends UIElement { | |||
|     private setupRenderOptions(config: UIEventSource<LayoutConfigJson>, | ||||
|                                languages: UIEventSource<string[]>, | ||||
|                                index: number, | ||||
|                                currentlySelected: UIEventSource<SingleSetting<any>>): UIElement { | ||||
|                                currentlySelected: UIEventSource<SingleSetting<any>>, | ||||
|                                userDetails: UserDetails | ||||
|     ): UIElement { | ||||
|         const iconSelect = new TagRenderingPanel( | ||||
|             languages, currentlySelected, | ||||
|             languages, currentlySelected, userDetails, | ||||
|             { | ||||
|                 title: "Icon", | ||||
|                 description: "A visual representation for this layer and for the points on the map.", | ||||
|                 disableQuestions: true | ||||
|             }); | ||||
|         const size = new TagRenderingPanel(languages, currentlySelected, | ||||
|         const size = new TagRenderingPanel(languages, currentlySelected, userDetails, | ||||
|             { | ||||
|                 title: "Icon Size", | ||||
|                 description: "The size of the icons on the map in pixels. Can vary based on the tagging", | ||||
|                 disableQuestions: true | ||||
|             }); | ||||
|         const color = new TagRenderingPanel(languages, currentlySelected, | ||||
|         const color = new TagRenderingPanel(languages, currentlySelected, userDetails, | ||||
|             { | ||||
|                 title: "Way and area color", | ||||
|                 description: "The color or a shown way or area. Can vary based on the tagging", | ||||
|                 disableQuestions: true | ||||
|             }); | ||||
|         const stroke = new TagRenderingPanel(languages, currentlySelected, | ||||
|         const stroke = new TagRenderingPanel(languages, currentlySelected, userDetails, | ||||
|             { | ||||
|                 title: "Stroke width", | ||||
|                 description: "The width of lines representing ways and the outline of areas. Can vary based on the tags", | ||||
|  |  | |||
|  | @ -8,16 +8,17 @@ import {FromJSON} from "../../Customizations/JSON/FromJSON"; | |||
| import Combine from "../Base/Combine"; | ||||
| import PageSplit from "../Base/PageSplit"; | ||||
| import TagRenderingPreview from "./TagRenderingPreview"; | ||||
| import {UserDetails} from "../../Logic/Osm/OsmConnection"; | ||||
| 
 | ||||
| 
 | ||||
| export default class LayerPanelWithPreview extends UIElement{ | ||||
|     private panel: UIElement; | ||||
|      | ||||
|     constructor(config: UIEventSource<any>, languages: UIEventSource<string[]>, index: number) { | ||||
|     constructor(config: UIEventSource<any>, languages: UIEventSource<string[]>, index: number, userDetails: UserDetails) { | ||||
|         super(); | ||||
| 
 | ||||
|         const currentlySelected = new UIEventSource<(SingleSetting<any>)>(undefined); | ||||
|         const layer = new LayerPanel(config, languages, index, currentlySelected); | ||||
|         const layer = new LayerPanel(config, languages, index, currentlySelected, userDetails); | ||||
|         const helpText = new HelpText(currentlySelected); | ||||
| 
 | ||||
|         const previewTagInput = new MultiTagInput(); | ||||
|  |  | |||
							
								
								
									
										49
									
								
								UI/CustomGenerator/SavePanel.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								UI/CustomGenerator/SavePanel.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,49 @@ | |||
| import {UIElement} from "../UIElement"; | ||||
| import {VariableUiElement} from "../Base/VariableUIElement"; | ||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | ||||
| import {LayoutConfigJson} from "../../Customizations/JSON/LayoutConfigJson"; | ||||
| import Combine from "../Base/Combine"; | ||||
| import {OsmConnection} from "../../Logic/Osm/OsmConnection"; | ||||
| import {FixedUiElement} from "../Base/FixedUiElement"; | ||||
| 
 | ||||
| export default class SavePanel extends UIElement { | ||||
|     private json: UIElement; | ||||
|     private lastSaveEl: UIElement; | ||||
| 
 | ||||
|     constructor( | ||||
|         connection: OsmConnection, | ||||
|         config: UIEventSource<LayoutConfigJson>, | ||||
|         chronic: UIEventSource<Date>) { | ||||
|         super(); | ||||
| 
 | ||||
|       | ||||
| 
 | ||||
|         this.lastSaveEl = new VariableUiElement(chronic | ||||
|             .map(date => { | ||||
|                 if (date === undefined) { | ||||
|                     return new FixedUiElement("Your theme will be saved automatically within two minutes... Click here to force saving").SetClass("alert").Render() | ||||
|                 } | ||||
|                 return "Your theme was last saved at " + date.toISOString() | ||||
|             })).onClick(() => chronic.setData(new Date())); | ||||
| 
 | ||||
|         this.json = new VariableUiElement(config.map(config => { | ||||
|             return JSON.stringify(config, null, 2) | ||||
|                 .replace(/\n/g, "<br/>") | ||||
|                 .replace(/ /g, " "); | ||||
|         })) | ||||
|             .SetClass("literal-code"); | ||||
|     } | ||||
| 
 | ||||
|     InnerRender(): string { | ||||
|         return new Combine([ | ||||
|             "<h3>Saving</h3>", | ||||
|             this.lastSaveEl, | ||||
|             "<h3>JSON configuration</h3>", | ||||
|             "The url hash is actually no more then a BASE64-encoding of the below JSON. This json contains the full configuration of the theme.<br/>" + | ||||
|             "This configuration is mainly useful for debugging", | ||||
|             this.json | ||||
|         ]).SetClass("scrollable") | ||||
|             .Render(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -12,6 +12,8 @@ import {MultiInput} from "../Input/MultiInput"; | |||
| import MappingInput from "./MappingInput"; | ||||
| import {AndOrTagConfigJson} from "../../Customizations/JSON/TagConfigJson"; | ||||
| import {TagRenderingConfigJson} from "../../Customizations/JSON/TagRenderingConfigJson"; | ||||
| import {UserDetails} from "../../Logic/Osm/OsmConnection"; | ||||
| import {State} from "../../State"; | ||||
| 
 | ||||
| export default class TagRenderingPanel extends InputElement<TagRenderingConfigJson> { | ||||
| 
 | ||||
|  | @ -24,6 +26,7 @@ export default class TagRenderingPanel extends InputElement<TagRenderingConfigJs | |||
| 
 | ||||
|     constructor(languages: UIEventSource<string[]>, | ||||
|                 currentlySelected: UIEventSource<SingleSetting<any>>, | ||||
|                 userDetails: UserDetails, | ||||
|                 options?: { | ||||
|                     title?: string, | ||||
|                     description?: string, | ||||
|  | @ -36,6 +39,10 @@ export default class TagRenderingPanel extends InputElement<TagRenderingConfigJs | |||
|         this.SetClass("min-height"); | ||||
| 
 | ||||
|         this.options = options ?? {}; | ||||
|         const questionsNotUnlocked = userDetails.csCount < State.userJourney.themeGeneratorFullUnlock; | ||||
|         this.options.disableQuestions =  | ||||
|             (this.options.disableQuestions ?? false) &&  | ||||
|             questionsNotUnlocked;  | ||||
| 
 | ||||
|         this.intro = new Combine(["<h3>", options?.title ?? "TagRendering", "</h3>", options?.description ?? ""]) | ||||
|         this.IsImage = options?.isImage ?? false; | ||||
|  | @ -47,9 +54,9 @@ export default class TagRenderingPanel extends InputElement<TagRenderingConfigJs | |||
|             return new SingleSetting<any>(value, input, id, name, description); | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         const questionSettings = [ | ||||
| 
 | ||||
|              | ||||
|             setting(new MultiLingualTextFields(languages), "question", "Question", "If the key or mapping doesn't match, this question is asked"), | ||||
| 
 | ||||
|             setting(new AndOrTagInput(), "condition", "Condition", | ||||
|  | @ -70,6 +77,8 @@ export default class TagRenderingPanel extends InputElement<TagRenderingConfigJs | |||
| 
 | ||||
|         const settings: (string | SingleSetting<any>)[] = [ | ||||
|             setting(new MultiLingualTextFields(languages), "render", "Value to show", " Renders this value. Note that <span class='literal-code'>{key}</span>-parts are substituted by the corresponding values of the element. If neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value."), | ||||
|              | ||||
|             questionsNotUnlocked ? `You need at least ${State.userJourney.themeGeneratorFullUnlock} changesets to unlock the 'question'-field and to use your theme to edit OSM data`: "", | ||||
|             ...(options?.disableQuestions ? [] : questionSettings), | ||||
| 
 | ||||
|             "<h3>Mappings</h3>", | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue