forked from MapComplete/MapComplete
		
	Feature: add favourite
This commit is contained in:
		
							parent
							
								
									a32ab16a5e
								
							
						
					
					
						commit
						f9827dd6ae
					
				
					 68 changed files with 1641 additions and 885 deletions
				
			
		
							
								
								
									
										157
									
								
								src/Logic/FeatureSource/Sources/FavouritesFeatureSource.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								src/Logic/FeatureSource/Sources/FavouritesFeatureSource.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,157 @@ | |||
| import StaticFeatureSource from "./StaticFeatureSource" | ||||
| import { Feature } from "geojson" | ||||
| import { Store, UIEventSource } from "../../UIEventSource" | ||||
| import { OsmConnection } from "../../Osm/OsmConnection" | ||||
| import { OsmId } from "../../../Models/OsmFeature" | ||||
| import { GeoOperations } from "../../GeoOperations" | ||||
| import FeaturePropertiesStore from "../Actors/FeaturePropertiesStore" | ||||
| import { IndexedFeatureSource } from "../FeatureSource" | ||||
| import LayoutConfig from "../../../Models/ThemeConfig/LayoutConfig" | ||||
| 
 | ||||
| /** | ||||
|  * Generates the favourites from the preferences and marks them as favourite | ||||
|  */ | ||||
| export default class FavouritesFeatureSource extends StaticFeatureSource { | ||||
|     public static readonly prefix = "mapcomplete-favourite-" | ||||
|     private readonly _osmConnection: OsmConnection | ||||
| 
 | ||||
|     constructor( | ||||
|         connection: OsmConnection, | ||||
|         indexedSource: FeaturePropertiesStore, | ||||
|         allFeatures: IndexedFeatureSource, | ||||
|         layout: LayoutConfig | ||||
|     ) { | ||||
|         const detectedIds = new UIEventSource<Set<string>>(undefined) | ||||
|         const features: Store<Feature[]> = connection.preferencesHandler.preferences.map( | ||||
|             (prefs) => { | ||||
|                 const feats: Feature[] = [] | ||||
|                 const allIds = new Set<string>() | ||||
|                 for (const key in prefs) { | ||||
|                     if (!key.startsWith(FavouritesFeatureSource.prefix)) { | ||||
|                         continue | ||||
|                     } | ||||
|                     const id = key.substring(FavouritesFeatureSource.prefix.length) | ||||
|                     const osmId = id.replace("-", "/") | ||||
|                     if (id.indexOf("-property-") > 0 || id.indexOf("-layer") > 0) { | ||||
|                         continue | ||||
|                     } | ||||
|                     allIds.add(osmId) | ||||
|                     const geometry = <[number, number]>JSON.parse(prefs[key]) | ||||
|                     const properties = FavouritesFeatureSource.getPropertiesFor(connection, id) | ||||
|                     properties._orig_layer = prefs[FavouritesFeatureSource.prefix + id + "-layer"] | ||||
|                     if (layout.layers.some((l) => l.id === properties._orig_layer)) { | ||||
|                         continue | ||||
|                     } | ||||
|                     properties.id = osmId | ||||
|                     properties._favourite = "yes" | ||||
|                     feats.push({ | ||||
|                         type: "Feature", | ||||
|                         properties, | ||||
|                         geometry: { | ||||
|                             type: "Point", | ||||
|                             coordinates: geometry, | ||||
|                         }, | ||||
|                     }) | ||||
|                 } | ||||
|                 console.log("Favouritess are", feats) | ||||
|                 detectedIds.setData(allIds) | ||||
|                 return feats | ||||
|             } | ||||
|         ) | ||||
| 
 | ||||
|         super(features) | ||||
| 
 | ||||
|         this._osmConnection = connection | ||||
|         detectedIds.addCallbackAndRunD((detected) => | ||||
|             this.markFeatures(detected, indexedSource, allFeatures) | ||||
|         ) | ||||
|         // We use the indexedFeatureSource as signal to update
 | ||||
|         allFeatures.features.map((_) => | ||||
|             this.markFeatures(detectedIds.data, indexedSource, allFeatures) | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     private static getPropertiesFor( | ||||
|         osmConnection: OsmConnection, | ||||
|         id: string | ||||
|     ): Record<string, string> { | ||||
|         const properties: Record<string, string> = {} | ||||
|         const prefs = osmConnection.preferencesHandler.preferences.data | ||||
|         const minLength = FavouritesFeatureSource.prefix.length + id.length + "-property-".length | ||||
|         for (const key in prefs) { | ||||
|             if (key.length < minLength) { | ||||
|                 continue | ||||
|             } | ||||
|             if (!key.startsWith(FavouritesFeatureSource.prefix + id)) { | ||||
|                 continue | ||||
|             } | ||||
|             const propertyName = key.substring(minLength) | ||||
|             properties[propertyName] = prefs[key] | ||||
|         } | ||||
|         return properties | ||||
|     } | ||||
| 
 | ||||
|     public markAsFavourite( | ||||
|         feature: Feature, | ||||
|         layer: string, | ||||
|         theme: string, | ||||
|         tags: UIEventSource<Record<string, string> & { id: OsmId }>, | ||||
|         isFavourite: boolean = true | ||||
|     ) { | ||||
|         { | ||||
|             const id = tags.data.id.replace("/", "-") | ||||
|             const pref = this._osmConnection.GetPreference("favourite-" + id) | ||||
|             if (isFavourite) { | ||||
|                 const center = GeoOperations.centerpointCoordinates(feature) | ||||
|                 pref.setData(JSON.stringify(center)) | ||||
|                 this._osmConnection.GetPreference("favourite-" + id + "-layer").setData(layer) | ||||
|                 this._osmConnection.GetPreference("favourite-" + id + "-theme").setData(theme) | ||||
| 
 | ||||
|                 for (const key in tags.data) { | ||||
|                     const pref = this._osmConnection.GetPreference( | ||||
|                         "favourite-" + id + "-property-" + key.replaceAll(":", "__") | ||||
|                     ) | ||||
|                     pref.setData(tags.data[key]) | ||||
|                 } | ||||
|             } else { | ||||
|                 this._osmConnection.preferencesHandler.removeAllWithPrefix( | ||||
|                     "mapcomplete-favourite-" + id | ||||
|                 ) | ||||
|             } | ||||
|         } | ||||
|         if (isFavourite) { | ||||
|             tags.data._favourite = "yes" | ||||
|             tags.ping() | ||||
|         } else { | ||||
|             delete tags.data._favourite | ||||
|             tags.ping() | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private markFeatures( | ||||
|         detected: Set<string>, | ||||
|         featureProperties: FeaturePropertiesStore, | ||||
|         allFeatures: IndexedFeatureSource | ||||
|     ) { | ||||
|         const feature = allFeatures.features.data | ||||
|         for (const f of feature) { | ||||
|             const id = f.properties.id | ||||
|             if (!id) { | ||||
|                 continue | ||||
|             } | ||||
|             const store = featureProperties.getStore(id) | ||||
|             const origValue = store.data._favourite | ||||
|             if (detected.has(id)) { | ||||
|                 if (origValue !== "yes") { | ||||
|                     store.data._favourite = "yes" | ||||
|                     store.ping() | ||||
|                 } | ||||
|             } else { | ||||
|                 if (origValue) { | ||||
|                     store.data._favourite = "" | ||||
|                     store.ping() | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -6,10 +6,14 @@ import FilteringFeatureSource from "./FilteringFeatureSource" | |||
| import LayerState from "../../State/LayerState" | ||||
| 
 | ||||
| export default class NearbyFeatureSource implements FeatureSource { | ||||
|     private readonly _result = new UIEventSource<Feature[]>(undefined) | ||||
| 
 | ||||
|     public readonly features: Store<Feature[]> | ||||
|     private readonly _targetPoint: Store<{ lon: number; lat: number }> | ||||
|     private readonly _numberOfNeededFeatures: number | ||||
|     private readonly _layerState?: LayerState | ||||
|     private readonly _currentZoom: Store<number> | ||||
|     private readonly _allSources: Store<{ feat: Feature; d: number }[]>[] = [] | ||||
| 
 | ||||
|     constructor( | ||||
|         targetPoint: Store<{ lon: number; lat: number }>, | ||||
|  | @ -18,41 +22,44 @@ export default class NearbyFeatureSource implements FeatureSource { | |||
|         layerState?: LayerState, | ||||
|         currentZoom?: Store<number> | ||||
|     ) { | ||||
|         this._layerState = layerState | ||||
|         this._targetPoint = targetPoint.stabilized(100) | ||||
|         this._numberOfNeededFeatures = numberOfNeededFeatures | ||||
|         this._currentZoom = currentZoom.stabilized(500) | ||||
| 
 | ||||
|         const allSources: Store<{ feat: Feature; d: number }[]>[] = [] | ||||
|         this.features = Stores.ListStabilized(this._result) | ||||
| 
 | ||||
|         sources.forEach((source, layer) => {}) | ||||
|     } | ||||
| 
 | ||||
|     public registerSource(source: FeatureSource, layerId: string) { | ||||
|         let minzoom = 999 | ||||
| 
 | ||||
|         const result = new UIEventSource<Feature[]>(undefined) | ||||
|         this.features = Stores.ListStabilized(result) | ||||
| 
 | ||||
|         function update() { | ||||
|             let features: { feat: Feature; d: number }[] = [] | ||||
|             for (const src of allSources) { | ||||
|                 features.push(...src.data) | ||||
|             } | ||||
|             features.sort((a, b) => a.d - b.d) | ||||
|             if (numberOfNeededFeatures !== undefined) { | ||||
|                 features = features.slice(0, numberOfNeededFeatures) | ||||
|             } | ||||
|             result.setData(features.map((f) => f.feat)) | ||||
|         const flayer = this._layerState?.filteredLayers.get(layerId) | ||||
|         if (!flayer) { | ||||
|             return | ||||
|         } | ||||
| 
 | ||||
|         sources.forEach((source, layer) => { | ||||
|             const flayer = layerState?.filteredLayers.get(layer) | ||||
|             minzoom = Math.min(minzoom, flayer.layerDef.minzoom) | ||||
|             const calcSource = this.createSource( | ||||
|                 source.features, | ||||
|                 flayer.layerDef.minzoom, | ||||
|                 flayer.isDisplayed | ||||
|             ) | ||||
|             calcSource.addCallbackAndRunD((features) => { | ||||
|                 update() | ||||
|             }) | ||||
|             allSources.push(calcSource) | ||||
|         minzoom = Math.min(minzoom, flayer.layerDef.minzoom) | ||||
|         const calcSource = this.createSource( | ||||
|             source.features, | ||||
|             flayer.layerDef.minzoom, | ||||
|             flayer.isDisplayed | ||||
|         ) | ||||
|         calcSource.addCallbackAndRunD((features) => { | ||||
|             this.update() | ||||
|         }) | ||||
|         this._allSources.push(calcSource) | ||||
|     } | ||||
| 
 | ||||
|     private update() { | ||||
|         let features: { feat: Feature; d: number }[] = [] | ||||
|         for (const src of this._allSources) { | ||||
|             features.push(...src.data) | ||||
|         } | ||||
|         features.sort((a, b) => a.d - b.d) | ||||
|         if (this._numberOfNeededFeatures !== undefined) { | ||||
|             features = features.slice(0, this._numberOfNeededFeatures) | ||||
|         } | ||||
|         this._result.setData(features.map((f) => f.feat)) | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  |  | |||
|  | @ -285,4 +285,13 @@ export class OsmPreferences { | |||
|             } | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     removeAllWithPrefix(prefix: string) { | ||||
|         for (const key in this.preferences.data) { | ||||
|             if (key.startsWith(prefix)) { | ||||
|                 this.GetPreference(key, undefined, { prefix: "" }).setData(undefined) | ||||
|                 console.log("Clearing preference", key) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -23,6 +23,7 @@ export default class Constants { | |||
|         "gps_track", | ||||
|         "range", | ||||
|         "last_click", | ||||
|         "favourite", | ||||
|     ] as const | ||||
|     /** | ||||
|      * Special layers which are not included in a theme by default | ||||
|  | @ -131,6 +132,8 @@ export default class Constants { | |||
|         "clock", | ||||
|         "invalid", | ||||
|         "close", | ||||
|         "heart", | ||||
|         "heart_outline", | ||||
|     ] as const | ||||
|     public static readonly defaultPinIcons: string[] = <any>Constants._defaultPinIcons | ||||
| 
 | ||||
|  |  | |||
|  | @ -26,7 +26,7 @@ import predifined_filters from "../../../../assets/layers/filters/filters.json" | |||
| import { TagConfigJson } from "../Json/TagConfigJson" | ||||
| import PointRenderingConfigJson, { IconConfigJson } from "../Json/PointRenderingConfigJson" | ||||
| import ValidationUtils from "./ValidationUtils" | ||||
| import { RenderingSpecification, SpecialVisualization } from "../../../UI/SpecialVisualization" | ||||
| import { RenderingSpecification } from "../../../UI/SpecialVisualization" | ||||
| import { QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson" | ||||
| import { ConfigMeta } from "../../../UI/Studio/configMeta" | ||||
| import LineRenderingConfigJson from "../Json/LineRenderingConfigJson" | ||||
|  | @ -639,6 +639,13 @@ export class AddEditingElements extends DesugaringStep<LayerConfigJson> { | |||
|             json.tagRenderings.push(this._desugaring.tagRenderings.get("last_edit")) | ||||
|         } | ||||
| 
 | ||||
|         if (!usedSpecialFunctions.has("favourite_status")) { | ||||
|             json.tagRenderings.push({ | ||||
|                 id: "favourite_status", | ||||
|                 render: { "*": "{favourite_status()}" }, | ||||
|             }) | ||||
|         } | ||||
| 
 | ||||
|         if (!usedSpecialFunctions.has("all_tags")) { | ||||
|             const trc: QuestionableTagRenderingConfigJson = { | ||||
|                 id: "all-tags", | ||||
|  | @ -1193,6 +1200,31 @@ class ExpandMarkerRenderings extends DesugaringStep<IconConfigJson> { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| class AddFavouriteBadges extends DesugaringStep<LayerConfigJson> { | ||||
|     constructor() { | ||||
|         super( | ||||
|             "Adds the favourite heart to the title and the rendering badges", | ||||
|             [], | ||||
|             "AddFavouriteBadges" | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     convert(json: LayerConfigJson, context: ConversionContext): LayerConfigJson { | ||||
|         if (json.id === "favourite") { | ||||
|             return json | ||||
|         } | ||||
|         const pr = json.pointRendering?.[0] | ||||
|         if (pr) { | ||||
|             pr.iconBadges ??= [] | ||||
|             if (!pr.iconBadges.some((ti) => ti.if === "_favourite=yes")) { | ||||
|                 pr.iconBadges.push({ if: "_favourite=yes", then: "circle:white;heart:red" }) | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return json | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| export class AddRatingBadge extends DesugaringStep<LayerConfigJson> { | ||||
|     constructor() { | ||||
|         super( | ||||
|  | @ -1206,6 +1238,10 @@ export class AddRatingBadge extends DesugaringStep<LayerConfigJson> { | |||
|         if (!json.tagRenderings) { | ||||
|             return json | ||||
|         } | ||||
|         if (json.titleIcons.some((ti) => ti === "icons.rating" || ti["id"] === "rating")) { | ||||
|             // already added
 | ||||
|             return json | ||||
|         } | ||||
| 
 | ||||
|         const specialVis: Exclude<RenderingSpecification, string>[] = < | ||||
|             Exclude<RenderingSpecification, string>[] | ||||
|  | @ -1247,6 +1283,7 @@ export class PrepareLayer extends Fuse<LayerConfigJson> { | |||
|             ), | ||||
|             new SetDefault("titleIcons", ["icons.defaults"]), | ||||
|             new AddRatingBadge(), | ||||
|             new AddFavouriteBadges(), | ||||
|             new On( | ||||
|                 "titleIcons", | ||||
|                 (layer) => | ||||
|  |  | |||
|  | @ -968,7 +968,7 @@ export class ValidateTagRenderings extends Fuse<TagRenderingConfigJson> { | |||
|             "Various validation on tagRenderingConfigs", | ||||
|             new DetectShadowedMappings(layerConfig), | ||||
|             new DetectConflictingAddExtraTags(), | ||||
|             new DetectNonErasedKeysInMappings(), | ||||
|             //    new DetectNonErasedKeysInMappings(),
 | ||||
|             new DetectMappingsWithImages(doesImageExist), | ||||
|             new On("render", new ValidatePossibleLinks()), | ||||
|             new On("question", new ValidatePossibleLinks()), | ||||
|  |  | |||
|  | @ -305,6 +305,9 @@ export default class LayoutConfig implements LayoutInformation { | |||
|         } | ||||
|         for (const layer of this.layers) { | ||||
|             if (!layer.source) { | ||||
|                 if (layer.isShown?.matchesProperties(tags)) { | ||||
|                     return layer | ||||
|                 } | ||||
|                 continue | ||||
|             } | ||||
|             if (layer.source.osmTags.matchesProperties(tags)) { | ||||
|  |  | |||
|  | @ -58,6 +58,7 @@ import { PreferredRasterLayerSelector } from "../Logic/Actors/PreferredRasterLay | |||
| import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager" | ||||
| import { Imgur } from "../Logic/ImageProviders/Imgur" | ||||
| import NearbyFeatureSource from "../Logic/FeatureSource/Sources/NearbyFeatureSource" | ||||
| import FavouritesFeatureSource from "../Logic/FeatureSource/Sources/FavouritesFeatureSource" | ||||
| 
 | ||||
| /** | ||||
|  * | ||||
|  | @ -96,10 +97,11 @@ export default class ThemeViewState implements SpecialVisualizationState { | |||
|     readonly indexedFeatures: IndexedFeatureSource & LayoutSource | ||||
|     readonly currentView: FeatureSource<Feature<Polygon>> | ||||
|     readonly featuresInView: FeatureSource | ||||
|     readonly favourites: FavouritesFeatureSource | ||||
|     /** | ||||
|      * Contains a few (<10) >features that are near the center of the map. | ||||
|      */ | ||||
|     readonly closestFeatures: FeatureSource | ||||
|     readonly closestFeatures: NearbyFeatureSource | ||||
|     readonly newFeatures: WritableFeatureSource | ||||
|     readonly layerState: LayerState | ||||
|     readonly perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer> | ||||
|  | @ -220,8 +222,6 @@ export default class ThemeViewState implements SpecialVisualizationState { | |||
|                 this.fullNodeDatabase | ||||
|             ) | ||||
| 
 | ||||
|             this.indexedFeatures = layoutSource | ||||
| 
 | ||||
|             let currentViewIndex = 0 | ||||
|             const empty = [] | ||||
|             this.currentView = new StaticFeatureSource( | ||||
|  | @ -242,13 +242,19 @@ export default class ThemeViewState implements SpecialVisualizationState { | |||
|             this.featuresInView = new BBoxFeatureSource(layoutSource, this.mapProperties.bounds) | ||||
| 
 | ||||
|             this.dataIsLoading = layoutSource.isLoading | ||||
|             this.indexedFeatures = layoutSource | ||||
|             this.featureProperties = new FeaturePropertiesStore(layoutSource) | ||||
|             this.favourites = new FavouritesFeatureSource( | ||||
|                 this.osmConnection, | ||||
|                 this.featureProperties, | ||||
|                 layoutSource, | ||||
|                 layout | ||||
|             ) | ||||
| 
 | ||||
|             const indexedElements = this.indexedFeatures | ||||
|             this.featureProperties = new FeaturePropertiesStore(indexedElements) | ||||
|             this.changes = new Changes( | ||||
|                 { | ||||
|                     dryRun: this.featureSwitches.featureSwitchIsTesting, | ||||
|                     allElements: indexedElements, | ||||
|                     allElements: layoutSource, | ||||
|                     featurePropertiesStore: this.featureProperties, | ||||
|                     osmConnection: this.osmConnection, | ||||
|                     historicalUserLocations: this.geolocation.historicalUserLocations, | ||||
|  | @ -258,7 +264,7 @@ export default class ThemeViewState implements SpecialVisualizationState { | |||
|             this.historicalUserLocations = this.geolocation.historicalUserLocations | ||||
|             this.newFeatures = new NewGeometryFromChangesFeatureSource( | ||||
|                 this.changes, | ||||
|                 indexedElements, | ||||
|                 layoutSource, | ||||
|                 this.featureProperties | ||||
|             ) | ||||
|             layoutSource.addSource(this.newFeatures) | ||||
|  | @ -627,7 +633,10 @@ export default class ThemeViewState implements SpecialVisualizationState { | |||
|                 ) | ||||
|             ), | ||||
|             current_view: this.currentView, | ||||
|             favourite: this.favourites, | ||||
|         } | ||||
| 
 | ||||
|         this.closestFeatures.registerSource(specialLayers.favourite, "favourite") | ||||
|         if (this.layout?.lockLocation) { | ||||
|             const bbox = new BBox(this.layout.lockLocation) | ||||
|             this.mapProperties.maxbounds.setData(bbox) | ||||
|  | @ -663,12 +672,16 @@ export default class ThemeViewState implements SpecialVisualizationState { | |||
|             rangeIsDisplayed?.syncWith(this.featureSwitches.featureSwitchIsTesting, true) | ||||
|         } | ||||
| 
 | ||||
|         // enumarate all 'normal' layers and match them with the appropriate 'special' layer - if applicable
 | ||||
|         this.layerState.filteredLayers.forEach((flayer) => { | ||||
|             const id = flayer.layerDef.id | ||||
|             const features: FeatureSource = specialLayers[id] | ||||
|             if (features === undefined) { | ||||
|                 return | ||||
|             } | ||||
|             if (id === "favourite") { | ||||
|                 console.log("Matching special layer", id, flayer) | ||||
|             } | ||||
| 
 | ||||
|             this.featureProperties.trackFeatureSource(features) | ||||
|             new ShowDataLayer(this.map, { | ||||
|  |  | |||
|  | @ -4,12 +4,10 @@ | |||
|   import Translations from "../i18n/Translations"; | ||||
|   import Tr from "./Tr.svelte"; | ||||
| 
 | ||||
|   export let osmConnection: OsmConnection | ||||
|   export let osmConnection: OsmConnection; | ||||
| </script> | ||||
| 
 | ||||
| <button on:click={() => { | ||||
|                         state.osmConnection.LogOut() | ||||
|                     }}> | ||||
|   <Logout class="w-6 h-6"/> | ||||
|   <Tr t={Translations.t.general.logout}/> | ||||
| <button on:click={() => {osmConnection.LogOut()}}> | ||||
|   <Logout class="w-6 h-6" /> | ||||
|   <Tr t={Translations.t.general.logout} /> | ||||
| </button> | ||||
|  |  | |||
|  | @ -1,27 +1,7 @@ | |||
| <script lang="ts"> | ||||
|   import { IconConfig } from "../../Models/ThemeConfig/PointRenderingConfig" | ||||
|   import { Store } from "../../Logic/UIEventSource" | ||||
|   import Pin from "../../assets/svg/Pin.svelte" | ||||
|   import Square from "../../assets/svg/Square.svelte" | ||||
|   import Circle from "../../assets/svg/Circle.svelte" | ||||
|   import Checkmark from "../../assets/svg/Checkmark.svelte" | ||||
|   import Clock from "../../assets/svg/Clock.svelte" | ||||
|   import Close from "../../assets/svg/Close.svelte" | ||||
|   import Crosshair from "../../assets/svg/Crosshair.svelte" | ||||
|   import Help from "../../assets/svg/Help.svelte" | ||||
|   import Home from "../../assets/svg/Home.svelte" | ||||
|   import Invalid from "../../assets/svg/Invalid.svelte" | ||||
|   import Location from "../../assets/svg/Location.svelte" | ||||
|   import Location_empty from "../../assets/svg/Location_empty.svelte" | ||||
|   import Location_locked from "../../assets/svg/Location_locked.svelte" | ||||
|   import Note from "../../assets/svg/Note.svelte" | ||||
|   import Resolved from "../../assets/svg/Resolved.svelte" | ||||
|   import Ring from "../../assets/svg/Ring.svelte" | ||||
|   import Scissors from "../../assets/svg/Scissors.svelte" | ||||
|   import Teardrop from "../../assets/svg/Teardrop.svelte" | ||||
|   import Teardrop_with_hole_green from "../../assets/svg/Teardrop_with_hole_green.svelte" | ||||
|   import Triangle from "../../assets/svg/Triangle.svelte" | ||||
|   import Icon from "./Icon.svelte" | ||||
|   import { IconConfig } from "../../Models/ThemeConfig/PointRenderingConfig"; | ||||
|   import { Store } from "../../Logic/UIEventSource"; | ||||
|   import Icon from "./Icon.svelte"; | ||||
| 
 | ||||
|   /** | ||||
|    * Renders a single icon. | ||||
|  |  | |||
|  | @ -8,7 +8,7 @@ | |||
|    * Renders a 'marker', which consists of multiple 'icons' | ||||
|    */ | ||||
|   export let marker: IconConfig[] = config?.marker; | ||||
|   export let rotation: TagRenderingConfig | ||||
|   export let rotation: TagRenderingConfig; | ||||
|   export let tags: Store<Record<string, string>>; | ||||
|   let _rotation = rotation ? tags.map(tags => rotation.GetRenderValue(tags).Subs(tags).txt) : new ImmutableStore(0); | ||||
| </script> | ||||
|  | @ -16,7 +16,9 @@ | |||
| {#if marker && marker} | ||||
|   <div class="relative h-full w-full" style={`transform: rotate(${$_rotation})`}> | ||||
|     {#each marker as icon} | ||||
|       <DynamicIcon {icon} {tags} /> | ||||
|       <div class="absolute top-0 left-0 h-full w-full"> | ||||
|         <DynamicIcon {icon} {tags} /> | ||||
|       </div> | ||||
|     {/each} | ||||
|   </div> | ||||
| {/if} | ||||
|  |  | |||
|  | @ -1,27 +1,29 @@ | |||
| <script lang="ts"> | ||||
|   import Pin from "../../assets/svg/Pin.svelte" | ||||
|   import Square from "../../assets/svg/Square.svelte" | ||||
|   import Circle from "../../assets/svg/Circle.svelte" | ||||
|   import Checkmark from "../../assets/svg/Checkmark.svelte" | ||||
|   import Clock from "../../assets/svg/Clock.svelte" | ||||
|   import Close from "../../assets/svg/Close.svelte" | ||||
|   import Crosshair from "../../assets/svg/Crosshair.svelte" | ||||
|   import Help from "../../assets/svg/Help.svelte" | ||||
|   import Home from "../../assets/svg/Home.svelte" | ||||
|   import Invalid from "../../assets/svg/Invalid.svelte" | ||||
|   import Location from "../../assets/svg/Location.svelte" | ||||
|   import Location_empty from "../../assets/svg/Location_empty.svelte" | ||||
|   import Location_locked from "../../assets/svg/Location_locked.svelte" | ||||
|   import Note from "../../assets/svg/Note.svelte" | ||||
|   import Resolved from "../../assets/svg/Resolved.svelte" | ||||
|   import Ring from "../../assets/svg/Ring.svelte" | ||||
|   import Scissors from "../../assets/svg/Scissors.svelte" | ||||
|   import Teardrop from "../../assets/svg/Teardrop.svelte" | ||||
|   import Teardrop_with_hole_green from "../../assets/svg/Teardrop_with_hole_green.svelte" | ||||
|   import Triangle from "../../assets/svg/Triangle.svelte" | ||||
|   import Pin from "../../assets/svg/Pin.svelte"; | ||||
|   import Square from "../../assets/svg/Square.svelte"; | ||||
|   import Circle from "../../assets/svg/Circle.svelte"; | ||||
|   import Checkmark from "../../assets/svg/Checkmark.svelte"; | ||||
|   import Clock from "../../assets/svg/Clock.svelte"; | ||||
|   import Close from "../../assets/svg/Close.svelte"; | ||||
|   import Crosshair from "../../assets/svg/Crosshair.svelte"; | ||||
|   import Help from "../../assets/svg/Help.svelte"; | ||||
|   import Home from "../../assets/svg/Home.svelte"; | ||||
|   import Invalid from "../../assets/svg/Invalid.svelte"; | ||||
|   import Location from "../../assets/svg/Location.svelte"; | ||||
|   import Location_empty from "../../assets/svg/Location_empty.svelte"; | ||||
|   import Location_locked from "../../assets/svg/Location_locked.svelte"; | ||||
|   import Note from "../../assets/svg/Note.svelte"; | ||||
|   import Resolved from "../../assets/svg/Resolved.svelte"; | ||||
|   import Ring from "../../assets/svg/Ring.svelte"; | ||||
|   import Scissors from "../../assets/svg/Scissors.svelte"; | ||||
|   import Teardrop from "../../assets/svg/Teardrop.svelte"; | ||||
|   import Teardrop_with_hole_green from "../../assets/svg/Teardrop_with_hole_green.svelte"; | ||||
|   import Triangle from "../../assets/svg/Triangle.svelte"; | ||||
|   import Brick_wall_square from "../../assets/svg/Brick_wall_square.svelte"; | ||||
|   import Brick_wall_round from "../../assets/svg/Brick_wall_round.svelte"; | ||||
|   import Gps_arrow from "../../assets/svg/Gps_arrow.svelte"; | ||||
|   import { HeartIcon } from "@babeard/svelte-heroicons/solid"; | ||||
|   import { HeartIcon as HeartOutlineIcon } from "@babeard/svelte-heroicons/outline"; | ||||
| 
 | ||||
|   /** | ||||
|    * Renders a single icon. | ||||
|  | @ -29,68 +31,72 @@ | |||
|    * Icons -placed on top of each other- form a 'Marker' together | ||||
|    */ | ||||
| 
 | ||||
|   export let icon: string | undefined | ||||
|   export let color: string | undefined | ||||
|   export let icon: string | undefined; | ||||
|   export let color: string | undefined; | ||||
|   export let clss: string | undefined | ||||
| </script> | ||||
| 
 | ||||
| {#if icon} | ||||
|   <div class="absolute top-0 left-0 h-full w-full"> | ||||
|     {#if icon === "pin"} | ||||
|       <Pin {color} /> | ||||
|       <Pin {color} class={clss}/> | ||||
|     {:else if icon === "square"} | ||||
|       <Square {color} /> | ||||
|       <Square {color} class={clss}/> | ||||
|     {:else if icon === "circle"} | ||||
|       <Circle {color} /> | ||||
|       <Circle {color} class={clss}/> | ||||
|     {:else if icon === "checkmark"} | ||||
|       <Checkmark {color} /> | ||||
|       <Checkmark {color} class={clss}/> | ||||
|     {:else if icon === "clock"} | ||||
|       <Clock {color} /> | ||||
|       <Clock {color} class={clss}/> | ||||
|     {:else if icon === "close"} | ||||
|       <Close {color} /> | ||||
|       <Close {color} class={clss}/> | ||||
|     {:else if icon === "crosshair"} | ||||
|       <Crosshair {color} /> | ||||
|       <Crosshair {color} class={clss}/> | ||||
|     {:else if icon === "help"} | ||||
|       <Help {color} /> | ||||
|       <Help {color} class={clss}/> | ||||
|     {:else if icon === "home"} | ||||
|       <Home {color} /> | ||||
|       <Home {color} class={clss}/> | ||||
|     {:else if icon === "invalid"} | ||||
|       <Invalid {color} /> | ||||
|       <Invalid {color} class={clss}/> | ||||
|     {:else if icon === "location"} | ||||
|       <Location {color} /> | ||||
|       <Location {color} class={clss}/> | ||||
|     {:else if icon === "location_empty"} | ||||
|       <Location_empty {color} /> | ||||
|       <Location_empty {color} class={clss}/> | ||||
|     {:else if icon === "location_locked"} | ||||
|       <Location_locked {color} /> | ||||
|       <Location_locked {color} class={clss}/> | ||||
|     {:else if icon === "note"} | ||||
|       <Note {color} /> | ||||
|       <Note {color} class={clss}/> | ||||
|     {:else if icon === "resolved"} | ||||
|       <Resolved {color} /> | ||||
|       <Resolved {color} class={clss}/> | ||||
|     {:else if icon === "ring"} | ||||
|       <Ring {color} /> | ||||
|       <Ring {color} class={clss}/> | ||||
|     {:else if icon === "scissors"} | ||||
|       <Scissors {color} /> | ||||
|       <Scissors {color} class={clss}/> | ||||
|     {:else if icon === "teardrop"} | ||||
|       <Teardrop {color} /> | ||||
|       <Teardrop {color} class={clss}/> | ||||
|     {:else if icon === "teardrop_with_hole_green"} | ||||
|       <Teardrop_with_hole_green {color} /> | ||||
|       <Teardrop_with_hole_green {color} class={clss}/> | ||||
|     {:else if icon === "triangle"} | ||||
|       <Triangle {color} /> | ||||
|       <Triangle {color} class={clss}/> | ||||
|     {:else if icon === "brick_wall_square"} | ||||
|       <Brick_wall_square {color} /> | ||||
|       <Brick_wall_square {color} class={clss}/> | ||||
|     {:else if icon === "brick_wall_round"} | ||||
|       <Brick_wall_round {color} /> | ||||
|       <Brick_wall_round {color} class={clss}/> | ||||
|     {:else if icon === "gps_arrow"} | ||||
|       <Gps_arrow {color} /> | ||||
|       <Gps_arrow {color} class={clss}/> | ||||
|     {:else if icon === "checkmark"} | ||||
|       <Checkmark {color} /> | ||||
|       <Checkmark {color} class={clss}/> | ||||
|     {:else if icon === "help"} | ||||
|       <Help {color} /> | ||||
|       <Help {color} class={clss}/> | ||||
|     {:else if icon === "close"} | ||||
|       <Close {color} /> | ||||
|       <Close {color} class={clss}/> | ||||
|     {:else if icon === "invalid"} | ||||
|       <Invalid {color} /> | ||||
|       <Invalid {color} class={clss}/> | ||||
|     {:else if icon === "heart"} | ||||
|       <HeartIcon class={clss}/> | ||||
|     {:else if icon === "heart_outline"} | ||||
|       <HeartOutlineIcon class={clss}/> | ||||
|     {:else} | ||||
|       <img class="h-full w-full" src={icon} /> | ||||
|       <img class={clss ?? "h-full w-full"} src={icon}  aria-hidden="true" | ||||
|            alt="" /> | ||||
|     {/if} | ||||
|   </div> | ||||
| {/if} | ||||
|  |  | |||
|  | @ -1,16 +1,18 @@ | |||
| <script lang="ts"> | ||||
|   import Icon from "./Icon.svelte" | ||||
|   import Icon from "./Icon.svelte"; | ||||
| 
 | ||||
|   /** | ||||
|    * Renders a 'marker', which consists of multiple 'icons' | ||||
|    */ | ||||
|   export let icons: { icon: string; color: string }[] | ||||
|   export let icons: { icon: string; color: string }[]; | ||||
| </script> | ||||
| 
 | ||||
| {#if icons !== undefined && icons.length > 0} | ||||
|   <div class="relative h-full w-full"> | ||||
|     {#each icons as icon} | ||||
|       <Icon icon={icon.icon} color={icon.color} /> | ||||
|       <div class="absolute top-0 left-0 h-full w-full"> | ||||
|         <Icon icon={icon.icon} color={icon.color} /> | ||||
|       </div> | ||||
|     {/each} | ||||
|   </div> | ||||
| {/if} | ||||
|  |  | |||
|  | @ -12,11 +12,9 @@ import { Feature, Point } from "geojson" | |||
| import LineRenderingConfig from "../../Models/ThemeConfig/LineRenderingConfig" | ||||
| import { Utils } from "../../Utils" | ||||
| 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 SimpleFeatureSource from "../../Logic/FeatureSource/Sources/SimpleFeatureSource" | ||||
| import { CLIENT_RENEG_LIMIT } from "tls" | ||||
| 
 | ||||
| class PointRenderingLayer { | ||||
|     private readonly _config: PointRenderingConfig | ||||
|  |  | |||
|  | @ -18,7 +18,7 @@ | |||
|     ...(state?.layoutToUse?.layers?.map((l) => l.calculatedTags?.map((c) => c[0]) ?? []) ?? []) | ||||
|   ) | ||||
| 
 | ||||
|   const allTags = tags.map((tags) => { | ||||
|   const allTags = tags.mapD((tags) => { | ||||
|     const parts: (string | BaseUIElement)[][] = [] | ||||
|     for (const key in tags) { | ||||
|       let v = tags[key] | ||||
|  |  | |||
							
								
								
									
										48
									
								
								src/UI/Popup/MarkAsFavourite.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/UI/Popup/MarkAsFavourite.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,48 @@ | |||
| <script lang="ts"> | ||||
|   import type { SpecialVisualizationState } from "../SpecialVisualization"; | ||||
|   import { HeartIcon as HeartSolidIcon } from "@babeard/svelte-heroicons/solid"; | ||||
|   import { HeartIcon as HeartOutlineIcon } from "@babeard/svelte-heroicons/outline"; | ||||
|   import Tr from "../Base/Tr.svelte"; | ||||
|   import Translations from "../i18n/Translations"; | ||||
|   import LoginToggle from "../Base/LoginToggle.svelte"; | ||||
|   import type { Feature } from "geojson"; | ||||
|   import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; | ||||
| 
 | ||||
|   /** | ||||
|    * A full-blown 'mark as favourite'-button | ||||
|    */ | ||||
|   export let state: SpecialVisualizationState; | ||||
|   export let feature: Feature | ||||
|   export let tags: Record<string, string>; | ||||
|   export let layer: LayerConfig | ||||
|   let isFavourite = tags?.map(tags => tags._favourite === "yes"); | ||||
|   const t = Translations.t.favouritePoi; | ||||
| 
 | ||||
|   function markFavourite(isFavourite: boolean) { | ||||
|     state.favourites.markAsFavourite(feature, layer.id, state.layout.id, tags, isFavourite) | ||||
|   } | ||||
| 
 | ||||
| </script> | ||||
| 
 | ||||
| <LoginToggle ignoreLoading={true} {state}> | ||||
| {#if $isFavourite}   | ||||
|   <div class="flex h-fit items-start"> | ||||
|     <HeartSolidIcon class="w-16 shrink-0 mr-2" on:click={() => markFavourite(false)} /> | ||||
|     <div class="flex flex-col w-full"> | ||||
|       <button class="flex flex-col items-start" on:click={() => markFavourite(false)}> | ||||
|         <Tr t={t.button.unmark} /> | ||||
|         <Tr cls="normal-font subtle" t={t.button.unmarkNotDeleted}/> | ||||
|       </button> | ||||
|     </div> | ||||
|   </div> | ||||
|     <Tr cls="font-bold thanks m-2 p-2 block" t={t.button.isFavourite} /> | ||||
| {:else} | ||||
|   <div class="flex items-start"> | ||||
|     <HeartOutlineIcon class="w-16 shrink-0 mr-2" on:click={() => markFavourite(true)} /> | ||||
|       <button class="flex w-full flex-col items-start" on:click={() => markFavourite(true)}> | ||||
|         <Tr t={t.button.markAsFavouriteTitle} /> | ||||
|         <Tr cls="normal-font subtle" t={t.button.markDescription}/> | ||||
|       </button> | ||||
|   </div> | ||||
| {/if} | ||||
| </LoginToggle> | ||||
							
								
								
									
										36
									
								
								src/UI/Popup/MarkAsFavouriteMini.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/UI/Popup/MarkAsFavouriteMini.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,36 @@ | |||
| <script lang="ts"> | ||||
|   import type { SpecialVisualizationState } from "../SpecialVisualization"; | ||||
|   import { HeartIcon as HeartSolidIcon } from "@babeard/svelte-heroicons/solid"; | ||||
|   import { HeartIcon as HeartOutlineIcon } from "@babeard/svelte-heroicons/outline"; | ||||
|   import Translations from "../i18n/Translations"; | ||||
|   import LoginToggle from "../Base/LoginToggle.svelte"; | ||||
|   import type { Feature } from "geojson"; | ||||
|   import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; | ||||
| 
 | ||||
|   /** | ||||
|    * A small 'mark as favourite'-button to serve as title-icon | ||||
|    */ | ||||
|   export let state: SpecialVisualizationState; | ||||
|   export let feature: Feature; | ||||
|   export let tags: Record<string, string>; | ||||
|   export let layer: LayerConfig; | ||||
|   let isFavourite = tags?.map(tags => tags._favourite === "yes"); | ||||
|   const t = Translations.t.favouritePoi; | ||||
| 
 | ||||
|   function markFavourite(isFavourite: boolean) { | ||||
|     state.favourites.markAsFavourite(feature, layer.id, state.layout.id, tags, isFavourite); | ||||
|   } | ||||
| 
 | ||||
| </script> | ||||
| 
 | ||||
| <LoginToggle ignoreLoading={true} {state}> | ||||
|   {#if $isFavourite} | ||||
|     <button class="p-0 m-0 h-8 w-8" on:click={() => markFavourite(false)}> | ||||
|       <HeartSolidIcon/> | ||||
|     </button> | ||||
|   {:else} | ||||
|     <button class="p-0 m-0 h-8 w-8 no-image-background"  on:click={() => markFavourite(true)} > | ||||
|       <HeartOutlineIcon/> | ||||
|     </button> | ||||
|   {/if} | ||||
| </LoginToggle> | ||||
|  | @ -6,6 +6,7 @@ | |||
|   import { UIEventSource } from "../../../Logic/UIEventSource" | ||||
|   import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" | ||||
|   import { twJoin } from "tailwind-merge" | ||||
|   import Icon from "../../Map/Icon.svelte"; | ||||
| 
 | ||||
|   export let selectedElement: Feature | ||||
|   export let tags: UIEventSource<Record<string, string>> | ||||
|  | @ -28,12 +29,7 @@ | |||
| 
 | ||||
| {#if mapping.icon !== undefined} | ||||
|   <div class="inline-flex"> | ||||
|     <img | ||||
|       class={twJoin(`mapping-icon-${mapping.iconClass}`, "mr-1")} | ||||
|       src={mapping.icon} | ||||
|       aria-hidden="true" | ||||
|       alt="" | ||||
|     /> | ||||
|     <Icon icon={mapping.icon} clss={twJoin(`mapping-icon-${mapping.iconClass}`, "mr-1")}/> | ||||
|     <SpecialTranslation t={mapping.then} {tags} {state} {layer} feature={selectedElement} /> | ||||
|   </div> | ||||
| {:else if mapping.then !== undefined} | ||||
|  |  | |||
|  | @ -17,6 +17,7 @@ import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader" | |||
| import { RasterLayerPolygon } from "../Models/RasterLayers" | ||||
| import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager" | ||||
| import { OsmTags } from "../Models/OsmFeature" | ||||
| import FavouritesFeatureSource from "../Logic/FeatureSource/Sources/FavouritesFeatureSource" | ||||
| 
 | ||||
| /** | ||||
|  * The state needed to render a special Visualisation. | ||||
|  | @ -33,7 +34,6 @@ export interface SpecialVisualizationState { | |||
|     } | ||||
| 
 | ||||
|     readonly indexedFeatures: IndexedFeatureSource | ||||
| 
 | ||||
|     /** | ||||
|      * Some features will create a new element that should be displayed. | ||||
|      * These can be injected by appending them to this featuresource (and pinging it) | ||||
|  | @ -59,6 +59,8 @@ export interface SpecialVisualizationState { | |||
|     readonly selectedLayer: UIEventSource<LayerConfig> | ||||
|     readonly selectedElementAndLayer: Store<{ feature: Feature; layer: LayerConfig }> | ||||
| 
 | ||||
|     readonly favourites: FavouritesFeatureSource | ||||
| 
 | ||||
|     /** | ||||
|      * If data is currently being fetched from external sources | ||||
|      */ | ||||
|  |  | |||
|  | @ -79,6 +79,8 @@ import ThemeViewState from "../Models/ThemeViewState" | |||
| import LanguagePicker from "./InputElement/LanguagePicker.svelte" | ||||
| import LogoutButton from "./Base/LogoutButton.svelte" | ||||
| import OpenJosm from "./Base/OpenJosm.svelte" | ||||
| import MarkAsFavourite from "./Popup/MarkAsFavourite.svelte" | ||||
| import MarkAsFavouriteMini from "./Popup/MarkAsFavouriteMini.svelte" | ||||
| 
 | ||||
| class NearbyImageVis implements SpecialVisualization { | ||||
|     // Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
 | ||||
|  | @ -1481,7 +1483,7 @@ export default class SpecialVisualizations { | |||
|                     const tags = (<ThemeViewState>( | ||||
|                         state | ||||
|                     )).geolocation.currentUserLocation.features.map( | ||||
|                         (features) => features[0].properties | ||||
|                         (features) => features[0]?.properties | ||||
|                     ) | ||||
|                     return new SvelteUIElement(AllTagsPanel, { | ||||
|                         state, | ||||
|  | @ -1489,6 +1491,46 @@ export default class SpecialVisualizations { | |||
|                     }) | ||||
|                 }, | ||||
|             }, | ||||
|             { | ||||
|                 funcName: "favourite_status", | ||||
|                 needsUrls: [], | ||||
|                 docs: "A button that allows a (logged in) contributor to mark a location as a favourite location", | ||||
|                 args: [], | ||||
|                 constr( | ||||
|                     state: SpecialVisualizationState, | ||||
|                     tagSource: UIEventSource<Record<string, string>>, | ||||
|                     argument: string[], | ||||
|                     feature: Feature, | ||||
|                     layer: LayerConfig | ||||
|                 ): BaseUIElement { | ||||
|                     return new SvelteUIElement(MarkAsFavourite, { | ||||
|                         tags: tagSource, | ||||
|                         state, | ||||
|                         layer, | ||||
|                         feature, | ||||
|                     }) | ||||
|                 }, | ||||
|             }, | ||||
|             { | ||||
|                 funcName: "favourite_icon", | ||||
|                 needsUrls: [], | ||||
|                 docs: "A small button that allows a (logged in) contributor to mark a location as a favourite location, sized to fit a title-icon", | ||||
|                 args: [], | ||||
|                 constr( | ||||
|                     state: SpecialVisualizationState, | ||||
|                     tagSource: UIEventSource<Record<string, string>>, | ||||
|                     argument: string[], | ||||
|                     feature: Feature, | ||||
|                     layer: LayerConfig | ||||
|                 ): BaseUIElement { | ||||
|                     return new SvelteUIElement(MarkAsFavouriteMini, { | ||||
|                         tags: tagSource, | ||||
|                         state, | ||||
|                         layer, | ||||
|                         feature, | ||||
|                     }) | ||||
|                 }, | ||||
|             }, | ||||
|         ] | ||||
| 
 | ||||
|         specialVisualizations.push(new AutoApplyButton(specialVisualizations)) | ||||
|  |  | |||
|  | @ -1,11 +1,16 @@ | |||
| <script lang="ts"> | ||||
|   // Testing grounds | ||||
|   import LanguagePicker from "./InputElement/LanguagePicker.svelte"; | ||||
|   import Translations from "./i18n/Translations"; | ||||
|   import Tr from "./Base/Tr.svelte"; | ||||
|   import Locale from "./i18n/Locale"; | ||||
|   import MarkAsFavourite from "./Popup/MarkAsFavourite.svelte"; | ||||
|   let language = Locale.language | ||||
|   import { UIEventSource } from "../Logic/UIEventSource"; | ||||
|   import { OsmConnection } from "../Logic/Osm/OsmConnection"; | ||||
|    | ||||
|   const osmConnection = new OsmConnection({attemptLogin: true}) | ||||
|   function resetFavs(){ | ||||
|     osmConnection.preferencesHandler.removeAllWithPrefix("mapcomplete-favourite-") | ||||
|     console.log("CLEARED!") | ||||
|   } | ||||
|    | ||||
| </script> | ||||
| 
 | ||||
| <MarkAsFavourite/> | ||||
| 
 | ||||
| <button on:click={() => resetFavs()} >Clear</button> | ||||
|  |  | |||
|  | @ -76,7 +76,6 @@ | |||
|   let showCrosshair = state.userRelatedState.showCrosshair; | ||||
|   let arrowKeysWereUsed = state.mapProperties.lastKeyNavigation; | ||||
|   let centerFeatures = state.closestFeatures.features; | ||||
|   $: console.log("Centerfeatures are", $centerFeatures) | ||||
|   const selectedElementView = selectedElement.map( | ||||
|     (selectedElement) => { | ||||
|       // Svelte doesn't properly reload some of the legacy UI-elements | ||||
|  | @ -232,7 +231,7 @@ | |||
|       </div> | ||||
|     </div> | ||||
| 
 | ||||
|     {#if $arrowKeysWereUsed !== undefined} | ||||
|     {#if $arrowKeysWereUsed !== undefined && $centerFeatures?.length > 0} | ||||
|       <div class="pointer-events-auto interactive p-1"> | ||||
|         {#each $centerFeatures as feat, i (feat.properties.id)} | ||||
|           <div class="flex"> | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue