forked from MapComplete/MapComplete
		
	refactoring: more fixes, first attempt at tagRenderingAnswer
This commit is contained in:
		
							parent
							
								
									aaaaf1948d
								
							
						
					
					
						commit
						29372c465e
					
				
					 24 changed files with 278 additions and 113 deletions
				
			
		|  | @ -4,6 +4,7 @@ import SimpleFeatureSource from "./Sources/SimpleFeatureSource" | ||||||
| import { Feature } from "geojson" | import { Feature } from "geojson" | ||||||
| import { Utils } from "../../Utils" | import { Utils } from "../../Utils" | ||||||
| import { UIEventSource } from "../UIEventSource" | import { UIEventSource } from "../UIEventSource" | ||||||
|  | import { feature } from "@turf/turf" | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * In some rare cases, some elements are shown on multiple layers (when 'passthrough' is enabled) |  * In some rare cases, some elements are shown on multiple layers (when 'passthrough' is enabled) | ||||||
|  | @ -19,7 +20,7 @@ export default class PerLayerFeatureSourceSplitter< | ||||||
|         upstream: FeatureSource, |         upstream: FeatureSource, | ||||||
|         options?: { |         options?: { | ||||||
|             constructStore?: (features: UIEventSource<Feature[]>, layer: FilteredLayer) => T |             constructStore?: (features: UIEventSource<Feature[]>, layer: FilteredLayer) => T | ||||||
|             handleLeftovers?: (featuresWithoutLayer: any[]) => void |             handleLeftovers?: (featuresWithoutLayer: Feature[]) => void | ||||||
|         } |         } | ||||||
|     ) { |     ) { | ||||||
|         const knownLayers = new Map<string, T>() |         const knownLayers = new Map<string, T>() | ||||||
|  | @ -35,9 +36,6 @@ export default class PerLayerFeatureSourceSplitter< | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         upstream.features.addCallbackAndRunD((features) => { |         upstream.features.addCallbackAndRunD((features) => { | ||||||
|             if (features === undefined) { |  | ||||||
|                 return |  | ||||||
|             } |  | ||||||
|             if (layers === undefined) { |             if (layers === undefined) { | ||||||
|                 return |                 return | ||||||
|             } |             } | ||||||
|  | @ -82,7 +80,7 @@ export default class PerLayerFeatureSourceSplitter< | ||||||
|                 const src = layerSources.get(id) |                 const src = layerSources.get(id) | ||||||
| 
 | 
 | ||||||
|                 if (Utils.sameList(src.data, features)) { |                 if (Utils.sameList(src.data, features)) { | ||||||
|                     return |                     continue | ||||||
|                 } |                 } | ||||||
|                 src.setData(features) |                 src.setData(features) | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| import { Store, UIEventSource } from "../../UIEventSource" | import { Store, UIEventSource } from "../../UIEventSource" | ||||||
| import FeatureSource, { IndexedFeatureSource } from "../FeatureSource" | import FeatureSource, { IndexedFeatureSource } from "../FeatureSource" | ||||||
| import { Feature } from "geojson" | import { Feature } from "geojson" | ||||||
|  | import { Utils } from "../../../Utils" | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * |  * | ||||||
|  | @ -35,20 +36,21 @@ export default class FeatureSourceMerger implements IndexedFeatureSource { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     protected addData(featuress: Feature[][]) { |     protected addData(featuress: Feature[][]) { | ||||||
|  |         featuress = Utils.NoNull(featuress) | ||||||
|         let somethingChanged = false |         let somethingChanged = false | ||||||
|         const all: Map<string, Feature> = new Map() |         const all: Map<string, Feature> = new Map() | ||||||
|  |         const unseen = new Set<string>() | ||||||
|         // We seed the dictionary with the previously loaded features
 |         // We seed the dictionary with the previously loaded features
 | ||||||
|         const oldValues = this.features.data ?? [] |         const oldValues = this.features.data ?? [] | ||||||
|         for (const oldValue of oldValues) { |         for (const oldValue of oldValues) { | ||||||
|             all.set(oldValue.properties.id, oldValue) |             all.set(oldValue.properties.id, oldValue) | ||||||
|  |             unseen.add(oldValue.properties.id) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         for (const features of featuress) { |         for (const features of featuress) { | ||||||
|             if (features === undefined) { |  | ||||||
|                 continue |  | ||||||
|             } |  | ||||||
|             for (const f of features) { |             for (const f of features) { | ||||||
|                 const id = f.properties.id |                 const id = f.properties.id | ||||||
|  |                 unseen.delete(id) | ||||||
|                 if (!all.has(id)) { |                 if (!all.has(id)) { | ||||||
|                     // This is a new feature
 |                     // This is a new feature
 | ||||||
|                     somethingChanged = true |                     somethingChanged = true | ||||||
|  | @ -67,6 +69,9 @@ export default class FeatureSourceMerger implements IndexedFeatureSource { | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         somethingChanged ||= unseen.size > 0 | ||||||
|  |         unseen.forEach((id) => all.delete(id)) | ||||||
|  | 
 | ||||||
|         if (!somethingChanged) { |         if (!somethingChanged) { | ||||||
|             // We don't bother triggering an update
 |             // We don't bother triggering an update
 | ||||||
|             return |             return | ||||||
|  |  | ||||||
|  | @ -27,7 +27,7 @@ export default class LayoutSource extends FeatureSourceMerger { | ||||||
|     ) { |     ) { | ||||||
|         const { bounds, zoom } = mapProperties |         const { bounds, zoom } = mapProperties | ||||||
|         // remove all 'special' layers
 |         // remove all 'special' layers
 | ||||||
|         layers = layers.filter((flayer) => flayer.source !== null) |         layers = layers.filter((layer) => layer.source !== null && layer.source !== undefined) | ||||||
| 
 | 
 | ||||||
|         const geojsonlayers = layers.filter((layer) => layer.source.geojsonSource !== undefined) |         const geojsonlayers = layers.filter((layer) => layer.source.geojsonSource !== undefined) | ||||||
|         const osmLayers = layers.filter((layer) => layer.source.geojsonSource === undefined) |         const osmLayers = layers.filter((layer) => layer.source.geojsonSource === undefined) | ||||||
|  | @ -122,7 +122,8 @@ export default class LayoutSource extends FeatureSourceMerger { | ||||||
|             { |             { | ||||||
|                 zoom, |                 zoom, | ||||||
|                 bounds, |                 bounds, | ||||||
|                 layoutToUse: featureSwitches.layoutToUse, |                 layers: osmLayers, | ||||||
|  |                 widenFactor: featureSwitches.layoutToUse.widenFactor, | ||||||
|                 overpassUrl: featureSwitches.overpassUrl, |                 overpassUrl: featureSwitches.overpassUrl, | ||||||
|                 overpassTimeout: featureSwitches.overpassTimeout, |                 overpassTimeout: featureSwitches.overpassTimeout, | ||||||
|                 overpassMaxZoom: featureSwitches.overpassMaxZoom, |                 overpassMaxZoom: featureSwitches.overpassMaxZoom, | ||||||
|  |  | ||||||
|  | @ -3,7 +3,6 @@ import FeatureSource from "../FeatureSource" | ||||||
| import { ImmutableStore, Store, UIEventSource } from "../../UIEventSource" | import { ImmutableStore, Store, UIEventSource } from "../../UIEventSource" | ||||||
| import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" | import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" | ||||||
| import { Or } from "../../Tags/Or" | import { Or } from "../../Tags/Or" | ||||||
| import LayoutConfig from "../../../Models/ThemeConfig/LayoutConfig" |  | ||||||
| import { Overpass } from "../../Osm/Overpass" | import { Overpass } from "../../Osm/Overpass" | ||||||
| import { Utils } from "../../../Utils" | import { Utils } from "../../../Utils" | ||||||
| import { TagsFilter } from "../../Tags/TagsFilter" | import { TagsFilter } from "../../Tags/TagsFilter" | ||||||
|  | @ -26,7 +25,8 @@ export default class OverpassFeatureSource implements FeatureSource { | ||||||
| 
 | 
 | ||||||
|     private readonly state: { |     private readonly state: { | ||||||
|         readonly zoom: Store<number> |         readonly zoom: Store<number> | ||||||
|         readonly layoutToUse: LayoutConfig |         readonly layers: LayerConfig[] | ||||||
|  |         readonly widenFactor: number | ||||||
|         readonly overpassUrl: Store<string[]> |         readonly overpassUrl: Store<string[]> | ||||||
|         readonly overpassTimeout: Store<number> |         readonly overpassTimeout: Store<number> | ||||||
|         readonly bounds: Store<BBox> |         readonly bounds: Store<BBox> | ||||||
|  | @ -37,7 +37,8 @@ export default class OverpassFeatureSource implements FeatureSource { | ||||||
| 
 | 
 | ||||||
|     constructor( |     constructor( | ||||||
|         state: { |         state: { | ||||||
|             readonly layoutToUse: LayoutConfig |             readonly layers: LayerConfig[] | ||||||
|  |             readonly widenFactor: number | ||||||
|             readonly zoom: Store<number> |             readonly zoom: Store<number> | ||||||
|             readonly overpassUrl: Store<string[]> |             readonly overpassUrl: Store<string[]> | ||||||
|             readonly overpassTimeout: Store<number> |             readonly overpassTimeout: Store<number> | ||||||
|  | @ -117,7 +118,7 @@ export default class OverpassFeatureSource implements FeatureSource { | ||||||
|         let lastUsed = 0 |         let lastUsed = 0 | ||||||
| 
 | 
 | ||||||
|         const layersToDownload = [] |         const layersToDownload = [] | ||||||
|         for (const layer of this.state.layoutToUse.layers) { |         for (const layer of this.state.layers) { | ||||||
|             if (typeof layer === "string") { |             if (typeof layer === "string") { | ||||||
|                 throw "A layer was not expanded!" |                 throw "A layer was not expanded!" | ||||||
|             } |             } | ||||||
|  | @ -130,6 +131,14 @@ export default class OverpassFeatureSource implements FeatureSource { | ||||||
|             if (layer.doNotDownload) { |             if (layer.doNotDownload) { | ||||||
|                 continue |                 continue | ||||||
|             } |             } | ||||||
|  |             if (layer.source === null) { | ||||||
|  |                 // This is a special layer. Should not have been here
 | ||||||
|  |                 console.warn( | ||||||
|  |                     "OverpassFeatureSource received a layer for which the source is null:", | ||||||
|  |                     layer.id | ||||||
|  |                 ) | ||||||
|  |                 continue | ||||||
|  |             } | ||||||
|             if (layer.source.geojsonSource !== undefined) { |             if (layer.source.geojsonSource !== undefined) { | ||||||
|                 // Not our responsibility to download this layer!
 |                 // Not our responsibility to download this layer!
 | ||||||
|                 continue |                 continue | ||||||
|  | @ -151,7 +160,7 @@ export default class OverpassFeatureSource implements FeatureSource { | ||||||
|         do { |         do { | ||||||
|             try { |             try { | ||||||
|                 bounds = this.state.bounds.data |                 bounds = this.state.bounds.data | ||||||
|                     ?.pad(this.state.layoutToUse.widenFactor) |                     ?.pad(this.state.widenFactor) | ||||||
|                     ?.expandToTileBounds(this.padToZoomLevel?.data) |                     ?.expandToTileBounds(this.padToZoomLevel?.data) | ||||||
| 
 | 
 | ||||||
|                 if (bounds === undefined) { |                 if (bounds === undefined) { | ||||||
|  | @ -195,6 +204,7 @@ export default class OverpassFeatureSource implements FeatureSource { | ||||||
|             // Some metatags are delivered by overpass _without_ underscore-prefix; we fix them below
 |             // Some metatags are delivered by overpass _without_ underscore-prefix; we fix them below
 | ||||||
|             // TODO FIXME re-enable this data.features.forEach((f) => SimpleMetaTaggers.objectMetaInfo.applyMetaTagsOnFeature(f))
 |             // TODO FIXME re-enable this data.features.forEach((f) => SimpleMetaTaggers.objectMetaInfo.applyMetaTagsOnFeature(f))
 | ||||||
| 
 | 
 | ||||||
|  |             console.log("Overpass returned", data.features.length, "features") | ||||||
|             self.features.setData(data.features) |             self.features.setData(data.features) | ||||||
|             return [bounds, date, layersToDownload] |             return [bounds, date, layersToDownload] | ||||||
|         } catch (e) { |         } catch (e) { | ||||||
|  |  | ||||||
|  | @ -263,7 +263,11 @@ export abstract class Store<T> implements Readable<T> { | ||||||
|     public subscribe(run: Subscriber<T> & ((value: T) => void), invalidate?): Unsubscriber { |     public subscribe(run: Subscriber<T> & ((value: T) => void), invalidate?): Unsubscriber { | ||||||
|         // We don't need to do anything with 'invalidate', see
 |         // We don't need to do anything with 'invalidate', see
 | ||||||
|         // https://github.com/sveltejs/svelte/issues/3859
 |         // https://github.com/sveltejs/svelte/issues/3859
 | ||||||
|         return this.addCallbackAndRun(run) | 
 | ||||||
|  |         // Note: run is wrapped in an anonymous function. 'Run' returns the value. If this value happens to be true, it would unsubscribe
 | ||||||
|  |         return this.addCallbackAndRun((v) => { | ||||||
|  |             run(v) | ||||||
|  |         }) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ import { LayerConfigJson } from "../Json/LayerConfigJson" | ||||||
| import LayerConfig from "../LayerConfig" | import LayerConfig from "../LayerConfig" | ||||||
| import { Utils } from "../../../Utils" | import { Utils } from "../../../Utils" | ||||||
| import Constants from "../../Constants" | import Constants from "../../Constants" | ||||||
| import { Translation, TypedTranslation } from "../../../UI/i18n/Translation" | import { Translation } from "../../../UI/i18n/Translation" | ||||||
| import { LayoutConfigJson } from "../Json/LayoutConfigJson" | import { LayoutConfigJson } from "../Json/LayoutConfigJson" | ||||||
| import LayoutConfig from "../LayoutConfig" | import LayoutConfig from "../LayoutConfig" | ||||||
| import { TagRenderingConfigJson } from "../Json/TagRenderingConfigJson" | import { TagRenderingConfigJson } from "../Json/TagRenderingConfigJson" | ||||||
|  | @ -16,7 +16,6 @@ import FilterConfigJson from "../Json/FilterConfigJson" | ||||||
| import DeleteConfig from "../DeleteConfig" | import DeleteConfig from "../DeleteConfig" | ||||||
| import { QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson" | import { QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson" | ||||||
| import Validators from "../../../UI/InputElement/Validators" | import Validators from "../../../UI/InputElement/Validators" | ||||||
| import xml2js from "xml2js" |  | ||||||
| 
 | 
 | ||||||
| class ValidateLanguageCompleteness extends DesugaringStep<any> { | class ValidateLanguageCompleteness extends DesugaringStep<any> { | ||||||
|     private readonly _languages: string[] |     private readonly _languages: string[] | ||||||
|  | @ -631,14 +630,14 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> { | ||||||
|         } |         } | ||||||
|         const freeformType = json["freeform"]?.["type"] |         const freeformType = json["freeform"]?.["type"] | ||||||
|         if (freeformType) { |         if (freeformType) { | ||||||
|             if (Validators.AvailableTypes().indexOf(freeformType) < 0) { |             if (Validators.availableTypes.indexOf(freeformType) < 0) { | ||||||
|                 throw ( |                 throw ( | ||||||
|                     "At " + |                     "At " + | ||||||
|                     context + |                     context + | ||||||
|                     ".freeform.type is an unknown type: " + |                     ".freeform.type is an unknown type: " + | ||||||
|                     freeformType + |                     freeformType + | ||||||
|                     "; try one of " + |                     "; try one of " + | ||||||
|                     Validators.AvailableTypes().join(", ") |                     Validators.availableTypes.join(", ") | ||||||
|                 ) |                 ) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  | @ -943,9 +942,9 @@ export class ValidateFilter extends DesugaringStep<FilterConfigJson> { | ||||||
|             for (let i = 0; i < option.fields.length; i++) { |             for (let i = 0; i < option.fields.length; i++) { | ||||||
|                 const field = option.fields[i] |                 const field = option.fields[i] | ||||||
|                 const type = field.type ?? "string" |                 const type = field.type ?? "string" | ||||||
|                 if (Validators.AvailableTypes().find((t) => t === type) === undefined) { |                 if (Validators.availableTypes.find((t) => t === type) === undefined) { | ||||||
|                     const err = `Invalid filter: ${type} is not a valid textfield type (at ${context}.fields[${i}])\n\tTry one of ${Array.from( |                     const err = `Invalid filter: ${type} is not a valid textfield type (at ${context}.fields[${i}])\n\tTry one of ${Array.from( | ||||||
|                         Validators.AvailableTypes() |                         Validators.availableTypes | ||||||
|                     ).join(",")}` |                     ).join(",")}` | ||||||
|                     errors.push(err) |                     errors.push(err) | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|  | @ -119,10 +119,20 @@ export default class ThemeViewState implements SpecialVisualizationState { | ||||||
|         const indexedElements = this.indexedFeatures |         const indexedElements = this.indexedFeatures | ||||||
|         this.featureProperties = new FeaturePropertiesStore(indexedElements) |         this.featureProperties = new FeaturePropertiesStore(indexedElements) | ||||||
|         const perLayer = new PerLayerFeatureSourceSplitter( |         const perLayer = new PerLayerFeatureSourceSplitter( | ||||||
|             Array.from(this.layerState.filteredLayers.values()), |             Array.from(this.layerState.filteredLayers.values()).filter( | ||||||
|  |                 (l) => l.layerDef.source !== null | ||||||
|  |             ), | ||||||
|             indexedElements, |             indexedElements, | ||||||
|             { |             { | ||||||
|                 constructStore: (features, layer) => new GeoIndexedStoreForLayer(features, layer), |                 constructStore: (features, layer) => new GeoIndexedStoreForLayer(features, layer), | ||||||
|  |                 handleLeftovers: (features) => { | ||||||
|  |                     console.warn( | ||||||
|  |                         "Got ", | ||||||
|  |                         features.length, | ||||||
|  |                         "leftover features, such as", | ||||||
|  |                         features[0].properties | ||||||
|  |                     ) | ||||||
|  |                 }, | ||||||
|             } |             } | ||||||
|         ) |         ) | ||||||
|         this.perLayer = perLayer.perLayer |         this.perLayer = perLayer.perLayer | ||||||
|  | @ -141,16 +151,7 @@ export default class ThemeViewState implements SpecialVisualizationState { | ||||||
|                     (fs.layer.isDisplayed?.data ?? true) && z >= (fs.layer.layerDef?.minzoom ?? 0), |                     (fs.layer.isDisplayed?.data ?? true) && z >= (fs.layer.layerDef?.minzoom ?? 0), | ||||||
|                 [fs.layer.isDisplayed] |                 [fs.layer.isDisplayed] | ||||||
|             ) |             ) | ||||||
|             doShowLayer.addCallbackAndRunD((doShow) => | 
 | ||||||
|                 console.log( |  | ||||||
|                     "Layer", |  | ||||||
|                     fs.layer.layerDef.id, |  | ||||||
|                     "is", |  | ||||||
|                     doShow, |  | ||||||
|                     this.mapProperties.zoom.data, |  | ||||||
|                     fs.layer.layerDef.minzoom |  | ||||||
|                 ) |  | ||||||
|             ) |  | ||||||
|             new ShowDataLayer(this.map, { |             new ShowDataLayer(this.map, { | ||||||
|                 layer: fs.layer.layerDef, |                 layer: fs.layer.layerDef, | ||||||
|                 features: filtered, |                 features: filtered, | ||||||
|  |  | ||||||
|  | @ -9,7 +9,7 @@ | ||||||
|   import FromHtml from "./FromHtml.svelte"; |   import FromHtml from "./FromHtml.svelte"; | ||||||
| 
 | 
 | ||||||
|   export let t: Translation; |   export let t: Translation; | ||||||
|   export let tags: Record<string, string> | undefined; |   export let tags: Record<string, string> | undefined = undefined; | ||||||
|   // Text for the current language |   // Text for the current language | ||||||
|   let txt: string | undefined; |   let txt: string | undefined; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -78,6 +78,7 @@ | ||||||
|       </div> |       </div> | ||||||
|     {:else } |     {:else } | ||||||
|       <input |       <input | ||||||
|  |         type="search" | ||||||
|         bind:this={inputElement} |         bind:this={inputElement} | ||||||
|         on:keypress={keypr => keypr.key === "Enter" ? performSearch() : undefined} |         on:keypress={keypr => keypr.key === "Enter" ? performSearch() : undefined} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -83,9 +83,6 @@ export default class LeftControls extends Combine { | ||||||
|             "filters", |             "filters", | ||||||
|             guiState.filterViewIsOpened |             guiState.filterViewIsOpened | ||||||
|         ) |         ) | ||||||
|         const toggledFilter = new MapControlButton(Svg.layers_svg()).onClick(() => |  | ||||||
|             guiState.filterViewIsOpened.setData(true) |  | ||||||
|         ) |  | ||||||
|         state.featureSwitchFilter.addCallbackAndRun((f) => { |         state.featureSwitchFilter.addCallbackAndRun((f) => { | ||||||
|             Hotkeys.RegisterHotkey( |             Hotkeys.RegisterHotkey( | ||||||
|                 { nomod: "B" }, |                 { nomod: "B" }, | ||||||
|  | @ -96,8 +93,6 @@ export default class LeftControls extends Combine { | ||||||
|             ) |             ) | ||||||
|         }) |         }) | ||||||
| 
 | 
 | ||||||
|         const filterButton = new Toggle(toggledFilter, undefined, state.featureSwitchFilter) |  | ||||||
| 
 |  | ||||||
|         const mapSwitch = new Toggle( |         const mapSwitch = new Toggle( | ||||||
|             new BackgroundMapSwitch(state, state.backgroundLayer, { enableHotkeys: true }), |             new BackgroundMapSwitch(state, state.backgroundLayer, { enableHotkeys: true }), | ||||||
|             undefined, |             undefined, | ||||||
|  |  | ||||||
|  | @ -4,6 +4,7 @@ | ||||||
|   import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; |   import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; | ||||||
|   import type { SpecialVisualizationState } from "../SpecialVisualization"; |   import type { SpecialVisualizationState } from "../SpecialVisualization"; | ||||||
|   import TagRenderingAnswer from "../Popup/TagRenderingAnswer.svelte"; |   import TagRenderingAnswer from "../Popup/TagRenderingAnswer.svelte"; | ||||||
|  |   import TagRenderingQuestion from "../Popup/TagRenderingQuestion.svelte"; | ||||||
| 
 | 
 | ||||||
|   export let selectedElement: Feature; |   export let selectedElement: Feature; | ||||||
|   export let layer: LayerConfig; |   export let layer: LayerConfig; | ||||||
|  | @ -41,7 +42,7 @@ | ||||||
|   <div class="flex flex-col sm:flex-row flex-grow justify-between"> |   <div class="flex flex-col sm:flex-row flex-grow justify-between"> | ||||||
|     <!-- Title element--> |     <!-- Title element--> | ||||||
|     <h3> |     <h3> | ||||||
|       <TagRenderingAnswer config={layer.title} {tags} {selectedElement}></TagRenderingAnswer> |       <TagRenderingAnswer config={layer.title} {selectedElement} {tags}></TagRenderingAnswer> | ||||||
|     </h3> |     </h3> | ||||||
| 
 | 
 | ||||||
|     <div class="flex flex-row flex-wrap pt-0.5 sm:pt-1 items-center mr-2"> |     <div class="flex flex-row flex-wrap pt-0.5 sm:pt-1 items-center mr-2"> | ||||||
|  | @ -57,7 +58,11 @@ | ||||||
| 
 | 
 | ||||||
|   <div class="flex flex-col"> |   <div class="flex flex-col"> | ||||||
|     {#each layer.tagRenderings as config (config.id)} |     {#each layer.tagRenderings as config (config.id)} | ||||||
|       <TagRenderingAnswer {tags} {config} {state}></TagRenderingAnswer> |       {#if config.IsKnown($tags)} | ||||||
|  |         <TagRenderingAnswer {tags} {config} {state}></TagRenderingAnswer> | ||||||
|  |       {:else} | ||||||
|  |         <TagRenderingQuestion {config} {tags} {state}></TagRenderingQuestion> | ||||||
|  |       {/if} | ||||||
|     {/each} |     {/each} | ||||||
|   </div> |   </div> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										43
									
								
								UI/InputElement/ValidatedInput.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								UI/InputElement/ValidatedInput.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,43 @@ | ||||||
|  | <script lang="ts"> | ||||||
|  | 
 | ||||||
|  |   import { Store, UIEventSource } from "../../Logic/UIEventSource"; | ||||||
|  |   import type { ValidatorType } from "./Validators"; | ||||||
|  |   import Validators from "./Validators"; | ||||||
|  |   import { ExclamationIcon } from "@rgossiaux/svelte-heroicons/solid"; | ||||||
|  |   import { Translation } from "../i18n/Translation"; | ||||||
|  | 
 | ||||||
|  |   export let value: UIEventSource<string>; | ||||||
|  |   // Internal state, only copied to 'value' so that no invalid values leak outside | ||||||
|  |   let _value = new UIEventSource(value.data ?? "") | ||||||
|  |   export let type: ValidatorType; | ||||||
|  |   let validator = Validators.get(type); | ||||||
|  |   export let feedback: UIEventSource<Translation> | undefined = undefined | ||||||
|  |   _value.addCallbackAndRun(v => { | ||||||
|  |     if(validator.isValid(v)){ | ||||||
|  |       feedback?.setData(undefined) | ||||||
|  |       value.setData(v) | ||||||
|  |       return | ||||||
|  |     } | ||||||
|  |     value.setData(undefined) | ||||||
|  |     feedback?.setData(validator.getFeedback(v)); | ||||||
|  |   }) | ||||||
|  | 
 | ||||||
|  |   if (validator === undefined) { | ||||||
|  |     throw "Not a valid type for a validator:" + type; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   const isValid = _value.map(v => validator.isValid(v)); | ||||||
|  |    | ||||||
|  | 
 | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | {#if validator.textArea} | ||||||
|  |   <textarea bind:value={$_value} inputmode={validator.inputmode ?? "text"}></textarea> | ||||||
|  | {:else } | ||||||
|  |   <div class="flex"> | ||||||
|  |     <input bind:value={$_value} inputmode={validator.inputmode ?? "text"}> | ||||||
|  |     {#if !$isValid} | ||||||
|  |       <ExclamationIcon class="h-6 w-6 -ml-6"></ExclamationIcon> | ||||||
|  |     {/if} | ||||||
|  |   </div> | ||||||
|  | {/if} | ||||||
|  | @ -17,10 +17,17 @@ export abstract class Validator { | ||||||
|      * What HTML-inputmode to use |      * What HTML-inputmode to use | ||||||
|      */ |      */ | ||||||
|     public readonly inputmode?: string |     public readonly inputmode?: string | ||||||
|  |     public readonly textArea: boolean | ||||||
| 
 | 
 | ||||||
|     constructor(name: string, explanation: string | BaseUIElement, inputmode?: string) { |     constructor( | ||||||
|  |         name: string, | ||||||
|  |         explanation: string | BaseUIElement, | ||||||
|  |         inputmode?: string, | ||||||
|  |         textArea?: false | boolean | ||||||
|  |     ) { | ||||||
|         this.name = name |         this.name = name | ||||||
|         this.inputmode = inputmode |         this.inputmode = inputmode | ||||||
|  |         this.textArea = textArea ?? false | ||||||
|         if (this.name.endsWith("textfield")) { |         if (this.name.endsWith("textfield")) { | ||||||
|             this.name = this.name.substr(0, this.name.length - "TextField".length) |             this.name = this.name.substr(0, this.name.length - "TextField".length) | ||||||
|         } |         } | ||||||
|  | @ -46,7 +53,7 @@ export abstract class Validator { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public isValid(string: string, requestCountry: () => string): boolean { |     public isValid(string: string, requestCountry?: () => string): boolean { | ||||||
|         return true |         return true | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -19,8 +19,29 @@ import BaseUIElement from "../BaseUIElement" | ||||||
| import Combine from "../Base/Combine" | import Combine from "../Base/Combine" | ||||||
| import Title from "../Base/Title" | import Title from "../Base/Title" | ||||||
| 
 | 
 | ||||||
|  | export type ValidatorType = typeof Validators.availableTypes[number] | ||||||
|  | 
 | ||||||
| export default class Validators { | export default class Validators { | ||||||
|     private static readonly AllValidators: ReadonlyArray<Validator> = [ |     public static readonly availableTypes = [ | ||||||
|  |         "string", | ||||||
|  |         "text", | ||||||
|  |         "date", | ||||||
|  |         "nat", | ||||||
|  |         "int", | ||||||
|  |         "distance", | ||||||
|  |         "direction", | ||||||
|  |         "wikidata", | ||||||
|  |         "pnat", | ||||||
|  |         "float", | ||||||
|  |         "pfloat", | ||||||
|  |         "email", | ||||||
|  |         "url", | ||||||
|  |         "phone", | ||||||
|  |         "opening_hours", | ||||||
|  |         "color", | ||||||
|  |     ] as const | ||||||
|  | 
 | ||||||
|  |     public static readonly AllValidators: ReadonlyArray<Validator> = [ | ||||||
|         new StringValidator(), |         new StringValidator(), | ||||||
|         new TextValidator(), |         new TextValidator(), | ||||||
|         new DateValidator(), |         new DateValidator(), | ||||||
|  | @ -38,8 +59,16 @@ export default class Validators { | ||||||
|         new OpeningHoursValidator(), |         new OpeningHoursValidator(), | ||||||
|         new ColorValidator(), |         new ColorValidator(), | ||||||
|     ] |     ] | ||||||
|     public static allTypes: Map<string, Validator> = Validators.allTypesDict() |  | ||||||
| 
 | 
 | ||||||
|  |     private static _byType = Validators._byTypeConstructor() | ||||||
|  | 
 | ||||||
|  |     private static _byTypeConstructor(): Map<ValidatorType, Validator> { | ||||||
|  |         const map = new Map<ValidatorType, Validator>() | ||||||
|  |         for (const validator of Validators.AllValidators) { | ||||||
|  |             map.set(<ValidatorType>validator.name, validator) | ||||||
|  |         } | ||||||
|  |         return map | ||||||
|  |     } | ||||||
|     public static HelpText(): BaseUIElement { |     public static HelpText(): BaseUIElement { | ||||||
|         const explanations: BaseUIElement[] = Validators.AllValidators.map((type) => |         const explanations: BaseUIElement[] = Validators.AllValidators.map((type) => | ||||||
|             new Combine([new Title(type.name, 3), type.explanation]).SetClass("flex flex-col") |             new Combine([new Title(type.name, 3), type.explanation]).SetClass("flex flex-col") | ||||||
|  | @ -51,15 +80,7 @@ export default class Validators { | ||||||
|         ]).SetClass("flex flex-col") |         ]).SetClass("flex flex-col") | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static AvailableTypes(): string[] { |     static get(type: ValidatorType): Validator { | ||||||
|         return Validators.AllValidators.map((tp) => tp.name) |         return Validators._byType.get(type) | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private static allTypesDict(): Map<string, Validator> { |  | ||||||
|         const types = new Map<string, Validator>() |  | ||||||
|         for (const tp of Validators.AllValidators) { |  | ||||||
|             types.set(tp.name, tp) |  | ||||||
|         } |  | ||||||
|         return types |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -9,7 +9,17 @@ export default class DirectionValidator extends IntValidator { | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     isValid(str): boolean { | ||||||
|  |         if (str.endsWith("°")) { | ||||||
|  |             str = str.substring(0, str.length - 1) | ||||||
|  |         } | ||||||
|  |         return super.isValid(str) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     reformat(str): string { |     reformat(str): string { | ||||||
|  |         if (str.endsWith("°")) { | ||||||
|  |             str = str.substring(0, str.length - 1) | ||||||
|  |         } | ||||||
|         const n = Number(str) % 360 |         const n = Number(str) % 360 | ||||||
|         return "" + n |         return "" + n | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -2,6 +2,6 @@ import { Validator } from "../Validator" | ||||||
| 
 | 
 | ||||||
| export default class TextValidator extends Validator { | export default class TextValidator extends Validator { | ||||||
|     constructor() { |     constructor() { | ||||||
|         super("text", "A longer piece of text. Uses an textArea instead of a textField", "text") |         super("text", "A longer piece of text. Uses an textArea instead of a textField", "text", true) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -245,8 +245,17 @@ class LineRenderingLayer { | ||||||
|             }) |             }) | ||||||
| 
 | 
 | ||||||
|             this._visibility?.addCallbackAndRunD((visible) => { |             this._visibility?.addCallbackAndRunD((visible) => { | ||||||
|                 map.setLayoutProperty(linelayer, "visibility", visible ? "visible" : "none") |                 try { | ||||||
|                 map.setLayoutProperty(polylayer, "visibility", visible ? "visible" : "none") |                     map.setLayoutProperty(linelayer, "visibility", visible ? "visible" : "none") | ||||||
|  |                     map.setLayoutProperty(polylayer, "visibility", visible ? "visible" : "none") | ||||||
|  |                 } catch (e) { | ||||||
|  |                     console.warn( | ||||||
|  |                         "Error while setting visiblity of layers ", | ||||||
|  |                         linelayer, | ||||||
|  |                         polylayer, | ||||||
|  |                         e | ||||||
|  |                     ) | ||||||
|  |                 } | ||||||
|             }) |             }) | ||||||
|         } else { |         } else { | ||||||
|             src.setData({ |             src.setData({ | ||||||
|  | @ -323,6 +332,7 @@ export default class ShowDataLayer { | ||||||
|             }) |             }) | ||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|     public static showRange( |     public static showRange( | ||||||
|         map: Store<MlMap>, |         map: Store<MlMap>, | ||||||
|         features: FeatureSource, |         features: FeatureSource, | ||||||
|  |  | ||||||
							
								
								
									
										14
									
								
								UI/Popup/TagExplanation.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								UI/Popup/TagExplanation.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | ||||||
|  | <script lang="ts">/** | ||||||
|  |  * Shows an explanation about the given TagsFilter. | ||||||
|  |  */ | ||||||
|  | import { TagsFilter } from "../../Logic/Tags/TagsFilter"; | ||||||
|  | import FromHtml from "../Base/FromHtml.svelte"; | ||||||
|  | 
 | ||||||
|  | export let tagsFilter: TagsFilter; | ||||||
|  | export let properties: Record<string, string> | undefined = {}; | ||||||
|  | export let linkToWiki: boolean = false; | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | {#if tagsFilter !== undefined} | ||||||
|  |   <FromHtml src={tagsFilter.asHumanString(linkToWiki, false, properties)} /> | ||||||
|  | {/if} | ||||||
							
								
								
									
										53
									
								
								UI/Popup/TagRenderingQuestion.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								UI/Popup/TagRenderingQuestion.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,53 @@ | ||||||
|  | <script lang="ts"> | ||||||
|  |   import { UIEventSource } from "../../Logic/UIEventSource"; | ||||||
|  |   import type { SpecialVisualizationState } from "../SpecialVisualization"; | ||||||
|  |   import Tr from "../Base/Tr.svelte"; | ||||||
|  |   import If from "../Base/If.svelte"; | ||||||
|  |   import ValidatedInput from "../InputElement/ValidatedInput.svelte"; | ||||||
|  |   import TagRenderingMapping from "./TagRenderingMapping.svelte"; | ||||||
|  |   import type { Feature } from "geojson"; | ||||||
|  |   import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"; | ||||||
|  | 
 | ||||||
|  |   export let config: TagRenderingConfig; | ||||||
|  |   export let tags: UIEventSource<Record<string, string>>; | ||||||
|  |   export let selectedElement: Feature; | ||||||
|  | 
 | ||||||
|  |   export let state: SpecialVisualizationState; | ||||||
|  |   state.featureSwitchIsTesting; | ||||||
|  | 
 | ||||||
|  |   let freeformInput = new UIEventSource<string>(undefined); | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | {#if config.question !== undefined} | ||||||
|  |   <div class="border border-black subtle-background"> | ||||||
|  |     <If condition={state.featureSwitchIsTesting}> | ||||||
|  |       <div class="flex justify-between"> | ||||||
|  |         <Tr t={config.question}></Tr> | ||||||
|  |         {config.id} | ||||||
|  |       </div> | ||||||
|  |       <Tr slot="else" t={config.question}></Tr> | ||||||
|  |     </If> | ||||||
|  | 
 | ||||||
|  |     {#if config.questionhint} | ||||||
|  |       <div class="subtle"> | ||||||
|  |         <Tr t={config.question}></Tr> | ||||||
|  |       </div> | ||||||
|  |     {/if} | ||||||
|  | 
 | ||||||
|  |     {#if config.freeform?.key && !(config.mappings?.length > 0)} | ||||||
|  |       <!-- There are no options to choose from, simply show the input element: fill out the text field --> | ||||||
|  |       <ValidatedInput type={config.freeform.type} value={freeformInput}></ValidatedInput> | ||||||
|  |     {/if} | ||||||
|  | 
 | ||||||
|  |     {#if config.mappings !== undefined} | ||||||
|  |       <div class="flex flex-col"> | ||||||
|  |         {#each config.mappings as mapping} | ||||||
|  |           {#if mapping.hideInAnswer === true || !(mapping.hideInAnswer) || (console.log(tags) || true) || !(mapping.hideInAnswer?.matchesProperties($tags))  } | ||||||
|  |             <TagRenderingMapping {mapping} {tags} {state} {selectedElement}></TagRenderingMapping> | ||||||
|  |           {/if} | ||||||
|  |         {/each} | ||||||
|  |       </div> | ||||||
|  |     {/if} | ||||||
|  | 
 | ||||||
|  |   </div> | ||||||
|  | {/if} | ||||||
|  | @ -41,7 +41,7 @@ | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| <div class="h-screen w-screen absolute top-0 left-0 flex"> | <div class="h-screen w-screen absolute top-0 left-0 flex"> | ||||||
|   <MaplibreMap class="w-full h-full border border-black" map={maplibremap}></MaplibreMap> |   <MaplibreMap map={maplibremap}></MaplibreMap> | ||||||
| </div> | </div> | ||||||
| 
 | 
 | ||||||
| <div class="absolute top-0 left-0 mt-2 ml-2"> | <div class="absolute top-0 left-0 mt-2 ml-2"> | ||||||
|  | @ -64,7 +64,7 @@ | ||||||
| 
 | 
 | ||||||
| <div class="absolute bottom-0 right-0 mb-4 mr-4"> | <div class="absolute bottom-0 right-0 mb-4 mr-4"> | ||||||
|   <MapControlButton on:click={() => mapproperties.zoom.update(z => z+1)}> |   <MapControlButton on:click={() => mapproperties.zoom.update(z => z+1)}> | ||||||
|     <ToSvelte class="w-7 h-7 block" construct={Svg.plus_ui}></ToSvelte> |     <ToSvelte construct={Svg.plus_ui}></ToSvelte> | ||||||
|   </MapControlButton> |   </MapControlButton> | ||||||
|   <MapControlButton on:click={() => mapproperties.zoom.update(z => z-1)}> |   <MapControlButton on:click={() => mapproperties.zoom.update(z => z-1)}> | ||||||
|     <ToSvelte class="w-7 h-7 block" construct={Svg.min_ui}></ToSvelte> |     <ToSvelte class="w-7 h-7 block" construct={Svg.min_ui}></ToSvelte> | ||||||
|  | @ -81,7 +81,7 @@ | ||||||
|   <If condition={state.featureSwitches.featureSwitchSearch}> |   <If condition={state.featureSwitches.featureSwitchSearch}> | ||||||
|     <Geosearch bounds={state.mapProperties.bounds} layout={state.layout} location={state.mapProperties.location} |     <Geosearch bounds={state.mapProperties.bounds} layout={state.layout} location={state.mapProperties.location} | ||||||
|                {selectedElement} {selectedLayer} |                {selectedElement} {selectedLayer} | ||||||
|                zoom={state.mapProperties.zoom}></Geosearch> |               ></Geosearch> | ||||||
|   </If> |   </If> | ||||||
| </div> | </div> | ||||||
| 
 | 
 | ||||||
|  | @ -168,11 +168,14 @@ | ||||||
| </If> | </If> | ||||||
| 
 | 
 | ||||||
| {#if $selectedElement !== undefined && $selectedLayer !== undefined} | {#if $selectedElement !== undefined && $selectedLayer !== undefined} | ||||||
|   <div class="absolute top-0 right-0 normal-background"> |   <div class="absolute top-0 right-0 w-screen h-screen" style="background-color: #00000088"> | ||||||
| 
 | 
 | ||||||
|  |     <div class="w-full m-8 normal-background rounded overflow-auto"> | ||||||
|  |        | ||||||
|     <SelectedElementView layer={$selectedLayer} selectedElement={$selectedElement} |     <SelectedElementView layer={$selectedLayer} selectedElement={$selectedElement} | ||||||
|                          tags={$selectedElementTags} state={state}></SelectedElementView> |                          tags={$selectedElementTags} state={state}></SelectedElementView> | ||||||
| 
 | 
 | ||||||
|  |     </div> | ||||||
|   </div> |   </div> | ||||||
| {/if} | {/if} | ||||||
| <style> | <style> | ||||||
|  |  | ||||||
|  | @ -947,6 +947,10 @@ video { | ||||||
|   margin-right: 0px; |   margin-right: 0px; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .-ml-6 { | ||||||
|  |   margin-left: -1.5rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .mr-3 { | .mr-3 { | ||||||
|   margin-right: 0.75rem; |   margin-right: 0.75rem; | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										20
									
								
								scripts/BuildMeta.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								scripts/BuildMeta.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,20 @@ | ||||||
|  | import Script from "./Script" | ||||||
|  | import Validators from "../UI/InputElement/Validators" | ||||||
|  | 
 | ||||||
|  | export default class BuildMeta extends Script { | ||||||
|  |     constructor() { | ||||||
|  |         super( | ||||||
|  |             "Prints meta information about the mapcomplete codebase. Used to automate some things" | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  |     async main(args: string[]): Promise<void> { | ||||||
|  |         const types = Validators.AllValidators.map((v) => v.name) | ||||||
|  |             .map((s) => `"${s}"`) | ||||||
|  |             .join(", ") | ||||||
|  |         console.log("public static readonly availableTypes = [ " + types + " ] as const") | ||||||
|  | 
 | ||||||
|  |         return | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | new BuildMeta().run() | ||||||
							
								
								
									
										17
									
								
								test.ts
									
										
									
									
									
								
							
							
						
						
									
										17
									
								
								test.ts
									
										
									
									
									
								
							|  | @ -5,11 +5,11 @@ import LayoutConfig from "./Models/ThemeConfig/LayoutConfig" | ||||||
| import * as benches from "./assets/generated/themes/cyclofix.json" | import * as benches from "./assets/generated/themes/cyclofix.json" | ||||||
| import { UIEventSource } from "./Logic/UIEventSource" | import { UIEventSource } from "./Logic/UIEventSource" | ||||||
| import ThemeViewState from "./Models/ThemeViewState" | import ThemeViewState from "./Models/ThemeViewState" | ||||||
| import { SpecialVisualization, SpecialVisualizationState } from "./UI/SpecialVisualization" |  | ||||||
| import { Feature } from "geojson" |  | ||||||
| import Combine from "./UI/Base/Combine" | import Combine from "./UI/Base/Combine" | ||||||
| import SpecialVisualizations from "./UI/SpecialVisualizations" | import SpecialVisualizations from "./UI/SpecialVisualizations" | ||||||
| import BaseUIElement from "./UI/BaseUIElement" | import ValidatedInput from "./UI/InputElement/ValidatedInput.svelte" | ||||||
|  | import { VariableUiElement } from "./UI/Base/VariableUIElement" | ||||||
|  | import { Translation } from "./UI/i18n/Translation" | ||||||
| 
 | 
 | ||||||
| async function main() { | async function main() { | ||||||
|     new FixedUiElement("").AttachTo("extradiv") |     new FixedUiElement("").AttachTo("extradiv") | ||||||
|  | @ -18,7 +18,7 @@ async function main() { | ||||||
|     main.AttachTo("maindiv") |     main.AttachTo("maindiv") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| async function test() { | async function testspecial() { | ||||||
|     const layout = new LayoutConfig(<any>benches, true) // qp.data === "" ?  : new AllKnownLayoutsLazy().get(qp.data)
 |     const layout = new LayoutConfig(<any>benches, true) // qp.data === "" ?  : new AllKnownLayoutsLazy().get(qp.data)
 | ||||||
|     const state = new ThemeViewState(layout) |     const state = new ThemeViewState(layout) | ||||||
| 
 | 
 | ||||||
|  | @ -27,5 +27,12 @@ async function test() { | ||||||
|     ) |     ) | ||||||
|     new Combine(all).AttachTo("maindiv") |     new Combine(all).AttachTo("maindiv") | ||||||
| } | } | ||||||
| // test().then((_) => {}) /*/
 | async function test() { | ||||||
|  |     const value = new UIEventSource("Hello world!") | ||||||
|  |     const feedback = new UIEventSource<Translation>(undefined) | ||||||
|  |     new SvelteUIElement(ValidatedInput, { type: "direction", value, feedback }).AttachTo("maindiv") | ||||||
|  |     new VariableUiElement(feedback).AttachTo("extradiv") | ||||||
|  | } | ||||||
|  | /* | ||||||
|  | test().then((_) => {}) /*/ | ||||||
| main().then((_) => {}) //*/
 | main().then((_) => {}) //*/
 | ||||||
|  |  | ||||||
|  | @ -1,46 +0,0 @@ | ||||||
| import { TagRenderingConfigJson } from "../../../Models/ThemeConfig/Json/TagRenderingConfigJson" |  | ||||||
| import TagRenderingConfig from "../../../Models/ThemeConfig/TagRenderingConfig" |  | ||||||
| import TagRenderingQuestion from "../../../UI/Popup/TagRenderingQuestion" |  | ||||||
| import { UIEventSource } from "../../../Logic/UIEventSource" |  | ||||||
| import { describe, expect, it } from "vitest" |  | ||||||
| 
 |  | ||||||
| describe("TagRenderingQuestion", () => { |  | ||||||
|     it("should have a freeform text field with the user defined placeholder", () => { |  | ||||||
|         const configJson = <TagRenderingConfigJson>{ |  | ||||||
|             id: "test-tag-rendering", |  | ||||||
|             question: "Question?", |  | ||||||
|             render: "Rendering {capacity}", |  | ||||||
|             freeform: { |  | ||||||
|                 key: "capacity", |  | ||||||
|                 type: "pnat", |  | ||||||
|                 placeholder: "Some user defined placeholder", |  | ||||||
|             }, |  | ||||||
|         } |  | ||||||
|         const config = new TagRenderingConfig(configJson, "test") |  | ||||||
|         const ui = new TagRenderingQuestion(new UIEventSource<any>({}), config) |  | ||||||
| 
 |  | ||||||
|         const html = ui.ConstructElement() |  | ||||||
|         expect(html.getElementsByTagName("input")[0]["placeholder"]).toBe( |  | ||||||
|             "Some user defined placeholder" |  | ||||||
|         ) |  | ||||||
|     }) //*/
 |  | ||||||
|     /* |  | ||||||
|     it("should have a freeform text field with a type explanation", () => { |  | ||||||
|         Locale.language.setData("en") |  | ||||||
|         const configJson = <TagRenderingConfigJson>{ |  | ||||||
|             id: "test-tag-rendering", |  | ||||||
|             question: "Question?", |  | ||||||
|             render: "Rendering {capacity}", |  | ||||||
|             freeform: { |  | ||||||
|                 key: "capacity", |  | ||||||
|                 type: "pnat", |  | ||||||
|             }, |  | ||||||
|         } |  | ||||||
|         const config = new TagRenderingConfig(configJson, "test") |  | ||||||
|         const ui = new TagRenderingQuestion(new UIEventSource<any>({}), config) |  | ||||||
|         const html = ui.ConstructElement() |  | ||||||
|         expect(html.getElementsByTagName("input")[0]["placeholder"]).toBe( |  | ||||||
|             "capacity (a positive, whole number)" |  | ||||||
|         ) |  | ||||||
|     })//*/
 |  | ||||||
| }) |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue