From 2b47cf934caa2e1be9a5e0b17bdbc7585c1608e3 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 27 Apr 2023 02:24:38 +0200 Subject: [PATCH] Refactoring: highlight the currently selected element --- .../PerLayerFeatureSourceSplitter.ts | 2 +- .../Conversion/LegacyJsonConvert.ts | 2 +- Models/ThemeConfig/PointRenderingConfig.ts | 2 +- Models/ThemeViewState.ts | 54 ++++++++++++------- UI/Map/ShowDataLayer.ts | 49 +++++++++++++---- assets/layers/defibrillator/aed_checked.svg | 8 --- .../layers/defibrillator/defibrillator.json | 6 +-- assets/layers/defibrillator/defibrillator.svg | 45 ++++++++++++++++ assets/layers/defibrillator/license_info.json | 2 +- .../public_bookcase/public_bookcase.json | 4 +- assets/layers/toilet/toilet.json | 4 +- assets/layers/toilet/toilets.svg | 3 +- assets/svg/circle.svg | 11 ++-- assets/svg/pin.svg | 1 + assets/svg/square.svg | 25 +++++++-- assets/svg/star.svg | 4 +- assets/svg/teardrop.svg | 4 +- assets/themes/bookcases/bookcase.svg | 3 +- css/index-tailwind-output.css | 33 +++++++++--- index.css | 24 +++++++++ 20 files changed, 214 insertions(+), 72 deletions(-) delete mode 100644 assets/layers/defibrillator/aed_checked.svg create mode 100644 assets/layers/defibrillator/defibrillator.svg diff --git a/Logic/FeatureSource/PerLayerFeatureSourceSplitter.ts b/Logic/FeatureSource/PerLayerFeatureSourceSplitter.ts index f562e9748..38b64748b 100644 --- a/Logic/FeatureSource/PerLayerFeatureSourceSplitter.ts +++ b/Logic/FeatureSource/PerLayerFeatureSourceSplitter.ts @@ -107,7 +107,7 @@ export default class PerLayerFeatureSourceSplitter void) { + public forEach(f: (featureSource: T) => void) { for (const fs of this.perLayer.values()) { f(fs) } diff --git a/Models/ThemeConfig/Conversion/LegacyJsonConvert.ts b/Models/ThemeConfig/Conversion/LegacyJsonConvert.ts index 3b5ac7f8e..f0f9b805c 100644 --- a/Models/ThemeConfig/Conversion/LegacyJsonConvert.ts +++ b/Models/ThemeConfig/Conversion/LegacyJsonConvert.ts @@ -30,7 +30,7 @@ export class UpdateLegacyLayer extends DesugaringStep< config.source = config.source ?? { osmTags: config["overpassTags"], } - config.source.osmTags = config["overpassTags"] + config.source["osmTags"] = config["overpassTags"] delete config["overpassTags"] } diff --git a/Models/ThemeConfig/PointRenderingConfig.ts b/Models/ThemeConfig/PointRenderingConfig.ts index 0c51b0539..250f92e95 100644 --- a/Models/ThemeConfig/PointRenderingConfig.ts +++ b/Models/ThemeConfig/PointRenderingConfig.ts @@ -13,7 +13,7 @@ import Combine from "../../UI/Base/Combine" import { VariableUiElement } from "../../UI/Base/VariableUIElement" export default class PointRenderingConfig extends WithContextLoader { - private static readonly allowed_location_codes = new Set([ + static readonly allowed_location_codes: ReadonlySet = new Set([ "point", "centroid", "start", diff --git a/Models/ThemeViewState.ts b/Models/ThemeViewState.ts index 95d81d4bd..19af47bcc 100644 --- a/Models/ThemeViewState.ts +++ b/Models/ThemeViewState.ts @@ -476,29 +476,45 @@ export default class ThemeViewState implements SpecialVisualizationState { * Setup various services for which no reference are needed */ private initActors() { - this.selectedElement.addCallback((selected) => { - Hash.hash.setData(selected?.properties?.id) - }) + { + // Set the hash based on the selected element... + this.selectedElement.addCallback((selected) => { + Hash.hash.setData(selected?.properties?.id) + }) + // ... search and select an element based on the hash + Hash.hash.mapD( + (hash) => { + console.log("Searching for an id:", hash) + if (this.selectedElement.data?.properties?.id === hash) { + // We already have the correct hash + return + } - Hash.hash.mapD( - (hash) => { - console.log("Searching for an id:", hash) - if (this.selectedElement.data?.properties?.id === hash) { - // We already have the correct hash + const found = this.indexedFeatures.featuresById.data?.get(hash) + if (!found) { + return + } + const layer = this.layout.getMatchingLayer(found.properties) + this.selectedElement.setData(found) + this.selectedLayer.setData(layer) + }, + [this.indexedFeatures.featuresById.stabilized(250)] + ) + } + + { + // Unselect the selected element if it is panned out of view + this.mapProperties.bounds.stabilized(250).addCallbackD((bounds) => { + const selected = this.selectedElement.data + if (selected === undefined) { return } - - const found = this.indexedFeatures.featuresById.data?.get(hash) - if (!found) { - return + const bbox = BBox.get(selected) + if (!bbox.overlapsWith(bounds)) { + this.selectedElement.setData(undefined) } - const layer = this.layout.getMatchingLayer(found.properties) - this.selectedElement.setData(found) - this.selectedLayer.setData(layer) - }, - [this.indexedFeatures.featuresById.stabilized(250)] - ) - + }) + } new MetaTagging(this) new TitleHandler(this.selectedElement, this.selectedLayer, this.featureProperties, this) new ChangeToElementsActor(this.changes, this.featureProperties) diff --git a/UI/Map/ShowDataLayer.ts b/UI/Map/ShowDataLayer.ts index 7c1189ab1..de2bacd78 100644 --- a/UI/Map/ShowDataLayer.ts +++ b/UI/Map/ShowDataLayer.ts @@ -6,7 +6,7 @@ import { GeoOperations } from "../../Logic/GeoOperations" import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import PointRenderingConfig from "../../Models/ThemeConfig/PointRenderingConfig" import { OsmTags } from "../../Models/OsmFeature" -import { FeatureSource } from "../../Logic/FeatureSource/FeatureSource" +import { FeatureSource, FeatureSourceForLayer } from "../../Logic/FeatureSource/FeatureSource" import { BBox } from "../../Logic/BBox" import { Feature, Point } from "geojson" import LineRenderingConfig from "../../Models/ThemeConfig/LineRenderingConfig" @@ -15,7 +15,7 @@ import * as range_layer from "../../assets/layers/range/range.json" import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson" import PerLayerFeatureSourceSplitter from "../../Logic/FeatureSource/PerLayerFeatureSourceSplitter" import FilteredLayer from "../../Models/FilteredLayer" -import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource" +import SimpleFeatureSource from "../../Logic/FeatureSource/Sources/SimpleFeatureSource" class PointRenderingLayer { private readonly _config: PointRenderingConfig @@ -24,6 +24,8 @@ class PointRenderingLayer { private readonly _map: MlMap private readonly _onClick: (feature: Feature) => void private readonly _allMarkers: Map = new Map() + private readonly _selectedElement: Store<{ properties: { id?: string } }> + private readonly _markedAsSelected: HTMLElement[] = [] private _dirty = false constructor( @@ -32,13 +34,15 @@ class PointRenderingLayer { config: PointRenderingConfig, visibility?: Store, fetchStore?: (id: string) => Store>, - onClick?: (feature: Feature) => void + onClick?: (feature: Feature) => void, + selectedElement?: Store<{ properties: { id?: string } }> ) { this._visibility = visibility this._config = config this._map = map this._fetchStore = fetchStore this._onClick = onClick + this._selectedElement = selectedElement const self = this features.features.addCallbackAndRunD((features) => self.updateFeatures(features)) @@ -48,6 +52,24 @@ class PointRenderingLayer { } self.setVisibility(visible) }) + selectedElement?.addCallbackAndRun((selected) => { + this._markedAsSelected.forEach((el) => el.classList.remove("selected")) + this._markedAsSelected.splice(0, this._markedAsSelected.length) + if (selected === undefined) { + return + } + PointRenderingConfig.allowed_location_codes.forEach((code) => { + const marker = this._allMarkers + .get(selected.properties?.id + "-" + code) + ?.getElement() + if (marker === undefined) { + return + } + console.log("Marking", marker, "as selected for config", config) + marker?.classList?.add("selected") + this._markedAsSelected.push(marker) + }) + }) } private updateFeatures(features: Feature[]) { @@ -91,6 +113,10 @@ class PointRenderingLayer { } const marker = this.addPoint(feature, loc) + if (this._selectedElement?.data === feature.properties.id) { + marker.getElement().classList.add("selected") + this._markedAsSelected.push(marker.getElement()) + } cache.set(id, marker) } } @@ -179,6 +205,7 @@ class LineRenderingLayer { private readonly _onClick?: (feature: Feature) => void private readonly _layername: string private readonly _listenerInstalledOn: Set = new Set() + private currentSourceData constructor( map: MlMap, @@ -228,7 +255,6 @@ class LineRenderingLayer { return calculatedProps } - private currentSourceData private async update(featureSource: Store) { const map = this._map while (!map.isStyleLoaded()) { @@ -380,10 +406,14 @@ export default class ShowDataLayer { layers: LayerConfig[], options?: Partial ) { - const perLayer = new PerLayerFeatureSourceSplitter( - layers.filter((l) => l.source !== null).map((l) => new FilteredLayer(l)), - new StaticFeatureSource(features) - ) + const perLayer: PerLayerFeatureSourceSplitter = + new PerLayerFeatureSourceSplitter( + layers.filter((l) => l.source !== null).map((l) => new FilteredLayer(l)), + features, + { + constructStore: (features, layer) => new SimpleFeatureSource(layer, features), + } + ) perLayer.forEach((fs) => { new ShowDataLayer(mlmap, { layer: fs.layer.layerDef, @@ -445,7 +475,8 @@ export default class ShowDataLayer { pointRenderingConfig, doShowLayer, fetchStore, - onClick + onClick, + selectedElement ) } } diff --git a/assets/layers/defibrillator/aed_checked.svg b/assets/layers/defibrillator/aed_checked.svg deleted file mode 100644 index 246aab1e2..000000000 --- a/assets/layers/defibrillator/aed_checked.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/assets/layers/defibrillator/defibrillator.json b/assets/layers/defibrillator/defibrillator.json index 858e5755a..2ab15b75d 100644 --- a/assets/layers/defibrillator/defibrillator.json +++ b/assets/layers/defibrillator/defibrillator.json @@ -650,11 +650,11 @@ "mapRendering": [ { "icon": { - "render": "./assets/themes/aed/aed.svg", + "render": "square:#008754;./assets/layers/defibrillator/defibrillator.svg", "mappings": [ { "if": "_recently_surveyed=true", - "then": "./assets/layers/defibrillator/aed_checked.svg" + "then": "square:#28ba3d;./assets/layers/defibrillator/defibrillator.svg" } ] }, @@ -670,4 +670,4 @@ "has_image", "open_now" ] -} \ No newline at end of file +} diff --git a/assets/layers/defibrillator/defibrillator.svg b/assets/layers/defibrillator/defibrillator.svg new file mode 100644 index 000000000..cc3aca615 --- /dev/null +++ b/assets/layers/defibrillator/defibrillator.svg @@ -0,0 +1,45 @@ + + + + + + + + + diff --git a/assets/layers/defibrillator/license_info.json b/assets/layers/defibrillator/license_info.json index 353a61246..28426cd49 100644 --- a/assets/layers/defibrillator/license_info.json +++ b/assets/layers/defibrillator/license_info.json @@ -1,6 +1,6 @@ [ { - "path": "aed_checked.svg", + "path": "defibrillator.svg", "license": "CC0", "authors": [ "MaxxL" diff --git a/assets/layers/public_bookcase/public_bookcase.json b/assets/layers/public_bookcase/public_bookcase.json index cf839bd01..ad38f0d54 100644 --- a/assets/layers/public_bookcase/public_bookcase.json +++ b/assets/layers/public_bookcase/public_bookcase.json @@ -521,7 +521,7 @@ "mapRendering": [ { "icon": { - "render": "./assets/themes/bookcases/bookcase.svg" + "render": "circle:#ffffff;./assets/themes/bookcases/bookcase.svg" }, "label": { "mappings": [ @@ -545,4 +545,4 @@ } } ] -} \ No newline at end of file +} diff --git a/assets/layers/toilet/toilet.json b/assets/layers/toilet/toilet.json index 67041a248..02ae4005c 100644 --- a/assets/layers/toilet/toilet.json +++ b/assets/layers/toilet/toilet.json @@ -681,7 +681,7 @@ "mapRendering": [ { "icon": { - "render": "./assets/layers/toilet/toilets.svg", + "render": "circle:#ffffff;./assets/layers/toilet/toilets.svg", "mappings": [ { "if": { @@ -761,4 +761,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/assets/layers/toilet/toilets.svg b/assets/layers/toilet/toilets.svg index 02ade60d5..6edb85774 100644 --- a/assets/layers/toilet/toilets.svg +++ b/assets/layers/toilet/toilets.svg @@ -1,7 +1,6 @@ - - \ No newline at end of file + diff --git a/assets/svg/circle.svg b/assets/svg/circle.svg index 2cfde629f..fc6efd4fc 100644 --- a/assets/svg/circle.svg +++ b/assets/svg/circle.svg @@ -1,6 +1,9 @@ - - - - \ No newline at end of file + + + + diff --git a/assets/svg/pin.svg b/assets/svg/pin.svg index 4a10b32da..83f6ab2f6 100644 --- a/assets/svg/pin.svg +++ b/assets/svg/pin.svg @@ -11,6 +11,7 @@ id="defs11" /> diff --git a/assets/svg/square.svg b/assets/svg/square.svg index 0bad670ff..30a997c84 100644 --- a/assets/svg/square.svg +++ b/assets/svg/square.svg @@ -1,6 +1,21 @@ - - - - + + + + - \ No newline at end of file + diff --git a/assets/svg/star.svg b/assets/svg/star.svg index d7148bcef..61301ada9 100644 --- a/assets/svg/star.svg +++ b/assets/svg/star.svg @@ -1,6 +1,6 @@ - + - \ No newline at end of file + diff --git a/assets/svg/teardrop.svg b/assets/svg/teardrop.svg index efed7c4eb..c3f5edf4f 100644 --- a/assets/svg/teardrop.svg +++ b/assets/svg/teardrop.svg @@ -1,7 +1,7 @@ - + - \ No newline at end of file + diff --git a/assets/themes/bookcases/bookcase.svg b/assets/themes/bookcases/bookcase.svg index a49c40b27..a7240f2ba 100644 --- a/assets/themes/bookcases/bookcase.svg +++ b/assets/themes/bookcases/bookcase.svg @@ -1,7 +1,6 @@ - - \ No newline at end of file + diff --git a/css/index-tailwind-output.css b/css/index-tailwind-output.css index 4ce129e4f..3485f6eb7 100644 --- a/css/index-tailwind-output.css +++ b/css/index-tailwind-output.css @@ -2094,6 +2094,31 @@ li::marker { display: none; } +.selected svg path.selectable { + stroke: white !important; + stroke-width: 20px !important; + /* filter: drop-shadow(5px 5px 40px rgb(0 0 0 / 0.6));*/ + overflow: visible !important; + -webkit-animation: glowing-drop-shadow 1s ease-in-out infinite alternate; + animation: glowing-drop-shadow 1s ease-in-out infinite alternate; +} + +.selected svg { + overflow: visible !important; +} + +@-webkit-keyframes glowing-drop-shadow { + from { + -webkit-filter: drop-shadow(5px 5px 60px rgb(128 128 128 / 0.6)); + filter: drop-shadow(5px 5px 60px rgb(128 128 128 / 0.6)); + } + + to { + -webkit-filter: drop-shadow(5px 5px 80px rgb(0.5 0.5 0.5 / 0.8)); + filter: drop-shadow(5px 5px 80px rgb(0.5 0.5 0.5 / 0.8)); + } +} + /**************** GENERIC ****************/ .alert { @@ -2355,18 +2380,10 @@ input { height: 6rem; } - .sm\:h-6 { - height: 1.5rem; - } - .sm\:w-24 { width: 6rem; } - .sm\:w-6 { - width: 1.5rem; - } - .sm\:max-w-xl { max-width: 36rem; } diff --git a/index.css b/index.css index a98dc2916..d2378b8c7 100644 --- a/index.css +++ b/index.css @@ -304,6 +304,30 @@ li::marker { display: none; } +.selected svg path.selectable { + stroke: white !important; + stroke-width: 20px !important; + overflow: visible !important; + -webkit-animation: glowing-drop-shadow 1s ease-in-out infinite alternate; + -moz-animation: glowing-drop-shadow 1s ease-in-out infinite alternate; + animation: glowing-drop-shadow 1s ease-in-out infinite alternate; +} + +.selected svg { + overflow: visible !important; +} + + +@-webkit-keyframes glowing-drop-shadow { + from { + filter: drop-shadow(5px 5px 60px rgb(128 128 128 / 0.6)); + } + to { + filter: drop-shadow(5px 5px 80px rgb(0.5 0.5 0.5 / 0.8)); + } +} + + /**************** GENERIC ****************/ .alert {