From 0241f89d3deba8d8e1314b05a9f72b1556c8b773 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 6 Apr 2023 01:33:08 +0200 Subject: [PATCH] refactoring: fix basic flow to add a new point --- Logic/Actors/GeoLocationHandler.ts | 2 +- Logic/Actors/SelectedElementTagsUpdater.ts | 3 +- Logic/Actors/TitleHandler.ts | 4 + Logic/BBox.ts | 41 ++- .../Actors/FeaturePropertiesStore.ts | 2 +- Logic/FeatureSource/Actors/GeoIndexedStore.ts | 2 +- .../Actors/SaveFeatureSourceToLocalStorage.ts | 2 +- Logic/FeatureSource/FeatureSource.ts | 2 +- .../PerLayerFeatureSourceSplitter.ts | 5 +- .../Sources/ClippedFeatureSource.ts | 2 +- .../Sources/FeatureSourceMerger.ts | 2 +- .../Sources/FilteringFeatureSource.ts | 20 +- Logic/FeatureSource/Sources/GeoJsonSource.ts | 2 +- .../Sources/LastClickFeatureSource.ts | 23 +- Logic/FeatureSource/Sources/LayoutSource.ts | 11 +- .../NewGeometryFromChangesFeatureSource.ts | 2 +- .../Sources/OverpassFeatureSource.ts | 2 +- .../Sources/SnappingFeatureSource.ts | 74 +++++- .../Sources/StaticFeatureSource.ts | 2 +- .../Sources/TouchesBboxFeatureSource.ts | 2 +- .../DynamicGeoJsonTileSource.ts | 1 + .../TiledFeatureSource/DynamicTileSource.ts | 6 +- .../FullNodeDatabaseSource.ts | 2 +- Logic/GeoOperations.ts | 24 +- .../CreateMultiPolygonWithPointReuseAction.ts | 2 +- Logic/Osm/Actions/CreateNewNodeAction.ts | 6 +- .../Actions/CreateWayWithPointReuseAction.ts | 2 +- Logic/Osm/Actions/ReplaceGeometryAction.ts | 2 +- Logic/Osm/Changes.ts | 2 +- Logic/Osm/OsmConnection.ts | 2 +- Logic/Osm/OsmObject.ts | 3 +- Logic/State/LayerState.ts | 18 +- Logic/State/MapState.ts | 2 +- Logic/State/UserRelatedState.ts | 2 +- Logic/Tags/Tag.ts | 1 - Models/Constants.ts | 2 +- Models/FilteredLayer.ts | 121 ++++++++- Models/MapProperties.ts | 1 + Models/MenuState.ts | 64 +++++ Models/ThemeConfig/FilterConfig.ts | 15 +- Models/ThemeViewState.ts | 46 +++- Models/TileRange.ts | 2 +- UI/Base/DragInvitation.svelte | 9 +- UI/Base/FloatOver.svelte | 9 + UI/Base/LoginButton.svelte | 15 ++ UI/Base/LoginToggle.svelte | 45 ++++ UI/Base/MapControlButton.svelte | 2 +- UI/Base/SubtleButton.svelte | 9 +- UI/Base/SvelteUIElement.ts | 8 +- UI/Base/TabbedGroup.svelte | 68 +++++ UI/BigComponents/AddNewMarker.ts | 75 ------ UI/BigComponents/BackgroundMapSwitch.ts | 223 ---------------- UI/BigComponents/FilterView.ts | 214 +--------------- UI/BigComponents/Filterview.svelte | 37 ++- UI/BigComponents/FilterviewWithFields.svelte | 57 +++++ UI/BigComponents/FullWelcomePaneWithTabs.ts | 9 - UI/BigComponents/Geosearch.svelte | 6 +- UI/BigComponents/LeftControls.ts | 32 +-- UI/BigComponents/NewPointLocationInput.svelte | 100 ++++++++ UI/BigComponents/ShareScreen.ts | 26 +- UI/BigComponents/SimpleAddUI.ts | 168 +----------- UI/DefaultGUI.ts | 67 +---- UI/ImportFlow/ConflationChecker.ts | 73 +++--- UI/ImportFlow/MapPreview.ts | 10 +- UI/Input/LengthInput.ts | 32 +-- UI/InputElement/Helpers/LocationInput.svelte | 16 +- UI/InputElement/ValidatedInput.svelte | 10 +- UI/Map/MapLibreAdaptor.ts | 63 +++-- UI/Map/ShowDataLayer.ts | 23 +- UI/Map/ShowDataLayerOptions.ts | 2 +- UI/NewPoint/ConfirmLocationOfPoint.ts | 59 +---- UI/Popup/AddNewPoint/AddNewPoint.svelte | 239 ++++++++++++++++++ UI/Popup/AddNewPoint/PresetList.svelte | 88 +++++++ UI/Popup/CreateNewNote.svelte | 139 ++++++++++ UI/Popup/FeatureInfoBox.ts | 2 +- UI/Popup/ImportButton.ts | 2 +- UI/Popup/MinimapViz.ts | 3 + UI/Popup/NewNoteUi.ts | 124 --------- UI/Popup/TagHint.svelte | 35 +++ .../TagRendering/SpecialTranslation.svelte | 14 +- .../TagRendering/TagRenderingAnswer.svelte | 3 + .../TagRendering/TagRenderingQuestion.svelte | 8 +- UI/SpecialVisualization.ts | 21 +- UI/SpecialVisualizations.ts | 77 ++++-- UI/Test.svelte | 18 ++ UI/ThemeViewGUI.svelte | 209 ++++++++------- assets/layers/last_click/last_click.json | 64 ++++- .../selected_element/selected_element.json | 7 +- assets/tagRenderings/questions.json | 2 +- css/index-tailwind-output.css | 123 ++++----- langs/en.json | 2 + langs/layers/ca.json | 9 + langs/layers/cs.json | 9 + langs/layers/da.json | 9 + langs/layers/de.json | 9 + langs/layers/en.json | 27 ++ langs/layers/es.json | 9 + langs/layers/fil.json | 12 +- langs/layers/fr.json | 9 + langs/layers/hu.json | 9 + langs/layers/id.json | 9 + langs/layers/it.json | 9 + langs/layers/nb_NO.json | 9 + langs/layers/nl.json | 27 ++ langs/layers/pt.json | 9 + langs/layers/zh_Hant.json | 9 + package.json | 2 +- test.html | 1 - test.ts | 22 +- 109 files changed, 1931 insertions(+), 1446 deletions(-) create mode 100644 Models/MenuState.ts create mode 100644 UI/Base/LoginButton.svelte create mode 100644 UI/Base/LoginToggle.svelte create mode 100644 UI/Base/TabbedGroup.svelte delete mode 100644 UI/BigComponents/AddNewMarker.ts delete mode 100644 UI/BigComponents/BackgroundMapSwitch.ts create mode 100644 UI/BigComponents/FilterviewWithFields.svelte create mode 100644 UI/BigComponents/NewPointLocationInput.svelte create mode 100644 UI/Popup/AddNewPoint/AddNewPoint.svelte create mode 100644 UI/Popup/AddNewPoint/PresetList.svelte create mode 100644 UI/Popup/CreateNewNote.svelte delete mode 100644 UI/Popup/NewNoteUi.ts create mode 100644 UI/Popup/TagHint.svelte create mode 100644 UI/Test.svelte diff --git a/Logic/Actors/GeoLocationHandler.ts b/Logic/Actors/GeoLocationHandler.ts index fe709f3e1..e5bb9ff7f 100644 --- a/Logic/Actors/GeoLocationHandler.ts +++ b/Logic/Actors/GeoLocationHandler.ts @@ -4,7 +4,7 @@ import Constants from "../../Models/Constants" import { GeoLocationPointProperties, GeoLocationState } from "../State/GeoLocationState" import { UIEventSource } from "../UIEventSource" import { Feature, LineString, Point } from "geojson" -import FeatureSource from "../FeatureSource/FeatureSource" +import { FeatureSource } from "../FeatureSource/FeatureSource" import { LocalStorageSource } from "../Web/LocalStorageSource" import { GeoOperations } from "../GeoOperations" import { OsmTags } from "../../Models/OsmFeature" diff --git a/Logic/Actors/SelectedElementTagsUpdater.ts b/Logic/Actors/SelectedElementTagsUpdater.ts index 5bc925cae..7da1309fa 100644 --- a/Logic/Actors/SelectedElementTagsUpdater.ts +++ b/Logic/Actors/SelectedElementTagsUpdater.ts @@ -9,6 +9,7 @@ import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" import SimpleMetaTagger from "../SimpleMetaTagger" import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore" import { Feature } from "geojson" +import { OsmTags } from "../../Models/OsmFeature" export default class SelectedElementTagsUpdater { private static readonly metatags = new Set([ @@ -87,7 +88,7 @@ export default class SelectedElementTagsUpdater { } }) } - private applyUpdate(latestTags: any, id: string) { + private applyUpdate(latestTags: OsmTags, id: string) { const state = this.state try { const leftRightSensitive = state.layoutToUse.isLeftRightSensitive() diff --git a/Logic/Actors/TitleHandler.ts b/Logic/Actors/TitleHandler.ts index 2cad3c993..c5b8dfea0 100644 --- a/Logic/Actors/TitleHandler.ts +++ b/Logic/Actors/TitleHandler.ts @@ -26,11 +26,15 @@ export default class TitleHandler { const tags = selected.properties const layer = selectedLayer.data + if (layer.title === undefined) { + return defaultTitle + } const tagsSource = allElements.getStore(tags.id) ?? new UIEventSource>(tags) const title = new SvelteUIElement(TagRenderingAnswer, { tags: tagsSource, state, + config: layer.title, selectedElement: selectedElement.data, layer, }) diff --git a/Logic/BBox.ts b/Logic/BBox.ts index 7f4214790..3a1dc9e34 100644 --- a/Logic/BBox.ts +++ b/Logic/BBox.ts @@ -138,6 +138,45 @@ export class BBox { return true } + squarify(): BBox { + const w = this.maxLon - this.minLon + const h = this.maxLat - this.minLat + const s = Math.sqrt(w * h) + const lon = (this.maxLon + this.minLon) / 2 + const lat = (this.maxLat + this.minLat) / 2 + // we want to have a more-or-less equal surface, so the new side 's' should be + // w * h = s * s + // The ratio for w is: + + return new BBox([ + [lon - s / 2, lat - s / 2], + [lon + s / 2, lat + s / 2], + ]) + } + + isNearby(location: [number, number], maxRange: number): boolean { + if (this.contains(location)) { + return true + } + const [lon, lat] = location + // We 'project' the point onto the near edges. If they are close to a horizontal _and_ vertical edge, it is nearby + // Vertically nearby: either wihtin minLat range or at most maxRange away + const nearbyVertical = + (this.minLat <= lat && + this.maxLat >= lat && + GeoOperations.distanceBetween(location, [lon, this.minLat]) <= maxRange) || + GeoOperations.distanceBetween(location, [lon, this.maxLat]) <= maxRange + if (!nearbyVertical) { + return false + } + const nearbyHorizontal = + (this.minLon <= lon && + this.maxLon >= lon && + GeoOperations.distanceBetween(location, [this.minLon, lat]) <= maxRange) || + GeoOperations.distanceBetween(location, [this.maxLon, lat]) <= maxRange + return nearbyHorizontal + } + getEast() { return this.maxLon } @@ -214,7 +253,7 @@ export class BBox { * @param zoomlevel */ expandToTileBounds(zoomlevel: number): BBox { - if(zoomlevel === undefined){ + if (zoomlevel === undefined) { return this } const ul = Tiles.embedded_tile(this.minLat, this.minLon, zoomlevel) diff --git a/Logic/FeatureSource/Actors/FeaturePropertiesStore.ts b/Logic/FeatureSource/Actors/FeaturePropertiesStore.ts index 1336ab374..b78cc79d6 100644 --- a/Logic/FeatureSource/Actors/FeaturePropertiesStore.ts +++ b/Logic/FeatureSource/Actors/FeaturePropertiesStore.ts @@ -1,4 +1,4 @@ -import FeatureSource, { IndexedFeatureSource } from "../FeatureSource" +import { FeatureSource, IndexedFeatureSource } from "../FeatureSource" import { UIEventSource } from "../../UIEventSource" /** diff --git a/Logic/FeatureSource/Actors/GeoIndexedStore.ts b/Logic/FeatureSource/Actors/GeoIndexedStore.ts index ec11a9c70..792641bfa 100644 --- a/Logic/FeatureSource/Actors/GeoIndexedStore.ts +++ b/Logic/FeatureSource/Actors/GeoIndexedStore.ts @@ -1,4 +1,4 @@ -import FeatureSource, { FeatureSourceForLayer } from "../FeatureSource" +import { FeatureSource , FeatureSourceForLayer } from "../FeatureSource" import { Feature } from "geojson" import { BBox } from "../../BBox" import { GeoOperations } from "../../GeoOperations" diff --git a/Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts b/Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts index 5e113aaff..938c1ccac 100644 --- a/Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts +++ b/Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts @@ -1,4 +1,4 @@ -import FeatureSource from "../FeatureSource" +import { FeatureSource } from "../FeatureSource" import { Feature } from "geojson" import TileLocalStorage from "./TileLocalStorage" import { GeoOperations } from "../../GeoOperations" diff --git a/Logic/FeatureSource/FeatureSource.ts b/Logic/FeatureSource/FeatureSource.ts index f55cd9592..def7d53e5 100644 --- a/Logic/FeatureSource/FeatureSource.ts +++ b/Logic/FeatureSource/FeatureSource.ts @@ -3,7 +3,7 @@ import FilteredLayer from "../../Models/FilteredLayer" import { BBox } from "../BBox" import { Feature } from "geojson" -export default interface FeatureSource { +export interface FeatureSource { features: Store } export interface WritableFeatureSource extends FeatureSource { diff --git a/Logic/FeatureSource/PerLayerFeatureSourceSplitter.ts b/Logic/FeatureSource/PerLayerFeatureSourceSplitter.ts index 6e6b4bfec..244981496 100644 --- a/Logic/FeatureSource/PerLayerFeatureSourceSplitter.ts +++ b/Logic/FeatureSource/PerLayerFeatureSourceSplitter.ts @@ -1,10 +1,9 @@ -import FeatureSource, { FeatureSourceForLayer } from "./FeatureSource" +import { FeatureSource, FeatureSourceForLayer } from "./FeatureSource" import FilteredLayer from "../../Models/FilteredLayer" import SimpleFeatureSource from "./Sources/SimpleFeatureSource" import { Feature } from "geojson" import { Utils } from "../../Utils" import { UIEventSource } from "../UIEventSource" -import { feature } from "@turf/turf" /** * In some rare cases, some elements are shown on multiple layers (when 'passthrough' is enabled) @@ -26,7 +25,7 @@ export default class PerLayerFeatureSourceSplitter< const knownLayers = new Map() this.perLayer = knownLayers const layerSources = new Map>() - + console.log("PerLayerFeatureSourceSplitter got layers", layers) const constructStore = options?.constructStore ?? ((store, layer) => new SimpleFeatureSource(layer, store)) for (const layer of layers) { diff --git a/Logic/FeatureSource/Sources/ClippedFeatureSource.ts b/Logic/FeatureSource/Sources/ClippedFeatureSource.ts index f382a594c..a07b17895 100644 --- a/Logic/FeatureSource/Sources/ClippedFeatureSource.ts +++ b/Logic/FeatureSource/Sources/ClippedFeatureSource.ts @@ -1,4 +1,4 @@ -import FeatureSource from "../FeatureSource" +import { FeatureSource } from "../FeatureSource" import { Feature, Polygon } from "geojson" import StaticFeatureSource from "./StaticFeatureSource" import { GeoOperations } from "../../GeoOperations" diff --git a/Logic/FeatureSource/Sources/FeatureSourceMerger.ts b/Logic/FeatureSource/Sources/FeatureSourceMerger.ts index f6b429d97..cd5bda68a 100644 --- a/Logic/FeatureSource/Sources/FeatureSourceMerger.ts +++ b/Logic/FeatureSource/Sources/FeatureSourceMerger.ts @@ -1,5 +1,5 @@ import { Store, UIEventSource } from "../../UIEventSource" -import FeatureSource, { IndexedFeatureSource } from "../FeatureSource" +import { FeatureSource , IndexedFeatureSource } from "../FeatureSource" import { Feature } from "geojson" import { Utils } from "../../../Utils" diff --git a/Logic/FeatureSource/Sources/FilteringFeatureSource.ts b/Logic/FeatureSource/Sources/FilteringFeatureSource.ts index 383f70c62..675002211 100644 --- a/Logic/FeatureSource/Sources/FilteringFeatureSource.ts +++ b/Logic/FeatureSource/Sources/FilteringFeatureSource.ts @@ -1,6 +1,6 @@ import { Store, UIEventSource } from "../../UIEventSource" import FilteredLayer from "../../../Models/FilteredLayer" -import FeatureSource from "../FeatureSource" +import { FeatureSource } from "../FeatureSource" import { TagsFilter } from "../../Tags/TagsFilter" import { Feature } from "geojson" import { GlobalFilter } from "../../../Models/GlobalFilter" @@ -73,21 +73,9 @@ export default class FilteringFeatureSource implements FeatureSource { return false } - for (const filter of layer.layerDef.filters) { - const state = layer.appliedFilters.get(filter.id).data - if (state === undefined) { - continue - } - let neededTags: TagsFilter - if (typeof state === "string") { - // This filter uses fields - } else { - neededTags = filter.options[state].osmTags - } - if (neededTags !== undefined && !neededTags.matchesProperties(f.properties)) { - // Hidden by the filter on the layer itself - we want to hide it no matter what - return false - } + let neededTags: TagsFilter = layer.currentFilter.data + if (neededTags !== undefined && !neededTags.matchesProperties(f.properties)) { + return false } for (const globalFilter of globalFilters ?? []) { diff --git a/Logic/FeatureSource/Sources/GeoJsonSource.ts b/Logic/FeatureSource/Sources/GeoJsonSource.ts index 82897cf22..96ed0b1f4 100644 --- a/Logic/FeatureSource/Sources/GeoJsonSource.ts +++ b/Logic/FeatureSource/Sources/GeoJsonSource.ts @@ -3,7 +3,7 @@ */ import { Store, UIEventSource } from "../../UIEventSource" import { Utils } from "../../../Utils" -import FeatureSource from "../FeatureSource" +import { FeatureSource } from "../FeatureSource" import { BBox } from "../../BBox" import { GeoOperations } from "../../GeoOperations" import { Feature } from "geojson" diff --git a/Logic/FeatureSource/Sources/LastClickFeatureSource.ts b/Logic/FeatureSource/Sources/LastClickFeatureSource.ts index f9d109e38..596a994ad 100644 --- a/Logic/FeatureSource/Sources/LastClickFeatureSource.ts +++ b/Logic/FeatureSource/Sources/LastClickFeatureSource.ts @@ -1,21 +1,23 @@ import LayoutConfig from "../../../Models/ThemeConfig/LayoutConfig" -import FeatureSource from "../FeatureSource" -import { ImmutableStore, Store } from "../../UIEventSource" +import { WritableFeatureSource } from "../FeatureSource" +import { ImmutableStore, Store, UIEventSource } from "../../UIEventSource" import { Feature, Point } from "geojson" import { TagUtils } from "../../Tags/TagUtils" import BaseUIElement from "../../../UI/BaseUIElement" import { Utils } from "../../../Utils" -import { regex_not_newline_characters } from "svelte/types/compiler/utils/patterns" -import { render } from "sass" /** * Highly specialized feature source. * Based on a lon/lat UIEVentSource, will generate the corresponding feature with the correct properties */ -export class LastClickFeatureSource implements FeatureSource { - features: Store +export class LastClickFeatureSource implements WritableFeatureSource { + public readonly features: UIEventSource = new UIEventSource([]) + /** + * Must be public: passed as tags into the selected view + */ public properties: Record + constructor(location: Store<{ lon: number; lat: number }>, layout: LayoutConfig) { const allPresets: BaseUIElement[] = [] for (const layer of layout.layers) @@ -43,15 +45,16 @@ export class LastClickFeatureSource implements FeatureSource { first_preset: renderings[0], } this.properties = properties - this.features = location.mapD(({ lon, lat }) => [ - >{ + location.addCallbackAndRunD(({ lon, lat }) => { + const point = >{ type: "Feature", properties, geometry: { type: "Point", coordinates: [lon, lat], }, - }, - ]) + } + this.features.setData([point]) + }) } } diff --git a/Logic/FeatureSource/Sources/LayoutSource.ts b/Logic/FeatureSource/Sources/LayoutSource.ts index 0bd6ca211..822534b55 100644 --- a/Logic/FeatureSource/Sources/LayoutSource.ts +++ b/Logic/FeatureSource/Sources/LayoutSource.ts @@ -1,15 +1,16 @@ import GeoJsonSource from "./GeoJsonSource" import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" -import FeatureSource from "../FeatureSource" +import { FeatureSource } from "../FeatureSource" import { Or } from "../../Tags/Or" import FeatureSwitchState from "../../State/FeatureSwitchState" import OverpassFeatureSource from "./OverpassFeatureSource" -import { Store } from "../../UIEventSource" +import { ImmutableStore, Store } from "../../UIEventSource" import OsmFeatureSource from "./OsmFeatureSource" import FeatureSourceMerger from "./FeatureSourceMerger" import DynamicGeoJsonTileSource from "../TiledFeatureSource/DynamicGeoJsonTileSource" import { BBox } from "../../BBox" import LocalStorageFeatureSource from "../TiledFeatureSource/LocalStorageFeatureSource" +import StaticFeatureSource from "./StaticFeatureSource" /** * This source will fetch the needed data from various sources for the given layout. @@ -78,6 +79,9 @@ export default class LayoutSource extends FeatureSourceMerger { backend: string, featureSwitches: FeatureSwitchState ): FeatureSource { + if (osmLayers.length == 0) { + return new StaticFeatureSource(new ImmutableStore([])) + } const minzoom = Math.min(...osmLayers.map((layer) => layer.minzoom)) const isActive = zoom.mapD((z) => { if (z < minzoom) { @@ -107,6 +111,9 @@ export default class LayoutSource extends FeatureSourceMerger { zoom: Store, featureSwitches: FeatureSwitchState ): FeatureSource { + if (osmLayers.length == 0) { + return new StaticFeatureSource(new ImmutableStore([])) + } const minzoom = Math.min(...osmLayers.map((layer) => layer.minzoom)) const isActive = zoom.mapD((z) => { if (z < minzoom) { diff --git a/Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource.ts b/Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource.ts index 86e92ae8f..639c375ed 100644 --- a/Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource.ts +++ b/Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource.ts @@ -1,6 +1,6 @@ import { Changes } from "../../Osm/Changes" import { OsmNode, OsmObject, OsmRelation, OsmWay } from "../../Osm/OsmObject" -import FeatureSource from "../FeatureSource" +import { FeatureSource } from "../FeatureSource" import { UIEventSource } from "../../UIEventSource" import { ChangeDescription } from "../../Osm/Actions/ChangeDescription" import { ElementStorage } from "../../ElementStorage" diff --git a/Logic/FeatureSource/Sources/OverpassFeatureSource.ts b/Logic/FeatureSource/Sources/OverpassFeatureSource.ts index 6cfe1ef9d..0188263eb 100644 --- a/Logic/FeatureSource/Sources/OverpassFeatureSource.ts +++ b/Logic/FeatureSource/Sources/OverpassFeatureSource.ts @@ -1,5 +1,5 @@ import { Feature } from "geojson" -import FeatureSource from "../FeatureSource" +import { FeatureSource } from "../FeatureSource" import { ImmutableStore, Store, UIEventSource } from "../../UIEventSource" import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" import { Or } from "../../Tags/Or" diff --git a/Logic/FeatureSource/Sources/SnappingFeatureSource.ts b/Logic/FeatureSource/Sources/SnappingFeatureSource.ts index 855e935ea..43dab4d22 100644 --- a/Logic/FeatureSource/Sources/SnappingFeatureSource.ts +++ b/Logic/FeatureSource/Sources/SnappingFeatureSource.ts @@ -1,37 +1,69 @@ -import FeatureSource from "../FeatureSource" -import { Store } from "../../UIEventSource" +import { FeatureSource } from "../FeatureSource" +import { Store, UIEventSource } from "../../UIEventSource" import { Feature, Point } from "geojson" import { GeoOperations } from "../../GeoOperations" +import { BBox } from "../../BBox" export interface SnappingOptions { /** * If the distance is bigger then this amount, don't snap. * In meter */ - maxDistance?: number + maxDistance: number + + allowUnsnapped?: false | boolean + + /** + * The snapped-to way will be written into this + */ + snappedTo?: UIEventSource + + /** + * The resulting snap coordinates will be written into this UIEventSource + */ + snapLocation?: UIEventSource<{ lon: number; lat: number }> } export default class SnappingFeatureSource implements FeatureSource { public readonly features: Store[]> + private readonly _snappedTo: UIEventSource + public readonly snappedTo: Store + constructor( snapTo: FeatureSource, location: Store<{ lon: number; lat: number }>, - options?: SnappingOptions + options: SnappingOptions ) { - const simplifiedFeatures = snapTo.features.mapD((features) => - features - .filter((feature) => feature.geometry.type !== "Point") - .map((f) => GeoOperations.forceLineString(f)) - ) + const maxDistance = options?.maxDistance + this._snappedTo = options.snappedTo ?? new UIEventSource(undefined) + this.snappedTo = this._snappedTo + const simplifiedFeatures = snapTo.features + .mapD((features) => + features + .filter((feature) => feature.geometry.type !== "Point") + .map((f) => GeoOperations.forceLineString(f)) + ) + .map( + (features) => { + const { lon, lat } = location.data + const loc: [number, number] = [lon, lat] + return features.filter((f) => BBox.get(f).isNearby(loc, maxDistance)) + }, + [location] + ) - location.mapD( + this.features = location.mapD( ({ lon, lat }) => { - const features = snapTo.features.data + const features = simplifiedFeatures.data const loc: [number, number] = [lon, lat] - const maxDistance = (options?.maxDistance ?? 1000) * 1000 + const maxDistance = (options?.maxDistance ?? 1000) / 1000 let bestSnap: Feature = undefined for (const feature of features) { + if (feature.geometry.type !== "LineString") { + // TODO handle Polygons with holes + continue + } const snapped = GeoOperations.nearestPoint(feature, loc) if (snapped.properties.dist > maxDistance) { continue @@ -44,7 +76,23 @@ export default class SnappingFeatureSource implements FeatureSource { bestSnap = snapped } } - return bestSnap + this._snappedTo.setData(bestSnap?.properties?.["snapped-to"]) + if (bestSnap === undefined && options?.allowUnsnapped) { + bestSnap = { + type: "Feature", + geometry: { + type: "Point", + coordinates: [lon, lat], + }, + properties: { + "snapped-to": undefined, + dist: -1, + }, + } + } + const c = bestSnap.geometry.coordinates + options?.snapLocation?.setData({ lon: c[0], lat: c[1] }) + return [bestSnap] }, [snapTo.features] ) diff --git a/Logic/FeatureSource/Sources/StaticFeatureSource.ts b/Logic/FeatureSource/Sources/StaticFeatureSource.ts index 97d6600cc..81af90b91 100644 --- a/Logic/FeatureSource/Sources/StaticFeatureSource.ts +++ b/Logic/FeatureSource/Sources/StaticFeatureSource.ts @@ -1,4 +1,4 @@ -import FeatureSource, { FeatureSourceForLayer, Tiled } from "../FeatureSource" +import { FeatureSource , FeatureSourceForLayer, Tiled } from "../FeatureSource" import { ImmutableStore, Store } from "../../UIEventSource" import FilteredLayer from "../../../Models/FilteredLayer" import { BBox } from "../../BBox" diff --git a/Logic/FeatureSource/Sources/TouchesBboxFeatureSource.ts b/Logic/FeatureSource/Sources/TouchesBboxFeatureSource.ts index 6384a3cfd..3a6c6ce49 100644 --- a/Logic/FeatureSource/Sources/TouchesBboxFeatureSource.ts +++ b/Logic/FeatureSource/Sources/TouchesBboxFeatureSource.ts @@ -1,4 +1,4 @@ -import FeatureSource, { FeatureSourceForLayer } from "../FeatureSource" +import {FeatureSource, FeatureSourceForLayer } from "../FeatureSource" import StaticFeatureSource from "./StaticFeatureSource" import { GeoOperations } from "../../GeoOperations" import { BBox } from "../../BBox" diff --git a/Logic/FeatureSource/TiledFeatureSource/DynamicGeoJsonTileSource.ts b/Logic/FeatureSource/TiledFeatureSource/DynamicGeoJsonTileSource.ts index cb787289c..c607d0d31 100644 --- a/Logic/FeatureSource/TiledFeatureSource/DynamicGeoJsonTileSource.ts +++ b/Logic/FeatureSource/TiledFeatureSource/DynamicGeoJsonTileSource.ts @@ -81,6 +81,7 @@ export default class DynamicGeoJsonTileSource extends DynamicTileSource { return new GeoJsonSource(layer, { zxy, featureIdBlacklist: blackList, + isActive: options?.isActive, }) }, mapProperties, diff --git a/Logic/FeatureSource/TiledFeatureSource/DynamicTileSource.ts b/Logic/FeatureSource/TiledFeatureSource/DynamicTileSource.ts index 65c249984..024d86574 100644 --- a/Logic/FeatureSource/TiledFeatureSource/DynamicTileSource.ts +++ b/Logic/FeatureSource/TiledFeatureSource/DynamicTileSource.ts @@ -1,7 +1,7 @@ import { Store, Stores } from "../../UIEventSource" import { Tiles } from "../../../Models/TileRange" import { BBox } from "../../BBox" -import FeatureSource from "../FeatureSource" +import { FeatureSource } from "../FeatureSource" import FeatureSourceMerger from "../Sources/FeatureSourceMerger" /*** @@ -26,10 +26,6 @@ export default class DynamicTileSource extends FeatureSourceMerger { mapProperties.bounds .mapD( (bounds) => { - if (options?.isActive?.data === false) { - // No need to download! - the layer is disabled - return undefined - } const tileRange = Tiles.TileRangeBetween( zoomlevel, bounds.getNorth(), diff --git a/Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource.ts b/Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource.ts index a1f35ac5b..883beb14c 100644 --- a/Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource.ts +++ b/Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource.ts @@ -1,4 +1,4 @@ -import FeatureSource, { FeatureSourceForLayer, Tiled } from "../FeatureSource" +import {FeatureSource, FeatureSourceForLayer, Tiled } from "../FeatureSource" import { OsmNode, OsmObject, OsmWay } from "../../Osm/OsmObject" import SimpleFeatureSource from "../Sources/SimpleFeatureSource" import FilteredLayer from "../../../Models/FilteredLayer" diff --git a/Logic/GeoOperations.ts b/Logic/GeoOperations.ts index 3b5df2add..038b18c81 100644 --- a/Logic/GeoOperations.ts +++ b/Logic/GeoOperations.ts @@ -1,7 +1,7 @@ import { BBox } from "./BBox" import LayerConfig from "../Models/ThemeConfig/LayerConfig" import * as turf from "@turf/turf" -import { AllGeoJSON, booleanWithin, Coord } from "@turf/turf" +import { AllGeoJSON, booleanWithin, Coord, Lines } from "@turf/turf" import { Feature, GeoJSON, @@ -273,7 +273,7 @@ export class GeoOperations { * @param point Point defined as [lon, lat] */ public static nearestPoint( - way: Feature, + way: Feature, point: [number, number] ): Feature< Point, @@ -951,4 +951,24 @@ export class GeoOperations { } throw "CalculateIntersection fallthrough: can not calculate an intersection between features" } + + /** + * Creates a linestring object based on the outer ring of the given polygon + * + * Returns the argument if not a polygon + * @param p + */ + public static outerRing

(p: Feature): Feature { + if (p.geometry.type !== "Polygon") { + return >p + } + return { + type: "Feature", + properties: p.properties, + geometry: { + type: "LineString", + coordinates: p.geometry.coordinates[0], + }, + } + } } diff --git a/Logic/Osm/Actions/CreateMultiPolygonWithPointReuseAction.ts b/Logic/Osm/Actions/CreateMultiPolygonWithPointReuseAction.ts index 353e94b1c..007c549db 100644 --- a/Logic/Osm/Actions/CreateMultiPolygonWithPointReuseAction.ts +++ b/Logic/Osm/Actions/CreateMultiPolygonWithPointReuseAction.ts @@ -7,7 +7,7 @@ import CreateWayWithPointReuseAction, { MergePointConfig } from "./CreateWayWith import { And } from "../../Tags/And" import { TagUtils } from "../../Tags/TagUtils" import { SpecialVisualizationState } from "../../../UI/SpecialVisualization" -import FeatureSource from "../../FeatureSource/FeatureSource" +import { FeatureSource } from "../../FeatureSource/FeatureSource" /** * More or less the same as 'CreateNewWay', except that it'll try to reuse already existing points diff --git a/Logic/Osm/Actions/CreateNewNodeAction.ts b/Logic/Osm/Actions/CreateNewNodeAction.ts index 7e69e8317..224f24aab 100644 --- a/Logic/Osm/Actions/CreateNewNodeAction.ts +++ b/Logic/Osm/Actions/CreateNewNodeAction.ts @@ -104,9 +104,13 @@ export default class CreateNewNodeAction extends OsmCreateAction { // Project the point onto the way console.log("Snapping a node onto an existing way...") const geojson = this._snapOnto.asGeoJson() - const projected = GeoOperations.nearestPoint(geojson, [this._lon, this._lat]) + const projected = GeoOperations.nearestPoint(GeoOperations.outerRing(geojson), [ + this._lon, + this._lat, + ]) const projectedCoor = <[number, number]>projected.geometry.coordinates const index = projected.properties.index + console.log("Attempting to snap:", { geojson, projected, projectedCoor, index }) // We check that it isn't close to an already existing point let reusedPointId = undefined let outerring: [number, number][] diff --git a/Logic/Osm/Actions/CreateWayWithPointReuseAction.ts b/Logic/Osm/Actions/CreateWayWithPointReuseAction.ts index df99cc97c..d8190da4d 100644 --- a/Logic/Osm/Actions/CreateWayWithPointReuseAction.ts +++ b/Logic/Osm/Actions/CreateWayWithPointReuseAction.ts @@ -5,7 +5,7 @@ import { ChangeDescription } from "./ChangeDescription" import { BBox } from "../../BBox" import { TagsFilter } from "../../Tags/TagsFilter" import { GeoOperations } from "../../GeoOperations" -import FeatureSource from "../../FeatureSource/FeatureSource" +import { FeatureSource } from "../../FeatureSource/FeatureSource" import StaticFeatureSource from "../../FeatureSource/Sources/StaticFeatureSource" import CreateNewNodeAction from "./CreateNewNodeAction" import CreateNewWayAction from "./CreateNewWayAction" diff --git a/Logic/Osm/Actions/ReplaceGeometryAction.ts b/Logic/Osm/Actions/ReplaceGeometryAction.ts index 3ec65299f..e131a1c58 100644 --- a/Logic/Osm/Actions/ReplaceGeometryAction.ts +++ b/Logic/Osm/Actions/ReplaceGeometryAction.ts @@ -2,7 +2,7 @@ import OsmChangeAction from "./OsmChangeAction" import { Changes } from "../Changes" import { ChangeDescription } from "./ChangeDescription" import { Tag } from "../../Tags/Tag" -import FeatureSource from "../../FeatureSource/FeatureSource" +import { FeatureSource } from "../../FeatureSource/FeatureSource" import { OsmNode, OsmObject, OsmWay } from "../OsmObject" import { GeoOperations } from "../../GeoOperations" import StaticFeatureSource from "../../FeatureSource/Sources/StaticFeatureSource" diff --git a/Logic/Osm/Changes.ts b/Logic/Osm/Changes.ts index 300c115cb..b19b1b5e8 100644 --- a/Logic/Osm/Changes.ts +++ b/Logic/Osm/Changes.ts @@ -6,7 +6,7 @@ import { ChangeDescription, ChangeDescriptionTools } from "./Actions/ChangeDescr import { Utils } from "../../Utils" import { LocalStorageSource } from "../Web/LocalStorageSource" import SimpleMetaTagger from "../SimpleMetaTagger" -import FeatureSource, { IndexedFeatureSource } from "../FeatureSource/FeatureSource" +import {FeatureSource, IndexedFeatureSource } from "../FeatureSource/FeatureSource" import { GeoLocationPointProperties } from "../State/GeoLocationState" import { GeoOperations } from "../GeoOperations" import { ChangesetHandler, ChangesetTag } from "./ChangesetHandler" diff --git a/Logic/Osm/OsmConnection.ts b/Logic/Osm/OsmConnection.ts index 70cc9f89d..f0846d474 100644 --- a/Logic/Osm/OsmConnection.ts +++ b/Logic/Osm/OsmConnection.ts @@ -368,7 +368,7 @@ export class OsmConnection { "Content-Type": "application/json", }) const parsed = JSON.parse(response) - const id = parsed.properties.id + const id = parsed.properties console.log("OPENED NOTE", id) return id } diff --git a/Logic/Osm/OsmObject.ts b/Logic/Osm/OsmObject.ts index 298277894..fe279f890 100644 --- a/Logic/Osm/OsmObject.ts +++ b/Logic/Osm/OsmObject.ts @@ -73,7 +73,8 @@ export abstract class OsmObject { if (rawData["error"] !== undefined && rawData["statuscode"] === 410) { return "deleted" } - return rawData["content"].elements[0].tags + // Tags is undefined if the element does not have any tags + return rawData["content"].elements[0].tags ?? {} } static async DownloadObjectAsync( diff --git a/Logic/State/LayerState.ts b/Logic/State/LayerState.ts index c2061e7f9..268cdfaf7 100644 --- a/Logic/State/LayerState.ts +++ b/Logic/State/LayerState.ts @@ -21,7 +21,7 @@ export default class LayerState { /** * Which layers are enabled in the current theme and what filters are applied onto them */ - public readonly filteredLayers: Map + public readonly filteredLayers: ReadonlyMap private readonly osmConnection: OsmConnection /** @@ -32,14 +32,15 @@ export default class LayerState { */ constructor(osmConnection: OsmConnection, layers: LayerConfig[], context: string) { this.osmConnection = osmConnection - this.filteredLayers = new Map() + const filteredLayers = new Map() for (const layer of layers) { - this.filteredLayers.set( + filteredLayers.set( layer.id, FilteredLayer.initLinkedState(layer, context, this.osmConnection) ) } - layers.forEach((l) => this.linkFilterStates(l)) + this.filteredLayers = filteredLayers + layers.forEach((l) => LayerState.linkFilterStates(l, filteredLayers)) } /** @@ -48,11 +49,14 @@ export default class LayerState { * * This methods links those states for the given layer */ - private linkFilterStates(layer: LayerConfig) { + private static linkFilterStates( + layer: LayerConfig, + filteredLayers: Map + ) { if (layer.filterIsSameAs === undefined) { return } - const toReuse = this.filteredLayers.get(layer.filterIsSameAs) + const toReuse = filteredLayers.get(layer.filterIsSameAs) if (toReuse === undefined) { throw ( "Error in layer " + @@ -65,6 +69,6 @@ export default class LayerState { console.warn( "Linking filter and isDisplayed-states of " + layer.id + " and " + layer.filterIsSameAs ) - this.filteredLayers.set(layer.id, toReuse) + filteredLayers.set(layer.id, toReuse) } } diff --git a/Logic/State/MapState.ts b/Logic/State/MapState.ts index 0e695d356..12295cfb8 100644 --- a/Logic/State/MapState.ts +++ b/Logic/State/MapState.ts @@ -3,7 +3,7 @@ import FilteredLayer from "../../Models/FilteredLayer" import TilesourceConfig from "../../Models/ThemeConfig/TilesourceConfig" import { QueryParameters } from "../Web/QueryParameters" import ShowOverlayLayer from "../../UI/ShowDataLayer/ShowOverlayLayer" -import FeatureSource, { FeatureSourceForLayer, Tiled } from "../FeatureSource/FeatureSource" +import { FeatureSource, FeatureSourceForLayer, Tiled } from "../FeatureSource/FeatureSource" import StaticFeatureSource, { TiledStaticFeatureSource, } from "../FeatureSource/Sources/StaticFeatureSource" diff --git a/Logic/State/UserRelatedState.ts b/Logic/State/UserRelatedState.ts index 06996e9e5..c4d86fde6 100644 --- a/Logic/State/UserRelatedState.ts +++ b/Logic/State/UserRelatedState.ts @@ -4,7 +4,7 @@ import { MangroveIdentity } from "../Web/MangroveReviews" import { Store, Stores, UIEventSource } from "../UIEventSource" import Locale from "../../UI/i18n/Locale" import StaticFeatureSource from "../FeatureSource/Sources/StaticFeatureSource" -import FeatureSource from "../FeatureSource/FeatureSource" +import { FeatureSource } from "../FeatureSource/FeatureSource" import { Feature } from "geojson" /** diff --git a/Logic/Tags/Tag.ts b/Logic/Tags/Tag.ts index 158be431d..4337e8b45 100644 --- a/Logic/Tags/Tag.ts +++ b/Logic/Tags/Tag.ts @@ -4,7 +4,6 @@ import { TagsFilter } from "./TagsFilter" export class Tag extends TagsFilter { public key: string public value: string - public static newlyCreated = new Tag("_newly_created", "yes") constructor(key: string, value: string) { super() this.key = key diff --git a/Models/Constants.ts b/Models/Constants.ts index d0b8e1a66..f30672b2b 100644 --- a/Models/Constants.ts +++ b/Models/Constants.ts @@ -1,7 +1,7 @@ import { Utils } from "../Utils" export default class Constants { - public static vNumber = "0.27.0" + public static vNumber = "0.30.0" public static ImgurApiKey = "7070e7167f0a25a" public static readonly mapillary_client_token_v4 = diff --git a/Models/FilteredLayer.ts b/Models/FilteredLayer.ts index 1cbe82a58..9eca42658 100644 --- a/Models/FilteredLayer.ts +++ b/Models/FilteredLayer.ts @@ -1,8 +1,13 @@ -import { UIEventSource } from "../Logic/UIEventSource" +import { Store, UIEventSource } from "../Logic/UIEventSource" import LayerConfig from "./ThemeConfig/LayerConfig" import { OsmConnection } from "../Logic/Osm/OsmConnection" import { LocalStorageSource } from "../Logic/Web/LocalStorageSource" import { QueryParameters } from "../Logic/Web/QueryParameters" +import { FilterConfigOption } from "./ThemeConfig/FilterConfig" +import { TagsFilter } from "../Logic/Tags/TagsFilter" +import { Utils } from "../Utils" +import { TagUtils } from "../Logic/Tags/TagUtils" +import { And } from "../Logic/Tags/And" export default class FilteredLayer { /** @@ -10,11 +15,22 @@ export default class FilteredLayer { */ readonly isDisplayed: UIEventSource /** - * Maps the filter.option.id onto the actual used state + * Maps the filter.option.id onto the actual used state. + * This state is either the chosen option (as number) or a representation of the fields */ - readonly appliedFilters: Map> + readonly appliedFilters: ReadonlyMap> readonly layerDef: LayerConfig + /** + * Indicates if some filter is set. + * If this is the case, adding a new element of this type might be a bad idea + */ + readonly hasFilter: Store + + /** + * Contains the current properties a feature should fulfill in order to match the filter + */ + readonly currentFilter: Store constructor( layer: LayerConfig, appliedFilters?: Map>, @@ -24,6 +40,105 @@ export default class FilteredLayer { this.isDisplayed = isDisplayed ?? new UIEventSource(true) this.appliedFilters = appliedFilters ?? new Map>() + + const hasFilter = new UIEventSource(false) + const self = this + const currentTags = new UIEventSource(undefined) + + this.appliedFilters.forEach((filterSrc) => { + filterSrc.addCallbackAndRun((filter) => { + if ((filter ?? 0) !== 0) { + hasFilter.setData(true) + currentTags.setData(self.calculateCurrentTags()) + return + } + + const hf = Array.from(self.appliedFilters.values()).some((f) => (f.data ?? 0) !== 0) + if (hf) { + currentTags.setData(self.calculateCurrentTags()) + } else { + currentTags.setData(undefined) + } + hasFilter.setData(hf) + }) + }) + + currentTags.addCallbackAndRunD((t) => console.log("Current filter is", t)) + + this.currentFilter = currentTags + } + + private calculateCurrentTags(): TagsFilter { + let needed: TagsFilter[] = [] + for (const filter of this.layerDef.filters) { + const state = this.appliedFilters.get(filter.id) + if (state.data === undefined) { + continue + } + if (filter.options[0].fields.length > 0) { + const fieldProperties = FilteredLayer.stringToFieldProperties(state.data) + const asTags = FilteredLayer.fieldsToTags(filter.options[0], fieldProperties) + needed.push(asTags) + continue + } + needed.push(filter.options[state.data].osmTags) + } + needed = Utils.NoNull(needed) + if (needed.length == 0) { + return undefined + } + let tags: TagsFilter + + if (needed.length == 1) { + tags = needed[1] + } else { + tags = new And(needed) + } + let optimized = tags.optimize() + if (optimized === true) { + return undefined + } + if (optimized === false) { + return tags + } + return optimized + } + + public static fieldsToString(values: Record): string { + return JSON.stringify(values) + } + + public static stringToFieldProperties(value: string): Record { + return JSON.parse(value) + } + + private static fieldsToTags( + option: FilterConfigOption, + fieldstate: string | Record + ): TagsFilter { + let properties: Record + if (typeof fieldstate === "string") { + properties = FilteredLayer.stringToFieldProperties(fieldstate) + } else { + properties = fieldstate + } + console.log("Building tagsspec with properties", properties) + const tagsSpec = Utils.WalkJson(option.originalTagsSpec, (v) => { + if (typeof v !== "string") { + return v + } + + for (const key in properties) { + v = (v).replace("{" + key + "}", properties[key]) + } + + return v + }) + return TagUtils.Tag(tagsSpec) + } + + public disableAllFilters(): void { + this.appliedFilters.forEach((value) => value.setData(undefined)) } /** diff --git a/Models/MapProperties.ts b/Models/MapProperties.ts index 41cf8d044..6e6da6c1a 100644 --- a/Models/MapProperties.ts +++ b/Models/MapProperties.ts @@ -5,6 +5,7 @@ import { RasterLayerPolygon } from "./RasterLayers" export interface MapProperties { readonly location: UIEventSource<{ lon: number; lat: number }> readonly zoom: UIEventSource + readonly minzoom: UIEventSource readonly bounds: UIEventSource readonly rasterLayer: UIEventSource readonly maxbounds: UIEventSource diff --git a/Models/MenuState.ts b/Models/MenuState.ts new file mode 100644 index 000000000..fc51e830c --- /dev/null +++ b/Models/MenuState.ts @@ -0,0 +1,64 @@ +import LayerConfig from "./ThemeConfig/LayerConfig" +import { UIEventSource } from "../Logic/UIEventSource" + +/** + * Indicates if a menu is open, and if so, which tab is selected; + * Some tabs allow to highlight an element. + * + * Some convenience methods are provided for this as well + */ +export class MenuState { + private static readonly _themeviewTabs = ["intro", "filters"] as const + public readonly themeIsOpened = new UIEventSource(true) + public readonly themeViewTabIndex: UIEventSource + public readonly themeViewTab: UIEventSource + + private static readonly _menuviewTabs = ["about", "settings", "community", "privacy"] as const + public readonly menuIsOpened = new UIEventSource(false) + public readonly menuViewTabIndex: UIEventSource + public readonly menuViewTab: UIEventSource + + public readonly highlightedLayerInFilters: UIEventSource = new UIEventSource( + undefined + ) + constructor() { + this.themeViewTabIndex = new UIEventSource(0) + this.themeViewTab = this.themeViewTabIndex.sync( + (i) => MenuState._themeviewTabs[i], + [], + (str) => MenuState._themeviewTabs.indexOf(str) + ) + + this.menuViewTabIndex = new UIEventSource(1) + this.menuViewTab = this.menuViewTabIndex.sync( + (i) => MenuState._menuviewTabs[i], + [], + (str) => MenuState._menuviewTabs.indexOf(str) + ) + this.themeIsOpened.addCallbackAndRun((isOpen) => { + if (!isOpen) { + this.highlightedLayerInFilters.setData(undefined) + } + }) + this.themeViewTab.addCallbackAndRun((tab) => { + if (tab !== "filters") { + this.highlightedLayerInFilters.setData(undefined) + } + }) + } + public openFilterView(highlightLayer?: LayerConfig | string) { + this.themeIsOpened.setData(true) + this.themeViewTab.setData("filters") + if (highlightLayer) { + if (typeof highlightLayer !== "string") { + highlightLayer = highlightLayer.id + } + this.highlightedLayerInFilters.setData(highlightLayer) + } + } + + public closeAll() { + this.menuIsOpened.setData(false) + this.themeIsOpened.setData(false) + } +} diff --git a/Models/ThemeConfig/FilterConfig.ts b/Models/ThemeConfig/FilterConfig.ts index a7774fb2a..3e6db8250 100644 --- a/Models/ThemeConfig/FilterConfig.ts +++ b/Models/ThemeConfig/FilterConfig.ts @@ -11,15 +11,16 @@ import { RegexTag } from "../../Logic/Tags/RegexTag" import BaseUIElement from "../../UI/BaseUIElement" import Table from "../../UI/Base/Table" import Combine from "../../UI/Base/Combine" - +export type FilterConfigOption = { + question: Translation + osmTags: TagsFilter | undefined + /* Only set if fields are present. Used to create `osmTags` (which are used to _actually_ filter) when the field is written*/ + readonly originalTagsSpec: TagConfigJson + fields: { name: string; type: string }[] +} export default class FilterConfig { public readonly id: string - public readonly options: { - question: Translation - osmTags: TagsFilter | undefined - originalTagsSpec: TagConfigJson - fields: { name: string; type: string }[] - }[] + public readonly options: FilterConfigOption[] public readonly defaultSelection?: number constructor(json: FilterConfigJson, context: string) { diff --git a/Models/ThemeViewState.ts b/Models/ThemeViewState.ts index f3b43e0b9..4bc4a7093 100644 --- a/Models/ThemeViewState.ts +++ b/Models/ThemeViewState.ts @@ -2,12 +2,12 @@ import LayoutConfig from "./ThemeConfig/LayoutConfig" import { SpecialVisualizationState } from "../UI/SpecialVisualization" import { Changes } from "../Logic/Osm/Changes" import { ImmutableStore, Store, UIEventSource } from "../Logic/UIEventSource" -import FeatureSource, { +import { + FeatureSource, IndexedFeatureSource, WritableFeatureSource, } from "../Logic/FeatureSource/FeatureSource" import { OsmConnection } from "../Logic/Osm/OsmConnection" -import { DefaultGuiState } from "../UI/DefaultGuiState" import { MapProperties } from "./MapProperties" import LayerState from "../Logic/State/LayerState" import { Feature } from "geojson" @@ -39,6 +39,8 @@ import Hotkeys from "../UI/Base/Hotkeys" import Translations from "../UI/i18n/Translations" import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore" import { LastClickFeatureSource } from "../Logic/FeatureSource/Sources/LastClickFeatureSource" +import SimpleFeatureSource from "../Logic/FeatureSource/Sources/SimpleFeatureSource" +import { MenuState } from "./MenuState" /** * @@ -63,11 +65,12 @@ export default class ThemeViewState implements SpecialVisualizationState { readonly mapProperties: MapProperties readonly dataIsLoading: Store // TODO - readonly guistate: DefaultGuiState + readonly guistate: MenuState readonly fullNodeDatabase?: FullNodeDatabaseSource // TODO readonly historicalUserLocations: WritableFeatureSource readonly indexedFeatures: IndexedFeatureSource + readonly newFeatures: WritableFeatureSource readonly layerState: LayerState readonly perLayer: ReadonlyMap readonly availableLayers: Store @@ -75,9 +78,10 @@ export default class ThemeViewState implements SpecialVisualizationState { readonly userRelatedState: UserRelatedState readonly geolocation: GeoLocationHandler + readonly lastClickObject: WritableFeatureSource constructor(layout: LayoutConfig) { this.layout = layout - this.guistate = new DefaultGuiState() + this.guistate = new MenuState() this.map = new UIEventSource(undefined) const initial = new InitialMapPositioning(layout) this.mapProperties = new MapLibreAdaptor(this.map, initial) @@ -109,20 +113,26 @@ export default class ThemeViewState implements SpecialVisualizationState { this.availableLayers = AvailableRasterLayers.layersAvailableAt(this.mapProperties.location) + const self = this this.layerState = new LayerState(this.osmConnection, layout.layers, layout.id) + this.newFeatures = new SimpleFeatureSource(undefined) this.indexedFeatures = new LayoutSource( layout.layers, this.featureSwitches, - new StaticFeatureSource([]), + this.newFeatures, this.mapProperties, this.osmConnection.Backend(), - (id) => this.layerState.filteredLayers.get(id).isDisplayed + (id) => self.layerState.filteredLayers.get(id).isDisplayed ) + const lastClick = (this.lastClickObject = new LastClickFeatureSource( + this.mapProperties.lastClickLocation, + this.layout + )) const indexedElements = this.indexedFeatures this.featureProperties = new FeaturePropertiesStore(indexedElements) const perLayer = new PerLayerFeatureSourceSplitter( Array.from(this.layerState.filteredLayers.values()).filter( - (l) => l.layerDef.source !== null + (l) => l.layerDef?.source !== null ), indexedElements, { @@ -176,9 +186,10 @@ export default class ThemeViewState implements SpecialVisualizationState { ) this.initActors() - this.drawSpecialLayers() + this.drawSpecialLayers(lastClick) this.initHotkeys() this.miscSetup() + console.log("State setup completed", this) } /** @@ -197,21 +208,30 @@ export default class ThemeViewState implements SpecialVisualizationState { this.guistate.closeAll() } ) + + Hotkeys.RegisterHotkey( + { + nomod: "b", + }, + Translations.t.hotkeyDocumentation.openLayersPanel, + () => { + if (this.featureSwitches.featureSwitchFilter.data) { + this.guistate.openFilterView() + } + } + ) } /** * Add the special layers to the map * @private */ - private drawSpecialLayers() { + private drawSpecialLayers(last_click: LastClickFeatureSource) { type AddedByDefaultTypes = typeof Constants.added_by_default[number] const empty = [] { // The last_click gets a _very_ special treatment - const last_click = new LastClickFeatureSource( - this.mapProperties.lastClickLocation, - this.layout - ) + const last_click_layer = this.layerState.filteredLayers.get("last_click") this.featureProperties.addSpecial( "last_click", diff --git a/Models/TileRange.ts b/Models/TileRange.ts index a23b45966..a711a99a3 100644 --- a/Models/TileRange.ts +++ b/Models/TileRange.ts @@ -84,7 +84,7 @@ export class Tiles { * Return x, y of the tile containing (lat, lon) on the given zoom level */ static embedded_tile(lat: number, lon: number, z: number): { x: number; y: number; z: number } { - return { x: Tiles.lon2tile(lon, z), y: Tiles.lat2tile(lat, z), z: z } + return { x: Tiles.lon2tile(lon, z), y: Tiles.lat2tile(lat, z), z } } static tileRangeFrom(bbox: BBox, zoomlevel: number) { diff --git a/UI/Base/DragInvitation.svelte b/UI/Base/DragInvitation.svelte index 853a0ca06..333588e53 100644 --- a/UI/Base/DragInvitation.svelte +++ b/UI/Base/DragInvitation.svelte @@ -11,18 +11,17 @@ let mainElem: HTMLElement; export let hideSignal: Store; function hide(){ - console.trace("Hiding...") mainElem.style.visibility = "hidden"; } if (hideSignal) { onDestroy(hideSignal.addCallbackD(() => { - console.trace("Hiding invitation") + console.log("Received hide signal") + hide() return true; })); } $: { - console.log("Binding listeners on", mainElem) mainElem?.addEventListener("click",_ => hide()) mainElem?.addEventListener("touchstart",_ => hide()) } @@ -30,8 +29,8 @@ $: {

-
- +
+
diff --git a/UI/Base/FloatOver.svelte b/UI/Base/FloatOver.svelte index 2242c4578..934883a57 100644 --- a/UI/Base/FloatOver.svelte +++ b/UI/Base/FloatOver.svelte @@ -1,11 +1,20 @@
+ +
dispatch("close")}> + +
+
diff --git a/UI/Base/LoginButton.svelte b/UI/Base/LoginButton.svelte new file mode 100644 index 000000000..bb86bf244 --- /dev/null +++ b/UI/Base/LoginButton.svelte @@ -0,0 +1,15 @@ + + + osmConnection.AttemptLogin()}> + + + + + diff --git a/UI/Base/LoginToggle.svelte b/UI/Base/LoginToggle.svelte new file mode 100644 index 000000000..c14edd125 --- /dev/null +++ b/UI/Base/LoginToggle.svelte @@ -0,0 +1,45 @@ + + +{#if $badge} + {#if !ignoreLoading && $loadingStatus === "loading"} + + + + {:else if $loadingStatus === "error"} +
+ + +
+ + {:else if $loadingStatus === "logged-in"} + + {:else if $loadingStatus === "not-attempted"} + + + + {/if} +{/if} diff --git a/UI/Base/MapControlButton.svelte b/UI/Base/MapControlButton.svelte index 9d875b984..4508a98ae 100644 --- a/UI/Base/MapControlButton.svelte +++ b/UI/Base/MapControlButton.svelte @@ -8,6 +8,6 @@ -
dispatch("click", e)} class="subtle-background rounded-full min-w-10 w-fit h-10 m-0.5 md:m-1 p-1"> +
dispatch("click", e)} class="subtle-background rounded-full min-w-10 w-fit h-10 m-0.5 md:m-1 p-1 cursor-pointer">
diff --git a/UI/Base/SubtleButton.svelte b/UI/Base/SubtleButton.svelte index adce8c3ce..98cf35ad0 100644 --- a/UI/Base/SubtleButton.svelte +++ b/UI/Base/SubtleButton.svelte @@ -1,5 +1,5 @@ dispatch("click", e)} > {#if imageUrl !== undefined} {#if typeof imageUrl === "string"} - + {:else }