forked from MapComplete/MapComplete
		
	Performance improvements
This commit is contained in:
		
							parent
							
								
									49f78d5604
								
							
						
					
					
						commit
						8f8ef690a4
					
				
					 11 changed files with 125 additions and 101 deletions
				
			
		|  | @ -8,13 +8,13 @@ import {TagRenderingConfigJson} from "./TagRenderingConfigJson"; | ||||||
| import {Translation} from "../../UI/i18n/Translation"; | import {Translation} from "../../UI/i18n/Translation"; | ||||||
| import Img from "../../UI/Base/Img"; | import Img from "../../UI/Base/Img"; | ||||||
| import Svg from "../../Svg"; | import Svg from "../../Svg"; | ||||||
| import {SubstitutedTranslation} from "../../UI/SpecialVisualizations"; |  | ||||||
| import {Utils} from "../../Utils"; | import {Utils} from "../../Utils"; | ||||||
| import Combine from "../../UI/Base/Combine"; | import Combine from "../../UI/Base/Combine"; | ||||||
| import {VariableUiElement} from "../../UI/Base/VariableUIElement"; | import {VariableUiElement} from "../../UI/Base/VariableUIElement"; | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import {FixedUiElement} from "../../UI/Base/FixedUiElement"; | import {FixedUiElement} from "../../UI/Base/FixedUiElement"; | ||||||
| import {UIElement} from "../../UI/UIElement"; | import {UIElement} from "../../UI/UIElement"; | ||||||
|  | import {SubstitutedTranslation} from "../../UI/SubstitutedTranslation"; | ||||||
| 
 | 
 | ||||||
| export default class LayerConfig { | export default class LayerConfig { | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -336,7 +336,7 @@ export class InitUiElements { | ||||||
|             } |             } | ||||||
|             let features = featuresFreshness.map(ff => ff.feature); |             let features = featuresFreshness.map(ff => ff.feature); | ||||||
|             features.forEach(feature => { |             features.forEach(feature => { | ||||||
|                 State.state.allElements.addElement(feature); |                 State.state.allElements.addOrGetElement(feature); | ||||||
|                  |                  | ||||||
|                 if(Hash.hash.data === feature.properties.id.replace("/","_")){ |                 if(Hash.hash.data === feature.properties.id.replace("/","_")){ | ||||||
|                     State.state.selectedElement.setData(feature); |                     State.state.selectedElement.setData(feature); | ||||||
|  |  | ||||||
|  | @ -16,7 +16,7 @@ export class ElementStorage { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     addElement(element): UIEventSource<any> { |     addElement(element): UIEventSource<any> { | ||||||
|         const eventSource = new UIEventSource<any>(element.properties); |         const eventSource = new UIEventSource<any>(element.properties, "tags of "+element.properties.id); | ||||||
|         this._elements[element.properties.id] = eventSource; |         this._elements[element.properties.id] = eventSource; | ||||||
|         return eventSource; |         return eventSource; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -69,7 +69,7 @@ export class OsmConnection { | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         this.userDetails = new UIEventSource<UserDetails>(new UserDetails()); |         this.userDetails = new UIEventSource<UserDetails>(new UserDetails(), "userDetails"); | ||||||
|         this.userDetails.data.dryRun = dryRun; |         this.userDetails.data.dryRun = dryRun; | ||||||
|         this._dryRun = dryRun; |         this._dryRun = dryRun; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -13,7 +13,7 @@ export class UIEventSource<T> { | ||||||
|             return []; |             return []; | ||||||
|         } |         } | ||||||
|         // @ts-ignore
 |         // @ts-ignore
 | ||||||
|         window.mcperf = () => { |         window.mapcomplete_performance = () => { | ||||||
|             console.log(UIEventSource.allSources.length, "uieventsources created"); |             console.log(UIEventSource.allSources.length, "uieventsources created"); | ||||||
|             const copy = [...UIEventSource.allSources]; |             const copy = [...UIEventSource.allSources]; | ||||||
|             copy.sort((a,b) => b._callbacks.length - a._callbacks.length); |             copy.sort((a,b) => b._callbacks.length - a._callbacks.length); | ||||||
|  |  | ||||||
|  | @ -1,10 +1,10 @@ | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import TagRenderingConfig from "../../Customizations/JSON/TagRenderingConfig"; | import TagRenderingConfig from "../../Customizations/JSON/TagRenderingConfig"; | ||||||
| import {UIElement} from "../UIElement"; | import {UIElement} from "../UIElement"; | ||||||
| import {SubstitutedTranslation} from "../SpecialVisualizations"; |  | ||||||
| import {Utils} from "../../Utils"; | import {Utils} from "../../Utils"; | ||||||
| import Combine from "../Base/Combine"; | import Combine from "../Base/Combine"; | ||||||
| import {TagUtils} from "../../Logic/Tags"; | import {TagUtils} from "../../Logic/Tags"; | ||||||
|  | import {SubstitutedTranslation} from "../SubstitutedTranslation"; | ||||||
| 
 | 
 | ||||||
| /*** | /*** | ||||||
|  * Displays the correct value for a known tagrendering |  * Displays the correct value for a known tagrendering | ||||||
|  | @ -64,7 +64,7 @@ export default class TagRenderingAnswer extends UIElement { | ||||||
|          |          | ||||||
|         const tr = this._configuration.GetRenderValue(tags); |         const tr = this._configuration.GetRenderValue(tags); | ||||||
|         if (tr !== undefined) { |         if (tr !== undefined) { | ||||||
|             this._content = new SubstitutedTranslation(tr, this._tags); |             this._content = SubstitutedTranslation.construct(tr, this._tags); | ||||||
|             return this._content.Render(); |             return this._content.Render(); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -6,7 +6,6 @@ import {InputElement} from "../Input/InputElement"; | ||||||
| import {And, Tag, TagsFilter, TagUtils} from "../../Logic/Tags"; | import {And, Tag, TagsFilter, TagUtils} from "../../Logic/Tags"; | ||||||
| import ValidatedTextField from "../Input/ValidatedTextField"; | import ValidatedTextField from "../Input/ValidatedTextField"; | ||||||
| import {FixedInputElement} from "../Input/FixedInputElement"; | import {FixedInputElement} from "../Input/FixedInputElement"; | ||||||
| import {SubstitutedTranslation} from "../SpecialVisualizations"; |  | ||||||
| import {RadioButton} from "../Input/RadioButton"; | import {RadioButton} from "../Input/RadioButton"; | ||||||
| import {Utils} from "../../Utils"; | import {Utils} from "../../Utils"; | ||||||
| import CheckBoxes from "../Input/Checkboxes"; | import CheckBoxes from "../Input/Checkboxes"; | ||||||
|  | @ -19,6 +18,7 @@ import Translations from "../i18n/Translations"; | ||||||
| import {FixedUiElement} from "../Base/FixedUiElement"; | import {FixedUiElement} from "../Base/FixedUiElement"; | ||||||
| import {Translation} from "../i18n/Translation"; | import {Translation} from "../i18n/Translation"; | ||||||
| import Constants from "../../Models/Constants"; | import Constants from "../../Models/Constants"; | ||||||
|  | import {SubstitutedTranslation} from "../SubstitutedTranslation"; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Shows the question element. |  * Shows the question element. | ||||||
|  | @ -203,7 +203,7 @@ export default class TagRenderingQuestion extends UIElement { | ||||||
|             return undefined; |             return undefined; | ||||||
|         } |         } | ||||||
|         return new FixedInputElement( |         return new FixedInputElement( | ||||||
|             new SubstitutedTranslation(mapping.then, this._tags), |             SubstitutedTranslation.construct(mapping.then, this._tags), | ||||||
|             mapping.if, |             mapping.if, | ||||||
|             (t0, t1) => t1.isEquivalent(t0)); |             (t0, t1) => t1.isEquivalent(t0)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -116,12 +116,12 @@ export default class ShowDataLayer { | ||||||
|         const popup = L.popup({ |         const popup = L.popup({ | ||||||
|             autoPan: true, |             autoPan: true, | ||||||
|             closeOnEscapeKey: true, |             closeOnEscapeKey: true, | ||||||
|  |             closeButton: false | ||||||
|         }, leafletLayer); |         }, leafletLayer); | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         const tags = State.state.allElements.getEventSourceFor(feature); |         const tags = State.state.allElements.getEventSourceFor(feature); | ||||||
|         const uiElement: LazyElement<UIElement> = new LazyElement(() =>new Combine([ new FeatureInfoBox(tags, layer, () => { |         const uiElement: LazyElement<UIElement> = new LazyElement(() =>new Combine([ new FeatureInfoBox(tags, layer, () => { | ||||||
|                 console.log("Closing the popup!") |  | ||||||
|                 State.state.selectedElement.setData(undefined); |                 State.state.selectedElement.setData(undefined); | ||||||
|                 popup.remove(); |                 popup.remove(); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -5,9 +5,7 @@ import LiveQueryHandler from "../Logic/Web/LiveQueryHandler"; | ||||||
| import {ImageCarousel} from "./Image/ImageCarousel"; | import {ImageCarousel} from "./Image/ImageCarousel"; | ||||||
| import Combine from "./Base/Combine"; | import Combine from "./Base/Combine"; | ||||||
| import {FixedUiElement} from "./Base/FixedUiElement"; | import {FixedUiElement} from "./Base/FixedUiElement"; | ||||||
| import Locale from "../UI/i18n/Locale"; |  | ||||||
| import {ImageUploadFlow} from "./Image/ImageUploadFlow"; | import {ImageUploadFlow} from "./Image/ImageUploadFlow"; | ||||||
| import {Translation} from "./i18n/Translation"; |  | ||||||
| 
 | 
 | ||||||
| import ShareButton from "./BigComponents/ShareButton"; | import ShareButton from "./BigComponents/ShareButton"; | ||||||
| import Svg from "../Svg"; | import Svg from "../Svg"; | ||||||
|  | @ -19,93 +17,6 @@ import OpeningHoursVisualization from "./OpeningHours/OhVisualization"; | ||||||
| 
 | 
 | ||||||
| import State from "../State"; | import State from "../State"; | ||||||
| 
 | 
 | ||||||
| export class SubstitutedTranslation extends UIElement { |  | ||||||
|     private readonly tags: UIEventSource<any>; |  | ||||||
|     private readonly translation: Translation; |  | ||||||
|     private content: UIElement[]; |  | ||||||
| 
 |  | ||||||
|     constructor( |  | ||||||
|         translation: Translation, |  | ||||||
|         tags: UIEventSource<any>) { |  | ||||||
|         super(tags); |  | ||||||
|         this.translation = translation; |  | ||||||
|         this.tags = tags; |  | ||||||
|         const self = this; |  | ||||||
|         tags.addCallbackAndRun(() => { |  | ||||||
|             self.content = self.CreateContent(); |  | ||||||
|             self.Update(); |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|         Locale.language.addCallback(() => { |  | ||||||
|             self.content = self.CreateContent(); |  | ||||||
|             self.Update(); |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     InnerRender(): string { |  | ||||||
|         return new Combine(this.content).Render(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private CreateContent(): UIElement[] { |  | ||||||
|         let txt = this.translation?.txt; |  | ||||||
|         if (txt === undefined) { |  | ||||||
|             return [] |  | ||||||
|         } |  | ||||||
|         const tags = this.tags.data; |  | ||||||
|         txt = SubstitutedTranslation.SubstituteKeys(txt, tags); |  | ||||||
|         return this.EvaluateSpecialComponents(txt); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public static SubstituteKeys(txt: string, tags: any) { |  | ||||||
|         for (const key in tags) { |  | ||||||
|             // Poor mans replace all
 |  | ||||||
|             txt = txt.split("{" + key + "}").join(tags[key]); |  | ||||||
|         } |  | ||||||
|         return txt; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private EvaluateSpecialComponents(template: string): UIElement[] { |  | ||||||
| 
 |  | ||||||
|         for (const knownSpecial of SpecialVisualizations.specialVisualizations) { |  | ||||||
| 
 |  | ||||||
|             // Note: the '.*?' in the regex reads as 'any character, but in a non-greedy way'
 |  | ||||||
|             const matched = template.match(`(.*){${knownSpecial.funcName}\\((.*?)\\)}(.*)`); |  | ||||||
|             if (matched != null) { |  | ||||||
| 
 |  | ||||||
|                 // We found a special component that should be brought to live
 |  | ||||||
|                 const partBefore = this.EvaluateSpecialComponents(matched[1]); |  | ||||||
|                 const argument = matched[2].trim(); |  | ||||||
|                 const partAfter = this.EvaluateSpecialComponents(matched[3]); |  | ||||||
|                 try { |  | ||||||
|                     const args = knownSpecial.args.map(arg => arg.defaultValue ?? ""); |  | ||||||
|                     if (argument.length > 0) { |  | ||||||
|                         const realArgs = argument.split(",").map(str => str.trim()); |  | ||||||
|                         for (let i = 0; i < realArgs.length; i++) { |  | ||||||
|                             if (args.length <= i) { |  | ||||||
|                                 args.push(realArgs[i]); |  | ||||||
|                             } else { |  | ||||||
|                                 args[i] = realArgs[i]; |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|                     const element = knownSpecial.constr(State.state, this.tags, args); |  | ||||||
|                     return [...partBefore, element, ...partAfter] |  | ||||||
|                 } catch (e) { |  | ||||||
|                     console.error(e); |  | ||||||
|                     return [...partBefore, new FixedUiElement(`Failed loading ${knownSpecial.funcName}(${matched[2]}): ${e}`), ...partAfter] |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // IF we end up here, no changes have to be made
 |  | ||||||
|         return [new FixedUiElement(template)]; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export default class SpecialVisualizations { | export default class SpecialVisualizations { | ||||||
| 
 | 
 | ||||||
|     public static specialVisualizations: { |     public static specialVisualizations: { | ||||||
|  |  | ||||||
							
								
								
									
										113
									
								
								UI/SubstitutedTranslation.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								UI/SubstitutedTranslation.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,113 @@ | ||||||
|  | import {UIElement} from "./UIElement"; | ||||||
|  | import {UIEventSource} from "../Logic/UIEventSource"; | ||||||
|  | import {Translation} from "./i18n/Translation"; | ||||||
|  | import Locale from "./i18n/Locale"; | ||||||
|  | import Combine from "./Base/Combine"; | ||||||
|  | import State from "../State"; | ||||||
|  | import {FixedUiElement} from "./Base/FixedUiElement"; | ||||||
|  | import SpecialVisualizations from "./SpecialVisualizations"; | ||||||
|  | 
 | ||||||
|  | export class SubstitutedTranslation extends UIElement { | ||||||
|  |     private static cachedTranslations: Map<Translation, Map<UIEventSource<any>, SubstitutedTranslation>> = new Map<Translation, Map<UIEventSource<any>, SubstitutedTranslation>>(); | ||||||
|  |     private readonly tags: UIEventSource<any>; | ||||||
|  |     private readonly translation: Translation; | ||||||
|  |     private content: UIElement[]; | ||||||
|  | 
 | ||||||
|  |     private constructor( | ||||||
|  |         translation: Translation, | ||||||
|  |         tags: UIEventSource<any>) { | ||||||
|  |         super(tags); | ||||||
|  |         this.translation = translation; | ||||||
|  |         this.tags = tags; | ||||||
|  |         const self = this; | ||||||
|  |         tags.addCallbackAndRun(() => { | ||||||
|  |             self.content = self.CreateContent(); | ||||||
|  |             self.Update(); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         Locale.language.addCallback(() => { | ||||||
|  |             self.content = self.CreateContent(); | ||||||
|  |             self.Update(); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static construct( | ||||||
|  |         translation: Translation, | ||||||
|  |         tags: UIEventSource<any>): SubstitutedTranslation { | ||||||
|  |         if (!this.cachedTranslations.has(translation)) { | ||||||
|  |             this.cachedTranslations.set(translation, new Map<UIEventSource<any>, SubstitutedTranslation>()); | ||||||
|  |         } | ||||||
|  |         const innerMap = this.cachedTranslations.get(translation); | ||||||
|  | 
 | ||||||
|  |         const cachedTranslation = innerMap.get(tags); | ||||||
|  |         if (cachedTranslation !== undefined) { | ||||||
|  |             return cachedTranslation; | ||||||
|  |         } | ||||||
|  |         const st = new SubstitutedTranslation(translation, tags); | ||||||
|  |         innerMap.set(tags, st); | ||||||
|  |         return st; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static SubstituteKeys(txt: string, tags: any) { | ||||||
|  |         for (const key in tags) { | ||||||
|  |             // Poor mans replace all
 | ||||||
|  |             txt = txt.split("{" + key + "}").join(tags[key]); | ||||||
|  |         } | ||||||
|  |         return txt; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     InnerRender(): string { | ||||||
|  |         return new Combine(this.content).Render(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private CreateContent(): UIElement[] { | ||||||
|  |         let txt = this.translation?.txt; | ||||||
|  |         if (txt === undefined) { | ||||||
|  |             return [] | ||||||
|  |         } | ||||||
|  |         const tags = this.tags.data; | ||||||
|  |         txt = SubstitutedTranslation.SubstituteKeys(txt, tags); | ||||||
|  |         return this.EvaluateSpecialComponents(txt); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private EvaluateSpecialComponents(template: string): UIElement[] { | ||||||
|  | 
 | ||||||
|  |         for (const knownSpecial of SpecialVisualizations.specialVisualizations) { | ||||||
|  | 
 | ||||||
|  |             // Note: the '.*?' in the regex reads as 'any character, but in a non-greedy way'
 | ||||||
|  |             const matched = template.match(`(.*){${knownSpecial.funcName}\\((.*?)\\)}(.*)`); | ||||||
|  |             if (matched != null) { | ||||||
|  | 
 | ||||||
|  |                 // We found a special component that should be brought to live
 | ||||||
|  |                 const partBefore = this.EvaluateSpecialComponents(matched[1]); | ||||||
|  |                 const argument = matched[2].trim(); | ||||||
|  |                 const partAfter = this.EvaluateSpecialComponents(matched[3]); | ||||||
|  |                 try { | ||||||
|  |                     const args = knownSpecial.args.map(arg => arg.defaultValue ?? ""); | ||||||
|  |                     if (argument.length > 0) { | ||||||
|  |                         const realArgs = argument.split(",").map(str => str.trim()); | ||||||
|  |                         for (let i = 0; i < realArgs.length; i++) { | ||||||
|  |                             if (args.length <= i) { | ||||||
|  |                                 args.push(realArgs[i]); | ||||||
|  |                             } else { | ||||||
|  |                                 args[i] = realArgs[i]; | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |                     const element = knownSpecial.constr(State.state, this.tags, args); | ||||||
|  |                     return [...partBefore, element, ...partAfter] | ||||||
|  |                 } catch (e) { | ||||||
|  |                     console.error(e); | ||||||
|  |                     return [...partBefore, new FixedUiElement(`Failed loading ${knownSpecial.funcName}(${matched[2]}): ${e}`), ...partAfter] | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // IF we end up here, no changes have to be made
 | ||||||
|  |         return [new FixedUiElement(template)]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -9,10 +9,10 @@ import Translations from "../UI/i18n/Translations"; | ||||||
| import {UIEventSource} from "../Logic/UIEventSource"; | import {UIEventSource} from "../Logic/UIEventSource"; | ||||||
| import TagRenderingConfig from "../Customizations/JSON/TagRenderingConfig"; | import TagRenderingConfig from "../Customizations/JSON/TagRenderingConfig"; | ||||||
| import EditableTagRendering from "../UI/Popup/EditableTagRendering"; | import EditableTagRendering from "../UI/Popup/EditableTagRendering"; | ||||||
| import {SubstitutedTranslation} from "../UI/SpecialVisualizations"; |  | ||||||
| import {Translation} from "../UI/i18n/Translation"; | import {Translation} from "../UI/i18n/Translation"; | ||||||
| import {OH, OpeningHour} from "../UI/OpeningHours/OpeningHours"; | import {OH, OpeningHour} from "../UI/OpeningHours/OpeningHours"; | ||||||
| import PublicHolidayInput from "../UI/OpeningHours/PublicHolidayInput"; | import PublicHolidayInput from "../UI/OpeningHours/PublicHolidayInput"; | ||||||
|  | import {SubstitutedTranslation} from "../UI/SubstitutedTranslation"; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| new T([ | new T([ | ||||||
|  | @ -109,7 +109,7 @@ new T([ | ||||||
|         equal(undefined, tr.GetRenderValue({"foo": "bar"})); |         equal(undefined, tr.GetRenderValue({"foo": "bar"})); | ||||||
|         equal("Has no name", tr.GetRenderValue({"noname": "yes"})?.txt); |         equal("Has no name", tr.GetRenderValue({"noname": "yes"})?.txt); | ||||||
|         equal("Ook een {name}", tr.GetRenderValue({"name": "xyz"})?.txt); |         equal("Ook een {name}", tr.GetRenderValue({"name": "xyz"})?.txt); | ||||||
|         equal("Ook een xyz", new SubstitutedTranslation( tr.GetRenderValue({"name": "xyz"}), |         equal("Ook een xyz", SubstitutedTranslation.construct( tr.GetRenderValue({"name": "xyz"}), | ||||||
|             new UIEventSource<any>({"name":"xyz"})).InnerRender()); |             new UIEventSource<any>({"name":"xyz"})).InnerRender()); | ||||||
|         equal(undefined, tr.GetRenderValue({"foo": "bar"})); |         equal(undefined, tr.GetRenderValue({"foo": "bar"})); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue