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 BaseUIElement from "../BaseUIElement" | ||||||
| import SvelteUIElement from "./SvelteUIElement" | import SvelteUIElement from "./SvelteUIElement" | ||||||
| import { default as LoadingSvg } from "../../assets/svg/Loading.svelte" | import { default as LoadingSvg } from "../../assets/svg/Loading.svelte" | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * @deprecated | ||||||
|  |  */ | ||||||
| export default class Loading extends Combine { | export default class Loading extends Combine { | ||||||
|     constructor(msg?: BaseUIElement | string) { |     constructor(msg?: BaseUIElement | string) { | ||||||
|         const t = Translations.W(msg) ?? Translations.t.general.loading |         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 SvelteUIElement from "../../Base/SvelteUIElement" | ||||||
| import WayImportFlow from "./WayImportFlow.svelte" | import WayImportFlow from "./WayImportFlow.svelte" | ||||||
| import ConflateImportFlowState from "./ConflateImportFlowState" | import ConflateImportFlowState from "./ConflateImportFlowState" | ||||||
| import { AutoAction } from "../AutoApplyButton" | import { AutoAction } from "../AutoApplyButtonVis" | ||||||
| import { IndexedFeatureSource } from "../../../Logic/FeatureSource/FeatureSource" | import { IndexedFeatureSource } from "../../../Logic/FeatureSource/FeatureSource" | ||||||
| import { Changes } from "../../../Logic/Osm/Changes" | import { Changes } from "../../../Logic/Osm/Changes" | ||||||
| import ThemeConfig from "../../../Models/ThemeConfig/ThemeConfig" | import ThemeConfig from "../../../Models/ThemeConfig/ThemeConfig" | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| import { SpecialVisualization, SpecialVisualizationState } from "../../SpecialVisualization" | import { SpecialVisualization, SpecialVisualizationState } from "../../SpecialVisualization" | ||||||
| import { AutoAction } from "../AutoApplyButton" | import { AutoAction } from "../AutoApplyButtonVis" | ||||||
| import { Feature, LineString, Polygon } from "geojson" | import { Feature, LineString, Polygon } from "geojson" | ||||||
| import { UIEventSource } from "../../../Logic/UIEventSource" | import { UIEventSource } from "../../../Logic/UIEventSource" | ||||||
| import BaseUIElement from "../../BaseUIElement" | import BaseUIElement from "../../BaseUIElement" | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import { AutoAction } from "./AutoApplyButton" | import { AutoAction } from "./AutoApplyButtonVis" | ||||||
| import Translations from "../i18n/Translations" | import Translations from "../i18n/Translations" | ||||||
| import { VariableUiElement } from "../Base/VariableUIElement" | import { VariableUiElement } from "../Base/VariableUIElement" | ||||||
| import BaseUIElement from "../BaseUIElement" | import BaseUIElement from "../BaseUIElement" | ||||||
|  |  | ||||||
|  | @ -1,11 +1,7 @@ | ||||||
| import { FixedUiElement } from "./Base/FixedUiElement" | import { FixedUiElement } from "./Base/FixedUiElement" | ||||||
| import BaseUIElement from "./BaseUIElement" | import BaseUIElement from "./BaseUIElement" | ||||||
| import { default as FeatureTitle } from "./Popup/Title.svelte" | import { default as FeatureTitle } from "./Popup/Title.svelte" | ||||||
| import { | import { RenderingSpecification, SpecialVisualization, SpecialVisualizationState } from "./SpecialVisualization" | ||||||
|     RenderingSpecification, |  | ||||||
|     SpecialVisualization, |  | ||||||
|     SpecialVisualizationState, |  | ||||||
| } from "./SpecialVisualization" |  | ||||||
| import { HistogramViz } from "./Popup/HistogramViz" | import { HistogramViz } from "./Popup/HistogramViz" | ||||||
| import { UploadToOsmViz } from "./Popup/UploadToOsmViz" | import { UploadToOsmViz } from "./Popup/UploadToOsmViz" | ||||||
| import { MultiApplyViz } from "./Popup/MultiApplyViz" | import { MultiApplyViz } from "./Popup/MultiApplyViz" | ||||||
|  | @ -15,7 +11,7 @@ import { VariableUiElement } from "./Base/VariableUIElement" | ||||||
| import { Translation } from "./i18n/Translation" | import { Translation } from "./i18n/Translation" | ||||||
| import Translations from "./i18n/Translations" | import Translations from "./i18n/Translations" | ||||||
| import OpeningHoursVisualization from "./OpeningHours/OpeningHoursVisualization" | import OpeningHoursVisualization from "./OpeningHours/OpeningHoursVisualization" | ||||||
| import AutoApplyButton from "./Popup/AutoApplyButton" | import AutoApplyButtonVis from "./Popup/AutoApplyButtonVis" | ||||||
| import { LanguageElement } from "./Popup/LanguageElement/LanguageElement" | import { LanguageElement } from "./Popup/LanguageElement/LanguageElement" | ||||||
| import SvelteUIElement from "./Base/SvelteUIElement" | import SvelteUIElement from "./Base/SvelteUIElement" | ||||||
| import { Feature, LineString } from "geojson" | import { Feature, LineString } from "geojson" | ||||||
|  | @ -40,8 +36,11 @@ import { UISpecialVisualisations } from "./SpecialVisualisations/UISpecialVisual | ||||||
| import { SettingsVisualisations } from "./SpecialVisualisations/SettingsVisualisations" | import { SettingsVisualisations } from "./SpecialVisualisations/SettingsVisualisations" | ||||||
| import { ReviewSpecialVisualisations } from "./SpecialVisualisations/ReviewSpecialVisualisations" | import { ReviewSpecialVisualisations } from "./SpecialVisualisations/ReviewSpecialVisualisations" | ||||||
| import { DataImportSpecialVisualisations } from "./SpecialVisualisations/DataImportSpecialVisualisations" | import { DataImportSpecialVisualisations } from "./SpecialVisualisations/DataImportSpecialVisualisations" | ||||||
| import TagrenderingManipulationSpecialVisualisations from "./SpecialVisualisations/TagrenderingManipulationSpecialVisualisations" | import TagrenderingManipulationSpecialVisualisations | ||||||
| import { WebAndCommunicationSpecialVisualisations } from "./SpecialVisualisations/WebAndCommunicationSpecialVisualisations" |     from "./SpecialVisualisations/TagrenderingManipulationSpecialVisualisations" | ||||||
|  | import { | ||||||
|  |     WebAndCommunicationSpecialVisualisations | ||||||
|  | } from "./SpecialVisualisations/WebAndCommunicationSpecialVisualisations" | ||||||
| import ClearGPSHistory from "./BigComponents/ClearGPSHistory.svelte" | import ClearGPSHistory from "./BigComponents/ClearGPSHistory.svelte" | ||||||
| import AllFeaturesStatistics from "./Statistics/AllFeaturesStatistics.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 regex = /[a-zA-Z_]+/ | ||||||
|         const invalid = specialVisualizations |         const invalid = specialVisualizations | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue