diff --git a/Logic/FeatureSource/FeaturePipeline.ts b/Logic/FeatureSource/FeaturePipeline.ts index e6f4a5c05..c0eab954f 100644 --- a/Logic/FeatureSource/FeaturePipeline.ts +++ b/Logic/FeatureSource/FeaturePipeline.ts @@ -160,6 +160,11 @@ export default class FeaturePipeline { continue } + if (id === "selected_element") { + handlePriviligedFeatureSource(state.selectedElementsLayer) + continue + } + if (id === "gps_location") { handlePriviligedFeatureSource(state.currentUserLocation) continue diff --git a/Logic/State/MapState.ts b/Logic/State/MapState.ts index d063fb33c..d363a86e5 100644 --- a/Logic/State/MapState.ts +++ b/Logic/State/MapState.ts @@ -1,29 +1,29 @@ import UserRelatedState from "./UserRelatedState" -import { Store, Stores, UIEventSource } from "../UIEventSource" +import {Store, Stores, UIEventSource} from "../UIEventSource" import BaseLayer from "../../Models/BaseLayer" import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" import AvailableBaseLayers from "../Actors/AvailableBaseLayers" import Attribution from "../../UI/BigComponents/Attribution" -import Minimap, { MinimapObj } from "../../UI/Base/Minimap" -import { Tiles } from "../../Models/TileRange" +import Minimap, {MinimapObj} from "../../UI/Base/Minimap" +import {Tiles} from "../../Models/TileRange" import BaseUIElement from "../../UI/BaseUIElement" -import FilteredLayer, { FilterState } from "../../Models/FilteredLayer" +import FilteredLayer, {FilterState} from "../../Models/FilteredLayer" import TilesourceConfig from "../../Models/ThemeConfig/TilesourceConfig" -import { QueryParameters } from "../Web/QueryParameters" +import {QueryParameters} from "../Web/QueryParameters" import ShowOverlayLayer from "../../UI/ShowDataLayer/ShowOverlayLayer" -import { FeatureSourceForLayer, Tiled } from "../FeatureSource/FeatureSource" +import {FeatureSourceForLayer, Tiled} from "../FeatureSource/FeatureSource" import SimpleFeatureSource from "../FeatureSource/Sources/SimpleFeatureSource" -import { LocalStorageSource } from "../Web/LocalStorageSource" -import { GeoOperations } from "../GeoOperations" +import {LocalStorageSource} from "../Web/LocalStorageSource" +import {GeoOperations} from "../GeoOperations" import TitleHandler from "../Actors/TitleHandler" -import { BBox } from "../BBox" +import {BBox} from "../BBox" import LayerConfig from "../../Models/ThemeConfig/LayerConfig" -import { TiledStaticFeatureSource } from "../FeatureSource/Sources/StaticFeatureSource" -import { Translation, TypedTranslation } from "../../UI/i18n/Translation" -import { Tag } from "../Tags/Tag" -import { OsmConnection } from "../Osm/OsmConnection" -import { Feature, GeoJSON, LineString } from "geojson" -import { OsmTags } from "../../Models/OsmFeature" +import {TiledStaticFeatureSource} from "../FeatureSource/Sources/StaticFeatureSource" +import {Translation, TypedTranslation} from "../../UI/i18n/Translation" +import {Tag} from "../Tags/Tag" +import {OsmConnection} from "../Osm/OsmConnection" +import {Feature, LineString} from "geojson" +import {OsmTags} from "../../Models/OsmFeature" export interface GlobalFilter { filter: FilterState @@ -93,6 +93,13 @@ export default class MapState extends UserRelatedState { */ public homeLocation: FeatureSourceForLayer & Tiled + /** + * A builtin layer which contains the selected element. + * Loads 'selected_element.json' + * This _might_ contain multiple points, e.g. every center of a multipolygon + */ + public selectedElementsLayer: FeatureSourceForLayer & Tiled + public readonly mainMapObject: BaseUIElement & MinimapObj /** @@ -168,6 +175,7 @@ export default class MapState extends UserRelatedState { this.initGpsLocation() this.initUserLocationTrail() this.initCurrentView() + this.initSelectedElement() new TitleHandler(this) } @@ -249,7 +257,7 @@ export default class MapState extends UserRelatedState { private initGpsLocation() { // Initialize the gps layer data. This is emtpy for now, the actual writing happens in the Geolocationhandler - let gpsLayerDef: FilteredLayer = this.filteredLayers.data.filter( + const gpsLayerDef: FilteredLayer = this.filteredLayers.data.filter( (l) => l.layerDef.id === "gps_location" )[0] if (gpsLayerDef === undefined) { @@ -258,6 +266,29 @@ export default class MapState extends UserRelatedState { this.currentUserLocation = new SimpleFeatureSource(gpsLayerDef, Tiles.tile_index(0, 0, 0)) } + private initSelectedElement(){ + const layerDef: FilteredLayer = this.filteredLayers.data.filter( + (l) => l.layerDef.id === "selected_element" + )[0] + const empty = [] + const store = this.selectedElement.map(feature => { + if(feature === undefined || feature === null){ + return empty + } + return [{ + feature: { + type:"Feature", + properties: { + selected: "yes", + id: "selected" + feature.properties.id + }, + geometry:feature.geometry + } + , freshness: new Date()}]; + }); + this.selectedElementsLayer = new TiledStaticFeatureSource(store,layerDef); + } + private initUserLocationTrail() { const features = LocalStorageSource.GetParsed<{ feature: any; freshness: Date }[]>( "gps_location_history", diff --git a/Logic/Web/MangroveReviews.ts b/Logic/Web/MangroveReviews.ts index e54bf4b7f..750a7e941 100644 --- a/Logic/Web/MangroveReviews.ts +++ b/Logic/Web/MangroveReviews.ts @@ -127,7 +127,8 @@ export default class MangroveReviews { this._lastUpdate = new Date() const self = this - mangrove.getReviews({ sub: this.GetSubjectUri() }).then((data) => { + mangrove.getReviews({ sub: this.GetSubjectUri() }) + .then((data) => { const reviews = [] const reviewsByUser = [] for (const review of data.reviews) { @@ -153,6 +154,9 @@ export default class MangroveReviews { } self._reviews.setData(reviewsByUser.concat(reviews)) }) + .catch(e => { + console.error("Could not download review for ", e); + }) return this._reviews } diff --git a/Models/Constants.ts b/Models/Constants.ts index b1b94ced9..641713673 100644 --- a/Models/Constants.ts +++ b/Models/Constants.ts @@ -27,6 +27,7 @@ export default class Constants { ] public static readonly added_by_default: string[] = [ + "selected_element", "gps_location", "gps_location_history", "home_location", diff --git a/Models/ThemeConfig/Json/PointRenderingConfigJson.ts b/Models/ThemeConfig/Json/PointRenderingConfigJson.ts index 77088b5df..f675231c7 100644 --- a/Models/ThemeConfig/Json/PointRenderingConfigJson.ts +++ b/Models/ThemeConfig/Json/PointRenderingConfigJson.ts @@ -64,4 +64,15 @@ export default interface PointRenderingConfigJson { * Note that, if the wayhandling hides the icon then no label is shown as well. */ label?: string | TagRenderingConfigJson + + /** + * A snippet of css code + */ + css?: string | TagRenderingConfigJson + + + /** + * A snippet of css-classes. They can be space-separated + */ + cssClasses?: string | TagRenderingConfigJson } diff --git a/Models/ThemeConfig/LayerConfig.ts b/Models/ThemeConfig/LayerConfig.ts index 09ff272d8..a1cc914d0 100644 --- a/Models/ThemeConfig/LayerConfig.ts +++ b/Models/ThemeConfig/LayerConfig.ts @@ -301,7 +301,7 @@ export default class LayerConfig extends WithContextLoader { const hasCenterRendering = this.mapRendering.some( (r) => - r.location.has("centroid") || r.location.has("start") || r.location.has("end") + r.location.has("centroid") || r.location.has("projected_centerpoint") || r.location.has("start") || r.location.has("end") ) if (this.lineRendering.length === 0 && this.mapRendering.length === 0) { diff --git a/Models/ThemeConfig/PointRenderingConfig.ts b/Models/ThemeConfig/PointRenderingConfig.ts index 81181c34b..c7a65a10b 100644 --- a/Models/ThemeConfig/PointRenderingConfig.ts +++ b/Models/ThemeConfig/PointRenderingConfig.ts @@ -12,6 +12,7 @@ import { FixedUiElement } from "../../UI/Base/FixedUiElement" import Img from "../../UI/Base/Img" import Combine from "../../UI/Base/Combine" import { VariableUiElement } from "../../UI/Base/VariableUIElement" +import {TagRenderingConfigJson} from "./Json/TagRenderingConfigJson"; export default class PointRenderingConfig extends WithContextLoader { private static readonly allowed_location_codes = new Set([ @@ -25,11 +26,13 @@ export default class PointRenderingConfig extends WithContextLoader { "point" | "centroid" | "start" | "end" | "projected_centerpoint" | string > - public readonly icon: TagRenderingConfig + public readonly icon?: TagRenderingConfig public readonly iconBadges: { if: TagsFilter; then: TagRenderingConfig }[] public readonly iconSize: TagRenderingConfig public readonly label: TagRenderingConfig public readonly rotation: TagRenderingConfig + public readonly cssDef: TagRenderingConfig + public readonly cssClasses?: TagRenderingConfig constructor(json: PointRenderingConfigJson, context: string) { super(json, context) @@ -61,6 +64,10 @@ export default class PointRenderingConfig extends WithContextLoader { ) } this.icon = this.tr("icon", undefined) + if(json.css !== undefined){ + this.cssDef = this.tr("css", undefined) + } + this.cssClasses = this.tr("cssClasses", undefined) this.iconBadges = (json.iconBadges ?? []).map((overlay, i) => { let tr: TagRenderingConfig if ( @@ -240,6 +247,9 @@ export default class PointRenderingConfig extends WithContextLoader { iconAndBadges.SetClass("w-full h-full") } + const css= this.cssDef?.GetRenderValue(tags , undefined)?.txt + const cssClasses = this.cssClasses?.GetRenderValue(tags , undefined)?.txt + let label = this.GetLabel(tags) let htmlEl: BaseUIElement if (icon === undefined && label === undefined) { @@ -252,6 +262,13 @@ export default class PointRenderingConfig extends WithContextLoader { htmlEl = new Combine([iconAndBadges, label]).SetStyle("flex flex-col") } + if(css !== undefined){ + htmlEl?.SetStyle(css) + } + + if(cssClasses !== undefined){ + htmlEl?.SetClass(cssClasses) + } return { html: htmlEl, iconSize: [iconW, iconH], diff --git a/UI/Base/MinimapImplementation.ts b/UI/Base/MinimapImplementation.ts index 58c91cede..4b64d1bfe 100644 --- a/UI/Base/MinimapImplementation.ts +++ b/UI/Base/MinimapImplementation.ts @@ -5,7 +5,7 @@ import Loc from "../../Models/Loc" import BaseLayer from "../../Models/BaseLayer" import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers" import * as L from "leaflet" -import { Map } from "leaflet" +import {LeafletMouseEvent, Map} from "leaflet" import Minimap, { MinimapObj, MinimapOptions } from "./Minimap" import { BBox } from "../../Logic/BBox" import "leaflet-polylineoffset" @@ -316,8 +316,10 @@ export default class MinimapImplementation extends BaseUIElement implements Mini if (this._options.lastClickLocation) { const lastClickLocation = this._options.lastClickLocation - map.on("click", function (e) { - // @ts-ignore + map.on("click", function (e: LeafletMouseEvent) { + if(e.originalEvent["dismissed"] ){ + return + } lastClickLocation?.setData({ lat: e.latlng.lat, lon: e.latlng.lng }) }) diff --git a/UI/Base/ScrollableFullScreen.ts b/UI/Base/ScrollableFullScreen.ts index 842cdfd35..da6280ee6 100644 --- a/UI/Base/ScrollableFullScreen.ts +++ b/UI/Base/ScrollableFullScreen.ts @@ -71,18 +71,22 @@ export default class ScrollableFullScreen { self.Activate() } else { // Some cleanup... + ScrollableFullScreen.collapse() - const fs = document.getElementById("fullscreen") - if (fs !== null) { - ScrollableFullScreen.empty.AttachTo("fullscreen") - fs.classList.add("hidden") - } - - ScrollableFullScreen._currentlyOpen?.isShown?.setData(false) } }) } + public static collapse(){ + const fs = document.getElementById("fullscreen") + if (fs !== null) { + ScrollableFullScreen.empty.AttachTo("fullscreen") + fs.classList.add("hidden") + } + + ScrollableFullScreen._currentlyOpen?.isShown?.setData(false) + } + Destroy() { this._fullscreencomponent.Destroy() } @@ -99,6 +103,7 @@ export default class ScrollableFullScreen { fs.classList.remove("hidden") } + private BuildComponent(title: BaseUIElement, content: BaseUIElement): BaseUIElement { const returnToTheMap = new Combine([ Svg.back_svg().SetClass("block md:hidden w-12 h-12 p-2 svg-foreground"), diff --git a/UI/DefaultGUI.ts b/UI/DefaultGUI.ts index 2702b3846..974bbc28c 100644 --- a/UI/DefaultGUI.ts +++ b/UI/DefaultGUI.ts @@ -123,6 +123,9 @@ export default class DefaultGUI { addNewPoint, hasPresets ? new AddNewMarker(state.filteredLayers) : noteMarker ) + state.LastClickLocation.addCallbackAndRunD(_ => { + ScrollableFullScreen.collapse() + }) } if (noteLayer !== undefined) { @@ -153,6 +156,16 @@ export default class DefaultGUI { state, }) + const selectedElement: FilteredLayer = state.filteredLayers.data.filter( + (l) => l.layerDef.id === "selected_element" + )[0] + new ShowDataLayer({ + leafletMap: state.leafletMap, + layerToShow: selectedElement.layerDef, + features: state.selectedElementsLayer, + state + }) + state.leafletMap.addCallbackAndRunD((_) => { // Lets assume that all showDataLayers are initialized at this point state.selectedElement.ping() diff --git a/UI/Popup/TagRenderingQuestion.ts b/UI/Popup/TagRenderingQuestion.ts index e9ac449f3..847f0993b 100644 --- a/UI/Popup/TagRenderingQuestion.ts +++ b/UI/Popup/TagRenderingQuestion.ts @@ -684,7 +684,6 @@ export default class TagRenderingQuestion extends Combine { const tagsData = tags.data const feature = state?.allElements?.ContainingFeatures?.get(tagsData.id) const center = feature != undefined ? GeoOperations.centerpointCoordinates(feature) : [0, 0] - console.log("Creating a tr-question with applicableUnit", applicableUnit) const input: InputElement = ValidatedTextField.ForType( configuration.freeform.type )?.ConstructInputElement({ diff --git a/UI/ShowDataLayer/ShowDataLayerImplementation.ts b/UI/ShowDataLayer/ShowDataLayerImplementation.ts index 637a8da5b..d4b931010 100644 --- a/UI/ShowDataLayer/ShowDataLayerImplementation.ts +++ b/UI/ShowDataLayer/ShowDataLayerImplementation.ts @@ -286,7 +286,6 @@ export default class ShowDataLayerImplementation { // Leaflet cannot handle geojson points natively // We have to convert them to the appropriate icon // Click handling is done in the next step - const layer: LayerConfig = this._layerToShow if (layer === undefined) { return @@ -337,7 +336,6 @@ export default class ShowDataLayerImplementation { const self = this function activate (event: LeafletMouseEvent) { - console.log("Activating!") if (infobox === undefined) { const tags = self.allElements?.getEventSourceById(key) ?? @@ -350,6 +348,14 @@ export default class ShowDataLayerImplementation { }) } infobox.Activate() + self._selectedElement.setData( self.allElements.ContainingFeatures.get(feature.id) ?? feature ) + event?.originalEvent?.preventDefault() + event?.originalEvent?.stopPropagation() + event?.originalEvent?.stopImmediatePropagation() + if(event?.originalEvent){ + // This is a total workaround, as 'preventDefault' and everything above seems to be not working + event.originalEvent["dismissed"] = true + } } leafletLayer.addEventListener('click', activate) diff --git a/assets/layers/selected_element/selected_element.json b/assets/layers/selected_element/selected_element.json new file mode 100644 index 000000000..7c9393ba3 --- /dev/null +++ b/assets/layers/selected_element/selected_element.json @@ -0,0 +1,20 @@ +{ + "id": "selected_element", + "description": { + "en": "Highlights the currently selected element. Override this layer to have different colors", + "nl": "Toont het geselecteerde element" + }, + "source": { + "osmTags": "selected=yes", + "maxCacheAge": 0 + }, + "mapRendering": [ + { + "icon": "circle:red", + "iconSize": "1,1,center", + "location": ["point","projected_centerpoint"], + "css": "box-shadow: red 0 0 20px 20px; z-index: -1; height: 1px; width: 1px;", + "cssClasses": "block relative rounded-full" + } + ] +}