forked from MapComplete/MapComplete
		
	Refactoring: port AutoApplyButton to svelte
This commit is contained in:
		
							parent
							
								
									ab88afc397
								
							
						
					
					
						commit
						56020adc2f
					
				
					 8 changed files with 318 additions and 353 deletions
				
			
		|  | @ -3,6 +3,10 @@ import Translations from "../i18n/Translations" | |||
| import BaseUIElement from "../BaseUIElement" | ||||
| import SvelteUIElement from "./SvelteUIElement" | ||||
| import { default as LoadingSvg } from "../../assets/svg/Loading.svelte" | ||||
| 
 | ||||
| /** | ||||
|  * @deprecated | ||||
|  */ | ||||
| export default class Loading extends Combine { | ||||
|     constructor(msg?: BaseUIElement | string) { | ||||
|         const t = Translations.W(msg) ?? Translations.t.general.loading | ||||
|  |  | |||
							
								
								
									
										179
									
								
								src/UI/Popup/AutoApplyButton.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										179
									
								
								src/UI/Popup/AutoApplyButton.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,179 @@ | |||
| <script lang="ts"> | ||||
|   import Loading from "../Base/Loading.svelte" | ||||
|   import MaplibreMap from "../Map/MaplibreMap.svelte" | ||||
|   import { Utils } from "../../Utils" | ||||
|   import { Store, UIEventSource } from "../../Logic/UIEventSource" | ||||
|   import FilteredLayer from "../../Models/FilteredLayer" | ||||
|   import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig" | ||||
|   import type { SpecialVisualizationState } from "../SpecialVisualization" | ||||
|   import { MapLibreAdaptor } from "../Map/MapLibreAdaptor" | ||||
|   import ShowDataLayer from "../Map/ShowDataLayer" | ||||
|   import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource" | ||||
|   import SpecialVisualizations from "../SpecialVisualizations" | ||||
|   import type { AutoAction } from "./AutoApplyButtonVis" | ||||
|   import Tr from "../Base/Tr.svelte" | ||||
|   import Translations from "../i18n/Translations" | ||||
| 
 | ||||
|   /** | ||||
|    * The ids to handle. Might be data from an external dataset, we cannot assume an OSM-id | ||||
|    */ | ||||
|   export let ids: Store<string[] | undefined> | ||||
|   export let state: SpecialVisualizationState | ||||
|   export let options: { | ||||
|     target_layer_id: string, | ||||
|     targetTagRendering: string, | ||||
|     text: string, | ||||
|     icon: string, | ||||
|   } | ||||
|   let buttonState: UIEventSource< | ||||
|     "idle" | "running" | "done" | { error: string } | ||||
|   > = new UIEventSource< | ||||
|     "idle" | "running" | "done" | { error: string } | ||||
|   >("idle") | ||||
|   let tagRenderingConfig: TagRenderingConfig | ||||
|   let appliedNumberOfFeatures = new UIEventSource<number>(0) | ||||
| 
 | ||||
|   let layer: FilteredLayer = state.layerState.filteredLayers.get(options.target_layer_id) | ||||
|   tagRenderingConfig = layer.layerDef.tagRenderings.find( | ||||
|     (tr) => tr.id === options.targetTagRendering | ||||
|   ) | ||||
| 
 | ||||
|   const mlmap = new UIEventSource(undefined) | ||||
|   const mla = new MapLibreAdaptor(mlmap, { | ||||
|     rasterLayer: state.mapProperties.rasterLayer | ||||
|   }) | ||||
|   mla.allowZooming.setData(false) | ||||
|   mla.allowMoving.setData(false) | ||||
| 
 | ||||
|   const features = ids.mapD(ids => ids.map((id) => | ||||
|     state.indexedFeatures.featuresById.data.get(id) | ||||
|   )) | ||||
| 
 | ||||
| 
 | ||||
|   new ShowDataLayer(mlmap, { | ||||
|     features: StaticFeatureSource.fromGeojson(features), | ||||
|     zoomToFeatures: true, | ||||
|     layer: layer.layerDef | ||||
|   }) | ||||
| 
 | ||||
|   features.addCallbackAndRunD(f => console.log("Features are now", f)) | ||||
| 
 | ||||
|   async function applyAllChanges() { | ||||
|     buttonState.set("running") | ||||
|     try { | ||||
|       const target_feature_ids = ids.data | ||||
|       console.log("Applying auto-action on " + target_feature_ids.length + " features") | ||||
|       const appliedOn: string[] = [] | ||||
|       for (let i = 0; i < target_feature_ids.length; i++) { | ||||
|         const targetFeatureId = target_feature_ids[i] | ||||
|         console.log("Handling", targetFeatureId) | ||||
|         const feature = state.indexedFeatures.featuresById.data.get(targetFeatureId) | ||||
|         const featureTags = state.featureProperties.getStore(targetFeatureId) | ||||
|         const rendering = tagRenderingConfig.GetRenderValue(featureTags.data).txt | ||||
|         const specialRenderings = Utils.NoNull( | ||||
|           SpecialVisualizations.constructSpecification(rendering) | ||||
|         ).filter((v) => typeof v !== "string" && v.func["supportsAutoAction"] === true) | ||||
| 
 | ||||
|         if (specialRenderings.length == 0) { | ||||
|           console.warn( | ||||
|             "AutoApply: feature " + | ||||
|             targetFeatureId + | ||||
|             " got a rendering without supported auto actions:", | ||||
|             rendering | ||||
|           ) | ||||
|         } | ||||
| 
 | ||||
|         for (const specialRendering of specialRenderings) { | ||||
|           if (typeof specialRendering === "string") { | ||||
|             continue | ||||
|           } | ||||
|           const action = <AutoAction>specialRendering.func | ||||
|           await action.applyActionOn( | ||||
|             feature, | ||||
|             state, | ||||
|             featureTags, | ||||
|             specialRendering.args | ||||
|           ) | ||||
|         } | ||||
|         appliedOn.push(targetFeatureId) | ||||
|         if (i % 50 === 0) { | ||||
|           await state.changes.flushChanges("Auto button: intermediate save") | ||||
|         } | ||||
|         appliedNumberOfFeatures.setData(i + 1) | ||||
|       } | ||||
|       console.log("Flushing changes...") | ||||
|       await state.changes.flushChanges("Auto button: done") | ||||
|       buttonState.setData("done") | ||||
|       console.log( | ||||
|         "Applied changes onto", | ||||
|         appliedOn.length, | ||||
|         "items, unique IDs:", | ||||
|         new Set(appliedOn).size | ||||
|       ) | ||||
|     } catch (e) { | ||||
|       console.error("Error while running autoApply: ", e) | ||||
|       buttonState.setData({ error: e }) | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   const t = Translations.t.general.add.import | ||||
| 
 | ||||
| </script> | ||||
| 
 | ||||
| {#if !state.theme.official && !state.featureSwitchIsTesting.data} | ||||
|   <div class="alert">The auto-apply button is only available in official themes (or in testing mode)</div> | ||||
|   <Tr t={t.howToTest} /> | ||||
| 
 | ||||
| {:else if ids === undefined} | ||||
|   <Loading>Gathering which elements support auto-apply...</Loading> | ||||
| {:else if tagRenderingConfig === undefined} | ||||
|   <div class="alert">Target tagrendering {options.targetTagRendering} not found"</div> | ||||
| {:else if $ids.length === 0} | ||||
|   <div>No elements found to perform action</div> | ||||
| {:else if $buttonState.error !== undefined} | ||||
|   <div class="flex flex-col"> | ||||
| 
 | ||||
|     <div class="alert">Something went wrong</div> | ||||
|     <div>{$buttonState.error}</div> | ||||
|   </div> | ||||
| {:else if $buttonState === "done"} | ||||
|   <div class="thanks">All done!</div> | ||||
| {:else if $buttonState === "running"} | ||||
|   <Loading> | ||||
|     Applying changes, currently at {$appliedNumberOfFeatures} / {$ids.length} | ||||
|   </Loading> | ||||
| {:else if $buttonState === "idle"} | ||||
|   <div class="flex flex-col"> | ||||
|     <button on:click={() => {applyAllChanges()}}> | ||||
|       <img class="h-8 w-8" alt="" src={options.icon} /> | ||||
|       {options.text} | ||||
|     </button> | ||||
| 
 | ||||
|     <div class="h-48 w-full"> | ||||
|       <MaplibreMap mapProperties={mla} map={mlmap} /> | ||||
|     </div> | ||||
| 
 | ||||
|     <div class="subtle link-underline"> | ||||
|       The following objects will be updated: | ||||
|       <div class="flex flex-wrap gap-x-2"> | ||||
| 
 | ||||
|         {#each $ids as featId} | ||||
|           {#if layer.layerDef.source.geojsonSource === undefined} | ||||
|             <a href={"https://openstreetmap.org/" + featId} target="_blank">{featId}</a> | ||||
|           {:else} | ||||
|             <div> | ||||
|               {featId} | ||||
|             </div> | ||||
|           {/if} | ||||
|         {/each} | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| {:else} | ||||
|   <div>Not supposed to show this... AutoApplyButton has invalid buttonstate: {$buttonState}</div> | ||||
| {/if} | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|  | @ -1,341 +0,0 @@ | |||
| import BaseUIElement from "../BaseUIElement" | ||||
| import { Stores, UIEventSource } from "../../Logic/UIEventSource" | ||||
| import { SubtleButton } from "../Base/SubtleButton" | ||||
| import Img from "../Base/Img" | ||||
| import { FixedUiElement } from "../Base/FixedUiElement" | ||||
| import Combine from "../Base/Combine" | ||||
| import Link from "../Base/Link" | ||||
| import { Utils } from "../../Utils" | ||||
| import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource" | ||||
| import { VariableUiElement } from "../Base/VariableUIElement" | ||||
| import Loading from "../Base/Loading" | ||||
| import Translations from "../i18n/Translations" | ||||
| import ThemeConfig from "../../Models/ThemeConfig/ThemeConfig" | ||||
| import { Changes } from "../../Logic/Osm/Changes" | ||||
| import { UIElement } from "../UIElement" | ||||
| import FilteredLayer from "../../Models/FilteredLayer" | ||||
| import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig" | ||||
| import Lazy from "../Base/Lazy" | ||||
| import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" | ||||
| import { IndexedFeatureSource } from "../../Logic/FeatureSource/FeatureSource" | ||||
| import { MapLibreAdaptor } from "../Map/MapLibreAdaptor" | ||||
| import ShowDataLayer from "../Map/ShowDataLayer" | ||||
| import SvelteUIElement from "../Base/SvelteUIElement" | ||||
| import MaplibreMap from "../Map/MaplibreMap.svelte" | ||||
| import SpecialVisualizations from "../SpecialVisualizations" | ||||
| import { Feature } from "geojson" | ||||
| 
 | ||||
| export interface AutoAction extends SpecialVisualization { | ||||
|     supportsAutoAction: boolean | ||||
| 
 | ||||
|     applyActionOn( | ||||
|         feature: Feature, | ||||
|         state: { | ||||
|             theme: ThemeConfig | ||||
|             changes: Changes | ||||
|             indexedFeatures: IndexedFeatureSource | ||||
|         }, | ||||
|         tagSource: UIEventSource<any>, | ||||
|         argument: string[] | ||||
|     ): Promise<void> | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * @deprecated | ||||
|  */ | ||||
| class ApplyButton extends UIElement { | ||||
|     private readonly icon: string | ||||
|     private readonly text: string | ||||
|     private readonly targetTagRendering: string | ||||
|     private readonly target_layer_id: string | ||||
|     private readonly state: SpecialVisualizationState | ||||
|     private readonly target_feature_ids: string[] | ||||
|     private readonly buttonState = new UIEventSource< | ||||
|         "idle" | "running" | "done" | { error: string } | ||||
|     >("idle") | ||||
|     private readonly layer: FilteredLayer | ||||
|     private readonly tagRenderingConfig: TagRenderingConfig | ||||
|     private readonly appliedNumberOfFeatures = new UIEventSource<number>(0) | ||||
| 
 | ||||
|     constructor( | ||||
|         state: SpecialVisualizationState, | ||||
|         target_feature_ids: string[], | ||||
|         options: { | ||||
|             target_layer_id: string | ||||
|             targetTagRendering: string | ||||
|             text: string | ||||
|             icon: string | ||||
|         } | ||||
|     ) { | ||||
|         super() | ||||
|         this.state = state | ||||
|         this.target_feature_ids = target_feature_ids | ||||
|         this.target_layer_id = options.target_layer_id | ||||
|         this.targetTagRendering = options.targetTagRendering | ||||
|         this.text = options.text | ||||
|         this.icon = options.icon | ||||
|         this.layer = this.state.layerState.filteredLayers.get(this.target_layer_id) | ||||
|         this.tagRenderingConfig = this.layer.layerDef.tagRenderings.find( | ||||
|             (tr) => tr.id === this.targetTagRendering | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     protected InnerRender(): string | BaseUIElement { | ||||
|         if (this.target_feature_ids.length === 0) { | ||||
|             return new FixedUiElement("No elements found to perform action") | ||||
|         } | ||||
| 
 | ||||
|         if (this.tagRenderingConfig === undefined) { | ||||
|             return new FixedUiElement( | ||||
|                 "Target tagrendering " + this.targetTagRendering + " not found" | ||||
|             ).SetClass("alert") | ||||
|         } | ||||
|         const self = this | ||||
|         const button = new SubtleButton(new Img(this.icon), this.text).onClick(() => { | ||||
|             this.buttonState.setData("running") | ||||
|             window.setTimeout(() => { | ||||
|                 self.Run() | ||||
|             }, 50) | ||||
|         }) | ||||
| 
 | ||||
|         const explanation = new Combine([ | ||||
|             "The following objects will be updated: ", | ||||
|             ...this.target_feature_ids.map( | ||||
|                 (id) => new Combine([new Link(id, "https:/  /openstreetmap.org/" + id, true), ", "]) | ||||
|             ), | ||||
|         ]).SetClass("subtle") | ||||
| 
 | ||||
|         const mlmap = new UIEventSource(undefined) | ||||
|         const mla = new MapLibreAdaptor(mlmap, { | ||||
|             rasterLayer: this.state.mapProperties.rasterLayer, | ||||
|         }) | ||||
|         mla.allowZooming.setData(false) | ||||
|         mla.allowMoving.setData(false) | ||||
| 
 | ||||
|         const previewMap = new SvelteUIElement(MaplibreMap, { | ||||
|             mapProperties: mla, | ||||
|             map: mlmap, | ||||
|         }).SetClass("h-48") | ||||
| 
 | ||||
|         const features = this.target_feature_ids.map((id) => | ||||
|             this.state.indexedFeatures.featuresById.data.get(id) | ||||
|         ) | ||||
| 
 | ||||
|         new ShowDataLayer(mlmap, { | ||||
|             features: StaticFeatureSource.fromGeojson(features), | ||||
|             zoomToFeatures: true, | ||||
|             layer: this.layer.layerDef, | ||||
|         }) | ||||
| 
 | ||||
|         return new VariableUiElement( | ||||
|             this.buttonState.map((st) => { | ||||
|                 if (st === "idle") { | ||||
|                     return new Combine([button, previewMap, explanation]) | ||||
|                 } | ||||
|                 if (st === "done") { | ||||
|                     return new FixedUiElement("All done!").SetClass("thanks") | ||||
|                 } | ||||
|                 if (st === "running") { | ||||
|                     return new Loading( | ||||
|                         new VariableUiElement( | ||||
|                             this.appliedNumberOfFeatures.map((appliedTo) => { | ||||
|                                 return ( | ||||
|                                     "Applying changes, currently at " + | ||||
|                                     appliedTo + | ||||
|                                     "/" + | ||||
|                                     this.target_feature_ids.length | ||||
|                                 ) | ||||
|                             }) | ||||
|                         ) | ||||
|                     ) | ||||
|                 } | ||||
|                 const error = st.error | ||||
|                 return new Combine([ | ||||
|                     new FixedUiElement("Something went wrong...").SetClass("alert"), | ||||
|                     new FixedUiElement(error).SetClass("subtle"), | ||||
|                 ]).SetClass("flex flex-col") | ||||
|             }) | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Actually applies all the changes... | ||||
|      */ | ||||
|     private async Run() { | ||||
|         try { | ||||
|             console.log("Applying auto-action on " + this.target_feature_ids.length + " features") | ||||
|             const appliedOn: string[] = [] | ||||
|             for (let i = 0; i < this.target_feature_ids.length; i++) { | ||||
|                 const targetFeatureId = this.target_feature_ids[i] | ||||
|                 const feature = this.state.indexedFeatures.featuresById.data.get(targetFeatureId) | ||||
|                 const featureTags = this.state.featureProperties.getStore(targetFeatureId) | ||||
|                 const rendering = this.tagRenderingConfig.GetRenderValue(featureTags.data).txt | ||||
|                 const specialRenderings = Utils.NoNull( | ||||
|                     SpecialVisualizations.constructSpecification(rendering) | ||||
|                 ).filter((v) => typeof v !== "string" && v.func["supportsAutoAction"] === true) | ||||
| 
 | ||||
|                 if (specialRenderings.length == 0) { | ||||
|                     console.warn( | ||||
|                         "AutoApply: feature " + | ||||
|                             targetFeatureId + | ||||
|                             " got a rendering without supported auto actions:", | ||||
|                         rendering | ||||
|                     ) | ||||
|                 } | ||||
| 
 | ||||
|                 for (const specialRendering of specialRenderings) { | ||||
|                     if (typeof specialRendering === "string") { | ||||
|                         continue | ||||
|                     } | ||||
|                     const action = <AutoAction>specialRendering.func | ||||
|                     await action.applyActionOn( | ||||
|                         feature, | ||||
|                         this.state, | ||||
|                         featureTags, | ||||
|                         specialRendering.args | ||||
|                     ) | ||||
|                 } | ||||
|                 appliedOn.push(targetFeatureId) | ||||
|                 if (i % 50 === 0) { | ||||
|                     await this.state.changes.flushChanges("Auto button: intermediate save") | ||||
|                 } | ||||
|                 this.appliedNumberOfFeatures.setData(i + 1) | ||||
|             } | ||||
|             console.log("Flushing changes...") | ||||
|             await this.state.changes.flushChanges("Auto button: done") | ||||
|             this.buttonState.setData("done") | ||||
|             console.log( | ||||
|                 "Applied changes onto", | ||||
|                 appliedOn.length, | ||||
|                 "items, unique IDs:", | ||||
|                 new Set(appliedOn).size | ||||
|             ) | ||||
|         } catch (e) { | ||||
|             console.error("Error while running autoApply: ", e) | ||||
|             this.buttonState.setData({ error: e }) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| export default class AutoApplyButton implements SpecialVisualization { | ||||
|     public readonly docs: string | ||||
|     public readonly funcName: string = "auto_apply" | ||||
|     public readonly needsUrls = [] | ||||
| 
 | ||||
|     public readonly args: { | ||||
|         name: string | ||||
|         defaultValue?: string | ||||
|         doc: string | ||||
|         required?: boolean | ||||
|     }[] = [ | ||||
|         { | ||||
|             name: "target_layer", | ||||
|             doc: "The layer that the target features will reside in", | ||||
|             required: true, | ||||
|         }, | ||||
|         { | ||||
|             name: "target_feature_ids", | ||||
|             doc: "The key, of which the value contains a list of ids", | ||||
|             required: true, | ||||
|         }, | ||||
|         { | ||||
|             name: "tag_rendering_id", | ||||
|             doc: "The ID of the tagRendering containing the autoAction. This tagrendering will be calculated. The embedded actions will be executed", | ||||
|             required: true, | ||||
|         }, | ||||
|         { | ||||
|             name: "text", | ||||
|             doc: "The text to show on the button", | ||||
|             required: true, | ||||
|         }, | ||||
|         { | ||||
|             name: "icon", | ||||
|             doc: "The icon to show on the button", | ||||
|             defaultValue: "./assets/svg/robot.svg", | ||||
|         }, | ||||
|     ] | ||||
| 
 | ||||
|     constructor(allSpecialVisualisations: SpecialVisualization[]) { | ||||
|         this.docs = AutoApplyButton.generateDocs( | ||||
|             allSpecialVisualisations | ||||
|                 .filter((sv) => sv["supportsAutoAction"] === true) | ||||
|                 .map((sv) => sv.funcName) | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     private static generateDocs(supportedActions: string[]): string { | ||||
|         return ` | ||||
|         A button to run many actions for many features at once. | ||||
|         To effectively use this button, you'll need some ingredients: | ||||
| 
 | ||||
|         1. A target layer with features for which an action is defined in a tag rendering. The following special visualisations support an autoAction: ${supportedActions.join( | ||||
|             ", " | ||||
|         )} | ||||
|         2. A host feature to place the auto-action on. This can be a big outline (such as a city). Another good option for this is the layer [current_view](./BuiltinLayers.md#current_view) | ||||
|         3. Then, use a calculated tag on the host feature to determine the overlapping object ids | ||||
|         4. At last, add this component` | ||||
|     } | ||||
| 
 | ||||
|     constr( | ||||
|         state: SpecialVisualizationState, | ||||
|         tagSource: UIEventSource<Record<string, string>>, | ||||
|         argument: string[] | ||||
|     ): BaseUIElement { | ||||
|         try { | ||||
|             if (!state.theme.official && !state.featureSwitchIsTesting.data) { | ||||
|                 const t = Translations.t.general.add.import | ||||
|                 return new Combine([ | ||||
|                     new FixedUiElement( | ||||
|                         "The auto-apply button is only available in official themes (or in testing mode)" | ||||
|                     ).SetClass("alert"), | ||||
|                     t.howToTest, | ||||
|                 ]) | ||||
|             } | ||||
| 
 | ||||
|             const target_layer_id = argument[0] | ||||
|             const targetTagRendering = argument[2] | ||||
|             const text = argument[3] | ||||
|             const icon = argument[4] | ||||
|             const options = { | ||||
|                 target_layer_id, | ||||
|                 targetTagRendering, | ||||
|                 text, | ||||
|                 icon, | ||||
|             } | ||||
| 
 | ||||
|             return new Lazy(() => { | ||||
|                 const to_parse = new UIEventSource<string[]>(undefined) | ||||
|                 // Very ugly hack: read the value every 500ms
 | ||||
|                 Stores.Chronic(500, () => to_parse.data === undefined).addCallback(() => { | ||||
|                     let applicable = <string | string[]>tagSource.data[argument[1]] | ||||
|                     if (typeof applicable === "string") { | ||||
|                         applicable = JSON.parse(applicable) | ||||
|                     } | ||||
|                     to_parse.setData(<string[]>applicable) | ||||
|                 }) | ||||
| 
 | ||||
|                 const loading = new Loading("Gathering which elements support auto-apply... ") | ||||
|                 return new VariableUiElement( | ||||
|                     Stores.ListStabilized(to_parse).map((ids) => { | ||||
|                         if (ids === undefined) { | ||||
|                             return loading | ||||
|                         } | ||||
| 
 | ||||
|                         if (typeof ids === "string") { | ||||
|                             ids = JSON.parse(ids) | ||||
|                         } | ||||
|                         return new ApplyButton(state, ids, options) | ||||
|                     }) | ||||
|                 ) | ||||
|             }) | ||||
|         } catch (e) { | ||||
|             return new FixedUiElement( | ||||
|                 "Could not generate a auto_apply-button for key " + argument[0] + " due to " + e | ||||
|             ).SetClass("alert") | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     getLayerDependencies(args: string[]): string[] { | ||||
|         return [args[0]] | ||||
|     } | ||||
| } | ||||
							
								
								
									
										124
									
								
								src/UI/Popup/AutoApplyButtonVis.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								src/UI/Popup/AutoApplyButtonVis.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,124 @@ | |||
| import { Store, Stores, UIEventSource } from "../../Logic/UIEventSource" | ||||
| import ThemeConfig from "../../Models/ThemeConfig/ThemeConfig" | ||||
| import { Changes } from "../../Logic/Osm/Changes" | ||||
| import { SpecialVisualization, SpecialVisualizationState, SpecialVisualizationSvelte } from "../SpecialVisualization" | ||||
| import { IndexedFeatureSource } from "../../Logic/FeatureSource/FeatureSource" | ||||
| import SvelteUIElement from "../Base/SvelteUIElement" | ||||
| import { Feature } from "geojson" | ||||
| import AutoApplyButton from "./AutoApplyButton.svelte" | ||||
| 
 | ||||
| export interface AutoAction extends SpecialVisualization { | ||||
|     supportsAutoAction: boolean | ||||
| 
 | ||||
|     applyActionOn( | ||||
|         feature: Feature, | ||||
|         state: { | ||||
|             theme: ThemeConfig | ||||
|             changes: Changes | ||||
|             indexedFeatures: IndexedFeatureSource | ||||
|         }, | ||||
|         tagSource: UIEventSource<Record<string, string>>, | ||||
|         argument: string[] | ||||
|     ): Promise<void> | ||||
| } | ||||
| 
 | ||||
| export default class AutoApplyButtonVis implements SpecialVisualizationSvelte { | ||||
|     public readonly docs: string | ||||
|     public readonly funcName: string = "auto_apply" | ||||
|     public readonly needsUrls = [] | ||||
|     public readonly group = "import" | ||||
|     public readonly args: { | ||||
|         name: string | ||||
|         defaultValue?: string | ||||
|         doc: string | ||||
|         required?: boolean | ||||
|     }[] = [ | ||||
|         { | ||||
|             name: "target_layer", | ||||
|             doc: "The layer that the target features will reside in", | ||||
|             required: true, | ||||
|         }, | ||||
|         { | ||||
|             name: "target_feature_ids", | ||||
|             doc: "The key, of which the value contains a list of ids", | ||||
|             required: true, | ||||
|         }, | ||||
|         { | ||||
|             name: "tag_rendering_id", | ||||
|             doc: "The ID of the tagRendering containing the autoAction. This tagrendering will be calculated. The embedded actions will be executed", | ||||
|             required: true, | ||||
|         }, | ||||
|         { | ||||
|             name: "text", | ||||
|             doc: "The text to show on the button", | ||||
|             required: true, | ||||
|         }, | ||||
|         { | ||||
|             name: "icon", | ||||
|             doc: "The icon to show on the button", | ||||
|             defaultValue: "./assets/svg/robot.svg", | ||||
|         }, | ||||
|     ] | ||||
| 
 | ||||
|     constructor(allSpecialVisualisations: SpecialVisualization[]) { | ||||
|         this.docs = AutoApplyButtonVis.generateDocs( | ||||
|             allSpecialVisualisations | ||||
|                 .filter((sv) => sv["supportsAutoAction"] === true) | ||||
|                 .map((sv) => sv.funcName) | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     private static generateDocs(supportedActions: string[]): string { | ||||
|         return ` | ||||
|         A button to run many actions for many features at once. | ||||
|         To effectively use this button, you'll need some ingredients: | ||||
| 
 | ||||
|         1. A target layer with features for which an action is defined in a tag rendering. The following special visualisations support an autoAction: ${supportedActions.join( | ||||
|             ", " | ||||
|         )} | ||||
|         2. A host feature to place the auto-action on. This can be a big outline (such as a city). Another good option for this is the layer [current_view](./BuiltinLayers.md#current_view) | ||||
|         3. Then, use a calculated tag on the host feature to determine the overlapping object ids | ||||
|         4. At last, add this component` | ||||
|     } | ||||
| 
 | ||||
|     constr( | ||||
|         state: SpecialVisualizationState, | ||||
|         tagSource: UIEventSource<Record<string, string>>, | ||||
|         argument: string[] | ||||
|     ): SvelteUIElement { | ||||
|         const target_layer_id = argument[0] | ||||
|         const targetTagRendering = argument[2] | ||||
|         const text = argument[3] | ||||
|         const icon = argument[4] | ||||
|         const options = { | ||||
|             target_layer_id, | ||||
|             targetTagRendering, | ||||
|             text, | ||||
|             icon | ||||
|         } | ||||
| 
 | ||||
|         const to_parse: UIEventSource<string[]> = new UIEventSource<string[]>(undefined) | ||||
|         Stores.Chronic(500, () => to_parse.data === undefined).map(() => { | ||||
|             const applicable = <string | string[]>tagSource.data[argument[1]] | ||||
|             if (typeof applicable === "string") { | ||||
|                 return <string[]>JSON.parse(applicable) | ||||
|             } else { | ||||
|                 return applicable | ||||
|             } | ||||
|         }).addCallbackAndRunD(data => { | ||||
|             to_parse.set(data) | ||||
|         }) | ||||
| 
 | ||||
|         const stableIds: Store<string[]> = Stores.ListStabilized(to_parse).map((ids) => { | ||||
|             if (typeof ids === "string") { | ||||
|                 ids = JSON.parse(ids) | ||||
|             } | ||||
|             return ids.map(id => id) | ||||
|         }) | ||||
|         return new SvelteUIElement(AutoApplyButton, { state, ids: stableIds, options }) | ||||
|     } | ||||
| 
 | ||||
|     getLayerDependencies(args: string[]): string[] { | ||||
|         return [args[0]] | ||||
|     } | ||||
| } | ||||
|  | @ -8,7 +8,7 @@ import { Utils } from "../../../Utils" | |||
| import SvelteUIElement from "../../Base/SvelteUIElement" | ||||
| import WayImportFlow from "./WayImportFlow.svelte" | ||||
| import ConflateImportFlowState from "./ConflateImportFlowState" | ||||
| import { AutoAction } from "../AutoApplyButton" | ||||
| import { AutoAction } from "../AutoApplyButtonVis" | ||||
| import { IndexedFeatureSource } from "../../../Logic/FeatureSource/FeatureSource" | ||||
| import { Changes } from "../../../Logic/Osm/Changes" | ||||
| import ThemeConfig from "../../../Models/ThemeConfig/ThemeConfig" | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| import { SpecialVisualization, SpecialVisualizationState } from "../../SpecialVisualization" | ||||
| import { AutoAction } from "../AutoApplyButton" | ||||
| import { AutoAction } from "../AutoApplyButtonVis" | ||||
| import { Feature, LineString, Polygon } from "geojson" | ||||
| import { UIEventSource } from "../../../Logic/UIEventSource" | ||||
| import BaseUIElement from "../../BaseUIElement" | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| import { AutoAction } from "./AutoApplyButton" | ||||
| import { AutoAction } from "./AutoApplyButtonVis" | ||||
| import Translations from "../i18n/Translations" | ||||
| import { VariableUiElement } from "../Base/VariableUIElement" | ||||
| import BaseUIElement from "../BaseUIElement" | ||||
|  |  | |||
|  | @ -1,11 +1,7 @@ | |||
| import { FixedUiElement } from "./Base/FixedUiElement" | ||||
| import BaseUIElement from "./BaseUIElement" | ||||
| import { default as FeatureTitle } from "./Popup/Title.svelte" | ||||
| import { | ||||
|     RenderingSpecification, | ||||
|     SpecialVisualization, | ||||
|     SpecialVisualizationState, | ||||
| } from "./SpecialVisualization" | ||||
| import { RenderingSpecification, SpecialVisualization, SpecialVisualizationState } from "./SpecialVisualization" | ||||
| import { HistogramViz } from "./Popup/HistogramViz" | ||||
| import { UploadToOsmViz } from "./Popup/UploadToOsmViz" | ||||
| import { MultiApplyViz } from "./Popup/MultiApplyViz" | ||||
|  | @ -15,7 +11,7 @@ import { VariableUiElement } from "./Base/VariableUIElement" | |||
| import { Translation } from "./i18n/Translation" | ||||
| import Translations from "./i18n/Translations" | ||||
| import OpeningHoursVisualization from "./OpeningHours/OpeningHoursVisualization" | ||||
| import AutoApplyButton from "./Popup/AutoApplyButton" | ||||
| import AutoApplyButtonVis from "./Popup/AutoApplyButtonVis" | ||||
| import { LanguageElement } from "./Popup/LanguageElement/LanguageElement" | ||||
| import SvelteUIElement from "./Base/SvelteUIElement" | ||||
| import { Feature, LineString } from "geojson" | ||||
|  | @ -40,8 +36,11 @@ import { UISpecialVisualisations } from "./SpecialVisualisations/UISpecialVisual | |||
| import { SettingsVisualisations } from "./SpecialVisualisations/SettingsVisualisations" | ||||
| import { ReviewSpecialVisualisations } from "./SpecialVisualisations/ReviewSpecialVisualisations" | ||||
| import { DataImportSpecialVisualisations } from "./SpecialVisualisations/DataImportSpecialVisualisations" | ||||
| import TagrenderingManipulationSpecialVisualisations from "./SpecialVisualisations/TagrenderingManipulationSpecialVisualisations" | ||||
| import { WebAndCommunicationSpecialVisualisations } from "./SpecialVisualisations/WebAndCommunicationSpecialVisualisations" | ||||
| import TagrenderingManipulationSpecialVisualisations | ||||
|     from "./SpecialVisualisations/TagrenderingManipulationSpecialVisualisations" | ||||
| import { | ||||
|     WebAndCommunicationSpecialVisualisations | ||||
| } from "./SpecialVisualisations/WebAndCommunicationSpecialVisualisations" | ||||
| import ClearGPSHistory from "./BigComponents/ClearGPSHistory.svelte" | ||||
| import AllFeaturesStatistics from "./Statistics/AllFeaturesStatistics.svelte" | ||||
| 
 | ||||
|  | @ -609,7 +608,7 @@ export default class SpecialVisualizations { | |||
|             }, | ||||
|         ] | ||||
| 
 | ||||
|         specialVisualizations.push(new AutoApplyButton(specialVisualizations)) | ||||
|         specialVisualizations.push(new AutoApplyButtonVis(specialVisualizations)) | ||||
| 
 | ||||
|         const regex = /[a-zA-Z_]+/ | ||||
|         const invalid = specialVisualizations | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue