forked from MapComplete/MapComplete
		
	Optimize rendering of SpecialVisualizations.ts, speeds up studio as well (e.g. loading cyclofix took > 20s)
This commit is contained in:
		
							parent
							
								
									64c7250be0
								
							
						
					
					
						commit
						175d69870f
					
				
					 2 changed files with 73 additions and 64 deletions
				
			
		|  | @ -1,11 +1,7 @@ | |||
| import { RenderingSpecification, SpecialVisualization } from "./SpecialVisualization" | ||||
| 
 | ||||
| export default class SpecialVisualisationUtils { | ||||
|     /** | ||||
|      * Seeded by 'SpecialVisualisations' when that static class is initialized | ||||
|      * This is to avoid some pesky circular imports | ||||
|      */ | ||||
|     public static specialVisualizations: SpecialVisualization[] | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * | ||||
|  | @ -15,25 +11,23 @@ export default class SpecialVisualisationUtils { | |||
|      * import SpecialVisualisations from "./SpecialVisualizations" | ||||
|      * | ||||
|      * // Return empty list on empty input
 | ||||
|      * SpecialVisualisationUtils.specialVisualizations = SpecialVisualisations.specialVisualizations | ||||
|      * SpecialVisualisationUtils.constructSpecification("") // => []
 | ||||
|      * SpecialVisualisationUtils.constructSpecification("", SpecialVisualisations.specialVisualisationsDict) // => []
 | ||||
|      * | ||||
|      * // Simple case
 | ||||
|      * SpecialVisualisationUtils.specialVisualizations = SpecialVisualisations.specialVisualizations | ||||
|      * const oh = SpecialVisualisationUtils.constructSpecification("The opening hours with value {opening_hours} can be seen in the following table: <br/> {opening_hours_table()}") | ||||
|      * const oh = SpecialVisualisationUtils.constructSpecification("The opening hours with value {opening_hours} can be seen in the following table: <br/> {opening_hours_table()}", SpecialVisualisations.specialVisualisationsDict) | ||||
|      * oh[0] // => "The opening hours with value {opening_hours} can be seen in the following table: <br/> "
 | ||||
|      * oh[1].func.funcName // => "opening_hours_table"
 | ||||
|      * | ||||
|      * // Advanced cases with commas, braces and newlines should be handled without problem
 | ||||
|      * SpecialVisualisationUtils.specialVisualizations = SpecialVisualisations.specialVisualizations | ||||
|      * const templates = SpecialVisualisationUtils.constructSpecification("{send_email(&LBRACEemail&RBRACE,Broken bicycle pump,Hello&COMMA\n\nWith this email&COMMA I'd like to inform you that the bicycle pump located at https://mapcomplete.org/cyclofix?lat=&LBRACE_lat&RBRACE&lon=&LBRACE_lon&RBRACE&z=18#&LBRACEid&RBRACE is broken.\n\n Kind regards,Report this bicycle pump as broken)}") | ||||
|      * const templates = SpecialVisualisationUtils.constructSpecification("{send_email(&LBRACEemail&RBRACE,Broken bicycle pump,Hello&COMMA\n\nWith this email&COMMA I'd like to inform you that the bicycle pump located at https://mapcomplete.org/cyclofix?lat=&LBRACE_lat&RBRACE&lon=&LBRACE_lon&RBRACE&z=18#&LBRACEid&RBRACE is broken.\n\n Kind regards,Report this bicycle pump as broken)}",  SpecialVisualisations.specialVisualisationsDict) | ||||
|      * const templ = <Exclude<RenderingSpecification, string>> templates[0] | ||||
|      * templ.func.funcName // => "send_email"
 | ||||
|      * templ.args[0] = "{email}" | ||||
|      */ | ||||
|     public static constructSpecification( | ||||
|         template: string, | ||||
|         extraMappings: SpecialVisualization[] = [] | ||||
|         specialVisualisations: Map<string, SpecialVisualization>, | ||||
|         extraMappings: SpecialVisualization[] = [], | ||||
|     ): RenderingSpecification[] { | ||||
|         if (template === "") { | ||||
|             return [] | ||||
|  | @ -42,56 +36,68 @@ export default class SpecialVisualisationUtils { | |||
|         if (template["type"] !== undefined) { | ||||
|             console.trace( | ||||
|                 "Got a non-expanded template while constructing the specification, it still has a 'special-key':", | ||||
|                 template | ||||
|                 template, | ||||
|             ) | ||||
|             throw "Got a non-expanded template while constructing the specification" | ||||
|         } | ||||
|         const allKnownSpecials = extraMappings.concat( | ||||
|             SpecialVisualisationUtils.specialVisualizations | ||||
|         ) | ||||
|         for (const knownSpecial of allKnownSpecials) { | ||||
|             // Note: the '.*?' in the regex reads as 'any character, but in a non-greedy way'
 | ||||
|             const matched = template.match( | ||||
|                 new RegExp(`(.*){${knownSpecial.funcName}\\((.*?)\\)(:.*)?}(.*)`, "s") | ||||
|             ) | ||||
|             if (matched != null) { | ||||
|                 // We found a special component that should be brought to live
 | ||||
|                 const partBefore = SpecialVisualisationUtils.constructSpecification( | ||||
|                     matched[1], | ||||
|                     extraMappings | ||||
|                 ) | ||||
|                 const argument = | ||||
|                     matched[2] /* .trim()  // We don't trim, as spaces might be relevant, e.g. "what is ... of {title()}"*/ | ||||
|                 const style = matched[3]?.substring(1) ?? "" | ||||
|                 const partAfter = SpecialVisualisationUtils.constructSpecification( | ||||
|                     matched[4], | ||||
|                     extraMappings | ||||
|                 ) | ||||
|                 const args = knownSpecial.args.map((arg) => arg.defaultValue ?? "") | ||||
|                 if (argument.length > 0) { | ||||
|                     const realArgs = argument | ||||
|                         .split(",") | ||||
|                         .map((str) => SpecialVisualisationUtils.undoEncoding(str)) | ||||
|                     for (let i = 0; i < realArgs.length; i++) { | ||||
|                         if (args.length <= i) { | ||||
|                             args.push(realArgs[i]) | ||||
|                         } else { | ||||
|                             args[i] = realArgs[i] | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 const element: RenderingSpecification = { | ||||
|                     args, | ||||
|                     style, | ||||
|                     func: knownSpecial, | ||||
|         // Note: the '.*?' in the regex reads as 'any character, but in a non-greedy way'
 | ||||
|         const matched = template.match( | ||||
|             new RegExp(`(.*){\([a-zA-Z_]+\)\\((.*?)\\)(:.*)?}(.*)`, "s"), | ||||
|         ) | ||||
|         if (matched === null) { | ||||
|             // IF we end up here, no changes have to be made - except to remove any resting {}
 | ||||
|             return [template] | ||||
|         } | ||||
| 
 | ||||
|         const fName = matched[2] | ||||
|         let knownSpecial = specialVisualisations.get(fName) | ||||
|         if(!knownSpecial && extraMappings?.length > 0){ | ||||
|             knownSpecial = extraMappings.find(em => em.funcName === fName) | ||||
|         } | ||||
|         if(!knownSpecial){ | ||||
|             throw "Didn't find a special visualisation: "+fName | ||||
|         } | ||||
| 
 | ||||
|         // Always a boring string
 | ||||
|         const partBefore: string = matched[1] | ||||
|         const argument: string = | ||||
|             matched[3] /* .trim()  // We don't trim, as spaces might be relevant, e.g. "what is ... of {title()}"*/ | ||||
|         const style: string = matched[4]?.substring(1) ?? "" | ||||
|         const partAfter: RenderingSpecification[] = SpecialVisualisationUtils.constructSpecification( | ||||
|             matched[5], | ||||
|             specialVisualisations, | ||||
|             extraMappings, | ||||
|         ) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|         const args: string[] = knownSpecial.args.map((arg) => arg.defaultValue ?? "") | ||||
|         if (argument.length > 0) { | ||||
|             const realArgs = argument | ||||
|                 .split(",") | ||||
|                 .map((str) => SpecialVisualisationUtils.undoEncoding(str)) | ||||
|             for (let i = 0; i < realArgs.length; i++) { | ||||
|                 if (args.length <= i) { | ||||
|                     args.push(realArgs[i]) | ||||
|                 } else { | ||||
|                     args[i] = realArgs[i] | ||||
|                 } | ||||
|                 return [...partBefore, element, ...partAfter] | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // IF we end up here, no changes have to be made - except to remove any resting {}
 | ||||
|         return [template] | ||||
|         const element: RenderingSpecification = { | ||||
|             args, | ||||
|             style, | ||||
|             func: knownSpecial, | ||||
|         } | ||||
|         partAfter.unshift(element) | ||||
|         if(partBefore.length > 0){ | ||||
|             partAfter.unshift(partBefore) | ||||
| 
 | ||||
|         } | ||||
|         return partAfter | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     private static undoEncoding(str: string) { | ||||
|  |  | |||
|  | @ -2,11 +2,7 @@ import Combine from "./Base/Combine" | |||
| import { FixedUiElement } from "./Base/FixedUiElement" | ||||
| import BaseUIElement from "./BaseUIElement" | ||||
| import Title from "./Base/Title" | ||||
| import { | ||||
|     RenderingSpecification, | ||||
|     SpecialVisualization, | ||||
|     SpecialVisualizationState, | ||||
| } from "./SpecialVisualization" | ||||
| import { RenderingSpecification, SpecialVisualization, SpecialVisualizationState } from "./SpecialVisualization" | ||||
| import { HistogramViz } from "./Popup/HistogramViz" | ||||
| import MinimapViz from "./Popup/MinimapViz.svelte" | ||||
| import { ShareLinkViz } from "./Popup/ShareLinkViz" | ||||
|  | @ -23,7 +19,7 @@ import { ImageCarousel } from "./Image/ImageCarousel" | |||
| import { VariableUiElement } from "./Base/VariableUIElement" | ||||
| import { Utils } from "../Utils" | ||||
| import Wikidata, { WikidataResponse } from "../Logic/Web/Wikidata" | ||||
| import { Translation, TypedTranslation } from "./i18n/Translation" | ||||
| import { Translation } from "./i18n/Translation" | ||||
| import Translations from "./i18n/Translations" | ||||
| import OpeningHoursVisualization from "./OpeningHours/OpeningHoursVisualization" | ||||
| import { SubtleButton } from "./Base/SubtleButton" | ||||
|  | @ -326,6 +322,13 @@ export class QuestionViz implements SpecialVisualization { | |||
| 
 | ||||
| export default class SpecialVisualizations { | ||||
|     public static specialVisualizations: SpecialVisualization[] = SpecialVisualizations.initList() | ||||
|     public static specialVisualisationsDict: Map<string, SpecialVisualization> = new Map<string, SpecialVisualization>() | ||||
| 
 | ||||
|     static { | ||||
|         for (const specialVisualization of SpecialVisualizations.specialVisualizations) { | ||||
|             SpecialVisualizations.specialVisualisationsDict.set(specialVisualization.funcName, specialVisualization) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static DocumentationFor(viz: string | SpecialVisualization): string { | ||||
|         if (typeof viz === "string") { | ||||
|  | @ -361,7 +364,7 @@ export default class SpecialVisualizations { | |||
|         template: string, | ||||
|         extraMappings: SpecialVisualization[] = [], | ||||
|     ): RenderingSpecification[] { | ||||
|         return SpecialVisualisationUtils.constructSpecification(template, extraMappings) | ||||
|         return SpecialVisualisationUtils.constructSpecification(template, SpecialVisualizations.specialVisualisationsDict,  extraMappings) | ||||
|     } | ||||
| 
 | ||||
|     public static HelpMessage(): string { | ||||
|  | @ -2055,18 +2058,18 @@ export default class SpecialVisualizations { | |||
| 
 | ||||
|         specialVisualizations.push(new AutoApplyButton(specialVisualizations)) | ||||
| 
 | ||||
|         const regex= /[a-zA-Z_]+/ | ||||
|         const invalid = specialVisualizations | ||||
|             .map((sp, i) => ({ sp, i })) | ||||
|             .filter((sp) => sp.sp.funcName === undefined) | ||||
|             .filter((sp) => sp.sp.funcName === undefined || !sp.sp.funcName.match(regex)) | ||||
|         if (invalid.length > 0) { | ||||
|             throw ( | ||||
|                 "Invalid special visualisation found: funcName is undefined for " + | ||||
|                 "Invalid special visualisation found: funcName is undefined or doesn't match "+regex + | ||||
|                 invalid.map((sp) => sp.i).join(", ") + | ||||
|                 ". Did you perhaps type \n  funcName: \"funcname\" // type declaration uses COLON\ninstead of:\n  funcName = \"funcName\" // value definition uses EQUAL" | ||||
|             ) | ||||
|         } | ||||
| 
 | ||||
|         SpecialVisualisationUtils.specialVisualizations = Utils.NoNull(specialVisualizations) | ||||
|         return specialVisualizations | ||||
|     } | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue