forked from MapComplete/MapComplete
		
	Huge refactoring: split readonly and writable stores
This commit is contained in:
		
							parent
							
								
									0946d8ac9c
								
							
						
					
					
						commit
						4283b76f36
					
				
					 95 changed files with 819 additions and 625 deletions
				
			
		|  | @ -1,14 +1,14 @@ | ||||||
| import BaseLayer from "../../Models/BaseLayer"; | import BaseLayer from "../../Models/BaseLayer"; | ||||||
| import {UIEventSource} from "../UIEventSource"; | import {ImmutableStore, Store, UIEventSource} from "../UIEventSource"; | ||||||
| import Loc from "../../Models/Loc"; | import Loc from "../../Models/Loc"; | ||||||
| 
 | 
 | ||||||
| export interface AvailableBaseLayersObj { | export interface AvailableBaseLayersObj { | ||||||
|     readonly osmCarto: BaseLayer; |     readonly osmCarto: BaseLayer; | ||||||
|     layerOverview: BaseLayer[]; |     layerOverview: BaseLayer[]; | ||||||
| 
 | 
 | ||||||
|     AvailableLayersAt(location: UIEventSource<Loc>): UIEventSource<BaseLayer[]> |     AvailableLayersAt(location: Store<Loc>): Store<BaseLayer[]> | ||||||
| 
 | 
 | ||||||
|     SelectBestLayerAccordingTo(location: UIEventSource<Loc>, preferedCategory: UIEventSource<string | string[]>): UIEventSource<BaseLayer>; |     SelectBestLayerAccordingTo(location: Store<Loc>, preferedCategory: Store<string | string[]>): Store<BaseLayer>; | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -24,12 +24,12 @@ export default class AvailableBaseLayers { | ||||||
| 
 | 
 | ||||||
|     private static implementation: AvailableBaseLayersObj |     private static implementation: AvailableBaseLayersObj | ||||||
| 
 | 
 | ||||||
|     static AvailableLayersAt(location: UIEventSource<Loc>): UIEventSource<BaseLayer[]> { |     static AvailableLayersAt(location: Store<Loc>): Store<BaseLayer[]> { | ||||||
|         return AvailableBaseLayers.implementation?.AvailableLayersAt(location) ?? new UIEventSource<BaseLayer[]>([]); |         return AvailableBaseLayers.implementation?.AvailableLayersAt(location) ?? new ImmutableStore<BaseLayer[]>([]); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     static SelectBestLayerAccordingTo(location: UIEventSource<Loc>, preferedCategory: UIEventSource<string | string[]>): UIEventSource<BaseLayer> { |     static SelectBestLayerAccordingTo(location: Store<Loc>, preferedCategory: UIEventSource<string | string[]>): Store<BaseLayer> { | ||||||
|         return AvailableBaseLayers.implementation?.SelectBestLayerAccordingTo(location, preferedCategory) ?? new UIEventSource<BaseLayer>(undefined); |         return AvailableBaseLayers.implementation?.SelectBestLayerAccordingTo(location, preferedCategory) ?? new ImmutableStore<BaseLayer>(undefined); | ||||||
| 
 | 
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| import BaseLayer from "../../Models/BaseLayer"; | import BaseLayer from "../../Models/BaseLayer"; | ||||||
| import {UIEventSource} from "../UIEventSource"; | import {Store, Stores} from "../UIEventSource"; | ||||||
| import Loc from "../../Models/Loc"; | import Loc from "../../Models/Loc"; | ||||||
| import {GeoOperations} from "../GeoOperations"; | import {GeoOperations} from "../GeoOperations"; | ||||||
| import * as editorlayerindex from "../../assets/editor-layer-index.json"; | import * as editorlayerindex from "../../assets/editor-layer-index.json"; | ||||||
|  | @ -29,7 +29,7 @@ export default class AvailableBaseLayersImplementation implements AvailableBaseL | ||||||
| 
 | 
 | ||||||
|     public readonly layerOverview = AvailableBaseLayersImplementation.LoadRasterIndex().concat(AvailableBaseLayersImplementation.LoadProviderIndex()); |     public readonly layerOverview = AvailableBaseLayersImplementation.LoadRasterIndex().concat(AvailableBaseLayersImplementation.LoadProviderIndex()); | ||||||
|     public readonly globalLayers = this.layerOverview.filter(layer => layer.feature?.geometry === undefined || layer.feature?.geometry === null) |     public readonly globalLayers = this.layerOverview.filter(layer => layer.feature?.geometry === undefined || layer.feature?.geometry === null) | ||||||
|     public readonly localLayers = this.layerOverview.filter(layer => layer.feature?.geometry !== undefined && layer.featuer?.geometry !== null) |     public readonly localLayers = this.layerOverview.filter(layer => layer.feature?.geometry !== undefined && layer.feature?.geometry !== null) | ||||||
| 
 | 
 | ||||||
|     private static LoadRasterIndex(): BaseLayer[] { |     private static LoadRasterIndex(): BaseLayer[] { | ||||||
|         const layers: BaseLayer[] = [] |         const layers: BaseLayer[] = [] | ||||||
|  | @ -202,8 +202,8 @@ export default class AvailableBaseLayersImplementation implements AvailableBaseL | ||||||
|             }); |             }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public AvailableLayersAt(location: UIEventSource<Loc>): UIEventSource<BaseLayer[]> { |     public AvailableLayersAt(location: Store<Loc>): Store<BaseLayer[]> { | ||||||
|         return UIEventSource.ListStabilized(location.map( |         return Stores.ListStabilized(location.map( | ||||||
|             (currentLocation) => { |             (currentLocation) => { | ||||||
|                 if (currentLocation === undefined) { |                 if (currentLocation === undefined) { | ||||||
|                     return this.layerOverview; |                     return this.layerOverview; | ||||||
|  | @ -212,7 +212,7 @@ export default class AvailableBaseLayersImplementation implements AvailableBaseL | ||||||
|             })); |             })); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public SelectBestLayerAccordingTo(location: UIEventSource<Loc>, preferedCategory: UIEventSource<string | string[]>): UIEventSource<BaseLayer> { |     public SelectBestLayerAccordingTo(location: Store<Loc>, preferedCategory: Store<string | string[]>): Store<BaseLayer> { | ||||||
|         return this.AvailableLayersAt(location) |         return this.AvailableLayersAt(location) | ||||||
|             .map(available => { |             .map(available => { | ||||||
|                 // First float all 'best layers' to the top
 |                 // First float all 'best layers' to the top
 | ||||||
|  | @ -264,7 +264,7 @@ export default class AvailableBaseLayersImplementation implements AvailableBaseL | ||||||
|         if (lon === undefined || lat === undefined) { |         if (lon === undefined || lat === undefined) { | ||||||
|             return availableLayers.concat(this.globalLayers); |             return availableLayers.concat(this.globalLayers); | ||||||
|         } |         } | ||||||
|         const lonlat = [lon, lat]; |         const lonlat : [number, number] = [lon, lat]; | ||||||
|         for (const layerOverviewItem of this.localLayers) { |         for (const layerOverviewItem of this.localLayers) { | ||||||
|             const layer = layerOverviewItem; |             const layer = layerOverviewItem; | ||||||
|             const bbox = BBox.get(layer.feature) |             const bbox = BBox.get(layer.feature) | ||||||
|  |  | ||||||
|  | @ -1,12 +1,12 @@ | ||||||
| import {UIEventSource} from "../UIEventSource"; | import {Store, UIEventSource} from "../UIEventSource"; | ||||||
| import Svg from "../../Svg"; | import Svg from "../../Svg"; | ||||||
| import {LocalStorageSource} from "../Web/LocalStorageSource"; | import {LocalStorageSource} from "../Web/LocalStorageSource"; | ||||||
| import {VariableUiElement} from "../../UI/Base/VariableUIElement"; | import {VariableUiElement} from "../../UI/Base/VariableUIElement"; | ||||||
| import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; | import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; | ||||||
| import {QueryParameters} from "../Web/QueryParameters"; | import {QueryParameters} from "../Web/QueryParameters"; | ||||||
| import FeatureSource from "../FeatureSource/FeatureSource"; |  | ||||||
| import {BBox} from "../BBox"; | import {BBox} from "../BBox"; | ||||||
| import Constants from "../../Models/Constants"; | import Constants from "../../Models/Constants"; | ||||||
|  | import SimpleFeatureSource from "../FeatureSource/Sources/SimpleFeatureSource"; | ||||||
| 
 | 
 | ||||||
| export interface GeoLocationPointProperties { | export interface GeoLocationPointProperties { | ||||||
|     id: "gps", |     id: "gps", | ||||||
|  | @ -22,7 +22,7 @@ export interface GeoLocationPointProperties { | ||||||
| 
 | 
 | ||||||
| export default class GeoLocationHandler extends VariableUiElement { | export default class GeoLocationHandler extends VariableUiElement { | ||||||
| 
 | 
 | ||||||
|     private readonly currentLocation?: FeatureSource |     private readonly currentLocation?: SimpleFeatureSource | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Wether or not the geolocation is active, aka the user requested the current location |      * Wether or not the geolocation is active, aka the user requested the current location | ||||||
|  | @ -43,7 +43,7 @@ export default class GeoLocationHandler extends VariableUiElement { | ||||||
|      * Literally: _currentGPSLocation.data != undefined |      * Literally: _currentGPSLocation.data != undefined | ||||||
|      * @private |      * @private | ||||||
|      */ |      */ | ||||||
|     private readonly _hasLocation: UIEventSource<boolean>; |     private readonly _hasLocation: Store<boolean>; | ||||||
|     private readonly _currentGPSLocation: UIEventSource<Coordinates>; |     private readonly _currentGPSLocation: UIEventSource<Coordinates>; | ||||||
|     /** |     /** | ||||||
|      * Kept in order to update the marker |      * Kept in order to update the marker | ||||||
|  | @ -70,7 +70,7 @@ export default class GeoLocationHandler extends VariableUiElement { | ||||||
|     constructor( |     constructor( | ||||||
|         state: { |         state: { | ||||||
|             selectedElement: UIEventSource<any>; |             selectedElement: UIEventSource<any>; | ||||||
|             currentUserLocation?: FeatureSource, |             currentUserLocation?: SimpleFeatureSource, | ||||||
|             leafletMap: UIEventSource<any>, |             leafletMap: UIEventSource<any>, | ||||||
|             layoutToUse: LayoutConfig, |             layoutToUse: LayoutConfig, | ||||||
|             featureSwitchGeolocation: UIEventSource<boolean> |             featureSwitchGeolocation: UIEventSource<boolean> | ||||||
|  | @ -236,12 +236,9 @@ export default class GeoLocationHandler extends VariableUiElement { | ||||||
| 
 | 
 | ||||||
|             self.currentLocation?.features?.setData([{feature, freshness: new Date()}]) |             self.currentLocation?.features?.setData([{feature, freshness: new Date()}]) | ||||||
| 
 | 
 | ||||||
|             const timeSinceRequest = |  | ||||||
|                 (new Date().getTime() - (self._lastUserRequest.data?.getTime() ?? 0)) / 1000; |  | ||||||
| 
 |  | ||||||
|             if (willFocus.data) { |             if (willFocus.data) { | ||||||
|                 console.log("Zooming to user location: willFocus is set") |                 console.log("Zooming to user location: willFocus is set") | ||||||
|                 willFocus.setData(false) |                 lastClick.setData(undefined); | ||||||
|                 autozoomDone = true; |                 autozoomDone = true; | ||||||
|                 self.MoveToCurrentLocation(16); |                 self.MoveToCurrentLocation(16); | ||||||
|             } else if (self._isLocked.data) { |             } else if (self._isLocked.data) { | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import {UIEventSource} from "../UIEventSource"; | import {Store, UIEventSource} from "../UIEventSource"; | ||||||
| import {Or} from "../Tags/Or"; | import {Or} from "../Tags/Or"; | ||||||
| import {Overpass} from "../Osm/Overpass"; | import {Overpass} from "../Osm/Overpass"; | ||||||
| import FeatureSource from "../FeatureSource/FeatureSource"; | import FeatureSource from "../FeatureSource/FeatureSource"; | ||||||
|  | @ -34,13 +34,13 @@ export default class OverpassFeatureSource implements FeatureSource { | ||||||
|     private readonly retries: UIEventSource<number> = new UIEventSource<number>(0); |     private readonly retries: UIEventSource<number> = new UIEventSource<number>(0); | ||||||
| 
 | 
 | ||||||
|     private readonly state: { |     private readonly state: { | ||||||
|         readonly locationControl: UIEventSource<Loc>, |         readonly locationControl: Store<Loc>, | ||||||
|         readonly layoutToUse: LayoutConfig, |         readonly layoutToUse: LayoutConfig, | ||||||
|         readonly overpassUrl: UIEventSource<string[]>; |         readonly overpassUrl: Store<string[]>; | ||||||
|         readonly overpassTimeout: UIEventSource<number>; |         readonly overpassTimeout: Store<number>; | ||||||
|         readonly currentBounds: UIEventSource<BBox> |         readonly currentBounds: Store<BBox> | ||||||
|     } |     } | ||||||
|     private readonly _isActive: UIEventSource<boolean> |     private readonly _isActive: Store<boolean> | ||||||
|     /** |     /** | ||||||
|      * Callback to handle all the data |      * Callback to handle all the data | ||||||
|      */ |      */ | ||||||
|  | @ -54,16 +54,16 @@ export default class OverpassFeatureSource implements FeatureSource { | ||||||
| 
 | 
 | ||||||
|     constructor( |     constructor( | ||||||
|         state: { |         state: { | ||||||
|             readonly locationControl: UIEventSource<Loc>, |             readonly locationControl: Store<Loc>, | ||||||
|             readonly layoutToUse: LayoutConfig, |             readonly layoutToUse: LayoutConfig, | ||||||
|             readonly overpassUrl: UIEventSource<string[]>; |             readonly overpassUrl: Store<string[]>; | ||||||
|             readonly overpassTimeout: UIEventSource<number>; |             readonly overpassTimeout: Store<number>; | ||||||
|             readonly overpassMaxZoom: UIEventSource<number>, |             readonly overpassMaxZoom: Store<number>, | ||||||
|             readonly currentBounds: UIEventSource<BBox> |             readonly currentBounds: Store<BBox> | ||||||
|         }, |         }, | ||||||
|         options: { |         options: { | ||||||
|             padToTiles: UIEventSource<number>, |             padToTiles: Store<number>, | ||||||
|             isActive?: UIEventSource<boolean>, |             isActive?: Store<boolean>, | ||||||
|             relationTracker: RelationsTracker, |             relationTracker: RelationsTracker, | ||||||
|             onBboxLoaded?: (bbox: BBox, date: Date, layers: LayerConfig[], zoomlevel: number) => void, |             onBboxLoaded?: (bbox: BBox, date: Date, layers: LayerConfig[], zoomlevel: number) => void, | ||||||
|             freshnesses?: Map<string, TileFreshnessCalculator> |             freshnesses?: Map<string, TileFreshnessCalculator> | ||||||
|  |  | ||||||
|  | @ -1,5 +1,4 @@ | ||||||
| import {UIEventSource} from "../UIEventSource"; | import {Store, UIEventSource} from "../UIEventSource"; | ||||||
| import Translations from "../../UI/i18n/Translations"; |  | ||||||
| import Locale from "../../UI/i18n/Locale"; | import Locale from "../../UI/i18n/Locale"; | ||||||
| import TagRenderingAnswer from "../../UI/Popup/TagRenderingAnswer"; | import TagRenderingAnswer from "../../UI/Popup/TagRenderingAnswer"; | ||||||
| import Combine from "../../UI/Base/Combine"; | import Combine from "../../UI/Base/Combine"; | ||||||
|  | @ -9,11 +8,11 @@ import {Utils} from "../../Utils"; | ||||||
| 
 | 
 | ||||||
| export default class TitleHandler { | export default class TitleHandler { | ||||||
|     constructor(state: { |     constructor(state: { | ||||||
|         selectedElement: UIEventSource<any>, |         selectedElement: Store<any>, | ||||||
|         layoutToUse: LayoutConfig, |         layoutToUse: LayoutConfig, | ||||||
|         allElements: ElementStorage |         allElements: ElementStorage | ||||||
|     }) { |     }) { | ||||||
|         const currentTitle: UIEventSource<string> = state.selectedElement.map( |         const currentTitle: Store<string> = state.selectedElement.map( | ||||||
|             selected => { |             selected => { | ||||||
|                 const layout = state.layoutToUse |                 const layout = state.layoutToUse | ||||||
|                 const defaultTitle = layout?.title?.txt ?? "MapComplete" |                 const defaultTitle = layout?.title?.txt ?? "MapComplete" | ||||||
|  |  | ||||||
|  | @ -1,12 +1,12 @@ | ||||||
| import FeatureSource from "../FeatureSource"; | import FeatureSource from "../FeatureSource"; | ||||||
| import {UIEventSource} from "../../UIEventSource"; | import {Store} from "../../UIEventSource"; | ||||||
| import {ElementStorage} from "../../ElementStorage"; | import {ElementStorage} from "../../ElementStorage"; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Makes sure that every feature is added to the ElementsStorage, so that the tags-eventsource can be retrieved |  * Makes sure that every feature is added to the ElementsStorage, so that the tags-eventsource can be retrieved | ||||||
|  */ |  */ | ||||||
| export default class RegisteringAllFromFeatureSourceActor { | export default class RegisteringAllFromFeatureSourceActor { | ||||||
|     public readonly features: UIEventSource<{ feature: any; freshness: Date }[]>; |     public readonly features: Store<{ feature: any; freshness: Date }[]>; | ||||||
|     public readonly name; |     public readonly name; | ||||||
| 
 | 
 | ||||||
|     constructor(source: FeatureSource, allElements: ElementStorage) { |     constructor(source: FeatureSource, allElements: ElementStorage) { | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ import FilteringFeatureSource from "./Sources/FilteringFeatureSource"; | ||||||
| import PerLayerFeatureSourceSplitter from "./PerLayerFeatureSourceSplitter"; | import PerLayerFeatureSourceSplitter from "./PerLayerFeatureSourceSplitter"; | ||||||
| import FeatureSource, {FeatureSourceForLayer, IndexedFeatureSource, Tiled} from "./FeatureSource"; | import FeatureSource, {FeatureSourceForLayer, IndexedFeatureSource, Tiled} from "./FeatureSource"; | ||||||
| import TiledFeatureSource from "./TiledFeatureSource/TiledFeatureSource"; | import TiledFeatureSource from "./TiledFeatureSource/TiledFeatureSource"; | ||||||
| import {UIEventSource} from "../UIEventSource"; | import {Store, UIEventSource} from "../UIEventSource"; | ||||||
| import {TileHierarchyTools} from "./TiledFeatureSource/TileHierarchy"; | import {TileHierarchyTools} from "./TiledFeatureSource/TileHierarchy"; | ||||||
| import RememberingSource from "./Sources/RememberingSource"; | import RememberingSource from "./Sources/RememberingSource"; | ||||||
| import OverpassFeatureSource from "../Actors/OverpassFeatureSource"; | import OverpassFeatureSource from "../Actors/OverpassFeatureSource"; | ||||||
|  | @ -38,8 +38,8 @@ import {ElementStorage} from "../ElementStorage"; | ||||||
|  */ |  */ | ||||||
| export default class FeaturePipeline { | export default class FeaturePipeline { | ||||||
| 
 | 
 | ||||||
|     public readonly sufficientlyZoomed: UIEventSource<boolean>; |     public readonly sufficientlyZoomed: Store<boolean>; | ||||||
|     public readonly runningQuery: UIEventSource<boolean>; |     public readonly runningQuery: Store<boolean>; | ||||||
|     public readonly timeout: UIEventSource<number>; |     public readonly timeout: UIEventSource<number>; | ||||||
|     public readonly somethingLoaded: UIEventSource<boolean> = new UIEventSource<boolean>(false) |     public readonly somethingLoaded: UIEventSource<boolean> = new UIEventSource<boolean>(false) | ||||||
|     public readonly newDataLoadedSignal: UIEventSource<FeatureSource> = new UIEventSource<FeatureSource>(undefined) |     public readonly newDataLoadedSignal: UIEventSource<FeatureSource> = new UIEventSource<FeatureSource>(undefined) | ||||||
|  | @ -314,7 +314,7 @@ export default class FeaturePipeline { | ||||||
|                 // We don't bother to split them over tiles as it'll contain little features by default, so we simply add them like this
 |                 // We don't bother to split them over tiles as it'll contain little features by default, so we simply add them like this
 | ||||||
|                 perLayerHierarchy.get(perLayer.layer.layerDef.id).registerTile(perLayer) |                 perLayerHierarchy.get(perLayer.layer.layerDef.id).registerTile(perLayer) | ||||||
|                 // AT last, we always apply the metatags whenever possible
 |                 // AT last, we always apply the metatags whenever possible
 | ||||||
|                 perLayer.features.addCallbackAndRunD(feats => { |                 perLayer.features.addCallbackAndRunD(_ => { | ||||||
|                     self.onNewDataLoaded(perLayer); |                     self.onNewDataLoaded(perLayer); | ||||||
|                 }) |                 }) | ||||||
| 
 | 
 | ||||||
|  | @ -417,7 +417,7 @@ export default class FeaturePipeline { | ||||||
|     /* |     /* | ||||||
|     * Gives an UIEventSource containing the tileIndexes of the tiles that should be loaded from OSM |     * Gives an UIEventSource containing the tileIndexes of the tiles that should be loaded from OSM | ||||||
|     * */ |     * */ | ||||||
|     private getNeededTilesFromOsm(isSufficientlyZoomed: UIEventSource<boolean>): UIEventSource<number[]> { |     private getNeededTilesFromOsm(isSufficientlyZoomed: Store<boolean>): Store<number[]> { | ||||||
|         const self = this |         const self = this | ||||||
|         return this.state.currentBounds.map(bbox => { |         return this.state.currentBounds.map(bbox => { | ||||||
|             if (bbox === undefined) { |             if (bbox === undefined) { | ||||||
|  | @ -450,12 +450,12 @@ export default class FeaturePipeline { | ||||||
|     private initOverpassUpdater(state: { |     private initOverpassUpdater(state: { | ||||||
|         allElements: ElementStorage; |         allElements: ElementStorage; | ||||||
|         layoutToUse: LayoutConfig, |         layoutToUse: LayoutConfig, | ||||||
|         currentBounds: UIEventSource<BBox>, |         currentBounds: Store<BBox>, | ||||||
|         locationControl: UIEventSource<Loc>, |         locationControl: Store<Loc>, | ||||||
|         readonly overpassUrl: UIEventSource<string[]>; |         readonly overpassUrl: Store<string[]>; | ||||||
|         readonly overpassTimeout: UIEventSource<number>; |         readonly overpassTimeout: Store<number>; | ||||||
|         readonly overpassMaxZoom: UIEventSource<number>, |         readonly overpassMaxZoom: Store<number>, | ||||||
|     }, useOsmApi: UIEventSource<boolean>): OverpassFeatureSource { |     }, useOsmApi: Store<boolean>): OverpassFeatureSource { | ||||||
|         const minzoom = Math.min(...state.layoutToUse.layers.map(layer => layer.minzoom)) |         const minzoom = Math.min(...state.layoutToUse.layers.map(layer => layer.minzoom)) | ||||||
|         const overpassIsActive = state.currentBounds.map(bbox => { |         const overpassIsActive = state.currentBounds.map(bbox => { | ||||||
|             if (bbox === undefined) { |             if (bbox === undefined) { | ||||||
|  |  | ||||||
|  | @ -1,9 +1,9 @@ | ||||||
| import {UIEventSource} from "../UIEventSource"; | import {Store, UIEventSource} from "../UIEventSource"; | ||||||
| import FilteredLayer from "../../Models/FilteredLayer"; | import FilteredLayer from "../../Models/FilteredLayer"; | ||||||
| import {BBox} from "../BBox"; | import {BBox} from "../BBox"; | ||||||
| 
 | 
 | ||||||
| export default interface FeatureSource { | export default interface FeatureSource { | ||||||
|     features: UIEventSource<{ feature: any, freshness: Date }[]>; |     features: Store<{ feature: any, freshness: Date }[]>; | ||||||
|     /** |     /** | ||||||
|      * Mainly used for debuging |      * Mainly used for debuging | ||||||
|      */ |      */ | ||||||
|  | @ -26,14 +26,14 @@ export interface FeatureSourceForLayer extends FeatureSource { | ||||||
|  * A feature source which is aware of the indexes it contains |  * A feature source which is aware of the indexes it contains | ||||||
|  */ |  */ | ||||||
| export interface IndexedFeatureSource extends FeatureSource { | export interface IndexedFeatureSource extends FeatureSource { | ||||||
|     readonly containedIds: UIEventSource<Set<string>> |     readonly containedIds: Store<Set<string>> | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * A feature source which has some extra data about it's state |  * A feature source which has some extra data about it's state | ||||||
|  */ |  */ | ||||||
| export interface FeatureSourceState { | export interface FeatureSourceState { | ||||||
|     readonly sufficientlyZoomed: UIEventSource<boolean>; |     readonly sufficientlyZoomed: Store<boolean>; | ||||||
|     readonly runningQuery: UIEventSource<boolean>; |     readonly runningQuery: Store<boolean>; | ||||||
|     readonly timeout: UIEventSource<number>; |     readonly timeout: Store<number>; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| import FeatureSource, {FeatureSourceForLayer, Tiled} from "./FeatureSource"; | import FeatureSource, {FeatureSourceForLayer, Tiled} from "./FeatureSource"; | ||||||
| import {UIEventSource} from "../UIEventSource"; | import {Store} from "../UIEventSource"; | ||||||
| import FilteredLayer from "../../Models/FilteredLayer"; | import FilteredLayer from "../../Models/FilteredLayer"; | ||||||
| import SimpleFeatureSource from "./Sources/SimpleFeatureSource"; | import SimpleFeatureSource from "./Sources/SimpleFeatureSource"; | ||||||
| 
 | 
 | ||||||
|  | @ -11,7 +11,7 @@ import SimpleFeatureSource from "./Sources/SimpleFeatureSource"; | ||||||
|  */ |  */ | ||||||
| export default class PerLayerFeatureSourceSplitter { | export default class PerLayerFeatureSourceSplitter { | ||||||
| 
 | 
 | ||||||
|     constructor(layers: UIEventSource<FilteredLayer[]>, |     constructor(layers: Store<FilteredLayer[]>, | ||||||
|                 handleLayerData: (source: FeatureSourceForLayer & Tiled) => void, |                 handleLayerData: (source: FeatureSourceForLayer & Tiled) => void, | ||||||
|                 upstream: FeatureSource, |                 upstream: FeatureSource, | ||||||
|                 options?: { |                 options?: { | ||||||
|  | @ -19,7 +19,7 @@ export default class PerLayerFeatureSourceSplitter { | ||||||
|                     handleLeftovers?: (featuresWithoutLayer: any[]) => void |                     handleLeftovers?: (featuresWithoutLayer: any[]) => void | ||||||
|                 }) { |                 }) { | ||||||
| 
 | 
 | ||||||
|         const knownLayers = new Map<string, FeatureSourceForLayer & Tiled>() |         const knownLayers = new Map<string, SimpleFeatureSource>() | ||||||
| 
 | 
 | ||||||
|         function update() { |         function update() { | ||||||
|             const features = upstream.features?.data; |             const features = upstream.features?.data; | ||||||
|  |  | ||||||
|  | @ -3,12 +3,12 @@ | ||||||
|  * Data coming from upstream will always overwrite a previous value |  * Data coming from upstream will always overwrite a previous value | ||||||
|  */ |  */ | ||||||
| import FeatureSource, {Tiled} from "../FeatureSource"; | import FeatureSource, {Tiled} from "../FeatureSource"; | ||||||
| import {UIEventSource} from "../../UIEventSource"; | import {Store, UIEventSource} from "../../UIEventSource"; | ||||||
| import {BBox} from "../../BBox"; | import {BBox} from "../../BBox"; | ||||||
| 
 | 
 | ||||||
| export default class RememberingSource implements FeatureSource, Tiled { | export default class RememberingSource implements FeatureSource, Tiled { | ||||||
| 
 | 
 | ||||||
|     public readonly features: UIEventSource<{ feature: any, freshness: Date }[]>; |     public readonly features: Store<{ feature: any, freshness: Date }[]>; | ||||||
|     public readonly name; |     public readonly name; | ||||||
|     public readonly tileIndex: number |     public readonly tileIndex: number | ||||||
|     public readonly bbox: BBox |     public readonly bbox: BBox | ||||||
|  |  | ||||||
|  | @ -1,14 +1,14 @@ | ||||||
| /** | /** | ||||||
|  * This feature source helps the ShowDataLayer class: it introduces the necessary extra features and indicates with what renderConfig it should be rendered. |  * This feature source helps the ShowDataLayer class: it introduces the necessary extra features and indicates with what renderConfig it should be rendered. | ||||||
|  */ |  */ | ||||||
| import {UIEventSource} from "../../UIEventSource"; | import {Store, UIEventSource} from "../../UIEventSource"; | ||||||
| import {GeoOperations} from "../../GeoOperations"; | import {GeoOperations} from "../../GeoOperations"; | ||||||
| import FeatureSource from "../FeatureSource"; | import FeatureSource from "../FeatureSource"; | ||||||
| import PointRenderingConfig from "../../../Models/ThemeConfig/PointRenderingConfig"; | import PointRenderingConfig from "../../../Models/ThemeConfig/PointRenderingConfig"; | ||||||
| import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"; | import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"; | ||||||
| 
 | 
 | ||||||
| export default class RenderingMultiPlexerFeatureSource { | export default class RenderingMultiPlexerFeatureSource { | ||||||
|     public readonly features: UIEventSource<(any & { pointRenderingIndex: number | undefined, lineRenderingIndex: number | undefined })[]>; |     public readonly features: Store<(any & { pointRenderingIndex: number | undefined, lineRenderingIndex: number | undefined })[]>; | ||||||
| 
 | 
 | ||||||
|     constructor(upstream: FeatureSource, layer: LayerConfig) { |     constructor(upstream: FeatureSource, layer: LayerConfig) { | ||||||
|          |          | ||||||
|  | @ -27,7 +27,7 @@ export default class RenderingMultiPlexerFeatureSource { | ||||||
|         this.features = upstream.features.map( |         this.features = upstream.features.map( | ||||||
|             features => { |             features => { | ||||||
|                 if (features === undefined) { |                 if (features === undefined) { | ||||||
|                     return; |                     return undefined; | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -48,59 +48,64 @@ export default class RenderingMultiPlexerFeatureSource { | ||||||
| 
 | 
 | ||||||
|                 for (const f of features) { |                 for (const f of features) { | ||||||
|                     const feat = f.feature; |                     const feat = f.feature; | ||||||
|  |                     if(feat === undefined){ | ||||||
|  |                         continue | ||||||
|  |                     } | ||||||
|  |                     if(feat.geometry === undefined){ | ||||||
|  |                         console.error("No geometry in ", feat,"provided by", upstream.features.tag, upstream.name) | ||||||
|  |                     } | ||||||
|                     if (feat.geometry.type === "Point") { |                     if (feat.geometry.type === "Point") { | ||||||
| 
 |  | ||||||
|                         for (const rendering of pointRenderings) { |                         for (const rendering of pointRenderings) { | ||||||
|                             withIndex.push({ |                             withIndex.push({ | ||||||
|                                 ...feat, |                                 ...feat, | ||||||
|                                 pointRenderingIndex: rendering.index |                                 pointRenderingIndex: rendering.index | ||||||
|                             }) |                             }) | ||||||
|                         } |                         } | ||||||
|                     } else { |                         continue | ||||||
|                         // This is a a line: add the centroids
 |                     } | ||||||
|                         let centerpoint: [number, number] = undefined; |                      | ||||||
|                         let projectedCenterPoint : [number, number] = undefined |                     // This is a a line: add the centroids
 | ||||||
|                         if(hasCentroid){ |                     let centerpoint: [number, number] = undefined; | ||||||
|                             centerpoint  = GeoOperations.centerpointCoordinates(feat) |                     let projectedCenterPoint: [number, number] = undefined | ||||||
|                             if(projectedCentroidRenderings.length > 0){ |                     if (hasCentroid) { | ||||||
|                                 projectedCenterPoint = <[number,number]> GeoOperations.nearestPoint(feat, centerpoint).geometry.coordinates |                         centerpoint = GeoOperations.centerpointCoordinates(feat) | ||||||
|                             } |                         if (projectedCentroidRenderings.length > 0) { | ||||||
|  |                             projectedCenterPoint = <[number, number]>GeoOperations.nearestPoint(feat, centerpoint).geometry.coordinates | ||||||
|                         } |                         } | ||||||
|                         for (const rendering of centroidRenderings) { |                     } | ||||||
|  |                     for (const rendering of centroidRenderings) { | ||||||
|  |                         addAsPoint(feat, rendering, centerpoint) | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |                     if (feat.geometry.type === "LineString") { | ||||||
|  | 
 | ||||||
|  |                         for (const rendering of projectedCentroidRenderings) { | ||||||
|  |                             addAsPoint(feat, rendering, projectedCenterPoint) | ||||||
|  |                         } | ||||||
|  | 
 | ||||||
|  |                         // Add start- and endpoints
 | ||||||
|  |                         const coordinates = feat.geometry.coordinates | ||||||
|  |                         for (const rendering of startRenderings) { | ||||||
|  |                             addAsPoint(feat, rendering, coordinates[0]) | ||||||
|  |                         } | ||||||
|  |                         for (const rendering of endRenderings) { | ||||||
|  |                             const coordinate = coordinates[coordinates.length - 1] | ||||||
|  |                             addAsPoint(feat, rendering, coordinate) | ||||||
|  |                         } | ||||||
|  | 
 | ||||||
|  |                     } else { | ||||||
|  |                         for (const rendering of projectedCentroidRenderings) { | ||||||
|                             addAsPoint(feat, rendering, centerpoint) |                             addAsPoint(feat, rendering, centerpoint) | ||||||
|                         } |                         } | ||||||
|                          |                     } | ||||||
| 
 |  | ||||||
|                         if (feat.geometry.type === "LineString") { |  | ||||||
| 
 |  | ||||||
|                             for (const rendering of projectedCentroidRenderings) { |  | ||||||
|                                 addAsPoint(feat, rendering, projectedCenterPoint) |  | ||||||
|                             } |  | ||||||
|                              |  | ||||||
|                             // Add start- and endpoints
 |  | ||||||
|                             const coordinates = feat.geometry.coordinates |  | ||||||
|                             for (const rendering of startRenderings) { |  | ||||||
|                                 addAsPoint(feat, rendering, coordinates[0]) |  | ||||||
|                             } |  | ||||||
|                             for (const rendering of endRenderings) { |  | ||||||
|                                 const coordinate = coordinates[coordinates.length - 1] |  | ||||||
|                                 addAsPoint(feat, rendering, coordinate) |  | ||||||
|                             } |  | ||||||
| 
 |  | ||||||
|                         }else{ |  | ||||||
|                             for (const rendering of projectedCentroidRenderings) { |  | ||||||
|                                 addAsPoint(feat, rendering, centerpoint) |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
| 
 |  | ||||||
|                         // AT last, add it 'as is' to what we should render 
 |  | ||||||
|                         for (let i = 0; i < lineRenderObjects.length; i++) { |  | ||||||
|                             withIndex.push({ |  | ||||||
|                                 ...feat, |  | ||||||
|                                 lineRenderingIndex: i |  | ||||||
|                             }) |  | ||||||
|                         } |  | ||||||
| 
 | 
 | ||||||
|  |                     // AT last, add it 'as is' to what we should render 
 | ||||||
|  |                     for (let i = 0; i < lineRenderObjects.length; i++) { | ||||||
|  |                         withIndex.push({ | ||||||
|  |                             ...feat, | ||||||
|  |                             lineRenderingIndex: i | ||||||
|  |                         }) | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -10,7 +10,7 @@ export default class SimpleFeatureSource implements FeatureSourceForLayer, Tiled | ||||||
|     public readonly bbox: BBox = BBox.global; |     public readonly bbox: BBox = BBox.global; | ||||||
|     public readonly tileIndex: number; |     public readonly tileIndex: number; | ||||||
| 
 | 
 | ||||||
|     constructor(layer: FilteredLayer, tileIndex: number, featureSource?: UIEventSource<{ feature: any; freshness: Date }[]>) { |     constructor(layer: FilteredLayer, tileIndex: number, featureSource?: UIEventSource<{ feature: any; freshness: Date }[]> ) { | ||||||
|         this.name = "SimpleFeatureSource(" + layer.layerDef.id + ")" |         this.name = "SimpleFeatureSource(" + layer.layerDef.id + ")" | ||||||
|         this.layer = layer |         this.layer = layer | ||||||
|         this.tileIndex = tileIndex ?? 0; |         this.tileIndex = tileIndex ?? 0; | ||||||
|  |  | ||||||
|  | @ -1,31 +1,55 @@ | ||||||
| import FeatureSource from "../FeatureSource"; | import FeatureSource, {FeatureSourceForLayer, Tiled} from "../FeatureSource"; | ||||||
| import {UIEventSource} from "../../UIEventSource"; | import {ImmutableStore, Store, UIEventSource} from "../../UIEventSource"; | ||||||
|  | import {stat} from "fs"; | ||||||
|  | import FilteredLayer from "../../../Models/FilteredLayer"; | ||||||
|  | import {BBox} from "../../BBox"; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * A simple dummy implementation for whenever it is needed |  * A simple, read only feature store. | ||||||
|  */ |  */ | ||||||
| export default class StaticFeatureSource implements FeatureSource { | export default class StaticFeatureSource implements FeatureSource { | ||||||
|     public readonly features: UIEventSource<{ feature: any; freshness: Date }[]>; |     public readonly features: Store<{ feature: any; freshness: Date }[]>; | ||||||
|     public readonly name: string = "StaticFeatureSource" |     public readonly name: string | ||||||
| 
 | 
 | ||||||
|     constructor(features: any[] | UIEventSource<any[] | UIEventSource<{ feature: any, freshness: Date }>>, useFeaturesDirectly) { |     constructor(features: Store<{ feature: any, freshness: Date }[]>, name = "StaticFeatureSource") { | ||||||
|         const now = new Date(); |         if (features === undefined) { | ||||||
|         if(features === undefined){ |  | ||||||
|             throw "Static feature source received undefined as source" |             throw "Static feature source received undefined as source" | ||||||
|         } |         } | ||||||
|         if (useFeaturesDirectly) { |         this.name = name; | ||||||
|             // @ts-ignore
 |         this.features = features; | ||||||
|             this.features = features |     } | ||||||
|         } else if (features instanceof UIEventSource) { | 
 | ||||||
|             // @ts-ignore
 |     public static fromGeojsonAndDate(features: { feature: any, freshness: Date }[], name = "StaticFeatureSourceFromGeojsonAndDate"): StaticFeatureSource { | ||||||
|             this.features = features.map(features => features?.map(f => ({feature: f, freshness: now}) ?? [])) |         return new StaticFeatureSource(new ImmutableStore(features), name); | ||||||
|         } else { |  | ||||||
|             this.features = new UIEventSource(features?.map(f => ({ |  | ||||||
|                 feature: f, |  | ||||||
|                 freshness: now |  | ||||||
|             }))??[]) |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| } |     public static fromGeojson(geojson: any[], name = "StaticFeatureSourceFromGeojson"): StaticFeatureSource { | ||||||
|  |         const now = new Date(); | ||||||
|  |         return StaticFeatureSource.fromGeojsonAndDate(geojson.map(feature => ({feature, freshness: now})), name); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     static fromDateless(featureSource: Store<{ feature: any }[]>, name = "StaticFeatureSourceFromDateless") { | ||||||
|  |         const now = new Date(); | ||||||
|  |         return new StaticFeatureSource(featureSource.map(features => features.map(feature => ({ | ||||||
|  |             feature: feature.feature, | ||||||
|  |             freshness: now | ||||||
|  |         }))), name); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class TiledStaticFeatureSource extends StaticFeatureSource implements Tiled, FeatureSourceForLayer{ | ||||||
|  | 
 | ||||||
|  |     public readonly bbox: BBox = BBox.global; | ||||||
|  |     public readonly tileIndex: number;    | ||||||
|  |     public readonly layer: FilteredLayer; | ||||||
|  | 
 | ||||||
|  |     constructor(features: Store<{ feature: any, freshness: Date }[]>, layer: FilteredLayer ,tileIndex : number = 0) { | ||||||
|  |         super(features); | ||||||
|  |         this.tileIndex = tileIndex ; | ||||||
|  |         this.layer=  layer; | ||||||
|  |         this.bbox = BBox.fromTileIndex(this.tileIndex) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ import {Utils} from "../../../Utils"; | ||||||
| import * as OsmToGeoJson from "osmtogeojson"; | import * as OsmToGeoJson from "osmtogeojson"; | ||||||
| import StaticFeatureSource from "../Sources/StaticFeatureSource"; | import StaticFeatureSource from "../Sources/StaticFeatureSource"; | ||||||
| import PerLayerFeatureSourceSplitter from "../PerLayerFeatureSourceSplitter"; | import PerLayerFeatureSourceSplitter from "../PerLayerFeatureSourceSplitter"; | ||||||
| import {UIEventSource} from "../../UIEventSource"; | import {Store, UIEventSource} from "../../UIEventSource"; | ||||||
| import FilteredLayer from "../../../Models/FilteredLayer"; | import FilteredLayer from "../../../Models/FilteredLayer"; | ||||||
| import {FeatureSourceForLayer, Tiled} from "../FeatureSource"; | import {FeatureSourceForLayer, Tiled} from "../FeatureSource"; | ||||||
| import {Tiles} from "../../../Models/TileRange"; | import {Tiles} from "../../../Models/TileRange"; | ||||||
|  | @ -20,13 +20,13 @@ export default class OsmFeatureSource { | ||||||
|     public readonly downloadedTiles = new Set<number>() |     public readonly downloadedTiles = new Set<number>() | ||||||
|     public rawDataHandlers: ((osmJson: any, tileId: number) => void)[] = [] |     public rawDataHandlers: ((osmJson: any, tileId: number) => void)[] = [] | ||||||
|     private readonly _backend: string; |     private readonly _backend: string; | ||||||
|     private readonly filteredLayers: UIEventSource<FilteredLayer[]>; |     private readonly filteredLayers: Store<FilteredLayer[]>; | ||||||
|     private readonly handleTile: (fs: (FeatureSourceForLayer & Tiled)) => void; |     private readonly handleTile: (fs: (FeatureSourceForLayer & Tiled)) => void; | ||||||
|     private isActive: UIEventSource<boolean>; |     private isActive: Store<boolean>; | ||||||
|     private options: { |     private options: { | ||||||
|         handleTile: (tile: FeatureSourceForLayer & Tiled) => void; |         handleTile: (tile: FeatureSourceForLayer & Tiled) => void; | ||||||
|         isActive: UIEventSource<boolean>, |         isActive: Store<boolean>, | ||||||
|         neededTiles: UIEventSource<number[]>, |         neededTiles: Store<number[]>, | ||||||
|         state: { |         state: { | ||||||
|             readonly osmConnection: OsmConnection; |             readonly osmConnection: OsmConnection; | ||||||
|         }, |         }, | ||||||
|  | @ -36,8 +36,8 @@ export default class OsmFeatureSource { | ||||||
| 
 | 
 | ||||||
|     constructor(options: { |     constructor(options: { | ||||||
|         handleTile: (tile: FeatureSourceForLayer & Tiled) => void; |         handleTile: (tile: FeatureSourceForLayer & Tiled) => void; | ||||||
|         isActive: UIEventSource<boolean>, |         isActive: Store<boolean>, | ||||||
|         neededTiles: UIEventSource<number[]>, |         neededTiles: Store<number[]>, | ||||||
|         state: { |         state: { | ||||||
|             readonly filteredLayers: UIEventSource<FilteredLayer[]>; |             readonly filteredLayers: UIEventSource<FilteredLayer[]>; | ||||||
|             readonly osmConnection: OsmConnection; |             readonly osmConnection: OsmConnection; | ||||||
|  | @ -119,7 +119,7 @@ export default class OsmFeatureSource { | ||||||
|                 const index = Tiles.tile_index(z, x, y); |                 const index = Tiles.tile_index(z, x, y); | ||||||
|                 new PerLayerFeatureSourceSplitter(this.filteredLayers, |                 new PerLayerFeatureSourceSplitter(this.filteredLayers, | ||||||
|                     this.handleTile, |                     this.handleTile, | ||||||
|                     new StaticFeatureSource(geojson.features, false), |                     StaticFeatureSource.fromGeojson(geojson.features), | ||||||
|                     { |                     { | ||||||
|                         tileIndex: index |                         tileIndex: index | ||||||
|                     } |                     } | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| import FeatureSource, {FeatureSourceForLayer, IndexedFeatureSource, Tiled} from "../FeatureSource"; | import FeatureSource, {FeatureSourceForLayer, IndexedFeatureSource, Tiled} from "../FeatureSource"; | ||||||
| import {UIEventSource} from "../../UIEventSource"; | import {Store, UIEventSource} from "../../UIEventSource"; | ||||||
| import FilteredLayer from "../../../Models/FilteredLayer"; | import FilteredLayer from "../../../Models/FilteredLayer"; | ||||||
| import TileHierarchy from "./TileHierarchy"; | import TileHierarchy from "./TileHierarchy"; | ||||||
| import {Tiles} from "../../../Models/TileRange"; | import {Tiles} from "../../../Models/TileRange"; | ||||||
|  | @ -24,7 +24,7 @@ export default class TiledFeatureSource implements Tiled, IndexedFeatureSource, | ||||||
|     public readonly maxFeatureCount: number; |     public readonly maxFeatureCount: number; | ||||||
|     public readonly name; |     public readonly name; | ||||||
|     public readonly features: UIEventSource<{ feature: any, freshness: Date }[]> |     public readonly features: UIEventSource<{ feature: any, freshness: Date }[]> | ||||||
|     public readonly containedIds: UIEventSource<Set<string>> |     public readonly containedIds: Store<Set<string>> | ||||||
| 
 | 
 | ||||||
|     public readonly bbox: BBox; |     public readonly bbox: BBox; | ||||||
|     public readonly tileIndex: number; |     public readonly tileIndex: number; | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ import {Mapillary} from "./Mapillary"; | ||||||
| import {WikimediaImageProvider} from "./WikimediaImageProvider"; | import {WikimediaImageProvider} from "./WikimediaImageProvider"; | ||||||
| import {Imgur} from "./Imgur"; | import {Imgur} from "./Imgur"; | ||||||
| import GenericImageProvider from "./GenericImageProvider"; | import GenericImageProvider from "./GenericImageProvider"; | ||||||
| import {UIEventSource} from "../UIEventSource"; | import {Store, UIEventSource} from "../UIEventSource"; | ||||||
| import ImageProvider, {ProvidedImage} from "./ImageProvider"; | import ImageProvider, {ProvidedImage} from "./ImageProvider"; | ||||||
| import {WikidataImageProvider} from "./WikidataImageProvider"; | import {WikidataImageProvider} from "./WikidataImageProvider"; | ||||||
| 
 | 
 | ||||||
|  | @ -37,7 +37,7 @@ export default class AllImageProviders { | ||||||
| 
 | 
 | ||||||
|     private static _cache: Map<string, UIEventSource<ProvidedImage[]>> = new Map<string, UIEventSource<ProvidedImage[]>>() |     private static _cache: Map<string, UIEventSource<ProvidedImage[]>> = new Map<string, UIEventSource<ProvidedImage[]>>() | ||||||
| 
 | 
 | ||||||
|     public static LoadImagesFor(tags: UIEventSource<any>, tagKey?: string[]): UIEventSource<ProvidedImage[]> { |     public static LoadImagesFor(tags: Store<any>, tagKey?: string[]): Store<ProvidedImage[]> { | ||||||
|         if (tags.data.id === undefined) { |         if (tags.data.id === undefined) { | ||||||
|             return undefined; |             return undefined; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import {UIEventSource} from "../UIEventSource"; | import {Store, Stores, UIEventSource} from "../UIEventSource"; | ||||||
| import BaseUIElement from "../../UI/BaseUIElement"; | import BaseUIElement from "../../UI/BaseUIElement"; | ||||||
| import {LicenseInfo} from "./LicenseInfo"; | import {LicenseInfo} from "./LicenseInfo"; | ||||||
| import {Utils} from "../../Utils"; | import {Utils} from "../../Utils"; | ||||||
|  | @ -13,14 +13,14 @@ export default abstract class ImageProvider { | ||||||
| 
 | 
 | ||||||
|     public abstract readonly defaultKeyPrefixes: string[] |     public abstract readonly defaultKeyPrefixes: string[] | ||||||
| 
 | 
 | ||||||
|     private _cache = new Map<string, UIEventSource<LicenseInfo>>() |     private _cache = new Map<string, Store<LicenseInfo>>() | ||||||
| 
 | 
 | ||||||
|     GetAttributionFor(url: string): UIEventSource<LicenseInfo> { |     GetAttributionFor(url: string): Store<LicenseInfo> { | ||||||
|         const cached = this._cache.get(url); |         const cached = this._cache.get(url); | ||||||
|         if (cached !== undefined) { |         if (cached !== undefined) { | ||||||
|             return cached; |             return cached; | ||||||
|         } |         } | ||||||
|         const src = UIEventSource.FromPromise(this.DownloadAttribution(url)) |         const src = Stores.FromPromise(this.DownloadAttribution(url)) | ||||||
|         this._cache.set(url, src) |         this._cache.set(url, src) | ||||||
|         return src; |         return src; | ||||||
|     } |     } | ||||||
|  | @ -30,7 +30,7 @@ export default abstract class ImageProvider { | ||||||
|     /** |     /** | ||||||
|      * Given a properies object, maps it onto _all_ the available pictures for this imageProvider |      * Given a properies object, maps it onto _all_ the available pictures for this imageProvider | ||||||
|      */ |      */ | ||||||
|     public GetRelevantUrls(allTags: UIEventSource<any>, options?: { |     public GetRelevantUrls(allTags: Store<any>, options?: { | ||||||
|         prefixes?: string[] |         prefixes?: string[] | ||||||
|     }): UIEventSource<ProvidedImage[]> { |     }): UIEventSource<ProvidedImage[]> { | ||||||
|         const prefixes = options?.prefixes ?? this.defaultKeyPrefixes |         const prefixes = options?.prefixes ?? this.defaultKeyPrefixes | ||||||
|  |  | ||||||
|  | @ -182,7 +182,7 @@ export default class CreateWayWithPointReuseAction extends OsmCreateAction { | ||||||
|             features.push(newGeometry) |             features.push(newGeometry) | ||||||
| 
 | 
 | ||||||
|         } |         } | ||||||
|         return new StaticFeatureSource(features, false) |         return StaticFeatureSource.fromGeojson(features) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public async CreateChangeDescriptions(changes: Changes): Promise<ChangeDescription[]> { |     public async CreateChangeDescriptions(changes: Changes): Promise<ChangeDescription[]> { | ||||||
|  |  | ||||||
|  | @ -159,7 +159,7 @@ export default class ReplaceGeometryAction extends OsmChangeAction { | ||||||
|         }) |         }) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         return new StaticFeatureSource(Utils.NoNull(preview), false) |         return StaticFeatureSource.fromGeojson(Utils.NoNull(preview)) | ||||||
| 
 | 
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -327,7 +327,7 @@ export class Changes { | ||||||
|             const successes = await Promise.all(Array.from(pendingPerTheme, |             const successes = await Promise.all(Array.from(pendingPerTheme, | ||||||
|                 async ([theme, pendingChanges]) => { |                 async ([theme, pendingChanges]) => { | ||||||
|                     try { |                     try { | ||||||
|                         const openChangeset = this.state.osmConnection.GetPreference("current-open-changeset-" + theme).map( |                         const openChangeset = this.state.osmConnection.GetPreference("current-open-changeset-" + theme).sync( | ||||||
|                             str => { |                             str => { | ||||||
|                                 const n = Number(str); |                                 const n = Number(str); | ||||||
|                                 if (isNaN(n)) { |                                 if (isNaN(n)) { | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| import osmAuth from "osm-auth"; | import osmAuth from "osm-auth"; | ||||||
| import {UIEventSource} from "../UIEventSource"; | import {Stores, UIEventSource} from "../UIEventSource"; | ||||||
| import {OsmPreferences} from "./OsmPreferences"; | import {OsmPreferences} from "./OsmPreferences"; | ||||||
| import {ChangesetHandler} from "./ChangesetHandler"; | import {ChangesetHandler} from "./ChangesetHandler"; | ||||||
| import {ElementStorage} from "../ElementStorage"; | import {ElementStorage} from "../ElementStorage"; | ||||||
|  | @ -228,7 +228,7 @@ export class OsmConnection { | ||||||
|         } |         } | ||||||
|         if (this._dryRun.data) { |         if (this._dryRun.data) { | ||||||
|             console.warn("Dryrun enabled - not actually closing note ", id, " with text ", text) |             console.warn("Dryrun enabled - not actually closing note ", id, " with text ", text) | ||||||
|             return new Promise((ok, error) => { |             return new Promise((ok) => { | ||||||
|                 ok() |                 ok() | ||||||
|             }); |             }); | ||||||
|         } |         } | ||||||
|  | @ -236,7 +236,7 @@ export class OsmConnection { | ||||||
|             this.auth.xhr({ |             this.auth.xhr({ | ||||||
|                 method: 'POST', |                 method: 'POST', | ||||||
|                 path: `/api/0.6/notes/${id}/close${textSuffix}`, |                 path: `/api/0.6/notes/${id}/close${textSuffix}`, | ||||||
|             }, function (err, response) { |             }, function (err, _) { | ||||||
|                 if (err !== null) { |                 if (err !== null) { | ||||||
|                     error(err) |                     error(err) | ||||||
|                 } else { |                 } else { | ||||||
|  | @ -251,7 +251,7 @@ export class OsmConnection { | ||||||
|     public reopenNote(id: number | string, text?: string): Promise<void> { |     public reopenNote(id: number | string, text?: string): Promise<void> { | ||||||
|         if (this._dryRun.data) { |         if (this._dryRun.data) { | ||||||
|             console.warn("Dryrun enabled - not actually reopening note ", id, " with text ", text) |             console.warn("Dryrun enabled - not actually reopening note ", id, " with text ", text) | ||||||
|             return new Promise((ok, error) => { |             return new Promise((ok) => { | ||||||
|                 ok() |                 ok() | ||||||
|             }); |             }); | ||||||
|         } |         } | ||||||
|  | @ -263,7 +263,7 @@ export class OsmConnection { | ||||||
|             this.auth.xhr({ |             this.auth.xhr({ | ||||||
|                 method: 'POST', |                 method: 'POST', | ||||||
|                 path: `/api/0.6/notes/${id}/reopen${textSuffix}` |                 path: `/api/0.6/notes/${id}/reopen${textSuffix}` | ||||||
|             }, function (err, response) { |             }, function (err, _) { | ||||||
|                 if (err !== null) { |                 if (err !== null) { | ||||||
|                     error(err) |                     error(err) | ||||||
|                 } else { |                 } else { | ||||||
|  | @ -278,7 +278,7 @@ export class OsmConnection { | ||||||
|     public openNote(lat: number, lon: number, text: string): Promise<{ id: number }> { |     public openNote(lat: number, lon: number, text: string): Promise<{ id: number }> { | ||||||
|         if (this._dryRun.data) { |         if (this._dryRun.data) { | ||||||
|             console.warn("Dryrun enabled - not actually opening note with text ", text) |             console.warn("Dryrun enabled - not actually opening note with text ", text) | ||||||
|             return new Promise<{ id: number }>((ok, error) => { |             return new Promise<{ id: number }>((ok) => { | ||||||
|                 window.setTimeout(() => ok({id: Math.floor(Math.random() * 1000)}), Math.random() * 5000) |                 window.setTimeout(() => ok({id: Math.floor(Math.random() * 1000)}), Math.random() * 5000) | ||||||
|             }); |             }); | ||||||
|         } |         } | ||||||
|  | @ -315,7 +315,7 @@ export class OsmConnection { | ||||||
|     public addCommentToNode(id: number | string, text: string): Promise<void> { |     public addCommentToNode(id: number | string, text: string): Promise<void> { | ||||||
|         if (this._dryRun.data) { |         if (this._dryRun.data) { | ||||||
|             console.warn("Dryrun enabled - not actually adding comment ", text, "to  note ", id) |             console.warn("Dryrun enabled - not actually adding comment ", text, "to  note ", id) | ||||||
|             return new Promise((ok, error) => { |             return new Promise((ok) => { | ||||||
|                 ok() |                 ok() | ||||||
|             }); |             }); | ||||||
|         } |         } | ||||||
|  | @ -328,7 +328,7 @@ export class OsmConnection { | ||||||
|                 method: 'POST', |                 method: 'POST', | ||||||
| 
 | 
 | ||||||
|                 path: `/api/0.6/notes/${id}/comment?text=${encodeURIComponent(text)}` |                 path: `/api/0.6/notes/${id}/comment?text=${encodeURIComponent(text)}` | ||||||
|             }, function (err, response) { |             }, function (err, _) { | ||||||
|                 if (err !== null) { |                 if (err !== null) { | ||||||
|                     error(err) |                     error(err) | ||||||
|                 } else { |                 } else { | ||||||
|  | @ -374,7 +374,7 @@ export class OsmConnection { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|         this.isChecking = true; |         this.isChecking = true; | ||||||
|         UIEventSource.Chronic(5 * 60 * 1000).addCallback(_ => { |         Stores.Chronic(5 * 60 * 1000).addCallback(_ => { | ||||||
|             if (self.isLoggedIn.data) { |             if (self.isLoggedIn.data) { | ||||||
|                 console.log("Checking for messages") |                 console.log("Checking for messages") | ||||||
|                 self.AttemptLogin(); |                 self.AttemptLogin(); | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import {Utils} from "../../Utils"; | import {Utils} from "../../Utils"; | ||||||
| import * as polygon_features from "../../assets/polygon-features.json"; | import * as polygon_features from "../../assets/polygon-features.json"; | ||||||
| import {UIEventSource} from "../UIEventSource"; | import {Store, Stores, UIEventSource} from "../UIEventSource"; | ||||||
| import {BBox} from "../BBox"; | import {BBox} from "../BBox"; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -40,7 +40,7 @@ export abstract class OsmObject { | ||||||
|         this.backendURL = url; |         this.backendURL = url; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static DownloadObject(id: string, forceRefresh: boolean = false): UIEventSource<OsmObject> { |     public static DownloadObject(id: string, forceRefresh: boolean = false): Store<OsmObject> { | ||||||
|         let src: UIEventSource<OsmObject>; |         let src: UIEventSource<OsmObject>; | ||||||
|         if (OsmObject.objectCache.has(id)) { |         if (OsmObject.objectCache.has(id)) { | ||||||
|             src = OsmObject.objectCache.get(id) |             src = OsmObject.objectCache.get(id) | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| import {TagsFilter} from "../Tags/TagsFilter"; | import {TagsFilter} from "../Tags/TagsFilter"; | ||||||
| import RelationsTracker from "./RelationsTracker"; | import RelationsTracker from "./RelationsTracker"; | ||||||
| import {Utils} from "../../Utils"; | import {Utils} from "../../Utils"; | ||||||
| import {UIEventSource} from "../UIEventSource"; | import {ImmutableStore, Store} from "../UIEventSource"; | ||||||
| import {BBox} from "../BBox"; | import {BBox} from "../BBox"; | ||||||
| import * as osmtogeojson from "osmtogeojson"; | import * as osmtogeojson from "osmtogeojson"; | ||||||
| import {FeatureCollection} from "@turf/turf"; | import {FeatureCollection} from "@turf/turf"; | ||||||
|  | @ -12,7 +12,7 @@ import {FeatureCollection} from "@turf/turf"; | ||||||
| export class Overpass { | export class Overpass { | ||||||
|     private _filter: TagsFilter |     private _filter: TagsFilter | ||||||
|     private readonly _interpreterUrl: string; |     private readonly _interpreterUrl: string; | ||||||
|     private readonly _timeout: UIEventSource<number>; |     private readonly _timeout: Store<number>; | ||||||
|     private readonly _extraScripts: string[]; |     private readonly _extraScripts: string[]; | ||||||
|     private _includeMeta: boolean; |     private _includeMeta: boolean; | ||||||
|     private _relationTracker: RelationsTracker; |     private _relationTracker: RelationsTracker; | ||||||
|  | @ -20,10 +20,10 @@ export class Overpass { | ||||||
|     constructor(filter: TagsFilter, |     constructor(filter: TagsFilter, | ||||||
|                 extraScripts: string[], |                 extraScripts: string[], | ||||||
|                 interpreterUrl: string, |                 interpreterUrl: string, | ||||||
|                 timeout?: UIEventSource<number>, |                 timeout?: Store<number>, | ||||||
|                 relationTracker?: RelationsTracker, |                 relationTracker?: RelationsTracker, | ||||||
|                 includeMeta = true) { |                 includeMeta = true) { | ||||||
|         this._timeout = timeout ?? new UIEventSource<number>(90); |         this._timeout = timeout ?? new ImmutableStore<number>(90); | ||||||
|         this._interpreterUrl = interpreterUrl; |         this._interpreterUrl = interpreterUrl; | ||||||
|         const optimized = filter.optimize() |         const optimized = filter.optimize() | ||||||
|         if(optimized === true || optimized === false){ |         if(optimized === true || optimized === false){ | ||||||
|  |  | ||||||
|  | @ -56,8 +56,9 @@ export default class FeatureSwitchState { | ||||||
|             ); |             ); | ||||||
| 
 | 
 | ||||||
|             // It takes the current layout, extracts the default value for this query parameter. A query parameter event source is then retrieved and flattened
 |             // It takes the current layout, extracts the default value for this query parameter. A query parameter event source is then retrieved and flattened
 | ||||||
|             return queryParam.map((str) => |             return queryParam.sync((str) => | ||||||
|                 str === undefined ? defaultValue : str !== "false" |                 str === undefined ? defaultValue : str !== "false", [], | ||||||
|  |                 b => b == defaultValue ? undefined : (""+b) | ||||||
|             ) |             ) | ||||||
| 
 | 
 | ||||||
|         } |         } | ||||||
|  | @ -163,7 +164,7 @@ export default class FeatureSwitchState { | ||||||
|         this.overpassUrl = QueryParameters.GetQueryParameter("overpassUrl", |         this.overpassUrl = QueryParameters.GetQueryParameter("overpassUrl", | ||||||
|             (layoutToUse?.overpassUrl ?? Constants.defaultOverpassUrls).join(","), |             (layoutToUse?.overpassUrl ?? Constants.defaultOverpassUrls).join(","), | ||||||
|             "Point mapcomplete to a different overpass-instance. Example: https://overpass-api.de/api/interpreter" |             "Point mapcomplete to a different overpass-instance. Example: https://overpass-api.de/api/interpreter" | ||||||
|         ).map(param => param.split(","), [], urls => urls.join(",")) |         ).sync(param => param.split(","), [], urls => urls.join(",")) | ||||||
| 
 | 
 | ||||||
|         this.overpassTimeout = UIEventSource.asFloat(QueryParameters.GetQueryParameter("overpassTimeout", |         this.overpassTimeout = UIEventSource.asFloat(QueryParameters.GetQueryParameter("overpassTimeout", | ||||||
|             "" + layoutToUse?.overpassTimeout, |             "" + layoutToUse?.overpassTimeout, | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| import UserRelatedState from "./UserRelatedState"; | import UserRelatedState from "./UserRelatedState"; | ||||||
| import {UIEventSource} from "../UIEventSource"; | import {Store, Stores, UIEventSource} from "../UIEventSource"; | ||||||
| import BaseLayer from "../../Models/BaseLayer"; | import BaseLayer from "../../Models/BaseLayer"; | ||||||
| import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; | import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; | ||||||
| import AvailableBaseLayers from "../Actors/AvailableBaseLayers"; | import AvailableBaseLayers from "../Actors/AvailableBaseLayers"; | ||||||
|  | @ -18,6 +18,7 @@ import {GeoOperations} from "../GeoOperations"; | ||||||
| import TitleHandler from "../Actors/TitleHandler"; | import TitleHandler from "../Actors/TitleHandler"; | ||||||
| import {BBox} from "../BBox"; | import {BBox} from "../BBox"; | ||||||
| import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; | import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; | ||||||
|  | import {TiledStaticFeatureSource} from "../FeatureSource/Sources/StaticFeatureSource"; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Contains all the leaflet-map related state |  * Contains all the leaflet-map related state | ||||||
|  | @ -31,7 +32,7 @@ export default class MapState extends UserRelatedState { | ||||||
|     /** |     /** | ||||||
|      * A list of currently available background layers |      * A list of currently available background layers | ||||||
|      */ |      */ | ||||||
|     public availableBackgroundLayers: UIEventSource<BaseLayer[]>; |     public availableBackgroundLayers: Store<BaseLayer[]>; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * The current background layer |      * The current background layer | ||||||
|  | @ -52,12 +53,12 @@ export default class MapState extends UserRelatedState { | ||||||
|     /** |     /** | ||||||
|      * The location as delivered by the GPS |      * The location as delivered by the GPS | ||||||
|      */ |      */ | ||||||
|     public currentUserLocation: FeatureSourceForLayer & Tiled; |     public currentUserLocation: SimpleFeatureSource; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * All previously visited points |      * All previously visited points | ||||||
|      */ |      */ | ||||||
|     public historicalUserLocations: FeatureSourceForLayer & Tiled; |     public historicalUserLocations: SimpleFeatureSource; | ||||||
|     /** |     /** | ||||||
|      * The number of seconds that the GPS-locations are stored in memory. |      * The number of seconds that the GPS-locations are stored in memory. | ||||||
|      * Time in seconds |      * Time in seconds | ||||||
|  | @ -176,7 +177,7 @@ export default class MapState extends UserRelatedState { | ||||||
| 
 | 
 | ||||||
|         let i = 0 |         let i = 0 | ||||||
|         const self = this; |         const self = this; | ||||||
|         const features: UIEventSource<{ feature: any, freshness: Date }[]> = this.currentBounds.map(bounds => { |         const features: Store<{ feature: any, freshness: Date }[]> = this.currentBounds.map(bounds => { | ||||||
|             if (bounds === undefined) { |             if (bounds === undefined) { | ||||||
|                 return [] |                 return [] | ||||||
|             } |             } | ||||||
|  | @ -205,7 +206,7 @@ export default class MapState extends UserRelatedState { | ||||||
|             return [feature] |             return [feature] | ||||||
|         }) |         }) | ||||||
| 
 | 
 | ||||||
|         this.currentView = new SimpleFeatureSource(currentViewLayer, 0, features) |         this.currentView = new TiledStaticFeatureSource(features,  currentViewLayer); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private initGpsLocation() { |     private initGpsLocation() { | ||||||
|  | @ -289,13 +290,13 @@ export default class MapState extends UserRelatedState { | ||||||
|         }) |         }) | ||||||
|         let gpsLineLayerDef: FilteredLayer = this.filteredLayers.data.filter(l => l.layerDef.id === "gps_track")[0] |         let gpsLineLayerDef: FilteredLayer = this.filteredLayers.data.filter(l => l.layerDef.id === "gps_track")[0] | ||||||
|         if (gpsLineLayerDef !== undefined) { |         if (gpsLineLayerDef !== undefined) { | ||||||
|             this.historicalUserLocationsTrack = new SimpleFeatureSource(gpsLineLayerDef, Tiles.tile_index(0, 0, 0), asLine); |             this.historicalUserLocationsTrack = new TiledStaticFeatureSource(asLine, gpsLineLayerDef); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private initHomeLocation() { |     private initHomeLocation() { | ||||||
|         const empty = [] |         const empty = [] | ||||||
|         const feature = UIEventSource.ListStabilized(this.osmConnection.userDetails.map(userDetails => { |         const feature = Stores.ListStabilized(this.osmConnection.userDetails.map(userDetails => { | ||||||
| 
 | 
 | ||||||
|             if (userDetails === undefined) { |             if (userDetails === undefined) { | ||||||
|                 return undefined; |                 return undefined; | ||||||
|  | @ -328,7 +329,7 @@ export default class MapState extends UserRelatedState { | ||||||
| 
 | 
 | ||||||
|         const flayer = this.filteredLayers.data.filter(l => l.layerDef.id === "home_location")[0] |         const flayer = this.filteredLayers.data.filter(l => l.layerDef.id === "home_location")[0] | ||||||
|         if (flayer !== undefined) { |         if (flayer !== undefined) { | ||||||
|             this.homeLocation = new SimpleFeatureSource(flayer, Tiles.tile_index(0, 0, 0), feature) |             this.homeLocation = new TiledStaticFeatureSource(feature, flayer) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|     } |     } | ||||||
|  | @ -336,7 +337,7 @@ export default class MapState extends UserRelatedState { | ||||||
|     private getPref(key: string, layer: LayerConfig): UIEventSource<boolean> { |     private getPref(key: string, layer: LayerConfig): UIEventSource<boolean> { | ||||||
|       const pref = this.osmConnection |       const pref = this.osmConnection | ||||||
|             .GetPreference(key) |             .GetPreference(key) | ||||||
|             .map(v => { |             .sync(v => { | ||||||
|                 if(v === undefined){ |                 if(v === undefined){ | ||||||
|                     return undefined |                     return undefined | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; | import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; | ||||||
| import {OsmConnection} from "../Osm/OsmConnection"; | import {OsmConnection} from "../Osm/OsmConnection"; | ||||||
| import {MangroveIdentity} from "../Web/MangroveReviews"; | import {MangroveIdentity} from "../Web/MangroveReviews"; | ||||||
| import {UIEventSource} from "../UIEventSource"; | import {Store, UIEventSource} from "../UIEventSource"; | ||||||
| import {QueryParameters} from "../Web/QueryParameters"; | import {QueryParameters} from "../Web/QueryParameters"; | ||||||
| import {LocalStorageSource} from "../Web/LocalStorageSource"; | import {LocalStorageSource} from "../Web/LocalStorageSource"; | ||||||
| import {Utils} from "../../Utils"; | import {Utils} from "../../Utils"; | ||||||
|  | @ -37,7 +37,7 @@ export default class UserRelatedState extends ElementsState { | ||||||
|      */ |      */ | ||||||
|     public favouriteLayers: UIEventSource<string[]>; |     public favouriteLayers: UIEventSource<string[]>; | ||||||
| 
 | 
 | ||||||
|     public readonly isTranslator : UIEventSource<boolean>; |     public readonly isTranslator : Store<boolean>; | ||||||
|      |      | ||||||
|     constructor(layoutToUse: LayoutConfig, options?: { attemptLogin: true | boolean }) { |     constructor(layoutToUse: LayoutConfig, options?: { attemptLogin: true | boolean }) { | ||||||
|         super(layoutToUse); |         super(layoutToUse); | ||||||
|  | @ -53,7 +53,7 @@ export default class UserRelatedState extends ElementsState { | ||||||
|             osmConfiguration: <'osm' | 'osm-test'>this.featureSwitchApiURL.data, |             osmConfiguration: <'osm' | 'osm-test'>this.featureSwitchApiURL.data, | ||||||
|             attemptLogin: options?.attemptLogin |             attemptLogin: options?.attemptLogin | ||||||
|         }) |         }) | ||||||
|         const translationMode  = this.osmConnection.GetPreference("translation-mode").map(str => str === undefined ? undefined : str === "true", [], b => b === undefined ? undefined : b+"") |         const translationMode  = this.osmConnection.GetPreference("translation-mode").sync(str => str === undefined ? undefined : str === "true", [], b => b === undefined ? undefined : b+"") | ||||||
|              |              | ||||||
|         translationMode.syncWith(Locale.showLinkToWeblate) |         translationMode.syncWith(Locale.showLinkToWeblate) | ||||||
|          |          | ||||||
|  | @ -108,7 +108,7 @@ export default class UserRelatedState extends ElementsState { | ||||||
|         // Important: the favourite layers are initialized _after_ the installed themes, as these might contain an installedTheme
 |         // Important: the favourite layers are initialized _after_ the installed themes, as these might contain an installedTheme
 | ||||||
|         this.favouriteLayers = LocalStorageSource.Get("favouriteLayers") |         this.favouriteLayers = LocalStorageSource.Get("favouriteLayers") | ||||||
|             .syncWith(this.osmConnection.GetLongPreference("favouriteLayers")) |             .syncWith(this.osmConnection.GetLongPreference("favouriteLayers")) | ||||||
|             .map( |             .sync( | ||||||
|                 (str) => Utils.Dedup(str?.split(";")) ?? [], |                 (str) => Utils.Dedup(str?.split(";")) ?? [], | ||||||
|                 [], |                 [], | ||||||
|                 (layers) => Utils.Dedup(layers)?.join(";") |                 (layers) => Utils.Dedup(layers)?.join(";") | ||||||
|  |  | ||||||
|  | @ -1,64 +1,10 @@ | ||||||
| import {Utils} from "../Utils"; | import {Utils} from "../Utils"; | ||||||
| 
 | 
 | ||||||
| export class UIEventSource<T> { | /** | ||||||
| 
 |  * Various static utils | ||||||
|     private static allSources: UIEventSource<any>[] = UIEventSource.PrepPerf(); |  */ | ||||||
|     public data: T; | export class Stores { | ||||||
|     public trace: boolean; |     public static Chronic(millis: number, asLong: () => boolean = undefined): Store<Date> { | ||||||
|     private readonly tag: string; |  | ||||||
|     private _callbacks: ((t: T) => (boolean | void | any)) [] = []; |  | ||||||
| 
 |  | ||||||
|     constructor(data: T, tag: string = "") { |  | ||||||
|         this.tag = tag; |  | ||||||
|         this.data = data; |  | ||||||
|         if (tag === undefined || tag === "") { |  | ||||||
|             const callstack = new Error().stack.split("\n") |  | ||||||
|             this.tag = callstack[1] |  | ||||||
|         } |  | ||||||
|         UIEventSource.allSources.push(this); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     static PrepPerf(): UIEventSource<any>[] { |  | ||||||
|         if (Utils.runningFromConsole) { |  | ||||||
|             return []; |  | ||||||
|         } |  | ||||||
|         // @ts-ignore
 |  | ||||||
|         window.mapcomplete_performance = () => { |  | ||||||
|             console.log(UIEventSource.allSources.length, "uieventsources created"); |  | ||||||
|             const copy = [...UIEventSource.allSources]; |  | ||||||
|             copy.sort((a, b) => b._callbacks.length - a._callbacks.length); |  | ||||||
|             console.log("Topten is:") |  | ||||||
|             for (let i = 0; i < 10; i++) { |  | ||||||
|                 console.log(copy[i].tag, copy[i]); |  | ||||||
|             } |  | ||||||
|             return UIEventSource.allSources; |  | ||||||
|         } |  | ||||||
|         return []; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public static flatten<X>(source: UIEventSource<UIEventSource<X>>, possibleSources?: UIEventSource<any>[]): UIEventSource<X> { |  | ||||||
|         const sink = new UIEventSource<X>(source.data?.data); |  | ||||||
| 
 |  | ||||||
|         source.addCallback((latestData) => { |  | ||||||
|             sink.setData(latestData?.data); |  | ||||||
|             latestData.addCallback(data => { |  | ||||||
|                 if (source.data !== latestData) { |  | ||||||
|                     return true; |  | ||||||
|                 } |  | ||||||
|                 sink.setData(data) |  | ||||||
|             }) |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|         for (const possibleSource of possibleSources ?? []) { |  | ||||||
|             possibleSource?.addCallback(() => { |  | ||||||
|                 sink.setData(source.data?.data); |  | ||||||
|             }) |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return sink; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public static Chronic(millis: number, asLong: () => boolean = undefined): UIEventSource<Date> { |  | ||||||
|         const source = new UIEventSource<Date>(undefined); |         const source = new UIEventSource<Date>(undefined); | ||||||
| 
 | 
 | ||||||
|         function run() { |         function run() { | ||||||
|  | @ -72,17 +18,8 @@ export class UIEventSource<T> { | ||||||
|         return source; |         return source; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     public static FromPromiseWithErr<T>(promise: Promise<T>): Store<{ success: T } | { error: any }>{ | ||||||
|      * Converts a promise into a UIVentsource, sets the UIEVentSource when the result is calculated. |         return UIEventSource.FromPromiseWithErr(promise); | ||||||
|      * If the promise fails, the value will stay undefined |  | ||||||
|      * @param promise |  | ||||||
|      * @constructor |  | ||||||
|      */ |  | ||||||
|     public static FromPromise<T>(promise: Promise<T>): UIEventSource<T> { |  | ||||||
|         const src = new UIEventSource<T>(undefined) |  | ||||||
|         promise?.then(d => src.setData(d)) |  | ||||||
|         promise?.catch(err => console.warn("Promise failed:", err)) |  | ||||||
|         return src |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | @ -91,13 +28,17 @@ export class UIEventSource<T> { | ||||||
|      * @param promise |      * @param promise | ||||||
|      * @constructor |      * @constructor | ||||||
|      */ |      */ | ||||||
|     public static FromPromiseWithErr<T>(promise: Promise<T>): UIEventSource<{ success: T } | { error: any }> { |     public static FromPromise<T>(promise: Promise<T>): Store<T> { | ||||||
|         const src = new UIEventSource<{ success: T } | { error: any }>(undefined) |         const src = new UIEventSource<T>(undefined) | ||||||
|         promise?.then(d => src.setData({success: d})) |         promise?.then(d => src.setData(d)) | ||||||
|         promise?.catch(err => src.setData({error: err})) |         promise?.catch(err => console.warn("Promise failed:", err)) | ||||||
|         return src |         return src | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public static flatten<X>(source: Store<Store<X>>, possibleSources?: Store<any>[]): Store<X> { | ||||||
|  |         return UIEventSource.flatten(source, possibleSources); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Given a UIEVentSource with a list, returns a new UIEventSource which is only updated if the _contents_ of the list are different. |      * Given a UIEVentSource with a list, returns a new UIEventSource which is only updated if the _contents_ of the list are different. | ||||||
|      * E.g. |      * E.g. | ||||||
|  | @ -112,7 +53,7 @@ export class UIEventSource<T> { | ||||||
|      * @param src |      * @param src | ||||||
|      * @constructor |      * @constructor | ||||||
|      */ |      */ | ||||||
|     public static ListStabilized<T>(src: UIEventSource<T[]>): UIEventSource<T[]> { |     public static ListStabilized<T>(src: Store<T[]>): Store<T[]> { | ||||||
| 
 | 
 | ||||||
|         const stable = new UIEventSource<T[]>(src.data) |         const stable = new UIEventSource<T[]>(src.data) | ||||||
|         src.addCallback(list => { |         src.addCallback(list => { | ||||||
|  | @ -141,46 +82,58 @@ export class UIEventSource<T> { | ||||||
|         }) |         }) | ||||||
|         return stable |         return stable | ||||||
|     } |     } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|     public static asFloat(source: UIEventSource<string>): UIEventSource<number> { | export abstract class Store<T> { | ||||||
|         return source.map( |     abstract readonly data: T; | ||||||
|             (str) => { | 
 | ||||||
|                 let parsed = parseFloat(str); |     /** | ||||||
|                 return isNaN(parsed) ? undefined : parsed; |      * OPtional value giving a title to the UIEventSource, mainly used for debugging | ||||||
|             }, |      */ | ||||||
|             [], |     public readonly tag: string | undefined; | ||||||
|             (fl) => { |      | ||||||
|                 if (fl === undefined || isNaN(fl)) { | 
 | ||||||
|                     return undefined; |     constructor(tag: string = undefined) { | ||||||
|                 } |         this.tag = tag; | ||||||
|                 return ("" + fl).substr(0, 8); |         if ((tag === undefined || tag === "")) { | ||||||
|  |             let createStack = Utils.runningFromConsole; | ||||||
|  |             if(!Utils.runningFromConsole) { | ||||||
|  |                 createStack = window.location.hostname === "127.0.0.1" | ||||||
|             } |             } | ||||||
|         ) |             if(createStack) { | ||||||
|     } |                 const callstack = new Error().stack.split("\n") | ||||||
| 
 |                 this.tag = callstack[1] | ||||||
|     public AsPromise(condition?: ((t: T )=> boolean)): Promise<T> { |  | ||||||
|         const self = this; |  | ||||||
|         condition = condition ?? (t => t !== undefined) |  | ||||||
|         return new Promise((resolve, reject) => { |  | ||||||
|             if (condition(self.data)) { |  | ||||||
|                 resolve(self.data) |  | ||||||
|             } else { |  | ||||||
|                 self.addCallbackD(data => { |  | ||||||
|                     resolve(data) |  | ||||||
|                     return true; // return true to unregister as we only need to be called once
 |  | ||||||
|                 }) |  | ||||||
|             } |             } | ||||||
|         }) |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public WaitForPromise(promise: Promise<T>, onFail: ((any) => void)): UIEventSource<T> { |     abstract map<J>(f: ((t: T) => J)): Store<J> | ||||||
|         const self = this; |     abstract map<J>(f: ((t: T) => J), extraStoresToWatch: Store<any>[]): Store<J> | ||||||
|         promise?.then(d => self.setData(d)) |  | ||||||
|         promise?.catch(err => onFail(err)) |  | ||||||
|         return this |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     public withEqualityStabilized(comparator: (t: T | undefined, t1: T | undefined) => boolean): UIEventSource<T> { |     /** | ||||||
|  |      * Add a callback function which will run on future data changes | ||||||
|  |      */ | ||||||
|  |     abstract addCallback(callback: (data: T) => void); | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Adds a callback function, which will be run immediately. | ||||||
|  |      * Only triggers if the current data is defined | ||||||
|  |      */ | ||||||
|  |     abstract addCallbackAndRunD(callback: (data: T) => void); | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Add a callback function which will run on future data changes | ||||||
|  |      * Only triggers if the data is defined | ||||||
|  |      */ | ||||||
|  |     abstract addCallbackD(callback: (data: T) => void); | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Adds a callback function, which will be run immediately. | ||||||
|  |      * Only triggers if the current data is defined | ||||||
|  |      */ | ||||||
|  |     abstract addCallbackAndRun(callback: (data: T) => void); | ||||||
|  | 
 | ||||||
|  |     public withEqualityStabilized(comparator: (t: T | undefined, t1: T | undefined) => boolean): Store<T> { | ||||||
|         let oldValue = undefined; |         let oldValue = undefined; | ||||||
|         return this.map(v => { |         return this.map(v => { | ||||||
|             if (v == oldValue) { |             if (v == oldValue) { | ||||||
|  | @ -194,6 +147,205 @@ export class UIEventSource<T> { | ||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Monadic bind function | ||||||
|  |      */ | ||||||
|  |     public bind<X>(f: ((t: T) => Store<X>)): Store<X> { | ||||||
|  |         const mapped = this.map(f) | ||||||
|  |         const sink = new UIEventSource<X>(undefined) | ||||||
|  |         const seenEventSources = new Set<Store<X>>(); | ||||||
|  |         mapped.addCallbackAndRun(newEventSource => { | ||||||
|  |             if (newEventSource === null) { | ||||||
|  |                 sink.setData(null) | ||||||
|  |             } else if (newEventSource === undefined) { | ||||||
|  |                 sink.setData(undefined) | ||||||
|  |             } else if (!seenEventSources.has(newEventSource)) { | ||||||
|  |                 seenEventSources.add(newEventSource) | ||||||
|  |                 newEventSource.addCallbackAndRun(resultData => { | ||||||
|  |                     if (mapped.data === newEventSource) { | ||||||
|  |                         sink.setData(resultData); | ||||||
|  |                     } | ||||||
|  |                 }) | ||||||
|  |             } else { | ||||||
|  |                 // Already seen, so we don't have to add a callback, just update the value
 | ||||||
|  |                 sink.setData(newEventSource.data) | ||||||
|  |             } | ||||||
|  |         }) | ||||||
|  | 
 | ||||||
|  |         return sink; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public stabilized(millisToStabilize): Store<T> { | ||||||
|  |         if (Utils.runningFromConsole) { | ||||||
|  |             return this; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const newSource = new UIEventSource<T>(this.data); | ||||||
|  | 
 | ||||||
|  |         let currentCallback = 0; | ||||||
|  |         this.addCallback(latestData => { | ||||||
|  |             currentCallback++; | ||||||
|  |             const thisCallback = currentCallback; | ||||||
|  |             window.setTimeout(() => { | ||||||
|  |                 if (thisCallback === currentCallback) { | ||||||
|  |                     newSource.setData(latestData); | ||||||
|  |                 } | ||||||
|  |             }, millisToStabilize) | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         return newSource; | ||||||
|  |     } | ||||||
|  |     public AsPromise(condition?: ((t: T) => boolean)): Promise<T> { | ||||||
|  |         const self = this; | ||||||
|  |         condition = condition ?? (t => t !== undefined) | ||||||
|  |         return new Promise((resolve) => { | ||||||
|  |             if (condition(self.data)) { | ||||||
|  |                 resolve(self.data) | ||||||
|  |             } else { | ||||||
|  |                 self.addCallbackD(data => { | ||||||
|  |                     resolve(data) | ||||||
|  |                     return true; // return true to unregister as we only need to be called once
 | ||||||
|  |                 }) | ||||||
|  |             } | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export class ImmutableStore<T> extends Store<T> { | ||||||
|  |     public readonly data: T; | ||||||
|  | 
 | ||||||
|  |     constructor(data: T) { | ||||||
|  |         super(); | ||||||
|  |         this.data = data; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     addCallback(callback: (data: T) => void) { | ||||||
|  |         // pass: data will never change
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     addCallbackAndRun(callback: (data: T) => void) { | ||||||
|  |         callback(this.data) | ||||||
|  |         // no callback registry: data will never change
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     addCallbackAndRunD(callback: (data: T) => void) { | ||||||
|  |         if(this.data !== undefined){ | ||||||
|  |             callback(this.data) | ||||||
|  |         } | ||||||
|  |         // no callback registry: data will never change
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     addCallbackD(callback: (data: T) => void) { | ||||||
|  |         // pass: data will never change
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     map<J>(f: (t: T) => J): ImmutableStore<J> { | ||||||
|  |         return new ImmutableStore<J>(f(this.data)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | export class UIEventSource<T> extends Store<T> { | ||||||
|  | 
 | ||||||
|  |     private static allSources: UIEventSource<any>[] = UIEventSource.PrepPerf(); | ||||||
|  |     public data: T; | ||||||
|  |     private _callbacks: ((t: T) => (boolean | void | any)) [] = []; | ||||||
|  | 
 | ||||||
|  |     constructor(data: T, tag: string = "") { | ||||||
|  |         super(tag); | ||||||
|  |         this.data = data; | ||||||
|  |         UIEventSource.allSources.push(this); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     static PrepPerf(): UIEventSource<any>[] { | ||||||
|  |         if (Utils.runningFromConsole) { | ||||||
|  |             return []; | ||||||
|  |         } | ||||||
|  |         // @ts-ignore
 | ||||||
|  |         window.mapcomplete_performance = () => { | ||||||
|  |             console.log(UIEventSource.allSources.length, "uieventsources created"); | ||||||
|  |             const copy = [...UIEventSource.allSources]; | ||||||
|  |             copy.sort((a, b) => b._callbacks.length - a._callbacks.length); | ||||||
|  |             console.log("Topten is:") | ||||||
|  |             for (let i = 0; i < 10; i++) { | ||||||
|  |                 console.log(copy[i].tag, copy[i]); | ||||||
|  |             } | ||||||
|  |             return UIEventSource.allSources; | ||||||
|  |         } | ||||||
|  |         return []; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static flatten<X>(source: Store<Store<X>>, possibleSources?: Store<any>[]): UIEventSource<X> { | ||||||
|  |         const sink = new UIEventSource<X>(source.data?.data); | ||||||
|  | 
 | ||||||
|  |         source.addCallback((latestData) => { | ||||||
|  |             sink.setData(latestData?.data); | ||||||
|  |             latestData.addCallback(data => { | ||||||
|  |                 if (source.data !== latestData) { | ||||||
|  |                     return true; | ||||||
|  |                 } | ||||||
|  |                 sink.setData(data) | ||||||
|  |             }) | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         for (const possibleSource of possibleSources ?? []) { | ||||||
|  |             possibleSource?.addCallback(() => { | ||||||
|  |                 sink.setData(source.data?.data); | ||||||
|  |             }) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return sink; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Converts a promise into a UIVentsource, sets the UIEVentSource when the result is calculated. | ||||||
|  |      * If the promise fails, the value will stay undefined, but 'onError' will be called | ||||||
|  |      */ | ||||||
|  |     public static FromPromise<T>(promise: Promise<T>, onError :( (e: any) => void) = undefined): UIEventSource<T> { | ||||||
|  |         const src = new UIEventSource<T>(undefined) | ||||||
|  |         promise?.then(d => src.setData(d)) | ||||||
|  |         promise?.catch(err => { | ||||||
|  |             if(onError !== undefined){ | ||||||
|  |                 onError(err) | ||||||
|  |             }else{ | ||||||
|  |                 console.warn("Promise failed:", err); | ||||||
|  |             } | ||||||
|  |         }) | ||||||
|  |         return src | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Converts a promise into a UIVentsource, sets the UIEVentSource when the result is calculated. | ||||||
|  |      * If the promise fails, the value will stay undefined | ||||||
|  |      * @param promise | ||||||
|  |      * @constructor | ||||||
|  |      */ | ||||||
|  |     public static FromPromiseWithErr<T>(promise: Promise<T>): UIEventSource<{ success: T } | { error: any }> { | ||||||
|  |         const src = new UIEventSource<{ success: T } | { error: any }>(undefined) | ||||||
|  |         promise?.then(d => src.setData({success: d})) | ||||||
|  |         promise?.catch(err => src.setData({error: err})) | ||||||
|  |         return src | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static asFloat(source: UIEventSource<string>): UIEventSource<number> { | ||||||
|  |         return source.sync( | ||||||
|  |             (str) => { | ||||||
|  |                 let parsed = parseFloat(str); | ||||||
|  |                 return isNaN(parsed) ? undefined : parsed; | ||||||
|  |             }, | ||||||
|  |             [], | ||||||
|  |             (fl) => { | ||||||
|  |                 if (fl === undefined || isNaN(fl)) { | ||||||
|  |                     return undefined; | ||||||
|  |                 } | ||||||
|  |                 return ("" + fl).substr(0, 8); | ||||||
|  |             } | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Adds a callback |      * Adds a callback | ||||||
|      * |      * | ||||||
|  | @ -205,9 +357,6 @@ export class UIEventSource<T> { | ||||||
|             // This ^^^ actually works!
 |             // This ^^^ actually works!
 | ||||||
|             throw "Don't add console.log directly as a callback - you'll won't be able to find it afterwards. Wrap it in a lambda instead." |             throw "Don't add console.log directly as a callback - you'll won't be able to find it afterwards. Wrap it in a lambda instead." | ||||||
|         } |         } | ||||||
|         if (this.trace) { |  | ||||||
|             console.trace("Added a callback") |  | ||||||
|         } |  | ||||||
|         this._callbacks.push(callback); |         this._callbacks.push(callback); | ||||||
|         return this; |         return this; | ||||||
|     } |     } | ||||||
|  | @ -255,44 +404,47 @@ export class UIEventSource<T> { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Monadic bind function |      * Monoidal map which results in a read-only store | ||||||
|  |      * Given a function 'f', will construct a new UIEventSource where the contents will always be "f(this.data)' | ||||||
|  |      * @param f: The transforming function | ||||||
|  |      * @param extraSources: also trigger the update if one of these sources change | ||||||
|      */ |      */ | ||||||
|     public bind<X>(f: ((t: T) => UIEventSource<X>)): UIEventSource<X> { |     public map<J>(f: ((t: T) => J), | ||||||
|         const mapped = this.map(f) |                    extraSources: Store<any>[] = []): Store<J> { | ||||||
|         const sink = new UIEventSource<X>(undefined) |         const self = this; | ||||||
|         const seenEventSources = new Set<UIEventSource<X>>(); |  | ||||||
|         mapped.addCallbackAndRun(newEventSource => { |  | ||||||
|             if (newEventSource === null) { |  | ||||||
|                 sink.setData(null) |  | ||||||
|             } else if (newEventSource === undefined) { |  | ||||||
|                 sink.setData(undefined) |  | ||||||
|             } else if (!seenEventSources.has(newEventSource)) { |  | ||||||
|                 seenEventSources.add(newEventSource) |  | ||||||
|                 newEventSource.addCallbackAndRun(resultData => { |  | ||||||
|                     if (mapped.data === newEventSource) { |  | ||||||
|                         sink.setData(resultData); |  | ||||||
|                     } |  | ||||||
|                 }) |  | ||||||
|             } else { |  | ||||||
|                 // Already seen, so we don't have to add a callback, just update the value
 |  | ||||||
|                 sink.setData(newEventSource.data) |  | ||||||
|             } |  | ||||||
|         }) |  | ||||||
| 
 | 
 | ||||||
|         return sink; |         const stack = new Error().stack.split("\n"); | ||||||
|  |         const callee = stack[1] | ||||||
|  | 
 | ||||||
|  |         const newSource = new UIEventSource<J>( | ||||||
|  |             f(this.data), | ||||||
|  |             "map(" + this.tag + ")@" + callee | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         const update = function () { | ||||||
|  |             newSource.setData(f(self.data)); | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         this.addCallback(update); | ||||||
|  |         for (const extraSource of extraSources) { | ||||||
|  |             extraSource?.addCallback(update); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return newSource; | ||||||
|     } |     } | ||||||
| 
 |      | ||||||
|     /** |     /** | ||||||
|      * Monoidal map: |      * Two way sync with functions in both directions | ||||||
|      * Given a function 'f', will construct a new UIEventSource where the contents will always be "f(this.data)' |      * Given a function 'f', will construct a new UIEventSource where the contents will always be "f(this.data)' | ||||||
|      * @param f: The transforming function |      * @param f: The transforming function | ||||||
|      * @param extraSources: also trigger the update if one of these sources change |      * @param extraSources: also trigger the update if one of these sources change | ||||||
|      * @param g: a 'backfunction to let the sync run in two directions. (data of the new UIEVEntSource, currentData) => newData |      * @param g: a 'backfunction to let the sync run in two directions. (data of the new UIEVEntSource, currentData) => newData | ||||||
|      * @param allowUnregister: if set, the update will be halted if no listeners are registered |      * @param allowUnregister: if set, the update will be halted if no listeners are registered | ||||||
|      */ |      */ | ||||||
|     public map<J>(f: ((t: T) => J), |     public sync<J>(f: ((t: T) => J), | ||||||
|                   extraSources: UIEventSource<any>[] = [], |                   extraSources: Store<any>[], | ||||||
|                   g: ((j: J, t: T) => T) = undefined, |                   g: ((j: J, t: T) => T) , | ||||||
|                   allowUnregister = false): UIEventSource<J> { |                   allowUnregister = false): UIEventSource<J> { | ||||||
|         const self = this; |         const self = this; | ||||||
| 
 | 
 | ||||||
|  | @ -328,7 +480,7 @@ export class UIEventSource<T> { | ||||||
|         const self = this; |         const self = this; | ||||||
|         otherSource.addCallback((latest) => self.setData(latest)); |         otherSource.addCallback((latest) => self.setData(latest)); | ||||||
|         if (reverseOverride) { |         if (reverseOverride) { | ||||||
|             if(otherSource.data !== undefined){ |             if (otherSource.data !== undefined) { | ||||||
|                 this.setData(otherSource.data); |                 this.setData(otherSource.data); | ||||||
|             } |             } | ||||||
|         } else if (this.data === undefined) { |         } else if (this.data === undefined) { | ||||||
|  | @ -339,27 +491,6 @@ export class UIEventSource<T> { | ||||||
|         return this; |         return this; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public stabilized(millisToStabilize): UIEventSource<T> { |  | ||||||
|         if (Utils.runningFromConsole) { |  | ||||||
|             return this; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         const newSource = new UIEventSource<T>(this.data); |  | ||||||
| 
 |  | ||||||
|         let currentCallback = 0; |  | ||||||
|         this.addCallback(latestData => { |  | ||||||
|             currentCallback++; |  | ||||||
|             const thisCallback = currentCallback; |  | ||||||
|             window.setTimeout(() => { |  | ||||||
|                 if (thisCallback === currentCallback) { |  | ||||||
|                     newSource.setData(latestData); |  | ||||||
|                 } |  | ||||||
|             }, millisToStabilize) |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|         return newSource; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     addCallbackAndRunD(callback: (data: T) => void) { |     addCallbackAndRunD(callback: (data: T) => void) { | ||||||
|         this.addCallbackAndRun(data => { |         this.addCallbackAndRun(data => { | ||||||
|             if (data !== undefined && data !== null) { |             if (data !== undefined && data !== null) { | ||||||
|  | @ -375,4 +506,5 @@ export class UIEventSource<T> { | ||||||
|             } |             } | ||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
| } | } | ||||||
|  | @ -6,7 +6,7 @@ import {UIEventSource} from "../UIEventSource"; | ||||||
| export class LocalStorageSource { | export class LocalStorageSource { | ||||||
| 
 | 
 | ||||||
|     static GetParsed<T>(key: string, defaultValue: T): UIEventSource<T> { |     static GetParsed<T>(key: string, defaultValue: T): UIEventSource<T> { | ||||||
|         return LocalStorageSource.Get(key).map( |         return LocalStorageSource.Get(key).sync( | ||||||
|             str => { |             str => { | ||||||
|                 if (str === undefined) { |                 if (str === undefined) { | ||||||
|                     return defaultValue |                     return defaultValue | ||||||
|  |  | ||||||
|  | @ -33,7 +33,7 @@ export class QueryParameters { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static GetBooleanQueryParameter(key: string, deflt: boolean, documentation?: string): UIEventSource<boolean> { |     public static GetBooleanQueryParameter(key: string, deflt: boolean, documentation?: string): UIEventSource<boolean> { | ||||||
|         return QueryParameters.GetQueryParameter(key, ""+ deflt, documentation).map(str => str === "true", [], b => "" + b) |         return QueryParameters.GetQueryParameter(key, ""+ deflt, documentation).sync(str => str === "true", [], b => "" + b) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import {UIEventSource} from "../UIEventSource"; | import {Store} from "../UIEventSource"; | ||||||
| 
 | 
 | ||||||
| export interface Review { | export interface Review { | ||||||
|     comment?: string, |     comment?: string, | ||||||
|  | @ -9,5 +9,5 @@ export interface Review { | ||||||
|     /** |     /** | ||||||
|      * True if the current logged in user is the creator of this comment |      * True if the current logged in user is the creator of this comment | ||||||
|      */ |      */ | ||||||
|     made_by_user: UIEventSource<boolean> |     made_by_user: Store<boolean> | ||||||
| } | } | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| import {Translation} from "../UI/i18n/Translation"; | import {Translation} from "../UI/i18n/Translation"; | ||||||
| import {ApplicableUnitJson} from "./ThemeConfig/Json/UnitConfigJson"; | import {ApplicableUnitJson} from "./ThemeConfig/Json/UnitConfigJson"; | ||||||
| import Translations from "../UI/i18n/Translations"; | import Translations from "../UI/i18n/Translations"; | ||||||
| import {UIEventSource} from "../Logic/UIEventSource"; | import {Store, UIEventSource} from "../Logic/UIEventSource"; | ||||||
| import BaseUIElement from "../UI/BaseUIElement"; | import BaseUIElement from "../UI/BaseUIElement"; | ||||||
| import Toggle from "../UI/Input/Toggle"; | import Toggle from "../UI/Input/Toggle"; | ||||||
| 
 | 
 | ||||||
|  | @ -49,7 +49,7 @@ export class Denomination { | ||||||
|         return (this._humanSingular ?? this._human).Clone() |         return (this._humanSingular ?? this._human).Clone() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     getToggledHuman(isSingular: UIEventSource<boolean>): BaseUIElement { |     getToggledHuman(isSingular: Store<boolean>): BaseUIElement { | ||||||
|         if (this._humanSingular === undefined) { |         if (this._humanSingular === undefined) { | ||||||
|             return this.human |             return this.human | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -129,7 +129,7 @@ export default class FilterConfig { | ||||||
|             })) |             })) | ||||||
| 
 | 
 | ||||||
|             // We map the query parameter for this case
 |             // We map the query parameter for this case
 | ||||||
|             return qp.map(str => { |             return qp.sync(str => { | ||||||
|                 const parsed = Number(str) |                 const parsed = Number(str) | ||||||
|                 if (isNaN(parsed)) { |                 if (isNaN(parsed)) { | ||||||
|                     // Nope, not a correct number!
 |                     // Nope, not a correct number!
 | ||||||
|  | @ -143,7 +143,7 @@ export default class FilterConfig { | ||||||
|         const option = this.options[0] |         const option = this.options[0] | ||||||
| 
 | 
 | ||||||
|         if (option.fields.length > 0) { |         if (option.fields.length > 0) { | ||||||
|             return qp.map(str => { |             return qp.sync(str => { | ||||||
|                 // There are variables in play!
 |                 // There are variables in play!
 | ||||||
|                 // str should encode a json-hash
 |                 // str should encode a json-hash
 | ||||||
|                 try { |                 try { | ||||||
|  | @ -178,7 +178,7 @@ export default class FilterConfig { | ||||||
|             currentFilter: option.osmTags, |             currentFilter: option.osmTags, | ||||||
|             state: "true" |             state: "true" | ||||||
|         } |         } | ||||||
|         return qp.map( |         return qp.sync( | ||||||
|             str => { |             str => { | ||||||
|                 // Only a single option exists here
 |                 // Only a single option exists here
 | ||||||
|                 if (str === "true") { |                 if (str === "true") { | ||||||
|  |  | ||||||
|  | @ -65,7 +65,6 @@ class AutomationPanel extends Combine { | ||||||
|             console.warn("Triggered map on nextTileToHandle", tileIndex) |             console.warn("Triggered map on nextTileToHandle", tileIndex) | ||||||
|             const start = new Date() |             const start = new Date() | ||||||
|             return AutomationPanel.TileHandler(layoutToUse, tileIndex, layerId, tagRenderingToAutomate.tagRendering, extraCommentText, |             return AutomationPanel.TileHandler(layoutToUse, tileIndex, layerId, tagRenderingToAutomate.tagRendering, extraCommentText, | ||||||
|                 openChangeset, |  | ||||||
|                 (result, logMessage) => { |                 (result, logMessage) => { | ||||||
|                     const end = new Date() |                     const end = new Date() | ||||||
|                     const timeNeeded = (end.getTime() - start.getTime()) / 1000; |                     const timeNeeded = (end.getTime() - start.getTime()) / 1000; | ||||||
|  | @ -118,7 +117,6 @@ class AutomationPanel extends Combine { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private static TileHandler(layoutToUse: LayoutConfig, tileIndex: number, targetLayer: string, targetAction: TagRenderingConfig, extraCommentText: UIEventSource<string>, |     private static TileHandler(layoutToUse: LayoutConfig, tileIndex: number, targetLayer: string, targetAction: TagRenderingConfig, extraCommentText: UIEventSource<string>, | ||||||
|                                openChangeset: UIEventSource<number>, |  | ||||||
|                                whenDone: ((result: string, logMessage?: string) => void)): BaseUIElement { |                                whenDone: ((result: string, logMessage?: string) => void)): BaseUIElement { | ||||||
| 
 | 
 | ||||||
|         const state = new MapState(layoutToUse, {attemptLogin: false}) |         const state = new MapState(layoutToUse, {attemptLogin: false}) | ||||||
|  | @ -204,7 +202,7 @@ class AutomationPanel extends Combine { | ||||||
|                     whenDone("no-action", "Inspected " + inspected + " elements: " + log.join("; ")) |                     whenDone("no-action", "Inspected " + inspected + " elements: " + log.join("; ")) | ||||||
|                 } else { |                 } else { | ||||||
|                     state.osmConnection.AttemptLogin() |                     state.osmConnection.AttemptLogin() | ||||||
|                     state.changes.flushChanges("handled tile automatically, time to flush!", openChangeset) |                     state.changes.flushChanges("handled tile automatically, time to flush!") | ||||||
|                     whenDone("fixed", "Updated " + handled + " elements, inspected " + inspected + ": " + log.join("; ")) |                     whenDone("fixed", "Updated " + handled + " elements, inspected " + inspected + ": " + log.join("; ")) | ||||||
|                 } |                 } | ||||||
|                 return true; |                 return true; | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import BaseUIElement from "../BaseUIElement"; | import BaseUIElement from "../BaseUIElement"; | ||||||
| import {VariableUiElement} from "./VariableUIElement"; | import {VariableUiElement} from "./VariableUIElement"; | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {Stores, UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import Loading from "./Loading"; | import Loading from "./Loading"; | ||||||
| 
 | 
 | ||||||
| export default class AsyncLazy extends BaseUIElement { | export default class AsyncLazy extends BaseUIElement { | ||||||
|  | @ -15,7 +15,7 @@ export default class AsyncLazy extends BaseUIElement { | ||||||
|         // The caching of the BaseUIElement will guarantee that _f will only be called once
 |         // The caching of the BaseUIElement will guarantee that _f will only be called once
 | ||||||
| 
 | 
 | ||||||
|         return new VariableUiElement( |         return new VariableUiElement( | ||||||
|             UIEventSource.FromPromise(this._f()).map(el => { |             Stores.FromPromise(this._f()).map(el => { | ||||||
|                 if (el === undefined) { |                 if (el === undefined) { | ||||||
|                     return new Loading() |                     return new Loading() | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|  | @ -1,14 +1,14 @@ | ||||||
| import Translations from "../i18n/Translations"; | import Translations from "../i18n/Translations"; | ||||||
| import BaseUIElement from "../BaseUIElement"; | import BaseUIElement from "../BaseUIElement"; | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {Store, UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| export default class Link extends BaseUIElement { | export default class Link extends BaseUIElement { | ||||||
|     private readonly _href: string | UIEventSource<string>; |     private readonly _href: string | Store<string>; | ||||||
|     private readonly _embeddedShow: BaseUIElement; |     private readonly _embeddedShow: BaseUIElement; | ||||||
|     private readonly _newTab: boolean; |     private readonly _newTab: boolean; | ||||||
| 
 | 
 | ||||||
|     constructor(embeddedShow: BaseUIElement | string, href: string | UIEventSource<string>, newTab: boolean = false) { |     constructor(embeddedShow: BaseUIElement | string, href: string | Store<string>, newTab: boolean = false) { | ||||||
|         super(); |         super(); | ||||||
|         this._embeddedShow = Translations.W(embeddedShow); |         this._embeddedShow = Translations.W(embeddedShow); | ||||||
|         this._href = href; |         this._href = href; | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ import Combine from "./Combine"; | ||||||
| import BaseUIElement from "../BaseUIElement"; | import BaseUIElement from "../BaseUIElement"; | ||||||
| import Link from "./Link"; | import Link from "./Link"; | ||||||
| import Img from "./Img"; | import Img from "./Img"; | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {Store, UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import {UIElement} from "../UIElement"; | import {UIElement} from "../UIElement"; | ||||||
| import {VariableUiElement} from "./VariableUIElement"; | import {VariableUiElement} from "./VariableUIElement"; | ||||||
| import Lazy from "./Lazy"; | import Lazy from "./Lazy"; | ||||||
|  | @ -13,11 +13,11 @@ import Loading from "./Loading"; | ||||||
| export class SubtleButton extends UIElement { | export class SubtleButton extends UIElement { | ||||||
|     private readonly imageUrl: string | BaseUIElement; |     private readonly imageUrl: string | BaseUIElement; | ||||||
|     private readonly message: string | BaseUIElement; |     private readonly message: string | BaseUIElement; | ||||||
|     private readonly options: { url?: string | UIEventSource<string>; newTab?: boolean ; imgSize?: string}; |     private readonly options: { url?: string | Store<string>; newTab?: boolean ; imgSize?: string}; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     constructor(imageUrl: string | BaseUIElement, message: string | BaseUIElement, options: {  |     constructor(imageUrl: string | BaseUIElement, message: string | BaseUIElement, options: {  | ||||||
|         url?: string | UIEventSource<string>,  |         url?: string | Store<string>,  | ||||||
|         newTab?: boolean, |         newTab?: boolean, | ||||||
|         imgSize?: "h-11 w-11" | string |         imgSize?: "h-11 w-11" | string | ||||||
|     } = undefined) { |     } = undefined) { | ||||||
|  |  | ||||||
|  | @ -1,11 +1,11 @@ | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {Store} from "../../Logic/UIEventSource"; | ||||||
| import BaseUIElement from "../BaseUIElement"; | import BaseUIElement from "../BaseUIElement"; | ||||||
| import Combine from "./Combine"; | import Combine from "./Combine"; | ||||||
| 
 | 
 | ||||||
| export class VariableUiElement extends BaseUIElement { | export class VariableUiElement extends BaseUIElement { | ||||||
|     private readonly _contents: UIEventSource<string | BaseUIElement | BaseUIElement[]>; |     private readonly _contents: Store<string | BaseUIElement | BaseUIElement[]>; | ||||||
| 
 | 
 | ||||||
|     constructor(contents: UIEventSource<string | BaseUIElement | BaseUIElement[]>) { |     constructor(contents: Store<string | BaseUIElement | BaseUIElement[]>) { | ||||||
|         super(); |         super(); | ||||||
|         this._contents = contents; |         this._contents = contents; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -2,12 +2,12 @@ import {Utils} from "../../Utils"; | ||||||
| import {FixedInputElement} from "../Input/FixedInputElement"; | import {FixedInputElement} from "../Input/FixedInputElement"; | ||||||
| import {RadioButton} from "../Input/RadioButton"; | import {RadioButton} from "../Input/RadioButton"; | ||||||
| import {VariableUiElement} from "../Base/VariableUIElement"; | import {VariableUiElement} from "../Base/VariableUIElement"; | ||||||
| import Toggle from "../Input/Toggle"; | import Toggle, {ClickableToggle} from "../Input/Toggle"; | ||||||
| import Combine from "../Base/Combine"; | import Combine from "../Base/Combine"; | ||||||
| import Translations from "../i18n/Translations"; | import Translations from "../i18n/Translations"; | ||||||
| import {Translation} from "../i18n/Translation"; | import {Translation} from "../i18n/Translation"; | ||||||
| import Svg from "../../Svg"; | import Svg from "../../Svg"; | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {ImmutableStore, Store, UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import BaseUIElement from "../BaseUIElement"; | import BaseUIElement from "../BaseUIElement"; | ||||||
| import State from "../../State"; | import State from "../../State"; | ||||||
| import FilteredLayer, {FilterState} from "../../Models/FilteredLayer"; | import FilteredLayer, {FilterState} from "../../Models/FilteredLayer"; | ||||||
|  | @ -20,7 +20,6 @@ import {QueryParameters} from "../../Logic/Web/QueryParameters"; | ||||||
| import {TagUtils} from "../../Logic/Tags/TagUtils"; | import {TagUtils} from "../../Logic/Tags/TagUtils"; | ||||||
| import {InputElement} from "../Input/InputElement"; | import {InputElement} from "../Input/InputElement"; | ||||||
| import {DropDown} from "../Input/DropDown"; | import {DropDown} from "../Input/DropDown"; | ||||||
| import {UIElement} from "../UIElement"; |  | ||||||
| 
 | 
 | ||||||
| export default class FilterView extends VariableUiElement { | export default class FilterView extends VariableUiElement { | ||||||
|     constructor(filteredLayer: UIEventSource<FilteredLayer[]>,  |     constructor(filteredLayer: UIEventSource<FilteredLayer[]>,  | ||||||
|  | @ -180,7 +179,8 @@ export default class FilterView extends VariableUiElement { | ||||||
| 
 | 
 | ||||||
|         const filter = filterConfig.options[0] |         const filter = filterConfig.options[0] | ||||||
|         const mappings = new Map<string, BaseUIElement>() |         const mappings = new Map<string, BaseUIElement>() | ||||||
|         let allValid = new UIEventSource(true) |         let allValid: Store<boolean> = new ImmutableStore(true) | ||||||
|  |         var allFields: InputElement<string>[] = [] | ||||||
|         const properties = new UIEventSource<any>({}) |         const properties = new UIEventSource<any>({}) | ||||||
|         for (const {name, type} of filter.fields) { |         for (const {name, type} of filter.fields) { | ||||||
|             const value = QueryParameters.GetQueryParameter("filter-" + filterConfig.id + "-" + name, "", "Value for filter " + filterConfig.id) |             const value = QueryParameters.GetQueryParameter("filter-" + filterConfig.id + "-" + name, "", "Value for filter " + filterConfig.id) | ||||||
|  | @ -193,10 +193,11 @@ export default class FilterView extends VariableUiElement { | ||||||
|                 properties.data[name] = v.toLowerCase(); |                 properties.data[name] = v.toLowerCase(); | ||||||
|                 properties.ping() |                 properties.ping() | ||||||
|             }) |             }) | ||||||
|  |             allFields.push(field) | ||||||
|             allValid = allValid.map(previous => previous && field.IsValid(stable.data) && stable.data !== "", [stable]) |             allValid = allValid.map(previous => previous && field.IsValid(stable.data) && stable.data !== "", [stable]) | ||||||
|         } |         } | ||||||
|         const tr = new SubstitutedTranslation(filter.question, new UIEventSource<any>({id: filterConfig.id}), State.state, mappings) |         const tr = new SubstitutedTranslation(filter.question, new UIEventSource<any>({id: filterConfig.id}), State.state, mappings) | ||||||
|         const trigger: UIEventSource<FilterState> = allValid.map(isValid => { |         const trigger: Store<FilterState> = allValid.map(isValid => { | ||||||
|             if (!isValid) { |             if (!isValid) { | ||||||
|                 return undefined |                 return undefined | ||||||
|             } |             } | ||||||
|  | @ -221,8 +222,16 @@ export default class FilterView extends VariableUiElement { | ||||||
|                 state: JSON.stringify(props) |                 state: JSON.stringify(props) | ||||||
|             } |             } | ||||||
|         }, [properties]) |         }, [properties]) | ||||||
|  |          | ||||||
|  |         const settableFilter = new UIEventSource<FilterState>(undefined) | ||||||
|  |         trigger.addCallbackAndRun(state => settableFilter.setData(state)) | ||||||
|  |         settableFilter.addCallback(state => { | ||||||
|  |             if(state.currentFilter === undefined){ | ||||||
|  |                 allFields.forEach(f => f.GetValue().setData(undefined)); | ||||||
|  |             } | ||||||
|  |         }) | ||||||
| 
 | 
 | ||||||
|         return [tr, trigger]; |         return [tr, settableFilter]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private static createCheckboxFilter(filterConfig: FilterConfig): [BaseUIElement, UIEventSource<FilterState>] { |     private static createCheckboxFilter(filterConfig: FilterConfig): [BaseUIElement, UIEventSource<FilterState>] { | ||||||
|  | @ -231,14 +240,14 @@ export default class FilterView extends VariableUiElement { | ||||||
|         const icon = Svg.checkbox_filled_svg().SetClass("block mr-2 w-6"); |         const icon = Svg.checkbox_filled_svg().SetClass("block mr-2 w-6"); | ||||||
|         const iconUnselected = Svg.checkbox_empty_svg().SetClass("block mr-2 w-6"); |         const iconUnselected = Svg.checkbox_empty_svg().SetClass("block mr-2 w-6"); | ||||||
| 
 | 
 | ||||||
|         const toggle = new Toggle( |         const toggle = new ClickableToggle( | ||||||
|             new Combine([icon, option.question.Clone().SetClass("block")]).SetClass("flex"), |             new Combine([icon, option.question.Clone().SetClass("block")]).SetClass("flex"), | ||||||
|             new Combine([iconUnselected, option.question.Clone().SetClass("block")]).SetClass("flex") |             new Combine([iconUnselected, option.question.Clone().SetClass("block")]).SetClass("flex") | ||||||
|         ) |         ) | ||||||
|             .ToggleOnClick() |             .ToggleOnClick() | ||||||
|             .SetClass("block m-1") |             .SetClass("block m-1") | ||||||
| 
 | 
 | ||||||
|         return [toggle, toggle.isEnabled.map(enabled => enabled ? { |         return [toggle, toggle.isEnabled.sync(enabled => enabled ? { | ||||||
|                 currentFilter: option.osmTags, |                 currentFilter: option.osmTags, | ||||||
|                 state: "true" |                 state: "true" | ||||||
|             } : undefined, [], |             } : undefined, [], | ||||||
|  | @ -272,7 +281,7 @@ export default class FilterView extends VariableUiElement { | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return [filterPicker, |         return [filterPicker, | ||||||
|             filterPicker.GetValue().map( |             filterPicker.GetValue().sync( | ||||||
|                 i => values[i], |                 i => values[i], | ||||||
|                 [], |                 [], | ||||||
|                 selected => { |                 selected => { | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| import {VariableUiElement} from "../Base/VariableUIElement"; | import {VariableUiElement} from "../Base/VariableUIElement"; | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {Store, UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import Table from "../Base/Table"; | import Table from "../Base/Table"; | ||||||
| import Combine from "../Base/Combine"; | import Combine from "../Base/Combine"; | ||||||
| import {FixedUiElement} from "../Base/FixedUiElement"; | import {FixedUiElement} from "../Base/FixedUiElement"; | ||||||
|  | @ -19,7 +19,7 @@ export default class Histogram<T> extends VariableUiElement { | ||||||
|         "#fa61fa" |         "#fa61fa" | ||||||
|     ] |     ] | ||||||
| 
 | 
 | ||||||
|     constructor(values: UIEventSource<string[]>, |     constructor(values: Store<string[]>, | ||||||
|                 title: string | BaseUIElement, |                 title: string | BaseUIElement, | ||||||
|                 countTitle: string | BaseUIElement, |                 countTitle: string | BaseUIElement, | ||||||
|                 options?: { |                 options?: { | ||||||
|  |  | ||||||
|  | @ -7,7 +7,7 @@ import * as personal from "../../assets/themes/personal/personal.json" | ||||||
| import Constants from "../../Models/Constants"; | import Constants from "../../Models/Constants"; | ||||||
| import BaseUIElement from "../BaseUIElement"; | import BaseUIElement from "../BaseUIElement"; | ||||||
| import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; | import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {ImmutableStore, Store, Stores, UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import Loc from "../../Models/Loc"; | import Loc from "../../Models/Loc"; | ||||||
| import {OsmConnection} from "../../Logic/Osm/OsmConnection"; | import {OsmConnection} from "../../Logic/Osm/OsmConnection"; | ||||||
| import UserRelatedState from "../../Logic/State/UserRelatedState"; | import UserRelatedState from "../../Logic/State/UserRelatedState"; | ||||||
|  | @ -117,7 +117,7 @@ export default class MoreScreen extends Combine { | ||||||
|     private static createUrlFor(layout: { id: string, definition?: string }, |     private static createUrlFor(layout: { id: string, definition?: string }, | ||||||
|                                 isCustom: boolean, |                                 isCustom: boolean, | ||||||
|                                 state?: { locationControl?: UIEventSource<{ lat, lon, zoom }>, layoutToUse?: { id } } |                                 state?: { locationControl?: UIEventSource<{ lat, lon, zoom }>, layoutToUse?: { id } } | ||||||
|     ): UIEventSource<string> { |     ): Store<string> { | ||||||
|         if (layout === undefined) { |         if (layout === undefined) { | ||||||
|             return undefined; |             return undefined; | ||||||
|         } |         } | ||||||
|  | @ -163,7 +163,7 @@ export default class MoreScreen extends Combine { | ||||||
|                 .map(part => part[0] + "=" + part[1]) |                 .map(part => part[0] + "=" + part[1]) | ||||||
|                 .join("&") |                 .join("&") | ||||||
|             return `${linkPrefix}${params}${hash}`; |             return `${linkPrefix}${params}${hash}`; | ||||||
|         }) ?? new UIEventSource<string>(`${linkPrefix}`) |         }) ?? new ImmutableStore<string>(`${linkPrefix}`) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     } |     } | ||||||
|  | @ -237,7 +237,7 @@ export default class MoreScreen extends Combine { | ||||||
|     private static createUnofficialThemeList(buttonClass: string, state: UserRelatedState, themeListClasses: string, search: UIEventSource<string>): BaseUIElement { |     private static createUnofficialThemeList(buttonClass: string, state: UserRelatedState, themeListClasses: string, search: UIEventSource<string>): BaseUIElement { | ||||||
|         const prefix = "mapcomplete-unofficial-theme-"; |         const prefix = "mapcomplete-unofficial-theme-"; | ||||||
| 
 | 
 | ||||||
|         var currentIds: UIEventSource<string[]> = state.osmConnection.preferencesHandler.preferences |         var currentIds: Store<string[]> = state.osmConnection.preferencesHandler.preferences | ||||||
|             .map(allPreferences => { |             .map(allPreferences => { | ||||||
|                 const ids: string[] = [] |                 const ids: string[] = [] | ||||||
| 
 | 
 | ||||||
|  | @ -250,7 +250,7 @@ export default class MoreScreen extends Combine { | ||||||
|                 return ids |                 return ids | ||||||
|             }); |             }); | ||||||
| 
 | 
 | ||||||
|         var stableIds = UIEventSource.ListStabilized<string>(currentIds) |         var stableIds = Stores.ListStabilized<string>(currentIds) | ||||||
|         return new VariableUiElement( |         return new VariableUiElement( | ||||||
|             stableIds.map(ids => { |             stableIds.map(ids => { | ||||||
|                 const allThemes: { element: BaseUIElement, predicate?: (s: string) => boolean }[] = [] |                 const allThemes: { element: BaseUIElement, predicate?: (s: string) => boolean }[] = [] | ||||||
|  |  | ||||||
|  | @ -2,9 +2,8 @@ import {VariableUiElement} from "../Base/VariableUIElement"; | ||||||
| import {Translation} from "../i18n/Translation"; | import {Translation} from "../i18n/Translation"; | ||||||
| import Svg from "../../Svg"; | import Svg from "../../Svg"; | ||||||
| import Combine from "../Base/Combine"; | import Combine from "../Base/Combine"; | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {Store, UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import {Utils} from "../../Utils"; | import {Utils} from "../../Utils"; | ||||||
| import Toggle from "../Input/Toggle"; |  | ||||||
| import Translations from "../i18n/Translations"; | import Translations from "../i18n/Translations"; | ||||||
| import BaseUIElement from "../BaseUIElement"; | import BaseUIElement from "../BaseUIElement"; | ||||||
| import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; | import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; | ||||||
|  | @ -13,7 +12,7 @@ import Loc from "../../Models/Loc"; | ||||||
| import BaseLayer from "../../Models/BaseLayer"; | import BaseLayer from "../../Models/BaseLayer"; | ||||||
| import FilteredLayer from "../../Models/FilteredLayer"; | import FilteredLayer from "../../Models/FilteredLayer"; | ||||||
| import {InputElement} from "../Input/InputElement"; | import {InputElement} from "../Input/InputElement"; | ||||||
| import CheckBoxes, {CheckBox} from "../Input/Checkboxes"; | import {CheckBox} from "../Input/Checkboxes"; | ||||||
| import {SubtleButton} from "../Base/SubtleButton"; | import {SubtleButton} from "../Base/SubtleButton"; | ||||||
| import LZString from "lz-string"; | import LZString from "lz-string"; | ||||||
| 
 | 
 | ||||||
|  | @ -24,7 +23,7 @@ export default class ShareScreen extends Combine { | ||||||
|         const tr = Translations.t.general.sharescreen; |         const tr = Translations.t.general.sharescreen; | ||||||
| 
 | 
 | ||||||
|         const optionCheckboxes: InputElement<boolean>[] = [] |         const optionCheckboxes: InputElement<boolean>[] = [] | ||||||
|         const optionParts: (UIEventSource<string>)[] = []; |         const optionParts: (Store<string>)[] = []; | ||||||
| 
 | 
 | ||||||
|         const includeLocation = new CheckBox(tr.fsIncludeCurrentLocation, true) |         const includeLocation = new CheckBox(tr.fsIncludeCurrentLocation, true) | ||||||
|         optionCheckboxes.push(includeLocation); |         optionCheckboxes.push(includeLocation); | ||||||
|  |  | ||||||
|  | @ -124,7 +124,7 @@ export default class TranslatorsPanel extends Toggle { | ||||||
|         const completeness = new Map<string, number>() |         const completeness = new Map<string, number>() | ||||||
|         const untranslated = new Map<string, string[]>() |         const untranslated = new Map<string, string[]>() | ||||||
| 
 | 
 | ||||||
|         Utils.WalkObject(layout, (o, path) => { |         Utils.WalkObject(layout, (o) => { | ||||||
|             const translation = <Translation><any>o; |             const translation = <Translation><any>o; | ||||||
|             if (translation.translations["*"] !== undefined) { |             if (translation.translations["*"] !== undefined) { | ||||||
|                 return |                 return | ||||||
|  |  | ||||||
|  | @ -2,13 +2,13 @@ import Combine from "../Base/Combine"; | ||||||
| import Translations from "../i18n/Translations"; | import Translations from "../i18n/Translations"; | ||||||
| import BaseUIElement from "../BaseUIElement"; | import BaseUIElement from "../BaseUIElement"; | ||||||
| import {VariableUiElement} from "../Base/VariableUIElement"; | import {VariableUiElement} from "../Base/VariableUIElement"; | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {Store, UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import {LicenseInfo} from "../../Logic/ImageProviders/LicenseInfo"; | import {LicenseInfo} from "../../Logic/ImageProviders/LicenseInfo"; | ||||||
| import {FixedUiElement} from "../Base/FixedUiElement"; | import {FixedUiElement} from "../Base/FixedUiElement"; | ||||||
| 
 | 
 | ||||||
| export default class Attribution extends VariableUiElement { | export default class Attribution extends VariableUiElement { | ||||||
| 
 | 
 | ||||||
|     constructor(license: UIEventSource<LicenseInfo>, icon: BaseUIElement, date?: Date) { |     constructor(license: Store<LicenseInfo>, icon: BaseUIElement, date?: Date) { | ||||||
|         if (license === undefined) { |         if (license === undefined) { | ||||||
|             throw "No license source given in the attribution element" |             throw "No license source given in the attribution element" | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {Store} from "../../Logic/UIEventSource"; | ||||||
| import Translations from "../i18n/Translations"; | import Translations from "../i18n/Translations"; | ||||||
| import Toggle from "../Input/Toggle"; | import Toggle, {ClickableToggle} from "../Input/Toggle"; | ||||||
| import Combine from "../Base/Combine"; | import Combine from "../Base/Combine"; | ||||||
| import Svg from "../../Svg"; | import Svg from "../../Svg"; | ||||||
| import {Tag} from "../../Logic/Tags/Tag"; | import {Tag} from "../../Logic/Tags/Tag"; | ||||||
|  | @ -11,7 +11,7 @@ import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; | ||||||
| 
 | 
 | ||||||
| export default class DeleteImage extends Toggle { | export default class DeleteImage extends Toggle { | ||||||
| 
 | 
 | ||||||
|     constructor(key: string, tags: UIEventSource<any>, state: { layoutToUse: LayoutConfig, changes?: Changes, osmConnection?: OsmConnection }) { |     constructor(key: string, tags: Store<any>, state: { layoutToUse: LayoutConfig, changes?: Changes, osmConnection?: OsmConnection }) { | ||||||
|         const oldValue = tags.data[key] |         const oldValue = tags.data[key] | ||||||
|         const isDeletedBadge = Translations.t.image.isDeleted.Clone() |         const isDeletedBadge = Translations.t.image.isDeleted.Clone() | ||||||
|             .SetClass("rounded-full p-1") |             .SetClass("rounded-full p-1") | ||||||
|  | @ -37,7 +37,7 @@ export default class DeleteImage extends Toggle { | ||||||
| 
 | 
 | ||||||
|         const cancelButton = Translations.t.general.cancel.Clone().SetClass("bg-white pl-4 pr-4").SetStyle("border-bottom-left-radius:30rem; border-bottom-right-radius: 30rem;"); |         const cancelButton = Translations.t.general.cancel.Clone().SetClass("bg-white pl-4 pr-4").SetStyle("border-bottom-left-radius:30rem; border-bottom-right-radius: 30rem;"); | ||||||
|         const openDelete = Svg.delete_icon_svg().SetStyle("width: 2em; height: 2em; display:block;") |         const openDelete = Svg.delete_icon_svg().SetStyle("width: 2em; height: 2em; display:block;") | ||||||
|         const deleteDialog = new Toggle( |         const deleteDialog = new ClickableToggle( | ||||||
|             new Combine([ |             new Combine([ | ||||||
|                 deleteButton, |                 deleteButton, | ||||||
|                 cancelButton |                 cancelButton | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| import {SlideShow} from "./SlideShow"; | import {SlideShow} from "./SlideShow"; | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {Store} from "../../Logic/UIEventSource"; | ||||||
| import Combine from "../Base/Combine"; | import Combine from "../Base/Combine"; | ||||||
| import DeleteImage from "./DeleteImage"; | import DeleteImage from "./DeleteImage"; | ||||||
| import {AttributedImage} from "./AttributedImage"; | import {AttributedImage} from "./AttributedImage"; | ||||||
|  | @ -12,8 +12,8 @@ import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; | ||||||
| 
 | 
 | ||||||
| export class ImageCarousel extends Toggle { | export class ImageCarousel extends Toggle { | ||||||
| 
 | 
 | ||||||
|     constructor(images: UIEventSource<{ key: string, url: string, provider: ImageProvider }[]>, |     constructor(images: Store<{ key: string, url: string, provider: ImageProvider }[]>, | ||||||
|                 tags: UIEventSource<any>, |                 tags: Store<any>, | ||||||
|                 state: { osmConnection?: OsmConnection, changes?: Changes, layoutToUse: LayoutConfig }) { |                 state: { osmConnection?: OsmConnection, changes?: Changes, layoutToUse: LayoutConfig }) { | ||||||
|         const uiElements = images.map((imageURLS: { key: string, url: string, provider: ImageProvider }[]) => { |         const uiElements = images.map((imageURLS: { key: string, url: string, provider: ImageProvider }[]) => { | ||||||
|             const uiElements: BaseUIElement[] = []; |             const uiElements: BaseUIElement[] = []; | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {Store, UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import Combine from "../Base/Combine"; | import Combine from "../Base/Combine"; | ||||||
| import Translations from "../i18n/Translations"; | import Translations from "../i18n/Translations"; | ||||||
| import Svg from "../../Svg"; | import Svg from "../../Svg"; | ||||||
|  | @ -22,12 +22,12 @@ export class ImageUploadFlow extends Toggle { | ||||||
| 
 | 
 | ||||||
|     private static readonly uploadCountsPerId = new Map<string, UIEventSource<number>>() |     private static readonly uploadCountsPerId = new Map<string, UIEventSource<number>>() | ||||||
| 
 | 
 | ||||||
|     constructor(tagsSource: UIEventSource<any>, |     constructor(tagsSource: Store<any>, | ||||||
|                 state: { |                 state: { | ||||||
|                     osmConnection: OsmConnection; |                     osmConnection: OsmConnection; | ||||||
|                     layoutToUse: LayoutConfig; |                     layoutToUse: LayoutConfig; | ||||||
|                     changes: Changes, |                     changes: Changes, | ||||||
|                     featureSwitchUserbadge: UIEventSource<boolean>; |                     featureSwitchUserbadge: Store<boolean>; | ||||||
|                 }, |                 }, | ||||||
|                 imagePrefix: string = "image", text: string = undefined) { |                 imagePrefix: string = "image", text: string = undefined) { | ||||||
|         const perId = ImageUploadFlow.uploadCountsPerId |         const perId = ImageUploadFlow.uploadCountsPerId | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {Store, UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import BaseUIElement from "../BaseUIElement"; | import BaseUIElement from "../BaseUIElement"; | ||||||
| import {Utils} from "../../Utils"; | import {Utils} from "../../Utils"; | ||||||
| import Combine from "../Base/Combine"; | import Combine from "../Base/Combine"; | ||||||
|  | @ -6,9 +6,9 @@ import Combine from "../Base/Combine"; | ||||||
| export class SlideShow extends BaseUIElement { | export class SlideShow extends BaseUIElement { | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     private readonly embeddedElements: UIEventSource<BaseUIElement[]>; |     private readonly embeddedElements: Store<BaseUIElement[]>; | ||||||
| 
 | 
 | ||||||
|     constructor(embeddedElements: UIEventSource<BaseUIElement[]>) { |     constructor(embeddedElements: Store<BaseUIElement[]>) { | ||||||
|         super() |         super() | ||||||
|         this.embeddedElements = embeddedElements; |         this.embeddedElements = embeddedElements; | ||||||
|         this.SetStyle("scroll-snap-type: x mandatory; overflow-x: auto") |         this.SetStyle("scroll-snap-type: x mandatory; overflow-x: auto") | ||||||
|  |  | ||||||
|  | @ -1,12 +1,11 @@ | ||||||
| import Combine from "../Base/Combine"; | import Combine from "../Base/Combine"; | ||||||
| import {FlowStep} from "./FlowStep"; | import {FlowStep} from "./FlowStep"; | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {Store} from "../../Logic/UIEventSource"; | ||||||
| import ValidatedTextField from "../Input/ValidatedTextField"; | import ValidatedTextField from "../Input/ValidatedTextField"; | ||||||
| import {LocalStorageSource} from "../../Logic/Web/LocalStorageSource"; | import {LocalStorageSource} from "../../Logic/Web/LocalStorageSource"; | ||||||
| import Title from "../Base/Title"; | import Title from "../Base/Title"; | ||||||
| import {VariableUiElement} from "../Base/VariableUIElement"; | import {VariableUiElement} from "../Base/VariableUIElement"; | ||||||
| import Translations from "../i18n/Translations"; | import Translations from "../i18n/Translations"; | ||||||
| import {FixedUiElement} from "../Base/FixedUiElement"; |  | ||||||
| import {SubtleButton} from "../Base/SubtleButton"; | import {SubtleButton} from "../Base/SubtleButton"; | ||||||
| import Svg from "../../Svg"; | import Svg from "../../Svg"; | ||||||
| import {Utils} from "../../Utils"; | import {Utils} from "../../Utils"; | ||||||
|  | @ -19,14 +18,14 @@ export class AskMetadata extends Combine implements FlowStep<{ | ||||||
|     theme: string |     theme: string | ||||||
| }> { | }> { | ||||||
| 
 | 
 | ||||||
|     public readonly Value: UIEventSource<{ |     public readonly Value: Store<{ | ||||||
|         features: any[], |         features: any[], | ||||||
|         wikilink: string, |         wikilink: string, | ||||||
|         intro: string, |         intro: string, | ||||||
|         source: string, |         source: string, | ||||||
|         theme: string |         theme: string | ||||||
|     }>; |     }>; | ||||||
|     public readonly IsValid: UIEventSource<boolean>; |     public readonly IsValid: Store<boolean>; | ||||||
| 
 | 
 | ||||||
|     constructor(params: ({ features: any[], theme: string })) { |     constructor(params: ({ features: any[], theme: string })) { | ||||||
|         const t = Translations.t.importHelper.askMetadata |         const t = Translations.t.importHelper.askMetadata | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ import Combine from "../Base/Combine"; | ||||||
| import {FlowStep} from "./FlowStep"; | import {FlowStep} from "./FlowStep"; | ||||||
| import {BBox} from "../../Logic/BBox"; | import {BBox} from "../../Logic/BBox"; | ||||||
| import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; | import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {Store, UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import CreateNoteImportLayer from "../../Models/ThemeConfig/Conversion/CreateNoteImportLayer"; | import CreateNoteImportLayer from "../../Models/ThemeConfig/Conversion/CreateNoteImportLayer"; | ||||||
| import FilteredLayer, {FilterState} from "../../Models/FilteredLayer"; | import FilteredLayer, {FilterState} from "../../Models/FilteredLayer"; | ||||||
| import GeoJsonSource from "../../Logic/FeatureSource/Sources/GeoJsonSource"; | import GeoJsonSource from "../../Logic/FeatureSource/Sources/GeoJsonSource"; | ||||||
|  | @ -17,7 +17,6 @@ import * as import_candidate from "../../assets/layers/import_candidate/import_c | ||||||
| import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"; | import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"; | ||||||
| import Title from "../Base/Title"; | import Title from "../Base/Title"; | ||||||
| import Loading from "../Base/Loading"; | import Loading from "../Base/Loading"; | ||||||
| import {FixedUiElement} from "../Base/FixedUiElement"; |  | ||||||
| import {VariableUiElement} from "../Base/VariableUIElement"; | import {VariableUiElement} from "../Base/VariableUIElement"; | ||||||
| import * as known_layers from "../../assets/generated/known_layers.json" | import * as known_layers from "../../assets/generated/known_layers.json" | ||||||
| import {LayerConfigJson} from "../../Models/ThemeConfig/Json/LayerConfigJson"; | import {LayerConfigJson} from "../../Models/ThemeConfig/Json/LayerConfigJson"; | ||||||
|  | @ -28,8 +27,8 @@ import Translations from "../i18n/Translations"; | ||||||
|  */ |  */ | ||||||
| export class CompareToAlreadyExistingNotes extends Combine implements FlowStep<{ bbox: BBox, layer: LayerConfig, features: any[], theme: string }> { | export class CompareToAlreadyExistingNotes extends Combine implements FlowStep<{ bbox: BBox, layer: LayerConfig, features: any[], theme: string }> { | ||||||
| 
 | 
 | ||||||
|     public IsValid: UIEventSource<boolean> |     public IsValid: Store<boolean> | ||||||
|     public Value: UIEventSource<{ bbox: BBox, layer: LayerConfig, features: any[], theme: string }> |     public Value: Store<{ bbox: BBox, layer: LayerConfig, features: any[], theme: string }> | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     constructor(state, params: { bbox: BBox, layer: LayerConfig, features: any[], theme: string }) { |     constructor(state, params: { bbox: BBox, layer: LayerConfig, features: any[], theme: string }) { | ||||||
|  | @ -94,7 +93,7 @@ export class CompareToAlreadyExistingNotes extends Combine implements FlowStep<{ | ||||||
|             state, |             state, | ||||||
|             zoomToFeatures: true, |             zoomToFeatures: true, | ||||||
|             leafletMap: comparisonMap.leafletMap, |             leafletMap: comparisonMap.leafletMap, | ||||||
|             features: new StaticFeatureSource(partitionedImportPoints.map(p => p.hasNearby), false), |             features: new StaticFeatureSource(partitionedImportPoints.map(p => p.hasNearby)), | ||||||
|             popup: (tags, layer) => new FeatureInfoBox(tags, layer, state) |             popup: (tags, layer) => new FeatureInfoBox(tags, layer, state) | ||||||
|         }) |         }) | ||||||
| 
 | 
 | ||||||
|  | @ -103,7 +102,8 @@ export class CompareToAlreadyExistingNotes extends Combine implements FlowStep<{ | ||||||
|             new VariableUiElement( |             new VariableUiElement( | ||||||
|                 alreadyOpenImportNotes.features.map(notesWithImport => { |                 alreadyOpenImportNotes.features.map(notesWithImport => { | ||||||
|                     if (allNotesWithinBbox.state.data !== undefined && allNotesWithinBbox.state.data["error"] !== undefined) { |                     if (allNotesWithinBbox.state.data !== undefined && allNotesWithinBbox.state.data["error"] !== undefined) { | ||||||
|                         t.loadingFailed.Subs(allNotesWithinBbox.state.data) |                         const error = allNotesWithinBbox.state.data["error"] | ||||||
|  |                         t.loadingFailed.Subs({error}) | ||||||
|                     } |                     } | ||||||
|                     if (allNotesWithinBbox.features.data === undefined || allNotesWithinBbox.features.data.length === 0) { |                     if (allNotesWithinBbox.features.data === undefined || allNotesWithinBbox.features.data.length === 0) { | ||||||
|                         return new Loading(t.loading) |                         return new Loading(t.loading) | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import Combine from "../Base/Combine"; | import Combine from "../Base/Combine"; | ||||||
| import {FlowStep} from "./FlowStep"; | import {FlowStep} from "./FlowStep"; | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {Store, UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import Link from "../Base/Link"; | import Link from "../Base/Link"; | ||||||
| import CheckBoxes from "../Input/Checkboxes"; | import CheckBoxes from "../Input/Checkboxes"; | ||||||
| import Title from "../Base/Title"; | import Title from "../Base/Title"; | ||||||
|  | @ -8,8 +8,8 @@ import Translations from "../i18n/Translations"; | ||||||
| 
 | 
 | ||||||
| export class ConfirmProcess extends Combine implements FlowStep<{ features: any[], theme: string }> { | export class ConfirmProcess extends Combine implements FlowStep<{ features: any[], theme: string }> { | ||||||
| 
 | 
 | ||||||
|     public IsValid: UIEventSource<boolean> |     public IsValid: Store<boolean> | ||||||
|     public Value: UIEventSource<{ features: any[], theme: string }> |     public Value: Store<{ features: any[], theme: string }> | ||||||
| 
 | 
 | ||||||
|     constructor(v: { features: any[], theme: string }) { |     constructor(v: { features: any[], theme: string }) { | ||||||
|         const t = Translations.t.importHelper.confirmProcess; |         const t = Translations.t.importHelper.confirmProcess; | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; | ||||||
| import Combine from "../Base/Combine"; | import Combine from "../Base/Combine"; | ||||||
| import Title from "../Base/Title"; | import Title from "../Base/Title"; | ||||||
| import {Overpass} from "../../Logic/Osm/Overpass"; | import {Overpass} from "../../Logic/Osm/Overpass"; | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {Store, UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import Constants from "../../Models/Constants"; | import Constants from "../../Models/Constants"; | ||||||
| import RelationsTracker from "../../Logic/Osm/RelationsTracker"; | import RelationsTracker from "../../Logic/Osm/RelationsTracker"; | ||||||
| import {VariableUiElement} from "../Base/VariableUIElement"; | import {VariableUiElement} from "../Base/VariableUIElement"; | ||||||
|  | @ -35,7 +35,7 @@ import Translations from "../i18n/Translations"; | ||||||
| export default class ConflationChecker extends Combine implements FlowStep<{ features: any[], theme: string }> { | export default class ConflationChecker extends Combine implements FlowStep<{ features: any[], theme: string }> { | ||||||
| 
 | 
 | ||||||
|     public readonly IsValid |     public readonly IsValid | ||||||
|     public readonly Value: UIEventSource<{ features: any[], theme: string }> |     public readonly Value: Store<{ features: any[], theme: string }> | ||||||
| 
 | 
 | ||||||
|     constructor( |     constructor( | ||||||
|         state, |         state, | ||||||
|  | @ -89,7 +89,7 @@ export default class ConflationChecker extends Combine implements FlowStep<{ fea | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         const geojson: UIEventSource<any> = fromLocalStorage.map(d => { |         const geojson: Store<any> = fromLocalStorage.map(d => { | ||||||
|             if (d === undefined) { |             if (d === undefined) { | ||||||
|                 return undefined |                 return undefined | ||||||
|             } |             } | ||||||
|  | @ -120,7 +120,7 @@ export default class ConflationChecker extends Combine implements FlowStep<{ fea | ||||||
|             } |             } | ||||||
|             const bounds = osmLiveData.bounds.data |             const bounds = osmLiveData.bounds.data | ||||||
|             return geojson.features.filter(f => BBox.get(f).overlapsWith(bounds)) |             return geojson.features.filter(f => BBox.get(f).overlapsWith(bounds)) | ||||||
|         }, [osmLiveData.bounds, zoomLevel.GetValue()]), false); |         }, [osmLiveData.bounds, zoomLevel.GetValue()])); | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         new ShowDataLayer({ |         new ShowDataLayer({ | ||||||
|  | @ -129,9 +129,9 @@ export default class ConflationChecker extends Combine implements FlowStep<{ fea | ||||||
|             leafletMap: osmLiveData.leafletMap, |             leafletMap: osmLiveData.leafletMap, | ||||||
|             popup: undefined, |             popup: undefined, | ||||||
|             zoomToFeatures: true, |             zoomToFeatures: true, | ||||||
|             features: new StaticFeatureSource([ |             features: StaticFeatureSource.fromGeojson([ | ||||||
|                 bbox.asGeoJson({}) |                 bbox.asGeoJson({}) | ||||||
|             ], false) |             ]) | ||||||
|         }) |         }) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -150,7 +150,7 @@ export default class ConflationChecker extends Combine implements FlowStep<{ fea | ||||||
|             leafletMap: osmLiveData.leafletMap, |             leafletMap: osmLiveData.leafletMap, | ||||||
|             popup: (tags, layer) => new FeatureInfoBox(tags, layer, state), |             popup: (tags, layer) => new FeatureInfoBox(tags, layer, state), | ||||||
|             zoomToFeatures: false, |             zoomToFeatures: false, | ||||||
|             features: new StaticFeatureSource(toImport.features, false) |             features: StaticFeatureSource.fromGeojson(toImport.features) | ||||||
|         }) |         }) | ||||||
| 
 | 
 | ||||||
|         const nearbyCutoff = ValidatedTextField.ForType("pnat").ConstructInputElement() |         const nearbyCutoff = ValidatedTextField.ForType("pnat").ConstructInputElement() | ||||||
|  | @ -172,11 +172,11 @@ export default class ConflationChecker extends Combine implements FlowStep<{ fea | ||||||
|             return osmData.features.filter(f => |             return osmData.features.filter(f => | ||||||
|                 toImport.features.some(imp => |                 toImport.features.some(imp => | ||||||
|                     maxDist >= GeoOperations.distanceBetween(imp.geometry.coordinates, GeoOperations.centerpointCoordinates(f)))) |                     maxDist >= GeoOperations.distanceBetween(imp.geometry.coordinates, GeoOperations.centerpointCoordinates(f)))) | ||||||
|         }, [nearbyCutoff.GetValue().stabilized(500)]), false); |         }, [nearbyCutoff.GetValue().stabilized(500)])); | ||||||
|         const paritionedImport = ImportUtils.partitionFeaturesIfNearby(toImport, geojson, nearbyCutoff.GetValue().map(Number)); |         const paritionedImport = ImportUtils.partitionFeaturesIfNearby(toImport, geojson, nearbyCutoff.GetValue().map(Number)); | ||||||
| 
 | 
 | ||||||
|         // Featuresource showing OSM-features which are nearby a toImport-feature 
 |         // Featuresource showing OSM-features which are nearby a toImport-feature 
 | ||||||
|         const toImportWithNearby = new StaticFeatureSource(paritionedImport.map(els => els?.hasNearby ?? []), false); |         const toImportWithNearby = new StaticFeatureSource(paritionedImport.map(els => els?.hasNearby ?? [])); | ||||||
| 
 | 
 | ||||||
|         new ShowDataLayer({ |         new ShowDataLayer({ | ||||||
|             layerToShow: layer, |             layerToShow: layer, | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {Store, UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import Combine from "../Base/Combine"; | import Combine from "../Base/Combine"; | ||||||
| import BaseUIElement from "../BaseUIElement"; | import BaseUIElement from "../BaseUIElement"; | ||||||
| import {SubtleButton} from "../Base/SubtleButton"; | import {SubtleButton} from "../Base/SubtleButton"; | ||||||
|  | @ -10,8 +10,8 @@ import {UIElement} from "../UIElement"; | ||||||
| import {FixedUiElement} from "../Base/FixedUiElement"; | import {FixedUiElement} from "../Base/FixedUiElement"; | ||||||
| 
 | 
 | ||||||
| export interface FlowStep<T> extends BaseUIElement { | export interface FlowStep<T> extends BaseUIElement { | ||||||
|     readonly IsValid: UIEventSource<boolean> |     readonly IsValid: Store<boolean> | ||||||
|     readonly Value: UIEventSource<T> |     readonly Value: Store<T> | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export class FlowPanelFactory<T> { | export class FlowPanelFactory<T> { | ||||||
|  |  | ||||||
|  | @ -12,7 +12,6 @@ import ConflationChecker from "./ConflationChecker"; | ||||||
| import {AskMetadata} from "./AskMetadata"; | import {AskMetadata} from "./AskMetadata"; | ||||||
| import {ConfirmProcess} from "./ConfirmProcess"; | import {ConfirmProcess} from "./ConfirmProcess"; | ||||||
| import {CreateNotes} from "./CreateNotes"; | import {CreateNotes} from "./CreateNotes"; | ||||||
| import {FixedUiElement} from "../Base/FixedUiElement"; |  | ||||||
| import {VariableUiElement} from "../Base/VariableUIElement"; | import {VariableUiElement} from "../Base/VariableUIElement"; | ||||||
| import List from "../Base/List"; | import List from "../Base/List"; | ||||||
| import {CompareToAlreadyExistingNotes} from "./CompareToAlreadyExistingNotes"; | import {CompareToAlreadyExistingNotes} from "./CompareToAlreadyExistingNotes"; | ||||||
|  |  | ||||||
|  | @ -1,8 +1,8 @@ | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {Store} from "../../Logic/UIEventSource"; | ||||||
| import {GeoOperations} from "../../Logic/GeoOperations"; | import {GeoOperations} from "../../Logic/GeoOperations"; | ||||||
| 
 | 
 | ||||||
| export class ImportUtils { | export class ImportUtils { | ||||||
|     public static partitionFeaturesIfNearby(toPartitionFeatureCollection: ({ features: any[] }), compareWith: UIEventSource<{ features: any[] }>, cutoffDistanceInMeters: UIEventSource<number>): UIEventSource<{ hasNearby: any[], noNearby: any[] }> { |     public static partitionFeaturesIfNearby(toPartitionFeatureCollection: ({ features: any[] }), compareWith: Store<{ features: any[] }>, cutoffDistanceInMeters: Store<number>): Store<{ hasNearby: any[], noNearby: any[] }> { | ||||||
|         return compareWith.map(osmData => { |         return compareWith.map(osmData => { | ||||||
|             if (osmData?.features === undefined) { |             if (osmData?.features === undefined) { | ||||||
|                 return undefined |                 return undefined | ||||||
|  |  | ||||||
|  | @ -13,7 +13,7 @@ import BaseUIElement from "../BaseUIElement"; | ||||||
| import ValidatedTextField from "../Input/ValidatedTextField"; | import ValidatedTextField from "../Input/ValidatedTextField"; | ||||||
| import {SubtleButton} from "../Base/SubtleButton"; | import {SubtleButton} from "../Base/SubtleButton"; | ||||||
| import Svg from "../../Svg"; | import Svg from "../../Svg"; | ||||||
| import Toggle from "../Input/Toggle"; | import Toggle, {ClickableToggle} from "../Input/Toggle"; | ||||||
| import Table from "../Base/Table"; | import Table from "../Base/Table"; | ||||||
| import LeftIndex from "../Base/LeftIndex"; | import LeftIndex from "../Base/LeftIndex"; | ||||||
| import Toggleable, {Accordeon} from "../Base/Toggleable"; | import Toggleable, {Accordeon} from "../Base/Toggleable"; | ||||||
|  | @ -271,7 +271,7 @@ class BatchView extends Toggleable { | ||||||
|             const selected = new Combine([BatchView.icons[status]().SetClass("h-6 m-1"), count + " " + status]) |             const selected = new Combine([BatchView.icons[status]().SetClass("h-6 m-1"), count + " " + status]) | ||||||
|                 .SetClass("flex ml-1 mb-1 pl-1 pr-3 items-center rounded-full border-4 border-black animate-pulse") |                 .SetClass("flex ml-1 mb-1 pl-1 pr-3 items-center rounded-full border-4 border-black animate-pulse") | ||||||
| 
 | 
 | ||||||
|             const toggle = new Toggle(selected, normal, filterOn.map(f => f === status, [], (selected, previous) => { |             const toggle = new ClickableToggle(selected, normal, filterOn.sync(f => f === status, [], (selected, previous) => { | ||||||
|                 if (selected) { |                 if (selected) { | ||||||
|                     return status; |                     return status; | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| import Combine from "../Base/Combine"; | import Combine from "../Base/Combine"; | ||||||
| import {FlowStep} from "./FlowStep"; | import {FlowStep} from "./FlowStep"; | ||||||
| import UserRelatedState from "../../Logic/State/UserRelatedState"; | import UserRelatedState from "../../Logic/State/UserRelatedState"; | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {Store, UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import Translations from "../i18n/Translations"; | import Translations from "../i18n/Translations"; | ||||||
| import Title from "../Base/Title"; | import Title from "../Base/Title"; | ||||||
| import {VariableUiElement} from "../Base/VariableUIElement"; | import {VariableUiElement} from "../Base/VariableUIElement"; | ||||||
|  | @ -15,8 +15,8 @@ import MoreScreen from "../BigComponents/MoreScreen"; | ||||||
| import CheckBoxes from "../Input/Checkboxes"; | import CheckBoxes from "../Input/Checkboxes"; | ||||||
| 
 | 
 | ||||||
| export default class LoginToImport extends Combine implements FlowStep<UserRelatedState> { | export default class LoginToImport extends Combine implements FlowStep<UserRelatedState> { | ||||||
|     readonly IsValid: UIEventSource<boolean>; |     readonly IsValid: Store<boolean>; | ||||||
|     readonly Value: UIEventSource<UserRelatedState>; |     readonly Value: Store<UserRelatedState>; | ||||||
| 
 | 
 | ||||||
|     private static readonly whitelist = [15015689]; |     private static readonly whitelist = [15015689]; | ||||||
|      |      | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| import Combine from "../Base/Combine"; | import Combine from "../Base/Combine"; | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {Store, UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import {BBox} from "../../Logic/BBox"; | import {BBox} from "../../Logic/BBox"; | ||||||
| import UserRelatedState from "../../Logic/State/UserRelatedState"; | import UserRelatedState from "../../Logic/State/UserRelatedState"; | ||||||
| import Translations from "../i18n/Translations"; | import Translations from "../i18n/Translations"; | ||||||
|  | @ -27,7 +27,7 @@ import {AllTagsPanel} from "../AllTagsPanel"; | ||||||
| 
 | 
 | ||||||
| class PreviewPanel extends ScrollableFullScreen { | class PreviewPanel extends ScrollableFullScreen { | ||||||
| 
 | 
 | ||||||
|     constructor(tags: UIEventSource<any>, layer) { |     constructor(tags: UIEventSource<any>) { | ||||||
|         super( |         super( | ||||||
|             _ => new FixedUiElement("Element to import"), |             _ => new FixedUiElement("Element to import"), | ||||||
|             _ => new Combine(["The tags are:", |             _ => new Combine(["The tags are:", | ||||||
|  | @ -43,8 +43,8 @@ class PreviewPanel extends ScrollableFullScreen { | ||||||
|  * Shows the data to import on a map, asks for the correct layer to be selected |  * Shows the data to import on a map, asks for the correct layer to be selected | ||||||
|  */ |  */ | ||||||
| export class MapPreview extends Combine implements FlowStep<{ bbox: BBox, layer: LayerConfig, features: any[] }> { | export class MapPreview extends Combine implements FlowStep<{ bbox: BBox, layer: LayerConfig, features: any[] }> { | ||||||
|     public readonly IsValid: UIEventSource<boolean>; |     public readonly IsValid: Store<boolean>; | ||||||
|     public readonly Value: UIEventSource<{ bbox: BBox, layer: LayerConfig, features: any[] }> |     public readonly Value: Store<{ bbox: BBox, layer: LayerConfig, features: any[] }> | ||||||
| 
 | 
 | ||||||
|     constructor( |     constructor( | ||||||
|         state: UserRelatedState, |         state: UserRelatedState, | ||||||
|  | @ -85,7 +85,7 @@ export class MapPreview extends Combine implements FlowStep<{ bbox: BBox, layer: | ||||||
|             return copy |             return copy | ||||||
|         }) |         }) | ||||||
| 
 | 
 | ||||||
|         const matching: UIEventSource<{ properties: any, geometry: { coordinates: [number, number] } }[]> = layerPicker.GetValue().map((layer: LayerConfig) => { |         const matching: Store<{ properties: any, geometry: { coordinates: [number, number] } }[]> = layerPicker.GetValue().map((layer: LayerConfig) => { | ||||||
|             if (layer === undefined) { |             if (layer === undefined) { | ||||||
|                 return []; |                 return []; | ||||||
|             } |             } | ||||||
|  | @ -120,9 +120,9 @@ export class MapPreview extends Combine implements FlowStep<{ bbox: BBox, layer: | ||||||
|                     appliedFilters: new UIEventSource<Map<string, FilterState>>(undefined) |                     appliedFilters: new UIEventSource<Map<string, FilterState>>(undefined) | ||||||
|                 }))), |                 }))), | ||||||
|             zoomToFeatures: true, |             zoomToFeatures: true, | ||||||
|             features: new StaticFeatureSource(matching, false), |             features: StaticFeatureSource.fromDateless(matching.map(features => features.map(feature => ({feature})))), | ||||||
|             leafletMap: map.leafletMap, |             leafletMap: map.leafletMap, | ||||||
|             popup: (tag, layer) => new PreviewPanel(tag, layer).SetClass("font-lg") |             popup: (tag) => new PreviewPanel(tag).SetClass("font-lg") | ||||||
|         }) |         }) | ||||||
|         var bbox = matching.map(feats => BBox.bboxAroundAll(feats.map(f => new BBox([f.geometry.coordinates])))) |         var bbox = matching.map(feats => BBox.bboxAroundAll(feats.map(f => new BBox([f.geometry.coordinates])))) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| import Combine from "../Base/Combine"; | import Combine from "../Base/Combine"; | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {Store, UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import UserRelatedState from "../../Logic/State/UserRelatedState"; | import UserRelatedState from "../../Logic/State/UserRelatedState"; | ||||||
| import Translations from "../i18n/Translations"; | import Translations from "../i18n/Translations"; | ||||||
| import {Utils} from "../../Utils"; | import {Utils} from "../../Utils"; | ||||||
|  | @ -15,8 +15,8 @@ import CheckBoxes from "../Input/Checkboxes"; | ||||||
|  * Shows the attributes by value, requests to check them of |  * Shows the attributes by value, requests to check them of | ||||||
|  */ |  */ | ||||||
| export class PreviewAttributesPanel extends Combine implements FlowStep<{ features: { properties: any, geometry: { coordinates: [number, number] } }[] }> { | export class PreviewAttributesPanel extends Combine implements FlowStep<{ features: { properties: any, geometry: { coordinates: [number, number] } }[] }> { | ||||||
|     public readonly IsValid: UIEventSource<boolean>; |     public readonly IsValid: Store<boolean>; | ||||||
|     public readonly Value: UIEventSource<{ features: { properties: any, geometry: { coordinates: [number, number] } }[] }> |     public readonly Value: Store<{ features: { properties: any, geometry: { coordinates: [number, number] } }[] }> | ||||||
| 
 | 
 | ||||||
|     constructor( |     constructor( | ||||||
|         state: UserRelatedState, |         state: UserRelatedState, | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| import Combine from "../Base/Combine"; | import Combine from "../Base/Combine"; | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {Store, Stores} from "../../Logic/UIEventSource"; | ||||||
| import Translations from "../i18n/Translations"; | import Translations from "../i18n/Translations"; | ||||||
| import {SubtleButton} from "../Base/SubtleButton"; | import {SubtleButton} from "../Base/SubtleButton"; | ||||||
| import {VariableUiElement} from "../Base/VariableUIElement"; | import {VariableUiElement} from "../Base/VariableUIElement"; | ||||||
|  | @ -10,7 +10,6 @@ import FileSelectorButton from "../Input/FileSelectorButton"; | ||||||
| import {FlowStep} from "./FlowStep"; | import {FlowStep} from "./FlowStep"; | ||||||
| import {parse} from "papaparse"; | import {parse} from "papaparse"; | ||||||
| import {FixedUiElement} from "../Base/FixedUiElement"; | import {FixedUiElement} from "../Base/FixedUiElement"; | ||||||
| import {del} from "idb-keyval"; |  | ||||||
| import {TagUtils} from "../../Logic/Tags/TagUtils"; | import {TagUtils} from "../../Logic/Tags/TagUtils"; | ||||||
| 
 | 
 | ||||||
| class FileSelector extends InputElementMap<FileList, { name: string, contents: Promise<string> }> { | class FileSelector extends InputElementMap<FileList, { name: string, contents: Promise<string> }> { | ||||||
|  | @ -38,11 +37,11 @@ class FileSelector extends InputElementMap<FileList, { name: string, contents: P | ||||||
|  */ |  */ | ||||||
| export class RequestFile extends Combine implements FlowStep<{features: any[]}> { | export class RequestFile extends Combine implements FlowStep<{features: any[]}> { | ||||||
| 
 | 
 | ||||||
|     public readonly IsValid: UIEventSource<boolean> |     public readonly IsValid: Store<boolean> | ||||||
|     /** |     /** | ||||||
|      * The loaded GeoJSON |      * The loaded GeoJSON | ||||||
|      */ |      */ | ||||||
|     public readonly Value: UIEventSource<{features: any[]}> |     public readonly Value: Store<{features: any[]}> | ||||||
| 
 | 
 | ||||||
|     constructor() { |     constructor() { | ||||||
|         const t = Translations.t.importHelper.selectFile; |         const t = Translations.t.importHelper.selectFile; | ||||||
|  | @ -54,15 +53,15 @@ export class RequestFile extends Combine implements FlowStep<{features: any[]}> | ||||||
|             return t.loadedFilesAre.Subs({file: file.name}).SetClass("thanks") |             return t.loadedFilesAre.Subs({file: file.name}).SetClass("thanks") | ||||||
|         })) |         })) | ||||||
| 
 | 
 | ||||||
|         const text = UIEventSource.flatten( |         const text = Stores.flatten( | ||||||
|             csvSelector.GetValue().map(v => { |             csvSelector.GetValue().map(v => { | ||||||
|                 if (v === undefined) { |                 if (v === undefined) { | ||||||
|                     return undefined |                     return undefined | ||||||
|                 } |                 } | ||||||
|                 return UIEventSource.FromPromise(v.contents) |                 return Stores.FromPromise(v.contents) | ||||||
|             })) |             })) | ||||||
| 
 | 
 | ||||||
|         const asGeoJson: UIEventSource<any | { error: string | BaseUIElement }> = text.map(src => { |         const asGeoJson: Store<any | { error: string | BaseUIElement }> = text.map((src: string) => { | ||||||
|             if (src === undefined) { |             if (src === undefined) { | ||||||
|                 return undefined |                 return undefined | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import {FlowStep} from "./FlowStep"; | import {FlowStep} from "./FlowStep"; | ||||||
| import Combine from "../Base/Combine"; | import Combine from "../Base/Combine"; | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {Store} from "../../Logic/UIEventSource"; | ||||||
| import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; | import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; | ||||||
| import {InputElement} from "../Input/InputElement"; | import {InputElement} from "../Input/InputElement"; | ||||||
| import {AllKnownLayouts} from "../../Customizations/AllKnownLayouts"; | import {AllKnownLayouts} from "../../Customizations/AllKnownLayouts"; | ||||||
|  | @ -24,13 +24,13 @@ export default class SelectTheme extends Combine implements FlowStep<{ | ||||||
|     bbox: BBox, |     bbox: BBox, | ||||||
| }> { | }> { | ||||||
| 
 | 
 | ||||||
|     public readonly Value: UIEventSource<{ |     public readonly Value: Store<{ | ||||||
|         features: any[], |         features: any[], | ||||||
|         theme: string, |         theme: string, | ||||||
|         layer: LayerConfig, |         layer: LayerConfig, | ||||||
|         bbox: BBox, |         bbox: BBox, | ||||||
|     }>; |     }>; | ||||||
|     public readonly IsValid: UIEventSource<boolean>; |     public readonly IsValid: Store<boolean>; | ||||||
| 
 | 
 | ||||||
|     constructor(params: ({ features: any[], layer: LayerConfig, bbox: BBox, })) { |     constructor(params: ({ features: any[], layer: LayerConfig, bbox: BBox, })) { | ||||||
|         const t = Translations.t.importHelper.selectTheme |         const t = Translations.t.importHelper.selectTheme | ||||||
|  |  | ||||||
|  | @ -1,11 +1,12 @@ | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {Store, UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import BaseUIElement from "../BaseUIElement"; | import BaseUIElement from "../BaseUIElement"; | ||||||
| 
 | 
 | ||||||
| export abstract class InputElement<T> extends BaseUIElement { | export interface ReadonlyInputElement<T> extends BaseUIElement{ | ||||||
| 
 |     GetValue(): Store<T>; | ||||||
| 
 | } | ||||||
|     abstract GetValue(): UIEventSource<T>; | 
 | ||||||
| 
 | 
 | ||||||
|     abstract IsValid(t: T): boolean; | export abstract class InputElement<T> extends BaseUIElement implements ReadonlyInputElement<any>{ | ||||||
| 
 |     abstract GetValue(): UIEventSource<T>; | ||||||
|  |     abstract IsValid(t: T): boolean; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -21,7 +21,7 @@ export default class InputElementMap<T, X> extends InputElement<X> { | ||||||
|         this.toX = toX; |         this.toX = toX; | ||||||
|         this._inputElement = inputElement; |         this._inputElement = inputElement; | ||||||
|         const self = this; |         const self = this; | ||||||
|         this._value = inputElement.GetValue().map( |         this._value = inputElement.GetValue().sync( | ||||||
|             (t => { |             (t => { | ||||||
|                 const newX = toX(t); |                 const newX = toX(t); | ||||||
|                 const currentX = self.GetValue()?.data; |                 const currentX = self.GetValue()?.data; | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import {InputElement} from "./InputElement"; | import {ReadonlyInputElement} from "./InputElement"; | ||||||
| import Loc from "../../Models/Loc"; | import Loc from "../../Models/Loc"; | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {Store, UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import Minimap, {MinimapObj} from "../Base/Minimap"; | import Minimap, {MinimapObj} from "../Base/Minimap"; | ||||||
| import BaseLayer from "../../Models/BaseLayer"; | import BaseLayer from "../../Models/BaseLayer"; | ||||||
| import Combine from "../Base/Combine"; | import Combine from "../Base/Combine"; | ||||||
|  | @ -17,11 +17,10 @@ import BaseUIElement from "../BaseUIElement"; | ||||||
| import Toggle from "./Toggle"; | import Toggle from "./Toggle"; | ||||||
| import * as matchpoint from "../../assets/layers/matchpoint/matchpoint.json" | import * as matchpoint from "../../assets/layers/matchpoint/matchpoint.json" | ||||||
| 
 | 
 | ||||||
| export default class LocationInput extends InputElement<Loc> implements MinimapObj { | export default class LocationInput extends BaseUIElement implements ReadonlyInputElement<Loc>, MinimapObj { | ||||||
| 
 | 
 | ||||||
|     private static readonly matchLayer = new LayerConfig(matchpoint, "LocationInput.matchpoint", true) |     private static readonly matchLayer = new LayerConfig(matchpoint, "LocationInput.matchpoint", true) | ||||||
| 
 | 
 | ||||||
|     IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false); |  | ||||||
|     public readonly snappedOnto: UIEventSource<any> = new UIEventSource<any>(undefined) |     public readonly snappedOnto: UIEventSource<any> = new UIEventSource<any>(undefined) | ||||||
|     public readonly _matching_layer: LayerConfig; |     public readonly _matching_layer: LayerConfig; | ||||||
|     public readonly leafletMap: UIEventSource<any> |     public readonly leafletMap: UIEventSource<any> | ||||||
|  | @ -33,9 +32,9 @@ export default class LocationInput extends InputElement<Loc> implements MinimapO | ||||||
|      * The features to which the input should be snapped |      * The features to which the input should be snapped | ||||||
|      * @private |      * @private | ||||||
|      */ |      */ | ||||||
|     private readonly _snapTo: UIEventSource<{ feature: any }[]> |     private readonly _snapTo: Store<{ feature: any }[]> | ||||||
|     private readonly _value: UIEventSource<Loc> |     private readonly _value: Store<Loc> | ||||||
|     private readonly _snappedPoint: UIEventSource<any> |     private readonly _snappedPoint: Store<any> | ||||||
|     private readonly _maxSnapDistance: number |     private readonly _maxSnapDistance: number | ||||||
|     private readonly _snappedPointTags: any; |     private readonly _snappedPointTags: any; | ||||||
|     private readonly _bounds: UIEventSource<BBox>; |     private readonly _bounds: UIEventSource<BBox>; | ||||||
|  | @ -151,7 +150,7 @@ export default class LocationInput extends InputElement<Loc> implements MinimapO | ||||||
|         this.location = this.map.location; |         this.location = this.map.location; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     GetValue(): UIEventSource<Loc> { |     GetValue(): Store<Loc> { | ||||||
|         return this._value; |         return this._value; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -188,7 +187,7 @@ export default class LocationInput extends InputElement<Loc> implements MinimapO | ||||||
|                 // Show the lines to snap to
 |                 // Show the lines to snap to
 | ||||||
|                 console.log("Constructing the snap-to layer", this._snapTo) |                 console.log("Constructing the snap-to layer", this._snapTo) | ||||||
|                 new ShowDataMultiLayer({ |                 new ShowDataMultiLayer({ | ||||||
|                         features: new StaticFeatureSource(this._snapTo, true), |                         features: StaticFeatureSource.fromDateless(this._snapTo), | ||||||
|                         zoomToFeatures: false, |                         zoomToFeatures: false, | ||||||
|                         leafletMap: this.map.leafletMap, |                         leafletMap: this.map.leafletMap, | ||||||
|                         layers: State.state.filteredLayers |                         layers: State.state.filteredLayers | ||||||
|  | @ -201,8 +200,10 @@ export default class LocationInput extends InputElement<Loc> implements MinimapO | ||||||
|                     } |                     } | ||||||
|                     return [{feature: loc}]; |                     return [{feature: loc}]; | ||||||
|                 }) |                 }) | ||||||
|  |                 console.log("Constructing the match layer", matchPoint) | ||||||
|  | 
 | ||||||
|                 new ShowDataLayer({ |                 new ShowDataLayer({ | ||||||
|                     features: new StaticFeatureSource(matchPoint, true), |                     features: StaticFeatureSource.fromDateless(matchPoint), | ||||||
|                     zoomToFeatures: false, |                     zoomToFeatures: false, | ||||||
|                     leafletMap: this.map.leafletMap, |                     leafletMap: this.map.leafletMap, | ||||||
|                     layerToShow: this._matching_layer, |                     layerToShow: this._matching_layer, | ||||||
|  |  | ||||||
|  | @ -152,7 +152,7 @@ export class RadioButton<T> extends InputElement<T> { | ||||||
|             form.appendChild(block); |             form.appendChild(block); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         value.addCallbackAndRun((selected) => { |         value.addCallbackAndRun((selected:T) => { | ||||||
|             let somethingChecked = false; |             let somethingChecked = false; | ||||||
|             for (let i = 0; i < inputs.length; i++) { |             for (let i = 0; i < inputs.length; i++) { | ||||||
|                 let input = inputs[i]; |                 let input = inputs[i]; | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {Store, UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import BaseUIElement from "../BaseUIElement"; | import BaseUIElement from "../BaseUIElement"; | ||||||
| import {VariableUiElement} from "../Base/VariableUIElement"; | import {VariableUiElement} from "../Base/VariableUIElement"; | ||||||
| import Lazy from "../Base/Lazy"; | import Lazy from "../Base/Lazy"; | ||||||
|  | @ -9,16 +9,16 @@ import Lazy from "../Base/Lazy"; | ||||||
|  */ |  */ | ||||||
| export default class Toggle extends VariableUiElement { | export default class Toggle extends VariableUiElement { | ||||||
| 
 | 
 | ||||||
|     public readonly isEnabled: UIEventSource<boolean>; |     public readonly isEnabled: Store<boolean>; | ||||||
| 
 | 
 | ||||||
|     constructor(showEnabled: string | BaseUIElement, showDisabled: string | BaseUIElement, isEnabled: UIEventSource<boolean> = new UIEventSource<boolean>(false)) { |     constructor(showEnabled: string | BaseUIElement, showDisabled: string | BaseUIElement, isEnabled: Store<boolean> = new UIEventSource<boolean>(false)) { | ||||||
|         super( |         super( | ||||||
|             isEnabled?.map(isEnabled => isEnabled ? showEnabled : showDisabled) |             isEnabled?.map(isEnabled => isEnabled ? showEnabled : showDisabled) | ||||||
|         ); |         ); | ||||||
|         this.isEnabled = isEnabled |         this.isEnabled = isEnabled | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static If(condition: UIEventSource<boolean>, constructor: () => BaseUIElement): BaseUIElement { |     public static If(condition: Store<boolean>, constructor: () => BaseUIElement): BaseUIElement { | ||||||
|         if (constructor === undefined) { |         if (constructor === undefined) { | ||||||
|             return undefined |             return undefined | ||||||
|         } |         } | ||||||
|  | @ -29,8 +29,24 @@ export default class Toggle extends VariableUiElement { | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|     } |     } | ||||||
|  |      | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|     public ToggleOnClick(): Toggle { | /** | ||||||
|  |  * Same as `Toggle`, but will swap on click | ||||||
|  |  */ | ||||||
|  | export class ClickableToggle extends Toggle { | ||||||
|  | 
 | ||||||
|  |     public readonly isEnabled: UIEventSource<boolean>; | ||||||
|  | 
 | ||||||
|  |     constructor(showEnabled: string | BaseUIElement, showDisabled: string | BaseUIElement, isEnabled: UIEventSource<boolean> = new UIEventSource<boolean>(false)) { | ||||||
|  |         super( | ||||||
|  |           showEnabled, showDisabled, isEnabled | ||||||
|  |         ); | ||||||
|  |         this.isEnabled = isEnabled | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     public ToggleOnClick(): ClickableToggle { | ||||||
|         const self = this; |         const self = this; | ||||||
|         this.onClick(() => { |         this.onClick(() => { | ||||||
|             self.isEnabled.setData(!self.isEnabled.data); |             self.isEnabled.setData(!self.isEnabled.data); | ||||||
|  |  | ||||||
|  | @ -1,23 +1,22 @@ | ||||||
| import {InputElement} from "./InputElement"; | import {InputElement, ReadonlyInputElement} from "./InputElement"; | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {Store} from "../../Logic/UIEventSource"; | ||||||
| import BaseUIElement from "../BaseUIElement"; | import BaseUIElement from "../BaseUIElement"; | ||||||
| import {VariableUiElement} from "../Base/VariableUIElement"; | import {VariableUiElement} from "../Base/VariableUIElement"; | ||||||
| 
 | 
 | ||||||
| export default class VariableInputElement<T> extends InputElement<T> { | export default class VariableInputElement<T> extends BaseUIElement implements ReadonlyInputElement<T> { | ||||||
| 
 | 
 | ||||||
|     private readonly value: UIEventSource<T>; |     private readonly value: Store<T>; | ||||||
|     private readonly element: BaseUIElement |     private readonly element: BaseUIElement | ||||||
|     private readonly upstream: UIEventSource<InputElement<T>>; |     private readonly upstream: Store<InputElement<T>>; | ||||||
| 
 |  | ||||||
|     constructor(upstream: UIEventSource<InputElement<T>>) { |  | ||||||
| 
 | 
 | ||||||
|  |     constructor(upstream: Store<InputElement<T>>) { | ||||||
|         super() |         super() | ||||||
|         this.upstream = upstream; |         this.upstream = upstream; | ||||||
|         this.value = upstream.bind(v => v.GetValue()) |         this.value = upstream.bind(v => v.GetValue()) | ||||||
|         this.element = new VariableUiElement(upstream) |         this.element = new VariableUiElement(upstream) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     GetValue(): UIEventSource<T> { |     GetValue(): Store<T> { | ||||||
|         return this.value; |         return this.value; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -367,6 +367,9 @@ export class OH { | ||||||
|         return OH.ToString(OH.MergeTimes(OH.Parse(str))) |         return OH.ToString(OH.MergeTimes(OH.Parse(str))) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Parses a string into Opening Hours | ||||||
|  |      */ | ||||||
|     public static Parse(rules: string): OpeningHour[] { |     public static Parse(rules: string): OpeningHour[] { | ||||||
|         if (rules === undefined || rules === "") { |         if (rules === undefined || rules === "") { | ||||||
|             return [] |             return [] | ||||||
|  |  | ||||||
|  | @ -4,15 +4,14 @@ | ||||||
|  * Exports everything conventiently as a string, for direct use |  * Exports everything conventiently as a string, for direct use | ||||||
|  */ |  */ | ||||||
| import OpeningHoursPicker from "./OpeningHoursPicker"; | import OpeningHoursPicker from "./OpeningHoursPicker"; | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {Store, UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import {VariableUiElement} from "../Base/VariableUIElement"; | import {VariableUiElement} from "../Base/VariableUIElement"; | ||||||
| import Combine from "../Base/Combine"; | import Combine from "../Base/Combine"; | ||||||
| import {FixedUiElement} from "../Base/FixedUiElement"; | import {FixedUiElement} from "../Base/FixedUiElement"; | ||||||
| import {OH} from "./OpeningHours"; | import {OH, OpeningHour} from "./OpeningHours"; | ||||||
| import {InputElement} from "../Input/InputElement"; | import {InputElement} from "../Input/InputElement"; | ||||||
| import PublicHolidayInput from "./PublicHolidayInput"; | import PublicHolidayInput from "./PublicHolidayInput"; | ||||||
| import Translations from "../i18n/Translations"; | import Translations from "../i18n/Translations"; | ||||||
| import {Utils} from "../../Utils"; |  | ||||||
| import BaseUIElement from "../BaseUIElement"; | import BaseUIElement from "../BaseUIElement"; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -28,8 +27,7 @@ export default class OpeningHoursInput extends InputElement<string> { | ||||||
|         this._value = value; |         this._value = value; | ||||||
|         let valueWithoutPrefix = value |         let valueWithoutPrefix = value | ||||||
|         if (prefix !== "" && postfix !== "") { |         if (prefix !== "" && postfix !== "") { | ||||||
| 
 |             valueWithoutPrefix = value.sync(str => { | ||||||
|             valueWithoutPrefix = value.map(str => { |  | ||||||
|                 if (str === undefined) { |                 if (str === undefined) { | ||||||
|                     return undefined; |                     return undefined; | ||||||
|                 } |                 } | ||||||
|  | @ -55,7 +53,7 @@ export default class OpeningHoursInput extends InputElement<string> { | ||||||
|             }) |             }) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const leftoverRules = valueWithoutPrefix.map<string[]>(str => { |         const leftoverRules: Store<string[]> = valueWithoutPrefix.map(str => { | ||||||
|             if (str === undefined) { |             if (str === undefined) { | ||||||
|                 return [] |                 return [] | ||||||
|             } |             } | ||||||
|  | @ -72,35 +70,40 @@ export default class OpeningHoursInput extends InputElement<string> { | ||||||
|             } |             } | ||||||
|             return leftOvers; |             return leftOvers; | ||||||
|         }) |         }) | ||||||
|         // Note: MUST be bound AFTER the leftover rules!
 |         | ||||||
|         const rulesFromOhPicker = valueWithoutPrefix.map(OH.Parse); |         let ph = ""; | ||||||
| 
 |         const rules = valueWithoutPrefix.data?.split(";") ?? []; | ||||||
|         const ph = valueWithoutPrefix.map<string>(str => { |         for (const rule of rules) { | ||||||
|             if (str === undefined) { |             if (OH.ParsePHRule(rule) !== null) { | ||||||
|                 return "" |                 ph = rule; | ||||||
|  |                 break; | ||||||
|             } |             } | ||||||
|             const rules = str.split(";"); |  | ||||||
|             for (const rule of rules) { |  | ||||||
|                 if (OH.ParsePHRule(rule) !== null) { |  | ||||||
|                     return rule; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             return ""; |  | ||||||
|         }) |  | ||||||
|         const phSelector = new PublicHolidayInput(ph); |  | ||||||
| 
 |  | ||||||
|         function update() { |  | ||||||
|             const regular = OH.ToString(rulesFromOhPicker.data); |  | ||||||
|             const rules: string[] = [ |  | ||||||
|                 regular, |  | ||||||
|                 ...leftoverRules.data, |  | ||||||
|                 ph.data |  | ||||||
|             ] |  | ||||||
|             valueWithoutPrefix.setData(Utils.NoEmpty(rules).join(";")); |  | ||||||
|         } |         } | ||||||
|  |         const phSelector = new PublicHolidayInput(new UIEventSource<string>(ph)); | ||||||
|  |          | ||||||
|  |          | ||||||
|  |         // Note: MUST be bound AFTER the leftover rules!
 | ||||||
|  |         const rulesFromOhPicker: UIEventSource<OpeningHour[]> = valueWithoutPrefix.sync(str => { | ||||||
|  |             console.log(">> Parsing '"+ str+"'") | ||||||
|  |             return OH.Parse(str); | ||||||
|  |         }, [leftoverRules, phSelector.GetValue()], (rules, oldString) => { | ||||||
|  |             let str = OH.ToString(rules); | ||||||
|  |             const ph = phSelector.GetValue().data; | ||||||
|  |             if(ph){ | ||||||
|  |                str += "; "+ph  | ||||||
|  |             } | ||||||
|  |              | ||||||
|  |             str += leftoverRules.data.join("; ") | ||||||
|  |             if(!str.endsWith(";")){ | ||||||
|  |                 str += ";" | ||||||
|  |             } | ||||||
|  |             if(str === oldString){ | ||||||
|  |                 return oldString; // We pass a reference to the old string to stabilize the EventSource
 | ||||||
|  |             } | ||||||
|  |             console.log("Reconstructed '"+ str+"'") | ||||||
|  |             return str; | ||||||
|  |         }); | ||||||
| 
 | 
 | ||||||
|         rulesFromOhPicker.addCallback(update); |  | ||||||
|         ph.addCallback(update); |  | ||||||
| 
 | 
 | ||||||
|         const leftoverWarning = new VariableUiElement(leftoverRules.map((leftovers: string[]) => { |         const leftoverWarning = new VariableUiElement(leftoverRules.map((leftovers: string[]) => { | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| import {SpecialVisualization} from "../SpecialVisualizations"; | import {SpecialVisualization} from "../SpecialVisualizations"; | ||||||
| import FeaturePipelineState from "../../Logic/State/FeaturePipelineState"; | import FeaturePipelineState from "../../Logic/State/FeaturePipelineState"; | ||||||
| import BaseUIElement from "../BaseUIElement"; | import BaseUIElement from "../BaseUIElement"; | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {Stores, UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import {DefaultGuiState} from "../DefaultGuiState"; | import {DefaultGuiState} from "../DefaultGuiState"; | ||||||
| import {SubtleButton} from "../Base/SubtleButton"; | import {SubtleButton} from "../Base/SubtleButton"; | ||||||
| import Img from "../Base/Img"; | import Img from "../Base/Img"; | ||||||
|  | @ -97,7 +97,7 @@ class ApplyButton extends UIElement { | ||||||
|         new ShowDataLayer({ |         new ShowDataLayer({ | ||||||
|             leafletMap: previewMap.leafletMap, |             leafletMap: previewMap.leafletMap, | ||||||
|             zoomToFeatures: true, |             zoomToFeatures: true, | ||||||
|             features: new StaticFeatureSource(features, false), |             features: StaticFeatureSource.fromGeojson(features), | ||||||
|             state: this.state, |             state: this.state, | ||||||
|             layerToShow: this.layer.layerDef, |             layerToShow: this.layer.layerDef, | ||||||
|         }) |         }) | ||||||
|  | @ -218,7 +218,7 @@ export default class AutoApplyButton implements SpecialVisualization { | ||||||
|             return new Lazy(() => { |             return new Lazy(() => { | ||||||
|                 const to_parse = new UIEventSource(undefined) |                 const to_parse = new UIEventSource(undefined) | ||||||
|                 // Very ugly hack: read the value every 500ms
 |                 // Very ugly hack: read the value every 500ms
 | ||||||
|                 UIEventSource.Chronic(500, () => to_parse.data === undefined).addCallback(() => { |                 Stores.Chronic(500, () => to_parse.data === undefined).addCallback(() => { | ||||||
|                     const applicable = tagSource.data[argument[1]] |                     const applicable = tagSource.data[argument[1]] | ||||||
|                     to_parse.setData(applicable) |                     to_parse.setData(applicable) | ||||||
|                 }) |                 }) | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ import Toggle from "../Input/Toggle"; | ||||||
| import Translations from "../i18n/Translations"; | import Translations from "../i18n/Translations"; | ||||||
| import Svg from "../../Svg"; | import Svg from "../../Svg"; | ||||||
| import DeleteAction from "../../Logic/Osm/Actions/DeleteAction"; | import DeleteAction from "../../Logic/Osm/Actions/DeleteAction"; | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {Store, UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import {TagsFilter} from "../../Logic/Tags/TagsFilter"; | import {TagsFilter} from "../../Logic/Tags/TagsFilter"; | ||||||
| import Combine from "../Base/Combine"; | import Combine from "../Base/Combine"; | ||||||
| import {SubtleButton} from "../Base/SubtleButton"; | import {SubtleButton} from "../Base/SubtleButton"; | ||||||
|  | @ -106,7 +106,7 @@ export default class DeleteWizard extends Toggle { | ||||||
|                 } |                 } | ||||||
|             ) |             ) | ||||||
| 
 | 
 | ||||||
|         const isShown: UIEventSource<boolean> = tagsSource.map(tgs => tgs.id.indexOf("-") < 0) |         const isShown: Store<boolean> = tagsSource.map(tgs => tgs.id.indexOf("-") < 0) | ||||||
| 
 | 
 | ||||||
|         const deleteOptionPicker = DeleteWizard.constructMultipleChoice(options, tagsSource, state); |         const deleteOptionPicker = DeleteWizard.constructMultipleChoice(options, tagsSource, state); | ||||||
|         const deleteDialog = new Combine([ |         const deleteDialog = new Combine([ | ||||||
|  | @ -350,8 +350,10 @@ class DeleteabilityChecker { | ||||||
| 
 | 
 | ||||||
|             if (allByMyself.data === null && useTheInternet) { |             if (allByMyself.data === null && useTheInternet) { | ||||||
|                 // We kickoff the download here as it hasn't yet been downloaded. Note that this is mapped onto 'all by myself' above
 |                 // We kickoff the download here as it hasn't yet been downloaded. Note that this is mapped onto 'all by myself' above
 | ||||||
|                 OsmObject.DownloadHistory(id).map(versions => versions.map(version => version.tags["_last_edit:contributor:uid"])).syncWith(previousEditors) |                 const hist = OsmObject.DownloadHistory(id).map(versions => versions.map(version => version.tags["_last_edit:contributor:uid"])) | ||||||
|  |                 hist.addCallbackAndRunD(hist => previousEditors.setData(hist)) | ||||||
|             } |             } | ||||||
|  |              | ||||||
|             if (allByMyself.data === true) { |             if (allByMyself.data === true) { | ||||||
|                 // Yay! We can download!
 |                 // Yay! We can download!
 | ||||||
|                 return true; |                 return true; | ||||||
|  |  | ||||||
|  | @ -241,7 +241,7 @@ ${Utils.special_visualizations_importRequirementDocs} | ||||||
|         new ShowDataMultiLayer({ |         new ShowDataMultiLayer({ | ||||||
|             leafletMap: confirmationMap.leafletMap, |             leafletMap: confirmationMap.leafletMap, | ||||||
|             zoomToFeatures: true, |             zoomToFeatures: true, | ||||||
|             features: new StaticFeatureSource([feature], false), |             features: StaticFeatureSource.fromGeojson([feature]), | ||||||
|             state: state, |             state: state, | ||||||
|             layers: state.filteredLayers |             layers: state.filteredLayers | ||||||
|         }) |         }) | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {Store} from "../../Logic/UIEventSource"; | ||||||
| import BaseUIElement from "../BaseUIElement"; | import BaseUIElement from "../BaseUIElement"; | ||||||
| import Combine from "../Base/Combine"; | import Combine from "../Base/Combine"; | ||||||
| import {SubtleButton} from "../Base/SubtleButton"; | import {SubtleButton} from "../Base/SubtleButton"; | ||||||
|  | @ -16,12 +16,12 @@ import {OsmConnection} from "../../Logic/Osm/OsmConnection"; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| export interface MultiApplyParams { | export interface MultiApplyParams { | ||||||
|     featureIds: UIEventSource<string[]>, |     featureIds: Store<string[]>, | ||||||
|     keysToApply: string[], |     keysToApply: string[], | ||||||
|     text: string, |     text: string, | ||||||
|     autoapply: boolean, |     autoapply: boolean, | ||||||
|     overwrite: boolean, |     overwrite: boolean, | ||||||
|     tagsSource: UIEventSource<any>, |     tagsSource: Store<any>, | ||||||
|     state: { |     state: { | ||||||
|         changes: Changes, |         changes: Changes, | ||||||
|         allElements: ElementStorage, |         allElements: ElementStorage, | ||||||
|  | @ -145,7 +145,7 @@ export default class MultiApply extends Toggle { | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         const isShown: UIEventSource<boolean> = p.state.osmConnection.isLoggedIn.map(loggedIn => { |         const isShown: Store<boolean> = p.state.osmConnection.isLoggedIn.map(loggedIn => { | ||||||
|             return loggedIn && p.featureIds.data.length > 0 |             return loggedIn && p.featureIds.data.length > 0 | ||||||
|         }, [p.featureIds]) |         }, [p.featureIds]) | ||||||
|         super(new Combine(elems), undefined, isShown); |         super(new Combine(elems), undefined, isShown); | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| import Combine from "../Base/Combine"; | import Combine from "../Base/Combine"; | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {Store, Stores, UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import {SlideShow} from "../Image/SlideShow"; | import {SlideShow} from "../Image/SlideShow"; | ||||||
| import Toggle from "../Input/Toggle"; | import {ClickableToggle} from "../Input/Toggle"; | ||||||
| import Loading from "../Base/Loading"; | import Loading from "../Base/Loading"; | ||||||
| import {AttributedImage} from "../Image/AttributedImage"; | import {AttributedImage} from "../Image/AttributedImage"; | ||||||
| import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders"; | import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders"; | ||||||
|  | @ -15,8 +15,6 @@ import {SubtleButton} from "../Base/SubtleButton"; | ||||||
| import {GeoOperations} from "../../Logic/GeoOperations"; | import {GeoOperations} from "../../Logic/GeoOperations"; | ||||||
| import {ElementStorage} from "../../Logic/ElementStorage"; | import {ElementStorage} from "../../Logic/ElementStorage"; | ||||||
| import Lazy from "../Base/Lazy"; | import Lazy from "../Base/Lazy"; | ||||||
| import {Utils} from "../../Utils"; |  | ||||||
| import beginningOfLine = Mocha.reporters.Base.cursor.beginningOfLine; |  | ||||||
| 
 | 
 | ||||||
| export interface P4CPicture { | export interface P4CPicture { | ||||||
|     pictureUrl: string, |     pictureUrl: string, | ||||||
|  | @ -42,7 +40,7 @@ export interface NearbyImageOptions { | ||||||
|     // Radius of the upstream search
 |     // Radius of the upstream search
 | ||||||
|     searchRadius?: 500 | number, |     searchRadius?: 500 | number, | ||||||
|     maxDaysOld?: 1095 | number, |     maxDaysOld?: 1095 | number, | ||||||
|     blacklist: UIEventSource<{ url: string }[]>, |     blacklist: Store<{ url: string }[]>, | ||||||
|     shownImagesCount?: UIEventSource<number>, |     shownImagesCount?: UIEventSource<number>, | ||||||
|     towardscenter?: UIEventSource<boolean>; |     towardscenter?: UIEventSource<boolean>; | ||||||
|     allowSpherical?: UIEventSource<boolean> |     allowSpherical?: UIEventSource<boolean> | ||||||
|  | @ -173,7 +171,7 @@ export default class NearbyImages extends Lazy { | ||||||
|         const nearbyImages = state !== undefined ? new ImagesInLoadedDataFetcher(state).fetchAround(options) : [] |         const nearbyImages = state !== undefined ? new ImagesInLoadedDataFetcher(state).fetchAround(options) : [] | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         return UIEventSource.FromPromise<P4CPicture[]>( |         return Stores.FromPromise<P4CPicture[]>( | ||||||
|             picManager.startPicsRetrievalAround(new P4C.LatLng(options.lat, options.lon), options.searchRadius ?? 500, { |             picManager.startPicsRetrievalAround(new P4C.LatLng(options.lat, options.lon), options.searchRadius ?? 500, { | ||||||
|                 mindate: new Date().getTime() - (options.maxDaysOld ?? (3 * 365)) * 24 * 60 * 60 * 1000, |                 mindate: new Date().getTime() - (options.maxDaysOld ?? (3 * 365)) * 24 * 60 * 60 * 1000, | ||||||
|                 towardscenter: false |                 towardscenter: false | ||||||
|  | @ -234,7 +232,7 @@ export default class NearbyImages extends Lazy { | ||||||
|         return new AttributedImage({url: info.thumbUrl, provider, date: new Date(info.date)}) |         return new AttributedImage({url: info.thumbUrl, provider, date: new Date(info.date)}) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     protected asToggle(info: P4CPicture): Toggle { |     protected asToggle(info: P4CPicture): ClickableToggle { | ||||||
|         const imgNonSelected = NearbyImages.asAttributedImage(info); |         const imgNonSelected = NearbyImages.asAttributedImage(info); | ||||||
|         const imageSelected = NearbyImages.asAttributedImage(info); |         const imageSelected = NearbyImages.asAttributedImage(info); | ||||||
| 
 | 
 | ||||||
|  | @ -246,7 +244,7 @@ export default class NearbyImages extends Lazy { | ||||||
|             hoveringCheckmark, |             hoveringCheckmark, | ||||||
|         ]).SetClass("relative block") |         ]).SetClass("relative block") | ||||||
| 
 | 
 | ||||||
|         return new Toggle(selected, nonSelected).SetClass("").ToggleOnClick(); |         return new ClickableToggle(selected, nonSelected).SetClass("").ToggleOnClick(); | ||||||
| 
 | 
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -7,9 +7,8 @@ import Translations from "../i18n/Translations"; | ||||||
| import {Utils} from "../../Utils"; | import {Utils} from "../../Utils"; | ||||||
| import Img from "../Base/Img"; | import Img from "../Base/Img"; | ||||||
| import {SlideShow} from "../Image/SlideShow"; | import {SlideShow} from "../Image/SlideShow"; | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {Stores, UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import {OsmConnection} from "../../Logic/Osm/OsmConnection"; | import {OsmConnection} from "../../Logic/Osm/OsmConnection"; | ||||||
| import {UIElement} from "../UIElement"; |  | ||||||
| import {VariableUiElement} from "../Base/VariableUIElement"; | import {VariableUiElement} from "../Base/VariableUIElement"; | ||||||
| 
 | 
 | ||||||
| export default class NoteCommentElement extends Combine { | export default class NoteCommentElement extends Combine { | ||||||
|  | @ -25,7 +24,7 @@ export default class NoteCommentElement extends Combine { | ||||||
|     }) { |     }) { | ||||||
|         const t = Translations.t.notes; |         const t = Translations.t.notes; | ||||||
| 
 | 
 | ||||||
|         let actionIcon: BaseUIElement = undefined; |         let actionIcon: BaseUIElement; | ||||||
|         if (comment.action === "opened" || comment.action === "reopened") { |         if (comment.action === "opened" || comment.action === "reopened") { | ||||||
|             actionIcon = Svg.note_svg() |             actionIcon = Svg.note_svg() | ||||||
|         } else if (comment.action === "closed") { |         } else if (comment.action === "closed") { | ||||||
|  | @ -41,7 +40,7 @@ export default class NoteCommentElement extends Combine { | ||||||
|             user = new Link(comment.user, comment.user_url ?? "", true) |             user = new Link(comment.user, comment.user_url ?? "", true) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         let userinfo = UIEventSource.FromPromise( Utils.downloadJsonCached("https://www.openstreetmap.org/api/0.6/user/"+comment.uid, 24*60*60*1000)) |         let userinfo = Stores.FromPromise( Utils.downloadJsonCached("https://www.openstreetmap.org/api/0.6/user/"+comment.uid, 24*60*60*1000)) | ||||||
|         let userImg = new VariableUiElement( userinfo.map(userinfo => { |         let userImg = new VariableUiElement( userinfo.map(userinfo => { | ||||||
|             const href = userinfo?.user?.img?.href; |             const href = userinfo?.user?.img?.href; | ||||||
|             if(href !== undefined){ |             if(href !== undefined){ | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {Store, UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import TagRenderingQuestion from "./TagRenderingQuestion"; | import TagRenderingQuestion from "./TagRenderingQuestion"; | ||||||
| import Translations from "../i18n/Translations"; | import Translations from "../i18n/Translations"; | ||||||
| import Combine from "../Base/Combine"; | import Combine from "../Base/Combine"; | ||||||
|  | @ -14,7 +14,7 @@ import Lazy from "../Base/Lazy"; | ||||||
|  */ |  */ | ||||||
| export default class QuestionBox extends VariableUiElement { | export default class QuestionBox extends VariableUiElement { | ||||||
|     public readonly skippedQuestions: UIEventSource<number[]>; |     public readonly skippedQuestions: UIEventSource<number[]>; | ||||||
|     public readonly restingQuestions: UIEventSource<BaseUIElement[]>; |     public readonly restingQuestions: Store<BaseUIElement[]>; | ||||||
| 
 | 
 | ||||||
|     constructor(state, options: { |     constructor(state, options: { | ||||||
|         tagsSource: UIEventSource<any>, |         tagsSource: UIEventSource<any>, | ||||||
|  | @ -81,7 +81,7 @@ export default class QuestionBox extends VariableUiElement { | ||||||
|             return undefined; // The questions are depleted
 |             return undefined; // The questions are depleted
 | ||||||
|         }, [skippedQuestions]); |         }, [skippedQuestions]); | ||||||
| 
 | 
 | ||||||
|         const questionsToAsk: UIEventSource<BaseUIElement[]> = tagsSource.map(tags => { |         const questionsToAsk: Store<BaseUIElement[]> = tagsSource.map(tags => { | ||||||
|             if (tags === undefined) { |             if (tags === undefined) { | ||||||
|                 return []; |                 return []; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {Store, UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import Translations from "../i18n/Translations"; | import Translations from "../i18n/Translations"; | ||||||
| import {OsmConnection} from "../../Logic/Osm/OsmConnection"; | import {OsmConnection} from "../../Logic/Osm/OsmConnection"; | ||||||
| import Toggle from "../Input/Toggle"; | import Toggle from "../Input/Toggle"; | ||||||
|  | @ -6,7 +6,7 @@ import BaseUIElement from "../BaseUIElement"; | ||||||
| 
 | 
 | ||||||
| export class SaveButton extends Toggle { | export class SaveButton extends Toggle { | ||||||
| 
 | 
 | ||||||
|     constructor(value: UIEventSource<any>, osmConnection: OsmConnection, textEnabled ?: BaseUIElement, textDisabled ?: BaseUIElement) { |     constructor(value: Store<any>, osmConnection: OsmConnection, textEnabled ?: BaseUIElement, textDisabled ?: BaseUIElement) { | ||||||
|         if (value === undefined) { |         if (value === undefined) { | ||||||
|             throw "No event source for savebutton, something is wrong" |             throw "No event source for savebutton, something is wrong" | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -78,7 +78,7 @@ export default class SplitRoadWizard extends Toggle { | ||||||
| 
 | 
 | ||||||
|         // Datalayer displaying the road and the cut points (if any)
 |         // Datalayer displaying the road and the cut points (if any)
 | ||||||
|         new ShowDataMultiLayer({ |         new ShowDataMultiLayer({ | ||||||
|             features: new StaticFeatureSource([roadElement], false), |             features: StaticFeatureSource.fromGeojson([roadElement]), | ||||||
|             layers: state.filteredLayers, |             layers: state.filteredLayers, | ||||||
|             leafletMap: miniMap.leafletMap, |             leafletMap: miniMap.leafletMap, | ||||||
|             zoomToFeatures: true, |             zoomToFeatures: true, | ||||||
|  | @ -86,7 +86,7 @@ export default class SplitRoadWizard extends Toggle { | ||||||
|         }) |         }) | ||||||
| 
 | 
 | ||||||
|         new ShowDataLayer({ |         new ShowDataLayer({ | ||||||
|             features: new StaticFeatureSource(splitPoints, true), |             features: new StaticFeatureSource(splitPoints), | ||||||
|             leafletMap: miniMap.leafletMap, |             leafletMap: miniMap.leafletMap, | ||||||
|             zoomToFeatures: false, |             zoomToFeatures: false, | ||||||
|             layerToShow: SplitRoadWizard.splitLayerStyling, |             layerToShow: SplitRoadWizard.splitLayerStyling, | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ import Translations from "../i18n/Translations"; | ||||||
| import {VariableUiElement} from "../Base/VariableUIElement"; | import {VariableUiElement} from "../Base/VariableUIElement"; | ||||||
| import BaseUIElement from "../BaseUIElement"; | import BaseUIElement from "../BaseUIElement"; | ||||||
| import {FixedUiElement} from "../Base/FixedUiElement"; | import {FixedUiElement} from "../Base/FixedUiElement"; | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {Store, UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import {SubtleButton} from "../Base/SubtleButton"; | import {SubtleButton} from "../Base/SubtleButton"; | ||||||
| import Combine from "../Base/Combine"; | import Combine from "../Base/Combine"; | ||||||
| import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"; | import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"; | ||||||
|  | @ -40,7 +40,7 @@ export default class TagApplyButton implements AutoAction { | ||||||
|     ]; |     ]; | ||||||
|     public readonly example = "`{tag_apply(survey_date=$_now:date, Surveyed today!)}`, `{tag_apply(addr:street=$addr:street, Apply the address, apply_icon.svg, _closest_osm_id)"; |     public readonly example = "`{tag_apply(survey_date=$_now:date, Surveyed today!)}`, `{tag_apply(addr:street=$addr:street, Apply the address, apply_icon.svg, _closest_osm_id)"; | ||||||
| 
 | 
 | ||||||
|     public static generateTagsToApply(spec: string, tagSource: UIEventSource<any>): UIEventSource<Tag[]> { |     public static generateTagsToApply(spec: string, tagSource: Store<any>): Store<Tag[]> { | ||||||
| 
 | 
 | ||||||
|         const tgsSpec = spec.split(";").map(spec => { |         const tgsSpec = spec.split(";").map(spec => { | ||||||
|             const kv = spec.split("=").map(s => s.trim()); |             const kv = spec.split("=").map(s => s.trim()); | ||||||
|  |  | ||||||
|  | @ -2,7 +2,6 @@ import {UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import {Utils} from "../../Utils"; | import {Utils} from "../../Utils"; | ||||||
| import BaseUIElement from "../BaseUIElement"; | import BaseUIElement from "../BaseUIElement"; | ||||||
| import {VariableUiElement} from "../Base/VariableUIElement"; | import {VariableUiElement} from "../Base/VariableUIElement"; | ||||||
| import List from "../Base/List"; |  | ||||||
| import {SubstitutedTranslation} from "../SubstitutedTranslation"; | import {SubstitutedTranslation} from "../SubstitutedTranslation"; | ||||||
| import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"; | import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"; | ||||||
| import Combine from "../Base/Combine"; | import Combine from "../Base/Combine"; | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {Store, Stores, UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import Combine from "../Base/Combine"; | import Combine from "../Base/Combine"; | ||||||
| import {InputElement} from "../Input/InputElement"; | import {InputElement, ReadonlyInputElement} from "../Input/InputElement"; | ||||||
| import ValidatedTextField from "../Input/ValidatedTextField"; | import ValidatedTextField from "../Input/ValidatedTextField"; | ||||||
| import {FixedInputElement} from "../Input/FixedInputElement"; | import {FixedInputElement} from "../Input/FixedInputElement"; | ||||||
| import {RadioButton} from "../Input/RadioButton"; | import {RadioButton} from "../Input/RadioButton"; | ||||||
|  | @ -45,14 +45,14 @@ export default class TagRenderingQuestion extends Combine { | ||||||
|                     units?: Unit[], |                     units?: Unit[], | ||||||
|                     afterSave?: () => void, |                     afterSave?: () => void, | ||||||
|                     cancelButton?: BaseUIElement, |                     cancelButton?: BaseUIElement, | ||||||
|                     saveButtonConstr?: (src: UIEventSource<TagsFilter>) => BaseUIElement, |                     saveButtonConstr?: (src: Store<TagsFilter>) => BaseUIElement, | ||||||
|                     bottomText?: (src: UIEventSource<TagsFilter>) => BaseUIElement |                     bottomText?: (src: Store<TagsFilter>) => BaseUIElement | ||||||
|                 } |                 } | ||||||
|     ) { |     ) { | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         const applicableMappingsSrc = |         const applicableMappingsSrc = | ||||||
|             UIEventSource.ListStabilized(tags.map(tags => { |             Stores.ListStabilized(tags.map(tags => { | ||||||
|                 const applicableMappings: { if: TagsFilter, icon?: string, then: TypedTranslation<object>, ifnot?: TagsFilter, addExtraTags: Tag[] }[] = [] |                 const applicableMappings: { if: TagsFilter, icon?: string, then: TypedTranslation<object>, ifnot?: TagsFilter, addExtraTags: Tag[] }[] = [] | ||||||
|                 for (const mapping of configuration.mappings ?? []) { |                 for (const mapping of configuration.mappings ?? []) { | ||||||
|                     if (mapping.hideInAnswer === true) { |                     if (mapping.hideInAnswer === true) { | ||||||
|  | @ -81,7 +81,7 @@ export default class TagRenderingQuestion extends Combine { | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         const feedback = new UIEventSource<Translation>(undefined) |         const feedback = new UIEventSource<Translation>(undefined) | ||||||
|         const inputElement: InputElement<TagsFilter> = |         const inputElement: ReadonlyInputElement<TagsFilter> = | ||||||
|             new VariableInputElement(applicableMappingsSrc.map(applicableMappings => |             new VariableInputElement(applicableMappingsSrc.map(applicableMappings => | ||||||
|                 TagRenderingQuestion.GenerateInputElement(state, configuration, applicableMappings, applicableUnit, tags, feedback) |                 TagRenderingQuestion.GenerateInputElement(state, configuration, applicableMappings, applicableUnit, tags, feedback) | ||||||
|             )) |             )) | ||||||
|  | @ -452,8 +452,8 @@ export default class TagRenderingQuestion extends Combine { | ||||||
| 
 | 
 | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     public static CreateTagExplanation(selectedValue: UIEventSource<TagsFilter>, |     public static CreateTagExplanation(selectedValue: Store<TagsFilter>, | ||||||
|                                        tags: UIEventSource<object>, |                                        tags: Store<object>, | ||||||
|                                        state?: {osmConnection?: OsmConnection}){ |                                        state?: {osmConnection?: OsmConnection}){ | ||||||
|         return new VariableUiElement( |         return new VariableUiElement( | ||||||
|             selectedValue.map( |             selectedValue.map( | ||||||
|  |  | ||||||
|  | @ -37,7 +37,6 @@ export default class ReviewForm extends InputElement<Review> { | ||||||
|         const comment = new TextField({ |         const comment = new TextField({ | ||||||
|             placeholder: Translations.t.reviews.write_a_comment.Clone(), |             placeholder: Translations.t.reviews.write_a_comment.Clone(), | ||||||
|             htmlType: "area", |             htmlType: "area", | ||||||
|             value: this._value.map(r => r?.comment), |  | ||||||
|             textAreaRows: 5 |             textAreaRows: 5 | ||||||
|         }) |         }) | ||||||
|         comment.GetValue().addCallback(comment => { |         comment.GetValue().addCallback(comment => { | ||||||
|  | @ -62,10 +61,10 @@ export default class ReviewForm extends InputElement<Review> { | ||||||
|                     new SaveButton( |                     new SaveButton( | ||||||
|                         this._value.map(r => self.IsValid(r)), osmConnection |                         this._value.map(r => self.IsValid(r)), osmConnection | ||||||
|                     ).onClick(() => { |                     ).onClick(() => { | ||||||
|                         reviewIsSaving.setData(true), |                         reviewIsSaving.setData(true); | ||||||
|                             onSave(this._value.data, () => { |                         onSave(this._value.data, () => { | ||||||
|                                 reviewIsSaved.setData(true) |                             reviewIsSaved.setData(true) | ||||||
|                             }); |                         }); | ||||||
|                     }), |                     }), | ||||||
|                     reviewIsSaving |                     reviewIsSaving | ||||||
|                 ), |                 ), | ||||||
|  |  | ||||||
|  | @ -195,7 +195,7 @@ export default class ShowDataLayerImplementation { | ||||||
|                     const tagsSource = this.allElements?.addOrGetElement(feat) ?? new UIEventSource<any>(feat.properties); |                     const tagsSource = this.allElements?.addOrGetElement(feat) ?? new UIEventSource<any>(feat.properties); | ||||||
|                     let offsettedLine; |                     let offsettedLine; | ||||||
|                     tagsSource |                     tagsSource | ||||||
|                         .map(tags => this._layerToShow.lineRendering[feat.lineRenderingIndex].GenerateLeafletStyle(tags), [], undefined, true) |                         .map(tags => this._layerToShow.lineRendering[feat.lineRenderingIndex].GenerateLeafletStyle(tags)) | ||||||
|                         .withEqualityStabilized((a, b) => { |                         .withEqualityStabilized((a, b) => { | ||||||
|                             if (a === b) { |                             if (a === b) { | ||||||
|                                 return true |                                 return true | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| import FeatureSource from "../../Logic/FeatureSource/FeatureSource"; | import FeatureSource from "../../Logic/FeatureSource/FeatureSource"; | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {Store, UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import {ElementStorage} from "../../Logic/ElementStorage"; | import {ElementStorage} from "../../Logic/ElementStorage"; | ||||||
| import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; | import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; | ||||||
| import ScrollableFullScreen from "../Base/ScrollableFullScreen"; | import ScrollableFullScreen from "../Base/ScrollableFullScreen"; | ||||||
|  | @ -10,6 +10,6 @@ export interface ShowDataLayerOptions { | ||||||
|     leafletMap: UIEventSource<L.Map>, |     leafletMap: UIEventSource<L.Map>, | ||||||
|     popup?: undefined | ((tags: UIEventSource<any>, layer: LayerConfig) => ScrollableFullScreen), |     popup?: undefined | ((tags: UIEventSource<any>, layer: LayerConfig) => ScrollableFullScreen), | ||||||
|     zoomToFeatures?: false | boolean, |     zoomToFeatures?: false | boolean, | ||||||
|     doShowLayer?: UIEventSource<boolean>, |     doShowLayer?: Store<boolean>, | ||||||
|     state?: { allElements?: ElementStorage } |     state?: { allElements?: ElementStorage } | ||||||
| } | } | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| import FeatureSource, {Tiled} from "../../Logic/FeatureSource/FeatureSource"; | import FeatureSource, {Tiled} from "../../Logic/FeatureSource/FeatureSource"; | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {Store, UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; | import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; | ||||||
| import ShowDataLayer from "./ShowDataLayer"; | import ShowDataLayer from "./ShowDataLayer"; | ||||||
| import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"; | import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"; | ||||||
|  | @ -18,7 +18,7 @@ export default class ShowTileInfo { | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         const source = options.source |         const source = options.source | ||||||
|         const metaFeature: UIEventSource<any[]> = |         const metaFeature: Store<{feature, freshness: Date}[]> = | ||||||
|             source.features.map(features => { |             source.features.map(features => { | ||||||
|                 const bbox = source.bbox |                 const bbox = source.bbox | ||||||
|                 const [z, x, y] = Tiles.tile_from_index(source.tileIndex) |                 const [z, x, y] = Tiles.tile_from_index(source.tileIndex) | ||||||
|  | @ -47,12 +47,12 @@ export default class ShowTileInfo { | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|                 const center = GeoOperations.centerpoint(box) |                 const center = GeoOperations.centerpoint(box) | ||||||
|                 return [box, center] |                 return [box, center].map(feature => ({feature, freshness: new Date()})) | ||||||
|             }) |             }) | ||||||
| 
 | 
 | ||||||
|         new ShowDataLayer({ |         new ShowDataLayer({ | ||||||
|             layerToShow: ShowTileInfo.styling, |             layerToShow: ShowTileInfo.styling, | ||||||
|             features: new StaticFeatureSource(metaFeature, false), |             features: new StaticFeatureSource(metaFeature), | ||||||
|             leafletMap: options.leafletMap, |             leafletMap: options.leafletMap, | ||||||
|             doShowLayer: options.doShowLayer, |             doShowLayer: options.doShowLayer, | ||||||
|             state: State.state, |             state: State.state, | ||||||
|  |  | ||||||
|  | @ -141,7 +141,7 @@ export class TileHierarchyAggregator implements FeatureSource { | ||||||
|                 return empty |                 return empty | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             const features = [] |             const features: {feature: any, freshness: Date}[] = [] | ||||||
|             self.visitSubTiles(aggr => { |             self.visitSubTiles(aggr => { | ||||||
|                 if (aggr.showCount < cutoff) { |                 if (aggr.showCount < cutoff) { | ||||||
|                     return false |                     return false | ||||||
|  | @ -156,7 +156,7 @@ export class TileHierarchyAggregator implements FeatureSource { | ||||||
|             return features |             return features | ||||||
|         }, [this.updateSignal.stabilized(500)]) |         }, [this.updateSignal.stabilized(500)]) | ||||||
| 
 | 
 | ||||||
|         return new StaticFeatureSource(features, true); |         return new StaticFeatureSource(features); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private update() { |     private update() { | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import {UIEventSource} from "../Logic/UIEventSource"; | import {Store, UIEventSource} from "../Logic/UIEventSource"; | ||||||
| import {VariableUiElement} from "./Base/VariableUIElement"; | import {VariableUiElement} from "./Base/VariableUIElement"; | ||||||
| import LiveQueryHandler from "../Logic/Web/LiveQueryHandler"; | import LiveQueryHandler from "../Logic/Web/LiveQueryHandler"; | ||||||
| import {ImageCarousel} from "./Image/ImageCarousel"; | import {ImageCarousel} from "./Image/ImageCarousel"; | ||||||
|  | @ -207,7 +207,7 @@ class NearbyImageVis implements SpecialVisualization { | ||||||
|         const nearby = new Lazy(() => { |         const nearby = new Lazy(() => { | ||||||
|             const towardsCenter = new CheckBox(t.onlyTowards, false) |             const towardsCenter = new CheckBox(t.onlyTowards, false) | ||||||
|              |              | ||||||
|             const radiusValue=   state?.osmConnection?.GetPreference("nearby-images-radius","300").map(s => Number(s), [], i => ""+i) ?? new UIEventSource(300); |             const radiusValue=   state?.osmConnection?.GetPreference("nearby-images-radius","300").sync(s => Number(s), [], i => ""+i) ?? new UIEventSource(300); | ||||||
| 
 | 
 | ||||||
|             const radius = new Slider(25, 500, {value:  |             const radius = new Slider(25, 500, {value:  | ||||||
|                     radiusValue, step: 25}) |                     radiusValue, step: 25}) | ||||||
|  | @ -453,7 +453,7 @@ export default class SpecialVisualizations { | ||||||
|                         const keys = [...args] |                         const keys = [...args] | ||||||
|                         keys.splice(0, 1) |                         keys.splice(0, 1) | ||||||
|                         const featureStore = state.allElements.ContainingFeatures |                         const featureStore = state.allElements.ContainingFeatures | ||||||
|                         const featuresToShow: UIEventSource<{ freshness: Date, feature: any }[]> = tagSource.map(properties => { |                         const featuresToShow: Store<{ freshness: Date, feature: any }[]> = tagSource.map(properties => { | ||||||
|                             const values: string[] = Utils.NoNull(keys.map(key => properties[key])) |                             const values: string[] = Utils.NoNull(keys.map(key => properties[key])) | ||||||
|                             const features: { freshness: Date, feature: any }[] = [] |                             const features: { freshness: Date, feature: any }[] = [] | ||||||
|                             for (const value of values) { |                             for (const value of values) { | ||||||
|  | @ -507,7 +507,7 @@ export default class SpecialVisualizations { | ||||||
|                                 leafletMap: minimap["leafletMap"], |                                 leafletMap: minimap["leafletMap"], | ||||||
|                                 zoomToFeatures: true, |                                 zoomToFeatures: true, | ||||||
|                                 layers: state.filteredLayers, |                                 layers: state.filteredLayers, | ||||||
|                                 features: new StaticFeatureSource(featuresToShow, true) |                                 features: new StaticFeatureSource(featuresToShow) | ||||||
|                             } |                             } | ||||||
|                         ) |                         ) | ||||||
| 
 | 
 | ||||||
|  | @ -553,7 +553,7 @@ export default class SpecialVisualizations { | ||||||
|                                 leafletMap: minimap["leafletMap"], |                                 leafletMap: minimap["leafletMap"], | ||||||
|                                 zoomToFeatures: true, |                                 zoomToFeatures: true, | ||||||
|                                 layerToShow: new LayerConfig(left_right_style_json, "all_known_layers", true), |                                 layerToShow: new LayerConfig(left_right_style_json, "all_known_layers", true), | ||||||
|                                 features: new StaticFeatureSource([copy], false), |                                 features: StaticFeatureSource.fromGeojson([copy]), | ||||||
|                                 state |                                 state | ||||||
|                             } |                             } | ||||||
|                         ) |                         ) | ||||||
|  | @ -683,7 +683,7 @@ export default class SpecialVisualizations { | ||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
| 
 | 
 | ||||||
|                         const listSource: UIEventSource<string[]> = tagSource |                         const listSource: Store<string[]> = tagSource | ||||||
|                             .map(tags => { |                             .map(tags => { | ||||||
|                                 try { |                                 try { | ||||||
|                                     const value = tags[args[0]] |                                     const value = tags[args[0]] | ||||||
|  | @ -801,7 +801,7 @@ export default class SpecialVisualizations { | ||||||
|                         const text = args[2] |                         const text = args[2] | ||||||
|                         const autoapply = args[3]?.toLowerCase() === "true" |                         const autoapply = args[3]?.toLowerCase() === "true" | ||||||
|                         const overwrite = args[4]?.toLowerCase() === "true" |                         const overwrite = args[4]?.toLowerCase() === "true" | ||||||
|                         const featureIds: UIEventSource<string[]> = tagsSource.map(tags => { |                         const featureIds: Store<string[]> = tagsSource.map(tags => { | ||||||
|                             const ids = tags[featureIdsKey] |                             const ids = tags[featureIdsKey] | ||||||
|                             try { |                             try { | ||||||
|                                 if (ids === undefined) { |                                 if (ids === undefined) { | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import {UIEventSource} from "../Logic/UIEventSource"; | import {Store, UIEventSource} from "../Logic/UIEventSource"; | ||||||
| import {Translation} from "./i18n/Translation"; | import {Translation} from "./i18n/Translation"; | ||||||
| import Locale from "./i18n/Locale"; | import Locale from "./i18n/Locale"; | ||||||
| import {FixedUiElement} from "./Base/FixedUiElement"; | import {FixedUiElement} from "./Base/FixedUiElement"; | ||||||
|  |  | ||||||
|  | @ -91,7 +91,7 @@ export default class WikidataPreviewBox extends VariableUiElement { | ||||||
|         let link = new Link( |         let link = new Link( | ||||||
|             new Combine([ |             new Combine([ | ||||||
|                 wikidata.id, |                 wikidata.id, | ||||||
|                 options.noImages ? wikidata.id : Svg.wikidata_svg().SetStyle("width: 2.5rem").SetClass("block") |                 options?.noImages ? wikidata.id : Svg.wikidata_svg().SetStyle("width: 2.5rem").SetClass("block") | ||||||
|             ]).SetClass("flex"), |             ]).SetClass("flex"), | ||||||
|             Wikidata.IdToArticle(wikidata.id), true)?.SetClass("must-link") |             Wikidata.IdToArticle(wikidata.id), true)?.SetClass("must-link") | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ import Combine from "../Base/Combine"; | ||||||
| import {InputElement} from "../Input/InputElement"; | import {InputElement} from "../Input/InputElement"; | ||||||
| import {TextField} from "../Input/TextField"; | import {TextField} from "../Input/TextField"; | ||||||
| import Translations from "../i18n/Translations"; | import Translations from "../i18n/Translations"; | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {ImmutableStore, Store, Stores, UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import Wikidata, {WikidataResponse} from "../../Logic/Web/Wikidata"; | import Wikidata, {WikidataResponse} from "../../Logic/Web/Wikidata"; | ||||||
| import Locale from "../i18n/Locale"; | import Locale from "../i18n/Locale"; | ||||||
| import {VariableUiElement} from "../Base/VariableUIElement"; | import {VariableUiElement} from "../Base/VariableUIElement"; | ||||||
|  | @ -10,6 +10,7 @@ import WikidataPreviewBox from "./WikidataPreviewBox"; | ||||||
| import Title from "../Base/Title"; | import Title from "../Base/Title"; | ||||||
| import WikipediaBox from "./WikipediaBox"; | import WikipediaBox from "./WikipediaBox"; | ||||||
| import Svg from "../../Svg"; | import Svg from "../../Svg"; | ||||||
|  | import Loading from "../Base/Loading"; | ||||||
| 
 | 
 | ||||||
| export default class WikidataSearchBox extends InputElement<string> { | export default class WikidataSearchBox extends InputElement<string> { | ||||||
| 
 | 
 | ||||||
|  | @ -51,48 +52,55 @@ export default class WikidataSearchBox extends InputElement<string> { | ||||||
|         }) |         }) | ||||||
|         const selectedWikidataId = this.wikidataId |         const selectedWikidataId = this.wikidataId | ||||||
| 
 | 
 | ||||||
|         const lastSearchResults = new UIEventSource<WikidataResponse[]>([]) |         const tooShort = new ImmutableStore<{success: WikidataResponse[]}>({success: undefined}) | ||||||
|         const searchFailMessage = new UIEventSource(undefined) |         const searchResult: Store<{success?: WikidataResponse[], error?: any}> = searchField.GetValue().bind( | ||||||
|         searchField.GetValue().addCallbackAndRunD(searchText => { |             searchText => { | ||||||
|             if (searchText.length < 3) { |                 if (searchText.length < 3) { | ||||||
|                 return; |                     return tooShort; | ||||||
|  |                 } | ||||||
|  |                 const lang = Locale.language.data | ||||||
|  |                 const key = lang + ":" + searchText | ||||||
|  |                 let promise = WikidataSearchBox._searchCache.get(key) | ||||||
|  |                 if (promise === undefined) { | ||||||
|  |                     promise = Wikidata.searchAndFetch(searchText, { | ||||||
|  |                             lang, | ||||||
|  |                             maxCount: 5, | ||||||
|  |                             notInstanceOf: this.notInstanceOf, | ||||||
|  |                             instanceOf: this.instanceOf | ||||||
|  |                         } | ||||||
|  |                     ) | ||||||
|  |                     WikidataSearchBox._searchCache.set(key, promise) | ||||||
|  |                 } | ||||||
|  |                 return Stores.FromPromiseWithErr(promise) | ||||||
|             } |             } | ||||||
|             searchFailMessage.setData(undefined) |         ) | ||||||
|  |    | ||||||
| 
 | 
 | ||||||
|             const lang = Locale.language.data |         const previews = new VariableUiElement(searchResult.map(searchResultsOrFail => { | ||||||
|             const key = lang + ":" + searchText |  | ||||||
|             let promise = WikidataSearchBox._searchCache.get(key) |  | ||||||
|             if (promise === undefined) { |  | ||||||
|                 promise = Wikidata.searchAndFetch(searchText, { |  | ||||||
|                         lang, |  | ||||||
|                         maxCount: 5, |  | ||||||
|                         notInstanceOf: this.notInstanceOf, |  | ||||||
|                         instanceOf: this.instanceOf |  | ||||||
|                     } |  | ||||||
|                 ) |  | ||||||
|                 WikidataSearchBox._searchCache.set(key, promise) |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             lastSearchResults.WaitForPromise(promise, err => searchFailMessage.setData(err)) |  | ||||||
| 
 |  | ||||||
|         }) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|         const previews = new VariableUiElement(lastSearchResults.map(searchResults => { |  | ||||||
|             if (searchFailMessage.data !== undefined) { |  | ||||||
|                 return new Combine([Translations.t.general.wikipedia.failed.Clone().SetClass("alert"), searchFailMessage.data]) |  | ||||||
|             } |  | ||||||
| 
 | 
 | ||||||
|             if (searchField.GetValue().data.length === 0) { |             if (searchField.GetValue().data.length === 0) { | ||||||
|                 return Translations.t.general.wikipedia.doSearch |                 return Translations.t.general.wikipedia.doSearch | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  |             if (searchField.GetValue().data.length < 3) { | ||||||
|  |                 return Translations.t.general.wikipedia.searchToShort | ||||||
|  |             } | ||||||
|  |              | ||||||
|  |             if( searchResultsOrFail === undefined) { | ||||||
|  |                 return new Loading(Translations.t.general.loading) | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (searchResultsOrFail.error !== undefined) { | ||||||
|  |                 return new Combine([Translations.t.general.wikipedia.failed.Clone().SetClass("alert"), searchResultsOrFail.error]) | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |             const searchResults = searchResultsOrFail.success; | ||||||
|             if (searchResults.length === 0) { |             if (searchResults.length === 0) { | ||||||
|                 return Translations.t.general.wikipedia.noResults.Subs({search: searchField.GetValue().data ?? ""}) |                 return Translations.t.general.wikipedia.noResults.Subs({search: searchField.GetValue().data ?? ""}) | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|             return new Combine(searchResults.map(wikidataresponse => { |             return new Combine(searchResults.map(wikidataresponse => { | ||||||
|                 const el = WikidataPreviewBox.WikidataResponsePreview(wikidataresponse).SetClass("rounded-xl p-1 sm:p-2 md:p-3 m-px border-2 sm:border-4 transition-colors") |                 const el = WikidataPreviewBox.WikidataResponsePreview(wikidataresponse).SetClass("rounded-xl p-1 sm:p-2 md:p-3 m-px border-2 sm:border-4 transition-colors") | ||||||
|                 el.onClick(() => { |                 el.onClick(() => { | ||||||
|  | @ -110,7 +118,7 @@ export default class WikidataSearchBox extends InputElement<string> { | ||||||
| 
 | 
 | ||||||
|             })).SetClass("flex flex-col") |             })).SetClass("flex flex-col") | ||||||
| 
 | 
 | ||||||
|         }, [searchFailMessage])) |         }, [searchField.GetValue()])) | ||||||
| 
 | 
 | ||||||
|         const full = new Combine([ |         const full = new Combine([ | ||||||
|             new Title(Translations.t.general.wikipedia.searchWikidata, 3).SetClass("m-2"), |             new Title(Translations.t.general.wikipedia.searchWikidata, 3).SetClass("m-2"), | ||||||
|  |  | ||||||
|  | @ -8,7 +8,7 @@ import Title from "../Base/Title"; | ||||||
| import Wikipedia from "../../Logic/Web/Wikipedia"; | import Wikipedia from "../../Logic/Web/Wikipedia"; | ||||||
| import Wikidata, {WikidataResponse} from "../../Logic/Web/Wikidata"; | import Wikidata, {WikidataResponse} from "../../Logic/Web/Wikidata"; | ||||||
| import {TabbedComponent} from "../Base/TabbedComponent"; | import {TabbedComponent} from "../Base/TabbedComponent"; | ||||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | import {Store, UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import Loading from "../Base/Loading"; | import Loading from "../Base/Loading"; | ||||||
| import {FixedUiElement} from "../Base/FixedUiElement"; | import {FixedUiElement} from "../Base/FixedUiElement"; | ||||||
| import Translations from "../i18n/Translations"; | import Translations from "../i18n/Translations"; | ||||||
|  | @ -128,7 +128,7 @@ export default class WikipediaBox extends Combine { | ||||||
| 
 | 
 | ||||||
|         const wp = Translations.t.general.wikipedia; |         const wp = Translations.t.general.wikipedia; | ||||||
| 
 | 
 | ||||||
|         const wikiLink: UIEventSource<[string, string, WikidataResponse] | "loading" | "failed" | ["no page", WikidataResponse]> = |         const wikiLink: Store<[string, string, WikidataResponse] | "loading" | "failed" | ["no page", WikidataResponse]> = | ||||||
|             Wikidata.LoadWikidataEntry(wikidataId) |             Wikidata.LoadWikidataEntry(wikidataId) | ||||||
|                 .map(maybewikidata => { |                 .map(maybewikidata => { | ||||||
|                     if (maybewikidata === undefined) { |                     if (maybewikidata === undefined) { | ||||||
|  |  | ||||||
|  | @ -27,7 +27,7 @@ | ||||||
|           ] |           ] | ||||||
|         }, |         }, | ||||||
|         "type!=multipolygon", |         "type!=multipolygon", | ||||||
|         "area!=yes"  |         "area!=yes" | ||||||
|       ] |       ] | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|  | @ -70,7 +70,7 @@ | ||||||
|             "nl": "Dit is een woonerf en heeft dus een maximale snelheid van 20km/h" |             "nl": "Dit is een woonerf en heeft dus een maximale snelheid van 20km/h" | ||||||
|           }, |           }, | ||||||
|           "icon": { |           "icon": { | ||||||
|             "path":"./assets/layers/maxspeed/living_street_be.svg", |             "path": "./assets/layers/maxspeed/living_street_be.svg", | ||||||
|             "size": "large" |             "size": "large" | ||||||
|           }, |           }, | ||||||
|           "hideInAnswer": "_country!=be" |           "hideInAnswer": "_country!=be" | ||||||
|  |  | ||||||
|  | @ -211,6 +211,10 @@ | ||||||
|                 "if": "theme=maps", |                 "if": "theme=maps", | ||||||
|                 "then": "./assets/themes/maps/logo.svg" |                 "then": "./assets/themes/maps/logo.svg" | ||||||
|               }, |               }, | ||||||
|  |               { | ||||||
|  |                 "if": "theme=maxspeed", | ||||||
|  |                 "then": "./assets/themes/maxspeed/maxspeed_logo.svg" | ||||||
|  |               }, | ||||||
|               { |               { | ||||||
|                 "if": "theme=nature", |                 "if": "theme=nature", | ||||||
|                 "then": "./assets/themes/nature/logo.svg" |                 "then": "./assets/themes/nature/logo.svg" | ||||||
|  |  | ||||||
|  | @ -112,7 +112,7 @@ async function downloadRaw(targetdir: string, r: TileRange, theme: LayoutConfig, | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|                 console.log("Got the response - writing to ", filename) |                 console.log("Got the response - writing ",json.elements.length," elements to ", filename) | ||||||
|                 writeFileSync(filename, JSON.stringify(json, null, "  ")); |                 writeFileSync(filename, JSON.stringify(json, null, "  ")); | ||||||
|             } catch (err) { |             } catch (err) { | ||||||
|                 console.log(url) |                 console.log(url) | ||||||
|  | @ -172,7 +172,7 @@ function loadAllTiles(targetdir: string, r: TileRange, theme: LayoutConfig, extr | ||||||
|             allFeatures.push(...geojson.features) |             allFeatures.push(...geojson.features) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     return new StaticFeatureSource(allFeatures, false) |     return StaticFeatureSource.fromGeojson(allFeatures) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  |  | ||||||
|  | @ -127,7 +127,7 @@ async function main(args: string[]) { | ||||||
|         delete f.bbox |         delete f.bbox | ||||||
|     } |     } | ||||||
|     TiledFeatureSource.createHierarchy( |     TiledFeatureSource.createHierarchy( | ||||||
|         new StaticFeatureSource(allFeatures, false), |         StaticFeatureSource.fromGeojson(allFeatures), | ||||||
|         { |         { | ||||||
|             minZoomLevel: zoomlevel, |             minZoomLevel: zoomlevel, | ||||||
|             maxZoomLevel: zoomlevel, |             maxZoomLevel: zoomlevel, | ||||||
|  |  | ||||||
|  | @ -43,7 +43,7 @@ describe("GenerateCache", () => { | ||||||
|                 "51.15423567022531", "3.250579833984375", "51.162821593316934", "3.262810707092285", |                 "51.15423567022531", "3.250579833984375", "51.162821593316934", "3.262810707092285", | ||||||
|                 "--generate-point-overview", "nature_reserve,visitor_information_centre" |                 "--generate-point-overview", "nature_reserve,visitor_information_centre" | ||||||
|             ]) |             ]) | ||||||
|             await ScriptUtils.sleep(100) |             await ScriptUtils.sleep(500) | ||||||
|             const birdhides = JSON.parse(readFileSync("/tmp/np-cache/natuurpunt_birdhide_12_2085_1368.geojson", "UTF8")) |             const birdhides = JSON.parse(readFileSync("/tmp/np-cache/natuurpunt_birdhide_12_2085_1368.geojson", "UTF8")) | ||||||
|             expect(birdhides.features.length).deep.equal(5) |             expect(birdhides.features.length).deep.equal(5) | ||||||
|             expect(birdhides.features.some(f => f.properties.id === "node/5158056232"), "Didn't find birdhide node/5158056232 ").true |             expect(birdhides.features.some(f => f.properties.id === "node/5158056232"), "Didn't find birdhide node/5158056232 ").true | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue