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